diff --git a/data/daisy.txt b/data/daisy.txt new file mode 100644 index 000000000..72115e8be --- /dev/null +++ b/data/daisy.txt @@ -0,0 +1,10 @@ +D | 0 | ip | midnight +E | 0 | birthday | monday | tuesday +E | 0 | holiday | today. | tomorrow +T | 0 | sleep +D | 0 | assignment 1 | august 5 +D | 0 | project1 | friday 23:59 +E | 0 | conference | monday 1PM | monday 7PM +T | 0 | dream +T | 0 | gardening +D | 0 | dance | tomorrow diff --git a/docs/README.md b/docs/README.md index 47b9f984f..0bf5d18ee 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,150 @@ -# Duke User Guide +# Daisy ๐ŸŒผUser Guide -// Update the title above to match the actual product name +Hello! I'm Daisy ๐ŸŒผ a flowery chatbot eager to help you bloom into a more organized and productive you! From tracking tasks to keeping you on top of your game, I'm here to make your life a little more fragrant and a lot more colourful. Let's get started! -// Product screenshot goes here +## Help: shows command formats -// Product intro goes here +Displays all possible commands and their correct format -## Adding deadlines +Format: `help` +Example: `help` +Example output: -// Describe the action and its outcome. +``` +Of course, I'm happy to help! Here's all the available commands: + list - Show all tasks + todo [task] - Add a to-do + deadline [task] /by [date] - Add a deadline + event [task] /from [start] /to [end] - Add an event + mark [index] - Mark task as done + unmark [index] - Unmark task + delete [index] - Delete task + find [keyword] - Find task + bye - Exit program +``` + +## List: shows all tasks + +Displays all tasks currently in the list + +Format: `help` +Example: `list` +Example output: + +``` +1. [D] [ ] ip (by: midnight) +2. [E] [ ] birthday (from: monday to: tuesday) +3. [T] [ ] sleep +4. [E] [X] wedding (from: 6 to: 7) +5. [D] [ ] assignment 1 (by: august 5) +``` + +## Todo: adding a to-do task + +Adds a to-do task to the list + +Format: `todo [task]` +Example: `todo laundry` +Example output: + +``` +Woww that sounds delightful! I will add:: [T] [ ] laundry +``` -// Give examples of usage +## Deadline: adding a deadline -Example: `keyword (optional arguments)` +Adds a deadline task to the list -// A description of the expected outcome goes here +Format: `deadline [task] /by [date]` +Example: `deadline project1 /by friday 23:59` + +Example output: + +``` +Woww that sounds delightful! I will add:: [D] [ ] project1 (by: friday 23:59) +``` + +## Event: adding an event + +Adds an event task to the list + +Format: `event [task] /from [start] /to [end]` +Example: `event conference /from monday 1PM /to monday 7PM` + +Example output: ``` -expected output +Woww that sounds delightful! I will add:: [E] [ ] conference (from: monday 1PM to: monday 7PM) ``` -## Feature ABC +## Mark: marking a task as done + +Marks a task as done + +Format: `mark [index]` +Example: `mark 5` + +Example output: + +``` +Well done! I've marked this task as done: [E] [X] wedding (from: 6 to: 7) +``` -// Feature details +## Unmark: unmarking a task as done + +Unmarks a task, if a task turns out to be incomplete + +Format: `unmark [index]` +Example: `unmark 5` + +Example output: + +``` +Uhoh are we getting ahead of ourselves? I've unmarked: [E] [] wedding (from: 6 to: 7) +``` + +## Delete: delete a task + +Deletes a task from the list + +Format: `delete [index]` +Example: `delete 5` + +Example output: + +``` +Let's remove this: [E] [ ] wedding (from: 6 to: 7) +``` + +## find: find a task + +Searches for tasks that have a given keyword in their description + +Format: `find [keyword]` +Example: `find day` + +Example output: + +``` +I see colours everywhere! Here are the matching tasks in your list: +1. [E] [ ] birthday (from: monday to: tuesday) +2. [E] [ ] holiday (from: today. to: tomorrow) +``` + +## bye: exit program + +Closes the program + +Format: `bye` +Example: `bye` + +Example output: + +``` + Byebye! I'll be photosynthesizing in the corner but I hope to see you bloom again soon! ๐ŸŒธ +``` +## save and store data -## Feature XYZ +Tasks are saved automatically after any changes to the list. It is saved as a text file in `[JAR file location]/data/daisy.txt`. -// Feature details \ No newline at end of file diff --git a/src/main/java/Daisy.java b/src/main/java/Daisy.java new file mode 100644 index 000000000..b67c6ca09 --- /dev/null +++ b/src/main/java/Daisy.java @@ -0,0 +1,493 @@ +import java.io.*; +import java.util.ArrayList; +import java.util.Scanner; + +// Main Daisy class +public class Daisy { + private Storage storage; + private TaskList tasks; + private Ui ui; + + /** + * Constructor for Daisy + * Initializes the user interface (Ui), storage (Storage) and loads saved tasks + * + * @param filePath the file path to load and save tasks. + */ + public Daisy(String filePath) { + ui = new Ui(); + storage = new Storage(filePath); + try { + tasks = new TaskList(storage.load()); + } catch (Exception e) { + ui.showLoadingError(); + tasks = new TaskList(); + } + } + + /** + * Displays the welcome message and handles user input until the user chooses to exit ("bye") + */ + public void run() { + ui.showWelcome(); + boolean isExit = false; + while (!isExit) { + try { + String fullCommand = ui.readCommand(); + ui.showLine(); + Command c = Parser.parse(fullCommand); + c.execute(tasks, ui, storage); + isExit = c.isExit(); + } catch (Exception e) { + ui.showError(e.getMessage()); + } finally { + ui.showLine(); + } + } + } + + public static void main(String[] args) { + new Daisy("data/daisy.txt").run(); + } +} + +// Ui for dealing with user input and output +class Ui { + private final Scanner scanner; + + /** + * Initializes the scanner for reading user input + */ + public Ui() { + scanner = new Scanner(System.in); + } + + /** + * Displays the welcome message + */ + public void showWelcome() { + System.out.println("____________________________________________________________"); + System.out.println(" Hello! I'm Daisy ๐ŸŒผ"); + System.out.println(" How can I help you today?"); + System.out.println(" Type 'help' to see all of my petals (possible commands)."); + System.out.println("____________________________________________________________"); + } + + /** + * Displays the goodbye message + */ + public void showGoodbye() { + System.out.println(" Byebye! I'll be photosynthesizing in the corner but I hope to see you bloom again soon! ๐ŸŒธ"); + } + + public String readCommand() { + return scanner.nextLine(); + } + + public void showLine() { + System.out.println("____________________________________________________________"); + } + + /** + * Displays an error message when loading tasks doesn't work + */ + public void showLoadingError() { + System.out.println("Oh no! There appears to be an error loading saved tasks. Please try again!"); + } + + /** + * Displays an error message + * + * @param message the error message to display + */ + public void showError(String message) { + System.out.println("Oh no! There appears to be an error visiting us. This is its name: " + message); + } +} + +class Storage { + private final String filePath; + + public Storage(String filePath) { + this.filePath = filePath; + } + + /** + * Loads tasks from the storage file + * + * @return list of tasks from the storage file + * @throws IOException if an I/O error occurs + */ + public ArrayList load() throws IOException { + ArrayList tasks = new ArrayList<>(); + File file = new File(filePath); + if (!file.exists()) { + file.getParentFile().mkdirs(); + file.createNewFile(); + return tasks; + } + + try (Scanner scanner = new Scanner(file)) { + while (scanner.hasNextLine()) { + tasks.add(Task.fromFileString(scanner.nextLine())); + } + } + return tasks; + } + + /** + * Saves the tasks to the storage file + * + * @param tasks the list of tasks to save + * @throws IOException if an I/O error occurs + */ + public void save(ArrayList tasks) throws IOException { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) { + for (Task task : tasks) { + writer.write(task.toFileString() + "\n"); + } + } + } +} + +// Manages the task list +class TaskList { + private ArrayList tasks; + + public TaskList() { + this.tasks = new ArrayList<>(); + } + + public TaskList(ArrayList tasks) { + this.tasks = tasks; + } + + /** + * Adds a task to the task list + * + * @param task the task to add + */ + public void addTask(Task task) { + tasks.add(task); + System.out.println("Woww that sounds delightful! I will add: " + task); + } + + /** + * Finds and displays tasks that match the provided keyword + * + * @param keyword the keyword to search for in tasks + */ + public void findTask(String keyword) { + System.out.println("I see colours everywhere! Here are the matching tasks in your list:"); + int count = 0; + for (int i = 0; i < tasks.size(); i++) { + if (tasks.get(i).description.contains(keyword)) { + System.out.println((count + 1) + ". " + tasks.get(i)); + count++; + } + } + if (count == 0) { + System.out.println("Oh no! I couldn't find any matching tasks. Please refer to the 'list' function instead."); + } + } + + public void deleteTask(int index) { + if (index < 1 || index > tasks.size()) { + System.out.println("Oh no! This is an invalid task number! Please look at the 'list'."); + return; + } + Task removed = tasks.remove(index - 1); + System.out.println("Let's remove this: " + removed); + } + + public void listTasks() { + if (tasks.isEmpty()) { + System.out.println("Your task list is empty! Time to fill it up with lots of fun activities :)"); + return; + } + for (int i = 0; i < tasks.size(); i++) { + System.out.println((i + 1) + ". " + tasks.get(i)); + } + } + + public void markTask(int index) { + tasks.get(index - 1).markAsDone(); + System.out.println("Well done! I've marked this task as done: " + tasks.get(index - 1)); + } + + public void unmarkTask(int index) { + tasks.get(index - 1).markAsNotDone(); + System.out.println("Uhoh are we getting ahead of ourselves? I've unmarked: " + tasks.get(index - 1)); + } + + public ArrayList getTasks() { + return tasks; + } +} + +// Parses ui and returns command +class Parser { + public static Command parse(String input) { + String[] parts = input.split(" ", 2); + String command = parts[0]; + String args = parts.length > 1 ? parts[1] : ""; + + switch (command) { + case "list": + return new ListCommand(); + case "mark": + return new MarkCommand(Integer.parseInt(args)); + case "unmark": + return new UnmarkCommand(Integer.parseInt(args)); + case "todo": + return new AddCommand(new TodoTask(args)); + case "deadline": + String[] deadlineParts = args.split(" /by "); + return new AddCommand(new DeadlineTask(deadlineParts[0], deadlineParts[1])); + case "event": + String[] eventParts = args.split(" /from | /to "); + return new AddCommand(new EventTask(eventParts[0], eventParts[1], eventParts[2])); + case "delete": + return new DeleteCommand(Integer.parseInt(args)); + case "bye": + return new ExitCommand(); + case "help": + return new HelpCommand(); + case "find": + return new FindCommand(args); + default: + throw new IllegalArgumentException("Oh no! This is an invalid command! Use the 'help' command to see all possibilities."); + } + } +} + +abstract class Command { + /** + * Excecutes the command + * + * @param tasks the task list + * @param ui the user interface + * @param storage the storage to save the tasks + * @throws IOException if an error occurs during I/O operations + */ + public abstract void execute(TaskList tasks, Ui ui, Storage storage) throws IOException; + + public boolean isExit() { + return false; + } +} + +class ListCommand extends Command { + public void execute(TaskList tasks, Ui ui, Storage storage) { + tasks.listTasks(); + } +} + +class MarkCommand extends Command { + private final int index; + + public MarkCommand(int index) { + this.index = index; + } + + public void execute(TaskList tasks, Ui ui, Storage storage) { + tasks.markTask(index); + } +} + +class UnmarkCommand extends Command { + private final int index; + + public UnmarkCommand(int index) { + this.index = index; + } + + public void execute(TaskList tasks, Ui ui, Storage storage) { + tasks.unmarkTask(index); + } +} + +class AddCommand extends Command { + private final Task task; + + public AddCommand(Task task) { + this.task = task; + } + + public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException { + tasks.addTask(task); + storage.save(tasks.getTasks()); + } +} + +class DeleteCommand extends Command { + private final int index; + + public DeleteCommand(int index) { + this.index = index; + } + + public void execute(TaskList tasks, Ui ui, Storage storage) throws IOException { + tasks.deleteTask(index); + storage.save(tasks.getTasks()); + } +} + +class ExitCommand extends Command { + public void execute(TaskList tasks, Ui ui, Storage storage) { + ui.showGoodbye(); + } + + public boolean isExit() { + return true; + } +} + +class HelpCommand extends Command { + public void execute(TaskList tasks, Ui ui, Storage storage) { + System.out.println("Of course, I'm happy to help! Here's all the available commands:"); + System.out.println(" list - Show all tasks"); + System.out.println(" todo [task] - Add a to-do"); + System.out.println(" deadline [task] /by [date] - Add a deadline"); + System.out.println(" event [task] /from [start] /to [end] - Add an event"); + System.out.println(" mark [index] - Mark task as done"); + System.out.println(" unmark [index] - Unmark task"); + System.out.println(" delete [index] - Delete task"); + System.out.println(" find [keyword] - Find task"); + System.out.println(" bye - Exit program"); + } +} + +class FindCommand extends Command { + private String keyword; + + public FindCommand(String keyword) { + this.keyword = keyword; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) { + tasks.findTask(keyword); + } + + @Override + public boolean isExit() { + return false; + } +} + +abstract class Task { + protected String description; + protected boolean isDone; + + public Task(String description) { + this.description = description; + this.isDone = false; + } + + public void markAsDone() { + this.isDone = true; + } + + public void markAsNotDone() { + this.isDone = false; + } + + /** + * Returns the status of the task + * + * @return "[X]" if done, "[ ]" if not done + */ + public String getStatusIcon() { + return (isDone ? "[X]" : "[ ]"); + } + + /** + * Saving the task to a file + */ + public abstract String toFileString(); + + /** + * Creating a task from a file. + */ + public static Task fromFileString(String line) { + String[] parts = line.split(" \\| "); + switch (parts[0]) { + case "T": + TodoTask todo = new TodoTask(parts[2]); + if (parts[1].equals("1")) todo.markAsDone(); + return todo; + case "D": + DeadlineTask deadline = new DeadlineTask(parts[2], parts[3]); + if (parts[1].equals("1")) deadline.markAsDone(); + return deadline; + case "E": + EventTask event = new EventTask(parts[2], parts[3], parts[4]); + if (parts[1].equals("1")) event.markAsDone(); + return event; + default: + throw new IllegalArgumentException("Oh no! There is an invalid task type in the file. Please check the file for more information."); + } + } + + @Override + public String toString() { + return getStatusIcon() + " " + description; + } +} + +class TodoTask extends Task { + public TodoTask(String description) { + super(description); + } + + @Override + public String toFileString() { + return "T | " + (isDone ? "1" : "0") + " | " + description; + } + + @Override + public String toString() { + return "[T] " + super.toString(); + } +} + +class DeadlineTask extends Task { + private String by; + + public DeadlineTask(String description, String by) { + super(description); + this.by = by; + } + + @Override + public String toFileString() { + return "D | " + (isDone ? "1" : "0") + " | " + description + " | " + by; + } + + @Override + public String toString() { + return "[D] " + super.toString() + " (by: " + by + ")"; + } +} + +class EventTask extends Task { + private String from; + private String to; + + public EventTask(String description, String from, String to) { + super(description); + this.from = from; + this.to = to; + } + + @Override + public String toFileString() { + return "E | " + (isDone ? "1" : "0") + " | " + description + " | " + from + " | " + to; + } + + @Override + public String toString() { + return "[E] " + super.toString() + " (from: " + from + " to: " + to + ")"; + } +} diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334c..000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..390b6a539 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Daisy + diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e..63918c9b0 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,25 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - +____________________________________________________________ + Hello! I'm Daisy + What can I do for you? +____________________________________________________________ +____________________________________________________________ + Got it. I've added this task: + [T][ ] borrow book + Now you have 1 tasks in the list. +____________________________________________________________ +____________________________________________________________ + Here are the tasks in your list: +1.[T][ ] borrow book +____________________________________________________________ +____________________________________________________________ + Got it. I've added this task: + [D][ ] return book (by: Sunday) + Now you have 2 tasks in the list. +____________________________________________________________ +____________________________________________________________ + Got it. I've added this task: + [E][ ] project meeting (from: Mon 2pm to: 4pm) + Now you have 3 tasks in the list. +____________________________________________________________ + Bye. Hope to see you again soon! +____________________________________________________________ diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb..47e576978 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,5 @@ +todo borrow book +list +deadline return book /by Sunday +event project meeting /from Mon 2pm /to 4pm +bye diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh index c9ec87003..e887b2500 100644 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -20,7 +20,7 @@ then 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 Daisy < input.txt > ACTUAL.TXT # convert to UNIX format cp EXPECTED.TXT EXPECTED-UNIX.TXT