NAME
Getopt::Class - Extended dictionary version of Getopt::Long
SYNOPSIS
use Getopt::Class;
our $DEBUG = 0;
our $VERBOSE = 0;
our $VERSION = '0.1';
my $dict =
{
create_user => { type => 'boolean', alias => [qw(create_person create_customer)], action => 1 },
create_product => { type => 'boolean', action => 1 },
debug => { type => 'integer', default => \$DEBUG },
# Can be enabled with --enable-recurse
disable_recurse => { type => 'boolean', default => 1 },
# Can be disabled also with --disable-logging
enable_logging => { type => 'boolean', default => 0 },
help => { type => 'code', code => sub{ pod2usage(1); }, alias => '?', action => 1 },
man => { type => 'code', code => sub{ pod2usage( -exitstatus => 0, -verbose => 2 ); }, action => 1 },
quiet => { type => 'boolean', default => 0, alias => 'silent' },
verbose => { type => 'boolean', default => \$VERBOSE, alias => 'v' },
version => { type => 'code', code => sub{ printf( "v%.2f\n", $VERSION ); }, action => 1 },
api_server => { type => 'string', default => 'api.example.com' },
api_version => { type => 'string', default => 1 },
as_admin => { type => 'boolean' },
dry_run => { type => 'boolean', default => 0 },
# Can be enabled also with --with-zlib
without_zlib => { type => 'integer', default => 1 },
name => { type => 'string', class => [qw( person product )] },
created => { type => 'datetime', class => [qw( person product )] },
define => { type => 'string-hash', default => {} },
langs => { type => 'array', class => [qw( person product )], re => qr/^[a-z]{2}([_|-][A-Z]{2})?/, min => 1, default => [qw(en)] },
currency => { type => 'string', class => [qw(product)], name => 'currency', re => qr/^[a-z]{3}$/, error => "must be a three-letter iso 4217 value" },
age => { type => 'integer', class => [qw(person)], name => 'age', },
path => { type => 'file' },
skip => { type => 'file-array' },
url => { type => 'uri', package => 'URI' },
urls => { type => 'uri-array', package => 'URI::Fast' },
};
# Assuming command line arguments like:
prog.pl --create-user --name Bob --langs fr ja --age 30 --created now --debug 3 \
--path ./here/some/where --skip ./bad/directory ./not/here ./avoid/me/
my $opt = Getopt::Class->new({
dictionary => $dict,
}) || die( Getopt::Class->error, "\n" );
my $opts = $opt->exec || die( $opt->error, "\n" );
$opt->required( [qw( name langs )] );
my $err = $opt->check_class_data( 'person' );
printf( "User is %s and is %d years old\n", $opts{qw( name age )} ) if( $opts->{debug} );
# Get all the properties for class person
my $props = $opt->class_properties( 'person' );
# Get values collected for class 'person'
if( $opts->{create_user} )
{
my $values = $opt->get_class_values( 'person' );
# Having collected the values for our class of properties, and making sure all
# required are here, we can add them to database or make api calls, etc
}
elsif( $opts->{create_product} )
{
# etc...
}
# Or you can also access those values as object methods
if( $opts->create_product )
{
$opts->langs->push( 'en_GB' ) if( !$opts->langs->length );
printf( "Created on %s\n", $opts->created->iso8601 );
}
VERSION
v1.1.3
DESCRIPTION
Getopt::Class is a lightweight wrapper around Getopt::Long that implements the idea of class of properties and makes it easier and powerful to set up Getopt::Long. This module is particularly useful if you want to provide several sets of options for different features or functions of your program. For example, you may have a part of your program that deals with user while another deals with product. Each of them needs their own properties to be provided.
CONSTRUCTOR
new
To instantiate a new Getopt::Class object, pass an hash reference of following parameters:
dictionary
This is required. It must contain a key value pair where the value is an anonymous hash reference that can contain the following parameters:
alias
This is an array reference of alternative options that can be used in an interchangeable way
my $dict = { last_name => { type => 'string', alias => [qw( family_name surname )] }, }; # would make it possible to use either of the following combinations --last-name Doe # or --surname Doe # or --family-name Doe
default
This contains the default value. For a string, this could be anything, and also a reference to a scalar, such as:
our $DEBUG = 0; my $dict = { debug => { type => 'integer', default => \$DEBUG }, };
It can also be used to provide default value for an array, such as:
my $dict = { langs => { type => 'array', class => [qw( person product )], re => qr/^[a-z]{2}([_|-][A-Z]{2})?/, min => 1, default => [qw(en)] }, };
But beware that if you provide a value, it will not superseed the existing default value, but add it on top of it, so
--langs en fr ja
would not produce an array with
en
,fr
andja
entries, but an array such as:['en', 'en', 'fr', 'ja' ]
because the initial default value is not replaced when one is provided. This is a design from Getopt::Long and although I could circumvent this, I a not sure I should.
error
A string to be used to set an error by "check_class_data". Typically the string should provide meaningful information as to what the data should normally be. For example:
my $dict = { currency => { type => 'string', class => [qw(product)], name => 'currency', re => qr/^[a-z]{3}$/, error => "must be a three-letter iso 4217 value" }, };
max
This is well explained in "Options with multiple values" in Getopt::Long
It serves "to specify the minimal and maximal number of arguments an option takes".
min
Same as above
re
This must be a regular expression and is used by "check_class_data" to check the sanity of the data provided by the user. So, for example:
my $dict = { currency => { type => 'string', class => [qw(product)], name => 'currency', re => qr/^[a-z]{3}$/, error => "must be a three-letter iso 4217 value" }, };
then the user calls your program with, among other options:
--currency euro
would set an error that can be retrieved as an output of "check_class_data"
required
Set this to true or false (1 or 0) to instruct "check_class_data" whether to check if it is missing or not.
This is an alternative to the "required" method which is used at an earlier stage, during "exec"
type
Supported types are:
array
This type will set the resulting value to be a Module::Generic::Array object of values provided.
boolean
If type is
boolean
and the key is eitherwith
,without
,enable
,disable
, their counterpart will automatically be available as well, such as you can do, as show in the excerpt in the synopsis above:--enable-recurse --with-zlib
Be careful though. If, in your dictionary, as shown in the synopsis, you defined
without_zlib
with a default value of true, then using the option--with-zlib
will set that value to false. So in your application, you would need to check like this:if( $opts->{without_zlib} ) { # Do something } else { # Do something else }
code
Type code implies an anonymous sub routine and should be accompanied with the attribute code, such as:
{ type => 'code', code => sub{ pod2usage(1); exit( 0 ) }, alias => '?', action => 1 },
datetime
This type will set the resulting value to be a DateTime object of the value provided.
decimal
This type will set the resulting value to be a Module::Generic::Number object of the value provided.
file
This type will mark the value as a directory or file path and will become a Module::Generic::File object.
This is particularly convenient when the user provided you with a relative path, such as:
./my_prog.pl --debug 3 --path ./here/
And if you are not very careful and inadvertently change directory like when using File::Find, then this relative path could lead to some unpleasant surprise.
Setting this argument type to
file
ensure the resulting value is a Module::Generic::File, whose underlying file or directory will be resolved to their absolute path.file-array
Same as
file
argument type, but allows multiple value saved as an array. For example:./my_prog.pl --skip ./not/here ./avoid/me/ ./skip/this/directory
This would result in the option property
skip
being an array object containing 3 entries.hash
Type
hash
is convenient for free key-value pair such as:--define customer_id=10 --define transaction_id 123
would result for
define
with an anonymous hash as value containingcustomer_id
with value10
andtransaction_id
with value123
integer
This type will set the resulting value to be a Module::Generic::Number object of the value provided.
scalar
This type will set the resulting value to be a Module::Generic::Scalar object of the value provided.
string
Same as
scalar
. This type will set the resulting value to be a Module::Generic::Scalar object of the value provided.string-hash
This type will set the resulting value to be an Module::Generic::Hash object.
It enables the passing of <key=value> pairs as documented in "Options with hash values" in Getopt::Long
So you coul do something like:
use Getopt::Class; my $dict = { help => { type => 'code', code => sub{ pod2usage(1); }, alias => '?', action => 1 }, man => { type => 'code', code => sub{ pod2usage( -exitstatus => 0, -verbose => 2 ); }, action => 1 }, quiet => { type => 'boolean', default => 0, alias => 'silent' }, verbose => { type => 'boolean', default => \$VERBOSE, alias => 'v' }, version => { type => 'code', code => sub{ printf( "v%.2f\n", $VERSION ); }, action => 1 }, dry_run => { type => 'boolean', default => 0 }, name => { type => 'string', class => [qw( person product )] }, created => { type => 'datetime', class => [qw( person product )] }, define => { type => 'string-hash', default => {} }, }; # Assuming command line arguments like: prog.pl --create-user --name Bob --langs fr ja --age 30 --created now --debug 3 \ --path ./here/some/where --skip ./bad/directory ./not/here ./avoid/me/ my $opt = Getopt::Class->new({ dictionary => $dict, }) || die( Getopt::Class->error, "\n" ); my $opts = $opt->exec || die( $opt->error, "\n" ); # etc...
And then, call your script like:
./my_script.pl --define os=linux --define vendor=ubuntu
And, then you would have an hash reference of two keys and their value:
os
andvendor
available in$opts->{define}
uri
This type will mark the value as a directory or file path and will become a URI object, by default.
You can override this default pacage, by using
package
property, such as:url => { type => 'uri', package => 'URI' }
uri-array
Same as
uri
argument type, but allows multiple value saved as an array. For example:./my_prog.pl --uris https://example.com/some/where https://example.com/some/where/else
This would result in the option property
uris
being an array object containing 2 entries.
Also as seen in the example above, you can add additional properties to be used in your program, here such as
action
that could be used to identify all options that are used to trigger an action or a call to a sub routine.
debug
This takes an integer, and is used to set the level of debugging. Anything under 3 will not provide anything meaningful.
METHODS
check_class_data
Provided with a string corresponding to a class name, this will check the data provided by the user.
Currently this means it checks if the data is present when the attribute required is set, and it checks the data against a regular expression if one is provided with the attribute re
It returns an hash reference with 2 keys: missing and regexp. Each with an anonymous hash reference with key matching the option name and the value the error string. So:
my $dict =
{
name => { type => 'string', class => [qw( person product )], required => 1 },
langs => { type => 'array', class => [qw( person product )], re => qr/^[a-z]{2}([_|-][A-Z]{2})?/, min => 1, default => [qw(en)] },
};
Assuming your user calls your program without --name
and with --langs FR EN
this would have "check_class_data" return the following data structure:
$errors =
{
missing => { name => "name (name) is missing" },
regexp => { langs => "langs (langs) does not match requirements" },
};
class
Provided with a string representing a property class, and this returns an hash reference of all the dictionary entries matching this class
classes
This returns an hash reference containing class names, each of which has an anonymous hash reference with corresponding dictionary entries
class_properties
Provided with a string representing a class name, this returns an array reference of options, a.k.a. class properties.
The array reference is a Module::Generic::Array object.
configure
This calls "configure" in Getopt::Long with the "configure_options".
It can be overriden by calling "configure" with an array reference.
If there is an error, it will return undef and set an "error" accordingly.
Otherwise, it returns the Getopt::Class object, so it can be chained.
configure_errors
This returns an array reference of the errors generated by Getopt::Long upon calling "getoptions" in Getopt::Long by "exec"
The array is an Module::Generic::Array object
configure_options
This returns an array reference of the Getopt::Long configuration options upon calling "configure" in Getopt::Long by method "configure"
The array is an Module::Generic::Array object
dictionary
This returns the hash reference representing the dictionary set when the object was instantiated. See "new" method.
error
Return the last error set as a Module::Generic::Exception object. Because the object can be stringified, you can do directly:
die( $opt->error, "\n" ); # with a stack trace
or
die( sprintf( "Error occurred at line %d in file %s with message %s\n", $opt->error->line, $opt->error->file, $opt->error->message ) );
exec
This calls "getoptions" in Getopt::Long with the "options" hash reference and the "parameters" array reference and after having called "configure" to configure Getopt::Long with the proper parameters according to the dictionary provided at the time of object instantiation.
If there are any Getopt::Long error, they can be retrieved with method "configure_errors"
my $opt = Getopt::Class->new({ dictionary => $dict }) || die( Getopt::Class->error );
my $opts = $opt->exec || die( $opt->error );
if( $opt->configure_errors->length > 0 )
{
# do something about it
}
If any required options have been specified with the method "required", it will check any missing option then and set an array of those missing options that can be retrieved with method "missing"
This method makes sure that any option can be accessed with underscore or dash whichever, so a dictionary entry such as:
my $dict =
{
create_customer => { type => 'boolean', alias => [qw(create_client create_user)], action => 1 },
};
can be called by your user like:
---create-customer
# or
--create-client
# or
--create-user
because a duplicate entry with the underscore replaced by a dash is created (actually it's an alias of one to another). So you can say in your program:
my $opts = $opt->exec || die( $opt->error );
if( $opts->{create_user} )
{
# do something
}
"exec" returns an hash reference whose properties can be accessed directly, but those properties can also be accessed as methods.
This is made possible because the hash reference returned is a blessed object from Getopt::Class::Values and provides an object oriented access to all the option values.
A string is an object from Module::Generic::Scalar
$opts->customer_name->index( 'Doe' ) != -1
A boolean is an object from Module::Generic::Boolean
An integer or decimal is an object from Text::Number
A date/dateime value is an object from DateTime
$opts->created->iso8601 # 2020-05-01T17:10:20
An hash reference is an object created with "_set_get_hash_as_object" in Module::Generic
$opts->metadata->transaction_id
An array reference is an object created with "_set_get_array_as_object" in Module::Generic
$opts->langs->push( 'en_GB' ) if( !$opts->langs->exists( 'en_GB' ) );
$opts->langs->forEach(sub{
$self->active_user_lang( shift( @_ ) );
});
Whatever the object type of the option value is based on the dictionary definitions you provide to "new"
get_class_values
Provided with a string representing a property class, and this returns an hash reference of all the key-value pairs provided by your user. So:
my $dict =
{
create_customer => { type => 'boolean', alias => [qw(create_client create_user)], action => 1 },
name => { type => 'string', class => [qw( person product )] },
created => { type => 'datetime', class => [qw( person product )] },
define => { type => 'string-hash', default => {} },
langs => { type => 'array', class => [qw( person product )], re => qr/^[a-z]{2}([_|-][A-Z]{2})?/, min => 1, default => [] },
currency => { type => 'string', class => [qw(product)], name => 'currency', re => qr/^[a-z]{3}$/, error => "must be a three-letter iso 4217 value" },
age => { type => 'integer', class => [qw(person)], name => 'age', },
};
Then the user calls your program with:
--create-user --name Bob --age 30 --langs en ja --created now
# In your app
my $opt = Getopt::Class->new({ dictionary => $dict }) || die( Getopt::Class->error );
my $opts = $opt->exec || die( $opt->error );
# $vals being an hash reference as a subset of all the values returned in $opts above
my $vals = $opt->get_class_values( 'person' )
# returns an hash only with keys name, age, langs and created
getopt
Sets or get the Getopt::Long::Parser object. You can provide yours if you want but beware that certain options are necessary for Getopt::Class to work. You can check those options with the method "configure_options"
missing
Returns an array of missing options. The array reference returned is a Module::Generic::Array object, so you can do thins like
if( $opt->missing->length > 0 )
{
# do something
}
options
Returns an hash reference of options created by "new" based on the dictionary you provide. This hash reference is used by "exec" to call "getoptions" in Getopt::Long
parameters
Returns an array reference of parameters created by "new" based on the dictionary you provide. This hash reference is used by "exec" to call "getoptions" in Getopt::Long
This array reference is a Module::Generic::Array object
required
Set or get the array reference of required options. This returns a Module::Generic::Array object.
usage
Set or get the anonymous subroutine or sub routine reference used to show the user the proper usage of your program.
This is called by "exec" after calling "getoptions" in Getopt::Long if there is an error, i.e. if "getoptions" in Getopt::Long does not return a true value.
If you use object to call the sub routine usage, I recommend using the module curry
If this is not set, "exec" will simply return undef or an empty list depending on the calling context.
ERROR HANDLING
This module never dies, or at least not by design. If an error occurs, each method returns undef and sets an error that can be retrieved with the method "error"
AUTHOR
Jacques Deguest <jack@deguest.jp>
SEE ALSO
COPYRIGHT & LICENSE
Copyright (c) 2019-2020 DEGUEST Pte. Ltd.
You can use, copy, modify and redistribute this package and associated files under the same terms as Perl itself.