Perl Best Practices

14.6. Interface Consistency

Ensure that your interface, run-time messages, and documentation remain consistent.

Making sure that a program's documentation matches its actual behaviour is a universal problem. And that problem is even tougher for command-line interface code, where the functionality and documentation must also stay consistent with the messages provided by the usage, help, and man flags, as well as with any diagnostics produced by the command-line processor.

The best solutions to this challenge all rely on defining the desired command-line semantics in a single place, then using some tool to generate the actual parsing code, the meta-option responses, the error diagnostics, and the documentation.

For example, a feature of the Getopt::Clade module is that its man meta-option is context-sensitive. Normally, a call like:

> illustrate --man

extracts any POD documentation from the illustrate source file, replaces the SYNOPSIS, REQUIRED ARGUMENTS, and OPTIONS sections of that documentation with a description of the actual interface that was defined, feeds the modified POD though a pod-to-text formatter, and displays it. However, if man is specified when the program's standard output stream is not attached to a terminal:

> illustrate --man > illustrate.pod

then Getopt::Clade still extracts and modifies the program's documentation, but doesn't format or page it in any way. The resulting file of raw POD can then be pasted back into the source file to ensure that the documentation is consistent with the interface.

Getopt::Clade even allows this process to be fully automated. If you type:

> illustrate --man=update

then the module will generate its own SYNOPSIS, REQUIRED ARGUMENTS, and OPTIONS sections as usual, but then edit that POD directly into the source file[*].

[*] Provided, of course, the user has write permission on that file, and can flock it.

Another CPAN module, Getopt::Euclid, takes exactly the opposite approach. Instead of generating documentation directly from the program's specification of the interface, this module generates the interface directly from the program's documentation.

Using Getopt::Euclid, you can set up a command-line interface without writing any command-line processing code[] whatsoever. All you need to do is document the interface youd like, and the module will read that description at run time and automatically create the equivalent command-line parser. Example 14-5 shows how you could reproduce the same command-line interface that was repeatedly implemented in Examples 14-1 through 14-4.

[] Except for the use Getopt::Euclid line, of course. The module is clever, but its not psychic.

Example 14-5. Command-line parsing via Getopt::Euclid

# Handle command lines of the form:

#

# > orchestrate -in source.txt -o=dest.orc --verbose

# Create a command-line parser that implements the documentation below... use Getopt::Euclid;

# Report intended behaviour... if ($ARGV{-v}) { print "Loading first $ARGV{-l} chunks of file: $ARGV{-i}\n" }

# etc.

_ _END_ _ =head1 NAME orchestrate - Convert a file to Melkor's .orc format =head1 VERSION This documentation refers to orchestrate version 1.9.4 =head1 USAGE orchestrate -in source.txt -out dest.orc [options] =head1 OPTIONS =over =item -i[n] [=] <file> Specify input file =for Euclid: file.type: readable file.default: '-' =item -o[ut] [=] <file> Specify output file =for Euclid: file.type: writable file.default: '-' =item -l[en] [=] <l> Display length (default is 24 lines) =for Euclid: l.type: integer > 0 l.default: 24 =item -w[id] [=] <w> Display width (default is 78 columns) =for Euclid: w.type: integer > 0 w.default: 78 =item -v =item --verbose Print all warnings =item --version =item --usage =item --help =item --man Print the usual program information =back =begin remainder of documentation here...

At first glance, this approach looks like it requires far more effort than, say, Getopt::Clade did in Example 14-4. But that's not actually the case. The previous versions of this command-line parser didn't include the POD that the application would still require (see Chapter 7). If the length of that necessary documentation is taken into account, the Getopt::Euclid version is by far the quickest and easiest solution. It requires only a single line of code, and a small number of =for Euclid annotations in the POD itself.

Happily, it's also the most robust and maintainable approach. Using this module, it's simply impossible for the documentation, command-line processing behaviour, or diagnostic messages ever to differ in any way. And maintainers don't even have to type -man=update to ensure proper synchronization when the command-line specification changes.

The only significant limitation of the POD-based approach used by Getopt::Euclid is that the resulting interface must be explicit in the documentation and is fixed at the time that documentation is written. Procedural specifications, like those used by Getopt::Long or Getopt::Clade, allow the interface to be defined at run time. Such situations are comparatively rare, but may arise if your interface depends on external resources.

Категории