Multiple Scenario Script
Today I’d like to talk about another programming technique I employ from time to time for interactive command line tools. Previously, I have already presented a nice pattern to allow rapid testing/execution of interactive command line programs. In this post, I will deal with the situation where a tool should be able to execute different yet similar tasks. The user should in the very beginning choose which type of task (“scenario”) she wants to get executed.
I said similar task to that it makes sense to group them within the same program.
Another way of dealing with the situation would obviously be to modularize the business logic as much as you can and design a minimal program for each task.
Or use a common Unix pattern by setting up a symlink for each task and let them all link to the same executable.
The latter then executes a task depending on the name of $0
, i.e. the called program name.
However, if there a lot of tasks to execute, the end user gets confused quickly. Imagine a database query tool which allows to answer a long but predefined list of queries: It’s easiest to present the list of 15-20 questions to the user and let her decide which query to run. This also allows to explain each query in more detail with a little menu instead of having the user to remember which program runs which query.
I have prepared a simplified Ruby script to demonstrate the technique. Have a quick look at the script before I continue with the details below:
(Download)
The above scripts defines set of tasks (ACTIONS
) which are represented by simple functions (heat_milk()
etc.)
When running the script, one gets to see the list of actions and chooses one.
The appropriate function is then called.
The script allows to provide the task to be chosen by the command line with --aid <id>
or --aid <name>
to allow easier testing and meaningful cronjobs:
Looking at the code, the “magic” happens in the last two code lines.
get_action_id()
returns the id of the task chosen by the user.
Referring to the ACTIONS
hash, the appropriate function name is then chosen and executed with with send
.
The above pattern has multiple advantages.
Adding a new task is extremely simple.
One just needs to add a new entry in the ACTIONS
hash and point to the appropriate function implementing the task (heat_milk()
etc.)
This is especially handy when the set of tasks is not clear during development (never happens of course!).
Providing an action id by hand (1
, 2
, 3
) seems a bit weird in the first place but is convenient for interactive usage.
Using a “special” id that has to be typed in will make the user understand that she deliberately chose something abnormal:
You should treat each implemented task as a separate main program.
This will allow easier unit testing (you can mock all user input) and refactoring.
In the sample Ruby script, I have already forwarded the opts
arguments to the callee for that purpose (doesn’t work with silly getoptlong
but you’ll get the idea).
Also, not how generic get_action_id()
is.
It only needs some way to check the provided command line arguments and the actions
hash.
In one project, I had multiple Perl scripts (query.pl
, modify.pl
, dump.pl
) which all presented their own list of actions to the end user to chose from.
I defined get_action_id()
once and used it in all scripts each time providing the appropriate hash map.
Each script’s name was general enough to help the end user choose the type of task.
Everything else was then handled interactively.
The framework proved to be intuitive enough for the end user yet simple enough for developing.