diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..00a51aff5e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000000..391c46b4fe --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,34 @@ +name: Java CI + +on: [push, pull_request] + +jobs: + build: + strategy: + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.platform }} + + steps: + - name: Set up repository + uses: actions/checkout@master + + - name: Set up repository + uses: actions/checkout@master + with: + ref: master + + - name: Merge to master + run: git checkout --progress --force ${{ github.sha }} + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v1 + + - name: Setup JDK 11 + uses: actions/setup-java@v1 + with: + java-version: '11' + java-package: jdk+fx + + - name: Build and check with Gradle + run: ./gradlew check diff --git a/.gitignore b/.gitignore index 2873e189e1..f3d2d056cc 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,10 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT +data/ + +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..7e7f1ec503 --- /dev/null +++ b/build.gradle @@ -0,0 +1,61 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +checkstyle { + toolVersion = '10.2' +} + +repositories { + mavenCentral() +} + +dependencies { + String javaFxVersion = '11' + + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' + + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "roody.Roody" +} + +shadowJar { + archiveBaseName = "roody" + archiveClassifier = null +} + +run{ + standardInput = System.in +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..fb88cedfc2 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..135ea49ee0 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/docs/DaRoody - Docs.png b/docs/DaRoody - Docs.png new file mode 100644 index 0000000000..3da2cae9a3 Binary files /dev/null and b/docs/DaRoody - Docs.png differ diff --git a/docs/README.md b/docs/README.md index 8077118ebe..0cdff08d28 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,129 @@ # User Guide +Roody is a Java application that builds upon a Command-Line Interface to allow +users to manage their tasks and displays them via a Graphical Interface. +His bright personality comes from an 8-year-old dog named Roody. -## Features +![Picture of Roody](./DaRoody%20-%20Docs.png) -### Feature-ABC +## Requirements +- Java 11 -Description of the feature. +## Quick Start +1. Download the latest release of Roody [here](https://github.com/ryanchua00/ip/releases). + 1. Place the jar in a seperate directory named Roody. +2. From the command line, `cd` to the directory. +3. Execute `java -jar roody.jar` to start. -### Feature-XYZ +## Features -Description of the feature. +### Feature - Adding Tasks -## Usage +Roody can create three different types of tasks: +- To Do +- Deadline +- Event -### `Keyword` - Describe action +### Feature - Marking Tasks -Describe the action and its outcome. +Roody can mark tasks as completed or incomplete, tracking your progress. -Example of usage: +### Feature - Delete Tasks -`keyword (optional arguments)` +Roody can delete tasks. -Expected outcome: +### Feature - List -Description of the outcome. +Roody can display all tasks that you have currently. -``` -expected output -``` +### Feature - Finding Tasks + +Roody can take in specific keywords to find tasks that match. + +### Feature - Help + +Roody can inform you of different commands and their usages. + +## Usage + +### `help` - Asking for Help + +Gives descriptions of commands and their usage. + +Example of usage: + +- `help` +- `help c` +- `help tc` +- `help tm` + +### `list` - Listing all Tasks + +Lists all tasks present in the database. + +Example of usage: + +- `list` + +### `todo` - Creating a Todo + +Creates a Todo task and displays it. + +Example of usage: + +- `todo (description)` + +### `deadline` - Creating a Deadline + +Creates a Deadline task with a specified deadline and displays it. + +Example of usage: + +- `deadline (description) /by (date)` + +### `event` - Creating an Event + +Creates an Event task with a specified start and end date and displays it. + +Example of usage: + +- `event (description) /from (date) /to (date)` + +### `mark` - Marking a Task + +Marks the task with a specified index as complete. + +Example of usage: + +- `mark (index)` + +### `unmark` - Unmarking a Task + +Marks the task with a specified index as incomplete. + +Example of usage: + +- `unmark (index)` + +### `delete` - Deleting a Task + +Deletes a task with a specified index. + +Example of usage: + +- `delete (index)` + +### `find` - Finding relevant Tasks + +Searches for relevant tasks using the specified keyword. + +Example of usage: + +- `find (keyword)` + +### `bye` - Saving Tasks + +Saves the current tasks in the list. + +Example of usage: + +- `bye` diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..cc4fce3f80 Binary files /dev/null and b/docs/Ui.png differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..f3d88b1c2f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..b7c8c5dbf5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..2fe81a7d95 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..62bd9b9cce --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..4114212f88 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/6.2/userguide/multi_project_builds.html + */ + +rootProject.name = 'iP CS2103T' diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- 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/roody/Parser.java b/src/main/java/roody/Parser.java new file mode 100644 index 0000000000..7b0a342e73 --- /dev/null +++ b/src/main/java/roody/Parser.java @@ -0,0 +1,198 @@ +package roody; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.Arrays; + +import roody.commands.Command; +import roody.commands.DeleteCommand; +import roody.commands.EndCommand; +import roody.commands.FindCommand; +import roody.commands.HelpCommand; +import roody.commands.ListCommand; +import roody.commands.MakeDeadlineCommand; +import roody.commands.MakeEventCommand; +import roody.commands.MakeTodoCommand; +import roody.commands.MarkCommand; +import roody.commands.StartCommand; +import roody.commands.UnmarkCommand; +import roody.exceptions.DateFormatException; +import roody.exceptions.InputFormatException; +import roody.exceptions.RoodyException; + +/** + * Represents a parser to take in user input. + */ +public class Parser { + static final int SPLIT_ONCE = 2; + static final int SPLIT_ALL = 0; + + public Parser() {} + + /** + * Returns a array of strings with commands that are extracted from user input. + * @param command Input string. + * @return Commands. + * @throws RoodyException If faulty input is detected. + */ + public static Command parse(String command) throws RoodyException { + // splits once by whitespace to filter by first input + String[] commands = command.toLowerCase().split("\\s", SPLIT_ONCE); + // checks for '/' input count + String[] splitCmds = command.split("/", SPLIT_ALL); + if (commands[0].equals("event")) { + commands = parseEvent(splitCmds); + } else if (commands[0].equals("deadline")) { + commands = parseDeadline(splitCmds); + } + // checks formatting of commands + checkInputFormat(commands, splitCmds); + return createCommand(commands); + } + + private static void checkInputFormat(String[] commands, String[] splitCmds) throws RoodyException { + switch (commands[0]) { + case "start": + case "list": + case "bye": + if (commands.length != 1) { + throw new InputFormatException(InputFormatException.CMD); + } + break; + case "todo": + if (splitCmds.length != 1) { + throw new InputFormatException(InputFormatException.CMD_TODO); + } + break; + case "deadline": + if (splitCmds.length != 2) { + throw new InputFormatException(InputFormatException.CMD_DEADLINE); + } + try { + LocalDate.parse(commands[2]); + } catch (DateTimeParseException e) { + throw new DateFormatException(); + } + break; + case "event": + if (splitCmds.length != 3) { + throw new InputFormatException(InputFormatException.CMD_EVENT); + } + try { + LocalDate.parse(commands[2]); + LocalDate.parse(commands[3]); + } catch (DateTimeParseException e) { + throw new DateFormatException(); + } + break; + case "delete": + case "mark": + case "unmark": + if (commands.length != 2) { + throw new InputFormatException(InputFormatException.CMD_INDEX); + } + try { + Integer.parseInt(commands[1]); + } catch (NumberFormatException e) { + throw new InputFormatException(InputFormatException.CMD_INDEX); + } + break; + case "find": + if (commands.length != 2) { + throw new InputFormatException(InputFormatException.CMD_KEY); + } + break; + case "help": + // nothing since help can have multiple inputs + break; + default: + throw new RoodyException("I don't quite understand that. Type \"help\" for assistance!"); + } + + } + + private static Command createCommand(String[] inputs) throws RoodyException { + switch (inputs[0]) { + case "start": + return new StartCommand(); + case "list": + return new ListCommand(); + case "bye": + return new EndCommand(); + case "todo": + return new MakeTodoCommand(inputs[1]); + case "deadline": + return new MakeDeadlineCommand(inputs[1], LocalDate.parse(inputs[2])); + case "event": + return new MakeEventCommand(inputs[1], LocalDate.parse(inputs[2]), LocalDate.parse(inputs[3])); + case "delete": + return new DeleteCommand(Integer.parseInt(inputs[1]) - 1); + case "mark": + return new MarkCommand(Integer.parseInt(inputs[1]) - 1); + case "unmark": + return new UnmarkCommand(Integer.parseInt(inputs[1]) - 1); + case "find": + return new FindCommand(inputs[1]); + case "help": + if (inputs.length == 1) { + return new HelpCommand("start"); + } else { + return new HelpCommand(inputs[1]); + } + default: + throw new RoodyException("Error in Parser"); + } + } + + private static String[] parseEvent(String[] splitCmd) throws RoodyException { + System.out.println(Arrays.toString(splitCmd)); + boolean isWrongFlag = !(splitCmd[1].trim().substring(0, 4).equals("from") + && splitCmd[2].trim().substring(0, 2).equals("to")); + if (splitCmd.length != 3 || isWrongFlag) { + throw new RoodyException("No date was detected! - /from {date} /to {date}"); + } + + // maximum length should be 3 + assert splitCmd.length <= 3 : "Error while parsing, too many inputs"; + + // seperates command from description + String[] commands = new String[splitCmd.length + 1]; + for (int i = 1; i < splitCmd.length; i++) { + commands[i + 1] = splitCmd[i]; + } + String[] cmdAndDesc = splitCmd[0].split("\\s", SPLIT_ONCE); + commands[0] = cmdAndDesc[0]; + commands[1] = cmdAndDesc[1]; + + // seperates additional inputs + commands[2] = splitCmd[1].split("\\s", SPLIT_ALL)[1]; + commands[3] = splitCmd[2].split("\\s", SPLIT_ALL)[1]; + + return commands; + } + + private static String[] parseDeadline(String[] splitCmd) throws RoodyException { + // if wrong number of inputs or wrong input flag + boolean isWrongFlag = !splitCmd[1].substring(0, 2).equals("by"); + if (splitCmd.length != 2 || isWrongFlag) { + throw new RoodyException("No date was detected! - /by {date}"); + } + + // maximum length should be 3 + assert splitCmd.length <= 2 : "Error while parsing, too many inputs"; + + // seperates command from description + String[] commands = new String[splitCmd.length + 1]; + for (int i = 1; i < splitCmd.length; i++) { + commands[i + 1] = splitCmd[i]; + } + String[] cmdAndDesc = splitCmd[0].split("\\s", SPLIT_ONCE); + commands[0] = cmdAndDesc[0]; + commands[1] = cmdAndDesc[1]; + + // seperates additional inputs + commands[2] = splitCmd[1].split("\\s", SPLIT_ALL)[1]; + + return commands; + } +} diff --git a/src/main/java/roody/Roody.java b/src/main/java/roody/Roody.java new file mode 100644 index 0000000000..b0752e198a --- /dev/null +++ b/src/main/java/roody/Roody.java @@ -0,0 +1,59 @@ +package roody; + +import java.util.ArrayList; + +import javafx.application.Application; +import roody.commands.Command; +import roody.exceptions.RoodyException; +import roody.gui.RoodyMain; +import roody.tasks.Task; + +/** + * Represents a CLI chatbot named Roody. + */ +public class Roody { + /** Stores tasks */ + private ArrayList taskList; + + /** Manages loading/saving of task data */ + private Storage storage; + + /** Manages GUI */ + private Ui ui; + + /** + * Creates a chatbot with specified filepath to task data. + * @param filepath The filepath to task data. + */ + public Roody(String filepath) { + // Assumed no more than 100 tasks + this.taskList = new ArrayList(); + this.ui = new Ui(); + this.storage = new Storage(filepath); + taskList = storage.loadFile(); + } + + /** + * Returns a String response to given input. + * @param input A String to respond to. + * @return A Roody response. + */ + public String getResponse(String input) { + String message = ""; + try { + Command c = Parser.parse(input); + message += c.execute(taskList, ui, storage); + } catch (RoodyException e) { + message += e.getMessage(); + } + return message; + } + + /** + * Runs Roody's main process. + * @param args Args. + */ + public static void main(String[] args) { + Application.launch(RoodyMain.class, args); + } +} diff --git a/src/main/java/roody/Storage.java b/src/main/java/roody/Storage.java new file mode 100644 index 0000000000..8a322416cc --- /dev/null +++ b/src/main/java/roody/Storage.java @@ -0,0 +1,122 @@ +package roody; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Scanner; + +import roody.exceptions.RoodyException; +import roody.tasks.Deadline; +import roody.tasks.Event; +import roody.tasks.Task; +import roody.tasks.Todo; + +/** + * Represents the data storage handler + */ +public class Storage { + /** filepath to data file */ + private String filePath; + /** filepath to folder */ + private String defaultFolderPath = "./data"; + + /** + * Creates a Storage handler with specified filepath to save data. + * @param filePath The filepath to a file to save data on. + */ + public Storage(String filePath) { + this.filePath = filePath; + } + + /** + * Loads the data file. + * Creates directory if necessary. + * @return File found/created. + */ + private File initializeFile() { + File data = new File(filePath); + File folder = new File(defaultFolderPath); + if (!folder.exists()) { + folder.mkdir(); + } + return data; + } + + /** + * Scans data file into given ArrayList. + * @param data File to extract data from. + * @param list List to write data to. + * @throws RoodyException If faulty data is loaded. + * @throws FileNotFoundException If file is not found. + */ + private void scanFile(File data, ArrayList list) throws RoodyException, FileNotFoundException { + Scanner s = new Scanner(data); + while (s.hasNextLine()) { + String task = s.nextLine(); + String[] inputs = task.split("\\|"); + // inputs should be less than 5 + assert inputs.length <= 5 : "Error while loading, too many arguments"; + // filter by task + Task temp; + switch(inputs[2]) { + case "T": + temp = new Todo(inputs[0]); + break; + case "D": + temp = new Deadline(inputs[0], LocalDate.parse(inputs[3])); + break; + case "E": + temp = new Event(inputs[0], LocalDate.parse(inputs[3]), LocalDate.parse(inputs[4])); + break; + default: + throw new RoodyException("Error loading text"); + } + if (inputs[1].equals("true")) { + temp.setDone(); + } + list.add(temp); + } + s.close(); + } + + /** + * Returns information from the data file. + * If folder not present, creates a new folder. + * If file not present, creates a new file. + * @return Tasks. + */ + public ArrayList loadFile() { + ArrayList list = new ArrayList<>(); + try { + File data = initializeFile(); + if (!data.createNewFile()) { + scanFile(data, list); + } + } catch (IOException | RoodyException e) { + System.out.println("An error occurred.\n" + e.getMessage()); + } + return list; + } + + /** + * Saves Task information, if any, into a preset file. + * @param list The ArrayList of Tasks to be saved. + */ + public void saveFile(ArrayList list) { + ArrayList buffer = new ArrayList<>(); + Path output = Paths.get(filePath); + for (Task t : list) { + buffer.add(t.saveTask()); + } + try { + Files.write(output, buffer); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/roody/Ui.java b/src/main/java/roody/Ui.java new file mode 100644 index 0000000000..deff577aca --- /dev/null +++ b/src/main/java/roody/Ui.java @@ -0,0 +1,143 @@ +package roody; + +import java.util.ArrayList; + +import roody.exceptions.RoodyException; +import roody.tasks.Task; + +/** + * Represents a User Interface. + */ +public class Ui { + + + /** + * Creates a User Interface. + */ + public Ui() {} + + /** + * Returns basic line. + * @return A Line. + */ + public String showLine() { + return "____________________________________________________________\n"; + } + + /** + * Returns start of line + * @return An arrow for start of line. + */ + public String startNextLine() { + return "=> "; + } + + /** + * Returns a successful task addition message. + * @param task Task that has been added. + * @param listLength Length of the current list after addition. + * @return Delete task message. + */ + public String showAddTask(Task task, int listLength) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Got it. I've added this task:\n"); + stringBuilder.append(task.toString() + '\n'); + stringBuilder.append("Now you have " + listLength + " tasks in the list.\n"); + return stringBuilder.toString(); + } + + /** + * Returns a successful task deletion message. + * @param task Task that has been added. + * @param listLength Length of the current list after deletion. + * @return Delete task message. + */ + public String showDeleteTask(Task task, int listLength) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Noted. I've removed this task:\n"); + stringBuilder.append(task.toString() + '\n'); + stringBuilder.append("Now you have " + listLength + " tasks in the list.\n"); + return stringBuilder.toString(); + } + + /** + * Returns a successful marking of task message. + * @param task Task that has been added. + * @return Marking message. + */ + public String showMarkStatus(Task task) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Nice! I've marked this task as done:\n"); + stringBuilder.append(task.toString() + '\n'); + return stringBuilder.toString(); + } + + /** + * Returns a successful unmarking of task message.. + * @param task Task that has been added. + * @return Marking message. + */ + public String showUnmarkStatus(Task task) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("OK, I've marked this task as not done yet:\n"); + stringBuilder.append(task.toString() + '\n'); + return stringBuilder.toString(); + } + + /** + * Shows tasks that are found with relevant keywords in a String. + * @param list List of found tasks. + * @return Tasks with keywords in List. + * @throws RoodyException If no matching tasks found. + */ + public String showFoundTasks(ArrayList list) throws RoodyException { + if (list.size() == 0) { + throw new RoodyException("No matching tasks in your list!"); + } + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Here are the matching tasks in your list:\n"); + Integer listIndex = 0; + for (Task task : list) { + listIndex++; + stringBuilder.append(listIndex.toString() + '.' + task.toString() + '\n'); + } + assert list.size() == listIndex : "Error while printing, task number mismatch"; + return stringBuilder.toString(); + } + + + /** + * Returns tasks in given list as a String. + * @param list List of tasks to be printed. + * @return Tasks in List. + * @throws RoodyException If not tasks in list. + */ + public String printList(ArrayList list) throws RoodyException { + if (list.size() == 0) { + throw new RoodyException("There doesn't seem to be any tasks in your list."); + } + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("Here are the tasks in your list:\n"); + Integer listIndex = 0; + for (Task task : list) { + listIndex++; + stringBuilder.append(listIndex.toString() + ". " + task.toString() + '\n'); + } + assert list.size() == listIndex : "Error while printing, task number mismatch"; + return stringBuilder.toString(); + } + + /** + * Returns basic greeting string. + */ + public String greet() { + return "Hello, I'm Roody!\nWhat can I do for you?\n"; + } + + /** + * Prints basic farewell string. + */ + public String bye() { + return "Bye. Hope to see you again soon!\n"; + } +} diff --git a/src/main/java/roody/commands/Command.java b/src/main/java/roody/commands/Command.java new file mode 100644 index 0000000000..1a31b196b3 --- /dev/null +++ b/src/main/java/roody/commands/Command.java @@ -0,0 +1,15 @@ +package roody.commands; + +import java.util.ArrayList; + +import roody.Storage; +import roody.Ui; +import roody.exceptions.RoodyException; +import roody.tasks.Task; + +/** + * Represents an executable command. + */ +public abstract class Command { + public abstract String execute(ArrayList taskList, Ui ui, Storage storage) throws RoodyException; +} diff --git a/src/main/java/roody/commands/DeleteCommand.java b/src/main/java/roody/commands/DeleteCommand.java new file mode 100644 index 0000000000..86019716ff --- /dev/null +++ b/src/main/java/roody/commands/DeleteCommand.java @@ -0,0 +1,34 @@ +package roody.commands; + +import java.util.ArrayList; + +import roody.Storage; +import roody.Ui; +import roody.exceptions.RoodyException; +import roody.exceptions.TaskNotFoundException; +import roody.tasks.Task; + +/** + * Represents a command to delete a task. + */ +public class DeleteCommand extends Command { + private int taskindex; + + /** + * Creates a delete command. + * @param taskIndex Index of task to be deleted. + */ + public DeleteCommand(int taskIndex) { + this.taskindex = taskIndex; + } + @Override + public String execute(ArrayList taskList, Ui ui, Storage storage) throws RoodyException { + if (taskindex < 0 || taskindex > taskList.size() - 1 || taskList.get(taskindex) == null) { + throw new TaskNotFoundException(); + } else { + Task task = taskList.get(taskindex); + taskList.remove(taskindex); + return ui.showDeleteTask(task, taskList.size()); + } + } +} diff --git a/src/main/java/roody/commands/EndCommand.java b/src/main/java/roody/commands/EndCommand.java new file mode 100644 index 0000000000..f1ea6d14d6 --- /dev/null +++ b/src/main/java/roody/commands/EndCommand.java @@ -0,0 +1,22 @@ +package roody.commands; + +import java.util.ArrayList; + +import roody.Storage; +import roody.Ui; +import roody.tasks.Task; + +/** + * Represents a command to end the chat. + */ +public class EndCommand extends Command { + /** + * Creates an end command. + */ + public EndCommand() {} + @Override + public String execute(ArrayList taskList, Ui ui, Storage storage) { + storage.saveFile(taskList); + return ui.bye(); + } +} diff --git a/src/main/java/roody/commands/FindCommand.java b/src/main/java/roody/commands/FindCommand.java new file mode 100644 index 0000000000..5fed2b710f --- /dev/null +++ b/src/main/java/roody/commands/FindCommand.java @@ -0,0 +1,40 @@ +package roody.commands; + +import java.util.ArrayList; + +import roody.Storage; +import roody.Ui; +import roody.exceptions.RoodyException; +import roody.tasks.Task; + +/** + * Represents a command to find relevant commands by keyword. + */ +public class FindCommand extends Command { + private String keyword; + + /** + * Creates a find command. + * @param keyword Keyword to search for in commands. + */ + public FindCommand(String keyword) { + this.keyword = keyword; + } + @Override + public String execute(ArrayList taskList, Ui ui, Storage storage) throws RoodyException { + ArrayList foundTasks = new ArrayList<>(); + for (Task task : taskList) { + // Splits by "|" + String[] words = task.saveTask().split("\\|"); + // Further splits description by whitespace + String[] desc = words[0].split("\\s"); + for (String word : desc) { + // Searches for a match to keyword + if (word.equals(keyword)) { + foundTasks.add(task); + } + } + } + return ui.showFoundTasks(foundTasks); + } +} diff --git a/src/main/java/roody/commands/HelpCommand.java b/src/main/java/roody/commands/HelpCommand.java new file mode 100644 index 0000000000..3b59b41ee6 --- /dev/null +++ b/src/main/java/roody/commands/HelpCommand.java @@ -0,0 +1,55 @@ +package roody.commands; + +import java.util.ArrayList; + +import roody.Storage; +import roody.Ui; +import roody.exceptions.RoodyException; +import roody.tasks.Task; + +/** + * Represents a command to help the user. + */ +public class HelpCommand extends Command { + static final String START_HELP = "Woof! What do you need help in?\n"; + static final String CMD_LIST = " - For commands, type \"help c\"\n" + + " - For task creation, type \"help tc\"\n" + + " - For task management, type \"help tm\"\n"; + static final String CMD_HELP = "Here are a list of commands you can use!\n" + + " Task Creation: [todo, deadline, event]\n" + + " Task Management: [list, delete, mark, unmark, find]\n" + + " Task saving: [bye]\n"; + static final String TASK_CREATION_HELP = "Here are a few ways to make tasks!\n" + + " - todo {desc}\n" + + " - deadline {desc} /by {date}\n" + + " - event {desc} /from {date} /to {date}\n"; + static final String TASK_MANAGEMENT_HELP = "Here are a few ways you can manage your tasks!\n" + + " - delete {task number}\n" + + " - mark/unmark {task number}\n" + + " - find {keyword}\n"; + + private String query; + + /** + * Creates a help command. + * @param query Help that user requires. + */ + public HelpCommand(String query) { + this.query = query; + } + @Override + public String execute(ArrayList taskList, Ui ui, Storage storage) throws RoodyException { + switch(query) { + case "start": + return START_HELP + CMD_LIST; + case "c": + return CMD_HELP; + case "tc": + return TASK_CREATION_HELP; + case "tm": + return TASK_MANAGEMENT_HELP; + default: + return "Oh no :( I don't quite understand that.\n" + CMD_LIST; + } + } +} diff --git a/src/main/java/roody/commands/ListCommand.java b/src/main/java/roody/commands/ListCommand.java new file mode 100644 index 0000000000..749b1bef2e --- /dev/null +++ b/src/main/java/roody/commands/ListCommand.java @@ -0,0 +1,22 @@ +package roody.commands; + +import java.util.ArrayList; + +import roody.Storage; +import roody.Ui; +import roody.exceptions.RoodyException; +import roody.tasks.Task; + +/** + * Represents a command to list out tasks. + */ +public class ListCommand extends Command { + /** + * Creates a list command. + */ + public ListCommand() {} + @Override + public String execute(ArrayList taskList, Ui ui, Storage storage) throws RoodyException { + return ui.printList(taskList); + } +} diff --git a/src/main/java/roody/commands/MakeDeadlineCommand.java b/src/main/java/roody/commands/MakeDeadlineCommand.java new file mode 100644 index 0000000000..585dad2af1 --- /dev/null +++ b/src/main/java/roody/commands/MakeDeadlineCommand.java @@ -0,0 +1,33 @@ +package roody.commands; + +import java.time.LocalDate; +import java.util.ArrayList; + +import roody.Storage; +import roody.Ui; +import roody.tasks.Deadline; +import roody.tasks.Task; + +/** + * Represents a command to make a deadline task. + */ +public class MakeDeadlineCommand extends Command { + private String description; + private LocalDate deadline; + + /** + * Creates a deadline task. + * @param description Description of deadline. + * @param deadline Date of deadline. + */ + public MakeDeadlineCommand(String description, LocalDate deadline) { + this.description = description; + this.deadline = deadline; + } + @Override + public String execute(ArrayList taskList, Ui ui, Storage storage) { + Task task = new Deadline(description, deadline); + taskList.add(task); + return ui.showAddTask(task, taskList.size()); + } +} diff --git a/src/main/java/roody/commands/MakeEventCommand.java b/src/main/java/roody/commands/MakeEventCommand.java new file mode 100644 index 0000000000..54898df3b7 --- /dev/null +++ b/src/main/java/roody/commands/MakeEventCommand.java @@ -0,0 +1,36 @@ +package roody.commands; + +import java.time.LocalDate; +import java.util.ArrayList; + +import roody.Storage; +import roody.Ui; +import roody.tasks.Event; +import roody.tasks.Task; + +/** + * Represents a command to make an event task. + */ +public class MakeEventCommand extends Command { + private String description; + private LocalDate start; + private LocalDate end; + + /** + * Creates an event task. + * @param description Description. + * @param start Starting date. + * @param end End date. + */ + public MakeEventCommand(String description, LocalDate start, LocalDate end) { + this.description = description; + this.start = start; + this.end = end; + } + @Override + public String execute(ArrayList taskList, Ui ui, Storage storage) { + Task task = new Event(description, start, end); + taskList.add(task); + return ui.showAddTask(task, taskList.size()); + } +} diff --git a/src/main/java/roody/commands/MakeTodoCommand.java b/src/main/java/roody/commands/MakeTodoCommand.java new file mode 100644 index 0000000000..a18ca10172 --- /dev/null +++ b/src/main/java/roody/commands/MakeTodoCommand.java @@ -0,0 +1,29 @@ +package roody.commands; + +import java.util.ArrayList; + +import roody.Storage; +import roody.Ui; +import roody.tasks.Task; +import roody.tasks.Todo; + +/** + * Represents a command that makes a todo. + */ +public class MakeTodoCommand extends Command { + private String description; + + /** + * Creates a todo task and adds it to the list. + * @param description Description of todo. + */ + public MakeTodoCommand(String description) { + this.description = description; + } + @Override + public String execute(ArrayList taskList, Ui ui, Storage storage) { + Task task = new Todo(description); + taskList.add(task); + return ui.showAddTask(task, taskList.size()); + } +} diff --git a/src/main/java/roody/commands/MarkCommand.java b/src/main/java/roody/commands/MarkCommand.java new file mode 100644 index 0000000000..b5ea3280e5 --- /dev/null +++ b/src/main/java/roody/commands/MarkCommand.java @@ -0,0 +1,34 @@ +package roody.commands; + +import java.util.ArrayList; + +import roody.Storage; +import roody.Ui; +import roody.exceptions.TaskNotFoundException; +import roody.tasks.Task; + + + +/** + * Represents a mark command. + */ +public class MarkCommand extends Command { + private int taskindex; + + /** + * Creates a mark command. + * @param taskindex Index of task to be marked. + */ + public MarkCommand(int taskindex) { + this.taskindex = taskindex; + } + @Override + public String execute(ArrayList taskList, Ui ui, Storage storage) throws TaskNotFoundException { + if (taskindex < 0 || taskindex > taskList.size() - 1 || taskList.get(taskindex) == null) { + throw new TaskNotFoundException(); + } + Task task = taskList.get(taskindex); + task.setDone(); + return ui.showMarkStatus(task); + } +} diff --git a/src/main/java/roody/commands/StartCommand.java b/src/main/java/roody/commands/StartCommand.java new file mode 100644 index 0000000000..8a2d6024a3 --- /dev/null +++ b/src/main/java/roody/commands/StartCommand.java @@ -0,0 +1,21 @@ +package roody.commands; + +import java.util.ArrayList; + +import roody.Storage; +import roody.Ui; +import roody.tasks.Task; + +/** + * Represents a start command. + */ +public class StartCommand extends Command { + /** + * Creates a start command. + */ + public StartCommand() {} + @Override + public String execute(ArrayList taskList, Ui ui, Storage storage) { + return ui.greet(); + } +} diff --git a/src/main/java/roody/commands/UnmarkCommand.java b/src/main/java/roody/commands/UnmarkCommand.java new file mode 100644 index 0000000000..162481ae73 --- /dev/null +++ b/src/main/java/roody/commands/UnmarkCommand.java @@ -0,0 +1,32 @@ +package roody.commands; + +import java.util.ArrayList; + +import roody.Storage; +import roody.Ui; +import roody.exceptions.TaskNotFoundException; +import roody.tasks.Task; + +/** + * Represents an Unmark command. + */ +public class UnmarkCommand extends Command { + private int taskindex; + + /** + * Creates an Unmark command. + * @param taskindex Index of task to unmark. + */ + public UnmarkCommand(int taskindex) { + this.taskindex = taskindex; + } + @Override + public String execute(ArrayList taskList, Ui ui, Storage storage) throws TaskNotFoundException { + if (taskindex < 0 || taskindex > taskList.size() - 1 || taskList.get(taskindex) == null) { + throw new TaskNotFoundException(); + } + Task task = taskList.get(taskindex); + task.setUnDone(); + return ui.showUnmarkStatus(task); + } +} diff --git a/src/main/java/roody/exceptions/DateFormatException.java b/src/main/java/roody/exceptions/DateFormatException.java new file mode 100644 index 0000000000..da4cd6d702 --- /dev/null +++ b/src/main/java/roody/exceptions/DateFormatException.java @@ -0,0 +1,10 @@ +package roody.exceptions; + +/** + * Represents a custom date format exception + */ +public class DateFormatException extends RoodyException { + public DateFormatException() { + super("Accepted date format is yyyy-mm-dd."); + } +} diff --git a/src/main/java/roody/exceptions/InputFormatException.java b/src/main/java/roody/exceptions/InputFormatException.java new file mode 100644 index 0000000000..fa32588a66 --- /dev/null +++ b/src/main/java/roody/exceptions/InputFormatException.java @@ -0,0 +1,17 @@ +package roody.exceptions; + +/** + * A custom exception for incorrect inputs + */ +public class InputFormatException extends RoodyException { + public static final String CMD = "I expected \"{Command}\" but got something else"; + public static final String CMD_INDEX = "I expected \"{Command} {Index}\" but got something else"; + public static final String CMD_KEY = "I expected \"{Command} {Keyword}\" but got something else"; + public static final String CMD_TODO = "I expected \"{Command} {Index}\" but got something else"; + public static final String CMD_DEADLINE = "I expected \"{Command} {Desc} /by {DateTime}\" but got something else"; + public static final String CMD_EVENT = "I expected \"{Command} {Desc} /from {DateTime} /to {DateTime}\" " + + "but got something else"; + public InputFormatException(String s) { + super(s); + } +} diff --git a/src/main/java/roody/exceptions/RoodyException.java b/src/main/java/roody/exceptions/RoodyException.java new file mode 100644 index 0000000000..e048fcd966 --- /dev/null +++ b/src/main/java/roody/exceptions/RoodyException.java @@ -0,0 +1,10 @@ +package roody.exceptions; + +/** + * Represents a custom exception for Roody chatbot. + */ +public class RoodyException extends Exception { + public RoodyException(String s) { + super("Oh no :( " + s + '\n'); + } +} diff --git a/src/main/java/roody/exceptions/TaskNotFoundException.java b/src/main/java/roody/exceptions/TaskNotFoundException.java new file mode 100644 index 0000000000..b740bccf8b --- /dev/null +++ b/src/main/java/roody/exceptions/TaskNotFoundException.java @@ -0,0 +1,10 @@ +package roody.exceptions; + +/** + * A custom exception for missing task + */ +public class TaskNotFoundException extends RoodyException { + public TaskNotFoundException() { + super("Sorry, that task doesn't exist"); + } +} diff --git a/src/main/java/roody/gui/DialogBox.java b/src/main/java/roody/gui/DialogBox.java new file mode 100644 index 0000000000..239066ac98 --- /dev/null +++ b/src/main/java/roody/gui/DialogBox.java @@ -0,0 +1,62 @@ +package roody.gui; + +import java.io.IOException; +import java.util.Collections; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; + + +/** + * An example of a custom control using FXML. + * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ +public class DialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + private DialogBox(String text, Image img) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialog.setText(text); + displayPicture.setImage(img); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + public static DialogBox getUserDialog(String text, Image img) { + return new DialogBox(text, img); + } + + public static DialogBox getRoodyDialog(String text, Image img) { + var rd = new DialogBox(text, img); + rd.flip(); + return rd; + } +} diff --git a/src/main/java/roody/gui/MainWindow.java b/src/main/java/roody/gui/MainWindow.java new file mode 100644 index 0000000000..b8f2036e69 --- /dev/null +++ b/src/main/java/roody/gui/MainWindow.java @@ -0,0 +1,57 @@ +package roody.gui; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import roody.Roody; + +/** + * Controller for MainWindow. Provides the layout for the other controls. + */ +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private Roody roody; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images/DaPerson.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaRoody.png")); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + + public void setRoody(Roody r) { + roody = r; + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to + * the dialog container. Clears the user input after processing. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + String response = roody.getResponse(input); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getRoodyDialog(response, dukeImage) + ); + userInput.clear(); + } + @FXML + public void sendMessage(String message) { + dialogContainer.getChildren().add(DialogBox.getRoodyDialog(message, dukeImage)); + } +} diff --git a/src/main/java/roody/gui/RoodyMain.java b/src/main/java/roody/gui/RoodyMain.java new file mode 100644 index 0000000000..ed9dd0f67e --- /dev/null +++ b/src/main/java/roody/gui/RoodyMain.java @@ -0,0 +1,33 @@ +package roody.gui; + +import java.io.IOException; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; +import roody.Roody; + +/** + * A GUI for Duke using FXML. + */ +public class RoodyMain extends Application { + + private Roody roody = new Roody("./data/Roody.txt"); + + @Override + public void start(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(RoodyMain.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + fxmlLoader.getController().setRoody(roody); + stage.show(); + fxmlLoader.getController().sendMessage(roody.getResponse("start")); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/roody/tasks/Deadline.java b/src/main/java/roody/tasks/Deadline.java new file mode 100644 index 0000000000..43faa0094f --- /dev/null +++ b/src/main/java/roody/tasks/Deadline.java @@ -0,0 +1,44 @@ +package roody.tasks; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * Represents a Deadline Task + */ +public class Deadline extends Task { + private LocalDate deadline; + private char type = 'D'; + + /** + * Creates a Deadline with specified description and due date. + * @param description The description of the deadline. + * @param date The due date of the deadline. + */ + public Deadline(String description, LocalDate date) { + super(description); + this.deadline = date; + } + public void setDeadline(LocalDate date) { + this.deadline = date; + } + public LocalDate getDeadline() { + return this.deadline; + } + @Override + public String saveTask() { + return super.saveTask() + '|' + this.type + '|' + this.deadline; + } + @Override + public char getType() { + return type; + } + @Override + public String toString() { + char done = ' '; + if (isDone()) { + done = 'X'; + } + return "[D][" + done + "] " + super.toString() + " (by: " + + deadline.format(DateTimeFormatter.ofPattern("MMM d yyyy")) + ")"; + } +} diff --git a/src/main/java/roody/tasks/Event.java b/src/main/java/roody/tasks/Event.java new file mode 100644 index 0000000000..915fc80461 --- /dev/null +++ b/src/main/java/roody/tasks/Event.java @@ -0,0 +1,54 @@ +package roody.tasks; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * Represents an Event Task. + */ +public class Event extends Task { + private LocalDate start; + private LocalDate end; + private char type = 'E'; + + /** + * Creates an Event with specified description, start and end date. + * @param description The description of the Event. + * @param start The start date of the Event. + * @param end The end date of the Event. + */ + public Event(String description, LocalDate start, LocalDate end) { + super(description); + this.start = start; + this.end = end; + } + public void setStart(LocalDate time) { + this.start = time; + } + public void setEnd(LocalDate time) { + this.end = time; + } + public LocalDate getStart() { + return this.start; + } + public LocalDate getEnd() { + return this.end; + } + @Override + public String saveTask() { + return super.saveTask() + '|' + this.type + '|' + this.start + '|' + this.end; + } + @Override + public char getType() { + return type; + } + @Override + public String toString() { + char done = ' '; + if (isDone()) { + done = 'X'; + } + return "[E][" + done + "] " + super.toString() + " (from: " + + start.format(DateTimeFormatter.ofPattern("MMM d yyyy")) + + " to: " + end.format(DateTimeFormatter.ofPattern("MMM d yyyy")) + ")"; + } +} diff --git a/src/main/java/roody/tasks/Task.java b/src/main/java/roody/tasks/Task.java new file mode 100644 index 0000000000..0f086c9309 --- /dev/null +++ b/src/main/java/roody/tasks/Task.java @@ -0,0 +1,42 @@ +package roody.tasks; + +/** + * Represents a Task. + */ +public abstract class Task { + private boolean isDone; + private String description; + + /** + * Creates a Task with specified description. + * @param description The description of the Task. + */ + public Task(String description) { + this.isDone = false; + this.description = description; + } + public boolean isDone() { + return this.isDone; + } + public void setDone() { + this.isDone = true; + } + public void setUnDone() { + this.isDone = false; + } + public char getType() { + return 'a'; + } + + /** + * Returns the Task in string format + * @return Task for saving. + */ + public String saveTask() { + return this.description + '|' + this.isDone; + } + @Override + public String toString() { + return this.description; + } +} diff --git a/src/main/java/roody/tasks/Todo.java b/src/main/java/roody/tasks/Todo.java new file mode 100644 index 0000000000..e896e07fc8 --- /dev/null +++ b/src/main/java/roody/tasks/Todo.java @@ -0,0 +1,32 @@ +package roody.tasks; + +/** + * Represents a Todo Task. + */ +public class Todo extends Task { + private char type = 'T'; + + /** + * Creates a Todo with specified description. + * @param description The specified description of the todo. + */ + public Todo(String description) { + super(description); + } + @Override + public String saveTask() { + return super.saveTask() + '|' + this.type; + } + @Override + public char getType() { + return type; + } + @Override + public String toString() { + char done = ' '; + if (isDone()) { + done = 'X'; + } + return "[T][" + done + "] " + super.toString() + ""; + } +} diff --git a/src/main/resources/images/DaPerson.png b/src/main/resources/images/DaPerson.png new file mode 100644 index 0000000000..ff20a20a60 Binary files /dev/null and b/src/main/resources/images/DaPerson.png differ diff --git a/src/main/resources/images/DaRoody - Docs.png b/src/main/resources/images/DaRoody - Docs.png new file mode 100644 index 0000000000..3da2cae9a3 Binary files /dev/null and b/src/main/resources/images/DaRoody - Docs.png differ diff --git a/src/main/resources/images/DaRoody.png b/src/main/resources/images/DaRoody.png new file mode 100644 index 0000000000..84defeee8f Binary files /dev/null and b/src/main/resources/images/DaRoody.png differ diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png new file mode 100644 index 0000000000..3c82f45461 Binary files /dev/null and b/src/main/resources/images/DaUser.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..64edae1d81 --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..f911745421 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/roody/DeadlineTest.java b/src/test/java/roody/DeadlineTest.java new file mode 100644 index 0000000000..762008088f --- /dev/null +++ b/src/test/java/roody/DeadlineTest.java @@ -0,0 +1,33 @@ +package roody; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +import roody.tasks.Deadline; +import roody.tasks.Task; + +public class DeadlineTest { + @Test + public void testType() { + assertEquals(new Deadline("", LocalDate.now()).getType(), 'D'); + } + + @Test + public void testLocalDateConversion() { + LocalDate date = LocalDate.now(); + Task deadline = new Deadline("", date); + String testString = "[D][ ] (by: " + date.format(DateTimeFormatter.ofPattern("MMM d yyyy")) + ")"; + assertEquals(deadline.toString(), testString); + } + + @Test + public void testSavingTask() { + LocalDate date = LocalDate.now(); + Task deadline = new Deadline("sleep", date); + String testString = "sleep|false|D|" + date.toString(); + assertEquals(deadline.saveTask(), testString); + } +} diff --git a/src/test/java/roody/ParserTest.java b/src/test/java/roody/ParserTest.java new file mode 100644 index 0000000000..074ee07947 --- /dev/null +++ b/src/test/java/roody/ParserTest.java @@ -0,0 +1,34 @@ +package roody; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; + +import org.junit.jupiter.api.Test; + +import roody.commands.Command; +import roody.exceptions.RoodyException; +import roody.tasks.Task; + +public class ParserTest { + @Test + public void testParse_singleInput() { + try { + Command c = Parser.parse("todo dinner"); + ArrayList testList = new ArrayList<>(); + c.execute(testList, new Ui(), new Storage("test")); + assertTrue(testList.size() == 1); + } catch (RoodyException e) { + System.out.println(e.getMessage()); + } + } + + // To be achived in later implementations + /* + @Test + public void testParse_multipleInputs() { + String[] commands = Parser.parse("todo dinner tonight"); + assertEquals("todo", commands[0]); + assertEquals("dinner tonight", commands[1]); + } + */ +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..6ab0a4544c 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,65 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - +____________________________________________________________ +Hello, I'm Roody! +What can I do for you? +____________________________________________________________ +=> ____________________________________________________________ +Got it. I've added this task: + [T][ ] JS2230 readings +Now you have 1 tasks in the list. +____________________________________________________________ +=> ____________________________________________________________ +Got it. I've added this task: + [E][ ] Japan Trip (from: January 25th to: February 2nd) +Now you have 2 tasks in the list. +____________________________________________________________ +=> ____________________________________________________________ +Got it. I've added this task: + [D][ ] Japan Admin (Mum) (by: Thursday) +Now you have 3 tasks in the list. +____________________________________________________________ +=> ____________________________________________________________ +Here are the tasks in your list: +1.[T][ ] JS2230 readings +2.[E][ ] Japan Trip (from: January 25th to: February 2nd) +3.[D][ ] Japan Admin (Mum) (by: Thursday) +____________________________________________________________ +=> ____________________________________________________________ +Nice! I've marked this task as done: +[E][X] Japan Trip (from: January 25th to: February 2nd) +____________________________________________________________ +=> ____________________________________________________________ +OK, I've marked this task as not done yet: +[D][ ] Japan Admin (Mum) (by: Thursday) +____________________________________________________________ +=> ____________________________________________________________ +Oh no :( Sorry, that task doesn't exist +____________________________________________________________ +=> ____________________________________________________________ +Got it. I've added this task: + [T][ ] Lunch +Now you have 4 tasks in the list. +____________________________________________________________ +=> ____________________________________________________________ +Here are the tasks in your list: +1.[T][ ] JS2230 readings +2.[E][X] Japan Trip (from: January 25th to: February 2nd) +3.[D][ ] Japan Admin (Mum) (by: Thursday) +4.[T][ ] Lunch +____________________________________________________________ +=> ____________________________________________________________ +Noted. I've removed this task: + [E][ ] Japan Trip (from: January 25th to: February 2nd) +Now you have 3 tasks in the list. +____________________________________________________________ +=> ____________________________________________________________ +Oh no :( Sorry, that task doesn't exist +____________________________________________________________ +=> ____________________________________________________________ +Here are the tasks in your list: +1.[T][ ] JS2230 readings +2.[D][ ] Japan Admin (Mum) (by: Thursday) +3.[T][ ] Lunch +____________________________________________________________ +=> ____________________________________________________________ +Bye. Hope to see you again soon! +____________________________________________________________ diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..0863073b2b 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,13 @@ +todo JS2230 readings +event Japan Trip /from January 25th /to February 2nd +deadline Japan Admin (Mum) /by Thursday +list +mark 2 +unmark 3 +mark 4 +todo Lunch +list +delete 2 +delete 4 +list +bye \ No newline at end of file diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 0873744649..7b44eead77 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -15,7 +15,9 @@ IF ERRORLEVEL 1 ( REM no error here, errorlevel == 0 REM 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 Roody < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT + +CMD /k diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh old mode 100644 new mode 100755