diff --git a/docs/README.md b/docs/README.md index 47b9f984f..58563f389 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,169 @@ -# Duke User Guide +# Volkov User Guide -// Update the title above to match the actual product name +Volkov is a command-line task manager that allows you to add 3 different types of tasks, +set their relevant dates, and track your progress on the tasks with simple commands. +Your tasks are saved automatically, allowing you to revisit whenever you need to. -// Product screenshot goes here +## Adding a Todo Task: `todo` +Adds a todo task for Volkov to remember. -// Product intro goes here +**Format:** +`todo DESCRIPTION` +**Examples:** +``` +todo buy groceries +``` +**Expected Outcome:** +``` + ____________________________________________________________ + Roger. Task has been added to the list: + [T][ ] buy groceries + You now have 1 tasks in the list. + ____________________________________________________________ + +``` + +## Adding a Deadline Task: `deadline` +Adds a deadline task for Volkov to remember. + +**Format:** +`deadline DESCRIPTION /by DATE` +**Examples:** +``` +deadline do homework /by 2025-03-14 +``` +**Expected Outcome:** +``` + ____________________________________________________________ + Roger. Task has been added to the list: + [D][ ] do homework (by: Mar 14 2025) + You now have 2 tasks in the list. + ____________________________________________________________ +``` + +## Adding an Event Task: `event` +Adds an event task for Volkov to remember. + +**Format:** +`event DESCRIPTION /from STARTDATE /to ENDDATE` +**Examples:** +``` +event open house /from 2025-02-16 /to 2025-02-18 +``` +**Expected Outcome:** +``` + ____________________________________________________________ + Roger. Task has been added to the list: + [E][ ] open house (from: Feb 16 2025 to: Feb 18 2025) + You now have 3 tasks in the list. + ____________________________________________________________ +``` + +## Mark a task as done: `mark` +Marks a task as completed in Volkov's task list. + +**Format:** +`mark TASKNO` +**Examples:** +``` +mark 1 +``` +**Expected Outcome:** +``` + ____________________________________________________________ + Roger. This task has been marked as done: + [T][X] buy groceries + ____________________________________________________________ +``` -## Adding deadlines +## Mark a task as notdone: `unmark` +Marks a task as uncompleted in Volkov's task list. -// Describe the action and its outcome. +**Format:** +`unmark TASKNO` +**Examples:** +``` +unmark 1 +``` +**Expected Outcome:** +``` + ____________________________________________________________ + Roger. This task has been marked as not done yet: + [T][ ] buy groceries + ____________________________________________________________ +``` -// Give examples of usage +## Delete a task: `delete` +Removes a task from Volkov's task list. -Example: `keyword (optional arguments)` +**Format:** +`delete TASKNO` +**Examples:** +``` +delete 2 +``` +**Expected Outcome:** +``` + ____________________________________________________________ + Roger. Task has been removed: + [D][ ] do homework (by: Mar 14 2025) + You now have 2 tasks in the list. + ____________________________________________________________ +``` -// A description of the expected outcome goes here +## List all tasks: `list` +Lists all tasks in Volkov's task list. +**Format:** +`list` +**Expected Outcome:** ``` -expected output + ____________________________________________________________ + These are all the tasks in your list: + 1.[T][ ] buy groceries + 2.[E][ ] open house (from: Feb 16 2025 to: Feb 18 2025) + ____________________________________________________________ ``` -## Feature ABC +## Find a task: `find` +Finds the tasks in Volkov's task list that match the query -// Feature details +**Format:** +`find SEARCHTERM` +**Examples:** +``` +find groceries +``` +**Expected Outcome:** +``` + ____________________________________________________________ + These are the tasks that match you search query: + 1.[T][ ] buy groceries + ____________________________________________________________ +``` + +## Exit Program: `bye` +Saves the task list on the hard disk and closes the program +**Format:** +`bye` +**Expected Outcome:** +``` + ____________________________________________________________ + See you again soon! Service terminated. + ____________________________________________________________ +``` -## Feature XYZ +### Command Summary -// Feature details \ No newline at end of file +| Action | Format and Examples | +|--------------|--------------------------------------------------------------------------------------------------------------| +| **Todo** | `todo DESCRIPTION`
e.g., `todo buy groceries` | +| **Deadline** | `deadline DESCRIPTION /by DATE`
e.g., `deadline do homework /by 2025-03-14` | +| **Event** | `event DESCRIPTION /from STARTDATE /to ENDDATE`
e.g., `event open house /from 2025-02-16 /to 2025-02-18` | +| **Mark** | `mark TASKNO`
e.g., `mark 1` | +| **Unmark** | `unmark TASKNO`
e.g., `unmark 1` | +| **Delete** | `delete TASKNO`
e.g., `delete 1` | +| **List** | `list` | +| **Find** | `find SEARCHTERM`
e.g., `find groceries` | +| **Bye** | `bye` | diff --git a/src/main/java/ByeCommand.java b/src/main/java/ByeCommand.java new file mode 100644 index 000000000..1498397ce --- /dev/null +++ b/src/main/java/ByeCommand.java @@ -0,0 +1,25 @@ +/** + * Represents the command to exit the program + */ +public class ByeCommand extends Commands { + + /** + * Executes the exit command by displaying a farewell message to the user. + * + * @param tasks The {@code TaskList} containing all tasks. + * @param ui The {@code Ui} for user interaction. + * @param storage The {@code Storage} for file operations. + */ + public void execute(TaskList tasks, Ui ui, Storage storage) { + new Ui().formatResponse(" See you again soon! Service terminated."); + } + + /** + * Returns boolean true to indicate the exiting of the program + * + * @return {@code true} + */ + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/Commands.java b/src/main/java/Commands.java new file mode 100644 index 000000000..eae21798c --- /dev/null +++ b/src/main/java/Commands.java @@ -0,0 +1,29 @@ +/** + * Represents an abstract command to be executed in the main body. + * Subclasses of {@code Commands} implements methods with specific behavior + * relevant to their respective commands + */ +public abstract class Commands { + /** + * Executes the command with the task list, user interface, and storage. + *

+ * Exact implementations depends on the relevant command. + * + * @param tasks The {@code TaskList} containing all tasks. + * @param ui The {@code Ui} object handling user interactions. + * @param storage The {@code Storage} object for file operations. + */ + public void execute(TaskList tasks, Ui ui, Storage storage) {} + + /** + * Determines whether this command is an exit command. + *

+ * Defaults to {@code false}, and only exit commands + * would override this method to return {@code true}. + * + * @return {@code true} if the command should terminate the program, otherwise {@code false}. + */ + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 000000000..6e62d7ae5 --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,49 @@ +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * Represents a deadline task with a description, completion status, and deadline date. + *

+ * The {@code Deadline} class provides methods to mark the task as done, + * unmark it, retrieve its status, and formatting for displaying or saving. + */ +public class Deadline extends Task { + protected String deadline; + protected LocalDate deadlineLocalDate; + + public Deadline(String description, String deadline) { + super(description); + this.deadline = deadline; + try { + this.deadlineLocalDate = LocalDate.parse(deadline); + } catch (DateTimeParseException e) { + this.deadlineLocalDate = null; + } + + } + + /** + * {@inheritDoc} + * This method is inherited from {@link Task}. + * @return A string representation of the task, its description, and deadline date + */ + public String txtSave() { + return "[D]" + super.txtSave() + "|" + deadline; + } + + /** + * {@inheritDoc} + * This method is inherited from {@link Task}. + * @return A formatted string representing the task. + */ + @Override + public String toString() { + if (deadlineLocalDate != null) { + String deadlineFormatted = deadlineLocalDate.format(DateTimeFormatter.ofPattern("MMM d yyyy")); + return "[D]" + super.toString() + " (by: " + deadlineFormatted + ")"; + } else { + return "[D]" + super.toString() + " (by: " + deadline + ")"; + } + } +} diff --git a/src/main/java/DeadlineCommand.java b/src/main/java/DeadlineCommand.java new file mode 100644 index 000000000..035b449ae --- /dev/null +++ b/src/main/java/DeadlineCommand.java @@ -0,0 +1,30 @@ +/** + * Represents the command to add a deadline task + *

+ * The {@code DeadlineCommand} creates a new {@code Deadline} task + * with a specified description and a deadline date, + * and adds it to the {@code TaskList}. + */ +public class DeadlineCommand extends Commands { + private final String taskDesc; + private final String deadline; + + DeadlineCommand(String taskDesc, String deadline) { + this.taskDesc = taskDesc; + this.deadline = deadline; + } + + /** + * Executes the deadline command by creating a {@code Deadline} object, + * adds it to the taskList and displays a confirmation message. + * + * @param tasks The {@code TaskList} containing all tasks. + * @param ui The {@code Ui} for user interaction. + * @param storage The {@code Storage} for file operations. + */ + public void execute(TaskList tasks, Ui ui, Storage storage) { + Deadline t = new Deadline(taskDesc, deadline); + tasks.addTask(t); + ui.formatTaskMsg(t, tasks); + } +} diff --git a/src/main/java/DeleteCommand.java b/src/main/java/DeleteCommand.java new file mode 100644 index 000000000..51509ebcb --- /dev/null +++ b/src/main/java/DeleteCommand.java @@ -0,0 +1,36 @@ +/** + * Represents the command to delete a task based on its taskNo from the tasklist + */ +public class DeleteCommand extends Commands { + private final int taskNo; + + DeleteCommand(int taskNo) { + this.taskNo = taskNo; + } + + /** + * Executes the delete command by removing the specified task from the task list. + * Displays a confirmation message when successfully deleted. + * If the task number is invalid, an error message is displayed instead. + * + * @param tasks The {@code TaskList} containing all tasks. + * @param ui The {@code Ui} for user interaction. + * @param storage The {@code Storage} for file operations. + */ + public void execute(TaskList tasks, Ui ui, Storage storage) { + try { + String reply = " Roger. Task has been removed:\n" + + " " + tasks.getTask(taskNo).toString() + "\n" + + " You now have " + (tasks.size()-1) + " tasks in the list."; + ui.formatResponse(reply); + tasks.removeTask(taskNo); + } catch (NullPointerException | IndexOutOfBoundsException e) { + String reply = " Task number not found, reenter with correct task number:"; + new MissingCommand(reply).execute(tasks, ui, storage); + } catch (NumberFormatException e) { + String reply = " No task number detected, reenter with correct task number:"; + new MissingCommand(reply).execute(tasks, ui, storage); + } + + } +} 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/Event.java b/src/main/java/Event.java new file mode 100644 index 000000000..dbecc1b1f --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,54 @@ +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * Represents an event task with a description, completion status, and start and end dates. + *

+ * The {@code Event} class provides methods to mark the task as done, + * unmark it, retrieve its status, and formatting for displaying or saving. + */ +public class Event extends Task { + protected String startDate; + protected String endDate; + protected LocalDate startLocalDate; + protected LocalDate endLocalDate; + + public Event(String description, String startDate, String endDate) { + super(description); + this.startDate = startDate; + this.endDate = endDate; + try { + this.startLocalDate = LocalDate.parse(startDate); + this.endLocalDate = LocalDate.parse(endDate); + } catch (DateTimeParseException e) { + this.startLocalDate = null; + this.endLocalDate = null; + } + } + + /** + * {@inheritDoc} + * This method is inherited from {@link Task}. + * @return A string representation of the task, its description, and start and end dates. + */ + public String txtSave() { + return "[E]" + super.txtSave() + "|" + startDate + "|" + endDate; + } + + /** + * {@inheritDoc} + * This method is inherited from {@link Task}. + * @return A formatted string representing the task. + */ + @Override + public String toString() { + if (startLocalDate != null && endLocalDate != null) { + String startDateFormatted = startLocalDate.format(DateTimeFormatter.ofPattern("MMM d yyyy")); + String endDateFormatted = endLocalDate.format(DateTimeFormatter.ofPattern("MMM d yyyy")); + return "[E]" + super.toString() + " (from: " + startDateFormatted + " to: " + endDateFormatted + ")"; + } else { + return "[E]" + super.toString() + " (from: " + startDate + " to: " + endDate + ")"; + } + } +} diff --git a/src/main/java/EventCommand.java b/src/main/java/EventCommand.java new file mode 100644 index 000000000..6d4071ea8 --- /dev/null +++ b/src/main/java/EventCommand.java @@ -0,0 +1,32 @@ +/** + * Represents the command to add an event task. + *

+ * The {@code EventCommand} creates a new {@code Event} task with + * a specified description and a start and end date, + * and adds it to the {@code TaskList}. + */ +public class EventCommand extends Commands { + private final String taskDesc; + private final String startDate; + private final String endDate; + + EventCommand(String taskDesc, String startDate, String endDate) { + this.taskDesc = taskDesc; + this.startDate = startDate; + this.endDate = endDate; + } + + /** + * Executes the command by creating a new {@code Event} object, + * adds it to the task list, and displays a confirmation message. + * + * @param tasks The {@code TaskList} containing all tasks. + * @param ui The {@code Ui} for user interaction. + * @param storage The {@code Storage} for file operations. + */ + public void execute(TaskList tasks, Ui ui, Storage storage) { + Event t = new Event(taskDesc, startDate, endDate); + tasks.addTask(t); + ui.formatTaskMsg(t, tasks); + } +} diff --git a/src/main/java/FindCommand.java b/src/main/java/FindCommand.java new file mode 100644 index 000000000..a4657e8e7 --- /dev/null +++ b/src/main/java/FindCommand.java @@ -0,0 +1,33 @@ +/** + * Represents the command to search for tasks + *

+ * The {@code FindCommand} finds the task the user wishes to search for in the {@code TaskList}. + */ +public class FindCommand extends Commands { + private final String searchTerm; + + FindCommand(String searchTerm) { + this.searchTerm = searchTerm; + } + + /** + * Executes the command by iterating through and printing every task in the {@code TaskList} + * that matches the searchTerm. + * + * @param tasks The {@code TaskList} containing all tasks. + * @param ui The {@code Ui} for user interaction. + * @param storage The {@code Storage} for file operations. + */ + public void execute(TaskList tasks, Ui ui, Storage storage) { + StringBuilder sb = new StringBuilder(); + sb.append(" These are the tasks that match you search query:"); + int count = 1; + for (int i = 0; i < tasks.size(); i++) { + if (tasks.getTask(i).find(searchTerm)) { + sb.append("\n ").append(count).append(".").append(tasks.getTask(i)); + count++; + } + } + ui.formatResponse(sb.toString()); + } +} diff --git a/src/main/java/ListCommand.java b/src/main/java/ListCommand.java new file mode 100644 index 000000000..b5abb0a3a --- /dev/null +++ b/src/main/java/ListCommand.java @@ -0,0 +1,23 @@ +/** + * Represents the command to list all tasks in the task list. + *

+ * The {@code ListCommand} retrieves and displays all tasks currently stored in the {@code TaskList}. + */ +public class ListCommand extends Commands { + + /** + * Executes the command by iterating through and printing every task in the {@code TaskList}. + * + * @param tasks The {@code TaskList} containing all tasks. + * @param ui The {@code Ui} for user interaction. + * @param storage The {@code Storage} for file operations. + */ + public void execute(TaskList tasks, Ui ui, Storage storage) { + StringBuilder sb = new StringBuilder(); + sb.append(" These are all the tasks in your list:"); + for (int i = 0; i < tasks.size(); i++) { + sb.append("\n ").append(i + 1).append(".").append(tasks.getTask(i)); + } + ui.formatResponse(sb.toString()); + } +} diff --git a/src/main/java/MarkCommand.java b/src/main/java/MarkCommand.java new file mode 100644 index 000000000..2f32d0e11 --- /dev/null +++ b/src/main/java/MarkCommand.java @@ -0,0 +1,39 @@ +/** + * Represents the command to mark a task as done. + *

+ * The {@code MarkCommand} updates the specified task number's + * task's status as done and displays a confirmation message to the user. + */ +public class MarkCommand extends Commands { + private final int taskNo; + + MarkCommand(int taskNo) { + this.taskNo = taskNo; + } + + /** + * Executes the command by marking the specified task as done. + * Displays a confirmation message when successful, or an error + * message if the task number is invalid. + * + * @param tasks The {@code TaskList} containing all tasks. + * @param ui The {@code Ui} for user interaction. + * @param storage The {@code Storage} for file operations. + */ + public void execute(TaskList tasks, Ui ui, Storage storage) { + try { + tasks.getTask(taskNo).markAsDone(); + String reply = " Roger. This task has been marked as done:\n" + + " " + tasks.getTask(taskNo).toString(); + ui.formatResponse(reply); + } catch (NullPointerException | IndexOutOfBoundsException e) { + String reply = " Task number not found, reenter with correct task number:"; + new MissingCommand(reply).execute(tasks, ui, storage); + } catch (NumberFormatException e) { + String reply = " No task number detected, reenter with correct task number:"; + new MissingCommand(reply).execute(tasks, ui, storage); + } + } + + +} diff --git a/src/main/java/MissingCommand.java b/src/main/java/MissingCommand.java new file mode 100644 index 000000000..787481674 --- /dev/null +++ b/src/main/java/MissingCommand.java @@ -0,0 +1,25 @@ +/** + * Represents a command for handling unrecognized or incomplete commands. + *

+ * The {@code MissingCommand} is used when a user enters an invalid command + * or lacks relevant parameters. It then displays an error message to + * guide the user in correcting this. + */ +public class MissingCommand extends Commands{ + private final String reply; + + MissingCommand(String reply){ + this.reply = reply; + } + + /** + * Executes the command by displaying the provided error message to the user. + * + * @param tasks The {@code TaskList} containing all tasks. + * @param ui The {@code Ui} for user interaction. + * @param storage The {@code Storage} for file operations. + */ + public void execute(TaskList tasks, Ui ui, Storage storage) { + new Ui().formatResponse(" " + reply); + } +} diff --git a/src/main/java/MissingDescription.java b/src/main/java/MissingDescription.java new file mode 100644 index 000000000..7a2076be2 --- /dev/null +++ b/src/main/java/MissingDescription.java @@ -0,0 +1,5 @@ +public class MissingDescription extends RuntimeException { + public MissingDescription(String message) { + super(message); + } +} diff --git a/src/main/java/MissingKeyword.java b/src/main/java/MissingKeyword.java new file mode 100644 index 000000000..9ba9d6caa --- /dev/null +++ b/src/main/java/MissingKeyword.java @@ -0,0 +1,5 @@ +public class MissingKeyword extends RuntimeException { + public MissingKeyword(String message) { + super(message); + } +} diff --git a/src/main/java/Parser.java b/src/main/java/Parser.java new file mode 100644 index 000000000..21f90d611 --- /dev/null +++ b/src/main/java/Parser.java @@ -0,0 +1,131 @@ +/** + * The {@code Parser} class deals with making sense of the user command, + * then maps it to the respective commands. + */ +public class Parser { + private static final int FIND_CMD_LENGTH = 5; + private static final int MARK_CMD_LENGTH = 5; + private static final int UNMARK_CMD_LENGTH = 7; + private static final int DELETE_CMD_LENGTH = 7; + + private static final int TODO_CMD_LENGTH = 5; + private static final int DEADLINE_CMD_LENGTH = 8; + private static final int EVENT_CMD_LENGTH = 5; + private static final int DEADLINE_BY_LENGTH = 4; + private static final int EVENT_FROM_LENGTH = 6; + private static final int EVENT_TO_LENGTH = 4; + + private static final String BYE_COMMAND = "bye"; + private static final String LIST_COMMAND = "list"; + private static final String FIND_COMMAND = "find"; + private static final String MARK_COMMAND = "mark"; + private static final String UNMARK_COMMAND = "unmark"; + private static final String DELETE_COMMAND = "delete"; + private static final String TODO_COMMAND = "todo"; + private static final String DEADLINE_COMMAND = "deadline"; + private static final String EVENT_COMMAND = "event"; + private static final String BY_KEYWORD = "/by"; + private static final String FROM_KEYWORD = "/from"; + private static final String TO_KEYWORD = "/to"; + + /** + * Parses the given user input and returns the corresponding command. + *

+ * This method identifies the relevant command by checking the input string + * against predetermined command keywords and extracts the respective necessary + * parameters, using which to return an appropriate {@code Commands} object. + * + * @param input The user input to parse. + * @return A {@code Commands} object representing the parsed command. + */ + public static Commands parse(String input) { + if (input.startsWith(BYE_COMMAND)) { + return new ByeCommand(); + } else if (input.startsWith(LIST_COMMAND)) { + return new ListCommand(); + + } else if (input.startsWith(FIND_COMMAND)) { + String searchTerm = input.substring(FIND_CMD_LENGTH); + return new FindCommand(searchTerm); + + } else if (input.startsWith(MARK_COMMAND)) { + int taskNo = Integer.parseInt(input.substring(MARK_CMD_LENGTH)) - 1; + return new MarkCommand(taskNo); + + } else if (input.startsWith(UNMARK_COMMAND)) { + int taskNo = Integer.parseInt(input.substring(UNMARK_CMD_LENGTH)) - 1; + return new UnmarkCommand(taskNo); + + } else if (input.startsWith(DELETE_COMMAND)) { + int taskNo = Integer.parseInt(input.substring(DELETE_CMD_LENGTH)) - 1; + return new DeleteCommand(taskNo); + + } else if (input.startsWith(TODO_COMMAND)) { + try { + String taskDesc = input.substring(TODO_CMD_LENGTH); + if (taskDesc.trim().isEmpty()) { + throw new MissingDescription("MISSING DESCRIPTION, reenter with description"); + } + return new TodoCommand(taskDesc); + } catch (MissingDescription e) { + return new MissingCommand(" " + e.getMessage()); + } catch (IndexOutOfBoundsException e) { + return new MissingCommand(" " + "MISSING DESCRIPTION, reenter with description"); + } + + } else if (input.startsWith(DEADLINE_COMMAND)) { + try { + int indexOfDeadline = input.indexOf(BY_KEYWORD); + if (indexOfDeadline == -1) { + throw new MissingKeyword("MISSING '/by' keyword"); + } + String taskDesc = input.substring(DEADLINE_CMD_LENGTH, indexOfDeadline - 1).trim(); + if (taskDesc.trim().isEmpty()) { + throw new MissingDescription("MISSING DESCRIPTION, reenter with description"); + } + String deadline = input.substring(indexOfDeadline - 1 + DEADLINE_BY_LENGTH).trim(); + if (deadline.trim().isEmpty()) { + throw new MissingDescription("MISSING DEADLINE, reenter with deadline"); + } + return new DeadlineCommand(taskDesc, deadline); + } catch (MissingDescription | MissingKeyword e) { + return new MissingCommand(" " + e.getMessage()); + } catch (IndexOutOfBoundsException e) { + return new MissingCommand(" " + "MISSING DESCRIPTION, reenter with description"); + } + + } else if (input.startsWith(EVENT_COMMAND)) { + try { + int indexOfStartDate = input.indexOf(FROM_KEYWORD); + if (indexOfStartDate == -1) { + throw new MissingKeyword("MISSING '/from' keyword"); + } + int indexOfEndDate = input.indexOf(TO_KEYWORD); + if (indexOfEndDate == -1) { + throw new MissingKeyword("MISSING '/to' keyword"); + } + String taskDesc = input.substring(EVENT_CMD_LENGTH, indexOfStartDate - 1).trim(); + if (taskDesc.trim().isEmpty()) { + throw new MissingDescription("MISSING DESCRIPTION, reenter with description"); + } + + String startDate = input.substring(indexOfStartDate - 1 + EVENT_FROM_LENGTH, indexOfEndDate - 1).trim(); + if (startDate.trim().isEmpty()) { + throw new MissingDescription("MISSING DESCRIPTION, reenter with description"); + } + String endDate = input.substring(indexOfEndDate - 1 + EVENT_TO_LENGTH).trim(); + if (endDate.trim().isEmpty()) { + throw new MissingDescription("MISSING DESCRIPTION, reenter with description"); + } + return new EventCommand(taskDesc, startDate, endDate); + } catch (MissingDescription | MissingKeyword e) { + return new MissingCommand(" " + e.getMessage()); + } catch (IndexOutOfBoundsException e) { + return new MissingCommand(" " + "MISSING DESCRIPTION, reenter with description"); + } + + } else { + return new MissingCommand("Unknown command received, reenter command:"); + } + } +} diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java new file mode 100644 index 000000000..cb28404c6 --- /dev/null +++ b/src/main/java/Storage.java @@ -0,0 +1,113 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Scanner; + +/** + * The {@code Storage} class deals with the loading + * and saving of tasks in a local txt file. + */ +public class Storage { + private final String filePath; + Ui ui; + + Storage(String filePath, Ui ui) { + this.filePath = filePath; + this.ui = ui; + } + + /** + * Saves the current list of tasks to a txt file. + *

+ * If the file or directory does not exist, it will be created first. + * Tasks are saved in a format to ease the load process + * + * @param taskList The list of tasks to be saved. + */ + public void saveFile(TaskList taskList) { + try { + File f = new File(filePath); + + File parentDir = f.getParentFile(); + if (!parentDir.exists()) { + if (parentDir.mkdirs()) { + ui.formatResponse(" Folder missing, new folder created"); + } + } + + if (f.createNewFile()) { + ui.formatResponse(" No file present, new file created"); + } + + FileWriter fw = new FileWriter(filePath); + for (Task task : taskList.getList()) { + fw.write(task.txtSave() + "\n"); + } + fw.close(); + + } catch (IOException e) { + System.out.println("Something went wrong: " + e.getMessage()); + } + } + + /** + * Attempts to load tasks from the txt file into a new {@code TaskList}. + *

+ * If the file exists, it greets the user as a returning user, + * reads, parses and loads the tasks from the txt file to a new {@code TaskList} + * which then gets returned. Otherwise, it greets the user as a new user + * and returns an empty {@code TaskList}. + * + * @return A {@code TaskList} containing the loaded tasks. + */ + public TaskList loadFile() { + TaskList taskList = new TaskList(); + + File f = new File(filePath); + if (f.exists()) { + ui.returnUserGreet(); + } else { + ui.newUserGreet(); + } + + try { + Scanner s = new Scanner(f); + while (s.hasNextLine()) { + String taskString = s.nextLine(); + + if (taskString.startsWith("[T]")) { + Task t = new Todo(taskString.substring(7).trim()); + if (taskString.contains("[X]")) { + t.markAsDone(); + } + taskList.addTask(t); + } else if (taskString.startsWith("[D]")) { + String[] descAndDate = taskString.substring(7).split("\\|"); + String desc = descAndDate[0].trim(); + String date = descAndDate[1].trim(); + Task t = new Deadline(desc, date); + if (taskString.contains("[X]")) { + t.markAsDone(); + } + taskList.addTask(t); + } else if (taskString.startsWith("[E]")) { + String[] descAndDate = taskString.substring(7).split("\\|"); + String desc = descAndDate[0].trim(); + String start = descAndDate[1].trim(); + String end = descAndDate[2].trim(); + Task t = new Event(desc, start, end); + if (taskString.contains("[X]")) { + t.markAsDone(); + } + taskList.addTask(t); + } + } + } catch (FileNotFoundException e) { + + } + return taskList; + } +} + + diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 000000000..d640d7e85 --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,61 @@ +/** + * Represents a generic task with a description and completion status. + *

+ * The {@code Task} class provides methods to mark the task as done, + * unmark it, retrieve its status, and formatting for displaying or saving. + */ +public class Task { + protected String description; + protected boolean isDone; + + public Task(String description) { + this.description = description; + this.isDone = false; + } + + public void markAsDone() { + isDone = true; + } + + public void unmarkAsDone() { + isDone = false; + } + + /** + * Returns the string formatting that represents the completion status of the task. + * + * @return A string "[X] " if the task is done, "[ ] " if not. + */ + public String getMark() { + if (isDone) { + return "[X] "; + } else { + return "[ ] "; + } + } + + public boolean find(String searchTerm) { + return this.description.contains(searchTerm); + } + + /** + * Returns the string formatting used for saving of the task's information + * + * @return A string representation of the task and its information + */ + public String txtSave() { + return getMark() + description; + } + + /** + * Returns a string representation of the task, + * including its type, completion status and description. + * + * @return A formatted string representing the task. + */ + @Override + public String toString() { + String mark = getMark(); + return mark + description; + } +} diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java new file mode 100644 index 000000000..f4191d2c5 --- /dev/null +++ b/src/main/java/TaskList.java @@ -0,0 +1,31 @@ +/** + * Contains the list of tasks + *

+ * The {@code TaskList} class provides methods to add, remove, retrieve, + * and access the number of tasks stored in an {@code ArrayList}. + */ +import java.util.ArrayList; + +public class TaskList { + private static final ArrayList listOfTasks = new ArrayList<>(); + + public ArrayList getList() { + return listOfTasks; + } + + public void addTask(Task task) { + listOfTasks.add(task); + } + + public void removeTask(int index) { + listOfTasks.remove(index); + } + + public Task getTask(int index) { + return listOfTasks.get(index); + } + + public int size() { + return listOfTasks.size(); + } +} diff --git a/src/main/java/Todo.java b/src/main/java/Todo.java new file mode 100644 index 000000000..990c72eb7 --- /dev/null +++ b/src/main/java/Todo.java @@ -0,0 +1,31 @@ +/** + * Represents a todo task with a description and completion status. + *

+ * The {@code Todo} class provides methods to mark the task as done, + * unmark it, retrieve its status, and formatting for displaying or saving. + */ +public class Todo extends Task { + + public Todo(String description) { + super(description); + } + + /** + * {@inheritDoc} + * This method is inherited from {@link Task}. + * @return A string representation of the task and its description + */ + public String txtSave() { + return "[T]" + super.txtSave(); + } + + /** + * {@inheritDoc} + * This method is inherited from {@link Task}. + * @return A formatted string representing the task. + */ + @Override + public String toString() { + return "[T]" + super.toString(); + } +} diff --git a/src/main/java/TodoCommand.java b/src/main/java/TodoCommand.java new file mode 100644 index 000000000..109bf35b9 --- /dev/null +++ b/src/main/java/TodoCommand.java @@ -0,0 +1,27 @@ +/** + * Represents a command to add a new to-do task. + *

+ * The {@code TodoCommand} creates a new to-do task with a specified description + * and adds it to the task list.

+ */ +public class TodoCommand extends Commands { + private final String taskDesc; + + TodoCommand(String taskDesc) { + this.taskDesc = taskDesc; + } + + /** + * Executes the command by creating a new {@code Todo} object, + * adds it to the task list, and displays a confirmation message. + * + * @param tasks The {@code TaskList} containing all tasks. + * @param ui The {@code Ui} for user interaction. + * @param storage The {@code Storage} for file operations. + */ + public void execute(TaskList tasks, Ui ui, Storage storage) { + Todo t = new Todo(taskDesc); + tasks.addTask(t); + ui.formatTaskMsg(t, tasks); + } +} diff --git a/src/main/java/Ui.java b/src/main/java/Ui.java new file mode 100644 index 000000000..507ca6b6a --- /dev/null +++ b/src/main/java/Ui.java @@ -0,0 +1,71 @@ +import java.util.Scanner; +/** + * The {@code Ui} class deals with interactions with the + * user by handling the formatting of responses, + * reading user input, and displaying greeting messages. + */ +public class Ui { + private Scanner sc = new Scanner(System.in); + + private final String newUserOpening = " ____________________________________________________________\n" + + " Greetings user. I'm Volkov, your task manager\n" + + " What can I do for you?\n" + + " ____________________________________________________________\n"; + + private final String returnUserOpening = " ____________________________________________________________\n" + + " Welcome back!\n" + + " What can Volkov do for you?\n" + + " ____________________________________________________________\n"; + + /** + * Prints a formatted response. + * + * @param msg The message to be displayed after formatting. + */ + public void formatResponse(String msg) { + System.out.println(formatResponseString(msg)); + } + + /** + * Formats a given message. + * + * @param msg The message to be formatted. + * @return A formatted string of the given message + */ + public String formatResponseString(String msg) { + return " ____________________________________________________________\n" + + msg + "\n" + + " ____________________________________________________________\n"; + } + + /** + * Prints the formatted message for when a new task is added. + *

+ * @param t The task that was added. + * @param tasks The task list that the task is added to. + */ + public void formatTaskMsg(Task t, TaskList tasks) { + String msg = " Roger. Task has been added to the list:\n" + + " " + t.toString() + "\n" + + " You now have " + tasks.size() + " tasks in the list."; + System.out.println(formatResponseString(msg)); + } + + public String readCommand() { + return sc.nextLine(); + } + + /** + * Displays the new user greeting message. + */ + public void newUserGreet() { + System.out.println(newUserOpening); + } + + /** + * Displays the returning user greeting message. + */ + public void returnUserGreet() { + System.out.println(returnUserOpening); + } +} diff --git a/src/main/java/UnmarkCommand.java b/src/main/java/UnmarkCommand.java new file mode 100644 index 000000000..cfd3c6d55 --- /dev/null +++ b/src/main/java/UnmarkCommand.java @@ -0,0 +1,37 @@ +/** + * Represents the command to mark a task as not done. + *

+ * The {@code UnmarkCommand} updates the specified task number's + * task's status as not done and displays a confirmation message to the user. + */ +public class UnmarkCommand extends Commands { + private final int taskNo; + + UnmarkCommand(int taskNo) { + this.taskNo = taskNo; + } + + /** + * Executes the command by marking the specified task as not done. + * Displays a confirmation message when successful, or an error + * message if the task number is invalid. + * + * @param tasks The {@code TaskList} containing all tasks. + * @param ui The {@code Ui} for user interaction. + * @param storage The {@code Storage} for file operations. + */ + public void execute(TaskList tasks, Ui ui, Storage storage) { + try { + tasks.getTask(taskNo).unmarkAsDone(); + String reply = " Roger. This task has been marked as not done yet:\n" + + " " + tasks.getTask(taskNo).toString(); + ui.formatResponse(reply); + } catch (NullPointerException | IndexOutOfBoundsException e) { + String reply = " Task number not found, reenter with correct task number:"; + new MissingCommand(reply).execute(tasks, ui, storage); + } catch (NumberFormatException e) { + String reply = " No task number detected, reenter with correct task number:"; + new MissingCommand(reply).execute(tasks, ui, storage); + } + } +} diff --git a/src/main/java/Volkov.java b/src/main/java/Volkov.java new file mode 100644 index 000000000..39821a409 --- /dev/null +++ b/src/main/java/Volkov.java @@ -0,0 +1,33 @@ +/** + * The main code for the Volkov application that handles the overall workflow. + */ +public class Volkov { + private final Storage storage; + private final TaskList tasks; + private final Ui ui; + + public Volkov(String filePath) { + ui = new Ui(); + storage = new Storage("./data/volkov.txt", ui); + this.tasks = storage.loadFile(); + } + + /** + * Main application loop for processing user commands. This keeps + * running until the user exits the program. + */ + public void run() { + boolean isExit = false; + while (!isExit) { + String fullCommand = ui.readCommand(); + Commands c = Parser.parse(fullCommand); + c.execute(tasks, ui, storage); + isExit = c.isExit(); + } + storage.saveFile(tasks); + } + + public static void main(String[] args) { + new Volkov("data/tasks.txt").run(); + } +}