Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??

"There's seldom an excuse to have an undocumented Perl script."

"WWJD?     JWRTFM."

So say Jeffreys Copeland and Haemer, and I humbly concur.


In one of their very last columns from the now-defunct Server/Workstation Expert magazine (formerly SunExpert), the Jeffreys extolled the virtues of providing user-documentation in your camel-code with a particular pair of Perl modules: Getopt::Long and Pod::Usage:

  1. (necessary modules are) included in the standard Perl distribution
  2. they keep the docs in the same file as the program
  3. program messages are extracted from the documentsion itself
  4. argument parsing is relatively short


To put that in my own words:

  1. you don't have to convince a clue-free-PHB or snarling-SysAdmin* to let you install Yet Another Module
  2. you don't have to maintain separate (easy-to-fall-out-of-sync) doc files
  3. you don't have to maintain internal (easy-to-fall-out-of-sync) &Usage() subroutines
  4. Perl makes it easy and fun


The Jeffreys went so far as to helpify and manatize a helloworld.pl script.   However, in the interest of avoiding plaguarism lawsuits, I took this oportunity to make a still-small yet ever-so-slightly-more-useful program to fetch current condition reports from Weather Underground**.   By doculating it with these swell modules, I hope to show One Way To Do It.


Before we look at some code, here's a summary of what we'll do with These Fine Modules.

  • use Getopt::Long to set up --help and --man arguments.
  • use Getopt::Long to setup other program arguments (completely separate from Pod::Usage):
    • wunderg.pl --debug=1
    • wunderg.pl --versions
  • call the pod2usage() function to generate the associated messages.
    • The --help message is always extracted from pod sections titled USAGE, ARGUMENTS, and OPTIONS (case-sensitive).
    • The --man message is always the pod in it's entirety.



Code, discussion, runs, and sundry comments follow.
    cheers,
    ybiC


*No offense intended to monks of this ilk; I'm a recovering server-guy myself
**thanks to merlyn for mentioning the Weather::Underground module
***thanks to someFineMonk, I forget who, who pointed me to this keen pair o'doc-jones modules a year or so ago
****I'm sure gonna miss Mr Protocol   8^(
*****kudos to grinder for Parsing your script's command line, which I didn't spot until *after* posting this   8^P
Update:
Thanks to brothers gellyfish, belg4mit, and danger for code fixes   and to brothers impossiblerobot, Corion, wmono, and podmaster for posting+editing improvements   8^)



RELEVANT CODE, and DISCUSSION:



The Getopt::Long and Pod::Usage modules have *great* pod. (hint, hint)   So rather than regurgitate that, let's walk through relevant chunks of our example program.   Starting from the top:


Of course, you have to tell Perl to load the needed modules  (you do use warnings and strict, don't you??):

#!/usr/bin/perl -w
use strict;                  # avoid d'oh! bugs
use Getopt::Long;            # support options+arguments
use Pod::Usage;              # avoid redundant &Usage()


Since these scalar names start with "$opt_", we know that they're Getopt::Long-facilitated commandline option .   I happen to want a default value of 0 for debug, as it's used to set the verbosity level of Weather::Underground output.   The other three are declared as lexicals here, for use in just a bit.

my $opt_debug   = 0;
my ($opt_help, $opt_man, $opt_versions);


As the comment says, this Getopt::Long function allocates options and arguments for the program.   The first (alpha) part of the hash keys are the long form of the command-line switches.   The second (=1|!) part of the keys are argument specifiers.   Our example program uses two of the seven available argument specifiers.   "=i" requires an integer argument to assign as the variable's value, and "!" takes no argument, but does 'def' the variable.   You can perldoc Getopt::Long to see what the other 5 arg specs are.

GetOptions(
  'debug=i'   => \$opt_debug,
  'help!'     => \$opt_help,
  'man!'      => \$opt_man,
  'versions!' => \$opt_versions,
) or...
The hash values indicate the name of the variable, like our friend $opt_debug above.


So far, we've been dealing with Getopt::Long, but pod2usage is obviously a Pod::Usage function.   If the Getoptions() call above fails, we have Pod::Usage print the program's pod USAGE, OPTIONS, and ARGUMENTS (as spec'd by verbose level one), then end the program run.   Much friendlier than some cryptic or die $!; message, eh?

...or pod2usage(-verbose => 1) && exit;
pod2usage() also supports specifying error message, exit value, and output filehandle.   This program might benefit in places from a pod2usage() error message, but I'll leave that as an exercise for the reader.   Hint: perldoc Pod::Usage   8^)


In the same vein, we print the 'help' messages if an invalid value was entered for --debug, or if you purposely called --help:

pod2usage(-verbose => 1) && exit if ($opt_debug !~ /^[01]$/);
pod2usage(-verbose => 1) && exit if defined $opt_help;


Likewise, print the 'man' messages if the program was called with --man:

pod2usage(-verbose => 2) && exit if defined $opt_man;


The body of the program falls here, but since it does the Weather::Underground fetch stuff, we'll ignore it for now.


And finally, if the program is called with --versions, this block runs and prints a mess of version number info:

END{
  if(defined $opt_versions){
    print
      "\nModules, Perl, OS, Program info:\n",
      "  Weather::Underground  $Weather::Underground::VERSION\n",
      "  Pod::Usage            $Pod::Usage::VERSION\n",
      "  Getopt::Long          $Getopt::Long::VERSION\n",
      "  strict                $strict::VERSION\n",
      "  Perl                  $]\n",
      "  OS                    $^O\n",
      "  wunderg.pl            $wunderg_VER\n",
      "  $0\n",
      "  $site\n",
      "\n\n";
  }
}


RUN the PROGRAM, WILLYA?!



So, when we call our program like so wunderg.pl --help we get this message on screen:

Usage:
     wunderg.pl Paris,France Omaha,NE 'London, United Kingdom'

Arguments:
     Place
     --help      print Options and Arguments instead of fetching weather data
     --man       print complete man page instead of fetching weather data

     Place can be individual name:
       City
       State
       Country

     Place can be combinations like:
       City,State
       City,Country

     Note that if Place contains any spaces it must be surrounded with single
      or double quotes:
       'London,United Kingdom'
       'San Jose,CA'
       'Omaha, Nebraska'

Options:
     --versions   print Modules, Perl, OS, Program info
     --debug 0    don't print debugging information (default)
     --debug 1    print debugging information


And wunderg.pl --man results in:
Nah, let's skip that, it's too long.   If you want to see it, go to The Full Monty below and find the pod.


wunderg.pl paris,france 'london, united kingdom' actually *does* something, thanks to the fine folks at wunderground.com and Weather::Underground:

Sat Mar 30 12:34:53 2002
http://www.wunderground.com/members/signup.php

paris,france
  conditions = Unknown
  humidity = 34
  fahrenheit = 59

london,united kingdom
  conditions = Partly Cloudy
  humidity = 48
  fahrenheit = 58


And wunderg.pl --debug 'new york, new york' has W::U to tell us more details of the fetch:
Sat Mar 30 12:37:04 2002
http://www.wunderground.com/members/signup.php

Weather::Underground DEBUG NOTE: Creating a new Weather::Underground object
Weather::Underground DEBUG NOTE: Getting weather info for new york, new york
Weather::Underground DEBUG NOTE: Retrieving url http://www.wunderground.com/cgi-bin/findweather/getForecast?query=new york, new york
Weather::Underground DEBUG NOTE: SINGLE-LOCATION PARSED 1: new york, new york: Scattered Clouds: 70 * F . 46% humity
Weather::Underground DEBUG NOTE: Temperature in Fahrenheit. Converting accordingly
new york, new york
  conditions = Scattered Clouds
  humidity = 46
  fahrenheit = 70



SUNDRY COMMENTS:



* on Getopt::Long *

Options can be called in either longform:
      wunderg.pl --versions Minot,ND
      wunderg.pl --debug=1 Minot,ND
      wunderg.pl --help
      wunderg.pl --man

Or shortform:
      wunderg.pl -v Minot,ND
      wunderg.pl -d 1 Minot,ND
      wunderg.pl -h
      wunderg.pl -m


* on Pod::Usage *

podusage() called with only a 0 or 1 as argument is shortform verbose level:
      pod2usage(1);

podusage() called with only a quoted text string as argument is shortform for error message:
      pod2usage("Hrm...");

Arguments can be combined, but only in longform:
      pod2usage({-verbose=>1, -message=>"Hrm...",});



THE FULL MONTY:

This is the only place where I've used <code> tags, so that you can use the d/l button below to fetch this program.
#!/usr/bin/perl -w # wunderg.pl # pod at tail use strict; # avoid d'oh! bugs use Getopt::Long; # support options+arguments use Pod::Usage; # avoid redundant &Usage() use Weather::Underground; # fetch weather from www.wunderground.com my $wunderg_VER = '0.02.05'; my $site = 'http://www.wunderground.com/members/signup.php'; my $opt_debug = 0; my ($opt_help, $opt_man, $opt_versions); GetOptions( 'debug=i' => \$opt_debug, 'help!' => \$opt_help, 'man!' => \$opt_man, 'versions!' => \$opt_versions, ) or pod2usage(-verbose => 1) && exit; pod2usage(-verbose => 1) && exit if ($opt_debug !~ /^[01]$/); pod2usage(-verbose => 1) && exit if defined $opt_help; pod2usage(-verbose => 2) && exit if defined $opt_man; # Check this last to avoid parsing options as Places, # and so don't override $opt_man verbose level my @places = @ARGV; pod2usage(-verbose => 1) && exit unless @places; print "\n", my $time = localtime, "\n$site\n\n"; for (@places){ my $weather = Weather::Underground ->new( place => $_, debug => $opt_debug, ) or die "Error creating object:\n$@\n"; my $arrayref = $weather->getweather() or die "Error fetching:\n$@\n"; for(@$arrayref){ print "$_->{place}\n" if exists($_->{place}); while (my ($key, $value) = each %{$_}) { print " $key = $value\n" unless ($key eq 'celsius' or $key eq 'place'); # celcius matches misspelling in W::U # fixed in W::U v2.02 } } print "\n"; } BEGIN{ # allow to run from cron: # but doesn't work 8^( $ENV{HTTP_PROXY} = 'http://proxy:port/'; } END{ if(defined $opt_versions){ print "\nModules, Perl, OS, Program info:\n", " Weather::Underground $Weather::Underground::VERSION\n", " Pod::Usage $Pod::Usage::VERSION\n", " Getopt::Long $Getopt::Long::VERSION\n", " strict $strict::VERSION\n", " Perl $]\n", " OS $^O\n", " wunderg.pl $wunderg_VER\n", " $0\n", " $site\n", "\n\n"; } } =head1 NAME wunderg.pl =head1 SYNOPSIS wunderg.pl Paris,France Omaha,NE 'London, United Kingdom' =head1 DESCRIPTION Fetch and print weather conditions for one or more cities. Weather::Underground appears to read http_proxy environment variable, so wunderg.pl works behind a proxy (non-auth proxy, at least). Switches that don't define a value can be done in long or short form. eg: wunderg.pl --man wunderg.pl -m =head1 ARGUMENTS Place --help print Options and Arguments instead of fetching weather d +ata --man print complete man page instead of fetching weather data Place can be individual name: City State Country Place can be combinations like: City,State City,Country Note that if Place contains any spaces it must be surrounded with sin +gle or double quotes: 'London,United Kingdom' 'San Jose,CA' 'Omaha, Nebraska' =head1 OPTIONS --versions print Modules, Perl, OS, Program info --debug 0 don't print debugging information (default) --debug 1 print debugging information =head1 AUTHOR ybiC =head1 CREDITS Core loop derived directly from Weather::Underground pod. Thanks to merlyn for pointing out this cool weather module, gellyfish for tip to use regex match for valid $opt_debug values, belg4mit for cleaner syntax for printing "Place" key, danger for tip+fix for 5.6.1 warning on 'unless defined(places)' Oh yeah, and to some guy named vroom. You don't have to subscribe to www.wunderground.com to fetch their da +ta. But it's only $5USD/year, so why not? =head1 TESTED Weather::Underground 2.01 Pod::Usage 1.14 Getopt::Long 2.2602 Perl 5.00503 Debian 2.2r5 =head1 BUGS None that I know of. =head1 TODO Test from cron Test on Cygwin Test on ActivePerl Make it run from cron when behind proxy Use printf() to line up weather output in columns Print modules... info on error =head1 UPDATES 2002-03-29 17:30 CST Replace 'unless defined(@places)' with 'unless(@places)' to avoid warning on 5.6.1 Perlish idiom instead of looping through hash twice Post to PerlMonks 2002-03-29 12:05 CST Initial working code =cut

In reply to The Dynamic Duo --or-- Holy Getopt::Long, Pod::UsageMan! by ybiC

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having an uproarious good time at the Monastery: (6)
As of 2024-03-28 12:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found