diff --git a/.gitignore b/.gitignore index 2873e189e..2394419c5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +*.jar + diff --git a/docs/README.md b/docs/README.md index 47b9f984f..857361a84 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,125 @@ -# Duke User Guide +# Welcome to Lys - Your Personal Task Management Chatbot! +This guide will help you understand how to use **Lys** effectively. -// Update the title above to match the actual product name +--- -// Product screenshot goes here +## **Key Features** +Lys allows you to: +- ✅ Add different types of tasks (**ToDo, Deadline, Event**). +- 📋 List all tasks. +- ✔️ Mark tasks as **done** or **not done**. +- ❌ Delete tasks. +- 🔍 Find tasks by keywords. -// Product intro goes here +--- -## Adding deadlines +## **Command List** -// Describe the action and its outcome. +### **1️⃣ Adding Tasks** -// Give examples of usage +#### **➤ Adding a ToDo** +- **Command:** +todo TASK -Example: `keyword (optional arguments)` +- **Example:** +todo Assignment -// A description of the expected outcome goes here +- **Output:** +Got it. I've added this task: [T][ ] Assignment Now you have 1 task in the list. -``` -expected output -``` +#### **➤ Adding a Deadline** +- **Command:** +deadline TASK /by DEADLINE -## Feature ABC +- **Example:** +deadline Quiz /by Sunday 2359 -// Feature details +- **Output:** +Got it. I've added this task: [D][ ] Quiz (by: Sunday 2359) Now you have 2 tasks in the list. +#### **➤ Adding an Event** +- **Command:** +event TASK /from START_TIME /to END_TIME -## Feature XYZ +- **Example:** +event Lecture /from Friday 1600 /to 1800 -// Feature details \ No newline at end of file +- **Output:** +Got it. I've added this task: [E][ ] Lecture (from: Friday 1600 to: 1800) Now you have 3 tasks in the list. + +--- + +### **2️⃣ Deleting Tasks** +- **Command:** +delete INDEX + +- **Example:** +delete 3 + +- **Output:** +Noted. I've removed this task: [E][ ] Lecture (from: Friday 1600 to: 1800) Now you have 2 tasks in the list. + +--- + +### **3️⃣ Marking / Unmarking Tasks** + +#### **➤ Marking a Task as Done** +- **Command:** +mark INDEX + +- **Example:** +mark 2 + +- **Output:** +Nice! I've marked this task as done: [D][X] Quiz (by: Sunday 2359) + +#### **➤ Marking a Task as Not Done** +- **Command:** +unmark INDEX + +- **Example:** +unmark 2 + +**output:** +OK, I've marked this task as not done yet: [D][ ] Quiz (by: Sunday 2359) + +--- + +### **4️⃣ Finding Tasks** +- **Command:** +find TASK + +- **Example:** +find Quiz + +- **Output:** +Here are the matching tasks in your list: + +[D][ ] Quiz (by: Sunday 2359) + +--- + +### **5️⃣ Viewing the Task List** +- **Command:** +list + +- **Example:** +list + +- **Output:** +Here are the tasks in your list: + +[T][ ] Assignment +[D][ ] Quiz (by: Sunday 2359) + +--- + +### **6️⃣ Exiting the Program** +- **Command:** +bye + +- **Example:** +bye + +- **Output:** +Bye. Hope to see you again soon! Process finished with exit code 0 diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334c..000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/Lys.java b/src/main/java/Lys.java new file mode 100644 index 000000000..8633f80d4 --- /dev/null +++ b/src/main/java/Lys.java @@ -0,0 +1,464 @@ +import java.util.*; +import java.io.*; + +/** + * Represents a task with a description and completion status. + * This is an abstract class and should be extended by specific task types. + */ +abstract class Task { + protected String description; + protected boolean isDone; + + /** + * Constructs a new Task with a description. + * + * @param description The task description. + */ + public Task(String description) { + this.description = description; + this.isDone = false; + } + + /** + * Marks this task as completed. + */ + public void markAsDone() { + this.isDone = true; + } + + /** + * Marks this task as not completed. + */ + public void unmarkAsDone() { + this.isDone = false; + } + + /** + * Gets the type of the task as a string. + * + * @return Task type identifier. + */ + public abstract String getTaskType(); + + @Override + public String toString() { + return "[" + getTaskType() + "][" + (isDone ? "X" : " ") + "] " + description.trim(); + } +} + +/** + * Represents a basic ToDo task. + */ +class ToDo extends Task { + public ToDo(String description) { + super(description); + } + + @Override + public String getTaskType() { + return "T"; + } +} + +/** + * Represents a task with a deadline. + */ +class Deadline extends Task { + private String by; + + /** + * Constructs a Deadline task. + * + * @param description The task description. + * @param by The due date/time of the task. + */ + public Deadline(String description, String by) { + super(description); + this.by = by; + } + + @Override + public String getTaskType() { + return "D"; + } + + @Override + public String toString() { + return super.toString() + " (by: " + by + ")"; + } +} + +/** + * Represents an event task with a start and end time. + */ +class Event extends Task { + private String from; + private String to; + + /** + * Constructs an Event task. + * + * @param description The task description. + * @param from The starting time. + * @param to The ending time. + */ + public Event(String description, String from, String to) { + super(description); + this.from = from; + this.to = to; + } + + @Override + public String getTaskType() { + return "E"; + } + + @Override + public String toString() { + return super.toString() + " (from: " + from + " to: " + to + ")"; + } +} + +/** + * Handles user interface interactions. + */ +class Ui { + private Scanner scanner; + + public Ui() { + scanner = new Scanner(System.in); + } + + /** + * Reads a command from the user. + * + * @return The user input command. + */ + public String readCommand() { + return scanner.nextLine().trim(); + } + + /** + * Displays a line separator. + */ + public void showLine() { + System.out.println("____________________________________________________________"); + } + + /** + * Displays a message within formatted lines. + * + * @param message The message to be displayed. + */ + public void showMessage(String message) { + showLine(); + System.out.println(message); + showLine(); + } +} + +/** + * Handles loading and saving tasks to a file. + */ +class Storage { + private String filePath; + + public Storage(String filePath) { + this.filePath = filePath; + } + + /** + * Loads tasks from the storage file. + * + * @return A list of task descriptions. + */ + public List load() { + List tasks = new ArrayList<>(); + File file = new File(filePath); + + if (!file.exists()) { + return tasks; + } + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + tasks.add(line); + } + } catch (IOException e) { + System.out.println("Error reading file: " + e.getMessage()); + } + + return tasks; + } + + public void save(List tasks) { + File file = new File(filePath); + File parentDir = file.getParentFile(); + + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); + } + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + for (Task task : tasks) { + writer.write(task.toString()); + writer.newLine(); + } + } catch (IOException e) { + System.out.println("Error saving file: " + e.getMessage()); + } + } +} + +/** + * Parses user input commands. + */ +class Parser { + /** + * Splits input command into command word and arguments. + * + * @param input The full command string. + * @return An array containing the command and arguments. + */ + public static String[] parse(String input) { + return input.split(" ", 2); + } +} + +/** + * Manages a list of tasks. + */ +class TaskList { + private List tasks; + + public TaskList() { + this.tasks = new ArrayList<>(); + } + + /** + * Adds a task to the task list. + * + * @param task The task to be added. + */ + public void addTask(Task task) { + tasks.add(task); + } + + /** + * Marks or unmarks a task as completed. + * + * @param index The index of the task in the list. + * @param isDone True to mark as done, false to unmark. + */ + public void markTask(int index, boolean isDone) { + if (isDone) { + tasks.get(index).markAsDone(); + } else { + tasks.get(index).unmarkAsDone(); + } + } + + /** + * Deletes a task from the list. + * + * @param index The index of the task to delete. + */ + public void deleteTask(int index) { + tasks.remove(index); + } + + /** + * Displays all tasks in the list. + */ + public void listTasks() { + if (tasks.isEmpty()) { + System.out.println("Your task list is empty."); + } else { + System.out.println("Here are the tasks in your list:"); + for (int i = 0; i < tasks.size(); i++) { + System.out.println((i + 1) + "." + tasks.get(i).toString()); + } + } + } + + /** + * Find a task with relevant words in the list. + */ + public void findTasks(String keyword) { + List matchingTasks = new ArrayList<>(); + for (Task task : tasks) { + if (task.description.contains(keyword)) { + matchingTasks.add(task); + } + } + + if (matchingTasks.isEmpty()) { + System.out.println("No matching tasks found."); + } else { + System.out.println("Here are the matching tasks in your list:"); + for (int i = 0; i < matchingTasks.size(); i++) { + System.out.println((i + 1) + "." + matchingTasks.get(i)); + } + } + } + + /** + * Retrieves the number of tasks in the list. + * + * @return The size of the task list. + */ + public int getSize() { + return tasks.size(); + } + + public List getTasks() { + return tasks; + } +} + +/** + * Main class that runs the Lys chatbot. + */ +public class Lys { + private Ui ui; + private Storage storage; + private TaskList tasks; + + public Lys() { + ui = new Ui(); + storage = new Storage("data/tasks.txt"); + tasks = new TaskList(); + + List loadedTasks = storage.load(); + for (String taskDescription : loadedTasks) { + if (taskDescription.startsWith("[T]")) { + tasks.addTask(new ToDo(taskDescription.substring(6))); // Remove prefix + } else if (taskDescription.startsWith("[D]")) { + String[] parts = taskDescription.substring(6).split(" \\(by: "); + tasks.addTask(new Deadline(parts[0], parts[1].replace(")", ""))); + } else if (taskDescription.startsWith("[E]")) { + String[] parts = taskDescription.substring(6).split(" \\(from: | to: "); + tasks.addTask(new Event(parts[0], parts[1], parts[2].replace(")", ""))); + } + } + } + + /** + * Runs the chatbot, handling user commands. + */ + public void run() { + ui.showMessage("Hello! I'm Lys.\nWhat can I do for you?"); + boolean isRunning = true; + + while (isRunning) { + String input = ui.readCommand(); + String[] parsedInput = Parser.parse(input); + String command = parsedInput[0].toLowerCase(); + + try { + switch (command) { + case "bye": + ui.showMessage("Bye. Hope to see you again soon!"); + isRunning = false; + break; + + case "list": + ui.showLine(); + tasks.listTasks(); + ui.showLine(); + break; + + case "mark": + case "unmark": { + int index = Integer.parseInt(parsedInput[1]) - 1; + if (index < 0 || index >= tasks.getSize()) { + throw new IndexOutOfBoundsException("Invalid task number."); + } + tasks.markTask(index, command.equals("mark")); + storage.save(tasks.getTasks()); + if (command.equals("mark")) { + ui.showMessage("Nice! I've marked this task as done:\n " + tasks.getTasks().get(index)); + } else { + ui.showMessage("OK, I've marked this task as not done yet:\n " + tasks.getTasks().get(index)); + } + break; + } + + case "todo": + tasks.addTask(new ToDo(parsedInput[1])); + storage.save(tasks.getTasks()); // Ensure it's saved + ui.showMessage("Got it. I've added this task:\n " + tasks.getTasks().get(tasks.getSize() - 1) + + "\nNow you have " + tasks.getSize() + " tasks in the list."); + break; + + case "deadline": + if (parsedInput.length < 2 || !parsedInput[1].contains(" /by ")) { + throw new IllegalArgumentException("Deadline format should be: deadline [task] /by [date]"); + } + + String[] deadlineParts = parsedInput[1].split(" /by ", 2); + tasks.addTask(new Deadline(deadlineParts[0], deadlineParts[1])); + storage.save(tasks.getTasks()); + + ui.showMessage("Got it. I've added this task:\n " + tasks.getTasks().get(tasks.getSize() - 1) + + "\nNow you have " + tasks.getSize() + " tasks in the list."); + break; + + case "event": + if (parsedInput.length < 2 || !parsedInput[1].contains(" /from ") || !parsedInput[1].contains(" /to ")) { + throw new IllegalArgumentException("Event format should be: event [task] /from [start] /to [end]"); + } + + String[] eventParts = parsedInput[1].split(" /from ", 2); + String description = eventParts[0]; + + String[] timeParts = eventParts[1].split(" /to ", 2); + String from = timeParts[0]; + String to = timeParts[1]; + + tasks.addTask(new Event(description, from, to)); + storage.save(tasks.getTasks()); + + ui.showMessage("Got it. I've added this task:\n " + tasks.getTasks().get(tasks.getSize() - 1) + + "\nNow you have " + tasks.getSize() + " tasks in the list."); + break; + + case "delete": + int index = Integer.parseInt(parsedInput[1]) - 1; + if (index < 0 || index >= tasks.getSize()) { + throw new IndexOutOfBoundsException("Invalid task number."); + } + Task removedTask = tasks.getTasks().get(index); + tasks.deleteTask(index); + storage.save(tasks.getTasks()); + ui.showMessage("Noted. I've removed this task:\n " + removedTask + "\nNow you have " + tasks.getSize() + " tasks in the list."); + break; + + case "find": + if (parsedInput.length < 2) { + throw new IllegalArgumentException("Please provide a keyword to search."); + } + ui.showLine(); + tasks.findTasks(parsedInput[1]); + ui.showLine(); + break; + + default: + throw new IllegalArgumentException("Unknown command."); + } + } catch (Exception e) { + ui.showMessage("Error: " + e.getMessage()); + } + } + } + + /** + * Entry point of the program. + * + * @param args Command line arguments. + */ + public static void main(String[] args) { + new Lys().run(); + } +} \ No newline at end of file diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..b9d600278 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Lys +