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