diff --git a/.gitignore b/.gitignore index 2873e189e..ed657028b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,9 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +#.class files +*.class + +#data.txt +/data/data.txt diff --git a/README.md b/README.md index af0309a9e..58e1a6792 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# Thoth project template This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. @@ -13,7 +13,7 @@ Prerequisites: JDK 17, update Intellij to the most recent version. 1. If there are any further prompts, accept the defaults. 1. Configure the project to use **JDK 17** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
In the same dialog, set the **Project language level** field to the `SDK default` option. -1. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: +1. After that, locate the `src/main/java/Thoth.java` file, right-click it, and choose `Run Thoth.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: ``` Hello from ____ _ diff --git a/data/data.txt b/data/data.txt new file mode 100644 index 000000000..009e7be39 --- /dev/null +++ b/data/data.txt @@ -0,0 +1,3 @@ +[E][X] work on CS2113 IP (from: 13/03/2025 12 noon to: 13/03/2025 4 pm) +[E][ ] sleep (from: now to: never) +[D][X] sleep (by: 11pm) diff --git a/docs/README.md b/docs/README.md index 47b9f984f..755da9326 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,166 @@ -# Duke User Guide +# Thoth User Guide +Thoth Task Manager is a command-line application designed to help you manage your daily tasks efficiently. It supports adding todos, deadlines, and events, as well as marking tasks as done, unmarking them, deleting tasks, and searching for tasks. This README provides an overview of the application, installation instructions, usage details, and troubleshooting tips. -// Update the title above to match the actual product name +Thoth Task Manager enables you to manage three main types of tasks: -// Product screenshot goes here +- **Todos:** Simple tasks that need to be done. +- **Deadlines:** Tasks that must be completed by a specific date/time. +- **Events:** Tasks that occur within a specified time range. -// Product intro goes here +Users interact with the application via a command-line interface (CLI) by entering commands that the system parses and executes. Each command is processed through a central parser that maps user input to the corresponding functionality. -## Adding deadlines +When you start Thoth Task Manager, you will be greeted with a welcome message followed by a prompt where you can enter your commands. The application continuously listens for your input until you choose to exit. -// Describe the action and its outcome. +## Available Commands +### Exiting the Application: _bye_ -// Give examples of usage +The bye command terminates the application gracefully. It prints a goodbye message to indicate that the session is closing. -Example: `keyword (optional arguments)` +Syntax: +``` + bye +``` +Example code: +``` + bye +``` +Sample output: +``` + Bye. Hope to see you again soon! +``` + +### Listing the Task Lists: _list_ -// A description of the expected outcome goes here +The list command prints the list of the users to the screen of the CLI software +Syntax: +``` + list ``` -expected output +Example code: ``` + list +``` +Sample output: +``` + 1. [T][ ] eat + 2. [T][ ] sleep + 3. [T][ ] repeat +``` +### Adding a todo task : _todo_ +The `todo` command adds a new simple task without any associated deadline or time. -## Feature ABC +Syntax: +``` + todo TASK_DESCRIPTION +``` +Example code: +``` + todo work on CS2113 IP +``` +Sample output: +``` + Got it. I've added this task: + [T][ ] work on CS2113 IP + Now you have 4 tasks in the list. +``` +### Adding a deadline task : _deadline_ +The `deadline` command creates a task with a deadline. Both a task description and a deadline time must be specified. -// Feature details +Syntax: +``` + deadline TASK_DESCRIPTION /by DEADLINE_TIME +``` +Example code: +``` + deadline work on CS2113 IP /by tody 2359 +``` +Sample output: +``` + Got it. I've added this task: + [D][ ] work on CS2113 IP (by: today 2359) + Now you have 5 tasks in the list. +``` +### Adding an event task : _event_ +The `event` command schedules a task that occurs during a specified time range. The task description, start and end time of the event must be specified. +Syntax: +``` + event TASK_DESCRIPTION /from START_TIME /to END_TIME +``` +Example code: +``` + event work on CS2113 IP /from 13/03/2025 12 noon /to 13/03/2025 4 pm +``` +Sample output: +``` + Got it. I've added this task: + [E][ ] work on CS2113 IP (from: 13/03/2025 12 noon to: 13/03/2025 4 pm) + Now you have 6 tasks in the list. +``` +### Marking a task as Done: _mark_ +The `mark` command sets a task's status to the done state. -## Feature XYZ +Syntax: +``` + mark TASK_LIST_INDEX +``` +Example code: +``` + mark 1 +``` +Sample Output: +``` + Nice! I've marked this task as done: + [T][X] eat +``` +### Marking a task as Not Done: _unmark_ +The `unmark` command reverts a task's status back to the not done state. +Syntax: +``` + unmark TASK_LIST_INDEX +``` +Example code: +``` + unmark 1 +``` +Sample Output: +``` + OK, I've marked this task as not done yet: + [T][ ] eat +``` +### Deleting a task from the task list: _delete_ +The `delete` command removes a specified task from your list. + +Syntax: +``` + detele TASK_LIST_INDEX +``` +Example code: +``` + delete 2 +``` +Sample Output: +``` + Noted. I've removed this task: + [T][ ] sleep + Now you have 5 tasks in the list. +``` +### Finding tasks with a string: _find_ +The `find` command searches for tasks containing a specified keyword (case-insensitive) and displays the matching tasks. -// Feature details \ No newline at end of file +Syntax: +``` + find KEYWORD +``` +Example code: +``` + find CS2113 +``` +Sample Output: +``` + Here are the matching tasks: + 1. [T][ ] work on CS2113 IP + 2. [D][ ] work on CS2113 IP (by: today 2359) + 3. [E][ ] work on CS2113 IP (from: 13/03/2025 12 noon to: 13/03/2025 4 pm) +``` 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/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..0439442aa --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: thoth.Thoth + diff --git a/src/main/java/thoth/Storage.java b/src/main/java/thoth/Storage.java new file mode 100644 index 000000000..e7444f444 --- /dev/null +++ b/src/main/java/thoth/Storage.java @@ -0,0 +1,92 @@ +package thoth; + +import thoth.parser.TaskParser; +import thoth.tasks.Task; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + + +public class Storage { + + private static final String DEFAULT_FILE_PATH = "data/data.txt"; + private static final int MIN_HEADER_SIZE = 7; + private static final int TYPE_INDEX = 1; + private static final int DONE_INDEX = 4; + + /** + * Create a file to store data + * + * @throws IOException if an I/O error occurs while creating the file + */ + public static void createFile() throws IOException { + File file = new File(DEFAULT_FILE_PATH); + + // Ensure the parent directories exist (if there are any) + if (file.getParentFile() != null) { + file.getParentFile().mkdirs(); + } + // Create the file if it doesn't already exist + if (!file.exists()) { + file.createNewFile(); + } + } + + /** + * Add a new line into the file with the user input + * + * @param input the string to be written to the file + * @throws IOException if an I/O error occurs while writing to this file + */ + public static void writeFile(String input) throws IOException { + try (FileWriter fw = new FileWriter(DEFAULT_FILE_PATH, true)) { + fw.append(input).append(System.lineSeparator()); + } + } + + /** + * Saves the list of tasks into the data file + * + * @param tasks the task list that is to be saved + * @throws IOException if an I/O error occurs while writing to this file + */ + public static void saveTasks(List tasks) throws IOException { + try (FileWriter fw = new FileWriter(DEFAULT_FILE_PATH, false)) { + for (Task t : tasks) { + fw.write(t.getTaskString() + System.lineSeparator()); + } + } + } + + /** + * Load the task from the data file + * + * @return a list of tasks loaded from the file or an empty list if the file does not exist + * @throws IOException if an I/O error occurs while writing to this file + */ + public static List loadTasks() throws IOException { + List tasks = new ArrayList<>(); + File f = new File(DEFAULT_FILE_PATH); + if (!f.exists()) { + return tasks; // no file => no tasks + } + + try (Scanner s = new Scanner(f)) { + while (s.hasNextLine()) { + String line = s.nextLine().trim(); + Task t = TaskParser.parseLineToTask(line); + if (t != null) { + tasks.add(t); + } + } + } + + return tasks; + } + + +} diff --git a/src/main/java/thoth/TaskManager.java b/src/main/java/thoth/TaskManager.java new file mode 100644 index 000000000..364d75ed9 --- /dev/null +++ b/src/main/java/thoth/TaskManager.java @@ -0,0 +1,81 @@ +/** + * Manages a list of tasks. + *

+ * This class provides methods to add tasks, mark them as done or not done, remove tasks, + * and retrieve tasks or the task count. + */ +package thoth; + +import thoth.tasks.Task; + +import java.util.ArrayList; +import java.util.List; + +public class TaskManager { + + private final List taskList = new ArrayList<>(); + + /** + * Add a new task to the task list. + * + * @param task is the task to be added + */ + public void addTask(Task task) { + taskList.add(task); + } + + /** + * Marks the task at the specific task index as done + * + * @param taskId the index of th task that needs to be mark as done + */ + public void markTaskAsDone(int taskId) { + taskList.get(taskId).markAsDone(); + } + + /** + * Marks the task at the specific task index as not done + * + * @param taskId the index of th task that needs to be mark as not done + */ + public void markTaskAsNotDone(int taskId) { + taskList.get(taskId).markAsNotDone(); + } + + /** + * Return the number of tasks in the task list + * + * @return the size of the task list + */ + public int getTaskCount() { + return taskList.size(); + } + + /** + * Return the complete list of tasks + * + * @return the list of tasks + */ + public List getTaskList() { + return taskList; + } + + /** + * Remove the task at the specific task index + * + * @param taskId the index of tht task to be removed + */ + public void removeTask(int taskId) { + taskList.remove(taskId); + } + + /** + * Return a sublist of tasks from the specified index to the end of the list + * + * @param taskId the tarting index for the sublist + * @return a list of tasks starting from the specified index + */ + public List getTask(int taskId) { + return taskList.subList(taskId, taskList.size()); + } +} diff --git a/src/main/java/thoth/Thoth.java b/src/main/java/thoth/Thoth.java new file mode 100644 index 000000000..e203b2fee --- /dev/null +++ b/src/main/java/thoth/Thoth.java @@ -0,0 +1,56 @@ +package thoth; + +import thoth.command.Command; +import thoth.exceptions.TaskParsingException; +import thoth.exceptions.ThothException; +import thoth.parser.Parser; +import thoth.tasks.Task; + +import java.io.IOException; +import java.util.List; + +public class Thoth { + + public static void main(String[] args) { + // Create The task Manager and the User interface + TaskManager taskManager = new TaskManager(); + UserInterface ui = new UserInterface(); + + // Print Greeting. + ui.printGreetingMessage(); + + // String for user input + String userInput; + + try { + Storage.createFile(); + List loadedTasks = Storage.loadTasks(); + // Put those tasks into the TaskManager + for (Task t : loadedTasks) { + taskManager.addTask(t); + } + } catch (TaskParsingException | IOException e) { + System.err.println("Could not load tasks: " + e.getMessage()); + } + + + // Create an endless loop for adding list + while (true) { + + try { + userInput = ui.readInput(); + // extracts out the command from the user input + Command command = Parser.parse(userInput); + // Executes the command parsed out + command.execute(taskManager, ui); + + if (command.isExit()) { + break; + } + } catch (ThothException e) { + ui.showError(e.getMessage()); + } + + } + } +} diff --git a/src/main/java/thoth/UserInterface.java b/src/main/java/thoth/UserInterface.java new file mode 100644 index 000000000..c621d32a2 --- /dev/null +++ b/src/main/java/thoth/UserInterface.java @@ -0,0 +1,118 @@ +package thoth; + +import thoth.tasks.Task; + +import java.util.List; +import java.util.Scanner; + +public class UserInterface { + public static final String INDENT = "%4s"; + private final Scanner scanner; + + /** + * Constructs a new UserInterface and initializes the input scanner. + */ + public UserInterface() { + scanner = new Scanner(System.in); + } + + /** + * Prints the specified message to the console. + * + * @param message the message to be printed. + */ + public static void printMessage(String message) { + System.out.println(message); + } + + /** + * Prints a message indicating that a task has been marked as done. + * + * @param task the task that has been marked as done. + */ + public static void printMarkAsDone(Task task) { + System.out.printf(INDENT + "Nice! I've marked this task as done:%n", ""); + System.out.printf(INDENT + "%s\n", "", task.getTaskString()); + } + + /** + * Prints a message indicating that a task has been marked as not done. + * + * @param task the task that has been marked as not done. + */ + public static void printMarkAsUndone(Task task) { + System.out.printf(INDENT + "OK, I've marked this task as not done yet:%n", ""); + System.out.printf(INDENT + "%s\n", "", task.getTaskString()); + } + + /** + * Prints the list of tasks to the console. + * + * @param task the list of tasks to be printed. + * @param taskCount the number of tasks to print. + */ + public static void printTask(List task, int taskCount) { + int listIndex = 1; + for (int i = 0; i < taskCount; i++) { + System.out.printf(INDENT + "%d. %s%n", "", listIndex, task.get(i).getTaskString()); + listIndex++; + } + } + + /** + * Prints a message indicating that a task has been added. + * + * @param task the task that has been added. + * @param taskCount the total number of tasks after the addition. + */ + public static void printAddedTask(Task task, int taskCount) { + System.out.printf(INDENT + "Got it. I've added this task:\n", ""); + System.out.printf(INDENT + "%s\n", "", task.getTaskString()); + System.out.printf(INDENT + "Now you have %d tasks in the list.%n", "", taskCount); + } + + /** + * Prints a message indicating that a task has been deleted. + * + * @param task the list of tasks from which the deleted task is assumed to be the first element. + * @param taskCount the total number of tasks remaining after deletion. + */ + public static void printDeleteTask(List task, int taskCount) { + System.out.printf(INDENT + "Noted. I've removed this task:\n", ""); + System.out.printf(INDENT + "%s\n", "", task.get(0).getTaskString()); + System.out.printf(INDENT + "Now you have %d tasks in the list.%n", "", taskCount); + } + + /** + * Reads a line of input from the user. + * + * @return the input string entered by the user. + */ + public String readInput() { + return scanner.nextLine(); + } + + /** + * Prints the greeting message to the console. + */ + public void printGreetingMessage() { + System.out.println("Hello! I'm Thoth"); + System.out.println("What can I do for you?"); + } + + /** + * Prints a goodbye message to the console. + */ + public void printGoodbye() { + System.out.println("Bye. Hope to see you again soon!"); + } + + /** + * Prints the error message to the console. + * + * @param message the error message to be printed. + */ + public void showError(String message) { + System.out.println(message); + } +} diff --git a/src/main/java/thoth/command/Command.java b/src/main/java/thoth/command/Command.java new file mode 100644 index 000000000..a9b06c8f8 --- /dev/null +++ b/src/main/java/thoth/command/Command.java @@ -0,0 +1,12 @@ +package thoth.command; + +import thoth.TaskManager; +import thoth.UserInterface; + +public abstract class Command { + public abstract void execute(TaskManager taskManager, UserInterface ui); + + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/thoth/command/DeadlineCommand.java b/src/main/java/thoth/command/DeadlineCommand.java new file mode 100644 index 000000000..bf727af8c --- /dev/null +++ b/src/main/java/thoth/command/DeadlineCommand.java @@ -0,0 +1,31 @@ +package thoth.command; + +import thoth.TaskManager; +import thoth.Storage; +import thoth.tasks.Deadline; +import thoth.tasks.Task; +import thoth.UserInterface; + +import java.io.IOException; + +public class DeadlineCommand extends Command { + String description; + String by; + + public DeadlineCommand(String description, String by) { + this.description = description; + this.by = by; + } + + @Override + public void execute(TaskManager taskManager, UserInterface ui) { + Task newTask = new Deadline(description, by); + taskManager.addTask(newTask); + try { + Storage.writeFile(newTask.getTaskString()); + } catch (IOException e) { + UserInterface.printMessage("Error writing to file: " + e.getMessage()); + } + UserInterface.printAddedTask(newTask, taskManager.getTaskCount()); + } +} diff --git a/src/main/java/thoth/command/DeleteCommand.java b/src/main/java/thoth/command/DeleteCommand.java new file mode 100644 index 000000000..b6fdde123 --- /dev/null +++ b/src/main/java/thoth/command/DeleteCommand.java @@ -0,0 +1,30 @@ +package thoth.command; + +import thoth.TaskManager; +import thoth.Storage; +import thoth.UserInterface; +import thoth.exceptions.ThothException; + +import java.io.IOException; + +public class DeleteCommand extends Command { + int taskIndex; + + public DeleteCommand(int taskIndex) { + this.taskIndex = taskIndex; + } + + @Override + public void execute(TaskManager taskManager, UserInterface ui) { + if (taskIndex < 0 || taskIndex >= taskManager.getTaskList().size()) { + throw new ThothException("Task index out of range"); + } + UserInterface.printDeleteTask(taskManager.getTask(taskIndex), taskManager.getTaskCount() - 1); + taskManager.removeTask(taskIndex); + try { + Storage.saveTasks(taskManager.getTaskList()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/thoth/command/EventCommand.java b/src/main/java/thoth/command/EventCommand.java new file mode 100644 index 000000000..7a6960b43 --- /dev/null +++ b/src/main/java/thoth/command/EventCommand.java @@ -0,0 +1,33 @@ +package thoth.command; + +import thoth.TaskManager; +import thoth.Storage; +import thoth.tasks.Event; +import thoth.tasks.Task; +import thoth.UserInterface; + +import java.io.IOException; + +public class EventCommand extends Command { + String description; + String from; + String to; + + public EventCommand(String description, String from, String to) { + this.description = description; + this.from = from; + this.to = to; + } + + @Override + public void execute(TaskManager taskManager, UserInterface ui) { + Task newTask = new Event(description, from, to); + taskManager.addTask(newTask); + try { + Storage.writeFile(newTask.getTaskString()); + } catch (IOException e) { + UserInterface.printMessage("Error writing to file: " + e.getMessage()); + } + UserInterface.printAddedTask(newTask, taskManager.getTaskCount()); + } +} diff --git a/src/main/java/thoth/command/ExitCommand.java b/src/main/java/thoth/command/ExitCommand.java new file mode 100644 index 000000000..d9d98b463 --- /dev/null +++ b/src/main/java/thoth/command/ExitCommand.java @@ -0,0 +1,17 @@ +package thoth.command; + +import thoth.TaskManager; +import thoth.UserInterface; + +public class ExitCommand extends Command { + + @Override + public void execute(TaskManager taskManager, UserInterface ui) { + ui.printGoodbye(); + } + + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/thoth/command/FindCommand.java b/src/main/java/thoth/command/FindCommand.java new file mode 100644 index 000000000..404047466 --- /dev/null +++ b/src/main/java/thoth/command/FindCommand.java @@ -0,0 +1,46 @@ +package thoth.command; + +import thoth.TaskManager; +import thoth.UserInterface; +import thoth.tasks.Task; // Assuming tasks are represented by a Task class + +import java.util.ArrayList; +import java.util.List; + +public class FindCommand extends Command { + String keyWord; + + public FindCommand(String keyWord) { + this.keyWord = keyWord; + } + + @Override + public void execute(TaskManager taskManager, UserInterface ui) { + if (keyWord.trim().isEmpty()) { + UserInterface.printMessage("Keyword cannot be empty. Please enter a valid keyword."); + return; + } + + List matchedTasks = new ArrayList<>(); + + // Retrieve the list of tasks from TaskManager + List tasks = taskManager.getTaskList(); + + // Search for tasks that contain the keyword (case-insensitive) + for (Task task : tasks) { + if (task.getTaskString().toLowerCase().contains(keyWord.toLowerCase())) { + matchedTasks.add(task); + } + } + + // Display the matching tasks or an appropriate message if none are found + if (matchedTasks.isEmpty()) { + UserInterface.printMessage("No matching tasks found for keyword: " + keyWord); + } else { + UserInterface.printMessage("Here are the matching tasks:"); + for (int i = 0; i < matchedTasks.size(); i++) { + UserInterface.printMessage((i + 1) + ". " + matchedTasks.get(i).getTaskString()); + } + } + } +} diff --git a/src/main/java/thoth/command/ListCommand.java b/src/main/java/thoth/command/ListCommand.java new file mode 100644 index 000000000..bcb685ddd --- /dev/null +++ b/src/main/java/thoth/command/ListCommand.java @@ -0,0 +1,12 @@ +package thoth.command; + +import thoth.TaskManager; +import thoth.UserInterface; + +public class ListCommand extends Command { + + @Override + public void execute(TaskManager taskManager, UserInterface ui) { + UserInterface.printTask(taskManager.getTaskList(), taskManager.getTaskCount()); + } +} diff --git a/src/main/java/thoth/command/MarkCommand.java b/src/main/java/thoth/command/MarkCommand.java new file mode 100644 index 000000000..f5eb3f453 --- /dev/null +++ b/src/main/java/thoth/command/MarkCommand.java @@ -0,0 +1,32 @@ +package thoth.command; + +import thoth.TaskManager; +import thoth.Storage; +import thoth.exceptions.ThothException; +import thoth.tasks.Task; +import thoth.UserInterface; + +import java.io.IOException; + +public class MarkCommand extends Command { + int taskIndex; + + public MarkCommand(int taskIndex) { + this.taskIndex = taskIndex; + } + + @Override + public void execute(TaskManager taskManager, UserInterface ui) { + if (taskIndex < 0 || taskIndex >= taskManager.getTaskList().size()) { + throw new ThothException("Task index out of range"); + } + taskManager.markTaskAsDone(taskIndex); + Task updatedTask = taskManager.getTaskList().get(taskIndex); + UserInterface.printMarkAsDone(updatedTask); + try { + Storage.saveTasks(taskManager.getTaskList()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/thoth/command/TodoCommand.java b/src/main/java/thoth/command/TodoCommand.java new file mode 100644 index 000000000..689526e47 --- /dev/null +++ b/src/main/java/thoth/command/TodoCommand.java @@ -0,0 +1,29 @@ +package thoth.command; + +import thoth.TaskManager; +import thoth.Storage; +import thoth.tasks.Task; +import thoth.tasks.Todo; +import thoth.UserInterface; + +import java.io.IOException; + +public class TodoCommand extends Command { + String description; + + public TodoCommand(String description) { + this.description = description; + } + + @Override + public void execute(TaskManager taskManager, UserInterface ui) { + Task newTask = new Todo(description); + taskManager.addTask(newTask); + try { + Storage.writeFile(newTask.getTaskString()); + } catch (IOException e) { + UserInterface.printMessage("Error writing to file: " + e.getMessage()); + } + UserInterface.printAddedTask(newTask, taskManager.getTaskCount()); + } +} diff --git a/src/main/java/thoth/command/UnknownCommand.java b/src/main/java/thoth/command/UnknownCommand.java new file mode 100644 index 000000000..97a42e0c3 --- /dev/null +++ b/src/main/java/thoth/command/UnknownCommand.java @@ -0,0 +1,17 @@ +package thoth.command; + +import thoth.TaskManager; +import thoth.UserInterface; + +public class UnknownCommand extends Command { + String message; + + public UnknownCommand(String message) { + this.message = message; + } + + @Override + public void execute(TaskManager taskManager, UserInterface ui) { + UserInterface.printMessage(message); + } +} diff --git a/src/main/java/thoth/command/UnmarkCommand.java b/src/main/java/thoth/command/UnmarkCommand.java new file mode 100644 index 000000000..0ef73cfde --- /dev/null +++ b/src/main/java/thoth/command/UnmarkCommand.java @@ -0,0 +1,33 @@ +package thoth.command; + +import thoth.TaskManager; +import thoth.Storage; +import thoth.exceptions.ThothException; +import thoth.tasks.Task; +import thoth.UserInterface; + +import java.io.IOException; + +public class UnmarkCommand extends Command { + int taskIndex; + + public UnmarkCommand(int taskIndex) { + this.taskIndex = taskIndex; + } + + @Override + public void execute(TaskManager taskManager, UserInterface ui) { + if (taskIndex < 0 || taskIndex >= taskManager.getTaskList().size()) { + throw new ThothException("Task index out of range"); + } + + taskManager.markTaskAsNotDone(taskIndex); + Task updatedTask = taskManager.getTaskList().get(taskIndex); + UserInterface.printMarkAsUndone(updatedTask); + try { + Storage.saveTasks(taskManager.getTaskList()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/thoth/exceptions/TaskParsingException.java b/src/main/java/thoth/exceptions/TaskParsingException.java new file mode 100644 index 000000000..29e3d71ad --- /dev/null +++ b/src/main/java/thoth/exceptions/TaskParsingException.java @@ -0,0 +1,7 @@ +package thoth.exceptions; + +public class TaskParsingException extends RuntimeException { + public TaskParsingException(String message) { + super(message); + } +} diff --git a/src/main/java/thoth/exceptions/ThothException.java b/src/main/java/thoth/exceptions/ThothException.java new file mode 100644 index 000000000..af78231ec --- /dev/null +++ b/src/main/java/thoth/exceptions/ThothException.java @@ -0,0 +1,7 @@ +package thoth.exceptions; + +public class ThothException extends RuntimeException { + public ThothException(String message) { + super(message); + } +} diff --git a/src/main/java/thoth/parser/Parser.java b/src/main/java/thoth/parser/Parser.java new file mode 100644 index 000000000..a2cbcd3a8 --- /dev/null +++ b/src/main/java/thoth/parser/Parser.java @@ -0,0 +1,181 @@ +/** + * Provides functionality to parse user input into executable commands. + */ +package thoth.parser; + +import thoth.command.Command; +import thoth.command.DeadlineCommand; +import thoth.command.EventCommand; +import thoth.command.MarkCommand; +import thoth.command.UnmarkCommand; +import thoth.command.ExitCommand; +import thoth.command.FindCommand; +import thoth.command.ListCommand; +import thoth.command.TodoCommand; +import thoth.command.DeleteCommand; + +import thoth.exceptions.ThothException; + +public class Parser { + + private static final int INDEX_OFFSET = 1; + + /** + * Parses the user input into the corresponding executable command. + * + * @param userInput the input string provided by the user. + * @return the Command object corresponding to the user input. + */ + public static Command parse(String userInput) throws ThothException { + String commandWord = userInput.split(" ")[0].trim(); + + if (userInput.equals("bye")) { + return new ExitCommand(); + } else if (userInput.equals("list")) { + return new ListCommand(); + } else if (commandWord.equals("mark")) { + return parseMarkCommand(userInput); + } else if (commandWord.equals("unmark")) { + return parseUnmarkCommand(userInput); + } else if (commandWord.equals("delete")) { + return parseDeleteCommand(userInput); + } else if (commandWord.equals("todo")) { + return parseTodoCommand(userInput); + } else if (commandWord.equals("deadline")) { + return parseDeadlineCommand(userInput); + } else if (commandWord.equals("event")) { + return parseEventCommand(userInput); + } else if (commandWord.equals("find")) { + return parseFindCommand(userInput); + } else { + throw new ThothException("Opps, me no understand that command: " + userInput); + } + } + + + /** + * Parses a command starting with "mark" and returns the corresponding MarkCommand. + * + * @param input the input string starting with "mark". + * @return a MarkCommand if the index is valid; otherwise, an UnknownCommand with an error message. + */ + private static Command parseMarkCommand(String input) throws ThothException { + String[] parts = input.split(" "); + if (parts.length < 2) { + throw new ThothException("Please provide a task number for the mark command."); + } + try { + // parts[1] should contain the number + int taskIndex = Integer.parseInt(parts[1].trim()) - Parser.INDEX_OFFSET; + return new MarkCommand(taskIndex); + } catch (NumberFormatException e) { + throw new ThothException("Please enter a valid task index for the mark command."); + } + } + + + /** + * Parses a command starting with "unmark" and returns the corresponding UnmarkCommand. + * + * @param input the input string starting with "unmark". + * @return an UnmarkCommand if the index is valid; otherwise, an UnknownCommand with an error message. + */ + private static Command parseUnmarkCommand(String input) { + String[] parts = input.split(" "); + if (parts.length < 2) { + throw new ThothException("Please provide a task index for the unmark command."); + } + try { + // parts[1] should contain the number + int taskIndex = Integer.parseInt(parts[1].trim()) - Parser.INDEX_OFFSET; + return new UnmarkCommand(taskIndex); + } catch (NumberFormatException e) { + throw new ThothException("Please enter a valid task index for unmark command."); + } + } + + /** + * Parses a command starting with "delete" and returns the corresponding DeleteCommand. + * + * @param input the input string starting with "delete". + * @return a DeleteCommand if the index is valid; otherwise, an UnknownCommand with an error message. + */ + private static Command parseDeleteCommand(String input) { + String[] parts = input.split(" "); + if (parts.length < 2) { + throw new ThothException("Please provide a task index for the delete command."); + } + try { + // parts[1] should contain the number + int taskIndex = Integer.parseInt(parts[1].trim()) - Parser.INDEX_OFFSET; + return new DeleteCommand(taskIndex); + } catch (NumberFormatException e) { + throw new ThothException("Please enter a valid task index for delete command."); + } + } + + /** + * Parses a command starting with "todo" and returns the corresponding TodoCommand. + * + * @param input the input string starting with "todo". + * @return a TodoCommand if the description is non-empty; otherwise, an UnknownCommand with an error message. + */ + private static Command parseTodoCommand(String input) { + String description = input.replace("todo", "").trim(); + if (description.isEmpty()) { + throw new ThothException("The description for the todo command is empty."); + } + return new TodoCommand(description); + } + + /** + * Parses a command starting with "deadline" and returns the corresponding DeadlineCommand. + * + * @param input the input string starting with "deadline". + * @return a DeadlineCommand if both description and deadline are provided; otherwise, an UnknownCommand with an error message. + */ + private static Command parseDeadlineCommand(String input) { + String[] parts = input.replace("deadline", "").trim().split(" /by "); + String description = parts[0].trim(); + String by = (parts.length > 1) ? parts[1].trim() : ""; + if (description.isEmpty() || by.isEmpty()) { + throw new ThothException("Oops task description is empty or time range not specified"); + } + return new DeadlineCommand(description, by); + } + + /** + * Parses a command starting with "event" and returns the corresponding EventCommand. + * + * @param input the input string starting with "event". + * @return an EventCommand if the description and time range are provided; otherwise, an UnknownCommand with an error message. + */ + private static Command parseEventCommand(String input) { + String[] parts = input.replace("event", "").trim().split(" /from "); + String description = parts[0].trim(); + String from = ""; + String to = ""; + if (parts.length > 1) { + String[] timeParts = parts[1].split(" /to "); + from = timeParts[0].trim(); + if (timeParts.length > 1) { + to = timeParts[1].trim(); + } + } + if (description.isEmpty() || from.isEmpty() || to.isEmpty()) { + throw new ThothException("Oops task description is empty or time range not specified"); + } + return new EventCommand(description, from, to); + } + + /** + * Parses a command starting with "find" and returns the corresponding FindCommand. + * + * @param input the input string starting with "find". + * @return a FindCommand with the provided keyword. + */ + private static Command parseFindCommand(String input) { + String keyWord = input.replace("find", "").trim(); + return new FindCommand(keyWord); + } +} diff --git a/src/main/java/thoth/parser/TaskParser.java b/src/main/java/thoth/parser/TaskParser.java new file mode 100644 index 000000000..a52f92aa1 --- /dev/null +++ b/src/main/java/thoth/parser/TaskParser.java @@ -0,0 +1,126 @@ +package thoth.parser; + +import thoth.tasks.Deadline; +import thoth.tasks.Event; +import thoth.tasks.Task; +import thoth.tasks.Todo; +import thoth.exceptions.TaskParsingException; + +/** + * Provides functionality to parse a line from the storage file into a Task object. + */ +public class TaskParser { + private static final int MIN_HEADER_SIZE = 7; + private static final int TYPE_INDEX = 1; + private static final int DONE_INDEX = 4; + + /** + * Main method to parse a storage file line into a Task. + * + * @param line the storage file line. + * @return the corresponding Task object. + * @throws TaskParsingException if the line is not in the expected format. + */ + public static Task parseLineToTask(String line) throws TaskParsingException { + if (line.length() < MIN_HEADER_SIZE) { + throw new TaskParsingException("Line too short to parse: " + line); + } + + char taskType = line.charAt(TYPE_INDEX); + char doneChar = line.charAt(DONE_INDEX); + boolean isDone = (doneChar == 'X'); + String content = line.substring(MIN_HEADER_SIZE).trim(); + + switch (taskType) { + case 'T': + return parseTodo(content, isDone); + case 'D': + return parseDeadline(content, isDone); + case 'E': + return parseEvent(content, isDone); + default: + throw new TaskParsingException("Unknown task type: " + taskType + " in line: " + line); + } + } + + /** + * Helper method to parse a Todo task. + * + * @param content the content of the task. + * @param isDone whether the task is marked as done. + * @return the Todo task. + * @throws TaskParsingException if the content is empty. + */ + private static Task parseTodo(String content, boolean isDone) throws TaskParsingException { + if (content.isEmpty()) { + throw new TaskParsingException("Todo description is empty."); + } + Todo todo = new Todo(content); + if (isDone) { + todo.markAsDone(); + } + return todo; + } + + /** + * Helper method to parse a Deadline task. + * + * @param content the content of the task. + * @param isDone whether the task is marked as done. + * @return the Deadline task. + * @throws TaskParsingException if the '(by:' delimiter is missing or parts are empty. + */ + private static Task parseDeadline(String content, boolean isDone) throws TaskParsingException { + int byIndex = content.indexOf("(by:"); + if (byIndex == -1) { + throw new TaskParsingException("Deadline task is missing '(by:' section: " + content); + } + String description = content.substring(0, byIndex).trim(); + String byPart = content.substring(byIndex + 5).trim(); // skip "(by:" + if (byPart.endsWith(")")) { + byPart = byPart.substring(0, byPart.length() - 1).trim(); + } + if (description.isEmpty() || byPart.isEmpty()) { + throw new TaskParsingException("Deadline description or deadline time is empty: " + content); + } + Deadline deadline = new Deadline(description, byPart); + if (isDone) { + deadline.markAsDone(); + } + return deadline; + } + + /** + * Helper method to parse an Event task. + * + * @param content the content of the task. + * @param isDone whether the task is marked as done. + * @return the Event task. + * @throws TaskParsingException if the '(from:' or 'to:' delimiters are missing or parts are empty. + */ + private static Task parseEvent(String content, boolean isDone) throws TaskParsingException { + int fromIndex = content.indexOf("(from:"); + if (fromIndex == -1) { + throw new TaskParsingException("Event task is missing '(from:' section: " + content); + } + String description = content.substring(0, fromIndex).trim(); + String fromPart = content.substring(fromIndex + 6).trim(); // skip "(from:" + int toIndex = fromPart.indexOf("to:"); + if (toIndex == -1) { + throw new TaskParsingException("Event task is missing 'to:' section in: " + content); + } + String fromTime = fromPart.substring(0, toIndex).trim(); + String toPart = fromPart.substring(toIndex + 3).trim(); // skip "to:" + if (toPart.endsWith(")")) { + toPart = toPart.substring(0, toPart.length() - 1).trim(); + } + if (description.isEmpty() || fromTime.isEmpty() || toPart.isEmpty()) { + throw new TaskParsingException("Event description or time range is empty: " + content); + } + Event event = new Event(description, fromTime, toPart); + if (isDone) { + event.markAsDone(); + } + return event; + } +} diff --git a/src/main/java/thoth/tasks/Deadline.java b/src/main/java/thoth/tasks/Deadline.java new file mode 100644 index 000000000..716883f4e --- /dev/null +++ b/src/main/java/thoth/tasks/Deadline.java @@ -0,0 +1,28 @@ +package thoth.tasks; + +public class Deadline extends Task { + + protected String by; + + /** + * Constructs a Deadline task with the specified description and deadline. + * + * @param description the description for the deadline task + * @param by the deadline for the task + */ + public Deadline(String description, String by) { + super(description); + this.by = by; + } + + /** + * Return a String to representing the deadline task including it type and deadline + * + * @return the formatted string + */ + @Override + public String getTaskString() { + return "[D]" + super.getTaskString() + " (by: " + by + ")"; + } +} + diff --git a/src/main/java/thoth/tasks/Event.java b/src/main/java/thoth/tasks/Event.java new file mode 100644 index 000000000..0ce021720 --- /dev/null +++ b/src/main/java/thoth/tasks/Event.java @@ -0,0 +1,30 @@ +package thoth.tasks; + +public class Event extends Task { + + protected String to; + protected String from; + + /** + * Constructs the Event Task with specified description, start and end time of the event + * + * @param description the description of the event + * @param from the starting time of the event + * @param to the ending time of the event + */ + public Event(String description, String from, String to) { + super(description); + this.from = from; + this.to = to; + } + + /** + * Return a string representing the event with its type and start and end timeframe + * + * @return the formatted task string + */ + @Override + public String getTaskString() { + return "[E]" + super.getTaskString() + " (from: " + from + " to: " + to + ")"; + } +} diff --git a/src/main/java/thoth/tasks/Task.java b/src/main/java/thoth/tasks/Task.java new file mode 100644 index 000000000..6049c89dc --- /dev/null +++ b/src/main/java/thoth/tasks/Task.java @@ -0,0 +1,44 @@ +package thoth.tasks; + +public class Task { + //parameters for checking and unchecking tasks + public static final String EMPTY_BOX = "[ ]"; + public static final String MARKED_BOX = "[X]"; + + protected String description; + protected boolean isDone; + + /** + * Constructs a task with the description + * + * @param description the description for the task + */ + public Task(String description) { + this.description = description; + this.isDone = false; + } + + /** + * mark task as done + */ + public void markAsDone() { + this.isDone = true; + } + + /** + * mark task as not done + */ + public void markAsNotDone() { + this.isDone = false; + } + + /** + * Returns a string representation of the task, including its completion status and description. + * + * @return the formatted task string + */ + public String getTaskString() { + String statusIcon = isDone ? MARKED_BOX : EMPTY_BOX; + return statusIcon + " " + description; + } +} diff --git a/src/main/java/thoth/tasks/Todo.java b/src/main/java/thoth/tasks/Todo.java new file mode 100644 index 000000000..898d733f2 --- /dev/null +++ b/src/main/java/thoth/tasks/Todo.java @@ -0,0 +1,26 @@ +package thoth.tasks; + +public class Todo extends Task { + + protected String by; + + /** + * constructs a todo tak with the specified description + * + * @param description the description of the todo task + */ + public Todo(String description) { + super(description); + } + + /** + * Returns a string representation of the todo task including its type + * + * @return a formatted task string with the type + */ + @Override + public String getTaskString() { + return "[T]" + super.getTaskString(); + } +} + diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 087374464..3dac3dfd5 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -15,7 +15,7 @@ IF ERRORLEVEL 1 ( REM no error here, errorlevel == 0 REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ..\bin Duke < input.txt > ACTUAL.TXT +java -classpath ..\bin Thoth < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT