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:

#!/usr/bin/ruby

require 'getoptlong' # keep it simple for the demo

# This is the list of different tasks the end user gets to choose from.
ACTIONS = {
  1 => {name: 'eggs', desc: 'Make some nice eggs.', func: 'scramble_eggs'},
  2 => {name: 'milk', desc: 'Serve some milk.', func: 'heat_milk'},
  3 => {name: 'fruits', desc: 'Apples & Bananas!', func: 'slice_fruits'},
}

def get_action_id(opts, actions)
  # Get the provided command line argument. This would not work if more cmd
  # line arguments were possible; just for the sake of the example.
  _, aid = opts.get

  if aid == nil # no cmd line argument provided, show menu
    actions.each do |_aid, value|
      puts "%i: %s [aid '%s' or '%s']" % [_aid, actions[_aid][:desc], _aid, actions[_aid][:name]]
    end
    print "Please choose: "
    aid = gets.chomp
  else
    # Check if provided cmd line argument is the action name, not id
    name2aid = {} # temporary mapping 
    actions.each do |_aid, value|
      name2aid[actions[_aid][:name]] = _aid
    end
    if name2aid.key?(aid) # cmd line argument provided as name
      aid = name2aid[aid].to_s
    end
  end

  # Ensure that the returned aid is indeed a valid key of the "actions" hash.
  if aid.to_i.to_s != aid or !actions.has_key?(aid.to_i)
    abort("Action '#{aid}' is invalid!")
  end
  aid = aid.to_i
  return aid
end

# Task
def scramble_eggs(opts)
  puts 'Making eggs ...'
end

# Task
def heat_milk(opts)
  puts 'Heating milk ...'
end

# Task
def slice_fruits(opts)
  puts 'Slicing fruits ...'
end

# "main"
opts = GetoptLong.new(
  ['--aid', GetoptLong::REQUIRED_ARGUMENT],
)
# Choose task.
aid = get_action_id(opts, ACTIONS)
# Ruby arcane to call the named function.
# In other languages, a function pointer would do the trick.
send(ACTIONS[aid][:func], opts)

(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:

$ ruby kitchen_robot_choose_action.rb
1: Make some nice eggs. [aid '1' or 'eggs']
2: Serve some milk. [aid '2' or 'milk']
3: Apples & Bananas! [aid '3' or 'fruits']
Please choose: 2
Heating milk ...

$ ruby code/kitchen_robot_choose_action.rb --aid 3
Slicing fruits ...

$ ruby code/kitchen_robot_choose_action.rb --aid "fruits"
Slicing fruits ...

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:

1: Make some nice eggs. [aid '1' or 'eggs']
2: Serve some milk. [aid '2' or 'milk']
3: Apples & Bananas! [aid '3' or 'fruits']
99: Destroy Kitchen (DANGEROUS!) [aid '99' or 'destroy']
Please choose:

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.