A small command-line tool keeps a big if chain that maps each command word to its function, and every new command means editing that chain. Instead, each function should sign itself up where it is defined:
cli = Registry()
@cli.command("add")
def add(a, b):
return int(a) + int(b)
cli.run("add 2 3") # 5
Write a class Registry with these methods:
command(name=None)returns a decorator. The decorator records the function undername, or under the function's own__name__when no name is given (@cli.command()), and returns the same function object, unchanged, so it can still be called directly.- Registering a name that is already in use raises
ValueError, and the first registration stays. run(line)splitslineon whitespace. The first word is the command name; the other words are passed to the function as string arguments, in order.runreturns what the function returns. An unknown command raisesKeyError.names()returns the registered names as a sorted list.
Each Registry has its own commands. Setup helpers you can use with Run: calculator(lines), unchanged(), duplicate_name(), two_registries() and raises(fn, *args), which returns the name of the exception a call raises (or None).
Examples
Input: calculator(["add 2 3", "neg 7", "shout hello there", "count", "mul 2 3"])
Output: (["add", "count", "neg", "shout"], [5, -7, "HELLO THERE!", 4, "KeyError"])
Input: unchanged()
Output: (True, 1.5, 3.0)
Constraints
- Command names contain no spaces;
runreceives at least one word. - Arguments that a function does not accept make it raise
TypeError, whichrunlets through.
Goals
- Write a decorator factory: a method that takes a setting and returns a decorator
- Use a decorator to record a function somewhere instead of wrapping it
- Dispatch a text command to the function registered under its first word