From d54cfdb5e81fdd7f788e84f04321dd1f724a4f4a Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 15 Nov 2025 21:07:25 +0000 Subject: [PATCH 1/6] Sprint 2 updates --- data.json | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/data.json b/data.json index 4a07190..91466f7 100644 --- a/data.json +++ b/data.json @@ -1,8 +1,15 @@ { - "tasks":[], - "settings":{ - "app_name":"Task Manager", - "version":"1.0.0", - "max_tasks":100 - } -} + "tasks": [ + { + "id": 1, + "description": "somethn", + "priority": "high", + "completed": false + } + ], + "settings": { + "app_name": "Task Manager", + "version": "1.0.0", + "max_tasks": 100 + } +} \ No newline at end of file From e51a2383ed066e3646806380d578d41581920ea1 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 15 Nov 2025 21:39:37 +0000 Subject: [PATCH 2/6] Sprint 2 updated --- add_task.py | 96 +++++++++++++++++------------------------------- complete_task.py | 61 +++++++++++++++--------------- data.json | 6 ++- view_tasks.py | 58 ++++++++++++++--------------- 4 files changed, 95 insertions(+), 126 deletions(-) diff --git a/add_task.py b/add_task.py index c3662fa..b732a24 100644 --- a/add_task.py +++ b/add_task.py @@ -1,65 +1,35 @@ import json -import os - -DATA_FILE = "data.json" - -def load_data(): - """Load task data from data.json or create default structure.""" - if os.path.exists(DATA_FILE): - with open(DATA_FILE, "r") as f: - return json.load(f) - return { - "tasks": [], - "settings": { - "app_name": "Task Manager", - "version": "1.0.0", - "max_tasks": 100 - } - } - -def save_data(data): - """Save task data back to data.json.""" - with open(DATA_FILE, "w") as f: - json.dump(data, f, indent=4) - -def get_next_id(tasks): - """Return the next task ID.""" - if not tasks: - return 1 - highest = 0 - for t in tasks: - if int(t.get("id", 0)) > highest: - highest = int(t["id"]) - return highest + 1 - -def add_task(description, priority): - """Add a new task with description and priority.""" - data = load_data() - tasks = data["tasks"] - - max_tasks = data.get("settings", {}).get("max_tasks", 100) - if len(tasks) >= max_tasks: - print("You have reached the maximum number of tasks.") - return - - new_task = { - "id": get_next_id(tasks), - "description": description, - "priority": priority, - "completed": False - } - - tasks.append(new_task) - save_data(data) - print(" Task added:", description, "(Priority:", priority + ")") - -if __name__ == "__main__": - print("Enter task description:") - desc = input().strip() - print("Enter priority (high, medium, low):") - prio = input().strip().lower() - if prio not in ["high", "medium", "low"]: - print("Priority must be: high, medium, or low.") - else: - add_task(desc, prio) +import re +DATA_FILE="data.json" + +def is_valid_date(d): + """Validate YYYY--MM--DD format""" + return re.match(r"^\d{4}-\d{2}-\d{2}$",d) is not None + +def add_task(): + description=input("Enter task description: ").strip() + priority=input("Enter priority (high/medium/low): ").strip().lower() + due_date=input("Enter due date (YYYY-MM-DD) or press Enter to skip: ") + if due_date=="": + due_date=None + elif not is_valid_date(due_date): + print("Invalid date format. Must be YYYY-MM-DD.") + return + with open(DATA_FILE,"r") as f: + data=json.load(f) + task_id=len(data["tasks"])+1 + new_task={ + "id":task_id, + "description":description, + "priority": priority, + "completed": False, + "due_date":due_date, + "completion_date":None + } + data["tasks"].append(new_task) + with open(DATA_FILE,"w") as f: + json.dump(data,f,indent=4) + print(f"Task #{task_id} added successfully!") +if __name__=="__main__": + add_task() diff --git a/complete_task.py b/complete_task.py index 53f4f38..2875faf 100644 --- a/complete_task.py +++ b/complete_task.py @@ -1,38 +1,37 @@ import json +from datetime import datetime + DATA_FILE="data.json" -def load_tasks(): - if not os.path.exists(DATA_FILE): - print("Error: data.json file not found.") - return {"tasks":[]} + +def complete_task(): + task_id=input("Enter task ID to complete: ").strip() + if not task_id.isdigit(): + print("Invalid ID format.") + return + task_id=int(task_id) with open(DATA_FILE,"r") as f: data=json.load(f) - return data -def save_tasks(data): - with open(DATA_FILE,"w") as f: - json.dump(data,f,indent=4) - -def complete_task(task_id): - data=load_tasks() - tasks=data["tasks"] - found=False - for task in tasks: - if task["id"]==task_id: - found=True - if task["completed"]: - print("Task",task_id,"is already completed.") - else: - task["completed"]=True - save_tasks(data) - print("Task",task_id,"marked as completed.") + found=None + for t in data["tasks"]: + if t["id"] == task_id: + found=t break if not found: - print("Task with ID",task_id,"not found.") -if __name__=="__main__": - import os - print("Enter the task to complete:") - task_id_input=input().strip() - if task_id_input.isdigit(): - task_id=int(task_id_input) - complete_task(task_id) + print("Task not found.") + return + if found["completed"]: + undo=input("Task is already completed. Undo completion? (yes/no): ") + if undo=="yes": + found["completed"]=False + found["completion_date"]=None + print("Completion undone.") + else: + print("No changes made.") else: - print("Please enter a valid task ID number.") + found["completed"]=True + found["completion_date"]=datetime.now().strftime("%Y-%m-%d") + print("Task marked completed!") + with open(DATA_FILE,"w") as f: + json.dump(data,f,indent=4) +if __name__=="__main__": + complete_task() diff --git a/data.json b/data.json index 91466f7..4f9ed32 100644 --- a/data.json +++ b/data.json @@ -2,9 +2,11 @@ "tasks": [ { "id": 1, - "description": "somethn", + "description": "something", "priority": "high", - "completed": false + "completed": true, + "due_date": null, + "completion_date": "2025-11-15" } ], "settings": { diff --git a/view_tasks.py b/view_tasks.py index 4d37c7a..f117560 100644 --- a/view_tasks.py +++ b/view_tasks.py @@ -1,34 +1,32 @@ import json -import os +import sys +DATA_FILE="data.json" -DATA_FILE = "data.json" - -def load_data(): - """Load tasks from JSON file or return empty list wrapper.""" - if os.path.exists(DATA_FILE): - with open(DATA_FILE, "r") as f: - return json.load(f) - return {"tasks": []} +PRIORITY_ORDER={ + "high":1, + "medium":2, + "low":3 +} def view_tasks(): - """Display all tasks neatly with ID, status, priority, description.""" - data = load_data() - tasks = data.get("tasks", []) - - if not tasks: - print("No tasks available.") - return - - print("\n Task List") - print("-" * 50) - for task in tasks: - status = " Completed" if task.get("completed") else " Not Completed" - print("ID:", task.get("id")) - print("Description:", task.get("description")) - print("Priority:", task.get("priority")) - print("Status:", status) - print("-" * 50) - -if __name__ == "__main__": - view_tasks() - + show_completed="--show-completed" in sys.argv + with open(DATA_FILE,"r") as f: + data=json.load(f) + tasks=data["tasks"] + if not show_completed: + tasks=[t for t in tasks if not t["completed"]] + tasks.sort(key=lambda t: PRIORITY_ORDER.get(t["priority"],99)) + if not tasks: + print("No tasks to display.") + return + for t in tasks: + status="Completed" if t["completed"] else "Pending" + due=t["due_date"] if t["due_date"] else "None" + print(f"[{t['id']}] {t['description']} ({t['priority']})") + print(f" Due: {due}") + print(f" Status: {status}") + if t["completion_date"]: + print(f" Completed on: {t['completion_date']}") + print() +if __name__=="__main__": + view_tasks() From 8c09a7832a92af3961a7c3d10b801a0b8ba93ad7 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 19 Nov 2025 05:20:59 +0000 Subject: [PATCH 3/6] Updated delete_task --- delete_task.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/delete_task.py b/delete_task.py index e69de29..ca3175e 100644 --- a/delete_task.py +++ b/delete_task.py @@ -0,0 +1,71 @@ +import json +import argparse +from datetime import datetime +import os + +DATA_FILE = "data.json" +LOG_FILE = "deleted.log" + + +def load_tasks(): + if not os.path.exists(DATA_FILE): + return [] + with open(DATA_FILE, "r") as f: + return json.load(f) + + +def save_tasks(tasks): + with open(DATA_FILE, "w") as f: + json.dump(tasks, f, indent=4) + + +def log_deletion(task): + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + entry = f"[{timestamp}] Deleted Task ID={task['id']} | \"{task['description']}\"\n" + with open(LOG_FILE, "a") as log: + log.write(entry) + + +def delete_task(task_id, force=False): + tasks = load_tasks() + task_to_delete = None + + for t in tasks: + if t["id"] == task_id: + task_to_delete = t + break + + if task_to_delete is None: + print("Task not found.") + return + + + if task_to_delete.get("completed", False) and not force: + response = input( + "This task is already completed. Are you sure you want to delete it? (y/n): " ).strip().lower() + if response != "y": + print("Deletion cancelled.") + return + + tasks.remove(task_to_delete) + save_tasks(tasks) + log_deletion(task_to_delete) + + print(f"Task {task_id} deleted successfully.") + + +def main(): + parser = argparse.ArgumentParser(description="Delete a task by ID.") + parser.add_argument("id", type=int, help="ID of the task to delete") + parser.add_argument( + "--force", + action="store_true", + help="Delete completed tasks without confirmation" + ) + + args = parser.parse_args() + delete_task(args.id, args.force) + + +if __name__ == "__main__": + main() From ea65ccd1b0b3e4bfae167407e98091535bf679c2 Mon Sep 17 00:00:00 2001 From: Alex Bond <59181031+alexthemarvelnerd@users.noreply.github.com> Date: Wed, 19 Nov 2025 00:44:43 -0500 Subject: [PATCH 4/6] delete_task.py --- delete_task.py | 70 -------------------------------------------------- 1 file changed, 70 deletions(-) diff --git a/delete_task.py b/delete_task.py index ca3175e..8b13789 100644 --- a/delete_task.py +++ b/delete_task.py @@ -1,71 +1 @@ -import json -import argparse -from datetime import datetime -import os -DATA_FILE = "data.json" -LOG_FILE = "deleted.log" - - -def load_tasks(): - if not os.path.exists(DATA_FILE): - return [] - with open(DATA_FILE, "r") as f: - return json.load(f) - - -def save_tasks(tasks): - with open(DATA_FILE, "w") as f: - json.dump(tasks, f, indent=4) - - -def log_deletion(task): - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - entry = f"[{timestamp}] Deleted Task ID={task['id']} | \"{task['description']}\"\n" - with open(LOG_FILE, "a") as log: - log.write(entry) - - -def delete_task(task_id, force=False): - tasks = load_tasks() - task_to_delete = None - - for t in tasks: - if t["id"] == task_id: - task_to_delete = t - break - - if task_to_delete is None: - print("Task not found.") - return - - - if task_to_delete.get("completed", False) and not force: - response = input( - "This task is already completed. Are you sure you want to delete it? (y/n): " ).strip().lower() - if response != "y": - print("Deletion cancelled.") - return - - tasks.remove(task_to_delete) - save_tasks(tasks) - log_deletion(task_to_delete) - - print(f"Task {task_id} deleted successfully.") - - -def main(): - parser = argparse.ArgumentParser(description="Delete a task by ID.") - parser.add_argument("id", type=int, help="ID of the task to delete") - parser.add_argument( - "--force", - action="store_true", - help="Delete completed tasks without confirmation" - ) - - args = parser.parse_args() - delete_task(args.id, args.force) - - -if __name__ == "__main__": - main() From 64cced55ba1367d44cec9f4ba92a42d34c909f83 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 22 Nov 2025 16:23:27 +0000 Subject: [PATCH 5/6] Added export_data.py --- data.json | 26 ++++++--------- deleted.log | 1 + export_tasks.py | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ tasks_export.csv | 2 ++ 4 files changed, 101 insertions(+), 15 deletions(-) create mode 100644 deleted.log create mode 100644 export_tasks.py create mode 100644 tasks_export.csv diff --git a/data.json b/data.json index 4f9ed32..5376a53 100644 --- a/data.json +++ b/data.json @@ -1,17 +1,13 @@ { - "tasks": [ - { - "id": 1, - "description": "something", - "priority": "high", - "completed": true, - "due_date": null, - "completion_date": "2025-11-15" - } - ], - "settings": { - "app_name": "Task Manager", - "version": "1.0.0", - "max_tasks": 100 + "tasks": [ + { + "id": 1, + "description": "Do something", + "priority": "high", + "completed": false, + "due_date": null, + "completion_date": null } -} \ No newline at end of file + ] +} + diff --git a/deleted.log b/deleted.log new file mode 100644 index 0000000..86da04a --- /dev/null +++ b/deleted.log @@ -0,0 +1 @@ +[2025-11-19 05:17:13] Deleted Task ID=1 | "Finish homework" diff --git a/export_tasks.py b/export_tasks.py new file mode 100644 index 0000000..b1df345 --- /dev/null +++ b/export_tasks.py @@ -0,0 +1,87 @@ +import json +import os +import sys +from datetime import datetime + +DATA_FILE = "data.json" + +PRIORITY_ORDER = { + "high": 1, + "medium": 2, + "low": 3 +} + + +def load_data(): + """Load tasks from JSON file.""" + if os.path.exists(DATA_FILE): + with open(DATA_FILE, "r") as f: + return json.load(f) + return {"tasks": []} + + +def export_to_csv(tasks, filename="tasks_export.csv"): + """Write tasks to a CSV file.""" + with open(filename, "w") as f: + f.write("id,description,priority,completed,due_date,completion_date\n") + for t in tasks: + f.write(f"{t['id']}," + f"\"{t['description']}\"," + f"{t['priority']}," + f"{t['completed']}," + f"{t.get('due_date','')}," + f"{t.get('completion_date','')}\n") + print(f"Export complete! File saved as {filename}") + + +def export_to_txt(tasks, filename="tasks_export.txt"): + """Write tasks to a TXT file.""" + with open(filename, "w") as f: + for t in tasks: + f.write(f"ID: {t['id']}\n") + f.write(f"Description: {t['description']}\n") + f.write(f"Priority: {t['priority']}\n") + f.write(f"Completed: {t['completed']}\n") + f.write(f"Due Date: {t.get('due_date','None')}\n") + f.write(f"Completion Date: {t.get('completion_date','None')}\n") + f.write("-" * 40 + "\n") + print(f"Export complete! File saved as {filename}") + + +def export_tasks(): + args = sys.argv[1:] + + data = load_data() + tasks = data.get("tasks", []) + + # Filter flags + completed_only = "--completed-only" in args + pending_only = "--pending-only" in args + export_txt = "--txt" in args + + if completed_only and pending_only: + print("Error: Cannot use --completed-only and --pending-only together.") + return + + if completed_only: + tasks = [t for t in tasks if t.get("completed")] + elif pending_only: + tasks = [t for t in tasks if not t.get("completed")] + + # Sort tasks by priority for consistent output + tasks.sort(key=lambda t: PRIORITY_ORDER.get(t["priority"], 99)) + + if not tasks: + print("No tasks match the selected filters.") + return + + # Choose file format + if export_txt: + export_to_txt(tasks) + else: + export_to_csv(tasks) + + +if __name__ == "__main__": + export_tasks() + diff --git a/tasks_export.csv b/tasks_export.csv new file mode 100644 index 0000000..d2f9be7 --- /dev/null +++ b/tasks_export.csv @@ -0,0 +1,2 @@ +id,description,priority,completed,due_date,completion_date +1,"Do something",high,False,None,None From 7399baf30592dcac336bd1c48533cc4e40ee5f8b Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 22 Nov 2025 16:37:11 +0000 Subject: [PATCH 6/6] added readme and changelog --- CHANGELOG.md | 6 ++++++ README.md | 4 ++++ 2 files changed, 10 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f8c1b79 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +"""add_task.py updated""" +"""complete_task.py updated""" +"""view_tasks.py updated""" +11/18/2025 + +"""added export_data.py and what changed""" diff --git a/README.md b/README.md index e69de29..7f5b060 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,4 @@ +""" +#During Sprint 2, the add_task file was expanded to include an optional validated due_date, and every new task is created with a completion_date. +#This fixed the fact that Sprint 1 had an inconsistent JSON structure for add_tasks. view_tasks.py was improved to sort tasks by priority, show or hide completed tasks with a --show-completed flag, and display the new due-date and completion-date fields. complete_task.py was updated so finishing a task now records a timestamp and supports +# undoing completion. export_tasks.py was added, providing CSV/TXT export and filters for completed or pending tasks.