diff --git a/README.md b/README.md index 8715d4d91..0f2208ab6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# duke.Duke 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 11, update Intellij to the most recent version. 1. If there are any further prompts, accept the defaults. 1. Configure the project to use **JDK 11** (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. -3. 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: +3. After that, locate the `src/main/java/duke.Duke.java` file, right-click it, and choose `Run duke.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: ``` Hello from ____ _ diff --git a/data/duke.txt b/data/duke.txt new file mode 100644 index 000000000..e69de29bb diff --git a/docs/README.md b/docs/README.md index 8077118eb..d08486df6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,166 @@ -# User Guide +# User Guide for Duke -## Features +## Features of Duke +1. [Add a Task into the List](#add-a-task-into-the-list) +2. [Removing a Task from the List](#removing-a-task-from-the-list) +3. [Changing the Status of a Task](#changing-the-status-of-a-task) +4. [View all Tasks in the List](#view-all-tasks-in-the-list) +5. [Finding a Task in the List](#finding-a-task-in-the-list) +6. [Exiting Duke](#exiting-duke) -### Feature-ABC +### Add a Task into the List +1. ``todo`` +- this command allows users to add a task that does not specify a time or date -Description of the feature. +#### Format of Input -### Feature-XYZ +``` +todo buy bread +``` + +#### Expected Output: +``` +Got it. I've added this task: +[T][ ] buy bread +You currently have 1 tasks in your list. +``` + +2. ``deadline`` +- This command allows users to add a task that needs to be done before a specific date or time + +#### Format of Input + +``` +deadline finish tuna sandwich /by monday +``` + +#### Expected Output: +``` +Got it. I've added this task: +[D][ ] finish tuna sandwich (by: monday) +You currently have 1 tasks in your list. +``` + +3. ``event`` +- This command allows users to add a task that starts and ends at a specific date or time + +#### Format of Input + +``` +event family dinner at joyden /from 6pm /to 9pm +``` + +#### Expected Output: +``` +Got it. I've added this task: +[E][ ] family dinner at joyden (from: 6pm to: 9pm) +You currently have 1 tasks in your list. +``` + +### Removing a Task from the List +``delete`` +- This command allows users to delete a task from the list +- The number after the command is the index of the task in the list + +#### Format of Input + +``` +delete 3 +``` + +#### Expected Output: +``` +Noted. I've removed this task: +[T][ ] sell fridge +You currently have 2 tasks in your list. +``` -Description of the feature. +### Changing the Status of a Task +1. ``mark`` +- This command allows users to mark a task as done +- The number after the command is the index of the task in the list +- The task on the list will be updated from `[ ]` to `[X]` -## Usage +#### Format of Input -### `Keyword` - Describe action +``` +mark 1 +``` -Describe the action and its outcome. +#### Expected Output: +``` +Nice! I've marked this task as done: +Here are the tasks in your list: +1. [T][X] pump petrol +``` -Example of usage: +2. ``unmark`` +- This command allows users to mark a task as not done +- The number after the command is the index of the task in the list +- The task on the list will be updated from `[X]` to `[ ]` -`keyword (optional arguments)` +#### Format of Input -Expected outcome: +``` +unmark 1 +``` -Description of the outcome. +#### Expected Output: +``` +OK, I've marked this task as not done yet: +Here are the tasks in your list: +1. [T][ ] pump petrol +``` + +### View all Tasks in the List +``list`` +- This command allows users to see all tasks in their list + - Task of type `todo` will be displayed: `[T][ ] TASK_NAME` + - Task of type `deadline` will be displayed: `[D][ ] TASK_NAME (by: DATE)` + - Task of type `event` will be displayed: `[E][ ] TASK_NAME (from: START_DATE to: END_DATE)` + +#### Format of Input + +``` +list +``` + +#### Expected Output: +``` +Here are the tasks in your list: +1. [T][ ] paint a portrait of a horse +2. [D][ ] buy christmas presents (by: christmas) +3. [T][X] decorate christmas tree +``` + +### Finding a Task in the List +``find`` +- This command allows users to see all tasks that contains the keyword in their list + +#### Format of Input + +``` +find beef +``` + +#### Expected Output: +``` +Here are the matching tasks in your list: +1. [D][ ] cook beef shank (by: friday) +2. [E][ ] beef noodle party (from: 2am to: 5am) +``` + +### Exiting Duke +``bye`` +- This command allows users to exit the programme + +#### Format of Input + +``` +bye +``` +#### Expected Output: ``` -expected output +Bye! Hope to see you again soon! ``` 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..2c9a9745c --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: duke.Duke + diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java new file mode 100644 index 000000000..2af9c44ba --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,36 @@ +package duke; + +import duke.tasks.Task; +import duke.exceptions.EmptyDescriptionException; +import duke.exceptions.TaskToMarkDoesNotExistException; +import duke.exceptions.UnknownCommandException; + +import java.io.IOException; +import java.util.ArrayList; + + +/** + * Main class of Duke that contains code for Duke to function + */ +public class Duke { + + public static void main(String[] args) { + Ui.printGreet(); + ArrayList tasks = TaskList.tasks; + try { + tasks = Storage.getData(); + } catch (IOException e) { + System.out.println("Error obtaining data from file"); + } + try { + TaskList.editList(); + } catch (UnknownCommandException e) { + e.printErrorMessage(); + } catch (EmptyDescriptionException e) { + e.printErrorMessage(); + } catch (TaskToMarkDoesNotExistException e) { + e.printErrorMessage(); + Ui.printNumberOfTasks(tasks); + } + } +} \ No newline at end of file diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java new file mode 100644 index 000000000..1e2f00042 --- /dev/null +++ b/src/main/java/duke/Parser.java @@ -0,0 +1,20 @@ +package duke; + +import java.util.Scanner; + +/** + * Deals with loading tasks from the file and saving tasks in the file + */ +public class Parser { + + /** + * Takes in the user input and split the sentence into 2 parts by the " " + * + * @param input line that user has inputted + * @return an array of Strings that contains the command at index 0 and the description of the task at index 1 + */ + public static String[] parse(Scanner input) { + String text = input.nextLine(); // input the whole sentence into text + return text.split(" ", 2); + } +} diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java new file mode 100644 index 000000000..b36465b0d --- /dev/null +++ b/src/main/java/duke/Storage.java @@ -0,0 +1,146 @@ +package duke; + +import duke.tasks.Deadline; +import duke.tasks.Event; +import duke.tasks.Task; +import duke.tasks.Todo; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Deals with loading tasks from the file and saving tasks in the file + */ +public class Storage { + private static final String filepath = "./data/duke.txt"; + + /** + * Identify the type of task in the file, split the line into the + * correct variables of description, status and start, end date and return + * the Task with the required information + * + * @param description name of task + * @return taskToAdd the Task identified to be put into the ArrayList of Tasks + */ + public static Task addTask(String description) { + String[] taskDescription; + taskDescription = description.split("|", 3); + Task taskToAdd; + switch (taskDescription[0]) { + case "T": + taskToAdd = new Todo(taskDescription[2]); + if (taskDescription[1].equals("1")) { + taskToAdd.setDone(); + } else { + taskToAdd.setUndone(); + } + break; + case "D": + String[] deadlineDescription = taskDescription[2].split("|", 2); + taskToAdd = new Deadline(deadlineDescription[0], deadlineDescription[1]); + if (taskDescription[1].equals("1")) { + taskToAdd.setDone(); + } else { + taskToAdd.setUndone(); + } + break; + case "E": + String[] eventDescription = taskDescription[2].split("|", 2); + String dates[] = eventDescription[1].split("-"); + String start = dates[0]; + String end = dates[1]; + taskToAdd = new Event(eventDescription[0], start, end); + if (taskDescription[1].equals("1")) { + taskToAdd.setDone(); + } else { + taskToAdd.setUndone(); + } + break; + default: + taskToAdd = null; + break; + } + return taskToAdd; + } + + /** + * Read a file from the filepath and creates a new file if a current file does not exist + * + * @return an array of Tasks that stores the tasks in the file + * @throws IOException when file does not exist + */ + public static ArrayList getData() throws IOException { + ArrayList currentList = new ArrayList<>(); + File f = new File(filepath); + if (!f.getParentFile().exists()) { + f.getParentFile().mkdir(); + } + if (f.exists()) { + Scanner s = new Scanner(f); + while (s.hasNext()) { + String description = s.nextLine(); + currentList.add(addTask(description)); + } + } + return currentList; + } + + /** + * Identifies whether the task is done or undone + * If the task is done, return "1" but if the task is not done, return "0" + * + * @param task the task that we are currently interested in + * @return a string of either a "0" or "1" + */ + public static String getNumberIcon(Task task) { + if (task.getStatusIcon().equals("X")) { + return "1"; + } + return "0"; + } + + /** + * Convert the information of the task into the correct format that will be stored in the file + * The format tells us the type of task, the status of it and the name of the task, split with "|" + * + * @param task + * @return a String of the task in the correct format + * @throws NullPointerException + */ + public static String formatTask(Task task) throws NullPointerException { + String text = null; + if (task.getType().equals("todo")) { + text = "T" + " | " + getNumberIcon(task) + " | " + task.getDescription(); + } + if (task.getType().equals("deadline")) { + text = "D" + " | " + getNumberIcon(task) + " | " + task.getDescription() + " | " + task.getEnd(); + } + if (task.getType().equals("event")) { + text = "E" + " | " + getNumberIcon(task) + " | " + task.getDescription() + " | " + task.getStart() + "-" + task.getEnd(); + } + return text; + } + + /** + * Write to the file specified by the filepath when there are changes made to the list of tasks + * + * @param tasks an array of tasks that will be added to the file + * @throws IOException when file does not exist + * @throws NullPointerException + */ + public static void updateFile(ArrayList tasks) throws IOException, NullPointerException { + FileWriter fw = null; + try { + fw = new FileWriter(filepath); + for (Task i : tasks) { + String textToAdd = formatTask(i); + fw.write(textToAdd); + } + } finally { + fw.close(); + } + } +} diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java new file mode 100644 index 000000000..7cc66dfd0 --- /dev/null +++ b/src/main/java/duke/TaskList.java @@ -0,0 +1,179 @@ +package duke; + +import duke.exceptions.EmptyDescriptionException; +import duke.exceptions.TaskToMarkDoesNotExistException; +import duke.exceptions.UnknownCommandException; +import duke.tasks.Deadline; +import duke.tasks.Event; +import duke.tasks.Task; +import duke.tasks.Todo; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * Contains the task list + */ +public class TaskList { + protected static ArrayList tasks = new ArrayList<>(); + private static final String ADDED_COMMAND = "Got it. I've added this task:"; + private static final String REMOVED_COMMAND = "Noted. I've removed this task:"; + + /** + * Prints out all the tasks in the list in the correct format + * Prints out "You currently don't have any tasks in your list" when list is empty + */ + public static void displayList() { + if (tasks.size() > 0) { + System.out.println("Here are the tasks in your list:"); + for (int i = 0; i < tasks.size(); i += 1) { + System.out.print(i + 1 + ". "); + tasks.get(i).printTask(); + } + } else { + System.out.println("You currently don't have any tasks in your list"); + } + } + + private static void markTask(String task) { + tasks.get(Integer.parseInt(task) - 1).setDone(); + System.out.println("Nice! I've marked this task as done:"); + TaskList.displayList(); + } + + private static void unmarkTask(String task) { + tasks.get(Integer.parseInt(task) - 1).setUndone(); + System.out.println("OK, I've marked this task as not done yet:"); + TaskList.displayList(); + } + + private static void createTodo(String task) { + Task todoToAdd = new Todo(task); + tasks.add(todoToAdd); + System.out.println(ADDED_COMMAND); + tasks.get(tasks.size() - 1).printTask(); + Ui.printNumberOfTasks(tasks); + } + + private static void createEvent(String task) { + String[] input = task.split("/from"); + String description = input[0]; + String[] period = input[1].split("/to"); + String start = period[0]; + String end = period[1]; + Task eventToAdd = new Event(description, start, end); + tasks.add(eventToAdd); + System.out.println(ADDED_COMMAND); + tasks.get(tasks.size() - 1).printTask(); + Ui.printNumberOfTasks(tasks); + } + + private static void createDeadline(String task) { + String[] words = task.split("/by"); + String description = words[0]; + String end = words[1]; + Task deadlineToAdd = new Deadline(description, end); + tasks.add(deadlineToAdd); + System.out.println(ADDED_COMMAND); + tasks.get(tasks.size() - 1).printTask(); + Ui.printNumberOfTasks(tasks); + } + + private static void deleteTask(String task) { + Task taskToDelete = tasks.get(Integer.parseInt(task) - 1); + tasks.remove(Integer.parseInt(task) - 1); + System.out.println(REMOVED_COMMAND); + taskToDelete.printTask(); + Ui.printNumberOfTasks(tasks); + } + + private static void findTask(String task) { + System.out.println("Here are the matching tasks in your list:"); + int matchingTasksCount = 0; + for (Task i : tasks) { + if (i.getDescription().contains(task)) { + matchingTasksCount++; + System.out.print(matchingTasksCount + ". "); + i.printTask(); + } + } + } + + /** + * Identify the command user has inputted and edit the list of tasks accordingly + * + * @throws UnknownCommandException when user inputs an invalid command + * @throws EmptyDescriptionException when user does not include the description of the task + * @throws TaskToMarkDoesNotExistException when user enters a task that does not exist + */ + public static void editList() throws UnknownCommandException, EmptyDescriptionException, TaskToMarkDoesNotExistException { + String[] splitText = Ui.getInput(); + while (!splitText[0].equals("bye")) { + switch (splitText[0]) { + case "mark": + try { + TaskList.markTask(splitText[1]); + } catch (ArrayIndexOutOfBoundsException e) { + throw new EmptyDescriptionException("marked"); + } catch (NullPointerException e) { + throw new TaskToMarkDoesNotExistException("mark"); + } + break; + case "unmark": + try { + TaskList.unmarkTask(splitText[1]); + } catch (ArrayIndexOutOfBoundsException e) { + throw new EmptyDescriptionException("unmarked"); + } catch (NullPointerException e) { + throw new TaskToMarkDoesNotExistException("unmark"); + } + break; + case "list": + TaskList.displayList(); + break; + case "todo": + try { + TaskList.createTodo(splitText[1]); + } catch (ArrayIndexOutOfBoundsException e) { + throw new EmptyDescriptionException("todo"); + } + break; + case "deadline": + try { + TaskList.createDeadline(splitText[1]); + } catch (ArrayIndexOutOfBoundsException e) { + throw new EmptyDescriptionException("deadline"); + } + break; + case "event": + try { + TaskList.createEvent(splitText[1]); + } catch (ArrayIndexOutOfBoundsException e) { + throw new EmptyDescriptionException("event"); + } + break; + case "delete": + try { + TaskList.deleteTask(splitText[1]); + } catch (ArrayIndexOutOfBoundsException e) { + throw new EmptyDescriptionException("deleted"); + } catch (IndexOutOfBoundsException e) { + throw new TaskToMarkDoesNotExistException("delete"); + } + break; + case "find": + TaskList.findTask(splitText[1]); + break; + default: + throw new UnknownCommandException(); + } + splitText = Ui.getInput(); + } + try { + Storage.updateFile(tasks); + } catch (IOException e){ + System.out.println("Error updating data in file"); + } + Ui.printBye(); + } +} diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java new file mode 100644 index 000000000..2bb7e83ea --- /dev/null +++ b/src/main/java/duke/Ui.java @@ -0,0 +1,34 @@ +package duke; + +import duke.tasks.Task; + +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Deals with interactions with the user + */ +public class Ui { + public static void printGreet() { + System.out.println("Hello! I'm duke."); + System.out.println("What can I do for you?"); + } + + public static void printBye() { + System.out.println("Bye! Hope to see you again soon!"); + } + + public static String[] getInput() { + Scanner input = new Scanner(System.in); + return Parser.parse(input); + } + + /** + * Print the total number of tasks that are currently in the list + * + * @param tasks the array of tasks + */ + public static void printNumberOfTasks(ArrayList tasks) { + System.out.println("You currently have " + tasks.size() + " tasks in your list."); + } +} diff --git a/src/main/java/duke/exceptions/EmptyDescriptionException.java b/src/main/java/duke/exceptions/EmptyDescriptionException.java new file mode 100644 index 000000000..8fe28f92b --- /dev/null +++ b/src/main/java/duke/exceptions/EmptyDescriptionException.java @@ -0,0 +1,16 @@ +package duke.exceptions; + +public class EmptyDescriptionException extends Exception { + protected String typeOfTask; + + public EmptyDescriptionException (String typeOfTask) { + this.typeOfTask = typeOfTask; + } + public void printErrorMessage() { + if (typeOfTask.equals("marked") || typeOfTask.equals("unmarked") || typeOfTask.equals("deleted")) { + System.out.println("OOPS!! Task to be " + typeOfTask + " was not specified!"); + } else { + System.out.println("OOPS!! The description of " + typeOfTask + " cannot be empty!"); + } + } +} diff --git a/src/main/java/duke/exceptions/TaskToMarkDoesNotExistException.java b/src/main/java/duke/exceptions/TaskToMarkDoesNotExistException.java new file mode 100644 index 000000000..3bf9566e2 --- /dev/null +++ b/src/main/java/duke/exceptions/TaskToMarkDoesNotExistException.java @@ -0,0 +1,15 @@ +package duke.exceptions; + +import duke.tasks.Task; + +public class TaskToMarkDoesNotExistException extends Exception { + protected String command; + + public TaskToMarkDoesNotExistException(String command) { + this.command = command; + } + + public void printErrorMessage() { + System.out.println("You can only " + command + " tasks that are currently in the list!"); + } +} diff --git a/src/main/java/duke/exceptions/UnknownCommandException.java b/src/main/java/duke/exceptions/UnknownCommandException.java new file mode 100644 index 000000000..2eb3ab5ab --- /dev/null +++ b/src/main/java/duke/exceptions/UnknownCommandException.java @@ -0,0 +1,7 @@ +package duke.exceptions; + +public class UnknownCommandException extends Exception { + public void printErrorMessage() { + System.out.println("OOPS!! I'm sorry but I don't know what that means :-("); + } +} diff --git a/src/main/java/duke/tasks/Deadline.java b/src/main/java/duke/tasks/Deadline.java new file mode 100644 index 000000000..40887c7b0 --- /dev/null +++ b/src/main/java/duke/tasks/Deadline.java @@ -0,0 +1,26 @@ +package duke.tasks; + +/** + * Represents tasks that are classified as a Deadline + */ +public class Deadline extends Task { + protected String type = "deadline"; + protected String end; + + /** + * Creates a new object of type Deadline + * + * @param description name of task + * @param end the date at which task has to be completed by + */ + public Deadline(String description, String end) + { + super(description); + this.end = end; + } + + @Override + public void printTask() { + System.out.println("[D][" + getStatusIcon() + "] " + description + "(by:" + end + ")"); + } +} diff --git a/src/main/java/duke/tasks/Event.java b/src/main/java/duke/tasks/Event.java new file mode 100644 index 000000000..0f44eb39c --- /dev/null +++ b/src/main/java/duke/tasks/Event.java @@ -0,0 +1,29 @@ +package duke.tasks; + +/** + * Represents tasks that are classified as a Event + * These are tasks that have a start and end date + */ +public class Event extends Task { + protected String start; + protected String end; + protected String type = "event"; + + /** + * Creates an object of type Event + * + * @param description name of task + * @param start event's start date + * @param end event's end date + */ + public Event(String description, String start, String end) { + super(description); + this.start = start; + this.end = end; + } + + @Override + public void printTask() { + System.out.println("[E][" + getStatusIcon() + "] " + description + "(from:" + start + "to:" + end + ")"); + } +} \ No newline at end of file diff --git a/src/main/java/duke/tasks/Task.java b/src/main/java/duke/tasks/Task.java new file mode 100644 index 000000000..571c7be40 --- /dev/null +++ b/src/main/java/duke/tasks/Task.java @@ -0,0 +1,59 @@ +package duke.tasks; + +/** + * Represents tasks set by user + * Keeps track of the task type, description, dates and status + */ +public class Task { + + protected String description; + protected boolean isDone; + protected String type; + protected String start; + protected String end; + + public String getDescription() { + return description; + } + + public String getType() { + return type; + } + + public String getStart() { + return start; + } + + public String getEnd() { + return end; + } + + public Task(String description) { + this.description = description; + this.isDone = false; + } + + public void setDone() { + this.isDone = true; + } + + public void setUndone() { + this.isDone = false; + } + + /** + * Checks whether the task is done and returns the correct icon to represent the status of the task + * @return 'X' if the task is done and ' ' if task is not done + */ + public String getStatusIcon() { + return (isDone ? "X" : " "); + } + + /** + * Prints the Task in the correct format required in the list + * The format is determined by the type of task + */ + public void printTask() { + System.out.println("task"); + } +} diff --git a/src/main/java/duke/tasks/Todo.java b/src/main/java/duke/tasks/Todo.java new file mode 100644 index 000000000..7dc744d93 --- /dev/null +++ b/src/main/java/duke/tasks/Todo.java @@ -0,0 +1,23 @@ +package duke.tasks; + +/** + * Represents tasks that are classified as a Todo + * These are tasks that does not have a start or end date + */ +public class Todo extends Task { + protected String type = "todo"; + + /** + * Creates an object of type Todo + * + * @param description name of task + */ + public Todo (String description) { + super(description); + } + + @Override + public void printTask() { + System.out.println("[T][" + getStatusIcon() + "] " + description); + } +} diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 087374464..62752b881 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 duke.Duke < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT