First-Class CLI Applications
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.
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:
Usage: christmas christmas [-an] [long options...] -n --nice list only nice people -a --all list even people for whom shopping is done
We also get a phase before execution but after argument processing to decide whether the arguments we were given make any sense -- here we just ensure that we didn't get any!
Putting it to the Test
One of the big reasons to use App::Cmd was supposed to be its testability, so let's see how that works.
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
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
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:
Extra commands just need those three original methods, for example:
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
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
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
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:
...and then all your commands could use routines like
prompt_any_key without you having to waste keystrokes on ugly method calls to some object delegate.