The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Getopt::Modular - Modular access to Getopt::Long

VERSION

version 0.13

SYNOPSIS

Perhaps a little code snippet.

    use Getopt::Modular;

    Getopt::Modular->acceptParam(
                                  foo => {
                                      default => 3,
                                      spec    => '=s',
                                      validate => sub {
                                          3 <= $_ &&
                                          $_ <= determine_max_foo();
                                      }
                                  }
                                 );
    Getopt::Modular->parseArgs();
    my $foo = Getopt::Modular->getOpt('foo');

PURPOSE

There are a few goals in this module. The first is to find a way to allow a bunch of custom modules to specify what options they want to take. This allows you to reuse modules in multiple environments (applications) without having to repeat a bunch of code for handling silly things like the parameters themselves, their defaults, validation, etc. You also don't need to always be aware of what parameters a module may take if it merely grabs them from the global environment.

I find I'm reusing modules that should be user-configurable (via the commandline) in multiple applications a lot. By separating this out, I can just say "use Module;" and suddenly my new application takes all the parameters that Module requires (though I can modify this on a case-by- case basis in my application). This allows me to keep the information about a variable in close proximity to its use (i.e., the same file).

There is a lot of information here that otherwise would need to be handled with special code. This can greatly simplify things:

  • consistency

    Because the same parameters are used in multiple applications with the same meaning, spelling, valid values, etc., it makes all your applications consistent and thus easy to learn together.

  • help

    The online help is a big challenge in any application. This module will handle the help for your parameters by using what is provided to it from each module. Again, the help for a parameter will be the same in all your applications, maintaining consistency.

    Further, the help will be right beside the parameter. No more looking through hundreds or thousands of lines of pod and code trying to match up parameters and help, wondering if you missed something. Now you only have to address about 5-10 lines of code at a time wondering if you missed something.

  • defaults

    Defaults right beside the parameter. Again, you only need to address 5-10 lines of code to look for parameter and its default. They aren't separated any longer. Now, it's true that you don't necessarily need to have defaults far removed with Getopt::Long, but that really does depend on what you're doing.

    Further, the defaults can be dynamic. That means you can put in a code reference to determine the default. Your default may depend on other parameters, or it may depend on external environment (Is the destination directory writable? What is the current hostname? What time is it?). You can grab your default from a webserver from another continent (not recommended). It doesn't matter. But you can have that code right there with the parameter, making it easy to compartmentalise.

    You do not need to have dynamic defaults. Some would argue that dynamic defaults make applications more difficult for the user to know what will happen. Not only do I think that good dynamic defaults can help the application Do The Right Thing, but that the developer of the application should be able to choose, thus defaults can be dynamic, even if that is not necessarily useful to your application.

    In one application, my goal was to minimise any requirement to pass in parameters, thus having defaults that made sense, but to Do The Right Thing, which was usually different between different environments. As one example, a flag to specify mod_perl vs FastCGI vs CGI could be:

        'cgi-style' => {
            default => sub {
                if (detect_mod_perl()) {
                    return 'mod_perl';
                } elsif (detect_fastcgi()) {
                    return 'fastcgi';
                } else {
                    return 'cgi';
            }
        }

    This would Do The Right Thing, but you can override it during testing with a simple command line parameter.

  • validation

    Like everything above, the validation of a parameter is right beside the parameter, making it easy to address the entirety of a parameter all in a single screen (usually much less) of code.

    Validation is also automatically run against both the default (same idea as having tests for your perl modules: sanity test that your default is valid) when no parameter is given, and any programmatic changes to a value. Without this, I was always forgetting to validate my option changes. This automates that.

All this, the power of Getopt::Long, and huge thanks from whomever inherits your code for keeping everything about --foo in a single place.

The downside is that you need to ensure all modules that may require commandline parameters are loaded before you actually parse the commandline. For me, this has meant that my test harness needs to either ask for the module to test via environment variable or needs to pre-parse the commandline (kind of defeating the purpose of the module). I've opted for checking for the module via $ENV{MODULE}, loading it, and then parsing the commandline.

Also, another downside is that parameters are not positional. That is, --foo 3 --bar 5 is the same as --bar 5 --foo 3. The vast majority of software seems to agree that these are the same.

IMPORTS

As the module is intended to be used as a singleton (most of the time), and all methods are class (not object) methods, there really isn't much to import. However, typing out "Getopt::Modular->getOpt" all the time can be cumbersome. So a few pieces of syntactical sugar are provided. Note that as sugar can be bad for you, these are made optional.

  • -namespace

    By specifying -namespace => "GM", you can abbreviate all class calls from Getopt::Modular to simply GM. Another alternative is to simply create your own subclass of Getopt::Modular with a simple, short name, and use that.

    This only has to be done once per application.

  • -getOpt

    This will import getOpt as a simple function (not a class method) into your namespace. This can be done for any namespace that needs the getOpt function imported.

Arguably, more could be added. However, as most of the calls into this module will be getting (not setting, etc.), this is seen as the biggest sugar for least setup.

FUNCTIONS

new

Construct a new options object. If you just need a single, global options object, you don't need to call this. By default, all methods can be called as package functions, automatically instantiating a default global object.

Takes as parameters all modes accepted by setMode, as well as a 'global' mode to specify that this newly-created options object should become the global object, even if a global object already exists.

Note that if no global object exists, the first call to new will create it.

init

Overridable method for initialisation. Called during object creation to allow default parameters to be set up prior to any other module adding parameters.

Default action is to call $self->setMode(@_), though normally you'd set any mode(s) in your own init anyway.

setMode

Sets option modes.

Currently, the only supported mode is:

strict

Don't allow anyone to request an option that doesn't exist. This will catch typos. However, if you have options that may not exist in this particular program that may get called by one of your other modules, this may cause problems in that your code may die unexpectedly.

Since this is a key feature to this option approach, the default is not strict. If you always knew all your options up front, you could just define them and be done with it. But then you would likely be able to just go with Getopt::Long anyway.

setBoolHelp

Sets the names used by getHelp and getHelpRaw for boolean values. When your user checks the help for your application, we display the default or current values - but "0" and "1" don't make any sense for booleans for users. So we, by default, use "on" and "off". You can change this default. You can further override it on a parameter-by-parameter basis.

Pass in two strings: the off or false value, and the on or true value. (Mnemonic: index 0 is false, index 1 is true.)

acceptParam

Set up to accept parameters. All parameters will be passed to Getopt::Long for actual parsing.

e.g.,

    Getopt::Modular->acceptParam('fullname' => {
        aliases => [ 'f', 'fn' ],
        spec => '=s@', # see Getopt::Long for argument specification
        help => 'use fullname to do blah...',
        default => 'baz',
        validate => sub {
            # verify that the value passed in is ok
        },
    });

You can pass in more than one parameter at a time.

Note that order matters. That is, the order that parameters are told to Getopt::Modular is the same order that parameters will be validated when accepted from the commandline, regardless of the order the user passes them in. If this is no good to you, then you may need to find another method of handling arguments. If one parameter depends on another, e.g., for the default or validation, be sure to use the module that declares that parameter prior to calling acceptParam to ensure that the other parameter will be registered first and thus parsed/handled first.

The parameter name is given separately. Note that whatever this is will be the name used when you retrieve the option. I suggest you use the longest name here to keep the rest of your code readable, but you can use the shortest name or whatever you want.

Acceptable options are:

aliases

In Getopt::Long, these would be done like this:

    'fullname|f|fn'

Here, we separate them out to make them easier to read. They are combined back into a single string for Getopt::Long. Optionally, you can simply provide 'fullname|f|fn' as the parameter name, and it will be split apart. In this case, the name used to retrieve the value will be the first string given.

spec

This is the part that tells Getopt::Long what types to accept. This can be a quick check against what can be accepted (numeric, string, boolean) or may be more informative (such as taking a list). While this is mostly used to pass in to Getopt::Long, it is also used for context in the help option, or in returning options back to whoever needs them, such as knowing whether the given values can be a list, or if it's simply a boolean.

help

This is either a string, or a code ref that returns a string to display to the user for help. The reason why a code ref is allowed is in case the help string is dynamic based on the parameters that are given. For example, you may want to provide different help for the current flag based on the valid of some other flag.

If this is a code ref, it is not passed any parameters, and $_ is not set reliably.

help_bool

This is an array reference with the two values of boolean you want to use. It overrides the global strings. e.g., [ qw(false true) ]. The unset value is first (mnemonic: index 0 is false, 1 is true).

These strings are only used if this option is a boolean, and only in the help output.

default

This is either a scalar, an array ref (if spec includes @), a hash ref (if spec includes %), or a code ref that returns the appropriate type. A code ref can provide the opportunity to change the default for a given parameter based on the values of other parameters. Note that you can only rely on the values of parameters that have already been validated, i.e., parameters that were given to acceptParam earlier than this one. That's because ones given later would not have had their values set from the command line yet.

This is checked/called only once, maximum, per process, as once the default is retrieved, it is stored as if it were set by the user via the command line. It may be called either as part of the help display, or it may be called the first time the code requests the value of this parameter. If the current code path does not check this value, the default will not be checked or called even if the parameter is not passed in on the command line.

If this is a code ref, it is not passed any parameters, and $_ is not set reliably.

validate

This is a code ref that validates the parameter being passed in against not only valid values, but the current state of the parameters. This includes validation of the default value.

You can use this callback to ensure that the current values are allowed given all the parameters validates so far. That is, you can call getOpt on any previous parameter to check their values make sense given the current value. If it doesn't, simply die with the error message. Do not call exit, because this is called in an eval block for displaying help, and it's perfectly reasonable that a user requests help when some values are invalid.

The value(s) being validated are passed in via $_, which may be a reference if the type is an array or hash.

You may throw an exception in case of error, or you can simply return false and a generic exception will be thrown on your behalf. Obviously throwing your own exception with a useful error message for the user is the better choice.

If this key is not present, then anything Getopt::Long accepts (due to the specification) will be accepted as valid.

valid_values

If the list of valid values is limited and finite, it may be easier to just specify them. Then Getopt::Modular can verify the value provided is in the list. It can also use the list in the help.

This parameter needs to be either an array ref, or a CODE ref that generates the list (lazy). Note that the CODE ref will only be called once, so don't count on it being dynamic, too.

mandatory

If this is set to a true value, then during parameter validation, this option will always be set, either via the command line, or via checking/calling default (which will then be validated). The purpose of this is to ensure the validate code is called during the parsing of arguments even if the parameter was not passed in on the command line. If you have no default and your validate rejects an empty value, this can, in effect, make the parameter mandatory for the user.

hidden

If this is set to a true value, then getHelp and getHelpWrap, but not getHelpRaw, will not return this item in its output. Useful for debugging or other "internal" parameters.

unacceptParam

Sometimes you may load a module that has a parameter, but in this particular case, you don't want the user to be able to specify it. Either you want the default to always be used, or you want to set it to something explicitly. You can set the parameter to be "un"accepted, thereby eliminating it from the list of options the user can pass in.

However, this will not remove it from the list that Getopt::Modular will recognise inside the code. That is, Getopt::Modular->getOpt() will still accept that parameter, and setOpt will still allow you to set it programmatically.

To re-accept an unaccepted parameter, simply call acceptParam, passing in the parameter name and an empty hash of options, and all the old values will be used.

parseArgs

Once all parameters have been accepted (and, possibly, unaccepted), you must call parseArgs to perform the actual parsing.

Optionally, if you pass in a hash ref, it will be populated with every parameter. This is intended to provide a stop-gap for migration from Getopt::Long::GetOptions wherein you can provide your options hash and use that directly.

    GM->parseArgs(\my %opts);

The downside to this is that it will determine all values during parsing rather than deferring until the value is actually required. Most of the time, this will be okay, but if some defaults take a long time to resolve or validate, e.g., network activities such as looking up users via LDAP, requesting a value from a wiki page, or even just reading a file over NFS, sshfs, Samba, or similar, that time will be wasted if the value isn't actually required during this execution based on other parameters.

getOpt

Retrieve the desired option. This will "set" any option that has not been retrieved before, and was not on the command line, by calling the default.

If you need to know the difference between an implicit default and an explicit default, you need to do that in your default code. That said, you should think twice about that: is it intuitive to the user that there should be a difference between "--foo 3" and not specifying --foo at all when the default is 3?

setOpt

Programmatic changing of options. This should not be done until after the options have been parsed: defaults are set through the default flag, not by setting the option first.

Note that this will pass the value through the validation code, if any, so be sure you set the values to something that make sense. Will throw an exception if the value cannot be set, e.g., it is invalid.

getHelpRaw

This function will go through all the parameters and construct a list of hashes for constructing your own help. It's also the internal function used by getHelp to create its help screen.

Each hash has the following keys:

param

Array ref of parameter names. This is what the user passes in, e.g., "-f" or "--foo".

help

The string associated with the parameter (if this was a code ref, the code is called, and this is the return from there).

default

If there is a default (that doesn't die when validated), or if the value was already on the command line, that value. If the default does die, then this key will be absent (i.e., no default, or mandatory, or however you want to interpret this).

getHelp

Returns a string representation of the above raw help. If you need to translate extra strings, an extra hash-ref of callbacks will be used. For example:

    GM->getHelp({
        current_value => sub {
            lookup_string("Current value: '[_1]'", shift // '');
        },
        # only needed if you use the valid_value key at the moment, but
        # could be extended later.
        valid_values => sub {
            lookup_string("Valid values: '[_1]'", join ',', @_);
        },
    });

Callbacks:

current_value

Receives the current value (may be undef).

valid_values

Receives all valid values.

getHelpWrap

Similar to getHelp, this uses Text::WrapI18N, if available, otherwise Text::Wrap, to automatically wrap the text on for help, making it easier to write.

Default screen width is 80 - you can pass in the columns if you prefer.

A second parameter is the same as getHelp above with callbacks for translations.

Examples:

    print GM->getHelpWrap(70, { ... }); # specify cols and callbacks
    print GM->getHelpWrap({ ... }); # implicit cols (80), explicit callbacks
    print GM->getHelpWrap(70); # implicit cols, default English text
    print GM->getHelpWrap(); # implicit all.

EXCEPTIONS

Various exceptions can be thrown of either Getopt::Modular::Exception or Getopt::Modular::Internal types. All exceptions have a "type" field which you can retrieve with the ->type method (see Exception::Class). This is intended to facilitate translations. Rather than using the exception message contained in this object, you can substitute with your own translated text.

Exception types:

unknown-option

Internal error: an option was used, for example as one of the aliases, that didn't resolve. I don't think this should happen.

getopt-long-failure

Getopt::Long returned a failure. The warnings produced by Getopt::Long have been captured into the warnings of this exception ($e->warnings), but they are likely also English-only.

dev-error

getOpt didn't get any parameters. Probably doesn't need translating unless you are doing something odd (but has a type so you can do something odd).

valid-values-error

The valid_values key for an option wasn't either an array ref or a code ref.

no-such-option

Strict mode is on, and you asked getOpt for an option that G::M doesn't know about.

set-int-failure

Called setOpt on an integer value (types +, i, or o), without giving an integer.

set-real-failure

Called setOpt on an real value (type f), without giving a number.

validate-failure

The validation for this value failed. The option and value fields are filled in.

wrong-type

When calling setOpt, trying to set a value of the wrong type (a hash reference to a list, for example)

AUTHOR

Darin McBride, <dmcbride at cpan.org>

BUGS

Please report any bugs or feature requests to bug-getopt-modular at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Getopt-Modular. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Getopt::Modular

You can also look for information at:

ACKNOWLEDGEMENTS

COPYRIGHT & LICENSE

Copyright 2008, 2012 Darin McBride, all rights reserved.

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

AUTHOR

Darin McBride <dmcbride@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2014 by Darin McBride.

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