"""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()