diff --git a/.gitignore b/.gitignore index 2873e189e..5d4449135 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +wen-storage.txt \ No newline at end of file diff --git a/README.md b/README.md index af0309a9e..8e472bdc2 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,67 @@ -# 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 17, 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 17** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
- In the same dialog, set the **Project language level** field to the `SDK default` option. -1. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` - -**Warning:** Keep the `src\main\java` folder as the root folder for Java files (i.e., don't rename those folders or move Java files to another folder outside of this folder path), as this is the default location some tools (e.g., Gradle) expect to find Java files. +# Wen: Your assistant chatbot! + +This is Wen, your personal assistant for school-related tasks. Keep track of all your scheduling needs here! + +## User Guide + +### `todo` +Create a new to-do list item. +#### Example +```sh +todo Get some milk +``` + +### `deadline` +Create a new deadline. +#### Parameters +- `/by` - The deadline to complete this task by. +#### Example +```sh +deadline Complete CS2113 weekly quiz /by 12pm +``` + +### `event` +Create a new event. +#### Parameters +- `/from` - The start time of the event. +- `/to` - The end time of the event. +#### Example +```sh +deadline Join group meeting /from 5pm /to 8pm +``` + +### `list` +Displays all of your tasks. +#### Example +```sh +list +``` + +### `find` +Find a task with the search query in the description. +#### Parameters +The word you want to search for +#### Example +```sh +find CS2113 +``` + +### `mark` & `unmark` +Mark one of your tasks as done. +#### Parameters +The index of the task; you can find the task index by using `list` or `find` +#### Example +```sh +mark 5 +unmark 3 +``` + +### `delete` +Deletes one of your tasks +#### Parameters +The index of the task; you can find the task index by using `list` or `find` +#### Example +```sh +delete 4 +``` + diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 000000000..97f4ffb68 --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,20 @@ +public class Deadline extends Task { + + protected String by; + + public Deadline(String description, String by) { + super(description); + this.by = by; + } + + public Deadline(String description, String by, boolean done) { + super(description); + this.by = by; + this.isDone = done; + } + + @Override + public String toString() { + return "[D]" + super.toString() + " (by: " + by + ")"; + } +} \ 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/Event.java b/src/main/java/Event.java new file mode 100644 index 000000000..dcaf2e2f6 --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,23 @@ +public class Event extends Task { + + protected String from; + protected String by; + + public Event(String description, String from, String by) { + super(description); + this.from = from; + this.by = by; + } + + public Event(String description, String from, String by, boolean done) { + super(description); + this.from = from; + this.by = by; + this.isDone = done; + } + + @Override + public String toString() { + return "[E]" + super.toString() + " (from: " + from + " to: " + by + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..de13cdae7 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Wen + diff --git a/src/main/java/Parser.java b/src/main/java/Parser.java new file mode 100644 index 000000000..a39f516fa --- /dev/null +++ b/src/main/java/Parser.java @@ -0,0 +1,52 @@ +import java.util.HashMap; +import java.util.Map; + +public class Parser { + private final String commandName; + private final Map commandArgs; + private final String commandMainArg; + + + Parser(String input) { + + commandName = input.split(" ", 2)[0]; + String commandArgsString = input.split(" ", 2).length > 1 ? input.split(" ", 2)[1] : ""; + + commandArgs = new HashMap<>(); + + String[] commandParts = commandArgsString.split("/"); + if (commandParts.length > 1) { + for (int i = 1; i < commandParts.length; i++) { + String argName = commandParts[i].split(" ")[0].trim(); + String argValue = commandParts[i].split(" ").length > 1 ? commandParts[i].split(" ", 2)[1].trim() : ""; + commandArgs.put(argName, argValue); + } + } + commandMainArg = commandParts[0].trim().isEmpty() ? null : commandParts[0].trim(); + + } + + public boolean equals(String name) { + return commandName.equals(name); + } + + public String getName() { + return commandName; + } + + public String getArg(){ + return commandMainArg; + } + + public String getArg(String arg) { + return commandArgs.get(arg); + } + + public boolean hasArg() { + return commandMainArg != null; + } + public boolean hasArg(String arg) { + return commandArgs.containsKey(arg); + } + +} \ No newline at end of file diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 000000000..df32c760a --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,90 @@ +/** + * A task where users can mark as done/undone, with different specific types + */ +public class Task { + protected String description; + protected boolean isDone; + + /** + * Create a new task + * @param description basic description + */ + public Task(String description) { + this.description = description; + this.isDone = false; + } + + /** + * Parse the task from a string, usually for reading saved tasks. + * @param description The string to parse the task from + * @return a new task object, one of three types (Deadline, Event, Todo) + */ + public Task fromString(String description) { + char taskType = description.charAt(1); + boolean taskDone = description.charAt(4) == 'X'; + + switch (taskType) { + case 'D': + String deadlineDesc = description.substring(7); + String deadline = ""; + int deadlineIndex = description.indexOf(" (by: ", 1); + if (deadlineIndex != -1) { + deadlineDesc = description.substring(7, deadlineIndex); + deadline = description.substring(deadlineIndex + 6, description.length() - 1); + } + return new Deadline(deadlineDesc, deadline, taskDone); + + case 'E': + String eventDesc = description.substring(7); + String from = ""; + String to = ""; + + int fromIndex = description.indexOf(" (from: ", 1); + int toIndex = description.indexOf(" to: ", 1); + if (fromIndex != -1 && toIndex != -1) { + eventDesc = description.substring(7, fromIndex); + from = description.substring(fromIndex + 8, toIndex); + to = description.substring(toIndex + 5, description.length() - 1); + } + + return new Event(eventDesc, from, to, taskDone); + + case 'T': // todo + default: // just in case ?? + return new Todo(description.substring(7), taskDone); + } + } + + public String getStatusIcon() { + return (isDone ? "X" : " "); // mark done task with X + } + + public void print() { + System.out.print("[" + (isDone ? "X" : " ") + "] "); + System.out.println(description); + } + + /** + * Get the task description + * @return String form of task description + */ + public String getDescription() { + return description; + } + + /** + * Gets a simplified representation of the task + * @return A string form of the task, including its done state + */ + public String toString() { + return "[" + (isDone ? "X" : " ") + "] " + description; + } + + /** + * Set the task to be done or not + * @param done New state of task to be set + */ + public void setDone(boolean done){ + isDone = done; + } +} diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java new file mode 100644 index 000000000..60341f5a9 --- /dev/null +++ b/src/main/java/TaskList.java @@ -0,0 +1,139 @@ +import java.lang.reflect.Array; +import java.util.ArrayList; + +public class TaskList { + private final ArrayList tasks; + private final WenStorage WenStorage; + + /** + * Create a list of tasks, and initialize stored value from the default path + * @see Task + */ + public TaskList() { + tasks = new ArrayList<>(); + WenStorage = new WenStorage(); + } + + /** + * Create a list of task, and initialize stored value from a custom path + * @param filePath path of storage file + * @see WenStorage + */ + public TaskList(String filePath) { + tasks = new ArrayList<>(); + WenStorage = new WenStorage(filePath); + initializeFromStorage(filePath); + } + + /** + * Return size of task list + * @return integer, size of list + */ + private int count() { + return tasks.size(); + } + + /** + * Prettifies and prints all tasks on the list + */ + public void print() { + System.out.println("You currently have " + count() + " tasks!"); + for (int i = 0; i < count(); i++) { + System.out.println(i + 1 + ". " + tasks.get(i)); + } + } + + private void updateStorage() { + WenStorage.writeToFile(tasks.stream().map(Task::toString).toArray(String[]::new)); + } + + /** + * Find index of task + * @param task task to locate + * @return integer index, -1 if not found + */ + public int getIndex(Task task) { + for (int i = 0; i < count(); i++) { + if (tasks.get(i).equals(task)) { + return i; + } + } + return -1; + } + + /** + * Remove task from list + * @param taskIndex index of task to be removed + */ + public void deleteTask(int taskIndex) { + String taskToDelete = tasks.get(taskIndex - 1).toString(); + tasks.remove(taskIndex -1); + updateStorage(); + System.out.println("Task " + taskIndex + " has been deleted!"); + System.out.println("(" + taskToDelete + ")"); + System.out.println("You currently have " + count() + " tasks!"); + } + + /** + * Mark task as complete / incomplete + * @param taskIndex index of task to check + * @param done status to be set + */ + public void markTask(int taskIndex, boolean done) { + Task task = tasks.get(taskIndex - 1); + task.setDone(done); + updateStorage(); + if (done) { + System.out.println("Task marked as done: " + task); + } else { + System.out.println("Task marked as incomplete: " + task); + } + } + + /** + * Find task based on a string search query (has to be exact match) + * @param query string to search for in descriptions + * @return a list of tasks that match the query + */ + public ArrayList findTask(String query) { + ArrayList foundTasks = new ArrayList<>(); + for (int i = 0; i < count(); i++) { + if (tasks.get(i).getDescription().contains(query)) { + foundTasks.add(tasks.get(i)); + } + } + return foundTasks; + } + + public void addTodo(String description) { + tasks.add(new Todo(description)); + updateStorage(); + System.out.println("Task added successfully:" + tasks.get(tasks.size() - 1)); + } + + public void addDeadline(String desc, String by) { + tasks.add(new Deadline(desc, by)); + updateStorage(); + System.out.println("Task added successfully:" + tasks.get(tasks.size() - 1)); + } + + public void addEvent(String desc, String from, String to) { + tasks.add(new Event(desc, from, to)); + updateStorage(); + System.out.println("Task added successfully:" + tasks.get(tasks.size() - 1)); + } + + public void initializeFromStorage(String filePath){ + if (!WenStorage.isFileExists()) { + WenStorage.writeToFile(new String[0]); + } + String[] data = WenStorage.readFromFile(); + if (data.length > 0) { + System.out.println("[I've loaded " + data.length + " tasks from your local storage!]"); + for (String datum : data) { + Task t = new Task("").fromString(datum); + tasks.add(t); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/Todo.java b/src/main/java/Todo.java new file mode 100644 index 000000000..a37b53317 --- /dev/null +++ b/src/main/java/Todo.java @@ -0,0 +1,16 @@ +public class Todo extends Task { + + public Todo(String description) { + super(description); + } + + public Todo(String description, boolean done) { + super(description); + this.isDone = done; + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/Ui.java b/src/main/java/Ui.java new file mode 100644 index 000000000..77eeb9d8f --- /dev/null +++ b/src/main/java/Ui.java @@ -0,0 +1,18 @@ +public class Ui { + /** + * Greet users when starting app + */ + public static void greet() { + System.out.println("Hello, I'm Wen!"); + System.out.println("Let me know what I can help you with~☆"); + } + + /** + * Say goodbye to server when quitting app + */ + public static void goodbye() { + System.out.println(); + System.out.println("Aw, you're already going?"); + System.out.println("It's okay, let's meet again soon!"); + } +} \ No newline at end of file diff --git a/src/main/java/Wen.java b/src/main/java/Wen.java new file mode 100644 index 000000000..760229b45 --- /dev/null +++ b/src/main/java/Wen.java @@ -0,0 +1,120 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Scanner; + +public class Wen { +// private static String storagePath = "./wen-storage.txt"; + private final TaskList tasks; + + private Wen(String storagePath) { + tasks = new TaskList(storagePath); + } + + private void run() { + + Ui.greet(); + + String input = ""; + Scanner in = new Scanner(System.in); + + + while (!input.equals("bye")) { + Parser command = new Parser(input); + + switch (command.getName()) { + case "": + break; + + case "list": + tasks.print(); + break; + + case "delete": + try { + int taskIndex = Integer.parseInt(command.getArg()); + tasks.deleteTask(taskIndex); + } catch (NumberFormatException e) { + System.out.println("The task number is formatted incorrectly!"); + } catch (NullPointerException | IndexOutOfBoundsException e) { + System.out.println("This task doesn't exist!"); + } + break; + + case "find": + ArrayList foundTasks = tasks.findTask(command.getArg()); + if (foundTasks.isEmpty()) { + System.out.println("There are no tasks found!"); + } else { + System.out.println("Found " + foundTasks.size() + " tasks!"); + for (Task task : foundTasks) { + System.out.print(tasks.getIndex(task)+1); + System.out.print(". "); + System.out.println(task); + } + } + break; + + case "mark": + case "unmark": + try { + int taskIndex = Integer.parseInt(command.getArg()); + tasks.markTask(taskIndex, command.equals("mark")); + } catch (NumberFormatException e) { + System.out.println("The task number is formatted incorrectly!"); + } catch (NullPointerException | IndexOutOfBoundsException e) { + System.out.println("This task doesn't exist!"); + } + break; + + case "todo": + if (!command.hasArg()) { + System.out.println("Please specify a description for this todo!"); + break; + } + tasks.addTodo(command.getArg()); + break; + + case "deadline": + if (!command.hasArg()) { + System.out.println("Please specify a description for this deadline!"); + break; + } + if (!command.hasArg("by")) { + System.out.println("Please specify a due date using /by!"); + break; + } + + tasks.addDeadline(command.getArg(), command.getArg("by")); + break; + + case "event": + if (!command.hasArg()) { + System.out.println("Please specify a description for this event!"); + break; + } + if (!command.hasArg("from") || !command.hasArg("to")) { + System.out.println("Please specify the event duration using /from and /to!"); + System.out.println("Example: \"event Birthday party /from 6pm /to 8pm\""); + break; + } + + tasks.addEvent(command.getArg(), command.getArg("from"), command.getArg("to")); + break; + + default: + System.out.println("Unknown command \"" + command.getName() + "\"! Double check your message and try running again~"); + break; + } + + input = in.nextLine(); + } + + Ui.goodbye(); + System.exit(0); + } + + public static void main(String[] args) { + new Wen("./wen-storage.txt").run(); + } + +} diff --git a/src/main/java/WenStorage.java b/src/main/java/WenStorage.java new file mode 100644 index 000000000..5166f0668 --- /dev/null +++ b/src/main/java/WenStorage.java @@ -0,0 +1,77 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Scanner; + +public class WenStorage { + private final String filePath; + private final File storageFile; + + /** + * Create a new storage instance using the default file path + */ + public WenStorage() { + filePath = "./wen-storage.txt"; + storageFile = new File(filePath); + } + + /** + * Create a new storage instance using a custom file path + * @param customFilePath path to save info to, relative to the main code + */ + public WenStorage(String customFilePath) { + filePath = customFilePath; + storageFile = new File(filePath); + } + + /** + * Check if the storage file exists + * @return boolean to represent file state + */ + public boolean isFileExists() { + return storageFile.exists(); + } + + /** + * Write any string to file + * @param data array of strings, to be joined in the saved file with newlines (\n) + */ + public void writeToFile(String[] data) { + String dataString = ""; + for (String datum : data) { + dataString += datum; + dataString += System.lineSeparator(); + } + FileWriter fw = null; + try { + fw = new FileWriter(filePath); + fw.write( + dataString + ); + fw.close(); + } catch (IOException e) { + System.out.println("Error writing to file! This may be because your memory is full, or the file is corrupted :("); + } + } + + /** + * Read from file and return as strings + * @return array of strings, delimited with newlines (\n) + */ + public String[] readFromFile() { + ArrayList output = new ArrayList<>(); + Scanner s = null; + try { + s = new Scanner(storageFile); + while (s.hasNext()) { + output.add(s.nextLine()); + } + } catch (FileNotFoundException e) { + System.out.println("File not found!"); + } + return output.toArray(new String[0]); + } +} \ No newline at end of file diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e..218f4f337 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,8 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - +Hello, I'm Wen! +Let me know what I can help you with~☆ +Unknown command "test"! Double check your message and try running again~ +Task added successfully:[E][ ] hello (from: today to: tomorrow) +Task added successfully:[T][ ] hi +You currently have 2 tasks! +1. [E][ ] hello (from: today to: tomorrow) +2. [T][ ] hi diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb..ebda8ae15 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,4 @@ +test +event hello /to tomorrow /from today +todo hi +list diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index c9ec87003..1a08892a4 100644 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,14 +13,14 @@ then fi # compile the code into the bin folder, terminates if error occurred -if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java +if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/Wen.java then echo "********** BUILD FAILURE **********" exit 1 fi # run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ../bin Duke < input.txt > ACTUAL.TXT +java -classpath ../bin Wen < input.txt > ACTUAL.TXT # convert to UNIX format cp EXPECTED.TXT EXPECTED-UNIX.TXT