advanced Step 20 of 20

Building a CLI Tool

Python Programming

Building a CLI Tool

Command-line interface (CLI) tools are programs that run in the terminal and accept arguments, options, and subcommands. They are essential for automation, system administration, development workflows, and DevOps. Python is an excellent choice for building CLI tools thanks to its readable syntax, rich standard library, and powerful third-party packages. In this lesson, you will learn to build a fully-featured CLI tool using Python's built-in argparse module and see how to structure it for real-world use.

Basic argparse Usage

import argparse

def main():
    parser = argparse.ArgumentParser(
        description="A simple greeting CLI tool"
    )

    # Positional argument
    parser.add_argument("name", help="Name of the person to greet")

    # Optional arguments
    parser.add_argument(
        "-g", "--greeting",
        default="Hello",
        help="Greeting to use (default: Hello)"
    )
    parser.add_argument(
        "-n", "--count",
        type=int,
        default=1,
        help="Number of times to greet"
    )
    parser.add_argument(
        "-u", "--uppercase",
        action="store_true",
        help="Print greeting in uppercase"
    )

    args = parser.parse_args()

    message = f"{args.greeting}, {args.name}!"
    if args.uppercase:
        message = message.upper()

    for _ in range(args.count):
        print(message)

if __name__ == "__main__":
    main()

# Usage:
# python greet.py Alice
# python greet.py Alice -g "Hi" -n 3
# python greet.py Alice --uppercase
# python greet.py --help

Subcommands

import argparse
import json
import os

DB_FILE = "tasks.json"

def load_tasks():
    if os.path.exists(DB_FILE):
        with open(DB_FILE, "r") as f:
            return json.load(f)
    return []

def save_tasks(tasks):
    with open(DB_FILE, "w") as f:
        json.dump(tasks, f, indent=2)

def cmd_add(args):
    tasks = load_tasks()
    task = {
        "id": len(tasks) + 1,
        "title": args.title,
        "done": False,
        "priority": args.priority
    }
    tasks.append(task)
    save_tasks(tasks)
    print(f"Added task #{task['id']}: {task['title']}")

def cmd_list(args):
    tasks = load_tasks()
    if not tasks:
        print("No tasks found.")
        return

    for task in tasks:
        status = "x" if task["done"] else " "
        pri = task.get("priority", "medium")
        print(f"  [{status}] #{task['id']} [{pri}] {task['title']}")

def cmd_done(args):
    tasks = load_tasks()
    for task in tasks:
        if task["id"] == args.task_id:
            task["done"] = True
            save_tasks(tasks)
            print(f"Marked task #{args.task_id} as done.")
            return
    print(f"Task #{args.task_id} not found.")

def main():
    parser = argparse.ArgumentParser(
        description="Task Manager CLI"
    )
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # 'add' subcommand
    add_parser = subparsers.add_parser("add", help="Add a new task")
    add_parser.add_argument("title", help="Task title")
    add_parser.add_argument(
        "-p", "--priority",
        choices=["low", "medium", "high"],
        default="medium"
    )

    # 'list' subcommand
    subparsers.add_parser("list", help="List all tasks")

    # 'done' subcommand
    done_parser = subparsers.add_parser("done", help="Mark a task as done")
    done_parser.add_argument("task_id", type=int, help="Task ID to complete")

    args = parser.parse_args()

    if args.command == "add":
        cmd_add(args)
    elif args.command == "list":
        cmd_list(args)
    elif args.command == "done":
        cmd_done(args)
    else:
        parser.print_help()

if __name__ == "__main__":
    main()

# Usage:
# python tasks.py add "Learn Python" -p high
# python tasks.py add "Buy groceries"
# python tasks.py list
# python tasks.py done 1

Adding Color and Formatting

# ANSI color codes for terminal output
class Colors:
    RED = "\033[91m"
    GREEN = "\033[92m"
    YELLOW = "\033[93m"
    BLUE = "\033[94m"
    BOLD = "\033[1m"
    RESET = "\033[0m"

def success(msg):
    print(f"{Colors.GREEN}{msg}{Colors.RESET}")

def error(msg):
    print(f"{Colors.RED}Error: {msg}{Colors.RESET}")

def warning(msg):
    print(f"{Colors.YELLOW}Warning: {msg}{Colors.RESET}")

def header(msg):
    print(f"\n{Colors.BOLD}{Colors.BLUE}{msg}{Colors.RESET}")
    print("=" * len(msg))

# Usage in the task manager
header("Task List")
success("Task added successfully!")
error("Task not found.")

Making It Installable

# pyproject.toml
# [project]
# name = "task-cli"
# version = "1.0.0"
# description = "A simple task manager CLI"
# requires-python = ">=3.8"
#
# [project.scripts]
# tasks = "task_cli.main:main"

# After: pip install -e .
# You can run: tasks add "My task"
# Instead of: python tasks.py add "My task"
Pro tip: For more advanced CLI tools, consider using the click or typer library instead of argparse. Click uses decorators for a cleaner API, while Typer leverages Python type hints to automatically generate CLI interfaces with minimal code. Both handle colors, progress bars, and user prompts elegantly.

Key Takeaways

  • Use argparse for building CLI tools with positional arguments, optional flags, and subcommands.
  • Subcommands (like git commit, git push) organize complex CLI tools into logical operations.
  • Use choices, type, and default parameters to validate and process user input automatically.
  • Add ANSI color codes for better terminal output readability and user experience.
  • Make your CLI installable with pyproject.toml entry points so users can run it as a system command.
arrow_back Virtual Environments check_circle Lap Complete!