First-Class CLI Applications

App::Cmd - 2009-12-14

Scripts are Our Friends

In my experience, it's pretty common to find a large piece of functionality built into a command-line program (or "script" (and I am totally not going to get into the "script" vs. "program" debate here)) and then to find that the program isn't tested at all. When you say, "For the love of God, why are you not testing this vital program?" the answer is, "Well, scripts are really hard to test!"

It's true. Maybe they're not as hard to test as people think, but it's still a pain. They're also full of too many standards for getopt or error messages. People just do whatever gets work done, and then later have to pay the price for making crazy decisions.

A Simple App::Cmd Program

App::Cmd is a simple framework for writing command-line applications that are easy to test, that have powerful and easy to use standard tools, and that can be extended easily.

For example, here's a simple command we might write, in two parts. First, the script that we put in our path, then the library that implements it.

my@presents=Christmas::Presents->get( # In other words: use switches to build query:($opt->all?():(done=>0),($opt->nice?(nice=>1):()),);

print$self->_list_presents(@presents);# implementation left to imagination}

So far, we've only added a little structure to our code, but it's already a big help. The opt_spec routine uses Getopt::Long::Descriptive to not only process command line switches (with quite a lot of power), but also to generate helpful usage messages like:

This should be fairly straightforward: test_app runs the application, using the passed arrayref as the value for @ARGS. It doesn't run in a subprocess, so there's no weird issues with interprocess communication. Also, because it runs in process, you can replace hunks of the app with mocks if you want, and you'll have them available for inspection after testing.

Organizing Complex Interfaces

I didn't write App::Cmd for simple programs, though, I wrote it for complex ones. I wanted to write programs that behave like svn or git, where the first thing you tell the command-line program is which of its subcommands you want to run. So, maybe the program we wrote above is meant to be run as christmas list. We also want to have christmas music to control our MP3 player and christmas cards to assemble and send off some mkit Christmas cards.

This is easy, we do it like this:

rename Christmas::App to Christmas::App::Command::list

replace its use base 'App::Cmd::Simple' with use Christmas::App -command

create a new Christmas::App (shown below)

create Christmas::App::Command::music and ::cards

Christmas::App is easy to write; here it is in its entirety:

1: 2: 3:

packageChristmas::App;useApp::Cmd::Setup-app;1;

Extra commands just need those three original methods, for example:

1: 2: 3: 4: 5: 6:

packageChristmas::App::Command::music;useChristmas::App-command;

subvalidate_args{...}subopt_spec{...}subexecute{...}

That's it. Now the new Christmas::App will be run, it will find all the command classes we've written, and it will decide which one to execute based on the first argument to the christmas command.

Other Cool Stuff

When you write a "full" App::Cmd program -- that is, one that uses App::Cmd and not App::Cmd::Simple -- you get a bunch more features for free. For one thing, you get commands for commands and help that can list and describe the other available commands. (commands is what happens by default if you were to run christmas with no arguments, but you can change that by writing a default_command method.)

You get access to the plugins system, which is woefully underdocumented, but allows you to set up easy to use routines in all your commands, so that you could say, in Christmas::App: