Getopt::Yath::Tutorial(3)

NAME
Getopt::Yath::Tutorial - A step-by-step guide to using Getopt::Yath

DESCRIPTION
This tutorial walks you through using Getopt::Yath to handle command-line options in your Perl scripts and modules. It starts with simple examples and builds up to advanced features.

YOUR FIRST SCRIPT
Here is the simplest useful script with Getopt::Yath:

    #!/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";

Run it:

    $ perl hello.pl --name Bob
    Hello, Bob!

    $ perl hello.pl
    Hello, World!

  What just happened?
"use Getopt::Yath" exports several functions into your package:

option
    Define a single command-line option with a name and specification.

option_group
    Set shared attributes (group, category, prefix, etc.) for a block of options.

parse_options
    Process an arrayref of command-line arguments and return a state hashref with settings, remaining args, skipped items, and more.

include_options
    Import option definitions from another module that also uses Getopt::Yath.

option_post_process
    Register a callback to run after all options have been parsed, for cross-option validation or fixups.

options
    Return the Getopt::Yath::Instance object that holds all defined options. Used for generating help and documentation output.

category_sort_map
    Control the display order of option categories in help output.

Every option needs a group (the key under which it appears in settings) and a type (how it parses arguments). The category is a human-readable label used for help output.

OPTION GROUPS
"option_group" lets you set shared attributes for a block of options so you don't have to repeat yourself:

    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 this block inherits "group => 'server'" and "category => 'Server Options'". The parsed values are accessed via "$settings->server->host", "$settings->server->port", etc.

OPTION TYPES
Getopt::Yath provides a rich set of option types. Here they are from simplest to most specialized.

  Bool
A simple on/off flag. No argument needed.

    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.

    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. Each use bumps the value by one.

    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. Can be specified multiple times.

    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 other delimiter) 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, and "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 provided value. Does not support "--opt val" (the space-separated form), because without a required argument the next token is ambiguous.

    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 are expanded using glob().

    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.

    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 is embedded into "qr/^--(no-)?$pattern$/" automatically. Each captured key gets a true or false value depending on the "--no-" prefix.

COMMON OPTION ATTRIBUTES
These attributes work across all option types.

  short
A single-character alias for the option:

    option jobs => (
        type  => 'Scalar',
        short => 'j',
        ...
    );

    # -j4   --jobs 4   --jobs=4   -j 4

  alt and alt_no
Alternate long names for the 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. Especially useful in 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.

  field and name
By default, the option title determines both the field name (underscores) and the CLI name (dashes). You can override either:

    option test_args => (
        type  => 'List',
        field => 'args',        # $settings->tests->args (not test_args)
        alt   => ['test-arg'],
        ...
    );

  default
A value used when the option is not provided on the command line and not 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
Set an initial value before parsing begins. This differs from "default" in that "default" is applied after parsing if the option was never set, while "initialize" sets a starting value that can then be modified by command-line arguments.

    option verbosity => (
        type       => 'Count',
        initialize => 0,
        ...
    );

ENVIRONMENT VARIABLES

  from_env_vars
Read an option's initial value from environment variables. The first one that is defined wins:

    option verbose => (
        type          => 'Bool',
        from_env_vars => ['MYAPP_VERBOSE', 'VERBOSE'],
        description   => 'Enable verbose output',
    );

Prefix a variable with "!" to negate its value:

    option quiet => (
        type          => 'Bool',
        from_env_vars => ['!VERBOSE'],     # quiet = true when VERBOSE is false
        description   => 'Suppress output',
    );

  set_env_vars
Set environment variables after parsing is complete (Bool, Scalar, Count, and Auto types only):

    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 also works here for inverted env vars.

  clear_env_vars
Clear 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 is parsed:

    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 is called on each value individually.

  trigger
A callback invoked whenever the option is set or cleared from the command line. Triggers do not fire for defaults, autofill, or initialization.

    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:

    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 customize the help message:

    allowed_values_text => 'json, csv, or xml',

PARSING OPTIONS
The "parse_options" function is how you process command-line arguments. It returns a state hashref with all the parsed data.

  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. This is 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']

You can define multiple stop tokens. "::" is commonly used as a separator for 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, encountering a non-option (something not starting with "-") throws an error. 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
Similarly, you can control what happens with unrecognized 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
Prevent parse_options from modifying %ENV:

    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. This is 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 (which end up in "skipped" when "skip_non_opts" is enabled).

GENERATING HELP OUTPUT

  CLI help
    sub show_help {
        print options()->docs('cli');
    }

This produces formatted terminal output with ANSI colors (when available), organized by category.

  POD documentation
    sub show_pod {
        print options()->docs('pod', head => 2);
    }

This produces POD markup suitable for embedding in your module's documentation. The "head" parameter controls the heading level (e.g., "=head2").

  Controlling category order
By default, categories are sorted alphabetically. Use "category_sort_map" to control the order:

    category_sort_map(
        'General Options' => 1,
        'Server Options'  => 2,
        'Debug Options'   => 3,
    );

Lower values appear first.

REUSABLE OPTION LIBRARIES
You can define options in a module and include them elsewhere. This lets you build composable option sets.

  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
You can include only specific options from a library:

    include_options(
        'My::Options::Database' => [qw/host port/],
    );

Only the "host" and "port" options will be included; "name" will be excluded.

POST-PROCESSING
"option_post_process" registers a callback that runs after all options have been parsed but before environment variables are set. This is useful for validation that depends on multiple options:

    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):

    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 runs:

    option_post_process 0 => sub { ... if server group exists ... } => sub {
        my ($instance, $state) = @_;
        ...
    };

If used inside an "option_group" block, the group's "applicable" is inherited automatically.

DYNAMIC OPTIONS WITH mod_adds_options
Some options specify a module name whose options should be dynamically loaded when the option is used:

    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 allows plugins and extensions to contribute their own command-line options.

APPLICABILITY
Options can be conditionally shown/hidden based on runtime state:

    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.

THE SETTINGS OBJECT
The "$state->settings" value is a Getopt::Yath::Settings object. Groups are accessed as methods:

    $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:

    #!/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
Many tools follow a "script [GLOBAL OPTIONS] subcommand [COMMAND OPTIONS]" pattern, where the script has its own set of options, a subcommand name, and then the subcommand has its own options. Getopt::Yath supports this through multi-stage parsing: parse the global options first (stopping at the subcommand), then load the subcommand and parse its options from the remaining arguments.

  Step 1: Define global options
Create a package for your script-level options.

    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
Each subcommand is a separate package with its own options:

    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
Parse global options first, stopping at the subcommand name. Then load the subcommand module, include its options, and parse the remaining arguments using the combined option set. Pass the settings object through so both stages share state.

    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
    $ 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)

The key techniques are:

•   stop_at_non_opts in Stage 1 stops parsing at the subcommand name, placing it in "$state->stop" and the rest in "$state->remains".

•   include_options merges the subcommand's options into the script's option instance before Stage 2.

•   settings is 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/>