diff --git a/docs/README.md b/docs/README.md index 8077118eb..3ae34b4a7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,195 @@ +# Pythia + + █████████████ + ██ ██ + ██ █████ ██ + ██ █████ ██ + ██ ██ + █████████████ + ██ ██ + ████████████████████ + +This is Pythia an assistant that makes use of the CLI keep track of all your day to day tasks + # User Guide -## Features +## Features + +### `todo` + +Add a todo task (task with no due date) + +Format: `todo [TASK]` + +Example of usage: + +`todo placeholder1` + +Expected outcome: +``` +added: [T][ ] placeholder1 +---------------------------------------------------------- +``` + +### `deadline` + +Add a deadline task (task with a due date) -### Feature-ABC +Format: `deadline [TASK] /[DUEDATE]` -Description of the feature. +Example of usage: -### Feature-XYZ +`deadline placeholder2 /June 25th 6pm` + +Expected outcome: +``` +added: [D][ ] placeholder2 (June 25th 6pm) +---------------------------------------------------------- +``` -Description of the feature. +### `event` -## Usage +Add an event task (task with a start and end time) -### `Keyword` - Describe action +Format: `event [TASK] /[STARTTIME] /[ENDTIME]` -Describe the action and its outcome. +Example of usage: + +`event placeholder3 /3pm /6pm` + +Expected outcome: +``` +added: [E][ ] placeholder3 (from: 3pm to: 6pm) +---------------------------------------------------------- +``` + +### `list` + +Lists all tasks currently in the list + +Format: `list` Example of usage: -`keyword (optional arguments)` +`list` + +Expected outcome: +``` +1.[T][ ] placeholder1 +2.[D][ ] placeholder2 (June 25th 6pm) +3.[E][ ] placeholder3 (from: 3pm to: 6pm) +---------------------------------------------------------- +``` + +### `mark` + +Marks the specified index in the list + +Format: `mark [INDEX]` + +Example of usage: + +`mark 1` Expected outcome: +``` +Marked placeholder1 +---------------------------------------------------------- +list +1.[T][X] placeholder1 +2.[D][ ] placeholder2 (June 25th 6pm) +3.[E][ ] placeholder3 (from: 3pm to: 6pm) +---------------------------------------------------------- +``` + +### `delete` + +Delete a specific index in the task list + +Format: `delete [INDEX]` + +Example of usage: + +`delete 2` + +Expected outcome: +``` +Deleted [D][ ] placeholder2 (June 25th 6pm) +---------------------------------------------------------- +list +1.[T][X] placeholder1 +2.[E][ ] placeholder3 (from: 3pm to: 6pm) +---------------------------------------------------------- +``` + +### `unmark` + +Unmarks the specified index in the list + +Format: `unmark [INDEX]` -Description of the outcome. +Example of usage: +`unmark 1` + +Expected outcome: ``` -expected output +Unmarked placeholder1 +---------------------------------------------------------- +list +1.[T][ ] placeholder1 +2.[E][ ] placeholder3 (from: 3pm to: 6pm) +---------------------------------------------------------- ``` + +### `find` + +Finds and prints all tasks that contain the specified keyword (only applies to task description) + +Format: `find [KEYWORD]` + +Example of usage: + +`find 3` + +Expected outcome: +``` +[E][ ] placeholder3 (from: 3pm to: 6pm) +---------------------------------------------------------- +``` + +### `help` + +Lists all commands that can be inputted by the user + +Format: `help` + +Example of usage: + +`help` + +Expected outcome: +``` +list + - prints all items in list + - Format: list +mark/unmark + - marks/unmarks an item in the list + - Format: mark/unmark [INDEX] +delete + - deletes an item in the list + - Format: delete [INDEX] +find + - finds all task descriptions that contain a keyword + - Format: find [KEYWORD] +todo + - creates a todo task with no deadline + - Format: todo [TASK]deadline + - creates a task with a deadline + - Format: deadline [TASK] /[DUEDATE] +event + - creates a task with start and end time/date + - Format: event [TASK] /[STARTTIME] /[ENDTIME] +---------------------------------------------------------- +``` + 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/duke/Command.java b/src/main/java/duke/Command.java new file mode 100644 index 000000000..ea72f8220 --- /dev/null +++ b/src/main/java/duke/Command.java @@ -0,0 +1,168 @@ +package duke; + +/** + * Class that contains the executions for all possible commands + */ +public class Command { + + private static final String HELP_TEXT = "list\n" + + " - prints all items in list\n" + + " - Format: list\n" + + "mark/unmark\n" + + " - marks/unmarks an item in the list\n" + + " - Format: mark/unmark [INDEX]\n" + + "delete\n" + + " - deletes an item in the list\n" + + " - Format: delete [INDEX]\n" + + "find\n" + + " - finds all task descriptions that contain a keyword\n" + + " - Format: find [KEYWORD]\n" + + "todo\n" + + " - creates a todo task with no deadline\n" + + " - Format: todo [TASK]" + + "deadline\n" + + " - creates a task with a deadline\n" + + " - Format: deadline [TASK] /[DUEDATE]\n" + + "event\n" + + " - creates a task with start and end time/date\n" + + " - Format: event [TASK] /[STARTTIME] /[ENDTIME]\n"; + + private static TaskList taskList = new TaskList(); + + private static UnparsedTaskList inputList = new UnparsedTaskList(); + + /** + * Prints out all items in the task list + */ + public static void list() { + for (int i = 0; i < taskList.size(); i++) { + System.out.println((i+1)+"."+taskList.get(i)); + } + } + + /** + * Tries to add a task to the task list. + * If a proper command is not given asks user to provide a proper input + * Also adds the raw command into the inputlist to be saved on their device + * + * @param input Input provided by the user + */ + public static void tryAddTask(String input) { + try { + taskList.add(addTask(input)); + inputList.add(input); + System.out.println("added: " + taskList.get(taskList.size() - 1)); + } catch (PythiaException pe) { + System.out.println("Please provide a proper input"); + } + } + + /** + * Adds a task without the 'added ...' message appearing + * Used only on startup when the locally saved list is retrieved + * + * @param input Input from the savedlist.txt file + */ + public static void directAddTask(String input) { + try { + taskList.add(addTask(input)); + inputList.add(input); + } catch (PythiaException pe) { + System.out.println("Please provide a proper input"); + } + } + + /** + * Parses the input string and returns the appropriate task type + * + * @param task The user input the contains the task command + * @return A Task type variable + * @throws PythiaException Thrown if the required number of fields are not present + */ + public static Task addTask(String task) throws PythiaException{ + Parser taskParser = new Parser(); + String[] parsedTask; + if (task.contains("todo ")) { + task = task.replaceFirst("todo", ""); + if (task.isBlank()) { + throw new PythiaException(); + } + return new Todo(task); + } else if (task.contains("deadline ")) { + parsedTask = taskParser.parseDeadline(task); + if (parsedTask.length < 2) { + throw new PythiaException(); + } + return new Deadline(parsedTask[0], parsedTask[1]); + } else if (task.contains("event ")){ + parsedTask = taskParser.parseEvent(task); + if (parsedTask.length < 3) { + throw new PythiaException(); + } + return new Event(parsedTask[0], parsedTask[1], parsedTask[2]); + } else { + throw new PythiaException(); + } + } + + /** + * Updates the Done status of a task to be false + * + * @param input The user input that contains the unmark command + */ + public static void unmark(String input) { + String[] splitInput = input.split(" "); + taskList.get(Integer.parseInt(splitInput[1])-1).doneIsFalse(); + System.out.println("Unmarked "+ taskList.get(Integer.parseInt(splitInput[1])-1).getDescription()); + } + + /** + * Updates the Done status of a task to be true + * + * @param input The user input that contains the mark command + */ + public static void mark(String input) { + String[] splitInput = input.split(" "); + taskList.get(Integer.parseInt(splitInput[1])-1).doneIsTrue(); + System.out.println("Marked "+ taskList.get(Integer.parseInt(splitInput[1])-1).getDescription()); + } + + /** + * Deletes a specific task from both the taskList and the inputList + * + * @param input The user input that contains the index of the item they want to delete + */ + public static void delete(String input) { + String[] splitInput = input.split(" "); + int indexToRemove = Integer.parseInt(splitInput[1])-1; + System.out.println("Deleted " + taskList.get(Integer.parseInt(splitInput[1])-1)); + taskList.remove(taskList.get(indexToRemove)); + inputList.remove(inputList.get(indexToRemove)); + } + + /** + * Finds and prints all tasks that contain the keyword specified by the user + * + * @param input User input that contains the keyword they would like to find + */ + public static void find(String input) { + boolean isFound = false; + String[] splitInput = input.split(" "); + for (int i = 0; i < taskList.size(); i++) { + if (taskList.get(i).getDescription().contains(splitInput[1])) { + isFound = true; + System.out.println(taskList.get(i)); + } + } + if (!isFound) { + System.out.println("No such keyword exists"); + } + } + + /** + * Prints out a list of commands that the user can perform + */ + public static void help() { + System.out.print(HELP_TEXT); + } +} diff --git a/src/main/java/duke/Deadline.java b/src/main/java/duke/Deadline.java new file mode 100644 index 000000000..edc7a1208 --- /dev/null +++ b/src/main/java/duke/Deadline.java @@ -0,0 +1,16 @@ +package duke; + +public class Deadline extends Task { + protected String by; + + public Deadline(String description, String by) { + super(description); + this.by = by.replaceFirst("by", "by:"); + this.taskId = "[D]"; + } + + @Override + public String toString() { + return taskId + this.getDoneStatus() + " " + description + " (" + by + ")"; + } +} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java new file mode 100644 index 000000000..bc0c8bd9b --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,31 @@ +package duke; + +/** + * Main class of the programme + */ +public class Duke { + private UI input; + private Storage storage; + + /** + * Constructor class to instantiate variables + */ + public Duke() { + input = new UI(); + storage = new Storage(); + } + + /** + * Main logic flow of the programme + */ + public void run() { + storage.tryRetrieveList(); + System.out.println(MoodSprite.getIdle() + "Hello, Im Pythia, input 'help' if you would like to know what I can do!\n"+ MoodSprite.getLineBreak()); + input.mainLoop(); + System.out.println(MoodSprite.getHappy()+"Happy to help, have a great day.\n"+ MoodSprite.getLineBreak()); + } + + public static void main(String[] args) { + new Duke().run(); + } +} diff --git a/src/main/java/duke/Event.java b/src/main/java/duke/Event.java new file mode 100644 index 000000000..e0162a3b0 --- /dev/null +++ b/src/main/java/duke/Event.java @@ -0,0 +1,18 @@ +package duke; + +public class Event extends Task{ + protected String from; + protected String to; + + public Event(String description, String from, String to) { + super(description); + this.from = from.replaceFirst("from", "from:"); + this.to = to.replaceFirst("to", "to:"); + this.taskId = "[E]"; + } + + @Override + public String toString() { + return taskId + this.getDoneStatus() + " " + description + " (from: " + from + " to: " + to + ")"; + } +} diff --git a/src/main/java/duke/MoodSprite.java b/src/main/java/duke/MoodSprite.java new file mode 100644 index 000000000..6d8444d0f --- /dev/null +++ b/src/main/java/duke/MoodSprite.java @@ -0,0 +1,50 @@ +package duke; + +public class MoodSprite { + private static final String HAPPY = " ███ \n" + + " ███████ \n" + + " ████ ████ \n" + + " ███ ███ \n" + + " ██ ██ \n" + + " ██ ██\n" + + " ███ ███ \n" + + " ███████████████ \n\n"; + + private static final String IDLE = " █████████████ \n" + + " ██ ██ \n" + + " ██ █████ ██ \n" + + " ██ █████ ██ \n" + + " ██ ██ \n" + + " █████████████ \n" + + " ██ ██\n" + + " ██████████████████ \n\n"; + + private static final String ANGRY = " ███ ███ \n" + + " ██ ██ ███ ██ \n" + + " ██ █████ ██ \n" + + " ██ █████ ██ \n" + + " ██ ██ \n" + + " █████████████ \n" + + " \n" + + " ███████████████ \n" + + " ███ ███ \n" + + " ██ ██\n\n"; + + private static final String LINEBREAK = "----------------------------------------------------------"; + + public static String getHappy() { + return HAPPY; + } + + public static String getIdle() { + return IDLE; + } + + public static String getLineBreak() { + return LINEBREAK; + } + + public static String getAngry() { + return ANGRY; + } +} diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java new file mode 100644 index 000000000..a0c965408 --- /dev/null +++ b/src/main/java/duke/Parser.java @@ -0,0 +1,94 @@ +package duke; + +public class Parser { + private Storage storage = new Storage(); + + /** + * Tries to parse the input. + * If an exception is thrown prints out that it is not a valid command + * + * @param input The user input read from UI + */ + public void tryParseInput(String input) { + try { + parseInput(input); + } catch (PythiaException pe) { + System.out.println(MoodSprite.getAngry() + "Not a valid command\n" + MoodSprite.getLineBreak()); + } + } + + /** + * Parses the input to determine what command was given and executes the relevant command + * + * @param input Input given by user + * @throws PythiaException Thrown if no recognised commands are found in the user input + */ + public void parseInput(String input) throws PythiaException { + if (input.equalsIgnoreCase("list")) { + Command.list(); + } else if (isTaskCommand(input)) { + Command.tryAddTask(input); + } else if (input.contains("unmark ")) { + Command.unmark(input); + } else if (input.contains("mark ")) { + Command.mark(input); + } else if (input.contains("delete ")) { + Command.delete(input); + } else if (input.contains("find ")) { + Command.find(input); + } else if (input.contains("help")) { + Command.help(); + } else if (input.contains("bye")) { + return; + } else { + throw new PythiaException(); + } + storage.trySaveList(); + System.out.println(MoodSprite.getLineBreak()); + } + + /** + * Removes any extra spaces for each of the inputs + * + * @param splitTask An array which contains a split version of the user input + * @return Trimmed split array + */ + public String[] splitTaskTrimmer(String[] splitTask) { + for (int i = 0; i < splitTask.length; i++) { + splitTask[i] = splitTask[i].trim(); + } + return splitTask; + } + + /** + * Parses the input containing deadline command + * + * @param task The deadline command provided by the user + * @return A array with just the time of the deadline + */ + public String[] parseDeadline(String task) { + task = task.replaceFirst("deadline", ""); + return splitTaskTrimmer(task.split("/")); + } + + /** + * Parses an input containing an event command + * + * @param task The event command provided by the user + * @return An array with the start and end times of the event + */ + public String[] parseEvent(String task) { + task = task.replaceFirst("event", ""); + return splitTaskTrimmer((task.split("/"))); + } + + /** + * Determines if a string contains one of the task related commands (todo, deadline, event) + * + * @param task The string containing the input from the user + * @return True if the input contains a task command and false otherwise + */ + public boolean isTaskCommand(String task) { + return (task.contains("todo") | task.contains("deadline") | task.contains("event")); + } +} diff --git a/src/main/java/duke/PythiaException.java b/src/main/java/duke/PythiaException.java new file mode 100644 index 000000000..828b7d9f8 --- /dev/null +++ b/src/main/java/duke/PythiaException.java @@ -0,0 +1,5 @@ +package duke; + +public class PythiaException extends Exception{ + +} diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java new file mode 100644 index 000000000..17778c881 --- /dev/null +++ b/src/main/java/duke/Storage.java @@ -0,0 +1,62 @@ +package duke; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Scanner; + +/** + * Class that deals with storage and retrieval of task list + */ +public class Storage { + private static UnparsedTaskList inputList = new UnparsedTaskList(); + + /** + * Tries to save the list locally if the file is not found it creates it instead + */ + public void trySaveList() { + try { + saveList(); + } catch (IOException e) { + System.out.println("savedList not found, creating now"); + } + } + + /** + * Saves list the list locally the users device so that the list data can be later retrieved + * + * @throws IOException Thrown savedList.txt is not found + */ + public void saveList() throws IOException { + FileWriter fileInput = new FileWriter("./savedList.txt"); + for (int i = 0; i < inputList.size(); i++) { + fileInput.write(inputList.get(i) + "\n"); + } + fileInput.close(); + } + + /** + * Tries to retrieve the list stored locally, prints error message if the list does not exist + */ + public void tryRetrieveList() { + try { + retrieveList(); + } catch (FileNotFoundException e) { + System.out.println(MoodSprite.getHappy() + "Saved list not found, creating now\n" + MoodSprite.getLineBreak()); + } + } + + /** + * Reads from locally saved text file and adds the tasks to the task list, creates file if it doesnt exist + * + * @throws FileNotFoundException Thrown when the file does not exist + */ + public void retrieveList() throws FileNotFoundException { + File f = new File("./savedList.txt"); + Scanner in = new Scanner(f); + while (in.hasNext()) { + Command.directAddTask(in.nextLine()); + } + } +} diff --git a/src/main/java/duke/Task.java b/src/main/java/duke/Task.java new file mode 100644 index 000000000..90fb04107 --- /dev/null +++ b/src/main/java/duke/Task.java @@ -0,0 +1,34 @@ +package duke; + +public class Task { + protected String description; + protected boolean isDone; + protected String taskId; + + public Task(String description) { + this.description = description; + isDone = false; + } + + public void doneIsFalse() { + isDone = false; + } + + public void doneIsTrue() { + isDone = true; + } + + public String getDoneStatus() { + return (isDone ? "[X]" : "[ ]"); + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + return taskId + this.getDoneStatus() + " " + description; + } + +} diff --git a/src/main/java/duke/TaskList.java b/src/main/java/duke/TaskList.java new file mode 100644 index 000000000..713492def --- /dev/null +++ b/src/main/java/duke/TaskList.java @@ -0,0 +1,47 @@ +package duke; + +import java.util.ArrayList; + +/** + * Class that holds all the parsed tasks + */ +public class TaskList { + private static ArrayList parsedTaskList = new ArrayList<>(2); + + /** + * Returns the Task at the chosen index + * + * @param i The chosen index + * @return The chosen Task + */ + public Task get(int i) { + return parsedTaskList.get(i); + } + + /** + * Adds the Task into the list + * + * @param input Task that is to be added to the list + */ + public void add(Task input) { + parsedTaskList.add(input); + } + + /** + * Removes chosen Task from list + * + * @param input Task to be removed + */ + public void remove(Task input) { + parsedTaskList.remove(input); + } + + /** + * Returns the current size of the task list + * + * @return Length of list + */ + public int size() { + return parsedTaskList.size(); + } +} diff --git a/src/main/java/duke/Todo.java b/src/main/java/duke/Todo.java new file mode 100644 index 000000000..7de01e430 --- /dev/null +++ b/src/main/java/duke/Todo.java @@ -0,0 +1,9 @@ +package duke; + +public class Todo extends Task{ + + public Todo (String description) { + super(description); + this.taskId = "[T]"; + } +} diff --git a/src/main/java/duke/UI.java b/src/main/java/duke/UI.java new file mode 100644 index 000000000..6357762be --- /dev/null +++ b/src/main/java/duke/UI.java @@ -0,0 +1,21 @@ +package duke; +import java.util.Scanner; + +/** + * A class that acts as the User Interface (UI) to take in inputs from the user + */ +public class UI { + private Parser parser = new Parser(); + + /** + * the main loop of the programme which keeps running until the user inputs 'bye' + */ + public void mainLoop() { + String input = "Start"; + Scanner in = new Scanner(System.in); + while (!input.equals("bye")) { + input = in.nextLine(); + parser.tryParseInput(input); + } + } +} diff --git a/src/main/java/duke/UnparsedTaskList.java b/src/main/java/duke/UnparsedTaskList.java new file mode 100644 index 000000000..985b09e59 --- /dev/null +++ b/src/main/java/duke/UnparsedTaskList.java @@ -0,0 +1,47 @@ +package duke; + +import java.util.ArrayList; + +/** + * Class contains a static list that holds unparsed task commands, to be saved locally to the users device + */ +public class UnparsedTaskList { + private static ArrayList unparsedTaskList = new ArrayList<>(2); + + /** + * Returns the unparsed task at the chosen index + * + * @param i The chosen index + * @return The chosen unparsed task + */ + public String get(int i) { + return unparsedTaskList.get(i); + } + + /** + * Adds the unparsed task into the list + * + * @param input unparsed task that is to be added to the list + */ + public void add(String input) { + unparsedTaskList.add(input); + } + + /** + * Removes chosen unparsed task from list + * + * @param input Unparsed task to be removed + */ + public void remove(String input) { + unparsedTaskList.remove(input); + } + + /** + * Returns the current size of the unparsed task list + * + * @return Length of list + */ + public int size() { + return unparsedTaskList.size(); + } +}