diff --git a/.gitignore b/.gitignore index 2873e189e..531a9eb81 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +#testing data +/data/ diff --git a/docs/README.md b/docs/README.md index 47b9f984f..dc3495cf2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,89 @@ -# Duke User Guide +# Pixy User Guide -// Update the title above to match the actual product name +``` +Hello! I'm Pixy +What can I do for you? +``` + +Pixy allows you to manage your tasks with ease, while having an anti-border tendency. + +## Adding ToDos + +`todo {taskName}` + +Adds a todo task with the specified task name. + +``` +todo added: [T][ ] add new task +``` + +## Adding Deadlines + +`deadline {taskName} /by {deadline}` + +Adds a deadline task with the specified task name and deadline. + +``` +deadline added: [D][ ] add new deadline (by: yesterday) +``` + +## Adding Events -// Product screenshot goes here +`event {eventName} /from {eventStart} /to {eventEnd}` -// Product intro goes here +Adds an event task with the specified event name, start time and end time. -## Adding deadlines +``` +event added: [E][ ] erase all borders (from: 25 Dec 1995, to: 31 Dec 1995) +``` + +## Listing Tasks + +`list` -// Describe the action and its outcome. +Lists all tasks in the task list. -// Give examples of usage +``` +Here are the tasks in your list: +[T][ ] add new task +[D][ ] add new deadline (by: yesterday) +[E][ ] erase all borders (from: 25 Dec 1995, to: 31 Dec 1995) +``` -Example: `keyword (optional arguments)` +## Marking Task as done -// A description of the expected outcome goes here +`mark {taskNumber}` +Marks the task with the specified task number as done. ``` -expected output +Nice! I've marked this task as done: +[T][X] add new task ``` -## Feature ABC +## Marking Task as undone + +`unmark {taskNumber}` + +Marks the task with the specified task number as undone. -// Feature details +## Deleting Tasks +`delete {taskNumber}` -## Feature XYZ +Deletes the task with the specified task number. -// Feature details \ No newline at end of file +``` +OK, I've marked this task as not done yet: +[T][ ] add new task +``` + +## Searching Tasks + +`find {keyword}` + +Searches for tasks with the specified keyword. + +``` +Here are the matching tasks in your list: +[E][ ] erase all borders (from: 25 Dec 1995, to: 31 Dec 1995) +``` 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..3f33e774a --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: pixy.ui.Pixy + diff --git a/src/main/java/pixy/parser/Parser.java b/src/main/java/pixy/parser/Parser.java new file mode 100644 index 000000000..3f450118e --- /dev/null +++ b/src/main/java/pixy/parser/Parser.java @@ -0,0 +1,136 @@ +package pixy.parser; + +import pixy.ui.PixyException; +import pixy.task.Deadline; +import pixy.task.Event; +import pixy.task.TaskList; +import pixy.task.Todo; + +/** + * The Parser class is responsible for parsing user commands and executing corresponding actions on the TaskList. + */ +public class Parser { + private static final String MESSAGE_INVALID_TIME = "invalid time >:("; + private static final String MESSAGE_EMPTY_DESCRIPTION = "empty description >:("; + + /** + * Parses the user input and executes the corresponding command. + * + * @param input the user input to be parsed + * @param taskList the task list to be modified + * @throws PixyException if an error occurs during parsing or execution of the command + */ + public static void parseCommand(String input, TaskList taskList) throws PixyException { + String command = input.trim().split(" ")[0]; + switch (command) { + case "list" -> taskList.printTasks(); + case "unmark" -> unmarkTask(input, taskList); + case "mark" -> markTask(input, taskList); + case "delete" -> deleteTask(input, taskList); + case "todo" -> addTodo(input, taskList); + case "deadline" -> addDeadline(input, taskList); + case "event" -> addEvent(input, taskList); + case "find" -> findTasks(input, taskList); + default -> invalidCommand(); + } + } + + /** + * Deletes a task from the task list based on the given input. + * + * @param input the input string containing the task index to be deleted + * @param taskList the task list from which the task will be deleted + */ + private static void deleteTask(String input, TaskList taskList) { + int taskIndex = Integer.parseInt(input.substring(7)) - 1; + System.out.println("Noted. I've removed this task:"); + System.out.println(taskList.deleteTask(taskIndex)); + } + + private static void invalidCommand() { + System.out.println("not a command!"); + } + + /** + * Adds an event task to the task list. + * + * @param input the input string containing the event task details + * @param taskList the task list to add the event task to + * @throws PixyException if the input string does not contain "/from" or "/to" + */ + private static void addEvent(String input, TaskList taskList) throws PixyException { + if (!input.contains(" /from ") || !input.contains(" /to ")) { + throw new PixyException(MESSAGE_INVALID_TIME); + } + String[] eventTask = input.substring(6).split(" /from ", 2); + String[] eventTime = eventTask[1].split(" /to ", 2); + taskList.addTask(new Event(eventTask[0], eventTime[0], eventTime[1])); + } + + /** + * Adds a deadline task to the task list. + * + * @param input the input string containing the task details and deadline + * @param taskList the task list to add the deadline task to + * @throws PixyException if the input string does not contain the "/by" delimiter + */ + private static void addDeadline(String input, TaskList taskList) throws PixyException { + if (!input.contains(" /by ")) { + throw new PixyException(MESSAGE_INVALID_TIME); + } + String[] deadlineTask = input.substring(9).split(" /by ", 2); + taskList.addTask(new Deadline(deadlineTask[0], deadlineTask[1])); + } + + /** + * Adds a new Todo task to the task list. + * + * @param input the input string containing the description of the task + * @param taskList the task list to add the task to + * @throws PixyException if the description is empty + */ + private static void addTodo(String input, TaskList taskList) throws PixyException { + String description = input.substring(input.indexOf(" ") + 1); + if (description.isBlank()) { + throw new PixyException(MESSAGE_EMPTY_DESCRIPTION); + } + taskList.addTask(new Todo(description)); + } + + /** + * Marks a task as done in the task list. + * + * @param input the input string containing the task index + * @param taskList the task list to mark the task in + */ + private static void markTask(String input, TaskList taskList) { + int taskIndex = Integer.parseInt(input.substring(5)) - 1; + taskList.markTask(taskIndex, true); + System.out.println("Nice! I've marked this task as done:"); + System.out.println(taskList.getTask(taskIndex)); + } + + /** + * Unmarks a task as not done yet. + * + * @param input the input string containing the task index + * @param taskList the task list to modify + */ + private static void unmarkTask(String input, TaskList taskList) { + int taskIndex = Integer.parseInt(input.substring(7)) - 1; + taskList.markTask(taskIndex, false); + System.out.println("OK, I've marked this task as not done yet:"); + System.out.println(taskList.getTask(taskIndex)); + } + + /** + * Finds tasks in the given input and searches for them in the task list. + * + * @param input The input string containing the keyword to search for tasks. + * @param taskList The task list to search for tasks in. + */ + private static void findTasks(String input, TaskList taskList) { + String keyword = input.substring(5).trim(); + taskList.searchTasks(keyword); + } +} \ No newline at end of file diff --git a/src/main/java/pixy/storage/Storage.java b/src/main/java/pixy/storage/Storage.java new file mode 100644 index 000000000..16d2ab6a5 --- /dev/null +++ b/src/main/java/pixy/storage/Storage.java @@ -0,0 +1,82 @@ +package pixy.storage; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; +import pixy.task.Task; +import pixy.task.Todo; +import pixy.task.Deadline; +import pixy.task.Event; + +/** + * The `Storage` class is responsible for saving and loading tasks to/from a file. + * It provides methods to save a list of tasks to a specified file path and load tasks from an existing file. + */ +public class Storage { + private final String filePath; + + public Storage(String filePath) { + this.filePath = filePath; + } + + /** + * Saves the given list of tasks to a file. + * + * @param tasks The list of tasks to be saved. + */ + public void saveTasksToFile(List tasks) { + try { + File file = new File(filePath); + file.getParentFile().mkdirs(); // Create directories if they do not exist + try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) { + for (Task task : tasks) { + writer.write(task.toFileFormat()); + writer.newLine(); + } + } + } catch (IOException e) { + System.out.println("An error occurred while saving tasks: " + e.getMessage()); + } + } + + /** + * Loads tasks from a file and adds them to the provided list of tasks. + * + * @param tasks The list of tasks to add the loaded tasks to. + */ + public void loadTasksFromFile(List tasks) { + try { + File file = new File(filePath); + if (!file.exists()) { + return; // If the file does not exist, there's nothing to load + } + try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) { + String line; + while ((line = reader.readLine()) != null) { + String[] parts = line.split(" \\| "); + String type = parts[0]; + boolean isDone = parts[1].equals("1"); + String description = parts[2]; + switch (type) { + case "T" -> tasks.add(new Todo(description, isDone)); + case "D" -> { + String by = parts[3]; + tasks.add(new Deadline(description, by, isDone)); + } + case "E" -> { + String from = parts[3]; + String to = parts[4]; + tasks.add(new Event(description, from, to, isDone)); + } + } + } + } + } catch (IOException e) { + System.out.println("An error occurred while loading tasks: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/pixy/task/Deadline.java b/src/main/java/pixy/task/Deadline.java new file mode 100644 index 000000000..947c98e01 --- /dev/null +++ b/src/main/java/pixy/task/Deadline.java @@ -0,0 +1,48 @@ +package pixy.task; + +/** + * Represents a task with a deadline. + * Inherits from the Task class. + */ +public class Deadline extends Task { + protected String by; + + /** + * Creates a new Deadline task with the given description and deadline. + * + * @param description The description of the Deadline task. + * @param by The deadline of the task. + */ + public Deadline(String description, String by) { + super(description); + this.by = by; + System.out.println("deadline added: " + this); + } + + /** + * Constructs a new Deadline object with the given description, due date, and completion status. + * + * @param description the description of the deadline + * @param by the due date of the deadline + * @param isDone the completion status of the deadline + */ + public Deadline(String description, String by, boolean isDone) { + super(description, isDone); + this.by = by; + } + + @Override + protected String getTaskType() { + return "D"; + } + + @Override + public String toFileFormat() { + return String.format("%s | %d | %s | %s", getTaskType(), isDone ? 1 : 0, description, by); + } + + @Override + public String toString() { + return "[D]" + super.toString() + " (by: " + by + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/pixy/task/Event.java b/src/main/java/pixy/task/Event.java new file mode 100644 index 000000000..76957bfbf --- /dev/null +++ b/src/main/java/pixy/task/Event.java @@ -0,0 +1,53 @@ +package pixy.task; + +/** + * Represents an event task that extends the Task class. + * An event task has a description, a start time, and an end time. + */ +public class Event extends Task { + protected String from; + protected String to; + + /** + * Constructs a new Event object with the given description, start time, and end time. + * + * @param description the description of the event + * @param from the start time of the event + * @param to the end time of the event + */ + public Event(String description, String from, String to) { + super(description); + this.from = from; + this.to = to; + System.out.println("event added: " + this); + } + + /** + * Constructs a new Event object with the given description, start time, end time, and completion status. + * + * @param description the description of the event + * @param from the start time of the event + * @param to the end time of the event + * @param isDone the completion status of the event + */ + public Event(String description, String from, String to, boolean isDone) { + super(description, isDone); + this.from = from; + this.to = to; + } + + @Override + protected String getTaskType() { + return "E"; + } + + @Override + public String toFileFormat() { + return String.format("%s | %d | %s | %s | %s", getTaskType(), isDone ? 1 : 0, description, from, to); + } + + @Override + public String toString() { + return "[E]" + super.toString() + " (from: " + from + ", to: " + to + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/pixy/task/Task.java b/src/main/java/pixy/task/Task.java new file mode 100644 index 000000000..87f8959df --- /dev/null +++ b/src/main/java/pixy/task/Task.java @@ -0,0 +1,61 @@ +package pixy.task; + +/** + * Represents a task with a description and completion status. + */ +public class Task { + protected String description; + protected boolean isDone; + + /** + * Constructs a new Task object with the given description. + * + * @param description the description of the task + */ + public Task(String description) { + this.description = description; + this.isDone = false; + } + + /** + * Constructs a new Task object with the given description and completion status. + * + * @param description the description of the task + * @param isDone the completion status of the task + */ + public Task(String description, boolean isDone) { + this.description = description; + this.isDone = isDone; + } + + /** + * Returns the status icon of the task. + * + * @return The status icon of the task. It is represented by "[X]" if the task is done, and "[ ]" if the task is not done. + */ + public String getStatusIcon() { + return (isDone ? "[X]" : "[ ]"); + } + + /** + * Sets the status of the task to indicate whether it is done or not. + * + * @param done the boolean value indicating whether the task is done or not + */ + public void setDone(boolean done) { + isDone = done; + } + + public String toFileFormat() { + return String.format("%s | %d | %s", getTaskType(), isDone ? 1 : 0, description); + } + + protected String getTaskType() { + return "T"; + } + + @Override + public String toString() { + return getStatusIcon() + " " + description; + } +} \ No newline at end of file diff --git a/src/main/java/pixy/task/TaskList.java b/src/main/java/pixy/task/TaskList.java new file mode 100644 index 000000000..56fccb401 --- /dev/null +++ b/src/main/java/pixy/task/TaskList.java @@ -0,0 +1,93 @@ +package pixy.task; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a list of tasks. + */ +public class TaskList { + private final List tasks; + + public TaskList() { + this.tasks = new ArrayList<>(); + } + + /** + * Constructs a TaskList object with the given list of tasks. + * + * @param tasks the list of tasks to be stored in the TaskList + */ + public TaskList(List tasks) { + this.tasks = tasks; + } + + public List getTasks() { + return tasks; + } + + /** + * Adds a task to the task list. + * + * @param task The task to be added. + */ + public void addTask(Task task) { + tasks.add(task); + } + + /** + * Deletes a task from the task list at the specified index. + * + * @param index The index of the task to be deleted. + * @return The task that was deleted. + */ + public Task deleteTask(int index) { + return tasks.remove(index); + } + + /** + * Retrieves the task at the specified index from the task list. + * + * @param index The index of the task to retrieve. + * @return The task at the specified index. + */ + public Task getTask(int index) { + return tasks.get(index); + } + + /** + * Marks the task at the specified index as done or not done. + * + * @param index the index of the task to be marked + * @param isDone the status indicating whether the task is done or not + */ + public void markTask(int index, boolean isDone) { + Task task = tasks.get(index); + task.setDone(isDone); + } + + /** + * Prints all the tasks in the task list. + */ + public void printTasks() { + System.out.println("Here are the tasks in your list:"); + for (Task task : tasks) { + System.out.println(task.toString()); + } + } + + /** + * Searches for tasks in the task list that contain the specified keyword. + * Prints the matching tasks to the console. + * + * @param keyword the keyword to search for + */ + public void searchTasks(String keyword) { + System.out.println("Here are the matching tasks in your list:"); + for (Task task : tasks) { + if (task.toString().contains(keyword)) { + System.out.println(task.toString()); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/pixy/task/Todo.java b/src/main/java/pixy/task/Todo.java new file mode 100644 index 000000000..ee8ec3e4d --- /dev/null +++ b/src/main/java/pixy/task/Todo.java @@ -0,0 +1,37 @@ +package pixy.task; + +/** + * Represents a todo task. + * Inherits from the Task class. + */ +public class Todo extends Task { + /** + * Constructs a new Todo object with the given description. + * + * @param description the description of the Todo + */ + public Todo(String description) { + super(description); + System.out.println("todo added: " + this); + } + + /** + * Constructs a new Todo object with the given description and completion status. + * + * @param description the description of the todo + * @param isDone the completion status of the todo + */ + public Todo(String description, boolean isDone) { + super(description, isDone); + } + + @Override + protected String getTaskType() { + return "T"; + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/pixy/ui/Pixy.java b/src/main/java/pixy/ui/Pixy.java new file mode 100644 index 000000000..ded5b7083 --- /dev/null +++ b/src/main/java/pixy/ui/Pixy.java @@ -0,0 +1,43 @@ +package pixy.ui; + +import pixy.parser.Parser; +import pixy.storage.Storage; +import pixy.task.TaskList; + +/** + * The `Pixy` class represents the main entry point for the Pixy application. + * It handles the user interface, task management, and file storage. + */ +public class Pixy { + + private static final String MESSAGE_INVALID_NUMBER = "invalid number..?"; + private static final String FILE_PATH = "./data/pixy.txt"; + private static boolean isRunning = true; + private static final Storage storage = new Storage(FILE_PATH); + + public static void main(String[] args) { + Ui ui = new Ui(); + TaskList taskList = new TaskList(); + + storage.loadTasksFromFile(taskList.getTasks()); + + ui.showWelcomeMessage(); + + while (isRunning) { + try { + String input = ui.readCommand(); + if (input.trim().equals("bye")) { + isRunning = false; + } else { + Parser.parseCommand(input, taskList); + } + } catch (PixyException e) { + ui.showErrorMessage(e.getMessage()); + } catch (NumberFormatException e) { + ui.showErrorMessage(MESSAGE_INVALID_NUMBER); + } + } + ui.showGoodbyeMessage(); + storage.saveTasksToFile(taskList.getTasks()); + } +} diff --git a/src/main/java/pixy/ui/PixyException.java b/src/main/java/pixy/ui/PixyException.java new file mode 100644 index 000000000..8e539bfe3 --- /dev/null +++ b/src/main/java/pixy/ui/PixyException.java @@ -0,0 +1,7 @@ +package pixy.ui; + +public class PixyException extends Exception { + public PixyException(String message) { + super(message); + } +} diff --git a/src/main/java/pixy/ui/Ui.java b/src/main/java/pixy/ui/Ui.java new file mode 100644 index 000000000..ee94223fd --- /dev/null +++ b/src/main/java/pixy/ui/Ui.java @@ -0,0 +1,35 @@ +package pixy.ui; + +import java.util.Scanner; + +/** + * The `Ui` class represents the user interface of the Pixy application. + * It provides methods for interacting with the user, displaying messages, and reading user input. + */ +public class Ui { + private final Scanner scanner; + + public Ui() { + this.scanner = new Scanner(System.in); + } + + public String readCommand() { + return scanner.nextLine(); + } + + public void showWelcomeMessage() { + System.out.println("Hello! I'm Pixy\nWhat can I do for you?"); + } + + public void showGoodbyeMessage() { + System.out.println("Bye. Hope to see you again soon!"); + } + + public void showErrorMessage(String message) { + System.out.println(message); + } + + public void showMessage(String message) { + System.out.println(message); + } +} \ No newline at end of file