Skip to content

Commit 0e83c99

Browse files
Create todo.py
1 parent 788d95b commit 0e83c99

File tree

1 file changed

+208
-0
lines changed

1 file changed

+208
-0
lines changed

todo.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import json
2+
import os
3+
4+
# Define the file where tasks will be stored.
5+
DATA_FILE = "tasks.json"
6+
7+
def load_tasks():
8+
"""
9+
Loads tasks from the DATA_FILE.
10+
11+
If the file does not exist, an empty list is returned.
12+
Handles potential JSON decoding errors or OS-related file errors.
13+
14+
Returns:
15+
list: A list of dictionaries, where each dictionary represents a task.
16+
Returns an empty list if the file is not found or corrupted.
17+
"""
18+
# Check if the data file exists. If not, there are no tasks to load yet.
19+
if not os.path.exists(DATA_FILE):
20+
return []
21+
try:
22+
# Open the file in read mode with UTF-8 encoding for broader character support.
23+
with open(DATA_FILE, "r", encoding="utf-8") as f:
24+
# Load the JSON data from the file.
25+
return json.load(f)
26+
except (json.JSONDecodeError, OSError) as e:
27+
# Catch errors if the JSON is malformed or if there's an OS-related error
28+
# (e.g., permission denied, disk error during read).
29+
print(f"Error loading tasks: {e}. Starting with an empty task list.")
30+
return []
31+
32+
def save_tasks(tasks):
33+
"""
34+
Saves the current list of tasks to the DATA_FILE.
35+
36+
Handles potential OS-related file errors during saving.
37+
38+
Args:
39+
tasks (list): The list of task dictionaries to be saved.
40+
"""
41+
try:
42+
# Open the file in write mode with UTF-8 encoding.
43+
# `ensure_ascii=False` allows non-ASCII characters (like emojis) to be saved directly.
44+
# `indent=2` formats the JSON for better readability.
45+
with open(DATA_FILE, "w", encoding="utf-8") as f:
46+
json.dump(tasks, f, ensure_ascii=False, indent=2)
47+
except OSError as e:
48+
# Catch errors if there's an OS-related error during write (e.g., permission denied, disk full).
49+
print(f"Error saving tasks: {e}. Your changes might not have been saved.")
50+
51+
def list_tasks(tasks):
52+
"""
53+
Prints all tasks to the console, showing their status and index.
54+
55+
Args:
56+
tasks (list): The list of task dictionaries to display.
57+
"""
58+
if not tasks:
59+
print("No tasks to display.")
60+
return
61+
62+
print("\n--- Your To-Do List ---")
63+
for i, t in enumerate(tasks, 1):
64+
# Determine the status symbol based on the 'done' flag.
65+
status = "✔" if t["done"] else "✗"
66+
print(f"{i}. [{status}] {t['title']}")
67+
print("-----------------------\n")
68+
69+
def add_task(tasks, title):
70+
"""
71+
Adds a new task to the list.
72+
73+
Args:
74+
tasks (list): The current list of task dictionaries.
75+
title (str): The title of the new task.
76+
"""
77+
title = title.strip() # Remove leading/trailing whitespace
78+
if not title:
79+
print("Error: Task title cannot be empty.")
80+
return
81+
82+
# Create a new task dictionary with a default 'done' status of False.
83+
tasks.append({"title": title, "done": False})
84+
save_tasks(tasks) # Save the updated list to file.
85+
print(f"Task '{title}' added successfully.")
86+
87+
def toggle_task(tasks, index_str):
88+
"""
89+
Toggles the 'done' status of a task at the specified index.
90+
91+
Args:
92+
tasks (list): The current list of task dictionaries.
93+
index_str (str): The string representation of the 1-based index of the task.
94+
"""
95+
try:
96+
index = int(index_str)
97+
except ValueError:
98+
print(f"Error: Invalid task number '{index_str}'. Please enter a valid integer.")
99+
return
100+
101+
# Adjust index to be 0-based for list access.
102+
# Validate the index to ensure it's within the bounds of the task list.
103+
if index < 1 or index > len(tasks):
104+
print(f"Error: Task number {index} is out of range. Please choose a number between 1 and {len(tasks) if tasks else 0}.")
105+
return
106+
107+
# Toggle the 'done' status.
108+
tasks[index - 1]["done"] = not tasks[index - 1]["done"]
109+
save_tasks(tasks) # Save the updated list.
110+
status_msg = "completed" if tasks[index - 1]["done"] else "marked as not completed"
111+
print(f"Task {index} '{tasks[index-1]['title']}' {status_msg}.")
112+
113+
def delete_task(tasks, index_str):
114+
"""
115+
Deletes a task from the list at the specified index.
116+
117+
Args:
118+
tasks (list): The current list of task dictionaries.
119+
index_str (str): The string representation of the 1-based index of the task.
120+
"""
121+
try:
122+
index = int(index_str)
123+
except ValueError:
124+
print(f"Error: Invalid task number '{index_str}'. Please enter a valid integer.")
125+
return
126+
127+
# Adjust index to be 0-based for list access.
128+
# Validate the index.
129+
if index < 1 or index > len(tasks):
130+
print(f"Error: Task number {index} is out of range. Please choose a number between 1 and {len(tasks) if tasks else 0}.")
131+
return
132+
133+
# Remove the task from the list and store its title for confirmation message.
134+
removed_task_title = tasks.pop(index - 1)["title"]
135+
save_tasks(tasks) # Save the updated list.
136+
print(f"Task {index} '{removed_task_title}' deleted successfully.")
137+
138+
def help_menu():
139+
"""
140+
Displays the help menu with available commands.
141+
"""
142+
print("\n--- Commands ---")
143+
print(" list : Show all tasks.")
144+
print(" add <title> : Add a new task with the given title.")
145+
print(" done <num> : Toggle the completion status of task <num>.")
146+
print(" del <num> : Delete task <num>.")
147+
print(" help : Show this help message.")
148+
print(" quit : Exit the application.")
149+
print("----------------\n")
150+
151+
def main():
152+
"""
153+
The main function to run the To-Do List application.
154+
It loads tasks, enters a command loop, and processes user input.
155+
"""
156+
tasks = load_tasks() # Load tasks at the start of the application.
157+
print("Welcome to the Simple To-Do List!")
158+
help_menu() # Display the help menu initially.
159+
160+
while True:
161+
try:
162+
# Prompt the user for a command.
163+
cmd_input = input("> ").strip()
164+
except (EOFError, KeyboardInterrupt):
165+
# Handle Ctrl+D (EOFError) or Ctrl+C (KeyboardInterrupt) to exit gracefully.
166+
print("\nExiting To-Do List. Goodbye!")
167+
break
168+
169+
# If the input is empty, just prompt again.
170+
if not cmd_input:
171+
continue
172+
173+
# Split the command into action and arguments.
174+
parts = cmd_input.split(maxsplit=1)
175+
action = parts[0].lower() # Convert action to lowercase for case-insensitive comparison.
176+
177+
if action == "list":
178+
list_tasks(tasks)
179+
elif action == "add":
180+
# Check if a title was provided for the 'add' command.
181+
if len(parts) < 2:
182+
print("Usage: add <title> (e.g., add Buy groceries)")
183+
else:
184+
add_task(tasks, parts[1])
185+
elif action == "done":
186+
# Check if a task number was provided and if it's a digit.
187+
if len(parts) < 2:
188+
print("Usage: done <num> (e.g., done 1)")
189+
else:
190+
toggle_task(tasks, parts[1])
191+
elif action == "del":
192+
# Check if a task number was provided and if it's a digit.
193+
if len(parts) < 2:
194+
print("Usage: del <num> (e.g., del 2)")
195+
else:
196+
delete_task(tasks, parts[1])
197+
elif action == "help":
198+
help_menu()
199+
elif action == "quit":
200+
print("Exiting To-Do List. Goodbye!")
201+
break
202+
else:
203+
# Inform the user about unknown commands.
204+
print(f"Unknown command: '{action}'. Type 'help' to see available commands.")
205+
206+
# Entry point for the script.
207+
if __name__ == "__main__":
208+
main()

0 commit comments

Comments
 (0)