About this mini-article series. Each day for 24 days, I will be reviewing a module that parses command-line options (such module is usually under the Getopt::* namespace). First article is here.
Traditionally, option parsing modules in Perl like Getopt::Std and Getopt::Long
do not have the concept of subcommands. The rising popularity of CLI programs with subcommands, specifically git and other post-CVS version control tools, has prompted the option parsing libraries to include the concept too, like in node’s commander or Python’s argparse. In Perl, there are not many option parsing libraries that offer this feature (although ports of other languages’ libraries including argparse exist on CPAN, which I’ll cover in the following days). That said, you can also use a higher-level library like CLI frameworks that support subcommands.
App::Cmd is one such module: it is an OO CLI application framework which happens to use Getopt::Long::Descriptive as the command-line options parser. Both modules are written by Ricardo “Rik” Signes (RJBS). App::Cmd was first released in 2006 and is still being updated. Around 60 CPAN distributions use App::Cmd (90 if we also count users of MooseX::App::Cmd), making it possibly the most popular CLI application framework on CPAN. Toby Inkster (TOBYINK) even once called it “the PSGI of the command-line world”, although I don’t think that analogy is appropriate. A very popular CLI application, dzil (Dist::Zilla), also by Rik, uses App::Cmd.
As mentioned, App::Cmd is meant to be used to write CLI application which has subcommands (or commands, as it call them) like ‘git’ or ‘dzil’ (with ‘git clone’ or ‘dzil build’ as examples of command with subcommand). To use App::Cmd in your application, you need to create a single application class and then one class for each subcommand you want to support. App::Cmd does not use Moo or Moose, making it more universally usable. Of course, your application or command classes can be Moo-based or Moose-based, as demonstrated by MooseX::App::Cmd and dzil. The CLI script itself is reduced to something like:
use YourApp; # your application class YourApp->run;
Accepting and processing command-line options is pretty direct, if not low-level:
package YourApp::Command::cmd1; # a command class sub opt_spec { return ( [ "skip-check|C", "skip checking stuffs", ], [ "sleep-between|s=i", "delay between processing file", { default =>5 } ], ); } # optional sub usage_desc { "blah blah" } # optional sub validate_args { my ($self, $opt, $args) = @_; $self->usage_error("Please supply at least one file") unless @$args; $self->usage_error("Please specify a positive number") unless $opt->sleep_between >= 0; } sub execute { my ($self, $opt, $args) = @_; ... }
You provide a method opt_spec in your command class to specify which command-line options your subcommand will accept. The value returned by this method will be passed directly to Getopt::Long::Descriptive. The parse result will be $opt object (which is something you normally get from the Getopt::Long::Descriptive’s describe_options function) as well as $args (the remaining command-line arguments from @ARGV after the options has been stripped from).
You can also provide the usage description to be passed to Getopt::Long::Descriptive’s describe_options via the usage_desc method. And an additional method validate_args to further validate $opt and $args if needed. The main method in a command class is execute, which is fed $opt and $args.
So you can see this does not differ much from a “traditional” CLI using Getopt::Long. You still provide the command-line options specification manually. This differs from other CLI frameworks like MooseX::Getopt or my Perinci::CmdLine which try to be more DRY by directly setting object attributes or function arguments from command-line options.
Another thing to note is that no configuration file support is baked in: you need to read and parse configuration files yourself. So basically what App::Cmd provides is the structure. For getting many higher-level CLI features, you need to do on your own. App::Cmd is mature and widely used, but you might also want to take a look at some other CLI frameworks that do more stuffs for you.