Skip to Content
Course content

442: Capstone: Building a Multi-Feature Command-Line Productivity Suite

Click on the "Edit" button in the top corner of the screen to edit your slide content.

We've reached the capstone. By now, you know how to handle files, manage state, and wrap logic in functions. But there is a huge difference between writing a script that works and building a suite of tools that feels professional. When you're building something like a productivity suite—let's say a single CLI tool that handles a To-Do list, a Pomodoro timer, and a Quick-Note logger—the biggest trap you'll fall into is the "God Function."

The Giant If-Else Loop

When I first started building multi-tool CLIs, my instinct was to keep everything visible. I’d write a main() function that acted like a switchboard. You’d see a while True loop, a printout of options, and a massive block of if/elif/else statements to route the user to the right feature. It looks something like this:

def main():
    while True:
        cmd = input("Enter command (todo, note, timer, quit): ")
        if cmd == "todo":
            handle_todo()
        elif cmd == "note":
            handle_note()
        elif cmd == "timer":
            handle_timer()
        elif cmd == "quit":
            break
        else:
            print("Unknown command")

On the surface, this is fine. It's readable and it works for three features. But this is where the "naive" approach breaks. Imagine we want to add a "Calendar" feature, a "Budget" tracker, and a "Habit" logger. Suddenly, your main() function is 200 lines of routing logic. Even worse, every time you add a feature, you have to touch the main() function. In a professional environment, that's a red flag; you want to be able to add new functionality without risking the stability of the core entry point.

The Dispatcher Pattern

The better way—the way I approach this now—is to decouple the routing from the execution. Instead of hard-coding the logic into an if-else chain, we use a dispatch table. In Python, this is usually a dictionary where the keys are the command names and the values are the function objects themselves. This transforms your logic from a series of checks into a simple lookup.

def handle_todo():
    print("Managing your tasks...")

def handle_note():
    print("Writing a quick note...")

# This is our Registry
COMMANDS = {
    "todo": handle_todo,
    "note": handle_note,
}

def main():
    while True:
        cmd = input("Enter command: ").strip().lower()
        if cmd == "quit":
            break
        
        # Look up the function in the dictionary
        action = COMMANDS.get(cmd)
        if action:
            action() # Call the function directly
        else:
            print(f"Sorry, I don't know how to '{cmd}'.")

Now, look at the difference. If I want to add a Pomodoro timer, I don't touch the main() loop at all. I just write the handle_timer() function and add one line to the COMMANDS dictionary. I've effectively separated the "How it's called" from the "What it does."

Maintenance Sanity vs. Initial Overhead

You might be thinking, "Is this really worth it for a small project?" If your tool only has two commands and will never grow, then no, the if-else block is faster to write. But software is rarely static. The moment you move from a "script" to a "suite," you're managing a system.

The dispatcher pattern gives you a few things the naive way doesn't:

  • Dynamic Loading: You could eventually move each command into its own file and import them into the registry, keeping your project directory clean.
  • Easier Testing: You can test handle_todo() in isolation without having to simulate a user typing into a while loop.
  • Better Error Handling: You have a single, unified place (the .get() call) to handle "Command Not Found" errors, rather than relying on a trailing else block that might get buried under fifty elifs.

I've seen too many junior devs spend an entire afternoon debugging a typo in a 300-line if-else chain. Don't be that person. Build the registry, keep your main() lean, and let your features live in their own space.




📋 Practical Task

Build the "OmniTool" Command Dispatcher

Your task is to implement the core routing logic for a productivity suite called OmniTool. Instead of using a chain of if-statements, you must implement a dictionary-based dispatcher.

Requirements:

  • Create three distinct functions: task_manager(), focus_timer(), and journal_entry(). Each should simply print a message confirming which tool was activated.
  • Create a COMMAND_REGISTRY dictionary that maps the strings "tasks", "focus", and "journal" to these functions.
  • Implement a run_suite() function that:
    • Prompts the user for input in a loop.
    • Executes the corresponding function from the registry if the input matches.
    • Prints a specific "Command not recognized" message if the input is not in the registry.
    • Exits the loop gracefully if the user types "exit".

Challenge: Try making the input case-insensitive so that "TASKS" and "tasks" both trigger the task manager.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.