Files
todo-cli/todo_cli/main.py
T
2026-06-06 12:36:25 +00:00

214 lines
7.5 KiB
Python

"""Main entry point and CLI interface for the Todo CLI application.
This module defines the TodoCLI class that handles command-line argument
parsing and user interaction, as well as the main() function to start
the application.
"""
import argparse
import sys
from typing import List, Optional
from todo import TodoManager
from storage import Storage
class TodoCLI:
"""Command-line interface for managing todo items.
Attributes:
manager: The TodoManager instance handling business logic.
parser: The ArgumentParser for parsing command-line arguments.
"""
def __init__(self, manager: TodoManager) -> None:
"""Initialize the CLI with a TodoManager.
Args:
manager: A TodoManager instance for data operations.
"""
self.manager = manager
self.parser = argparse.ArgumentParser(
description="A simple CLI todo list manager."
)
self._setup_parser()
def _setup_parser(self) -> None:
"""Configure subparsers for each command."""
subparsers = self.parser.add_subparsers(dest="command", help="Available commands")
# add command
add_parser = subparsers.add_parser("add", help="Add a new todo item")
add_parser.add_argument("title", type=str, help="Title of the task")
add_parser.add_argument("-d", "--description", type=str, default="",
help="Description of the task")
add_parser.add_argument("-due", "--due_date", type=str, default="",
help="Due date (e.g., 2025-01-01)")
# list command
list_parser = subparsers.add_parser("list", help="List all todo items")
list_parser.add_argument("-s", "--status", type=str, choices=["done", "pending"],
help="Filter by completion status")
# done command
done_parser = subparsers.add_parser("done", help="Mark a todo item as completed")
done_parser.add_argument("item_id", type=int, help="ID of the item to mark done")
# undone command
undone_parser = subparsers.add_parser("undone", help="Mark a todo item as not completed")
undone_parser.add_argument("item_id", type=int, help="ID of the item to mark undone")
# delete command
delete_parser = subparsers.add_parser("delete", help="Delete a todo item")
delete_parser.add_argument("item_id", type=int, help="ID of the item to delete")
# edit command
edit_parser = subparsers.add_parser("edit", help="Edit a todo item")
edit_parser.add_argument("item_id", type=int, help="ID of the item to edit")
edit_parser.add_argument("--title", type=str, default=None,
help="New title (leave unchanged if omitted)")
edit_parser.add_argument("--description", type=str, default=None,
help="New description (leave unchanged if omitted)")
edit_parser.add_argument("--due_date", type=str, default=None,
help="New due date (leave unchanged if omitted)")
edit_parser.add_argument("--completed", type=str, choices=["true", "false"],
default=None,
help="Set completion status (true/false)")
def run(self, args: Optional[List[str]] = None) -> None:
"""Parse arguments and execute the corresponding command.
Args:
args: Command-line arguments (defaults to sys.argv[1:]).
"""
parsed_args = self.parser.parse_args(args)
if parsed_args.command is None:
self.parser.print_help()
return
command_handlers = {
"add": self._handle_add,
"list": self._handle_list,
"done": self._handle_done,
"undone": self._handle_undone,
"delete": self._handle_delete,
"edit": self._handle_edit,
}
handler = command_handlers.get(parsed_args.command)
if handler:
handler(parsed_args)
else:
self.parser.print_help()
def _handle_add(self, args: argparse.Namespace) -> None:
"""Handle the 'add' command.
Args:
args: Parsed arguments containing title, description, due_date.
"""
item = self.manager.add(args.title, args.description, args.due_date)
print(f"Added todo item #{item.id}: {item.title}")
def _handle_list(self, args: argparse.Namespace) -> None:
"""Handle the 'list' command.
Args:
args: Parsed arguments (may contain status filter).
"""
if args.status == "done":
items = self.manager.list_by_status(True)
elif args.status == "pending":
items = self.manager.list_by_status(False)
else:
items = self.manager.list_all()
if not items:
print("No todo items found.")
return
# Print header
print(f"{'ID':<4} {'Title':<30} {'Completed':<10} {'Due Date':<12} {'Description'}")
print("-" * 80)
for item in items:
completed_str = "✓" if item.completed else "✗"
due = item.due_date if item.due_date else "-"
desc = item.description if item.description else ""
print(f"{item.id:<4} {item.title:<30} {completed_str:<10} {due:<12} {desc}")
def _handle_done(self, args: argparse.Namespace) -> None:
"""Handle the 'done' command.
Args:
args: Parsed arguments containing item_id.
"""
success = self.manager.mark_done(args.item_id)
if success:
print(f"Marked item #{args.item_id} as completed.")
else:
print(f"Error: Item #{args.item_id} not found.")
def _handle_undone(self, args: argparse.Namespace) -> None:
"""Handle the 'undone' command.
Args:
args: Parsed arguments containing item_id.
"""
success = self.manager.mark_undone(args.item_id)
if success:
print(f"Marked item #{args.item_id} as not completed.")
else:
print(f"Error: Item #{args.item_id} not found.")
def _handle_delete(self, args: argparse.Namespace) -> None:
"""Handle the 'delete' command.
Args:
args: Parsed arguments containing item_id.
"""
success = self.manager.delete(args.item_id)
if success:
print(f"Deleted item #{args.item_id}.")
else:
print(f"Error: Item #{args.item_id} not found.")
def _handle_edit(self, args: argparse.Namespace) -> None:
"""Handle the 'edit' command.
Args:
args: Parsed arguments containing item_id and optional fields.
"""
# Convert --completed string to bool if provided
completed = None
if args.completed is not None:
completed = args.completed.lower() == "true"
success = self.manager.edit(
args.item_id,
title=args.title,
description=args.description,
due_date=args.due_date,
completed=completed,
)
if success:
print(f"Updated item #{args.item_id}.")
else:
print(f"Error: Item #{args.item_id} not found.")
def main() -> None:
"""Application entry point.
Creates a Storage instance with default path, initializes TodoManager,
and runs the CLI.
"""
storage = Storage()
manager = TodoManager(storage)
cli = TodoCLI(manager)
cli.run()
if __name__ == "__main__":
main()