add base file
This commit is contained in:
154
README.md
Normal file
154
README.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# Simple Todo Application
|
||||||
|
|
||||||
|
A command-line todo list manager with persistent JSON storage.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This is a simple yet complete todo application that demonstrates:
|
||||||
|
- Functional requirements implementation (CRUD operations)
|
||||||
|
- Non-functional requirements (persistence, validation, performance, error handling)
|
||||||
|
- Clean code architecture with proper error handling
|
||||||
|
- Data persistence using JSON
|
||||||
|
|
||||||
|
## Requirements Implemented
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
|
||||||
|
- **REQ-001**: Create Todo Items - Add new todos with title and optional description
|
||||||
|
- **REQ-002**: List Todo Items - Display all todos with status and metadata
|
||||||
|
- **REQ-003**: Mark Todo as Complete - Toggle completion status on/off
|
||||||
|
- **REQ-004**: Delete Todo Items - Remove todos from the list permanently
|
||||||
|
- **REQ-005**: Edit Todo Items - Update title and description of existing todos
|
||||||
|
|
||||||
|
### Non-Functional Requirements
|
||||||
|
|
||||||
|
- **REQ-006**: Data Persistence - All todos saved to `todos.json` file
|
||||||
|
- **REQ-007**: Input Validation - Titles validated (non-empty, max 200 chars)
|
||||||
|
- **REQ-008**: Performance - All operations complete within 100ms
|
||||||
|
- **REQ-009**: Code Quality - Comprehensive error handling with descriptive messages
|
||||||
|
- **REQ-010**: User Interface - Simple CLI with menu-driven interface
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
✅ Create, Read, Update, Delete (CRUD) todos
|
||||||
|
✅ Toggle completion status
|
||||||
|
✅ Persistent storage (JSON)
|
||||||
|
✅ Input validation
|
||||||
|
✅ Error handling
|
||||||
|
✅ Clear CLI interface
|
||||||
|
✅ Timestamps for created/updated dates
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Menu Options
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Create new todo - Add a new todo item
|
||||||
|
2. List all todos - View all todos with status
|
||||||
|
3. Mark todo complete - Toggle todo completion status
|
||||||
|
4. Edit todo - Update title or description
|
||||||
|
5. Delete todo - Remove a todo permanently
|
||||||
|
6. Exit - Save and quit application
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Storage
|
||||||
|
|
||||||
|
All todos are persisted in `todos.json` with the following structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Buy groceries",
|
||||||
|
"description": "Milk, eggs, bread",
|
||||||
|
"completed": false,
|
||||||
|
"created_at": "2025-12-09T10:30:00",
|
||||||
|
"updated_at": "2025-12-09T10:30:00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
simple_app/
|
||||||
|
├── requirements.md - 10 requirements for the application
|
||||||
|
├── app.py - Main application implementation
|
||||||
|
└── README.md - This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements Traceability
|
||||||
|
|
||||||
|
Each requirement is implemented and can be traced in the code:
|
||||||
|
|
||||||
|
| REQ ID | Feature | Implementation | Status |
|
||||||
|
|--------|---------|-----------------|--------|
|
||||||
|
| REQ-001 | Create todos | `create_todo()` method | ✅ |
|
||||||
|
| REQ-002 | List todos | `list_todos()` method | ✅ |
|
||||||
|
| REQ-003 | Mark complete | `mark_complete()` method | ✅ |
|
||||||
|
| REQ-004 | Delete todos | `delete_todo()` method | ✅ |
|
||||||
|
| REQ-005 | Edit todos | `edit_todo()` method | ✅ |
|
||||||
|
| REQ-006 | Data persistence | `_load_todos()`, `_save_todos()` | ✅ |
|
||||||
|
| REQ-007 | Input validation | `_validate_title()` method | ✅ |
|
||||||
|
| REQ-008 | Performance | JSON operations, efficient filtering | ✅ |
|
||||||
|
| REQ-009 | Error handling | Try-except blocks, error messages | ✅ |
|
||||||
|
| REQ-010 | CLI interface | `show_menu()`, `run()` methods | ✅ |
|
||||||
|
|
||||||
|
## Example Session
|
||||||
|
|
||||||
|
```
|
||||||
|
🚀 Welcome to the Simple Todo Application!
|
||||||
|
|
||||||
|
════════════════════════════════════════════════════════════════════
|
||||||
|
📝 Todo Application
|
||||||
|
════════════════════════════════════════════════════════════════════
|
||||||
|
1. Create new todo
|
||||||
|
2. List all todos
|
||||||
|
3. Mark todo complete/incomplete
|
||||||
|
4. Edit todo
|
||||||
|
5. Delete todo
|
||||||
|
6. Exit
|
||||||
|
════════════════════════════════════════════════════════════════════
|
||||||
|
Enter your choice (1-6): 1
|
||||||
|
Enter todo title: Buy groceries
|
||||||
|
Enter description (optional): Milk, eggs, bread
|
||||||
|
✅ Todo created: 'Buy groceries'
|
||||||
|
|
||||||
|
📋 Your Todos:
|
||||||
|
════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
○ [1] [TODO] Buy groceries
|
||||||
|
Description: Milk, eggs, bread
|
||||||
|
Created: 2025-12-09
|
||||||
|
|
||||||
|
════════════════════════════════════════════════════════════════════
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The application provides clear error messages for:
|
||||||
|
- Empty titles
|
||||||
|
- Titles exceeding 200 characters
|
||||||
|
- Invalid todo IDs
|
||||||
|
- File I/O errors
|
||||||
|
- Invalid menu selections
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
All operations are optimized:
|
||||||
|
- JSON file read on startup only
|
||||||
|
- Linear search for todo lookups (acceptable for typical use)
|
||||||
|
- Single file write per operation
|
||||||
|
- No external dependencies
|
||||||
|
|
||||||
|
## Code Quality
|
||||||
|
|
||||||
|
- Type hints for all function parameters
|
||||||
|
- Comprehensive docstrings
|
||||||
|
- Clear variable names
|
||||||
|
- Proper error handling throughout
|
||||||
|
- Separation of concerns (UI, data, logic)
|
||||||
306
app.py
Normal file
306
app.py
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Simple Todo Application
|
||||||
|
|
||||||
|
This application implements a command-line todo list manager that persists
|
||||||
|
data to JSON and provides full CRUD operations with validation.
|
||||||
|
|
||||||
|
Implements requirements: REQ-001 through REQ-010
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, List, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class TodoApp:
|
||||||
|
"""
|
||||||
|
Simple Todo Application with persistent storage.
|
||||||
|
|
||||||
|
Implements all 10 requirements:
|
||||||
|
- REQ-001: Create todo items
|
||||||
|
- REQ-002: List todo items
|
||||||
|
- REQ-003: Mark complete/incomplete
|
||||||
|
- REQ-004: Delete todo items
|
||||||
|
- REQ-005: Edit todo items
|
||||||
|
- REQ-006: Data persistence (JSON)
|
||||||
|
- REQ-007: Input validation
|
||||||
|
- REQ-008: Performance optimization
|
||||||
|
- REQ-009: Error handling
|
||||||
|
- REQ-010: CLI user interface
|
||||||
|
"""
|
||||||
|
|
||||||
|
DATA_FILE = "todos.json"
|
||||||
|
MAX_TITLE_LENGTH = 200
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the application with data persistence."""
|
||||||
|
self.todos: List[Dict[str, Any]] = []
|
||||||
|
self._load_todos()
|
||||||
|
|
||||||
|
def _load_todos(self) -> None:
|
||||||
|
"""Load todos from JSON file if it exists. REQ-006: Data Persistence."""
|
||||||
|
try:
|
||||||
|
if Path(self.DATA_FILE).exists():
|
||||||
|
with open(self.DATA_FILE, 'r') as f:
|
||||||
|
self.todos = json.load(f)
|
||||||
|
except (json.JSONDecodeError, IOError) as e:
|
||||||
|
print(f"⚠️ Could not load todos: {e}. Starting with empty list.")
|
||||||
|
self.todos = []
|
||||||
|
|
||||||
|
def _save_todos(self) -> None:
|
||||||
|
"""Save todos to JSON file. REQ-006: Data Persistence."""
|
||||||
|
try:
|
||||||
|
with open(self.DATA_FILE, 'w') as f:
|
||||||
|
json.dump(self.todos, f, indent=2)
|
||||||
|
except IOError as e:
|
||||||
|
raise RuntimeError(f"Failed to save todos: {e}") # REQ-009: Error handling
|
||||||
|
|
||||||
|
def _validate_title(self, title: str) -> bool:
|
||||||
|
"""
|
||||||
|
Validate todo title. REQ-007: Input Validation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: The title to validate
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if valid, False otherwise
|
||||||
|
"""
|
||||||
|
if not title or not title.strip():
|
||||||
|
print("❌ Error: Title cannot be empty")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len(title) > self.MAX_TITLE_LENGTH:
|
||||||
|
print(f"❌ Error: Title cannot exceed {self.MAX_TITLE_LENGTH} characters")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def create_todo(self, title: str, description: str = "") -> bool:
|
||||||
|
"""
|
||||||
|
Create a new todo item. REQ-001: Create Todo Items.
|
||||||
|
REQ-007: Input Validation. REQ-009: Error handling.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: The todo title
|
||||||
|
description: Optional todo description
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if created successfully
|
||||||
|
"""
|
||||||
|
if not self._validate_title(title):
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
todo = {
|
||||||
|
"id": len(self.todos) + 1,
|
||||||
|
"title": title.strip(),
|
||||||
|
"description": description.strip(),
|
||||||
|
"completed": False,
|
||||||
|
"created_at": datetime.now().isoformat(),
|
||||||
|
"updated_at": datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
self.todos.append(todo)
|
||||||
|
self._save_todos()
|
||||||
|
print(f"✅ Todo created: '{title}'")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error creating todo: {e}") # REQ-009: Error handling
|
||||||
|
return False
|
||||||
|
|
||||||
|
def list_todos(self) -> None:
|
||||||
|
"""
|
||||||
|
Display all todos in a formatted list. REQ-002: List Todo Items.
|
||||||
|
REQ-010: CLI user interface.
|
||||||
|
"""
|
||||||
|
if not self.todos:
|
||||||
|
print("\n📋 No todos yet. Create one to get started!")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\n📋 Your Todos:")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
for todo in self.todos:
|
||||||
|
status = "✓" if todo["completed"] else "○"
|
||||||
|
completed_text = "[DONE]" if todo["completed"] else "[TODO]"
|
||||||
|
|
||||||
|
print(f"\n {status} [{todo['id']}] {completed_text} {todo['title']}")
|
||||||
|
|
||||||
|
if todo["description"]:
|
||||||
|
print(f" Description: {todo['description']}")
|
||||||
|
|
||||||
|
print(f" Created: {todo['created_at'][:10]}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
|
||||||
|
def mark_complete(self, todo_id: int) -> bool:
|
||||||
|
"""
|
||||||
|
Mark a todo as complete or toggle completion status.
|
||||||
|
REQ-003: Mark Todo as Complete. REQ-009: Error handling.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
todo_id: The ID of the todo to mark complete
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
for todo in self.todos:
|
||||||
|
if todo["id"] == todo_id:
|
||||||
|
todo["completed"] = not todo["completed"]
|
||||||
|
todo["updated_at"] = datetime.now().isoformat()
|
||||||
|
self._save_todos()
|
||||||
|
|
||||||
|
status = "completed" if todo["completed"] else "reopened"
|
||||||
|
print(f"✅ Todo {status}: '{todo['title']}'")
|
||||||
|
return True
|
||||||
|
|
||||||
|
print(f"❌ Error: Todo with ID {todo_id} not found")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error marking todo: {e}") # REQ-009: Error handling
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_todo(self, todo_id: int) -> bool:
|
||||||
|
"""
|
||||||
|
Delete a todo item. REQ-004: Delete Todo Items.
|
||||||
|
REQ-009: Error handling.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
todo_id: The ID of the todo to delete
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
original_length = len(self.todos)
|
||||||
|
self.todos = [t for t in self.todos if t["id"] != todo_id]
|
||||||
|
|
||||||
|
if len(self.todos) < original_length:
|
||||||
|
self._save_todos()
|
||||||
|
print(f"✅ Todo deleted")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ Error: Todo with ID {todo_id} not found")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error deleting todo: {e}") # REQ-009: Error handling
|
||||||
|
return False
|
||||||
|
|
||||||
|
def edit_todo(self, todo_id: int, title: Optional[str] = None,
|
||||||
|
description: Optional[str] = None) -> bool:
|
||||||
|
"""
|
||||||
|
Edit an existing todo item. REQ-005: Edit Todo Items.
|
||||||
|
REQ-007: Input Validation. REQ-009: Error handling.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
todo_id: The ID of the todo to edit
|
||||||
|
title: New title (optional)
|
||||||
|
description: New description (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
for todo in self.todos:
|
||||||
|
if todo["id"] == todo_id:
|
||||||
|
if title is not None:
|
||||||
|
if not self._validate_title(title):
|
||||||
|
return False
|
||||||
|
todo["title"] = title.strip()
|
||||||
|
|
||||||
|
if description is not None:
|
||||||
|
todo["description"] = description.strip()
|
||||||
|
|
||||||
|
todo["updated_at"] = datetime.now().isoformat()
|
||||||
|
self._save_todos()
|
||||||
|
print(f"✅ Todo updated: '{todo['title']}'")
|
||||||
|
return True
|
||||||
|
|
||||||
|
print(f"❌ Error: Todo with ID {todo_id} not found")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error editing todo: {e}") # REQ-009: Error handling
|
||||||
|
return False
|
||||||
|
|
||||||
|
def show_menu(self) -> None:
|
||||||
|
"""Display the main menu. REQ-010: CLI user interface."""
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("📝 Todo Application")
|
||||||
|
print("=" * 70)
|
||||||
|
print("1. Create new todo")
|
||||||
|
print("2. List all todos")
|
||||||
|
print("3. Mark todo complete/incomplete")
|
||||||
|
print("4. Edit todo")
|
||||||
|
print("5. Delete todo")
|
||||||
|
print("6. Exit")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
"""Main application loop. REQ-010: CLI user interface."""
|
||||||
|
print("\n🚀 Welcome to the Simple Todo Application!")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self.show_menu()
|
||||||
|
choice = input("Enter your choice (1-6): ").strip()
|
||||||
|
|
||||||
|
if choice == "1":
|
||||||
|
# REQ-001: Create Todo Items
|
||||||
|
title = input("Enter todo title: ").strip()
|
||||||
|
description = input("Enter description (optional): ").strip()
|
||||||
|
self.create_todo(title, description)
|
||||||
|
|
||||||
|
elif choice == "2":
|
||||||
|
# REQ-002: List Todo Items
|
||||||
|
self.list_todos()
|
||||||
|
|
||||||
|
elif choice == "3":
|
||||||
|
# REQ-003: Mark Todo as Complete
|
||||||
|
self.list_todos()
|
||||||
|
try:
|
||||||
|
todo_id = int(input("Enter todo ID to toggle: "))
|
||||||
|
self.mark_complete(todo_id)
|
||||||
|
except ValueError:
|
||||||
|
print("❌ Error: Please enter a valid ID number")
|
||||||
|
|
||||||
|
elif choice == "4":
|
||||||
|
# REQ-005: Edit Todo Items
|
||||||
|
self.list_todos()
|
||||||
|
try:
|
||||||
|
todo_id = int(input("Enter todo ID to edit: "))
|
||||||
|
title = input("Enter new title (leave empty to skip): ").strip()
|
||||||
|
description = input("Enter new description (leave empty to skip): ").strip()
|
||||||
|
|
||||||
|
new_title = title if title else None
|
||||||
|
new_description = description if description else None
|
||||||
|
|
||||||
|
self.edit_todo(todo_id, new_title, new_description)
|
||||||
|
except ValueError:
|
||||||
|
print("❌ Error: Please enter a valid ID number")
|
||||||
|
|
||||||
|
elif choice == "5":
|
||||||
|
# REQ-004: Delete Todo Items
|
||||||
|
self.list_todos()
|
||||||
|
try:
|
||||||
|
todo_id = int(input("Enter todo ID to delete: "))
|
||||||
|
confirm = input(f"Are you sure? (yes/no): ").strip().lower()
|
||||||
|
if confirm == "yes":
|
||||||
|
self.delete_todo(todo_id)
|
||||||
|
except ValueError:
|
||||||
|
print("❌ Error: Please enter a valid ID number")
|
||||||
|
|
||||||
|
elif choice == "6":
|
||||||
|
# REQ-010: CLI user interface - graceful exit
|
||||||
|
print("\n👋 Goodbye! Your todos have been saved.")
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("❌ Error: Invalid choice. Please enter 1-6.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = TodoApp()
|
||||||
|
app.run()
|
||||||
35
requirements.md
Normal file
35
requirements.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Simple Todo Application - Requirements
|
||||||
|
|
||||||
|
## Functional Requirements
|
||||||
|
|
||||||
|
### REQ-001: Create Todo Items
|
||||||
|
The application shall allow users to create new todo items with a title and optional description.
|
||||||
|
|
||||||
|
### REQ-002: List Todo Items
|
||||||
|
The application shall display all created todo items in a list format with their current status.
|
||||||
|
|
||||||
|
### REQ-003: Mark Todo as Complete
|
||||||
|
The application shall provide functionality to mark a todo item as complete or incomplete with a toggle action.
|
||||||
|
|
||||||
|
### REQ-004: Delete Todo Items
|
||||||
|
The application shall allow users to delete todo items from the list permanently.
|
||||||
|
|
||||||
|
### REQ-005: Edit Todo Items
|
||||||
|
The application shall allow users to edit the title and description of existing todo items.
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
|
||||||
|
### REQ-006: Data Persistence
|
||||||
|
The application shall persist all todo items to a JSON file so that data remains available after application restart.
|
||||||
|
|
||||||
|
### REQ-007: Input Validation
|
||||||
|
The application shall validate that todo titles are not empty and are no longer than 200 characters.
|
||||||
|
|
||||||
|
### REQ-008: Performance
|
||||||
|
The application shall complete all todo operations (create, read, update, delete) within 100 milliseconds.
|
||||||
|
|
||||||
|
### REQ-009: Code Quality
|
||||||
|
The application shall implement proper error handling with descriptive error messages for all operations.
|
||||||
|
|
||||||
|
### REQ-010: User Interface
|
||||||
|
The application shall provide a simple command-line interface with clear menu options and status feedback for user actions.
|
||||||
Reference in New Issue
Block a user