Tanktalus has asked for the wisdom of the Perl Monks concerning the following question:

In a recent thread, I opined that I would try to CPAN-ise my wrapper around Getopt::Long. This is largely because this was the second time where I saw someone trying to reinvent this wheel - and, from experience, once you start making this wheel, it can grow to be quite large, yet very easy to use.

Obviously, a program has but one @ARGV set of command line options. (Well, except for the tests - that will be another kettle of fish to deal with.) Thus, the data will be global. The way I currently do that is to hide a singleton in the package, and do it OO-ish. From using this for the last 4 or 5 years, I'm not sure that's really necessary, so I'm thinking that while I'm at it, I can just change the way that it stores this global data.

For now, I'm working with a name of 'Getopt::Long::Framework' which is a bit long, but is the most descriptive I've come up with so far. (Better ideas are more than welcome.) I'm also using all the functions as package methods - disadvantage is that you may be typing 'Getopt::Long::Framework->...' all the time. Advantage is that you can create your own package, say 'GLF' which just does a "use base 'Getopt::Long::Framework'" and then will be able to override some functions, and even be able to set up with a default list of parameters (e.g., --debug) that may be applicable to your project.

Note: this is not intended for one-liners or other small scripts. This is more intended for a large framework of related applications that reuse each others' modules, and will allow you to build up the option list on the fly.

The question I'm wondering about is how you would approach this? Are package methods a "good" way to do this? Will I put off potential users?

FYI: the synopsis (thus far):

use Getopt::Long::Framework; Getopt::Long::Framework->accept( 'qwib=i' => { default => 30, validate => qr/^(?:\d|\d\d)$/, help => 'sets up the qwibble factor', }, 'blah|b=s' => { default => sub { $ENV{blah} || 'frobnicate' }, validate => sub { -d $_ }, help => 'sets the frobnicator directory', }, ); my $frob_dir = Getopt::Long::Framework->getOpt('blah');
An alternative is to export functions into the caller's space, but that probably would be much more difficult to manage how to override things in a central location.

Update: strike-out/italics based on jdporter's comments below.

Replies are listed 'Best First'.
Re: Design of a global-variable storage package
by ikegami (Patriarch) on Nov 22, 2005 at 22:45 UTC

    I don't like package methods since I can't import them.

    I can simply import
    Getopt::Long::Framework::getOpt
    with
    *getOpt = \&Getopt::Long::Framework::getOpt
    (or even more simply through use)

    But to import
    Getopt::Long::Framework->getOpt
    I need to create the wrapper
    sub getOpt { Getopt::Long::Framework->getOpt(@_) }

    You suggest using inheritance (use base ...), but I hate inherting where there isn't an is-a relationship.

    It doesn't seem to be needed here. If I understand correctly, you want to allow multiple modules to specify options they accept. Wouldn't the following be sufficient?

    # Module1 Getopt::Long::Framework::accept(...); . : # Module2 Getopt::Long::Framework::accept(...); . : # Module3 Getopt::Long::Framework::accept(...); . : # First call to getOpt processes @ARGV. $val = Getopt::Long::Framework::getOpt(...);
    or even something like
    # Module1 use Getopt::Long::Framework accept => ...; . : # Module2 use Getopt::Long::Framework accept => ...; . : # Module3 use Getopt::Long::Framework accept => ...; . : # First call to getOpt processes @ARGV. $val = Getopt::Long::Framework::getOpt(...);

      And this is one of the reasons why I asked. Sometimes I latch on to a single design viewpoint, and then I have no one to knock sense into me ;-)

      What I am trying to figure out is how the user will be able to override things this way. For example, if I factor out a function that "gets" the order of the parameters to be parsed, how does the user override that to reorder them? Or, more likely, some of the usage functions. With package methods, this is easy - we can re-use the built-in perl functions for doing so by simply capturing the first parameter and using it for dispatching to the next section. my $class = shift; ... $class->display_help(...) Otherwise, I need to maintain a bunch of code refs and figure out the right one to call, and then SUPER won't work as expected, either, so I need to pass in the original code ref in case you just want to modify existing behaviour. e.g., sub option_list { my $super = shift; my @o = $super->option_list();  sort \&some_funky_sort @o; } which is pretty ugly, too. If I don't provide for it, whether that be a singleton or a package or maintaining a bunch of extra coderefs, you won't be able to override things.

      I suppose the other question is - will anyone really need to override anything? Perhaps not. Am I just overdesigning? It's hard for me to tell. Afterall, I wrote the original methods the way I wanted them to be, and maybe that's good enough for others. I may have a lot of Hubris, but not that much - so I assume people will want to tweak things, but without modifying my module.

      You both appear to have overlooked the very simple alternative:

      my $p= 'Getopt::Long::Framework'; $p->What(); $p->Ever();

      But I don't buy the 'singleton' concept in general. Even CGI.pm realizes that sometimes There Can Be More Than One, even for things where that is almost never wanted and it is easy to fail to see any cases when someone would want Two.

      Implement your functionality using plain (non-singleton) objects and just provide a handy shortcut for getting the default, global instance. Then you have the benefits of 'singleton' without the limitations.

      - tye        

        I thought of that $p part, too, just forgot to mention it. I'm not sure, though, that this would be better-received. ;-)

        As for your non-singleton approach - something like this?

        my $default; sub new { my $class = shift || __PACKAGE__; my $self = {}; bless $self, $class; $default ||= $self; } sub global_instance { $default ||= (shift || __PACKAGE__)->new(); }
        And, perhaps, a way to change the global instance to be another one, if you really do need More Than One?

Re: Design of a global-variable storage package
by jdporter (Paladin) on Nov 23, 2005 at 20:10 UTC

    "App-Options combines command-line arguments, environment variables, option files, and program defaults..."

    Other interesting possibilities:

    Of these, the one I think holds the most promise is the last, because it was clearly designed with extensibility in mind. Config-Frontend doesn't seem to come with a backend for reading the command line, but one could easily be written.


    Background

    I went scrounging around CPAN for modules that would support simple, flexible application configuration management. Here's what I came up with.

    First, there are modules which simply read/write certain config file formats:

    For example:

    There are modules which try to be config file polyglots:

    There's a handful of modules that try to tackle the problem of locating config files:

    Then there are modules that try to handle more sources of config info than just files, e.g. integrating command-line options with configfile data.

    Those are the ones I listed in my reply, above.

    PS - I'm not trying to downplay the power of any of these modules. I realize that some of them do a lot more than what I've characterized here. I'm just focusing on the types of data sources supported.

    PPS - If you know of a module not listed here that you think ought to be, please /msg me first, rather than reply. Thanks.

    We're building the house of the future together.
Re: Design of a global-variable storage package
by jdporter (Paladin) on Nov 23, 2005 at 16:07 UTC
    Obviously, a program has but one @ARGV.
    Thus, the data will be global.
    sub func { local @ARGV = @_; my( @foo, @bar ); GetOptions( 'foo=s' => \@foo, 'bar=s' => \@bar, ); print "foo = @foo\nbar = @bar\n"; } func( -foo => 1, -bar => 1, -foo => 2, -bar => 2 );

    :-)

    We're building the house of the future together.

      You mostly have a point. Getopt::Long is simple enough for that scenario where everything pretty much lives inside a single function. This framework is targetted at more complex interaction where acceptable options come from multiple locations.

      I can imagine that some functions may find this useful, but I am having a hard time imagining what functions those may be ;-)