diff --git a/docs/README.md b/docs/README.md index 8077118eb..d758eacf2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,28 +2,137 @@ ## Features -### Feature-ABC +### Feature-Adding tasks -Description of the feature. +Duke can store your tasks to complete. -### Feature-XYZ +### Feature-Different types of tasks -Description of the feature. +Duke allows three different types of tasks: todo, deadline and event. + +### Feature-Mark a task as done/ undone + +Duke can change the completion status of a task. + +### Feature-Viewing tasks + +All the tasks and the corresponding type and completion status can be seen. + +### Feature-Viewing tasks + +Tasks can be deleted. + +### Feature-Storing information + +Duke can store your tasks locally when the programme is exited and loads the stored data whenever the programmed is run. ## Usage -### `Keyword` - Describe action +### command and details + +To use Duke, you need to tell it the command keyword and the details of the command. + +The format can be seen in the example below with the corresponding output. + +#### **Adding a task** + +Input the type of the task (todo, deadline, event) and follow the format given below: + +``` + todo study + + deadline submit assignments /by fri + + event lecture /from 4pm fri /to 6pm fri +``` + +**_Note: the "/" for deadline and event is very important for Duke to recognise the time/ duration!_** + +#### **Listing all the tasks** + +Simply enter list to see your tasks: + +``` +list +______________________________ +1. [T][ ]study +2. [D][ ]submit assignments (by: fri) +3. [E][ ]lecture (from: 4pm fri to: 6pm fri) +______________________________ +``` + +#### **Marking a task as done/ undone** -Describe the action and its outcome. +``` +mark 1 +``` +``` +______________________________ +Nice! I've marked this task as done: +[T][X]study +______________________________ +``` +``` +mark 2 +``` +``` +______________________________ +Nice! I've marked this task as done: +[D][X]submit assignments (by: fri) +______________________________ +``` + +``` +list +______________________________ +1. [T][X]study +2. [D][X]submit assignments (by: fri) +3. [E][ ]lecture (from: 4pm fri to: 6pm fri) +______________________________ +``` -Example of usage: +#### **Finding a task** -`keyword (optional arguments)` +``` +find study +______________________________ +Here are the matching tasks in your list: +[T][X]study +______________________________ +``` +``` +find submit +______________________________ +Here are the matching tasks in your list: +[D][X]submit assignments (by: fri) +______________________________ +``` -Expected outcome: +#### **Deleting a task** -Description of the outcome. +``` +list +______________________________ +1. [T][X]study +2. [D][X]submit assignments (by: fri) +3. [E][ ]lecture (from: 4pm fri to: 6pm fri) +______________________________ +``` +``` +delete 2 +______________________________ +deleted: +[D][X]submit assignments (by: fri) +______________________________ +Now you have 2 tasks in the list +``` ``` -expected output +list +______________________________ +1. [T][X]study +2. [E][ ]lecture (from: 4pm fri to: 6pm fri) +______________________________ ``` + + 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/Deadline.java b/src/main/java/duke/Deadline.java new file mode 100644 index 000000000..5feb3f434 --- /dev/null +++ b/src/main/java/duke/Deadline.java @@ -0,0 +1,38 @@ +package duke; + +/** + * A subclass Deadline of Task + * Represents a Deadline added by the user and contains the deadline info "by" + */ +public class Deadline extends Task{ + protected String by; + + /** + * Constructor to construct a Deadline + * @param description the details of the task + * @param by the time of the deadline + */ + public Deadline(String description, String by) { + super(description); + this.by = by; + } + + /** + * gets the type of the task which is deadline + * @return a string representing the type ([D]) + */ + @Override + public String getTypeIcon() { + return "[D]"; + } + + /** + * Returns all the information about the task + * + * @return a String with all the information about the task + */ + @Override + public String toString(){ + return getTypeIcon() + super.getStatusIcon() +super.description + " (by: " + this.by + ")"; + } +} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java new file mode 100644 index 000000000..7b8248161 --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,37 @@ +package duke; + + +import java.util.Scanner; +import java.util.ArrayList; + +public class Duke { + + public static boolean shouldContinue = true; + public static int taskCount = 0; + + public static void main(String[] args) { + Ui.greetUser(); + ArrayList tasks = new ArrayList<>(); + TaskData file = new TaskData("./duke.txt"); + tasks = file.loadData(tasks, file); + taskCount = tasks.size(); + Scanner in = new Scanner(System.in); + String action; + while (shouldContinue) { + action = in.nextLine(); + try { + tasks = Parser.handleAction(tasks, action, file); + } catch (DukeException e) { + Ui.printWrongCommand(); + } catch (DukeException.TaskEmpty e) { + Ui.printEmptyContent(); + } catch (StringIndexOutOfBoundsException e) { + Ui.printWrongFormat(); + } catch (NumberFormatException e) { + Ui.printWrongNumber(); + } catch (IndexOutOfBoundsException e) { + Ui.printWrongNumber(); + } + } + } +} diff --git a/src/main/java/duke/DukeException.java b/src/main/java/duke/DukeException.java new file mode 100644 index 000000000..920941def --- /dev/null +++ b/src/main/java/duke/DukeException.java @@ -0,0 +1,10 @@ +package duke; + +/** + * DukeException is for command word not recognised by Duke + * TaskEmpty is when the content of the command word recognised is empty + */ +public class DukeException extends Exception{ + public static class TaskEmpty extends Exception{} + +} diff --git a/src/main/java/duke/Event.java b/src/main/java/duke/Event.java new file mode 100644 index 000000000..d68abfcdd --- /dev/null +++ b/src/main/java/duke/Event.java @@ -0,0 +1,42 @@ +package duke; + +/** + * A subclass Event of Task + * Represents an event for the user containing info for the time period of the event + */ +public class Event extends Task{ + protected String from; + protected String to; + + /** + * Constructor to construct an event + * @param description details of the event + * @param from starting time + * @param to ending time + */ + public Event(String description, String from, String to){ + super(description); + this.from = from; + this.to = to; + } + + /** + * gets the type of the task which is deadline + * @return a string representing the type ([E]) + */ + @Override + public String getTypeIcon() { + return "[E]"; + } + + /** + * Returns all the information about the task + * + * @return a String with all the information about the task + */ + @Override + public String toString(){ + return getTypeIcon() + super.getStatusIcon() + super.description + + " (from: " + this.from + " to: " + this.to + ")"; + } +} diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java new file mode 100644 index 000000000..22f02a2ef --- /dev/null +++ b/src/main/java/duke/Parser.java @@ -0,0 +1,104 @@ +package duke; + +import java.util.ArrayList; + +/** + * Deals with making sense of user command + */ +public class Parser { + protected static int TODO_OFFSET = 5; + protected static int DEADLINE_OFFSET = 9; + protected static int BY_OFFSET = 4; + protected static int EVENT_OFFSET = 6; + protected static int FROM_OFFSET = 6; + protected static int TO_OFFSET = 4; + protected static int FIND_OFFSET = 5; + + /** + * Handles command(action) input by the user + * Returns updated tasks + * + * @param tasks all the tasks added + * @param action the input line by the user + * @param file the txt file storing the tasks + * @return tasks + * @throws DukeException when the command word is not recognised + * @throws DukeException.TaskEmpty when the content of task to be added is empty + */ + public static ArrayList handleAction(ArrayList tasks, String action, TaskData file + ) throws DukeException, DukeException.TaskEmpty{ + if (action.equals("bye")) { + Ui.exitProgram(); + Duke.shouldContinue = false; + + } else if (action.equals("list")) { + Ui.listTasks(tasks, Duke.taskCount); + + } else if (action.equals("todo") || action.equals("deadline") || action.equals("event")) { + throw new DukeException.TaskEmpty(); + + } else if (action.startsWith("mark")){ + int dividerPos = action.indexOf(" "); + int toBeMarked = Integer.parseInt(action.substring(dividerPos + 1)) - 1; + tasks.get(toBeMarked).markAsDone(); + Ui.printMarked(tasks.get(toBeMarked), action.split(" ")[0]); + file.updateFile(tasks, file); + + } else if (action.startsWith("unmark")) { + int dividerPos = action.indexOf(" "); + int toBeUnmarked = Integer.parseInt(action.substring(dividerPos + 1)) - 1; + tasks.get(toBeUnmarked).markAsUndone(); + Ui.printMarked(tasks.get(toBeUnmarked), action.split(" ")[0]); + file.updateFile(tasks, file); + + } else if (action.startsWith("delete")) { + int dividerPos = action.indexOf(" "); + int toBeDeleted = Integer.parseInt(action.substring(dividerPos + 1)) - 1; + Task removedTask = tasks.get(toBeDeleted); + tasks.remove(toBeDeleted); + Duke.taskCount -= 1; + Ui.printDeleted(removedTask); + Ui.printNumTask(Duke.taskCount); + file.updateFile(tasks, file); + + } else if (action.startsWith("todo")){ + Task tempTask = new Task(action.substring(TODO_OFFSET)); + tasks.add(tempTask); + Ui.printAdded(tasks, Duke.taskCount); + Duke.taskCount += 1; + Ui.printNumTask(Duke.taskCount); + tasks = file.addTaskToFile(tasks, tempTask, file); + + } else if (action.startsWith("deadline")) { + int dividerPosition = action.indexOf("/by"); + Task tempTask = new Deadline(action.substring(DEADLINE_OFFSET,dividerPosition - 1), + action.substring(dividerPosition + BY_OFFSET)); + tasks.add(tempTask); + Ui.printAdded(tasks, Duke.taskCount); + Duke.taskCount += 1; + Ui.printNumTask(Duke.taskCount); + tasks = file.addTaskToFile(tasks, tempTask, file); + + } else if (action.startsWith("event")) { + int dividerPosition1 = action.indexOf("/from"); + int dividerPosition2 = action.indexOf("/to"); + //extract the event details + Task tempTask = new Event(action.substring(EVENT_OFFSET,dividerPosition1 - 1), + action.substring(dividerPosition1 + FROM_OFFSET, dividerPosition2 - 1), + action.substring(dividerPosition2 + TO_OFFSET)); + tasks.add(tempTask); + Ui.printAdded(tasks, Duke.taskCount); + Duke.taskCount += 1; + Ui.printNumTask(Duke.taskCount); + tasks = file.addTaskToFile(tasks, tempTask, file); + + } else if (action.startsWith("find")) { + String taskToFind = action.substring(FIND_OFFSET); + Ui.printFound(tasks, taskToFind); + + } else { + throw new DukeException(); + } + return tasks; + } +} diff --git a/src/main/java/duke/Task.java b/src/main/java/duke/Task.java new file mode 100644 index 000000000..927b8f7c6 --- /dev/null +++ b/src/main/java/duke/Task.java @@ -0,0 +1,60 @@ +package duke; + +/** + * Represents a task to be tracked by the user. + * a Task is a "todo" by default + */ +public class Task { + protected String description; + protected boolean isDone; + + /** + * Constructor to construct a Task + * Mark the task as undone by default + * @param description the details of the todo + */ + public Task(String description) { + this.description = description; + this.isDone = false; + } + + /** + * get the completion status of the task (isDone) + * @return [X] if the task is done and [ ] otherwise + */ + public String getStatusIcon() { + return (isDone ? "[X]" : "[ ]"); // mark done task with X + } + + /** + * gets the icon of this task type + * @return [T] for task todo + */ + public String getTypeIcon() { + return "[T]"; + } + + /** + * mark the task as done + */ + public void markAsDone() { + this.isDone = true; + } + + /** + * mark the task as undone + */ + public void markAsUndone() { + this.isDone = false; + } + + /** + * Returns all the information about the task + * + * @return a String with all the information about the task + */ + public String toString() { + return getTypeIcon() + getStatusIcon() + description; + } + +} diff --git a/src/main/java/duke/TaskData.java b/src/main/java/duke/TaskData.java new file mode 100644 index 000000000..936202025 --- /dev/null +++ b/src/main/java/duke/TaskData.java @@ -0,0 +1,159 @@ +package duke; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Represents a file storing data for duke. + * Manages the file + */ +public class TaskData { + private File data; + private String fileName; + + public TaskData (String fileName) { + this.fileName = fileName; + data = new File(fileName); + } + + /** + * Reads data from a file and stores them inside ArrayList tasks + * + * @param tasks the ArrayList storing all the tasks + * @return tasks + * @throws FileNotFoundException when the file cannot be found + */ + public ArrayList readData (ArrayList tasks) throws FileNotFoundException { + if (!data.exists()) { + return tasks; + } + Scanner s = new Scanner(data); + String task; + String taskDescription; + while (s.hasNext()) { + task = s.nextLine(); + if (task.charAt(1) == 'T') { + taskDescription = task.substring(6); + Task tempTask = new Task(taskDescription); + if (task.charAt(4) == 'X') { + tempTask.markAsDone(); + } else { + tempTask.markAsUndone(); + } + tasks.add(tempTask); + } else if (task.charAt(1) == 'D') { + //extracts info + taskDescription = task.substring(6, task.indexOf("(by") - 1); + String by = task.substring(task.indexOf("(by") + 5, task.length() - 1); + Task tempTask = new Deadline(taskDescription, by); + if (task.charAt(4) == 'X') { + tempTask.markAsDone(); + } else { + tempTask.markAsUndone(); + } + tasks.add(tempTask); + } else { + //extracts info + taskDescription = task.substring(6, task.indexOf("(from") - 1); + String from = task.substring(task.indexOf("(from") + 7, task.indexOf("to") -1); + String to = task.substring(task.indexOf("to") + 4, task.length() - 1); + Task tempTask = new Event(taskDescription, from, to); + if (task.charAt(4) == 'X') { + tempTask.markAsDone(); + } else { + tempTask.markAsUndone(); + } + tasks.add(tempTask); + } + } + return tasks; + } + + /** + * Appends data of a task to the file + * @param tasks storing all the tasks + * @param task the task to be appended + * @return tasks + * @throws IOException when the file writer fails + */ + public ArrayList writeToFile (ArrayList tasks,String task) throws IOException { + FileWriter fw = new FileWriter(fileName, true); + fw.write(task + System.lineSeparator()); + fw.close(); + return tasks; + } + + /** + * updates the file entirely based on the current tasks + * + * @param tasks the ArrayList storing all the tasks + * @return tasks + * @throws IOException when file writer fails + */ + public ArrayList updateFile (ArrayList tasks) throws IOException { + FileWriter fw = new FileWriter(fileName); + fw.write(""); + fw.close(); + FileWriter fwAppend = new FileWriter(fileName, true); + for (Task t : tasks) { + fwAppend.write(t.toString() + System.lineSeparator()); + } + fwAppend.close(); + return tasks; + } + + /** + * Returns updated tasks from file "/duke.txt" + * tasks is unchanged if there is no such file already existing and creates a file + * + * @param tasks all the tasks added + * @param file the file storing the tasks + * @return tasks + */ + public static ArrayList loadData(ArrayList tasks, TaskData file) { + try { + file.readData(tasks); + } catch (FileNotFoundException e) { + System.out.println("file not found!"); + } + return tasks; + } + + /** + * Appends a task to the file and returns the updated tasks + * + * @param tasks all the tasks added + * @param taskToAdd the task to append to the text file + * @param file the txt file storing the tasks + * @return tasks + */ + public static ArrayList addTaskToFile(ArrayList tasks, Task taskToAdd, TaskData file) { + try { + tasks = file.writeToFile(tasks, taskToAdd.toString()); + } catch (IOException e) { + System.out.println("IOException!"); + } + return tasks; + } + + /** + * updates the file entirely to the current tasks + * + * @param tasks all the tasks added + * @param file the txt file storing the tasks + * @return tasks + */ + public static ArrayList updateFile(ArrayList tasks, TaskData file) { + try { + tasks = file.updateFile(tasks); + } catch (IOException e) { + System.out.println("IOException!"); + } + return tasks; + } + +} diff --git a/src/main/java/duke/Ui.java b/src/main/java/duke/Ui.java new file mode 100644 index 000000000..de0db5e68 --- /dev/null +++ b/src/main/java/duke/Ui.java @@ -0,0 +1,125 @@ +package duke; + +import java.util.ArrayList; + +/** + * Deals with interactions with the user + */ +public class Ui { + private static String DIVIDER_LINE = "______________________________\n"; + + /** + * Prints a message to greet the user + */ + public static void greetUser() { + String logo = " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n"; + System.out.println("Hello from\n" + logo); + String greet = DIVIDER_LINE + + "Hello! I'm Duke\n" + + "What can i do for you\n" + + DIVIDER_LINE; + System.out.println(greet); + } + + /** + * Prints a message to indicate exiting the programme + */ + public static void exitProgram() { + System.out.println(DIVIDER_LINE + "Bye. Hope to see you again soon!\n" + DIVIDER_LINE); + } + + /** + * List all the tasks stored + * @param tasks the Arraylist storing the tasks + * @param taskCount total number of tasks + */ + public static void listTasks(ArrayList tasks, int taskCount) { + System.out.print(DIVIDER_LINE); + for (int i = 0; i < taskCount; i += 1) { + System.out.println(Integer.toString(i + 1) + ". " +tasks.get(i).toString()); + } + System.out.println(DIVIDER_LINE); + } + + /** + * prints the results of finding the keyword + * @param tasks the Arraylist storing the tasks + * @param taskToFind the keyword to be found + */ + public static void printFound(ArrayList tasks, String taskToFind) { + System.out.println(DIVIDER_LINE + "Here are the matching tasks in your list:"); + for (Task t : tasks) { + if (t.description.contains(taskToFind)) { + System.out.println(t.toString()); + } + } + System.out.println(DIVIDER_LINE); + } + + /** + * prints the task just marked as done or undone + * @param tasks the Arraylist storing the tasks + * @param action the action to indicate mark or unmark + */ + public static void printMarked(Task tasks, String action) { + System.out.println(DIVIDER_LINE + "Nice! I've marked this task as "+ (action.equals("mark") ? "done:" : "undone:") + + "\n" + tasks.toString() + "\n" + DIVIDER_LINE); + } + + /** + * prints the added task details + * @param tasks the Arraylist storing the tasks + * @param taskCount the total number of tasks + */ + public static void printAdded(ArrayList tasks, int taskCount) { + System.out.println(DIVIDER_LINE + "added:\n" + tasks.get(taskCount).toString() + "\n" + DIVIDER_LINE); + } + + /** + * prints the task just deleted + * @param deletedTask the task that just got deleted + */ + public static void printDeleted(Task deletedTask) { + System.out.println(DIVIDER_LINE + "deleted:\n" + deletedTask.toString() + "\n" + DIVIDER_LINE); + } + + /** + * prints the total number of tasks + * @param taskCount total number of tasks + */ + public static void printNumTask(int taskCount) { + System.out.println("Now you have " + taskCount + " tasks in the list"); + } + + /** + * Print a message to indicate a wrong command word + */ + public static void printWrongCommand() { + System.out.println(DIVIDER_LINE + "Sorry! I don't know what that means.\n" + DIVIDER_LINE); + } + + /** + * prints a message to indicate empty content of a command + */ + public static void printEmptyContent() { + System.out.println(DIVIDER_LINE + "The content of the task cannot be empty!\n" + DIVIDER_LINE); + } + + /** + * prints a message to indicate wrong format for the command + */ + public static void printWrongFormat() { + System.out.println(DIVIDER_LINE + "Wrong format! Please try again\n" + DIVIDER_LINE); + } + + /** + * prints a message to indicate an invalid task number entered by the user + */ + public static void printWrongNumber() { + System.out.println(DIVIDER_LINE + "Please try again with a valid task number\n" + DIVIDER_LINE); + } +}