man Scriptalicious () - Make scripts more delicious to SysAdmins

NAME

Scriptalicious - Make scripts more delicious to SysAdmins

SYNOPSIS

 use Scriptalicious
      -progname => "pu";

 our $VERSION = "1.00";

 my $url = ".";
 getopt getconf("u|url" => \$url);

 run("echo", "doing something with $url");
 my $output = capture("svn", "info", $url);

 __END__

 =head1 NAME

 pu - an uncarved block of wood

 =head1 SYNOPSIS

 pu [options] arguments

 =head1 DESCRIPTION

 This script's function is to be a blank example that many
 great and simple scripts may be built upon.

 Remember, you cannot carve rotten wood.

 =head1 COMMAND LINE OPTIONS

 =over

 =item B<-h, --help>

 Display a program usage screen and exit.

 =item B<-V, --version>

 Display program version and exit.

 =item B<-v, --verbose>

 Verbose command execution, displaying things like the
 commands run, their output, etc.

 =item B<-q, --quiet>

 Suppress all normal program output; only display errors and
 warnings.

 =item B<-d, --debug>

 Display output to help someone debug this script, not the
 process going on.

 =back

DESCRIPTION

This module helps you write scripts that conform to best common practices, quickly. Just include the above as a template, and your script will accept all of the options that are included in the manual page, as well as summarising them when you use the CW-h option.

(Unfortunately, it is not possible to have a `use' dependency automatically add structure to your POD yet, so you have to include the above manually. If you want your help message and Perldoc to be meaningful, that is.)

Shortcuts are provided to help you abort or die with various error conditions; all of which print the name of the program running (taken from CW$0 if not passed). The motive for this is that small scripts tend to just get written and forgotten; so, when you have a larger system that is built out of lots of these pieces it is sometimes guesswork figuring out which script a printed message comes from!

For instance, if your program is called with invalid arguments, you may simply call CWabort with a one-line message saying what the particular problem was. When the script is run, it will invite the person running the script to try to use the CW--help option, which gives them a summary and in turn invites them to read the Perldoc. So, it reads well in the source;

  @ARGV and abort "unexpected arguments: @ARGV";
  $file or abort "no filename supplied";

And in the output;

  somescript: no filename supplied!
  Try `somescript --help' for a summary of options

On the other hand, if you call CWbarf, then it is considered to be a hard run-time failure, and the invitation to read the CW--help page to get usage not given. Also, the messages are much tidier than you get with CWdie et al.

  open FOO, "<$file" or barf "failed to open $file; $!";

Which will print:

  somescript: failed to open somefile; Permission denied

Scriptalicious has no hard dependancies; all the methods, save reading passwords from the user, will work in the absence of extra installed modules on all versions of Perl from 5.6.0 onwards.

To avoid unnecessary explicit importing of symbols, the following symbols and functions are exported into the caller's namespace: Set to 0 by default, and 1 if CW-v or CW--verbose was found during the call to CWgetopt(). Extra CW-v's or CW--debug will push this variable higher. If CW-q or CW--quiet is specified, this will be less than one. It is recommended that you only ever read this variable, and pass it in via the import. This is not automatically extracted from the POD for performance reasons.

getopt(@getopt_args)
Fetch arguments via CWGetopt::Long::GetOptions. The CWbundling option is enabled by default - which differs from the standard configuration of Getopt::Long. To alter the configuration, simply call CWGetopt::Long::config. See Getopt::Long for more information.
getconf(@getopt_args)
Fetches configuration, takes arguments in the same form as BIgetopt().. The configuration file is expected to be in ~/.PROGNAMErc, /etc/perl/PROGNAME.conf, or /etc/PROGNAME.conf. Only the first found file is read, and unknown options are ignored for the time being. The file is expected to be in YAML format, with the top entity being a hash, and the keys of the hash being the same as specifying options on the command line. Using YAML as a format allows some simplificiations to getopt-style processing - CW=s% and CW=s@ style options are expected to be in a real hash or list format in the config file, and boolean options must be set to CWtrue or CWfalse (or some common equivalents). Returns the configuration file as Load()'ed by YAML in scalar context, or the argument list it was passed in list context. For example, this minimal script (missing the documentation, but hey at least it's a start);
  getopt getconf
      ( "something|s" => \$foo,
        "invertable|I!" => \$invertable,
        "integer|i=i" => \$bar,
        "string|s=s" => \$cheese,
        "list|l=s@" => \@list,
        "hash|H=s%" => \%hash, );
Will accept the following invocation styles;
  foo.pl --help
  foo.pl --version
  foo.pl --verbose
  foo.pl --debug
  foo.pl --quiet
  foo.pl --something
  foo.pl --invertable
  foo.pl --no-invertable    <=== FORM DIFFERS IN CONFIG FILE
  foo.pl --integer=7
  foo.pl --string=anything
  foo.pl --list one --list two --list three
  foo.pl --hash foo=bar --hash baz=cheese
Equivalent config files:
  something: 1
  invertable: on
  invertable: off
  integer: 7
  string: anything
  list:
    - one
    - two
    - three
  list: [ one, two, three ]
  hash:
    foo: bar
    baz: cheese
Note that more complex and possibly arcane usages of Getopt::Long features may not work with getconf (patches welcome). This can be handy for things like database connection strings; all you have to do is make an option for them, and then the user of the script, or a SysAdmin can set up their own config file for the script to automatically set the options. As BIgetconf(), but specify a filename. Prints a message to standard output, unless quiet mode (CW-q or CW--quiet) was specified. For normal program messages. Prints a message to standard output, if verbose mode (CW-v) or debug mode (CW-d) is enabled (ie, if CW$VERBOSE > 0). For messages designed to help a user of the script to see more information about what is going on. Prints a message to standard output, if debug mode (CW-d) is enabled or multiple verbose options were passed (ie, if CW$VERBOSE > 1). For messages designed to help a person debugging the script to see more information about what is going on internally to the script. Prints a short program usage message (extracted from the POD synopsis) and exits with an error code. Prints a warning to standard error. It is preceded with the text CWwarning:. The program does not exit. Prints an error message to standard error. It is preceded with the text CWerror:. The program does not exit. Prints a warning to standard error. It is preceded with the text CWwarning:. The program does not exit. Runs a command or closure, barf's with a relevant error message if there is a problem. Program output is suppressed unless running in verbose mode. CWrun() and the three alternatives listed below may perform arbitrary filehandle redirection before executing the command. This can be a very convenient way to do shell-like filehandle plumbing. For example;
  run( -in => sub { print "Hello, world!\n" },
       -out => "/tmp/outfile",
       -out2 => "/dev/null",
       @command );
This will connect the child process' standard input (CW-in) to a closure that is printing "CWHello, world!\n". The output from the closure will appear on standard input of the run command. Note that the closure is run in a sub-process and so will not be able to set variables in the calling program. It will also connect the program's standard output (CW-out) to CW/tmp/outfile, and its standard error (filehandle 2) to CW/dev/null (the traditional way of junking error output). If you wanted to connect two filehandles to the same place, you could pass in CWGLOB references instead;
  run( -out => \*MYOUT,
       -out2 => \*MYOUT,
       @command );
Any filehandle can be opened in any mode; CW-in merely defaults to meaning CW-in0, and CW-out defaults to meaning CW-out1. There is no CW-err; use CW-out2. CW-rw exists (defaulting to CW-rw0), but is probably of limited use. Here is an example of using CWprompt_passwd() to hijack CWgpg's password grabbing;
  my $password = prompt_passwd("Encryption password: ");
  my $encrypted = run( -in4 => sub { print "$password\n" },
                       "gpg", "--passphrase-fd", "4", "-c", $file )
Same as run, but returns the error code rather than assuming that the command will successfully complete. Again, output it suppressed. runs a command, capturing its output, barfs if there is a problem. Returns the output of the command as a list or a scalar. Works as capture, but the first returned item is the error code of the command ($?) rather than the first line of its output. Also, it only ever returns the output as a list. Usage:
   my ($rc, @output) = capture_err("somecommand", @args);
BIstart_timer()
BIshow_delta()
BIshow_elapsed()
These three little functions are for printing run times in your scripts. Times are displayed for running external programs with verbose mode normally, but this will let you display running times for your main program easily. Returns a number, scaled using normal scientific prefixes (from atto to exa). Optionally specify a precision which is passed to sprintf() (see sprintf in perldoc). The default is three significant figures. From Scriptalicious 1.08, the u character is used in place of the Greek mu due to encoding compatibility issues.
prompt_regex($prompt, qr/(.*)/)
Prompts for something, using the prompt "CW$prompt", matching the entered value (sans trailing linefeed) against the passed regex. Note that this differs from the previous behaviour of prompt_regex, which took a sub. Prompts for something, using the prompt "CW$prompt", feeding the sub with the entered value (sans trailing linefeed), to use a default, the passed sub should simply return it with empty input.
prompt_passwd([$prompt])
Same as CWprompt_regex, but turns off echo. CW$prompt defaults to "CWPassword: " for this function.
prompt_string([$prompt])
Prompt for a string.
prompt_int([$prompt])
get an integer Prompts for a value for CW$what. This constructs a prompt saying, eg "CWEnter value for $what". Calls the equivalent CWprompt_foo() method.
prompt_yn([$prompt])
prompts for yes or no, presuming neither
prompt_Yn([$prompt])
prompt_yN([$prompt])
prompts for yes or no, presuming yes and no, respectively.
anydump($ref)
Dump CW$ref via CWYAML, falling back to CWData::Dumper if YAML fails to load (or dies during the dump). Returns a string. Prints the template CW$template with CW$vars. CW$template may be included at the end of the file as a data section, for instance:
 use Scriptalicious;
 tsay hello => { name => "Bernie" };
 __END__
 __hello__
 Hello, [% name %]
 [% INCLUDE yomomma %]
 __yomomma__
 Yo momma's so fat your family portrait has stretchmarks.
This will print:
 Hello, Bernie
 Yo momma's so fat your family portrait has stretchmarks.
Note that the script goes to lengths to make sure that the information is always printed whether or not Template Toolkit is installed. This gets pretty verbose, but at least solves the argh! that script failed, but I don't know why because it needed this huge dependancy to tell me problem. For example, the above would be printed as:
 scriptname:
BIfoo()
If you've got a short little Perl function that implements something useful for people writing Shell scripts in Perl, then please feel free to contribute it. And if it really is scriptalicious, you can bet your momma on it getting into this module!

SEE ALSO

Simon Cozen's Getopt::Auto module does a very similar thing to this module, in a quite different way. However, it is missing CWsay, CWrun, etc. So you'll need to use some other module for those. But it does have some other features you might like and is probably engineered better.

There's a template script at Documentation and help texts in Getopt::Long that contains a script template that demonstrates what is necessary to get the basic man page / usage things working with the traditional Getopt::Long and Pod::Usage combination.

Getopt::Plus is a swiss army chainsaw of Getopt::* style modules, contrasting to this module's approach of elegant simplicity (quiet in the cheap seats!).

If you have solved this problem in a new and interesting way, or even rehashed it in an old, boring and inelegant way and want your module to be listed here, please contact the

AUTHOR

Sam Vilain, samv@cpan.org