From 0d5d223569277598ed584709a5bb2ed85ecb4dc9 Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Thu, 24 Aug 2023 16:29:26 +0800 Subject: [PATCH 01/24] Update name and add greeting --- .gitignore | 3 +++ src/main/java/Duke.java | 10 ---------- src/main/java/Herbert.java | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 10 deletions(-) delete mode 100644 src/main/java/Duke.java create mode 100644 src/main/java/Herbert.java diff --git a/.gitignore b/.gitignore index 2873e189e..cdbb5cce2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +# Ignore .class files +*.class \ No newline at end of file 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/Herbert.java b/src/main/java/Herbert.java new file mode 100644 index 000000000..c03696d14 --- /dev/null +++ b/src/main/java/Herbert.java @@ -0,0 +1,32 @@ +import java.sql.SQLOutput; + +public class Herbert { + + public static void greeting() { + String logo = "( ) ( ) ( ) \n" + + " | | .-. .--. ___ .-. | |.-. .--. ___ .-. | |_ \n" + + " | |/ \\ / \\ ( ) \\ | / \\ / \\ ( ) \\ ( __) \n" + + " | .-. . | .-. ; | ' .-. ; | .-. | | .-. ; | ' .-. ; | | \n" + + " | | | | | | | | | / (___) | | | | | | | | | / (___) | | ___ \n" + + " | | | | | |/ | | | | | | | | |/ | | | | |( ) \n" + + " | | | | | ' _.' | | | | | | | ' _.' | | | | | | \n" + + " | | | | | .'.-. | | | ' | | | .'.-. | | | ' | | \n" + + " | | | | ' `-' / | | ' `-' ; ' `-' / | | ' `-' ; \n" + + "(___)(___) `.__.' (___) `.__. `.__.' (___) `.__. \n"; + + System.out.println("Hello from\n" + logo); + + System.out.println("___________________________________________________________________________"); + System.out.println(" Hello! I'm Herbert.\n What can I do for you?"); + System.out.println("___________________________________________________________________________"); + System.out.println(" Bye. Hope to see you again soon!"); + System.out.println("___________________________________________________________________________"); + + + } + + public static void main(String[] args) { + greeting(); + } +} + From 68f27438074ef7d42ed15b763fb7db2ebe42e7ef Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Tue, 29 Aug 2023 15:32:13 +0800 Subject: [PATCH 02/24] Add echo function and exit functionality --- src/main/java/Herbert.java | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/main/java/Herbert.java b/src/main/java/Herbert.java index c03696d14..c7d4c8715 100644 --- a/src/main/java/Herbert.java +++ b/src/main/java/Herbert.java @@ -1,4 +1,4 @@ -import java.sql.SQLOutput; +import java.util.Scanner; public class Herbert { @@ -14,19 +14,39 @@ public static void greeting() { + " | | | | ' `-' / | | ' `-' ; ' `-' / | | ' `-' ; \n" + "(___)(___) `.__.' (___) `.__. `.__.' (___) `.__. \n"; - System.out.println("Hello from\n" + logo); + System.out.println(System.lineSeparator() + logo); System.out.println("___________________________________________________________________________"); - System.out.println(" Hello! I'm Herbert.\n What can I do for you?"); + System.out.println("\tHello! I'm Herbert.\n\tWhat can I do for you?"); System.out.println("___________________________________________________________________________"); - System.out.println(" Bye. Hope to see you again soon!"); - System.out.println("___________________________________________________________________________"); - + System.out.println(); + } + public static void exit() { + System.out.println("___________________________________________________________________________"); + System.out.println("\tBye. Hope to see you again soon!"); + System.out.println("___________________________________________________________________________"); } public static void main(String[] args) { greeting(); + + Scanner scan = new Scanner(System.in); + + String line; + while (scan.hasNextLine()) { + line = scan.nextLine(); + + if (line.equalsIgnoreCase("bye")) { + exit(); + break; + } + + System.out.println("___________________________________________________________________________"); + System.out.println("\t" + line); + System.out.println("___________________________________________________________________________"); + System.out.println(); + } } } From d49c61b591e47308fda53dcbb4b504611c1fae3e Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Tue, 29 Aug 2023 23:48:35 +0800 Subject: [PATCH 03/24] Add list functionality and improve structure --- src/main/java/Herbert.java | 51 +++++++++++++++++++++++--------------- src/main/java/Main.java | 19 ++++++++++++++ 2 files changed, 50 insertions(+), 20 deletions(-) create mode 100644 src/main/java/Main.java diff --git a/src/main/java/Herbert.java b/src/main/java/Herbert.java index c7d4c8715..5ba08948d 100644 --- a/src/main/java/Herbert.java +++ b/src/main/java/Herbert.java @@ -1,8 +1,15 @@ -import java.util.Scanner; +import java.util.ArrayList; public class Herbert { - public static void greeting() { + private ArrayList history; + + public Herbert() { + this.history = new ArrayList<>(); + this.sayHello(); + } + + public void sayHello() { String logo = "( ) ( ) ( ) \n" + " | | .-. .--. ___ .-. | |.-. .--. ___ .-. | |_ \n" + " | |/ \\ / \\ ( ) \\ | / \\ / \\ ( ) \\ ( __) \n" @@ -19,34 +26,38 @@ public static void greeting() { System.out.println("___________________________________________________________________________"); System.out.println("\tHello! I'm Herbert.\n\tWhat can I do for you?"); System.out.println("___________________________________________________________________________"); - System.out.println(); } - public static void exit() { + public void sayGoodbye() { System.out.println("___________________________________________________________________________"); System.out.println("\tBye. Hope to see you again soon!"); System.out.println("___________________________________________________________________________"); } - public static void main(String[] args) { - greeting(); - - Scanner scan = new Scanner(System.in); - - String line; - while (scan.hasNextLine()) { - line = scan.nextLine(); - - if (line.equalsIgnoreCase("bye")) { - exit(); - break; - } - + public int processLine(String line) { + if (line.equalsIgnoreCase("bye")) { + sayGoodbye(); + return 1; + } else if (line.equalsIgnoreCase("list")) { + list(); + } else { + this.history.add(line); System.out.println("___________________________________________________________________________"); - System.out.println("\t" + line); + System.out.println("\tAdded: " + line); System.out.println("___________________________________________________________________________"); - System.out.println(); } + + return 0; } + + public void list() { + System.out.println("___________________________________________________________________________"); + for (int i = 0; i < history.size(); i++) { + System.out.println("\t" + (i + 1) + ". " + history.get(i)); + } + System.out.println("___________________________________________________________________________"); + } + + } diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 000000000..7511d51ca --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,19 @@ +import java.util.Scanner; + +public class Main { + + public static void main(String[] args) { + + Herbert herbert = new Herbert(); + + Scanner scan = new Scanner(System.in); + + String line; + while (scan.hasNextLine()) { + line = scan.nextLine(); + + if (herbert.processLine(line) == 1) return; + } + } + +} From 93a262a948376c897594ebe0a0a61e6fb7e31dbc Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 30 Aug 2023 00:26:24 +0800 Subject: [PATCH 04/24] Add task completion feature --- src/main/java/Herbert.java | 66 +++++++++++++++++++++++++++++++------- src/main/java/Task.java | 30 +++++++++++++++++ 2 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 src/main/java/Task.java diff --git a/src/main/java/Herbert.java b/src/main/java/Herbert.java index 5ba08948d..f915483d9 100644 --- a/src/main/java/Herbert.java +++ b/src/main/java/Herbert.java @@ -1,11 +1,12 @@ import java.util.ArrayList; +import java.lang.StringBuilder; public class Herbert { - private ArrayList history; + private ArrayList tasks; public Herbert() { - this.history = new ArrayList<>(); + this.tasks = new ArrayList<>(); this.sayHello(); } @@ -35,29 +36,72 @@ public void sayGoodbye() { } public int processLine(String line) { - if (line.equalsIgnoreCase("bye")) { + line = line.strip(); + line = line.toLowerCase(); + + if (line.equals("bye")) { sayGoodbye(); return 1; - } else if (line.equalsIgnoreCase("list")) { + } else if (line.equals("list")) { list(); + } else if (line.startsWith("mark")) { + markTask(line, true); + } else if (line.startsWith("unmark")) { + markTask(line, false); } else { - this.history.add(line); - System.out.println("___________________________________________________________________________"); - System.out.println("\tAdded: " + line); - System.out.println("___________________________________________________________________________"); + addTask(line); } return 0; } - public void list() { + public void markTask(String line, boolean completed) { + if (line.split(" ").length != 2) { + if (completed) { + System.out.println("Invalid command. Usage: mark "); + } else { + System.out.println("Invalid command. Usage: unmark "); + } + } + + int taskToMarkIndex = Integer.parseInt(line.split(" ")[1]) - 1; + Task taskToMark = tasks.get(taskToMarkIndex); + + taskToMark.setCompleted(completed); + System.out.println("___________________________________________________________________________"); - for (int i = 0; i < history.size(); i++) { - System.out.println("\t" + (i + 1) + ". " + history.get(i)); + if (completed) { + System.out.println("\tLovely! I've marked this task as done:"); + } else { + System.out.println("\tOkay, I've unmarked this task for you:"); } + System.out.println("\t [" + taskToMark.getStatusIcon() + "] " + taskToMark.getDescription()); System.out.println("___________________________________________________________________________"); } + public void addTask(String line) { + Task t = new Task(line); + + this.tasks.add(t); + System.out.println("___________________________________________________________________________"); + System.out.println("\tAdded: " + t.getDescription()); + System.out.println("___________________________________________________________________________"); + } + + public void list() { + System.out.println("___________________________________________________________________________"); + System.out.println("\tHere are the tasks in your list:"); + for (int i = 0; i < tasks.size(); i++) { + String s = "\t" + + (i + 1) + + ". [" + + tasks.get(i).getStatusIcon() + + "] " + + tasks.get(i).getDescription(); + System.out.println(s); + } + System.out.println("___________________________________________________________________________"); + } } diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 000000000..d260cd9e4 --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,30 @@ +public class Task { + + private String description; + private boolean completed; + + public Task(String description) { + this.description = description; + this.completed = false; + } + + public String getStatusIcon() { + return (completed ? "X" : " "); + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean isCompleted() { + return completed; + } + + public void setCompleted(boolean completed) { + this.completed = completed; + } +} From 0160070c50a09ea26e6acc1c90d2a03a001ddcd5 Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 6 Sep 2023 00:33:12 +0800 Subject: [PATCH 05/24] Add Todo, Deadline, and Event functionality --- src/main/java/Command.java | 66 +++++++++++++++ src/main/java/Deadline.java | 18 ++++ src/main/java/Event.java | 20 +++++ src/main/java/Herbert.java | 160 ++++++++++++++++++++++++++++++------ src/main/java/Main.java | 6 +- src/main/java/Task.java | 13 ++- src/main/java/Todo.java | 10 +++ 7 files changed, 266 insertions(+), 27 deletions(-) create mode 100644 src/main/java/Command.java create mode 100644 src/main/java/Deadline.java create mode 100644 src/main/java/Event.java create mode 100644 src/main/java/Todo.java diff --git a/src/main/java/Command.java b/src/main/java/Command.java new file mode 100644 index 000000000..1fb4de98a --- /dev/null +++ b/src/main/java/Command.java @@ -0,0 +1,66 @@ +public enum Command { + LIST { + @Override + public String toString() { + return "\tList all current tasks." + + System.lineSeparator() + + "\tUsage: list"; + } + }, + MARK { + @Override + public String toString() { + return "\tMark a task as completed." + + System.lineSeparator() + + "\tUsage: mark "; + } + }, + UNMARK { + @Override + public String toString() { + return "\tMark a task as incomplete." + + System.lineSeparator() + + "\tUsage: unmark "; + } + }, + TODO { + @Override + public String toString() { + return "\tAdd a new todo to your list of tasks." + + System.lineSeparator() + + "\tUsage: todo "; + } + }, + DEADLINE { + @Override + public String toString() { + return "\tAdd a new deadline to your list of tasks." + + System.lineSeparator() + + "\tUsage: deadline /by "; + } + }, + EVENT { + @Override + public String toString() { + return "\tAdd a new event to your list of tasks." + + System.lineSeparator() + + "\tUsage: event /from /to "; + } + }, + HELP { + @Override + public String toString() { + return "\tShow this help menu." + + System.lineSeparator() + + "\tUsage: help"; + } + }, + BYE { + @Override + public String toString() { + return "\tExit the Herbert application." + + System.lineSeparator() + + "\tUsage: bye"; + } + } +} diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 000000000..c38328f2f --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,18 @@ +public class Deadline extends Task { + + protected String dueDate; + + public Deadline(String description, String dueDate) { + super(description); + this.dueDate = dueDate; + } + + public String getCode() { + return "D"; + } + + @Override + public String toString() { + return super.toString() + " (by: " + this.dueDate + ")"; + } +} diff --git a/src/main/java/Event.java b/src/main/java/Event.java new file mode 100644 index 000000000..56d08d3c1 --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,20 @@ +public class Event extends Task { + + protected String from; + protected String to; + + public Event(String description, String from, String to) { + super(description); + this.from = from; + this.to = to; + } + + public String getCode() { + return "E"; + } + + @Override + public String toString() { + return super.toString() + " (from: " + this.from + " to: " + this.to + ")"; + } +} diff --git a/src/main/java/Herbert.java b/src/main/java/Herbert.java index f915483d9..e2e17484e 100644 --- a/src/main/java/Herbert.java +++ b/src/main/java/Herbert.java @@ -1,5 +1,7 @@ import java.util.ArrayList; -import java.lang.StringBuilder; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class Herbert { @@ -25,7 +27,11 @@ public void sayHello() { System.out.println(System.lineSeparator() + logo); System.out.println("___________________________________________________________________________"); - System.out.println("\tHello! I'm Herbert.\n\tWhat can I do for you?"); + System.out.println("\tHello! I'm Herbert." + System.lineSeparator() + "\tWhat can I do for you?"); + System.out.println(System.lineSeparator() + "\tYou may choose from the following commands:"); + for (Command s : Command.values()) { + System.out.println("\t- " + s.name().toLowerCase()); + } System.out.println("___________________________________________________________________________"); } @@ -35,6 +41,18 @@ public void sayGoodbye() { System.out.println("___________________________________________________________________________"); } + public void displayHelp() { + System.out.println("###########################################################################"); + System.out.println("\tWelcome to the Herbert Helpline!" + System.lineSeparator()); + System.out.println("\tCOMMANDS"); + for (Command s : Command.values()) { + System.out.println("\t* " + s.name().toLowerCase()); + System.out.println(s); + System.out.println(); + } + System.out.println("###########################################################################"); + } + public int processLine(String line) { line = line.strip(); line = line.toLowerCase(); @@ -44,12 +62,16 @@ public int processLine(String line) { return 1; } else if (line.equals("list")) { list(); + } else if (line.equals("help")) { + displayHelp(); + } else if (line.startsWith("todo") || line.startsWith("deadline") || line.startsWith("event")) { + addTask(line); } else if (line.startsWith("mark")) { markTask(line, true); } else if (line.startsWith("unmark")) { markTask(line, false); } else { - addTask(line); + printInvalidInputMessage(); } return 0; @@ -57,17 +79,30 @@ public int processLine(String line) { public void markTask(String line, boolean completed) { if (line.split(" ").length != 2) { - if (completed) { - System.out.println("Invalid command. Usage: mark "); - } else { - System.out.println("Invalid command. Usage: unmark "); + printInvalidInputMessage(); + return; + } + + // Extract which task the user wishes to (un)mark + int taskIndex; + try { + taskIndex = Integer.parseInt(line.split(" ")[1]) - 1; + if (taskIndex < 0) { + throw new IllegalArgumentException("The second argument must be a positive integer."); } + } catch (NumberFormatException e) { + System.out.printf("The second argument must be numeric (%s)\n", e); + printInvalidInputMessage(); + return; + } catch (IllegalArgumentException e) { + System.out.println(e); + printInvalidInputMessage(); + return; } - int taskToMarkIndex = Integer.parseInt(line.split(" ")[1]) - 1; - Task taskToMark = tasks.get(taskToMarkIndex); + Task task = tasks.get(taskIndex); - taskToMark.setCompleted(completed); + task.setCompleted(completed); System.out.println("___________________________________________________________________________"); if (completed) { @@ -75,33 +110,112 @@ public void markTask(String line, boolean completed) { } else { System.out.println("\tOkay, I've unmarked this task for you:"); } - System.out.println("\t [" + taskToMark.getStatusIcon() + "] " + taskToMark.getDescription()); + System.out.println("\t\t[" + task.getStatusIcon() + "] " + task.getDescription()); System.out.println("___________________________________________________________________________"); } public void addTask(String line) { - Task t = new Task(line); + String[] words = line.split(" "); - this.tasks.add(t); - System.out.println("___________________________________________________________________________"); - System.out.println("\tAdded: " + t.getDescription()); - System.out.println("___________________________________________________________________________"); + if (words.length < 2) { + printInvalidInputMessage(); + return; + } + + switch (words[0]) { + case "todo": { + // TODO + String[] todoArray = Arrays.copyOfRange(words, 1, words.length); + String description = String.join(" ", todoArray); + + Todo td = new Todo(description); + this.tasks.add(td); + + System.out.println("___________________________________________________________________________"); + System.out.println("\tOkay, I've added this todo to your task list:"); + System.out.printf("\t\t[%s][%s] %s\n", td.getCode(), td.getStatusIcon(), td); + System.out.printf("\tNow you have %d task(s) in your list.\n", tasks.size()); + System.out.println("___________________________________________________________________________"); + + break; + } + case "deadline": { + String patternString = "^deadline\\s+(.+?)\\s+/by\\s+(.+)$"; + Pattern pattern = Pattern.compile(patternString); + Matcher matcher = pattern.matcher(line); + + if (!matcher.find()) { + printInvalidInputMessage(); + return; + } + + String description = matcher.group(1); + String date = matcher.group(2); + + Deadline dl = new Deadline(description, date); + tasks.add(dl); + + System.out.println("___________________________________________________________________________"); + System.out.println("\tOkay, I've added this deadline to your task list:"); + System.out.printf("\t\t[%s][%s] %s\n", dl.getCode(), dl.getStatusIcon(), dl); + System.out.printf("\tNow you have %d task(s) in your list.\n", tasks.size()); + System.out.println("___________________________________________________________________________"); + break; + } + case "event": + String patternString = "^event\\s+(.+?)\\s+/from\\s+(.+?)\\s+/to\\s+(.+)$"; + Pattern pattern = Pattern.compile(patternString); + Matcher matcher = pattern.matcher(line); + + if (!matcher.find()) { + printInvalidInputMessage(); + return; + } + + String description = matcher.group(1); + String from = matcher.group(2); + String to = matcher.group(3); + + Event e = new Event(description, from, to); + tasks.add(e); + + System.out.println("___________________________________________________________________________"); + System.out.println("\tOkay, I've added this event to your task list:"); + System.out.printf("\t\t[%s][%s] %s\n", e.getCode(), e.getStatusIcon(), e); + System.out.printf("\tNow you have %d task(s) in your list.\n", tasks.size()); + System.out.println("___________________________________________________________________________"); + break; + } } public void list() { System.out.println("___________________________________________________________________________"); + + if (tasks.size() == 0) { + System.out.println("\tThere are no tasks in your list! Sit back, relax, and enjoy."); + System.out.println("___________________________________________________________________________"); + return; + } + System.out.println("\tHere are the tasks in your list:"); for (int i = 0; i < tasks.size(); i++) { - String s = "\t" + - (i + 1) + - ". [" + - tasks.get(i).getStatusIcon() + - "] " + - tasks.get(i).getDescription(); - System.out.println(s); + System.out.printf("\t%d. [%s][%s] %s\n", + (i + 1), + tasks.get(i).getCode(), + tasks.get(i).getStatusIcon(), + tasks.get(i).getDescription() + ); } + System.out.println("___________________________________________________________________________"); } + public void printInvalidInputMessage() { + System.out.println("___________________________________________________________________________"); + System.out.println("\tInvalid input. Use 'help' for usage instructions."); + System.out.println("___________________________________________________________________________"); + } } + + diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 7511d51ca..15e55f83a 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -11,8 +11,12 @@ public static void main(String[] args) { String line; while (scan.hasNextLine()) { line = scan.nextLine(); + System.out.println(); - if (herbert.processLine(line) == 1) return; + // User has inputted "bye" + if (herbert.processLine(line) == 1) { + break; + } } } diff --git a/src/main/java/Task.java b/src/main/java/Task.java index d260cd9e4..abbbddb46 100644 --- a/src/main/java/Task.java +++ b/src/main/java/Task.java @@ -1,7 +1,7 @@ -public class Task { +public abstract class Task { - private String description; - private boolean completed; + protected String description; + protected boolean completed; public Task(String description) { this.description = description; @@ -27,4 +27,11 @@ public boolean isCompleted() { public void setCompleted(boolean completed) { this.completed = completed; } + + @Override + public String toString() { + return this.description; + } + + public abstract String getCode(); } diff --git a/src/main/java/Todo.java b/src/main/java/Todo.java new file mode 100644 index 000000000..fb247b960 --- /dev/null +++ b/src/main/java/Todo.java @@ -0,0 +1,10 @@ +public class Todo extends Task { + + public Todo(String description) { + super(description); + } + + public String getCode() { + return "T"; + } +} From 39066db38eaed3b88143b116b6dbfd0dbc819109 Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 13 Sep 2023 10:53:02 +0800 Subject: [PATCH 06/24] Modularise code and handle errors --- src/main/java/Deadline.java | 5 + src/main/java/DukeException.java | 5 + src/main/java/Event.java | 6 + src/main/java/Herbert.java | 238 ++++++++++++++++++++----------- src/main/java/Main.java | 2 - 5 files changed, 170 insertions(+), 86 deletions(-) create mode 100644 src/main/java/DukeException.java diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index c38328f2f..8b475ed95 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -7,6 +7,11 @@ public Deadline(String description, String dueDate) { this.dueDate = dueDate; } + public Deadline(String[] details) { + super(details[0]); + this.dueDate = details[1]; + } + public String getCode() { return "D"; } diff --git a/src/main/java/DukeException.java b/src/main/java/DukeException.java new file mode 100644 index 000000000..31f5a63d1 --- /dev/null +++ b/src/main/java/DukeException.java @@ -0,0 +1,5 @@ +public class DukeException extends Exception { + public DukeException(String errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/Event.java b/src/main/java/Event.java index 56d08d3c1..221bf94c0 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -9,6 +9,12 @@ public Event(String description, String from, String to) { this.to = to; } + public Event(String[] details) { + super(details[0]); + this.from = details[1]; + this.to = details[2]; + } + public String getCode() { return "E"; } diff --git a/src/main/java/Herbert.java b/src/main/java/Herbert.java index e2e17484e..0261c59ab 100644 --- a/src/main/java/Herbert.java +++ b/src/main/java/Herbert.java @@ -1,5 +1,4 @@ import java.util.ArrayList; -import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -12,7 +11,7 @@ public Herbert() { this.sayHello(); } - public void sayHello() { + private void sayHello() { String logo = "( ) ( ) ( ) \n" + " | | .-. .--. ___ .-. | |.-. .--. ___ .-. | |_ \n" + " | |/ \\ / \\ ( ) \\ | / \\ / \\ ( ) \\ ( __) \n" @@ -35,13 +34,13 @@ public void sayHello() { System.out.println("___________________________________________________________________________"); } - public void sayGoodbye() { + private void sayGoodbye() { System.out.println("___________________________________________________________________________"); System.out.println("\tBye. Hope to see you again soon!"); System.out.println("___________________________________________________________________________"); } - public void displayHelp() { + private void displayHelp() { System.out.println("###########################################################################"); System.out.println("\tWelcome to the Herbert Helpline!" + System.lineSeparator()); System.out.println("\tCOMMANDS"); @@ -55,140 +54,175 @@ public void displayHelp() { public int processLine(String line) { line = line.strip(); - line = line.toLowerCase(); + String lowerLine = line.toLowerCase(); - if (line.equals("bye")) { + if (line.isEmpty()) { + printMessageInvalidInput(); + } + + if (lowerLine.equals("bye")) { sayGoodbye(); return 1; - } else if (line.equals("list")) { - list(); - } else if (line.equals("help")) { + } else if (lowerLine.equals("list")) { + listTasks(); + } else if (lowerLine.equals("help")) { displayHelp(); - } else if (line.startsWith("todo") || line.startsWith("deadline") || line.startsWith("event")) { + } else if (lowerLine.startsWith("todo") || lowerLine.startsWith("deadline") || lowerLine.startsWith("event")) { addTask(line); - } else if (line.startsWith("mark")) { + } else if (lowerLine.startsWith("mark")) { markTask(line, true); - } else if (line.startsWith("unmark")) { + } else if (lowerLine.startsWith("unmark")) { markTask(line, false); } else { - printInvalidInputMessage(); + printMessageUnknownCommand(line); } return 0; } - public void markTask(String line, boolean completed) { - if (line.split(" ").length != 2) { - printInvalidInputMessage(); + private void markTask(String line, boolean completed) { + // Check for valid user input + if (checkInputMarkTask(line) == -1) { return; } - // Extract which task the user wishes to (un)mark - int taskIndex; - try { - taskIndex = Integer.parseInt(line.split(" ")[1]) - 1; - if (taskIndex < 0) { - throw new IllegalArgumentException("The second argument must be a positive integer."); - } - } catch (NumberFormatException e) { - System.out.printf("The second argument must be numeric (%s)\n", e); - printInvalidInputMessage(); + // Extract task index and mark the task as completed + int taskIndex = extractTaskIndex(line); + if (taskIndex == -1) { return; - } catch (IllegalArgumentException e) { - System.out.println(e); - printInvalidInputMessage(); + } + int verify = verifyTaskIndex(taskIndex); + if (verify == -1) { return; } - Task task = tasks.get(taskIndex); - task.setCompleted(completed); - System.out.println("___________________________________________________________________________"); - if (completed) { - System.out.println("\tLovely! I've marked this task as done:"); - } else { - System.out.println("\tOkay, I've unmarked this task for you:"); + // Print result message to user + printMessageMarkTask(task, completed); + } + + private int checkInputMarkTask(String line) { + if (line.split(" ").length != 2) { + printMessageInvalidInput("Please enter the task number you wish to change the status of."); + return -1; } - System.out.println("\t\t[" + task.getStatusIcon() + "] " + task.getDescription()); - System.out.println("___________________________________________________________________________"); + + return 0; } - public void addTask(String line) { - String[] words = line.split(" "); + private int extractTaskIndex(String line) { + int taskIndex; + try { + taskIndex = Integer.parseInt(line.split(" ")[1]) - 1; + } catch (NumberFormatException e) { + printMessageInvalidInput("Task index must be a positive integer."); + return -1; + } - if (words.length < 2) { - printInvalidInputMessage(); + return taskIndex; + } + + private int verifyTaskIndex(int taskIndex) { + if (taskIndex >= tasks.size()) { + printMessageInvalidInput("No such task exists!"); + return -1; + } + if (taskIndex < 0) { + printMessageInvalidInput("Task index must be a positive integer."); + return -1; + } + return 0; + } + + private void addTask(String line) { + if (checkInputAddTask(line) == -1) { return; } + String[] words = line.split(" "); switch (words[0]) { case "todo": { - // TODO - String[] todoArray = Arrays.copyOfRange(words, 1, words.length); - String description = String.join(" ", todoArray); + // Get details + String description = line.substring(line.indexOf(" ") + 1); + // Create and add task Todo td = new Todo(description); this.tasks.add(td); - System.out.println("___________________________________________________________________________"); - System.out.println("\tOkay, I've added this todo to your task list:"); - System.out.printf("\t\t[%s][%s] %s\n", td.getCode(), td.getStatusIcon(), td); - System.out.printf("\tNow you have %d task(s) in your list.\n", tasks.size()); - System.out.println("___________________________________________________________________________"); + // Print success message + printMessageAddTask(td); break; } case "deadline": { - String patternString = "^deadline\\s+(.+?)\\s+/by\\s+(.+)$"; - Pattern pattern = Pattern.compile(patternString); - Matcher matcher = pattern.matcher(line); - - if (!matcher.find()) { - printInvalidInputMessage(); + // Get details + String[] dlDetails = getDeadlineDetails(line); + if (dlDetails == null) { return; } - String description = matcher.group(1); - String date = matcher.group(2); - - Deadline dl = new Deadline(description, date); + // Create and add task + Deadline dl = new Deadline(dlDetails); tasks.add(dl); - System.out.println("___________________________________________________________________________"); - System.out.println("\tOkay, I've added this deadline to your task list:"); - System.out.printf("\t\t[%s][%s] %s\n", dl.getCode(), dl.getStatusIcon(), dl); - System.out.printf("\tNow you have %d task(s) in your list.\n", tasks.size()); - System.out.println("___________________________________________________________________________"); + // Print success message + printMessageAddTask(dl); break; } case "event": - String patternString = "^event\\s+(.+?)\\s+/from\\s+(.+?)\\s+/to\\s+(.+)$"; - Pattern pattern = Pattern.compile(patternString); - Matcher matcher = pattern.matcher(line); - - if (!matcher.find()) { - printInvalidInputMessage(); + // Get details + String[] evDetails = getEventDetails(line); + if (evDetails == null) { return; } - String description = matcher.group(1); - String from = matcher.group(2); - String to = matcher.group(3); - - Event e = new Event(description, from, to); - tasks.add(e); + // Create and add task + Event ev = new Event(evDetails); + tasks.add(ev); - System.out.println("___________________________________________________________________________"); - System.out.println("\tOkay, I've added this event to your task list:"); - System.out.printf("\t\t[%s][%s] %s\n", e.getCode(), e.getStatusIcon(), e); - System.out.printf("\tNow you have %d task(s) in your list.\n", tasks.size()); - System.out.println("___________________________________________________________________________"); + // Print success message + printMessageAddTask(ev); break; } } - public void list() { + private int checkInputAddTask(String line) { + String[] words = line.split(" "); + if (words.length < 2) { + printMessageInvalidInput(); + return -1; + } + return 0; + } + + private String[] getDeadlineDetails(String line) { + String patternString = "^deadline\\s+(.+?)\\s+/by\\s+(.+)$"; + Pattern pattern = Pattern.compile(patternString); + Matcher matcher = pattern.matcher(line); + + if (!matcher.find()) { + printMessageInvalidInput(); + return null; + } + + return new String[] {matcher.group(1), matcher.group(2)}; + } + + private String[] getEventDetails(String line) { + String patternString = "^event\\s+(.+?)\\s+/from\\s+(.+?)\\s+/to\\s+(.+)$"; + Pattern pattern = Pattern.compile(patternString); + Matcher matcher = pattern.matcher(line); + + if (!matcher.find()) { + printMessageInvalidInput(); + return null; + } + + return new String[] {matcher.group(1), matcher.group(2), matcher.group(3)}; + } + + public void listTasks() { System.out.println("___________________________________________________________________________"); if (tasks.size() == 0) { @@ -210,11 +244,47 @@ public void list() { System.out.println("___________________________________________________________________________"); } - public void printInvalidInputMessage() { + //region Message methods + private void printMessageInvalidInput(String errorMessage) { + System.out.println("___________________________________________________________________________"); + System.out.printf("\t%s\n", errorMessage); + System.out.println("\tUse 'help' for usage instructions."); + System.out.println("___________________________________________________________________________"); + } + + private void printMessageInvalidInput() { System.out.println("___________________________________________________________________________"); System.out.println("\tInvalid input. Use 'help' for usage instructions."); System.out.println("___________________________________________________________________________"); + + } + + private void printMessageUnknownCommand(String userInput) { + System.out.println("___________________________________________________________________________"); + System.out.printf("\tUnknown command '%s'. Use 'help' for a full list of user commands.\n", userInput); + System.out.println("___________________________________________________________________________"); + } + + private void printMessageMarkTask(Task task, boolean completed) { + System.out.println("___________________________________________________________________________"); + if (completed) { + System.out.println("\tLovely! I've marked this task as done:"); + } else { + System.out.println("\tOkay, I've unmarked this task for you:"); + } + System.out.println("\t\t[" + task.getStatusIcon() + "] " + task.getDescription()); + System.out.println("___________________________________________________________________________"); } + + private void printMessageAddTask(Task t) { + System.out.println("___________________________________________________________________________"); + System.out.println("\tOkay, I've added this to your task list:"); + System.out.printf("\t\t[%s][%s] %s\n", t.getCode(), t.getStatusIcon(), t); + System.out.printf("\tNow you have %d task(s) in your list.\n", tasks.size()); + System.out.println("___________________________________________________________________________"); + } + //endregion + } diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 15e55f83a..841514efd 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -3,9 +3,7 @@ public class Main { public static void main(String[] args) { - Herbert herbert = new Herbert(); - Scanner scan = new Scanner(System.in); String line; From 1e8e89cbe8073b51e73afcff43cb87d48328db5d Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 13 Sep 2023 11:20:17 +0800 Subject: [PATCH 07/24] Handle blank input --- src/main/java/Herbert.java | 7 ++++--- src/main/java/Main.java | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/Herbert.java b/src/main/java/Herbert.java index 0261c59ab..5eb9f02a8 100644 --- a/src/main/java/Herbert.java +++ b/src/main/java/Herbert.java @@ -54,12 +54,13 @@ private void displayHelp() { public int processLine(String line) { line = line.strip(); - String lowerLine = line.toLowerCase(); - if (line.isEmpty()) { - printMessageInvalidInput(); + printMessageInvalidInput("Please enter a command!"); + return -1; } + String lowerLine = line.toLowerCase(); + if (lowerLine.equals("bye")) { sayGoodbye(); return 1; diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 841514efd..73bf0e550 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -11,8 +11,9 @@ public static void main(String[] args) { line = scan.nextLine(); System.out.println(); - // User has inputted "bye" - if (herbert.processLine(line) == 1) { + int process = herbert.processLine(line); + if (process == 1) { + // User has inputted "bye" break; } } From 53fe1c1e4b06149dda7b5172d953db27604dd2eb Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 13 Sep 2023 13:34:07 +0800 Subject: [PATCH 08/24] Update logo and README --- README.md | 74 +++++++++++++++++++++++++------------- src/main/java/Herbert.java | 19 ++++------ 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 8715d4d91..2b823a3b1 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,50 @@ -# 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. - -## Setting up in Intellij - -Prerequisites: JDK 11, update Intellij to the most recent version. - -1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first) -1. Open the project into Intellij as follows: - 1. Click `Open`. - 1. Select the project directory, and click `OK`. - 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: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` +# Project Herbert + +``` + __ __ _______ ______ _______ _______ ______ _______ +| | | || || _ | | _ || || _ | | | +| |_| || ___|| | || | |_| || ___|| | || |_ _| +| || |___ | |_||_ | || |___ | |_||_ | | +| || ___|| __ || _ | | ___|| __ | | | +| _ || |___ | | | || |_| || |___ | | | | | | +|__| |__||_______||___| |_||_______||_______||___| |_| |___| +``` + +Welcome to Herbert, the newest and friendliest AI assistant on the market. + + +## Commands + +``` +* list +List all current tasks. +Usage: list + +* mark + Mark a task as completed. + Usage: mark + +* unmark + Mark a task as incomplete. + Usage: unmark + +* todo + Add a new todo to your list of tasks. + Usage: todo + +* deadline + Add a new deadline to your list of tasks. + Usage: deadline /by + +* event + Add a new event to your list of tasks. + Usage: event /from /to + +* help + Show this help menu. + Usage: help + +* bye + Exit the Herbert application. + Usage: bye +``` \ No newline at end of file diff --git a/src/main/java/Herbert.java b/src/main/java/Herbert.java index 5eb9f02a8..691504e47 100644 --- a/src/main/java/Herbert.java +++ b/src/main/java/Herbert.java @@ -12,17 +12,13 @@ public Herbert() { } private void sayHello() { - String logo = "( ) ( ) ( ) \n" - + " | | .-. .--. ___ .-. | |.-. .--. ___ .-. | |_ \n" - + " | |/ \\ / \\ ( ) \\ | / \\ / \\ ( ) \\ ( __) \n" - + " | .-. . | .-. ; | ' .-. ; | .-. | | .-. ; | ' .-. ; | | \n" - + " | | | | | | | | | / (___) | | | | | | | | | / (___) | | ___ \n" - + " | | | | | |/ | | | | | | | | |/ | | | | |( ) \n" - + " | | | | | ' _.' | | | | | | | ' _.' | | | | | | \n" - + " | | | | | .'.-. | | | ' | | | .'.-. | | | ' | | \n" - + " | | | | ' `-' / | | ' `-' ; ' `-' / | | ' `-' ; \n" - + "(___)(___) `.__.' (___) `.__. `.__.' (___) `.__. \n"; - + String logo = " __ __ _______ ______ _______ _______ ______ _______ \n" + + "| | | || || _ | | _ || || _ | | |\n" + + "| |_| || ___|| | || | |_| || ___|| | || |_ _|\n" + + "| || |___ | |_||_ | || |___ | |_||_ | | \n" + + "| || ___|| __ || _ | | ___|| __ | | | \n" + + "| _ || |___ | | | || |_| || |___ | | | | | | \n" + + "|__| |__||_______||___| |_||_______||_______||___| |_| |___| "; System.out.println(System.lineSeparator() + logo); System.out.println("___________________________________________________________________________"); @@ -257,7 +253,6 @@ private void printMessageInvalidInput() { System.out.println("___________________________________________________________________________"); System.out.println("\tInvalid input. Use 'help' for usage instructions."); System.out.println("___________________________________________________________________________"); - } private void printMessageUnknownCommand(String userInput) { From 573753f0e867ee25ddb18bf94afb5141f66290df Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 13 Sep 2023 13:42:14 +0800 Subject: [PATCH 09/24] Divide classes into packages --- src/main/java/{ => duke}/Command.java | 2 ++ src/main/java/{ => duke}/DukeException.java | 2 ++ src/main/java/{ => duke}/Herbert.java | 4 ++++ src/main/java/{ => duke}/Main.java | 2 ++ src/main/java/{ => task}/Deadline.java | 2 ++ src/main/java/{ => task}/Event.java | 2 ++ src/main/java/{ => task}/Task.java | 2 ++ src/main/java/{ => task}/Todo.java | 2 ++ 8 files changed, 18 insertions(+) rename src/main/java/{ => duke}/Command.java (99%) rename src/main/java/{ => duke}/DukeException.java (89%) rename src/main/java/{ => duke}/Herbert.java (99%) rename src/main/java/{ => duke}/Main.java (97%) rename src/main/java/{ => task}/Deadline.java (96%) rename src/main/java/{ => task}/Event.java (97%) rename src/main/java/{ => task}/Task.java (98%) rename src/main/java/{ => task}/Todo.java (91%) diff --git a/src/main/java/Command.java b/src/main/java/duke/Command.java similarity index 99% rename from src/main/java/Command.java rename to src/main/java/duke/Command.java index 1fb4de98a..84b370a32 100644 --- a/src/main/java/Command.java +++ b/src/main/java/duke/Command.java @@ -1,3 +1,5 @@ +package duke; + public enum Command { LIST { @Override diff --git a/src/main/java/DukeException.java b/src/main/java/duke/DukeException.java similarity index 89% rename from src/main/java/DukeException.java rename to src/main/java/duke/DukeException.java index 31f5a63d1..3c1763dfd 100644 --- a/src/main/java/DukeException.java +++ b/src/main/java/duke/DukeException.java @@ -1,3 +1,5 @@ +package duke; + public class DukeException extends Exception { public DukeException(String errorMessage) { super(errorMessage); diff --git a/src/main/java/Herbert.java b/src/main/java/duke/Herbert.java similarity index 99% rename from src/main/java/Herbert.java rename to src/main/java/duke/Herbert.java index 691504e47..32743e94c 100644 --- a/src/main/java/Herbert.java +++ b/src/main/java/duke/Herbert.java @@ -1,3 +1,7 @@ +package duke; + +import task.*; + import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/Main.java b/src/main/java/duke/Main.java similarity index 97% rename from src/main/java/Main.java rename to src/main/java/duke/Main.java index 73bf0e550..dd6dea10b 100644 --- a/src/main/java/Main.java +++ b/src/main/java/duke/Main.java @@ -1,3 +1,5 @@ +package duke; + import java.util.Scanner; public class Main { diff --git a/src/main/java/Deadline.java b/src/main/java/task/Deadline.java similarity index 96% rename from src/main/java/Deadline.java rename to src/main/java/task/Deadline.java index 8b475ed95..39dd1ca0e 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/task/Deadline.java @@ -1,3 +1,5 @@ +package task; + public class Deadline extends Task { protected String dueDate; diff --git a/src/main/java/Event.java b/src/main/java/task/Event.java similarity index 97% rename from src/main/java/Event.java rename to src/main/java/task/Event.java index 221bf94c0..e2757ae32 100644 --- a/src/main/java/Event.java +++ b/src/main/java/task/Event.java @@ -1,3 +1,5 @@ +package task; + public class Event extends Task { protected String from; diff --git a/src/main/java/Task.java b/src/main/java/task/Task.java similarity index 98% rename from src/main/java/Task.java rename to src/main/java/task/Task.java index abbbddb46..57d3c630d 100644 --- a/src/main/java/Task.java +++ b/src/main/java/task/Task.java @@ -1,3 +1,5 @@ +package task; + public abstract class Task { protected String description; diff --git a/src/main/java/Todo.java b/src/main/java/task/Todo.java similarity index 91% rename from src/main/java/Todo.java rename to src/main/java/task/Todo.java index fb247b960..e636b6c42 100644 --- a/src/main/java/Todo.java +++ b/src/main/java/task/Todo.java @@ -1,3 +1,5 @@ +package task; + public class Todo extends Task { public Todo(String description) { From d4a0275ea1d59a31cdc2c418d8e3d96bdcff6b6c Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 20 Sep 2023 04:09:00 +0800 Subject: [PATCH 10/24] Add delete functionality --- src/main/java/duke/Command.java | 8 ++++++++ src/main/java/duke/Herbert.java | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/main/java/duke/Command.java b/src/main/java/duke/Command.java index 84b370a32..d746ff3bc 100644 --- a/src/main/java/duke/Command.java +++ b/src/main/java/duke/Command.java @@ -25,6 +25,14 @@ public String toString() { + "\tUsage: unmark "; } }, + DELETE { + @Override + public String toString() { + return "\tDelete a task from your list." + + System.lineSeparator() + + "\tUsage: delete "; + } + }, TODO { @Override public String toString() { diff --git a/src/main/java/duke/Herbert.java b/src/main/java/duke/Herbert.java index 32743e94c..503e3ff81 100644 --- a/src/main/java/duke/Herbert.java +++ b/src/main/java/duke/Herbert.java @@ -70,6 +70,8 @@ public int processLine(String line) { displayHelp(); } else if (lowerLine.startsWith("todo") || lowerLine.startsWith("deadline") || lowerLine.startsWith("event")) { addTask(line); + } else if (lowerLine.startsWith("delete")) { + deleteTask(line); } else if (lowerLine.startsWith("mark")) { markTask(line, true); } else if (lowerLine.startsWith("unmark")) { @@ -83,7 +85,7 @@ public int processLine(String line) { private void markTask(String line, boolean completed) { // Check for valid user input - if (checkInputMarkTask(line) == -1) { + if (checkInputTaskIndex(line) == -1) { return; } @@ -103,7 +105,7 @@ private void markTask(String line, boolean completed) { printMessageMarkTask(task, completed); } - private int checkInputMarkTask(String line) { + private int checkInputTaskIndex(String line) { if (line.split(" ").length != 2) { printMessageInvalidInput("Please enter the task number you wish to change the status of."); return -1; @@ -188,6 +190,25 @@ private void addTask(String line) { } } + private void deleteTask(String line) { + if (checkInputTaskIndex(line) == -1) { + return; + } + + int taskIndex = extractTaskIndex(line); + if (taskIndex == -1) { + return; + } + int verify = verifyTaskIndex(taskIndex); + if (verify == -1) { + return; + } + + Task taskCopy = tasks.get(taskIndex); + tasks.remove(taskIndex); + printMessageDeleteTask(taskCopy); + } + private int checkInputAddTask(String line) { String[] words = line.split(" "); if (words.length < 2) { @@ -283,6 +304,14 @@ private void printMessageAddTask(Task t) { System.out.printf("\tNow you have %d task(s) in your list.\n", tasks.size()); System.out.println("___________________________________________________________________________"); } + + private void printMessageDeleteTask(Task t) { + System.out.println("___________________________________________________________________________"); + System.out.println("\tNoted. I've removed this task from your list:"); + System.out.printf("\t\t[%s][%s] %s\n", t.getCode(), t.getStatusIcon(), t); + System.out.printf("\tNow you have %d task(s) in your list.\n", tasks.size()); + System.out.println("___________________________________________________________________________"); + } //endregion } From 7d83a7659d212e62f2a61c691d560fd17173323c Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 4 Oct 2023 00:07:43 +0800 Subject: [PATCH 11/24] Add functionality to auto-load from save file --- .gitignore | 5 +- src/main/java/duke/Herbert.java | 6 ++- src/main/java/duke/HerbertReader.java | 70 +++++++++++++++++++++++++++ src/main/java/duke/Main.java | 4 ++ src/main/java/task/Deadline.java | 3 ++ src/main/java/task/Event.java | 4 ++ 6 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/main/java/duke/HerbertReader.java diff --git a/.gitignore b/.gitignore index cdbb5cce2..867d239f2 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,7 @@ bin/ text-ui-test/EXPECTED-UNIX.TXT # Ignore .class files -*.class \ No newline at end of file +*.class + +# Ignore ./data directory +/data \ No newline at end of file diff --git a/src/main/java/duke/Herbert.java b/src/main/java/duke/Herbert.java index 32743e94c..23623f779 100644 --- a/src/main/java/duke/Herbert.java +++ b/src/main/java/duke/Herbert.java @@ -188,6 +188,10 @@ private void addTask(String line) { } } + public void addTask(Task t) { + this.tasks.add(t); + } + private int checkInputAddTask(String line) { String[] words = line.split(" "); if (words.length < 2) { @@ -238,7 +242,7 @@ public void listTasks() { (i + 1), tasks.get(i).getCode(), tasks.get(i).getStatusIcon(), - tasks.get(i).getDescription() + tasks.get(i) ); } diff --git a/src/main/java/duke/HerbertReader.java b/src/main/java/duke/HerbertReader.java new file mode 100644 index 000000000..35384f3ec --- /dev/null +++ b/src/main/java/duke/HerbertReader.java @@ -0,0 +1,70 @@ +package duke; + +import task.Deadline; +import task.Event; +import task.Todo; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; + +public abstract class HerbertReader { + + public static void createSaveFileIfNotExists() { + Path folderPath = Paths.get("data"); + Path filePath = folderPath.resolve("HerbertTasks.txt"); + + try { + // Create directory if it doesn't exist + if (!Files.exists(folderPath)) { + Files.createDirectories(folderPath); + } + + // Create file if it doesn't exist + if (!Files.exists(filePath)) { + Files.createFile(filePath); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void loadFromSaveFile(Herbert herbert) { + Path filePath = Paths.get("data", "HerbertTasks.txt"); + + try { + BufferedReader reader = Files.newBufferedReader(filePath); + String line = reader.readLine(); + + while (line != null) { + String[] split = line.split(" \\| "); + + switch (split[0]) { + case "T": + Todo t = new Todo(split[2]); + t.setCompleted(split[1].equals("1")); + herbert.addTask(t); + break; + case "D": + Deadline d = new Deadline(split[2], split[3]); + d.setCompleted(split[1].equals("1")); + herbert.addTask(d); + break; + case "E": + Event e = new Event(split[2], split[3], split[4]); + e.setCompleted(split[1].equals("1")); + herbert.addTask(e); + break; + } + + line = reader.readLine(); + } + } catch (IOException | SecurityException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java index dd6dea10b..036632245 100644 --- a/src/main/java/duke/Main.java +++ b/src/main/java/duke/Main.java @@ -6,6 +6,10 @@ public class Main { public static void main(String[] args) { Herbert herbert = new Herbert(); + + HerbertReader.createSaveFileIfNotExists(); + HerbertReader.loadFromSaveFile(herbert); + Scanner scan = new Scanner(System.in); String line; diff --git a/src/main/java/task/Deadline.java b/src/main/java/task/Deadline.java index 39dd1ca0e..5f642ca78 100644 --- a/src/main/java/task/Deadline.java +++ b/src/main/java/task/Deadline.java @@ -9,6 +9,9 @@ public Deadline(String description, String dueDate) { this.dueDate = dueDate; } + // String[] in the format: + // details[0] = description + // details[1] = due date public Deadline(String[] details) { super(details[0]); this.dueDate = details[1]; diff --git a/src/main/java/task/Event.java b/src/main/java/task/Event.java index e2757ae32..c5e7bc658 100644 --- a/src/main/java/task/Event.java +++ b/src/main/java/task/Event.java @@ -11,6 +11,10 @@ public Event(String description, String from, String to) { this.to = to; } + // String[] in the format: + // details[0] = description + // details[1] = from + // details[2] = to public Event(String[] details) { super(details[0]); this.from = details[1]; From 94e3ec61cda4b347951c4dc80a0ccc76196b182c Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 4 Oct 2023 01:01:21 +0800 Subject: [PATCH 12/24] Add automatic local save of newly added tasks --- src/main/java/duke/Herbert.java | 3 ++ src/main/java/duke/HerbertReader.java | 42 +++++++++++++++++++++++---- src/main/java/task/Deadline.java | 4 +++ src/main/java/task/Event.java | 8 +++++ 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/main/java/duke/Herbert.java b/src/main/java/duke/Herbert.java index 23623f779..abf6b9cb3 100644 --- a/src/main/java/duke/Herbert.java +++ b/src/main/java/duke/Herbert.java @@ -150,6 +150,7 @@ private void addTask(String line) { // Create and add task Todo td = new Todo(description); this.tasks.add(td); + HerbertReader.addTaskToSaveFile(td); // Print success message printMessageAddTask(td); @@ -166,6 +167,7 @@ private void addTask(String line) { // Create and add task Deadline dl = new Deadline(dlDetails); tasks.add(dl); + HerbertReader.addTaskToSaveFile(dl); // Print success message printMessageAddTask(dl); @@ -181,6 +183,7 @@ private void addTask(String line) { // Create and add task Event ev = new Event(evDetails); tasks.add(ev); + HerbertReader.addTaskToSaveFile(ev); // Print success message printMessageAddTask(ev); diff --git a/src/main/java/duke/HerbertReader.java b/src/main/java/duke/HerbertReader.java index 35384f3ec..d089da1d2 100644 --- a/src/main/java/duke/HerbertReader.java +++ b/src/main/java/duke/HerbertReader.java @@ -2,20 +2,25 @@ import task.Deadline; import task.Event; +import task.Task; import task.Todo; import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; +import java.nio.file.StandardOpenOption; +import java.sql.SQLOutput; public abstract class HerbertReader { + private static final Path folderPath = Paths.get("data"); + private static final Path filePath = folderPath.resolve("HerbertTasks.txt"); + public static void createSaveFileIfNotExists() { - Path folderPath = Paths.get("data"); - Path filePath = folderPath.resolve("HerbertTasks.txt"); try { // Create directory if it doesn't exist @@ -33,7 +38,6 @@ public static void createSaveFileIfNotExists() { } public static void loadFromSaveFile(Herbert herbert) { - Path filePath = Paths.get("data", "HerbertTasks.txt"); try { BufferedReader reader = Files.newBufferedReader(filePath); @@ -62,9 +66,37 @@ public static void loadFromSaveFile(Herbert herbert) { line = reader.readLine(); } - } catch (IOException | SecurityException e) { + } catch (IOException e) { e.printStackTrace(); } } + public static void addTaskToSaveFile(Task t) { + try { + BufferedWriter writer = Files.newBufferedWriter( + filePath, + StandardCharsets.UTF_8, + StandardOpenOption.APPEND + ); + + StringBuilder s = new StringBuilder(String.format( + "%s | %s | %s", + t.getCode(), + t.isCompleted() ? "1" : "0", + t.getDescription() + )); + if (t instanceof Deadline) { + s.append(String.format(" | %s", ((Deadline) t).getDueDate())); + } else if (t instanceof Event) { + Event e = (Event) t; + s.append(String.format(" | %s | %s", e.getFrom(), e.getTo())); + } + + writer.write(s.toString()); + writer.newLine(); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/src/main/java/task/Deadline.java b/src/main/java/task/Deadline.java index 5f642ca78..5bc3709c3 100644 --- a/src/main/java/task/Deadline.java +++ b/src/main/java/task/Deadline.java @@ -25,4 +25,8 @@ public String getCode() { public String toString() { return super.toString() + " (by: " + this.dueDate + ")"; } + + public String getDueDate() { + return this.dueDate; + } } diff --git a/src/main/java/task/Event.java b/src/main/java/task/Event.java index c5e7bc658..741c9b113 100644 --- a/src/main/java/task/Event.java +++ b/src/main/java/task/Event.java @@ -29,4 +29,12 @@ public String getCode() { public String toString() { return super.toString() + " (from: " + this.from + " to: " + this.to + ")"; } + + public String getFrom() { + return this.from; + } + + public String getTo() { + return this.to; + } } From e7544e26ef39a3cb2ce6ee3fe340d84557e69eb9 Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 4 Oct 2023 11:21:54 +0800 Subject: [PATCH 13/24] Extract storage functionality into HerbertReader class --- src/main/java/META-INF/MANIFEST.MF | 3 +++ src/main/java/duke/Herbert.java | 26 +++++++++++++++++++++++--- src/main/java/duke/HerbertReader.java | 21 ++++++++++++++------- src/main/java/duke/Main.java | 20 +------------------- 4 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 src/main/java/META-INF/MANIFEST.MF diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..97011d339 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: duke.Main + diff --git a/src/main/java/duke/Herbert.java b/src/main/java/duke/Herbert.java index 51818fb69..d23f551db 100644 --- a/src/main/java/duke/Herbert.java +++ b/src/main/java/duke/Herbert.java @@ -3,16 +3,36 @@ import task.*; import java.util.ArrayList; +import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Herbert { private ArrayList tasks; + private HerbertReader reader; public Herbert() { this.tasks = new ArrayList<>(); this.sayHello(); + + this.reader = new HerbertReader("data", "HerbertTasks.txt"); + this.reader.loadFromSaveFile(this); + } + + public void run() { + Scanner scan = new Scanner(System.in); + String line; + while (scan.hasNextLine()) { + line = scan.nextLine(); + System.out.println(); + + int process = this.processLine(line); + if (process == 1) { + // User has inputted "bye" + break; + } + } } private void sayHello() { @@ -152,7 +172,7 @@ private void addTask(String line) { // Create and add task Todo td = new Todo(description); this.tasks.add(td); - HerbertReader.addTaskToSaveFile(td); + this.reader.addTaskToSaveFile(td); // Print success message printMessageAddTask(td); @@ -169,7 +189,7 @@ private void addTask(String line) { // Create and add task Deadline dl = new Deadline(dlDetails); tasks.add(dl); - HerbertReader.addTaskToSaveFile(dl); + this.reader.addTaskToSaveFile(dl); // Print success message printMessageAddTask(dl); @@ -185,7 +205,7 @@ private void addTask(String line) { // Create and add task Event ev = new Event(evDetails); tasks.add(ev); - HerbertReader.addTaskToSaveFile(ev); + this.reader.addTaskToSaveFile(ev); // Print success message printMessageAddTask(ev); diff --git a/src/main/java/duke/HerbertReader.java b/src/main/java/duke/HerbertReader.java index d089da1d2..4182d99cd 100644 --- a/src/main/java/duke/HerbertReader.java +++ b/src/main/java/duke/HerbertReader.java @@ -15,13 +15,20 @@ import java.nio.file.StandardOpenOption; import java.sql.SQLOutput; -public abstract class HerbertReader { +public class HerbertReader { - private static final Path folderPath = Paths.get("data"); - private static final Path filePath = folderPath.resolve("HerbertTasks.txt"); + private final Path folderPath; + private final Path filePath; - public static void createSaveFileIfNotExists() { + public HerbertReader(String folderPath, String fileName) { + // Convert the paths given as Strings into Path objects + this.folderPath = Paths.get(folderPath); + this.filePath = this.folderPath.resolve(fileName); + this.createSaveFileIfNotExists(this.folderPath, this.filePath); + } + + private void createSaveFileIfNotExists(Path folderPath, Path filePath) { try { // Create directory if it doesn't exist if (!Files.exists(folderPath)) { @@ -37,7 +44,7 @@ public static void createSaveFileIfNotExists() { } } - public static void loadFromSaveFile(Herbert herbert) { + public void loadFromSaveFile(Herbert herbert) { try { BufferedReader reader = Files.newBufferedReader(filePath); @@ -71,10 +78,10 @@ public static void loadFromSaveFile(Herbert herbert) { } } - public static void addTaskToSaveFile(Task t) { + public void addTaskToSaveFile(Task t) { try { BufferedWriter writer = Files.newBufferedWriter( - filePath, + this.filePath, StandardCharsets.UTF_8, StandardOpenOption.APPEND ); diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java index 036632245..3b4bfae11 100644 --- a/src/main/java/duke/Main.java +++ b/src/main/java/duke/Main.java @@ -1,28 +1,10 @@ package duke; -import java.util.Scanner; - public class Main { public static void main(String[] args) { Herbert herbert = new Herbert(); - - HerbertReader.createSaveFileIfNotExists(); - HerbertReader.loadFromSaveFile(herbert); - - Scanner scan = new Scanner(System.in); - - String line; - while (scan.hasNextLine()) { - line = scan.nextLine(); - System.out.println(); - - int process = herbert.processLine(line); - if (process == 1) { - // User has inputted "bye" - break; - } - } + herbert.run(); } } From 97df3553bcf024f11411ce46c153c44053546547 Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 4 Oct 2023 13:35:13 +0800 Subject: [PATCH 14/24] Extract UI and parsing functionality --- src/main/java/duke/Herbert.java | 239 ++++---------------------- src/main/java/duke/HerbertParser.java | 77 +++++++++ src/main/java/duke/HerbertReader.java | 3 +- src/main/java/duke/HerbertUI.java | 124 +++++++++++++ src/main/java/duke/TaskList.java | 31 ++++ 5 files changed, 268 insertions(+), 206 deletions(-) create mode 100644 src/main/java/duke/HerbertParser.java create mode 100644 src/main/java/duke/HerbertUI.java create mode 100644 src/main/java/duke/TaskList.java diff --git a/src/main/java/duke/Herbert.java b/src/main/java/duke/Herbert.java index d23f551db..933a2e411 100644 --- a/src/main/java/duke/Herbert.java +++ b/src/main/java/duke/Herbert.java @@ -2,22 +2,20 @@ import task.*; -import java.util.ArrayList; import java.util.Scanner; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class Herbert { - private ArrayList tasks; - private HerbertReader reader; + private final HerbertReader reader; + private final TaskList taskList; public Herbert() { - this.tasks = new ArrayList<>(); - this.sayHello(); + this.taskList = new TaskList(); this.reader = new HerbertReader("data", "HerbertTasks.txt"); this.reader.loadFromSaveFile(this); + + HerbertUI.sayHello(); } public void run() { @@ -35,59 +33,23 @@ public void run() { } } - private void sayHello() { - String logo = " __ __ _______ ______ _______ _______ ______ _______ \n" - + "| | | || || _ | | _ || || _ | | |\n" - + "| |_| || ___|| | || | |_| || ___|| | || |_ _|\n" - + "| || |___ | |_||_ | || |___ | |_||_ | | \n" - + "| || ___|| __ || _ | | ___|| __ | | | \n" - + "| _ || |___ | | | || |_| || |___ | | | | | | \n" - + "|__| |__||_______||___| |_||_______||_______||___| |_| |___| "; - - System.out.println(System.lineSeparator() + logo); - System.out.println("___________________________________________________________________________"); - System.out.println("\tHello! I'm Herbert." + System.lineSeparator() + "\tWhat can I do for you?"); - System.out.println(System.lineSeparator() + "\tYou may choose from the following commands:"); - for (Command s : Command.values()) { - System.out.println("\t- " + s.name().toLowerCase()); - } - System.out.println("___________________________________________________________________________"); - } - - private void sayGoodbye() { - System.out.println("___________________________________________________________________________"); - System.out.println("\tBye. Hope to see you again soon!"); - System.out.println("___________________________________________________________________________"); - } - - private void displayHelp() { - System.out.println("###########################################################################"); - System.out.println("\tWelcome to the Herbert Helpline!" + System.lineSeparator()); - System.out.println("\tCOMMANDS"); - for (Command s : Command.values()) { - System.out.println("\t* " + s.name().toLowerCase()); - System.out.println(s); - System.out.println(); - } - System.out.println("###########################################################################"); - } public int processLine(String line) { line = line.strip(); if (line.isEmpty()) { - printMessageInvalidInput("Please enter a command!"); + HerbertUI.printMessageInvalidInput("Please enter a command!"); return -1; } String lowerLine = line.toLowerCase(); if (lowerLine.equals("bye")) { - sayGoodbye(); + HerbertUI.sayGoodbye(); return 1; } else if (lowerLine.equals("list")) { - listTasks(); + HerbertUI.listTasks(this.taskList); } else if (lowerLine.equals("help")) { - displayHelp(); + HerbertUI.displayHelp(); } else if (lowerLine.startsWith("todo") || lowerLine.startsWith("deadline") || lowerLine.startsWith("event")) { addTask(line); } else if (lowerLine.startsWith("delete")) { @@ -97,7 +59,7 @@ public int processLine(String line) { } else if (lowerLine.startsWith("unmark")) { markTask(line, false); } else { - printMessageUnknownCommand(line); + HerbertUI.printMessageUnknownCommand(line); } return 0; @@ -105,61 +67,28 @@ public int processLine(String line) { private void markTask(String line, boolean completed) { // Check for valid user input - if (checkInputTaskIndex(line) == -1) { + if (HerbertParser.checkInputTaskIndex(line) == -1) { return; } // Extract task index and mark the task as completed - int taskIndex = extractTaskIndex(line); + int taskIndex = HerbertParser.extractTaskIndex(line); if (taskIndex == -1) { return; } - int verify = verifyTaskIndex(taskIndex); + int verify = HerbertParser.verifyTaskIndex(taskIndex, this.taskList); if (verify == -1) { return; } - Task task = tasks.get(taskIndex); + Task task = taskList.get(taskIndex); task.setCompleted(completed); // Print result message to user - printMessageMarkTask(task, completed); - } - - private int checkInputTaskIndex(String line) { - if (line.split(" ").length != 2) { - printMessageInvalidInput("Please enter the task number you wish to change the status of."); - return -1; - } - - return 0; - } - - private int extractTaskIndex(String line) { - int taskIndex; - try { - taskIndex = Integer.parseInt(line.split(" ")[1]) - 1; - } catch (NumberFormatException e) { - printMessageInvalidInput("Task index must be a positive integer."); - return -1; - } - - return taskIndex; - } - - private int verifyTaskIndex(int taskIndex) { - if (taskIndex >= tasks.size()) { - printMessageInvalidInput("No such task exists!"); - return -1; - } - if (taskIndex < 0) { - printMessageInvalidInput("Task index must be a positive integer."); - return -1; - } - return 0; + HerbertUI.printMessageMarkTask(task, completed); } private void addTask(String line) { - if (checkInputAddTask(line) == -1) { + if (HerbertParser.checkInputAddTask(line) == -1) { return; } @@ -171,175 +100,75 @@ private void addTask(String line) { // Create and add task Todo td = new Todo(description); - this.tasks.add(td); + this.taskList.add(td); this.reader.addTaskToSaveFile(td); // Print success message - printMessageAddTask(td); + HerbertUI.printMessageAddTask(td, this.taskList); break; } case "deadline": { // Get details - String[] dlDetails = getDeadlineDetails(line); + String[] dlDetails = HerbertParser.getDeadlineDetails(line); if (dlDetails == null) { return; } // Create and add task Deadline dl = new Deadline(dlDetails); - tasks.add(dl); + this.taskList.add(dl); this.reader.addTaskToSaveFile(dl); // Print success message - printMessageAddTask(dl); + HerbertUI.printMessageAddTask(dl, this.taskList); break; } case "event": // Get details - String[] evDetails = getEventDetails(line); + String[] evDetails = HerbertParser.getEventDetails(line); if (evDetails == null) { return; } // Create and add task Event ev = new Event(evDetails); - tasks.add(ev); + this.taskList.add(ev); this.reader.addTaskToSaveFile(ev); // Print success message - printMessageAddTask(ev); + HerbertUI.printMessageAddTask(ev, this.taskList); break; } } + public void addTask(Task t) { + this.taskList.add(t); + } + private void deleteTask(String line) { - if (checkInputTaskIndex(line) == -1) { + if (HerbertParser.checkInputTaskIndex(line) == -1) { return; } - int taskIndex = extractTaskIndex(line); + int taskIndex = HerbertParser.extractTaskIndex(line); if (taskIndex == -1) { return; } - int verify = verifyTaskIndex(taskIndex); + int verify = HerbertParser.verifyTaskIndex(taskIndex, this.taskList); if (verify == -1) { return; } - Task taskCopy = tasks.get(taskIndex); - tasks.remove(taskIndex); - printMessageDeleteTask(taskCopy); - } - - public void addTask(Task t) { - this.tasks.add(t); - } - - private int checkInputAddTask(String line) { - String[] words = line.split(" "); - if (words.length < 2) { - printMessageInvalidInput(); - return -1; - } - return 0; - } - - private String[] getDeadlineDetails(String line) { - String patternString = "^deadline\\s+(.+?)\\s+/by\\s+(.+)$"; - Pattern pattern = Pattern.compile(patternString); - Matcher matcher = pattern.matcher(line); - - if (!matcher.find()) { - printMessageInvalidInput(); - return null; - } - - return new String[] {matcher.group(1), matcher.group(2)}; - } - - private String[] getEventDetails(String line) { - String patternString = "^event\\s+(.+?)\\s+/from\\s+(.+?)\\s+/to\\s+(.+)$"; - Pattern pattern = Pattern.compile(patternString); - Matcher matcher = pattern.matcher(line); - - if (!matcher.find()) { - printMessageInvalidInput(); - return null; - } - - return new String[] {matcher.group(1), matcher.group(2), matcher.group(3)}; - } - - public void listTasks() { - System.out.println("___________________________________________________________________________"); - - if (tasks.size() == 0) { - System.out.println("\tThere are no tasks in your list! Sit back, relax, and enjoy."); - System.out.println("___________________________________________________________________________"); - return; - } - - System.out.println("\tHere are the tasks in your list:"); - for (int i = 0; i < tasks.size(); i++) { - System.out.printf("\t%d. [%s][%s] %s\n", - (i + 1), - tasks.get(i).getCode(), - tasks.get(i).getStatusIcon(), - tasks.get(i) - ); - } - - System.out.println("___________________________________________________________________________"); - } - - //region Message methods - private void printMessageInvalidInput(String errorMessage) { - System.out.println("___________________________________________________________________________"); - System.out.printf("\t%s\n", errorMessage); - System.out.println("\tUse 'help' for usage instructions."); - System.out.println("___________________________________________________________________________"); + Task taskCopy = taskList.get(taskIndex); + this.taskList.remove(taskIndex); + HerbertUI.printMessageDeleteTask(taskCopy, this.taskList); } - private void printMessageInvalidInput() { - System.out.println("___________________________________________________________________________"); - System.out.println("\tInvalid input. Use 'help' for usage instructions."); - System.out.println("___________________________________________________________________________"); - } - private void printMessageUnknownCommand(String userInput) { - System.out.println("___________________________________________________________________________"); - System.out.printf("\tUnknown command '%s'. Use 'help' for a full list of user commands.\n", userInput); - System.out.println("___________________________________________________________________________"); - } - private void printMessageMarkTask(Task task, boolean completed) { - System.out.println("___________________________________________________________________________"); - if (completed) { - System.out.println("\tLovely! I've marked this task as done:"); - } else { - System.out.println("\tOkay, I've unmarked this task for you:"); - } - System.out.println("\t\t[" + task.getStatusIcon() + "] " + task.getDescription()); - System.out.println("___________________________________________________________________________"); - } - private void printMessageAddTask(Task t) { - System.out.println("___________________________________________________________________________"); - System.out.println("\tOkay, I've added this to your task list:"); - System.out.printf("\t\t[%s][%s] %s\n", t.getCode(), t.getStatusIcon(), t); - System.out.printf("\tNow you have %d task(s) in your list.\n", tasks.size()); - System.out.println("___________________________________________________________________________"); - } - private void printMessageDeleteTask(Task t) { - System.out.println("___________________________________________________________________________"); - System.out.println("\tNoted. I've removed this task from your list:"); - System.out.printf("\t\t[%s][%s] %s\n", t.getCode(), t.getStatusIcon(), t); - System.out.printf("\tNow you have %d task(s) in your list.\n", tasks.size()); - System.out.println("___________________________________________________________________________"); - } - //endregion } diff --git a/src/main/java/duke/HerbertParser.java b/src/main/java/duke/HerbertParser.java new file mode 100644 index 000000000..00cf90c15 --- /dev/null +++ b/src/main/java/duke/HerbertParser.java @@ -0,0 +1,77 @@ +package duke; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class HerbertParser { + + public static int checkInputTaskIndex(String line) { + if (line.split(" ").length != 2) { + HerbertUI.printMessageInvalidInput( + "Please enter the task number you wish to change the status of." + ); + return -1; + } + + return 0; + } + + public static int extractTaskIndex(String line) { + int taskIndex; + try { + taskIndex = Integer.parseInt(line.split(" ")[1]) - 1; + } catch (NumberFormatException e) { + HerbertUI.printMessageInvalidInput("Task index must be a positive integer."); + return -1; + } + + return taskIndex; + } + + public static int verifyTaskIndex(int taskIndex, TaskList taskList) { + if (taskIndex >= taskList.size()) { + HerbertUI.printMessageInvalidInput("No such task exists!"); + return -1; + } + if (taskIndex < 0) { + HerbertUI.printMessageInvalidInput("Task index must be a positive integer."); + return -1; + } + return 0; + } + + public static int checkInputAddTask(String line) { + String[] words = line.split(" "); + if (words.length < 2) { + HerbertUI.printMessageInvalidInput(); + return -1; + } + return 0; + } + + public static String[] getDeadlineDetails(String line) { + String patternString = "^deadline\\s+(.+?)\\s+/by\\s+(.+)$"; + Pattern pattern = Pattern.compile(patternString); + Matcher matcher = pattern.matcher(line); + + if (!matcher.find()) { + HerbertUI.printMessageInvalidInput(); + return null; + } + + return new String[] {matcher.group(1), matcher.group(2)}; + } + + public static String[] getEventDetails(String line) { + String patternString = "^event\\s+(.+?)\\s+/from\\s+(.+?)\\s+/to\\s+(.+)$"; + Pattern pattern = Pattern.compile(patternString); + Matcher matcher = pattern.matcher(line); + + if (!matcher.find()) { + HerbertUI.printMessageInvalidInput(); + return null; + } + + return new String[] {matcher.group(1), matcher.group(2), matcher.group(3)}; + } +} diff --git a/src/main/java/duke/HerbertReader.java b/src/main/java/duke/HerbertReader.java index 4182d99cd..b7f89f860 100644 --- a/src/main/java/duke/HerbertReader.java +++ b/src/main/java/duke/HerbertReader.java @@ -13,7 +13,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.sql.SQLOutput; public class HerbertReader { @@ -106,4 +105,6 @@ public void addTaskToSaveFile(Task t) { e.printStackTrace(); } } + + // TODO: Update task status in save file } diff --git a/src/main/java/duke/HerbertUI.java b/src/main/java/duke/HerbertUI.java new file mode 100644 index 000000000..315cc42f3 --- /dev/null +++ b/src/main/java/duke/HerbertUI.java @@ -0,0 +1,124 @@ +package duke; + +import task.Task; + +public abstract class HerbertUI { + + private static void println() { + System.out.println("___________________________________________________________________________"); + } + + private static void newln() { + System.out.print(System.lineSeparator()); + } + + public static void sayHello() { + String logo = " __ __ _______ ______ _______ _______ ______ _______ \n" + + "| | | || || _ | | _ || || _ | | |\n" + + "| |_| || ___|| | || | |_| || ___|| | || |_ _|\n" + + "| || |___ | |_||_ | || |___ | |_||_ | | \n" + + "| || ___|| __ || _ | | ___|| __ | | | \n" + + "| _ || |___ | | | || |_| || |___ | | | | | | \n" + + "|__| |__||_______||___| |_||_______||_______||___| |_| |___| "; + + System.out.println(logo); + println(); + System.out.println("\tHello! I'm Herbert."); + System.out.println("\tWhat can I do for you?"); + newln(); + System.out.println("\tYou may choose from the following commands:"); + for (Command s : Command.values()) { + System.out.println("\t- " + s.name().toLowerCase()); + } + println(); + } + + public static void sayGoodbye() { + println(); + System.out.println("\tBye. Hope to see you again soon!"); + println(); + } + + public static void listTasks(TaskList taskList) { + println(); + + if (taskList.size() == 0) { + System.out.println("\tThere are no tasks in your list! Sit back, relax, and enjoy."); + println(); + return; + } + + System.out.println("\tHere are the tasks in your list:"); + for (int i = 0; i < taskList.size(); i++) { + System.out.printf("\t%d. [%s][%s] %s\n", + (i + 1), + taskList.get(i).getCode(), + taskList.get(i).getStatusIcon(), + taskList.get(i) + ); + } + + println(); + } + + public static void displayHelp() { + System.out.println("###########################################################################"); + System.out.println("\tWelcome to the Herbert Helpline!" + System.lineSeparator()); + System.out.println("\tCOMMANDS"); + for (Command s : Command.values()) { + System.out.println("\t* " + s.name().toLowerCase()); + System.out.println(s); + System.out.println(); + } + System.out.println("###########################################################################"); + } + + //region Message methods + public static void printMessageInvalidInput(String errorMessage) { + println(); + System.out.printf("\t%s\n", errorMessage); + System.out.println("\tUse 'help' for usage instructions."); + println(); + } + + public static void printMessageInvalidInput() { + println(); + System.out.println("\tInvalid input. Use 'help' for usage instructions."); + println(); + } + + public static void printMessageUnknownCommand(String userInput) { + println(); + System.out.printf("\tUnknown command '%s'. Use 'help' for a full list of user commands.\n", userInput); + println(); + } + + public static void printMessageMarkTask(Task task, boolean completed) { + println(); + if (completed) { + System.out.println("\tLovely! I've marked this task as done:"); + } else { + System.out.println("\tOkay, I've unmarked this task for you:"); + } + System.out.println("\t\t[" + task.getStatusIcon() + "] " + task.getDescription()); + println(); + } + + public static void printMessageAddTask(Task t, TaskList taskList) { + println(); + System.out.println("\tOkay, I've added this to your task list:"); + System.out.printf("\t\t[%s][%s] %s\n", t.getCode(), t.getStatusIcon(), t); + System.out.printf("\tNow you have %d task(s) in your list.\n", taskList.size()); + println(); + } + + public static void printMessageDeleteTask(Task t, TaskList taskList) { + println(); + System.out.println("\tNoted. I've removed this task from your list:"); + System.out.printf("\t\t[%s][%s] %s\n", t.getCode(), t.getStatusIcon(), t); + System.out.printf("\tNow you have %d task(s) in your list.\n", taskList.size()); + println(); + } + //endregion + +} diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java new file mode 100644 index 000000000..7151ecf96 --- /dev/null +++ b/src/main/java/duke/TaskList.java @@ -0,0 +1,31 @@ +package duke; + +import task.Task; + +import java.util.ArrayList; + +public class TaskList { + + private final ArrayList tasks; + + public TaskList() { + this.tasks = new ArrayList<>(); + } + + public void add(Task t) { + this.tasks.add(t); + } + + public void remove(int index) { + this.tasks.remove(index); + } + + public Task get(int index) { + return this.tasks.get(index); + } + + public int size() { + return this.tasks.size(); + } + +} From cf187a12d7d7596dd9fc307e27d978b0a03514bb Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 4 Oct 2023 13:45:28 +0800 Subject: [PATCH 15/24] Rename and refactor packages --- src/main/java/META-INF/MANIFEST.MF | 2 +- src/main/java/{duke => }/Main.java | 2 +- src/main/java/{duke => herbert}/Command.java | 2 +- src/main/java/{duke => herbert}/DukeException.java | 2 +- src/main/java/{duke => herbert}/Herbert.java | 12 +++--------- src/main/java/{duke => herbert}/HerbertParser.java | 2 +- .../HerbertReader.java => herbert/HerbertSaver.java} | 6 +++--- src/main/java/{duke => herbert}/HerbertUI.java | 2 +- src/main/java/{duke => herbert}/TaskList.java | 2 +- 9 files changed, 13 insertions(+), 19 deletions(-) rename src/main/java/{duke => }/Main.java (85%) rename src/main/java/{duke => herbert}/Command.java (99%) rename src/main/java/{duke => herbert}/DukeException.java (88%) rename src/main/java/{duke => herbert}/Herbert.java (97%) rename src/main/java/{duke => herbert}/HerbertParser.java (99%) rename src/main/java/{duke/HerbertReader.java => herbert/HerbertSaver.java} (96%) rename src/main/java/{duke => herbert}/HerbertUI.java (99%) rename src/main/java/{duke => herbert}/TaskList.java (96%) diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF index 97011d339..37197ef4e 100644 --- a/src/main/java/META-INF/MANIFEST.MF +++ b/src/main/java/META-INF/MANIFEST.MF @@ -1,3 +1,3 @@ Manifest-Version: 1.0 -Main-Class: duke.Main +Main-Class: Main diff --git a/src/main/java/duke/Main.java b/src/main/java/Main.java similarity index 85% rename from src/main/java/duke/Main.java rename to src/main/java/Main.java index 3b4bfae11..f3feb215b 100644 --- a/src/main/java/duke/Main.java +++ b/src/main/java/Main.java @@ -1,4 +1,4 @@ -package duke; +import herbert.Herbert; public class Main { diff --git a/src/main/java/duke/Command.java b/src/main/java/herbert/Command.java similarity index 99% rename from src/main/java/duke/Command.java rename to src/main/java/herbert/Command.java index d746ff3bc..6b8e787e8 100644 --- a/src/main/java/duke/Command.java +++ b/src/main/java/herbert/Command.java @@ -1,4 +1,4 @@ -package duke; +package herbert; public enum Command { LIST { diff --git a/src/main/java/duke/DukeException.java b/src/main/java/herbert/DukeException.java similarity index 88% rename from src/main/java/duke/DukeException.java rename to src/main/java/herbert/DukeException.java index 3c1763dfd..a8c3ad31f 100644 --- a/src/main/java/duke/DukeException.java +++ b/src/main/java/herbert/DukeException.java @@ -1,4 +1,4 @@ -package duke; +package herbert; public class DukeException extends Exception { public DukeException(String errorMessage) { diff --git a/src/main/java/duke/Herbert.java b/src/main/java/herbert/Herbert.java similarity index 97% rename from src/main/java/duke/Herbert.java rename to src/main/java/herbert/Herbert.java index 933a2e411..8b8ffc77c 100644 --- a/src/main/java/duke/Herbert.java +++ b/src/main/java/herbert/Herbert.java @@ -1,4 +1,4 @@ -package duke; +package herbert; import task.*; @@ -6,13 +6,13 @@ public class Herbert { - private final HerbertReader reader; + private final HerbertSaver reader; private final TaskList taskList; public Herbert() { this.taskList = new TaskList(); - this.reader = new HerbertReader("data", "HerbertTasks.txt"); + this.reader = new HerbertSaver("data", "HerbertTasks.txt"); this.reader.loadFromSaveFile(this); HerbertUI.sayHello(); @@ -33,7 +33,6 @@ public void run() { } } - public int processLine(String line) { line = line.strip(); if (line.isEmpty()) { @@ -165,11 +164,6 @@ private void deleteTask(String line) { HerbertUI.printMessageDeleteTask(taskCopy, this.taskList); } - - - - - } diff --git a/src/main/java/duke/HerbertParser.java b/src/main/java/herbert/HerbertParser.java similarity index 99% rename from src/main/java/duke/HerbertParser.java rename to src/main/java/herbert/HerbertParser.java index 00cf90c15..b0c5033d5 100644 --- a/src/main/java/duke/HerbertParser.java +++ b/src/main/java/herbert/HerbertParser.java @@ -1,4 +1,4 @@ -package duke; +package herbert; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/duke/HerbertReader.java b/src/main/java/herbert/HerbertSaver.java similarity index 96% rename from src/main/java/duke/HerbertReader.java rename to src/main/java/herbert/HerbertSaver.java index b7f89f860..2a4e4ff98 100644 --- a/src/main/java/duke/HerbertReader.java +++ b/src/main/java/herbert/HerbertSaver.java @@ -1,4 +1,4 @@ -package duke; +package herbert; import task.Deadline; import task.Event; @@ -14,12 +14,12 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -public class HerbertReader { +public class HerbertSaver { private final Path folderPath; private final Path filePath; - public HerbertReader(String folderPath, String fileName) { + public HerbertSaver(String folderPath, String fileName) { // Convert the paths given as Strings into Path objects this.folderPath = Paths.get(folderPath); this.filePath = this.folderPath.resolve(fileName); diff --git a/src/main/java/duke/HerbertUI.java b/src/main/java/herbert/HerbertUI.java similarity index 99% rename from src/main/java/duke/HerbertUI.java rename to src/main/java/herbert/HerbertUI.java index 315cc42f3..8e354458e 100644 --- a/src/main/java/duke/HerbertUI.java +++ b/src/main/java/herbert/HerbertUI.java @@ -1,4 +1,4 @@ -package duke; +package herbert; import task.Task; diff --git a/src/main/java/duke/TaskList.java b/src/main/java/herbert/TaskList.java similarity index 96% rename from src/main/java/duke/TaskList.java rename to src/main/java/herbert/TaskList.java index 7151ecf96..1406d3dff 100644 --- a/src/main/java/duke/TaskList.java +++ b/src/main/java/herbert/TaskList.java @@ -1,4 +1,4 @@ -package duke; +package herbert; import task.Task; From 5abc26c81ef38c05d28bc0e3ef994bca92223467 Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 4 Oct 2023 22:38:28 +0800 Subject: [PATCH 16/24] Add JavaDoc comments --- src/main/java/herbert/Command.java | 5 +++ src/main/java/herbert/Herbert.java | 34 +++++++++++++++++++ src/main/java/herbert/HerbertParser.java | 36 ++++++++++++++++++++ src/main/java/herbert/HerbertSaver.java | 19 ++++++++++- src/main/java/herbert/HerbertUI.java | 43 ++++++++++++++++++++++++ src/main/java/herbert/TaskList.java | 23 +++++++++++++ src/main/java/task/Deadline.java | 3 ++ src/main/java/task/Event.java | 3 ++ src/main/java/task/Todo.java | 3 ++ 9 files changed, 168 insertions(+), 1 deletion(-) diff --git a/src/main/java/herbert/Command.java b/src/main/java/herbert/Command.java index 6b8e787e8..bb38ec547 100644 --- a/src/main/java/herbert/Command.java +++ b/src/main/java/herbert/Command.java @@ -1,5 +1,10 @@ package herbert; +/** + * Stores all legal Herbert commands, mostly for documentation purposes. + * The toString() method has been overridden with descriptive text for each command. This is printed when the + * `help` command is input by the user into the chatbot. + */ public enum Command { LIST { @Override diff --git a/src/main/java/herbert/Herbert.java b/src/main/java/herbert/Herbert.java index 8b8ffc77c..4ce0377e3 100644 --- a/src/main/java/herbert/Herbert.java +++ b/src/main/java/herbert/Herbert.java @@ -4,11 +4,19 @@ import java.util.Scanner; +/** + * The main chatbot class which contains methods to add, delete, update, and load tasks from file. + */ public class Herbert { private final HerbertSaver reader; private final TaskList taskList; + /** + * Constructor for the chatbot class. + * Automatically attempts to find a save file on disk at "./data/HerbertTasks.txt" and parse all tasks from it. + * Prints welcome message on success. + */ public Herbert() { this.taskList = new TaskList(); @@ -18,6 +26,9 @@ public Herbert() { HerbertUI.sayHello(); } + /** + * Main chatbot loop which constantly reads in new input from the user until the user inputs "bye". + */ public void run() { Scanner scan = new Scanner(System.in); String line; @@ -33,6 +44,11 @@ public void run() { } } + /** + * Parses each line of user input. + * @param line The raw input string from the user. + * @return -1 if no input is entered, 1 if user enters "bye", and 0 otherwise. + */ public int processLine(String line) { line = line.strip(); if (line.isEmpty()) { @@ -64,6 +80,11 @@ public int processLine(String line) { return 0; } + /** + * Marks a task as either complete or incomplete depending on given user input. + * @param line The raw input string from the user. + * @param completed Describes whether to mark the task as complete or incomplete. + */ private void markTask(String line, boolean completed) { // Check for valid user input if (HerbertParser.checkInputTaskIndex(line) == -1) { @@ -86,6 +107,11 @@ private void markTask(String line, boolean completed) { HerbertUI.printMessageMarkTask(task, completed); } + /** + * Parses user input to decode either an event, deadline or todo, and then adds the task to the Herbert TaskList. + * Also saves the new task to file. + * @param line The raw input string from the user. + */ private void addTask(String line) { if (HerbertParser.checkInputAddTask(line) == -1) { return; @@ -141,10 +167,18 @@ private void addTask(String line) { } } + /** + * Adds a given task to the Herbert TaskList. + * @param t The task to add to the TaskList. + */ public void addTask(Task t) { this.taskList.add(t); } + /** + * Parses user input to delete a specific task from the Herbert TaskList using the task index. + * @param line The raw input string from the user. + */ private void deleteTask(String line) { if (HerbertParser.checkInputTaskIndex(line) == -1) { return; diff --git a/src/main/java/herbert/HerbertParser.java b/src/main/java/herbert/HerbertParser.java index b0c5033d5..bc4770691 100644 --- a/src/main/java/herbert/HerbertParser.java +++ b/src/main/java/herbert/HerbertParser.java @@ -3,8 +3,17 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +/** + * Contains all functionality for parsing and validating user input. + */ public abstract class HerbertParser { + /** + * Validates user input for commands which require exactly 1 extra argument to be input. + * Used for commands such as `delete X` where X is a task index. + * @param line The raw input string from the user. + * @return 0 on success, -1 on an incorrect number of arguments input by the user. + */ public static int checkInputTaskIndex(String line) { if (line.split(" ").length != 2) { HerbertUI.printMessageInvalidInput( @@ -16,6 +25,11 @@ public static int checkInputTaskIndex(String line) { return 0; } + /** + * Extracts the integer task index from raw user input. + * @param line The raw input string from the user. + * @return The 0-indexed integer task index on success, -1 if the task index is not numeric. + */ public static int extractTaskIndex(String line) { int taskIndex; try { @@ -28,6 +42,12 @@ public static int extractTaskIndex(String line) { return taskIndex; } + /** + * Verifies that a certain task index is valid against a given TaskList. + * @param taskIndex The index of the task to validate. + * @param taskList The Herbert TaskList to validate the index against. + * @return 0 on success, -1 on invalid input. + */ public static int verifyTaskIndex(int taskIndex, TaskList taskList) { if (taskIndex >= taskList.size()) { HerbertUI.printMessageInvalidInput("No such task exists!"); @@ -40,6 +60,12 @@ public static int verifyTaskIndex(int taskIndex, TaskList taskList) { return 0; } + /** + * Validates user input upon adding a new task to Herbert. + * The method checks whether at least 1 more argument has been supplied by the user. + * @param line The raw input string from the user. + * @return 0 on success, -1 on invalid input. + */ public static int checkInputAddTask(String line) { String[] words = line.split(" "); if (words.length < 2) { @@ -49,6 +75,11 @@ public static int checkInputAddTask(String line) { return 0; } + /** + * Runs a regex pattern matcher to extract details from user input related to a new deadline task. + * @param line The raw input string from the user. + * @return A string array of the form [description, due date]. + */ public static String[] getDeadlineDetails(String line) { String patternString = "^deadline\\s+(.+?)\\s+/by\\s+(.+)$"; Pattern pattern = Pattern.compile(patternString); @@ -62,6 +93,11 @@ public static String[] getDeadlineDetails(String line) { return new String[] {matcher.group(1), matcher.group(2)}; } + /** + * Runs a regex pattern matcher to extract details from user input related to a new event task. + * @param line The raw input string from the user. + * @return A string array of the form [description, from, to]. + */ public static String[] getEventDetails(String line) { String patternString = "^event\\s+(.+?)\\s+/from\\s+(.+?)\\s+/to\\s+(.+)$"; Pattern pattern = Pattern.compile(patternString); diff --git a/src/main/java/herbert/HerbertSaver.java b/src/main/java/herbert/HerbertSaver.java index 2a4e4ff98..5e3a45f4a 100644 --- a/src/main/java/herbert/HerbertSaver.java +++ b/src/main/java/herbert/HerbertSaver.java @@ -14,11 +14,20 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +/** + * This class deals with the local storage of the Herbert TaskList to disk. + */ public class HerbertSaver { private final Path folderPath; private final Path filePath; + /** + * Constructor for the HerbertSaver class. Automatically creates a save file located at the given folder path + * and file name if it does not already exist. + * @param folderPath Path to the folder which the save file is located in. + * @param fileName The name of the save file e.g. "HerbertTasks.txt" + */ public HerbertSaver(String folderPath, String fileName) { // Convert the paths given as Strings into Path objects this.folderPath = Paths.get(folderPath); @@ -43,8 +52,11 @@ private void createSaveFileIfNotExists(Path folderPath, Path filePath) { } } + /** + * Reads and parses the save file line-by-line to re-populate the chatbot TaskList with previously saved tasks. + * @param herbert The current instance of the chatbot to add saved tasks to. + */ public void loadFromSaveFile(Herbert herbert) { - try { BufferedReader reader = Files.newBufferedReader(filePath); String line = reader.readLine(); @@ -77,6 +89,10 @@ public void loadFromSaveFile(Herbert herbert) { } } + /** + * Adds a new task to the save file in a standard encoding. + * @param t The task to be encoded into the save file. + */ public void addTaskToSaveFile(Task t) { try { BufferedWriter writer = Files.newBufferedWriter( @@ -106,5 +122,6 @@ public void addTaskToSaveFile(Task t) { } } + // TODO: Remove task from save file // TODO: Update task status in save file } diff --git a/src/main/java/herbert/HerbertUI.java b/src/main/java/herbert/HerbertUI.java index 8e354458e..52ce75c05 100644 --- a/src/main/java/herbert/HerbertUI.java +++ b/src/main/java/herbert/HerbertUI.java @@ -2,6 +2,9 @@ import task.Task; +/** + * Contains all methods relating to printing messages to the user through the CLI. + */ public abstract class HerbertUI { private static void println() { @@ -12,6 +15,9 @@ private static void newln() { System.out.print(System.lineSeparator()); } + /** + * Prints out the Herbert logo and presents all possible commands to the user. + */ public static void sayHello() { String logo = " __ __ _______ ______ _______ _______ ______ _______ \n" + "| | | || || _ | | _ || || _ | | |\n" @@ -33,12 +39,19 @@ public static void sayHello() { println(); } + /** + * Prints a goodbye message to the user. + */ public static void sayGoodbye() { println(); System.out.println("\tBye. Hope to see you again soon!"); println(); } + /** + * Pretty-prints a list of tasks given an instance of a TaskList. + * @param taskList The TaskList to be pretty-printed. + */ public static void listTasks(TaskList taskList) { println(); @@ -61,6 +74,9 @@ public static void listTasks(TaskList taskList) { println(); } + /** + * Prints out all Herbert commands available to the user as well as their usage. + */ public static void displayHelp() { System.out.println("###########################################################################"); System.out.println("\tWelcome to the Herbert Helpline!" + System.lineSeparator()); @@ -74,6 +90,11 @@ public static void displayHelp() { } //region Message methods + + /** + * Prints out a specific error message to the user. + * @param errorMessage The error message to be displayed to the user. + */ public static void printMessageInvalidInput(String errorMessage) { println(); System.out.printf("\t%s\n", errorMessage); @@ -81,18 +102,30 @@ public static void printMessageInvalidInput(String errorMessage) { println(); } + /** + * Prints out a generic error message to the user. + */ public static void printMessageInvalidInput() { println(); System.out.println("\tInvalid input. Use 'help' for usage instructions."); println(); } + /** + * Prints out an error message when the user inputs a command which was not successfully parsed. + * @param userInput The raw input string from the user. + */ public static void printMessageUnknownCommand(String userInput) { println(); System.out.printf("\tUnknown command '%s'. Use 'help' for a full list of user commands.\n", userInput); println(); } + /** + * Prints out a success message upon marking a task as complete or incomplete. + * @param task The newly-updated task. + * @param completed Whether the task was marked as complete or incomplete. + */ public static void printMessageMarkTask(Task task, boolean completed) { println(); if (completed) { @@ -104,6 +137,11 @@ public static void printMessageMarkTask(Task task, boolean completed) { println(); } + /** + * Prints out a success message when a new task is added to the TaskList. + * @param t The newly added task. + * @param taskList The TaskList instance the task was added to. + */ public static void printMessageAddTask(Task t, TaskList taskList) { println(); System.out.println("\tOkay, I've added this to your task list:"); @@ -112,6 +150,11 @@ public static void printMessageAddTask(Task t, TaskList taskList) { println(); } + /** + * Prints out a success message when a new task is removed from the TaskList. + * @param t The removed task. + * @param taskList The TaskList instance the task was removed from. + */ public static void printMessageDeleteTask(Task t, TaskList taskList) { println(); System.out.println("\tNoted. I've removed this task from your list:"); diff --git a/src/main/java/herbert/TaskList.java b/src/main/java/herbert/TaskList.java index 1406d3dff..d77babb1c 100644 --- a/src/main/java/herbert/TaskList.java +++ b/src/main/java/herbert/TaskList.java @@ -4,26 +4,49 @@ import java.util.ArrayList; +/** + * This class handles the in-program storage, retrieval, deletion, and addition of Herbert tasks. + */ public class TaskList { private final ArrayList tasks; + /** + * Constructor for the TaskList class. + */ public TaskList() { this.tasks = new ArrayList<>(); } + /** + * Adds a given task to the TaskList. + * @param t The new task to be added to the TaskList. + */ public void add(Task t) { this.tasks.add(t); } + /** + * Removes the task at the specified task index from the TaskList. + * @param index The (0-indexed) index of the task to be removed. + */ public void remove(int index) { this.tasks.remove(index); } + /** + * Retrieves a task at a given index from the TaskList. + * @param index The (0-indexed) index of the task to be retrieved. + * @return The Task object retrieved from the TaskList. + */ public Task get(int index) { return this.tasks.get(index); } + /** + * Retrieves the number of Tasks stored in the TaskList. + * @return The number of tasks stored in the TaskList. + */ public int size() { return this.tasks.size(); } diff --git a/src/main/java/task/Deadline.java b/src/main/java/task/Deadline.java index 5bc3709c3..5c135f553 100644 --- a/src/main/java/task/Deadline.java +++ b/src/main/java/task/Deadline.java @@ -1,5 +1,8 @@ package task; +/** + * A Deadline is a Task which has both a description and a due date. + */ public class Deadline extends Task { protected String dueDate; diff --git a/src/main/java/task/Event.java b/src/main/java/task/Event.java index 741c9b113..cc57c506c 100644 --- a/src/main/java/task/Event.java +++ b/src/main/java/task/Event.java @@ -1,5 +1,8 @@ package task; +/** + * An Event is a Task which has a description, a start time (from), and an end time (to). + */ public class Event extends Task { protected String from; diff --git a/src/main/java/task/Todo.java b/src/main/java/task/Todo.java index e636b6c42..44342c12c 100644 --- a/src/main/java/task/Todo.java +++ b/src/main/java/task/Todo.java @@ -1,5 +1,8 @@ package task; +/** + * The most basic of all 3 sub-classes of Task, a Todo is a Task which only has a description. + */ public class Todo extends Task { public Todo(String description) { From 70bf7fe40b4db3e3553c14478408977cf3d47c95 Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Wed, 4 Oct 2023 23:53:45 +0800 Subject: [PATCH 17/24] Add dates to deadlines and events --- src/main/java/herbert/Herbert.java | 18 ++++++++++++++++-- src/main/java/herbert/HerbertParser.java | 11 +++++++++++ src/main/java/herbert/HerbertSaver.java | 19 +++++++++++++++++-- src/main/java/task/Deadline.java | 16 +++++----------- src/main/java/task/Event.java | 22 +++++++--------------- 5 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/main/java/herbert/Herbert.java b/src/main/java/herbert/Herbert.java index 4ce0377e3..d04beef84 100644 --- a/src/main/java/herbert/Herbert.java +++ b/src/main/java/herbert/Herbert.java @@ -2,6 +2,7 @@ import task.*; +import java.time.LocalDate; import java.util.Scanner; /** @@ -140,8 +141,14 @@ private void addTask(String line) { return; } + String description = dlDetails[0]; + LocalDate dueDate = HerbertParser.parseDate(dlDetails[1]); + if (dueDate == null) { + return; + } + // Create and add task - Deadline dl = new Deadline(dlDetails); + Deadline dl = new Deadline(description, dueDate); this.taskList.add(dl); this.reader.addTaskToSaveFile(dl); @@ -156,8 +163,15 @@ private void addTask(String line) { return; } + String description = evDetails[0]; + LocalDate fromDate = HerbertParser.parseDate(evDetails[1]); + LocalDate toDate = HerbertParser.parseDate(evDetails[2]); + if (fromDate == null || toDate == null) { + return; + } + // Create and add task - Event ev = new Event(evDetails); + Event ev = new Event(description, fromDate, toDate); this.taskList.add(ev); this.reader.addTaskToSaveFile(ev); diff --git a/src/main/java/herbert/HerbertParser.java b/src/main/java/herbert/HerbertParser.java index bc4770691..0e021ad76 100644 --- a/src/main/java/herbert/HerbertParser.java +++ b/src/main/java/herbert/HerbertParser.java @@ -1,7 +1,9 @@ package herbert; +import java.time.format.DateTimeParseException; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.time.LocalDate; /** * Contains all functionality for parsing and validating user input. @@ -93,6 +95,15 @@ public static String[] getDeadlineDetails(String line) { return new String[] {matcher.group(1), matcher.group(2)}; } + public static LocalDate parseDate(String dateString) { + try { + return LocalDate.parse(dateString); + } catch (DateTimeParseException e) { + HerbertUI.printMessageInvalidInput("Please enter a date in the format YYYY-MM-DD"); + return null; + } + } + /** * Runs a regex pattern matcher to extract details from user input related to a new event task. * @param line The raw input string from the user. diff --git a/src/main/java/herbert/HerbertSaver.java b/src/main/java/herbert/HerbertSaver.java index 5e3a45f4a..5ba7406f4 100644 --- a/src/main/java/herbert/HerbertSaver.java +++ b/src/main/java/herbert/HerbertSaver.java @@ -13,6 +13,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.time.LocalDate; /** * This class deals with the local storage of the Herbert TaskList to disk. @@ -64,6 +65,7 @@ public void loadFromSaveFile(Herbert herbert) { while (line != null) { String[] split = line.split(" \\| "); + String description; switch (split[0]) { case "T": Todo t = new Todo(split[2]); @@ -71,12 +73,25 @@ public void loadFromSaveFile(Herbert herbert) { herbert.addTask(t); break; case "D": - Deadline d = new Deadline(split[2], split[3]); + description = split[2]; + LocalDate dueDate = HerbertParser.parseDate(split[3]); + if (dueDate == null) { + return; + } + + Deadline d = new Deadline(description, dueDate); d.setCompleted(split[1].equals("1")); herbert.addTask(d); break; case "E": - Event e = new Event(split[2], split[3], split[4]); + description = split[2]; + LocalDate fromDate = HerbertParser.parseDate(split[3]); + LocalDate toDate = HerbertParser.parseDate(split[4]); + if (fromDate == null || toDate == null) { + return; + } + + Event e = new Event(description, fromDate, toDate); e.setCompleted(split[1].equals("1")); herbert.addTask(e); break; diff --git a/src/main/java/task/Deadline.java b/src/main/java/task/Deadline.java index 5c135f553..fb9203604 100644 --- a/src/main/java/task/Deadline.java +++ b/src/main/java/task/Deadline.java @@ -1,25 +1,19 @@ package task; +import java.time.LocalDate; + /** * A Deadline is a Task which has both a description and a due date. */ public class Deadline extends Task { - protected String dueDate; + protected LocalDate dueDate; - public Deadline(String description, String dueDate) { + public Deadline(String description, LocalDate dueDate) { super(description); this.dueDate = dueDate; } - // String[] in the format: - // details[0] = description - // details[1] = due date - public Deadline(String[] details) { - super(details[0]); - this.dueDate = details[1]; - } - public String getCode() { return "D"; } @@ -29,7 +23,7 @@ public String toString() { return super.toString() + " (by: " + this.dueDate + ")"; } - public String getDueDate() { + public LocalDate getDueDate() { return this.dueDate; } } diff --git a/src/main/java/task/Event.java b/src/main/java/task/Event.java index cc57c506c..8404d18cb 100644 --- a/src/main/java/task/Event.java +++ b/src/main/java/task/Event.java @@ -1,29 +1,21 @@ package task; +import java.time.LocalDate; + /** * An Event is a Task which has a description, a start time (from), and an end time (to). */ public class Event extends Task { - protected String from; - protected String to; + protected LocalDate from; + protected LocalDate to; - public Event(String description, String from, String to) { + public Event(String description, LocalDate from, LocalDate to) { super(description); this.from = from; this.to = to; } - // String[] in the format: - // details[0] = description - // details[1] = from - // details[2] = to - public Event(String[] details) { - super(details[0]); - this.from = details[1]; - this.to = details[2]; - } - public String getCode() { return "E"; } @@ -33,11 +25,11 @@ public String toString() { return super.toString() + " (from: " + this.from + " to: " + this.to + ")"; } - public String getFrom() { + public LocalDate getFrom() { return this.from; } - public String getTo() { + public LocalDate getTo() { return this.to; } } From 4e07a60c0d79c381f2108596f46a225f2f009871 Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Thu, 5 Oct 2023 00:18:56 +0800 Subject: [PATCH 18/24] Add search functionality for tasks --- src/main/java/herbert/Herbert.java | 22 ++++++++++++++++++++- src/main/java/herbert/HerbertParser.java | 7 ++++++- src/main/java/herbert/HerbertUI.java | 25 ++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/main/java/herbert/Herbert.java b/src/main/java/herbert/Herbert.java index 4ce0377e3..2fff15c2e 100644 --- a/src/main/java/herbert/Herbert.java +++ b/src/main/java/herbert/Herbert.java @@ -2,6 +2,7 @@ import task.*; +import java.util.ArrayList; import java.util.Scanner; /** @@ -73,6 +74,8 @@ public int processLine(String line) { markTask(line, true); } else if (lowerLine.startsWith("unmark")) { markTask(line, false); + } else if (lowerLine.startsWith("find")) { + findTask(line); } else { HerbertUI.printMessageUnknownCommand(line); } @@ -80,6 +83,23 @@ public int processLine(String line) { return 0; } + private void findTask(String line) { + if (HerbertParser.checkInputTwoOrMoreArgs(line) == -1) { + return; + } + + String searchQuery = HerbertParser.getSearchQuery(line); + + TaskList searchResults = new TaskList(); + for (int i = 0; i < this.taskList.size(); i++) { + Task task = this.taskList.get(i); + if (task.getDescription().contains(searchQuery)) { + searchResults.add(task); + } + } + HerbertUI.printMessageSearchResults(searchResults); + } + /** * Marks a task as either complete or incomplete depending on given user input. * @param line The raw input string from the user. @@ -113,7 +133,7 @@ private void markTask(String line, boolean completed) { * @param line The raw input string from the user. */ private void addTask(String line) { - if (HerbertParser.checkInputAddTask(line) == -1) { + if (HerbertParser.checkInputTwoOrMoreArgs(line) == -1) { return; } diff --git a/src/main/java/herbert/HerbertParser.java b/src/main/java/herbert/HerbertParser.java index bc4770691..9441cdf03 100644 --- a/src/main/java/herbert/HerbertParser.java +++ b/src/main/java/herbert/HerbertParser.java @@ -66,7 +66,7 @@ public static int verifyTaskIndex(int taskIndex, TaskList taskList) { * @param line The raw input string from the user. * @return 0 on success, -1 on invalid input. */ - public static int checkInputAddTask(String line) { + public static int checkInputTwoOrMoreArgs(String line) { String[] words = line.split(" "); if (words.length < 2) { HerbertUI.printMessageInvalidInput(); @@ -110,4 +110,9 @@ public static String[] getEventDetails(String line) { return new String[] {matcher.group(1), matcher.group(2), matcher.group(3)}; } + + public static String getSearchQuery(String line) { + String lower = line.toLowerCase(); + return lower.substring("find ".length()); + } } diff --git a/src/main/java/herbert/HerbertUI.java b/src/main/java/herbert/HerbertUI.java index 52ce75c05..3db31cdfe 100644 --- a/src/main/java/herbert/HerbertUI.java +++ b/src/main/java/herbert/HerbertUI.java @@ -2,6 +2,8 @@ import task.Task; +import java.util.ArrayList; + /** * Contains all methods relating to printing messages to the user through the CLI. */ @@ -162,6 +164,29 @@ public static void printMessageDeleteTask(Task t, TaskList taskList) { System.out.printf("\tNow you have %d task(s) in your list.\n", taskList.size()); println(); } + + public static void printMessageSearchResults(TaskList searchResults) { + + println(); + + if (searchResults.size() == 0) { + System.out.println("\tSorry, I could not find any tasks matching that description :("); + println(); + return; + } + + System.out.println("\tHere are the matching tasks in your list:"); + for (int i = 0; i < searchResults.size(); i++) { + System.out.printf("\t%d. [%s][%s] %s\n", + (i + 1), + searchResults.get(i).getCode(), + searchResults.get(i).getStatusIcon(), + searchResults.get(i) + ); + } + + println(); + } //endregion } From 3cafb52126a93f20648d24b75f5a788ea649ca7b Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Thu, 5 Oct 2023 00:24:28 +0800 Subject: [PATCH 19/24] Add "find" to list of commands --- src/main/java/herbert/Command.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/herbert/Command.java b/src/main/java/herbert/Command.java index bb38ec547..f1c82c54a 100644 --- a/src/main/java/herbert/Command.java +++ b/src/main/java/herbert/Command.java @@ -62,6 +62,14 @@ public String toString() { + "\tUsage: event /from /to "; } }, + FIND { + @Override + public String toString() { + return "\tSearch for keywords amongst all tasks in your list." + + System.lineSeparator() + + "\tUsage: find "; + } + }, HELP { @Override public String toString() { From b7a26494f4a39466338303a104d4190043e67448 Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Thu, 5 Oct 2023 00:28:25 +0800 Subject: [PATCH 20/24] Update documentation regarding dates --- src/main/java/herbert/Command.java | 4 ++-- src/main/java/herbert/HerbertParser.java | 2 +- src/main/java/task/Deadline.java | 1 + src/main/java/task/Event.java | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/herbert/Command.java b/src/main/java/herbert/Command.java index bb38ec547..22498cd68 100644 --- a/src/main/java/herbert/Command.java +++ b/src/main/java/herbert/Command.java @@ -49,7 +49,7 @@ public String toString() { DEADLINE { @Override public String toString() { - return "\tAdd a new deadline to your list of tasks." + return "\tAdd a new deadline to your list of tasks. Due date must be specified in YYYY-MM-DD format." + System.lineSeparator() + "\tUsage: deadline /by "; } @@ -57,7 +57,7 @@ public String toString() { EVENT { @Override public String toString() { - return "\tAdd a new event to your list of tasks." + return "\tAdd a new event to your list of tasks. Start and end dates must be specified in YYYY-MM-DD format." + System.lineSeparator() + "\tUsage: event /from /to "; } diff --git a/src/main/java/herbert/HerbertParser.java b/src/main/java/herbert/HerbertParser.java index 0e021ad76..cf28496b8 100644 --- a/src/main/java/herbert/HerbertParser.java +++ b/src/main/java/herbert/HerbertParser.java @@ -99,7 +99,7 @@ public static LocalDate parseDate(String dateString) { try { return LocalDate.parse(dateString); } catch (DateTimeParseException e) { - HerbertUI.printMessageInvalidInput("Please enter a date in the format YYYY-MM-DD"); + HerbertUI.printMessageInvalidInput("Please enter a date in the format YYYY-MM-DD."); return null; } } diff --git a/src/main/java/task/Deadline.java b/src/main/java/task/Deadline.java index fb9203604..8cd643a65 100644 --- a/src/main/java/task/Deadline.java +++ b/src/main/java/task/Deadline.java @@ -4,6 +4,7 @@ /** * A Deadline is a Task which has both a description and a due date. + * The due date is formatted as YYYY-MM-DD. */ public class Deadline extends Task { diff --git a/src/main/java/task/Event.java b/src/main/java/task/Event.java index 8404d18cb..4f0cf86b8 100644 --- a/src/main/java/task/Event.java +++ b/src/main/java/task/Event.java @@ -3,7 +3,8 @@ import java.time.LocalDate; /** - * An Event is a Task which has a description, a start time (from), and an end time (to). + * An Event is a Task which has a description, a start date (from), and an end date (to). + * Dates are formatted as YYYY-MM-DD. */ public class Event extends Task { From a374ae09f864de29d77a29c1bc0324891ccb7b39 Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Thu, 5 Oct 2023 00:49:34 +0800 Subject: [PATCH 21/24] Update Javadoc comments --- src/main/java/herbert/Herbert.java | 6 ++++-- src/main/java/herbert/HerbertParser.java | 10 ++++++++++ src/main/java/herbert/HerbertUI.java | 9 ++++----- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/main/java/herbert/Herbert.java b/src/main/java/herbert/Herbert.java index 94cf8d2cf..53cc073e3 100644 --- a/src/main/java/herbert/Herbert.java +++ b/src/main/java/herbert/Herbert.java @@ -2,8 +2,6 @@ import task.*; - -import java.util.ArrayList; import java.time.LocalDate; import java.util.Scanner; @@ -85,6 +83,10 @@ public int processLine(String line) { return 0; } + /** + * Searches through all current tasks to find matches with the user input. + * @param line The raw input string from the user. + */ private void findTask(String line) { if (HerbertParser.checkInputTwoOrMoreArgs(line) == -1) { return; diff --git a/src/main/java/herbert/HerbertParser.java b/src/main/java/herbert/HerbertParser.java index 6db1004d5..923a2be53 100644 --- a/src/main/java/herbert/HerbertParser.java +++ b/src/main/java/herbert/HerbertParser.java @@ -95,6 +95,11 @@ public static String[] getDeadlineDetails(String line) { return new String[] {matcher.group(1), matcher.group(2)}; } + /** + * Attempts to parse a given string into a LocalDate object. + * @param dateString The date string inputted by the user. + * @return A LocalDate object on success, null on failure. + */ public static LocalDate parseDate(String dateString) { try { return LocalDate.parse(dateString); @@ -122,6 +127,11 @@ public static String[] getEventDetails(String line) { return new String[] {matcher.group(1), matcher.group(2), matcher.group(3)}; } + /** + * Extracts the search query from the user input. + * @param line The raw input string from the user. + * @return The complete string after the keyword 'find'. + */ public static String getSearchQuery(String line) { String lower = line.toLowerCase(); return lower.substring("find ".length()); diff --git a/src/main/java/herbert/HerbertUI.java b/src/main/java/herbert/HerbertUI.java index 3db31cdfe..9dbe0bfd5 100644 --- a/src/main/java/herbert/HerbertUI.java +++ b/src/main/java/herbert/HerbertUI.java @@ -2,8 +2,6 @@ import task.Task; -import java.util.ArrayList; - /** * Contains all methods relating to printing messages to the user through the CLI. */ @@ -165,10 +163,12 @@ public static void printMessageDeleteTask(Task t, TaskList taskList) { println(); } + /** + * Prints out the results of a search to the user. + * @param searchResults The TaskList containing all tasks which matched the user's search inmput. + */ public static void printMessageSearchResults(TaskList searchResults) { - println(); - if (searchResults.size() == 0) { System.out.println("\tSorry, I could not find any tasks matching that description :("); println(); @@ -184,7 +184,6 @@ public static void printMessageSearchResults(TaskList searchResults) { searchResults.get(i) ); } - println(); } //endregion From 565875864458d7c8d6bcce49ecde86f0873502ce Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Thu, 5 Oct 2023 13:54:59 +0800 Subject: [PATCH 22/24] Update User Guide --- docs/README.md | 153 +++++++++++++++++++++++++++++++++++++----- docs/chatbot_home.png | Bin 0 -> 94827 bytes 2 files changed, 137 insertions(+), 16 deletions(-) create mode 100644 docs/chatbot_home.png diff --git a/docs/README.md b/docs/README.md index 8077118eb..9b6da26e9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,150 @@ -# User Guide +# Project Herbert -## Features +``` + __ __ _______ ______ _______ _______ ______ _______ +| | | || || _ | | _ || || _ | | | +| |_| || ___|| | || | |_| || ___|| | || |_ _| +| || |___ | |_||_ | || |___ | |_||_ | | +| || ___|| __ || _ | | ___|| __ | | | +| _ || |___ | | | || |_| || |___ | | | | | | +|__| |__||_______||___| |_||_______||_______||___| |_| |___| +``` -### Feature-ABC +Welcome to Herbert, the newest and friendliest AI assistant on the market. -Description of the feature. +## Features -### Feature-XYZ +Herbert's main purpose is to help you organise your life's never-ending list of todos, deadlines, and events. -Description of the feature. +Through a simple command-line interface, you can add and remove tasks, mark tasks as complete or incomplete, and even search through your existing tasks by keyword! -## Usage +![Herbert's main menu](chatbot_home.png) -### `Keyword` - Describe action +### How Herbert handles tasks -Describe the action and its outcome. +There are three main types of tasks you can input into your chatbot: `Todo`, `Deadline` and `Event`. Of course, all three types must contain a description surrounding what the task entails; the distinction between the three comes with the metadata stored alongside the description. -Example of usage: +#### [T] Todo -`keyword (optional arguments)` +A todo is the simplest type of task you can input into Herbert. All that is stored in a todo is a description of what the task is about. -Expected outcome: +e.g. `[T][ ] Put the bins out`, `[T][X] Wash the dishes` -Description of the outcome. +There is no sense of time associated with the todo: only a sense of whether the task has been completed or not. -``` -expected output -``` +#### [D] Deadline + +A deadline is a todo which has a temporal property associated with it, namely _when_ the task must be completed by. + +e.g. `[D][ ] CS2113 Individual Project (by: 2023-10-06)` + +A deadline must always have a due date associated with it. The due date must be specified by the user in ISO-8601 format: `YYYY-MM-DD`. + +#### [E] Event + +An event is a task which takes places over a period of time. Therefore, associated with each event are _two_ dates to signify the _start_ and _end_ of the task. + +e.g. `[E][ ] CS2113 tP Iteration 1 (from: 2023-10-09 to: 2023-10-19)` + +Both the `from` and `to` dates must be specified by the user in ISO-8601 format: `YYYY-MM-DD`. + +### Local storage of tasks + +Herbert wouldn't be very helpful if he forgot your list of tasks each time you restarted him! But you can rest assured that at any moment, a copy of your tasks is stored to your local hard-drive from which Herbert will automatically repopulate your task list from on startup. + +*See #known-issues for more on this. + +## Commands + +### `list` + +Lists all current tasks stored by Herbert. + +Usage: `list` + +### `mark` + +Mark a task as completed. + +- The task number must be a positive integer [1, 2, ...] + +Usage: `mark ` + +### `unmark` + +Mark a task as incomplete. + +- The task number must be a positive integer [1, 2, ...] + +Usage: `unmark ` + +### `delete` + +Delete a task from the list. + +- The task number must be a positive integer [1, 2, ...] + +Usage: `delete ` + +### `todo` + +Add a new todo to the list of tasks. + +Usage: `todo ` + +### `deadline` + +Add a new deadline to your list of tasks. + +- Due date must be specified in YYYY-MM-DD format. + +Usage: `deadline /by ` + +### `event` + +Add a new event to your list of tasks. + +- Start and end dates must be specified in YYYY-MM-DD format. + +Usage: `event /from /to ` + +### `find` + +Search for keywords amongst all tasks in your list. + +- The search is case-insensitive. e.g `assignment` will match `Assignment` +- The order of the keywords matters. e.g. `final exam` will not match `exam final` +- The search query is atomic; that is, individual words in the search query are not searched for but rather the complete string. + +Usage: `find ` + +### `help` + +Show all possible user inputs with usage prompts. + +Usage: `help` + +### `bye` + +Exit the Herbert application. + +Usage: `bye` + +### Command summary + +| Command | Usage | +|----------|-----------------------------------------------------------| +| list | `list` | +| mark | `mark ` | +| unmark | `unmark ` | +| delete | `delete ` | +| todo | `todo ` | +| deadline | `deadline /by ` | +| event | `event /from /to ` | +| find | `find ` | +| help | `help` | +| bye | `bye` | + +## Known issues + +1. The local storage of tasks is not updated on the deletion of a task or the update of a task's status. This is still being worked on. \ No newline at end of file diff --git a/docs/chatbot_home.png b/docs/chatbot_home.png new file mode 100644 index 0000000000000000000000000000000000000000..2dc2dc9e769070c86ffe8469cf27c0d3d430f100 GIT binary patch literal 94827 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 Date: Thu, 5 Oct 2023 14:04:45 +0800 Subject: [PATCH 23/24] Update README.md --- README.md | 59 ++++++++++++++++++++++++------------------------------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 2b823a3b1..d6ebc3e3a 100644 --- a/README.md +++ b/README.md @@ -12,39 +12,32 @@ Welcome to Herbert, the newest and friendliest AI assistant on the market. +## Getting started + +1. Download the `.jar` file from the latest release and move it to a folder of your choice. +2. In your terminal, navigate to the folder containing the `.jar` file. +3. Run `java -jar ip.jar`. + ## Commands -``` -* list -List all current tasks. -Usage: list - -* mark - Mark a task as completed. - Usage: mark - -* unmark - Mark a task as incomplete. - Usage: unmark - -* todo - Add a new todo to your list of tasks. - Usage: todo - -* deadline - Add a new deadline to your list of tasks. - Usage: deadline /by - -* event - Add a new event to your list of tasks. - Usage: event /from /to - -* help - Show this help menu. - Usage: help - -* bye - Exit the Herbert application. - Usage: bye -``` \ No newline at end of file +Visit our [user guide](https://antrikshdhand.github.io/ip) for a full explanation of each command! + +| Command | Usage | +|----------|-----------------------------------------------------------| +| list | `list` | +| mark | `mark ` | +| unmark | `unmark ` | +| delete | `delete ` | +| todo | `todo ` | +| deadline | `deadline /by ` | +| event | `event /from /to ` | +| find | `find ` | +| help | `help` | +| bye | `bye` | + +## Contact + +Antriksh Dhand – `A0278458J` – `e1139698@u.nus.edu` + +Project link: https://github.com/antrikshdhand/ip \ No newline at end of file From 3c37c926503faa834521ad565a6e1db58e0a87f7 Mon Sep 17 00:00:00 2001 From: antrikshdhand <> Date: Thu, 5 Oct 2023 17:42:44 +0800 Subject: [PATCH 24/24] Fix save functionality on deletion and update of tasks --- docs/README.md | 10 +---- src/main/java/herbert/Herbert.java | 15 ++++--- src/main/java/herbert/HerbertSaver.java | 60 ++++++++++++++++++------- 3 files changed, 55 insertions(+), 30 deletions(-) diff --git a/docs/README.md b/docs/README.md index 9b6da26e9..69c97669b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -50,9 +50,7 @@ Both the `from` and `to` dates must be specified by the user in ISO-8601 format: ### Local storage of tasks -Herbert wouldn't be very helpful if he forgot your list of tasks each time you restarted him! But you can rest assured that at any moment, a copy of your tasks is stored to your local hard-drive from which Herbert will automatically repopulate your task list from on startup. - -*See #known-issues for more on this. +Herbert wouldn't be very helpful if he forgot your list of tasks each time you restarted him! But you can rest assured that at any moment, a copy of your tasks is stored to your local hard-drive from which Herbert will automatically repopulate your task list from on startup. Whenever you add, delete, or update a task, the changes will be reflected in the local storage. ## Commands @@ -143,8 +141,4 @@ Usage: `bye` | event | `event /from /to ` | | find | `find ` | | help | `help` | -| bye | `bye` | - -## Known issues - -1. The local storage of tasks is not updated on the deletion of a task or the update of a task's status. This is still being worked on. \ No newline at end of file +| bye | `bye` | \ No newline at end of file diff --git a/src/main/java/herbert/Herbert.java b/src/main/java/herbert/Herbert.java index 53cc073e3..2705adf9a 100644 --- a/src/main/java/herbert/Herbert.java +++ b/src/main/java/herbert/Herbert.java @@ -10,7 +10,7 @@ */ public class Herbert { - private final HerbertSaver reader; + private final HerbertSaver saver; private final TaskList taskList; /** @@ -21,8 +21,8 @@ public class Herbert { public Herbert() { this.taskList = new TaskList(); - this.reader = new HerbertSaver("data", "HerbertTasks.txt"); - this.reader.loadFromSaveFile(this); + this.saver = new HerbertSaver("data", "HerbertTasks.txt"); + this.saver.loadFromSaveFile(this); HerbertUI.sayHello(); } @@ -126,6 +126,7 @@ private void markTask(String line, boolean completed) { } Task task = taskList.get(taskIndex); task.setCompleted(completed); + this.saver.rewriteSaveFile(this.taskList); // Print result message to user HerbertUI.printMessageMarkTask(task, completed); @@ -150,7 +151,7 @@ private void addTask(String line) { // Create and add task Todo td = new Todo(description); this.taskList.add(td); - this.reader.addTaskToSaveFile(td); + this.saver.addTaskToSaveFile(td); // Print success message HerbertUI.printMessageAddTask(td, this.taskList); @@ -173,7 +174,7 @@ private void addTask(String line) { // Create and add task Deadline dl = new Deadline(description, dueDate); this.taskList.add(dl); - this.reader.addTaskToSaveFile(dl); + this.saver.addTaskToSaveFile(dl); // Print success message HerbertUI.printMessageAddTask(dl, this.taskList); @@ -196,7 +197,7 @@ private void addTask(String line) { // Create and add task Event ev = new Event(description, fromDate, toDate); this.taskList.add(ev); - this.reader.addTaskToSaveFile(ev); + this.saver.addTaskToSaveFile(ev); // Print success message HerbertUI.printMessageAddTask(ev, this.taskList); @@ -232,6 +233,8 @@ private void deleteTask(String line) { Task taskCopy = taskList.get(taskIndex); this.taskList.remove(taskIndex); + this.saver.rewriteSaveFile(this.taskList); + HerbertUI.printMessageDeleteTask(taskCopy, this.taskList); } diff --git a/src/main/java/herbert/HerbertSaver.java b/src/main/java/herbert/HerbertSaver.java index 5ba7406f4..956b35201 100644 --- a/src/main/java/herbert/HerbertSaver.java +++ b/src/main/java/herbert/HerbertSaver.java @@ -116,20 +116,8 @@ public void addTaskToSaveFile(Task t) { StandardOpenOption.APPEND ); - StringBuilder s = new StringBuilder(String.format( - "%s | %s | %s", - t.getCode(), - t.isCompleted() ? "1" : "0", - t.getDescription() - )); - if (t instanceof Deadline) { - s.append(String.format(" | %s", ((Deadline) t).getDueDate())); - } else if (t instanceof Event) { - Event e = (Event) t; - s.append(String.format(" | %s | %s", e.getFrom(), e.getTo())); - } - - writer.write(s.toString()); + String s = this.buildTaskString(t); + writer.write(s); writer.newLine(); writer.close(); } catch (IOException e) { @@ -137,6 +125,46 @@ public void addTaskToSaveFile(Task t) { } } - // TODO: Remove task from save file - // TODO: Update task status in save file + /** + * Rewrites the entire save file with all the tasks in a given task list. + * @param taskList The task list to override the save file with. + */ + public void rewriteSaveFile(TaskList taskList) { + try { + BufferedWriter writer = Files.newBufferedWriter( + this.filePath, + StandardCharsets.UTF_8, + StandardOpenOption.WRITE + ); + for (int i = 0; i < taskList.size(); i++) { + String s = this.buildTaskString(taskList.get(i)); + writer.write(s); + writer.newLine(); + } + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Encodes a given task into the format required for the save file. + * @param t The task to be encoded. + * @return The string encoding of the task in the standard format. + */ + private String buildTaskString(Task t) { + StringBuilder s = new StringBuilder(String.format( + "%s | %s | %s", + t.getCode(), + t.isCompleted() ? "1" : "0", + t.getDescription() + )); + if (t instanceof Deadline) { + s.append(String.format(" | %s", ((Deadline) t).getDueDate())); + } else if (t instanceof Event) { + Event e = (Event) t; + s.append(String.format(" | %s | %s", e.getFrom(), e.getTo())); + } + return s.toString(); + } }