The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Getopt::App - Write and test your script with ease

SYNOPSIS

The script file

#!/usr/bin/env perl
package My::Script;
use Getopt::App -signatures;

# See "APPLICATION METHODS"
sub getopt_post_process_argv ($app, $argv, $state) { ... }
sub getopt_configure ($app) { ... }

# run() must be the last statement in the script
run(

  # Specify your Getopt::Long options and optionally a help text
  'h|help # Output help',
  'v+     # Verbose output',
  'name=s # Specify a name',

  # Here is the main sub that will run the script
  sub ($app, @extra) {
    return print extract_usage() if $app->{h};
    say $app->{name} // 'no name'; # access command line options
    return 42; # Reture value is used as exit code
  }
);

Running the script

The example script above can be run like any other script:

$ my-script --name superwoman; # prints "superwoman"
$ echo $? # 42

Testing

use Test::More;
use Cwd qw(abs_path);
use Getopt::App -capture;

# Sourcing the script returns a callback
my $app = do(abs_path('./bin/myapp'));

# The callback can be called with any @ARGV
subtest name => sub {
  my $got = capture($app, [qw(--name superwoman)]);
  is $got->[0], "superwoman\n", 'stdout';
  is $got->[1], '', 'stderr';
  is $got->[2], 42, 'exit value';
};

done_testing;

DESCRIPTION

Getopt::App is a module that helps you structure your scripts and integrates Getopt::Long with a very simple API. In addition it makes it very easy to test your script, since the script file can be sourced without actually being run.

Getopt::App also supports infinite nested subcommands and a method for bundling this module with your script to prevent depending on a module from CPAN.

APPLICATION METHODS

These methods are optional, but can be defined in your script to override the default behavior.

getopt_configure

@configure = $app->getopt_configure;

This method can be defined if you want "Configure" in Getopt::Long to be set up differently. The default return value is:

qw(bundling no_auto_abbrev no_ignore_case pass_through require_order)

The default return value is currently EXPERIMENTAL.

getopt_load_subcommand

$code = $app->getopt_subcommand($subcommand, [@ARGV]);

Takes the subcommand found in the "getopt_subcommands" list and the command line arguments and must return a CODE block. The default implementation is simply:

$code = do($subcommand->[1]);

getopt_post_process_argv

$bool = $app->getopt_post_process_argv([@ARGV], {%state});

This method can be used to post process the options. %state contains a key "valid" which is true or false, depending on the return value from "GetOptionsFromArray" in Getopt::Long.

This method can die and optionally set $! to avoid calling the function passed to "run".

The default behavior is to check if the first item in $argv starts with a hyphen, and die with an error message if so:

Invalid argument or argument order: @$argv\n

getopt_post_process_exit_value

$exit_value = $app->getopt_post_process_exit_value($exit_value);

A method to be called after the "run" function has been called. $exit_value holds the return value from "run" which could be any value, not just 0-255. This value can then be changed to change the exit value from the program.

sub getopt_post_process_exit_value ($app, $exit_value) {
  return int(1 + rand 10);
}

getopt_pre_process_argv

$app->getopt_pre_process_argv($argv);

This method can be defined to pre-process $argv before it is passed on to "GetOptionsFromArray" in Getopt::Long. Example:

sub getopt_pre_process_argv ($app, $argv) {
  $app->{first_non_option} = shift @$argv if @$argv and $argv->[0] =~ m!^[a-z]!;
}

This method can die and optionally set $! to avoid calling the actual "run" function.

getopt_subcommands

$subcommands = $app->getopt_subcommands;

This method must be defined in the script to enable sub commands. The return value must be either undef to disable subcommands or an array-ref of array-refs like this:

[["subname", "/abs/path/to/sub-command-script", "help text"], ...]

The first element in each array-ref "subname" will be matched against the first argument passed to the script, and when matched the "sub-command-script" will be sourced and run inside the same perl process. The sub command script must also use Getopt::App for this to work properly.

See https://github.com/jhthorsen/getopt-app/tree/main/example for a working example.

getopt_unknown_subcommand

$exit_value = $app->getopt_unknown_subcommand($argv);

Will be called when "getopt_subcommands" is defined but $argv does not match an item in the list. Default behavior is to die with an error message:

Unknown subcommand: $argv->[0]\n

Returning undef instead of dieing or a number (0-255) will cause the "run" callback to be called.

EXPORTED FUNCTIONS

capture

use Getopt::App -capture;
my $app = do '/path/to/bin/myapp';
my $array_ref = capture($app, [@ARGV]); # [$stdout, $stderr, $exit_value]

Used to run an $app and capture STDOUT, STDERR and the exit value in that order in $array_ref. This function will also capture die. $@ will be set and captured in the second $array_ref element, and $exit_value will be set to $!.

extract_usage

# Default to "SYNOPSIS" from current file
my $str = extract_usage($section, $file);
my $str = extract_usage($section);
my $str = extract_usage();

Will extract a $section from POD $file and append command line option descriptions when called from inside of "run". Command line options can optionally have a description with "spaces-hash-spaces-description", like this:

run(
  'o|option  # Some description',
  'v|verbose # Enable verbose output',
  sub {
    ...
  },
);

This function will not be exported if a function with the same name already exists in the script.

new

my $obj = new($class, %args);
my $obj = new($class, \%args);

This function is exported into the caller package so we can construct a new object:

my $app = Application::Class->new(\%args);

This function will not be exported if a function with the same name already exists in the script.

run

# Run a code block on valid @ARGV
run(@rules, sub ($app, @extra) { ... });

# For testing
my $cb = run(@rules, sub ($app, @extra) { ... });
my $exit_value = $cb->([@ARGV]);

"run" can be used to call a callback when valid command line options is provided. On invalid arguments, warnings will be issued and the program exit with $? set to 1.

$app inside the callback is a hash blessed to the caller package. The keys in the hash are the parsed command line options, while @extra is the extra unparsed command line options.

@rules are the same options as Getopt::Long can take. Example:

# app.pl -vv --name superwoman -o OptX cool beans
run(qw(h|help v+ name=s o=s@), sub ($app, @extra) {
  die "No help here" if $app->{h};
  warn $app->{v};    # 2
  warn $app->{name}; # "superwoman"
  warn @{$app->{o}}; # "OptX"
  warn @extra;       # "cool beans"
  return 0;          # Used as exit code
});

In the example above, @extra gets populated, since there is a non-flag value "cool" after a list of valid command line options.

METHODS

bundle

Getopt::App->bundle($path_to_script);
Getopt::App->bundle($path_to_script, $fh);

This method can be used to combine Getopt::App and $path_to_script into a a single script that does not need to have Getopt::App installed from CPAN. This is for example useful for sysadmin scripts that otherwize only depends on core Perl modules.

The script will be printed to $fh, which defaults to STDOUT.

Example usage:

perl -MGetopt::App -e'Getopt::App->bundle(shift)' ./src/my-script.pl > ./bin/my-script;

import

use Getopt::App;
use Getopt::App 'My::Script::Base', -signatures;
use Getopt::App -capture;
  • Default

    use Getopt::App;

    Passing in no flags will export the default functions "extract_usage", "new" and "run". In addition it will save you from a lot of typing, since it will also import the following:

    use strict;
    use warnings;
    use utf8;
    use feature ':5.16';
  • Signatures

    use Getopt::App -signatures;

    Same as "Default", but will also import "signatures" in experimental. This requires Perl 5.20+.

  • Class name

    package My::Script::Foo;
    use Getopt::App 'My::Script';

    Same as "Default" but will also make My::Script::Foo inherit from My::Script. Note that a package definition is required.

  • Capture

    use Getopt::App -capture;

    This will only export "capture".

COPYRIGHT AND LICENSE

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

AUTHOR

Jan Henning Thorsen - jhthorsen@cpan.org