|
|
"""Main CLI interface for Todo application.""" |
|
|
|
|
|
import sys |
|
|
from datetime import datetime |
|
|
|
|
|
from ..models.todo import TodoStatus |
|
|
from ..services.todo_service import ( |
|
|
AlreadyCompletedError, |
|
|
InvalidTitleError, |
|
|
TodoNotFoundError, |
|
|
TodoService, |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_todo(todo) -> str: |
|
|
"""Format todo for display. |
|
|
|
|
|
Args: |
|
|
todo: Todo object to format |
|
|
|
|
|
Returns: |
|
|
Formatted string representation |
|
|
""" |
|
|
description = todo.description if todo.description else "No description" |
|
|
created_str = todo.created_at.strftime("%Y-%m-%d %H:%M:%S") |
|
|
return ( |
|
|
f"ID: {todo.id}\n" |
|
|
f"Title: {todo.title}\n" |
|
|
f"Description: {description}\n" |
|
|
f"Status: {todo.status.value}\n" |
|
|
f"Created: {created_str}" |
|
|
) |
|
|
|
|
|
|
|
|
def format_todo_list_item(todo) -> str: |
|
|
"""Format todo for list display. |
|
|
|
|
|
Args: |
|
|
todo: Todo object to format |
|
|
|
|
|
Returns: |
|
|
Formatted string for list view |
|
|
""" |
|
|
return f"ID: {todo.id} | Title: {todo.title} | Status: {todo.status.value}" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cmd_create_todo(service: TodoService) -> None: |
|
|
"""Create a new todo. |
|
|
|
|
|
Args: |
|
|
service: TodoService instance |
|
|
""" |
|
|
print("\n=== Create Todo ===") |
|
|
title = input("Enter title (required): ").strip() |
|
|
|
|
|
if not title: |
|
|
print("\nERROR: Title cannot be empty.") |
|
|
return |
|
|
|
|
|
description = input("Enter description (optional, press Enter to skip): ").strip() |
|
|
description = description if description else None |
|
|
|
|
|
try: |
|
|
todo = service.create_todo(title, description) |
|
|
print("\n=== Todo Created Successfully ===") |
|
|
print(format_todo(todo)) |
|
|
except (InvalidTitleError, Exception) as e: |
|
|
print(f"\nERROR: {e}") |
|
|
|
|
|
|
|
|
def cmd_list_todos(service: TodoService) -> None: |
|
|
"""List all todos. |
|
|
|
|
|
Args: |
|
|
service: TodoService instance |
|
|
""" |
|
|
print("\n=== Your Todos ===") |
|
|
todos = service.list_todos() |
|
|
|
|
|
if not todos: |
|
|
print("\nNo todos found.") |
|
|
return |
|
|
|
|
|
for todo in todos: |
|
|
print(format_todo_list_item(todo)) |
|
|
|
|
|
|
|
|
def cmd_view_todo(service: TodoService) -> None: |
|
|
"""View details of a specific todo. |
|
|
|
|
|
Args: |
|
|
service: TodoService instance |
|
|
""" |
|
|
print("\n=== View Todo ===") |
|
|
todo_id_str = input("Enter todo ID: ").strip() |
|
|
|
|
|
try: |
|
|
todo_id = int(todo_id_str) |
|
|
except ValueError: |
|
|
print("\n❌ Error: Todo ID must be a number.") |
|
|
return |
|
|
|
|
|
try: |
|
|
todo = service.get_todo_by_id(todo_id) |
|
|
print("\n=== Todo Details ===") |
|
|
print(format_todo(todo)) |
|
|
except TodoNotFoundError as e: |
|
|
print(f"\nERROR: {e}") |
|
|
|
|
|
|
|
|
def cmd_update_todo(service: TodoService) -> None: |
|
|
"""Update an existing todo. |
|
|
|
|
|
Args: |
|
|
service: TodoService instance |
|
|
""" |
|
|
print("\n=== Update Todo ===") |
|
|
todo_id_str = input("Enter todo ID: ").strip() |
|
|
|
|
|
try: |
|
|
todo_id = int(todo_id_str) |
|
|
except ValueError: |
|
|
print("\n❌ Error: Todo ID must be a number.") |
|
|
return |
|
|
|
|
|
|
|
|
try: |
|
|
current_todo = service.get_todo_by_id(todo_id) |
|
|
except TodoNotFoundError as e: |
|
|
print(f"\nERROR: {e}") |
|
|
return |
|
|
|
|
|
print(f"\nCurrent title: {current_todo.title}") |
|
|
new_title = input("Enter new title (press Enter to keep current): ").strip() |
|
|
|
|
|
print(f"\nCurrent description: {current_todo.description or 'No description'}") |
|
|
new_description = input("Enter new description (press Enter to keep current): ").strip() |
|
|
new_description = new_description if new_description else None |
|
|
|
|
|
|
|
|
if not new_title and new_description is None: |
|
|
print("\nNo changes provided.") |
|
|
return |
|
|
|
|
|
try: |
|
|
updated_todo = service.update_todo( |
|
|
todo_id, |
|
|
title=new_title if new_title else None, |
|
|
description=new_description, |
|
|
) |
|
|
print("\n✅ Todo updated successfully!") |
|
|
print(format_todo(updated_todo)) |
|
|
except (TodoNotFoundError, InvalidTitleError, Exception) as e: |
|
|
print(f"\nERROR: {e}") |
|
|
|
|
|
|
|
|
def cmd_complete_todo(service: TodoService) -> None: |
|
|
"""Mark a todo as completed. |
|
|
|
|
|
Args: |
|
|
service: TodoService instance |
|
|
""" |
|
|
print("\n=== Complete Todo ===") |
|
|
todo_id_str = input("Enter todo ID: ").strip() |
|
|
|
|
|
try: |
|
|
todo_id = int(todo_id_str) |
|
|
except ValueError: |
|
|
print("\n❌ Error: Todo ID must be a number.") |
|
|
return |
|
|
|
|
|
try: |
|
|
todo = service.complete_todo(todo_id) |
|
|
print("\n✅ Todo marked as completed!") |
|
|
print(format_todo(todo)) |
|
|
except AlreadyCompletedError as e: |
|
|
print(f"\nERROR: {e}") |
|
|
except TodoNotFoundError as e: |
|
|
print(f"\nERROR: {e}") |
|
|
|
|
|
|
|
|
def cmd_delete_todo(service: TodoService) -> None: |
|
|
"""Delete a todo. |
|
|
|
|
|
Args: |
|
|
service: TodoService instance |
|
|
""" |
|
|
print("\n=== Delete Todo ===") |
|
|
todo_id_str = input("Enter todo ID: ").strip() |
|
|
|
|
|
try: |
|
|
todo_id = int(todo_id_str) |
|
|
except ValueError: |
|
|
print("\n❌ Error: Todo ID must be a number.") |
|
|
return |
|
|
|
|
|
try: |
|
|
service.delete_todo(todo_id) |
|
|
print(f"\n✅ Todo {todo_id} deleted successfully!") |
|
|
except TodoNotFoundError as e: |
|
|
print(f"\nERROR: {e}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main_menu(service: TodoService) -> None: |
|
|
"""Display main menu and handle user choices. |
|
|
|
|
|
Args: |
|
|
service: TodoService instance |
|
|
""" |
|
|
while True: |
|
|
print("\n" + "=" * 40) |
|
|
print("=== Todo App ===") |
|
|
print("=" * 40) |
|
|
print("1. Create Todo") |
|
|
print("2. List Todos") |
|
|
print("3. View Todo") |
|
|
print("4. Update Todo") |
|
|
print("5. Complete Todo") |
|
|
print("6. Delete Todo") |
|
|
print("7. Exit") |
|
|
print("=" * 40) |
|
|
|
|
|
choice = input("\nEnter choice (1-7): ").strip() |
|
|
|
|
|
if choice == "1": |
|
|
cmd_create_todo(service) |
|
|
elif choice == "2": |
|
|
cmd_list_todos(service) |
|
|
elif choice == "3": |
|
|
cmd_view_todo(service) |
|
|
elif choice == "4": |
|
|
cmd_update_todo(service) |
|
|
elif choice == "5": |
|
|
cmd_complete_todo(service) |
|
|
elif choice == "6": |
|
|
cmd_delete_todo(service) |
|
|
elif choice == "7": |
|
|
print("\n=== Goodbye! ===") |
|
|
sys.exit(0) |
|
|
else: |
|
|
print("\nERROR: Invalid choice. Please enter a number between 1 and 7.") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> None: |
|
|
"""Main entry point for the CLI application.""" |
|
|
print("Welcome to Phase-1 In-Memory Todo App!") |
|
|
print("All data is stored in memory and will be lost on exit.\n") |
|
|
|
|
|
service = TodoService() |
|
|
main_menu(service) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
main() |
|
|
|