http://qs321.pair.com?node_id=479355

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

Great studious and wise monks I am guilty of the sin of typing the same thing into my code more than once.

I feel I must have been negligent in my study of the great cannon of wisdom. My code uses Getopts::Long to set a bunch of switches. When the switches are initialised their use is documented in comments. GetOptions then checks the command line using command line switch names almost the same as the $variable names (easy to make the same). Then I have a usage sub which again lists all these switches and reproduces the same info as the #comments.

A Super Search drowned me in links but did not turn up anything that look relevant.

Is there a simple way to write this information once and have both GetOptions, variable initialisation and the usage sub grab it from the one place ? Is there an existing module ? Do people have their own ad-hoc ways to do this. Should I be thinking POD here ?

Here is an example chunk of code exhibiting this sin. It even exhibits the other problem this causes, available switches do not match usage documentation (slowdown option works but not documented)

###################################################################### +########## our $debug = 0; # switch on debuging our $verbose = 0; # important info to STDOUT my $help = 0; # show usage my $force = 0; # run even if less than 5 since last run my $slowdown = 0; # run slow (useful to test locking) my $test = 0; # send no events my $clean_cache = 0; # clean ep cache of obsolete entries found my $max_probs = 150; # max number ep problems my $sched = 11; # schedule frequency my $monitor_freq = 5; # frequency of string script monitor my $Options_OK= GetOptions ("debug" => \$debug, "verbose" => \$verbose, "help" => \$help, "force" => \$force, "clean" => \$clean, "slow" => \$slowdown, "test" => \$test, "probs=i" => \$max_probs, "schedule=i" => \$sched, ); unless ($Options_OK) { print "Sorry you gave a bad command line option\n"; usage (); exit 1; } if ($help) { usage (); exit 0; } sub usage { print "\nusage: ".SCRIPTNAME."[-debug|-verbose] [-help] [-force] [ +-clean] [-test] [-probs] [-schedule]\n"; print " -debug = switch on debuging\n"; print " -help = show this usage\n"; print " -verbose = important infor to STDOUT\n"; print " -force = run even if less than 5 mins since last + run\n"; print " -clean = clean obsolete entries found in ep cach +e\n"; print " -test = send no events to T/EC\n"; print " -probs = max number ep problems before complaini +ng\n"; print " -schedule = frequency that script is being run in m +inutes\n"; print "\n"; print "NOTE: any of the command line options can be abbreviated to + the\n"; print "shortest unique string, ie -h for help is good, but if we h +ad\n"; print "options hex & help then you would use -hex and -hel as mini +mum\n\n"; }

Cheers,
R.

Pereant, qui ante nos nostra dixerunt!

Replies are listed 'Best First'.
Re: deriving usage from Getopts::Long
by jimbojones (Friar) on Jul 29, 2005 at 12:40 UTC
    Hi,

    Check out The Dynamic Duo --or-- Holy Getopt::Long, Pod::UsageMan! on how to use Getopt::Long with Pod::Usage. This should will cover the case of the POD (the user doc) matching the the usage sub, as the text will be derived from the same point, the POD.

    The one thing it won't cover is the comments:
    our $debug = 0; # switch on debuging our $verbose = 0; # important info to STDOUT my $help = 0; # show usage my $force = 0; # run even if less than 5 since last run
    ....

    However, as has been discussed in multiple places in the Monastery, the choice of variable names (self-commenting) is much more important than the comments after the variable declarations. All of your variable names seem to be self-documenting, so I'm not sure it's necessary to have such detailed comments after their declarations. I would maintain that having POD, usage and self-documenting variables is the way to go here.

    - j

      I second using Getopt::Long with Pod::Usage when possible. It works well in practice, and I don't have to lug around extra README files or man pages.

      For scripts that I've written without Pod::Usage, I like putting my own usage() function at the top of the script. That way I can see it up front as a reference (and make it easier to remember I need to keep it up to date).

Re: deriving usage from Getopts::Long
by crashtest (Curate) on Jul 29, 2005 at 13:10 UTC

    As for addressing the redundancy of declaring variables and then retyping the option names in the Getopt call, an alternative use of Getopt allows you to populate a hash with results instead of individual variables:

    my %h = (); GetOptions (\%h, 'length=i'); # will store in $h{length}
    See the documentation on CPAN.

    The only downside I see here is that you can't initialize your options with defaults, although it is easy to do after the Getopt call (at the cost of typing the option names again, of course):

    $h{'max_probs'} = 150 unless (exists $h{'max_probs'});

    Hope this helps...

Re: deriving usage from Getopts::Long
by pbeckingham (Parson) on Jul 29, 2005 at 14:01 UTC

    I suggest you take a look at Damian's excellent and weird Getopt::Declare.



    pbeckingham - typist, perishable vertebrate.
      One caveat with Getopt::Declare is that you must use tabs to separate the switch definitions. I set my editor to convert tabs to spaces (in source files), so would have to turn off that setting to use Getopt::Declare.
Re: deriving usage from Getopts::Long
by Tanktalus (Canon) on Jul 29, 2005 at 16:22 UTC

    What I've done, and I really need to find a way to get it opened, is write my very own wrapper around GetOpt::Long. What it does is multi-step:

    1. It accepts parameters from the rest of the code - these parameters do not need to be passed in all at once, but the "accept parameter" function can be called multiple times. This allows me to load all my modules, and each module can register what options it wants to accept with the global wrapper.
    2. It then takes the parameters, puts them together in a way that GetOpt::Long will like, and calls GetOpt::Long to do the actual option parsing.
    3. It then calls any call-backs that were initially registered for extra validation. Sure, you can tell GetOpt that you want an integer. But this allows you to put in a CODE ref that then checks that the value is really between 1 and 12, for example, if you're validating months. The parameter is also passed in such that if the validation assigns back to $_[0], it changes the value which is useful when you want to be able to have a parameter that is something like --smtp_server AUTO and let the validation code figure out the automatic value. (Not that I have any idea how to do that for an smtp server, but maybe you do have such an idea.)
    4. It then checks if the -? or --help parm was passed in. If so, it generates the help based on what was given to it. It also will print out the default values as it knows them, prints out all the variations (e.g., a parameter of 'v|verbose!' would have -v, --verbose, --noverbose as valid options - this would automatically be part of the help), and then exits.
    The parameters are just a list of hashes which have keys for name, default, validate, help, and maybe others I don't recall. Except for name, the rest can be code refs or strings.

    This setup has saved us countless weeks of development time over the last 4 years or so. I am positive that it has saved us multiple weeks of development time just in the last 6 months.

      What you describe is easily done with Getopt::Declare. I haven't had the multiple module situation, so I haven't had to use an "accept parameter" function, but that could easily be done.

      The parameter description is just a string passed to G::D. The string can be built up piecemeal by modules as they load (or perhaps each module adds a key to a hash, and after all modules are loaded, the hash is processed into a string).

      The code callbacks are called "actions" in the G::D documentation. Parameter checking syntax, either by fundamental type (e.g., integer), regex, or code block, is already provided.

      Perhaps I should develop a helper module for G::D to do what you suggest?

      -QM
      --
      Quantum Mechanics: The dreams stuff is made of

        I actually doubt it would be that easy to do with Getopt::Declare. The reason is that the actions are done in the caller's namespace - and G::D is not going to know which namespace is which. Worse, it looks like these actions will affect only package variables. They aren't proper closures, and I need closures to affect the proper private variables. I'm sure it could still be faked somehow, but it would not be the straight-forward method you may think it is.

        Besides, once you have a wrapper, it really doesn't matter which one it wraps, as long as the caller(s) don't need to change. ;-)

        (Also, it's imperative for my app, anyway, that all parms are parsed in a certain order, irrespective of the order on the commandline, which is also contrary to G::D's method. So my wrapper just gets G::L to parse the whole thing, and then we validate in order.)

Re: deriving usage from Getopts::Long
by QM (Parson) on Jul 29, 2005 at 15:33 UTC