diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000000..133b0e7f2b --- /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 \ No newline at end of file diff --git a/.gitignore b/.gitignore index f69985ef1f..98c24b7e05 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,7 @@ src/main/resources/docs/ *.iml bin/ +/data/ +/text-ui-test/data/ /text-ui-test/ACTUAL.txt -text-ui-test/EXPECTED-UNIX.TXT +/text-ui-test/EXPECTED-UNIX.TXT \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..5a3c3737f4 --- /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' +} + +repositories { + mavenCentral() +} + +dependencies { + String javaFxVersion = '11' + + 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' + 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' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "ren.Launcher" +} + +checkstyle { + toolVersion = '10.2' +} + +shadowJar { + archiveBaseName = "ren" + archiveClassifier = null +} + +run{ + standardInput = System.in + enableAssertions = true +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..d618671b83 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..39efb6e4ac --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/docs/README.md b/docs/README.md index 8077118ebe..38d2959619 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,235 @@ # User Guide -## Features +Ren is a **desktop task manager app optimized for use via a Command Line Interface** (CLI) +while still having the benefits of a Graphical User Interface (GUI). If you can type fast, +Ren can manage your tasks faster than traditional GUI apps. -### Feature-ABC +-------------------------------------------------------------------------------------------------------------------- -Description of the feature. +## Quick start -### Feature-XYZ +1. Ensure you have Java `11` or above installed in your Computer. +2. Download the latest `ren.jar` from [here](https://github.com/Eugene-Ong-W-X/ip/releases). +3. Copy the file to the folder you want to use as the _home folder_ for your Ren Task Manager. +4. Double-click the file to start the app. The GUI similar to the below should appear in a few seconds.
+ ![Ui](/docs/Ui.png) +5. Type a command at the bottom and press Enter or click Send to execute it. +6. Refer to the [Usage](#usage) below for details of each command. -Description of the feature. +-------------------------------------------------------------------------------------------------------------------- + +## Features + +### Manage Tasks + +3 different types of Tasks. Operations supported +* add task +* delete task / empty list +* mark/unmark task + +### Organise Tasks + +View your list of Tasks. Sort the list. Find a task from the list. + +### Saved Data + +Your TaskList data is saved in the hard disk automatically after +any command that modifies the data. Have no fear of losing your data! +Have no worries about saving your data manually! + +-------------------------------------------------------------------------------------------------------------------- ## Usage -### `Keyword` - Describe action +
+ +**Notes about the command format:**
+ +* Words in `UPPER_CASE` are the parameters to be supplied by the user.
+ +* Extra parameters for commands that do not take in parameters will be ignored.
+ e.g. if the command specifies `list 123`, it will be interpreted as `list`. + +
+ +### Add a Todo Task - `todo` + +Adds a Task, that needs to be done, to the TaskList. + +Format: `todo DESCRIPTION` + +Example Usage + +>`todo Watch recorded lecture videos` + +Example Outcome +``` + Understood. I have added the following task: + [T][ ] Watch recorded lecture videos + You now have a total of 1 task(s). +``` + +### Add a Deadline Task - `deadline` + +Adds a Task, that needs to be done by a certain date or time, to the TaskList. + +Format: `deadline DESCRIPTION /by DD/MM/YY-HH:MM` +* Time must be in 24hr format (i.e. 23:59 instead of 11:59 PM) + +Example Usage + +>`deadline Submit Essay /by 11/9/2022-23:59` + +Example Outcome +``` + Understood. I have added the following task: + [D][ ] Submit Essay (by: Sun, 11 September 2022 11:59 PM) + You now have a total of 1 task(s). +``` + +### Add an Event Task - `event` + +Adds an Event, that takes place between certain dates or time, to the TaskList. + +Format: `event DESCRIPTION /at DD/MM/YY-HH:MM ~ DD/MM/YY-HH:MM` +* Time must be in 24hr format (i.e. 23:59 instead of 11:59 PM) + +Example Usage + +>`event Recess Week /at 17/9/2022-00:00 ~ 25/9/2022-23:59` + +Example Outcome +``` + Understood. I have added the following task: + [E][ ] Recess Week (at: Sat, 17 September 2022 12:00 AM - Sun, 25 September 2022 11:59 PM) + You now have a total of 1 task(s). +``` + +### Mark a Task - `mark` + +Sets a Task, in the TaskList, as completed. + +Format: `mark INDEX` +* Use index of task from the list of tasks + +Example Usage + +>`mark 1` + +Example Outcome +``` + Great job! I will mark the task as completed. + [T][X] Watch recorded lecture videos +``` + +### Unmark a Task - `unmark` + +Sets a Task, in the TaskList, to uncompleted. + +Format: `unmark INDEX` +* Use index of task from the list of tasks + +Example Usage + +>`unmark 1` + +Example Outcome +``` + Understood. I will mark the task as uncompleted. + [T][ ] Watch recorded lecture videos +``` + +### Lists all Tasks - `list` + +Displays all Tasks in the TaskList. + +Format: `list` + +Example Usage + +>`list` + +Example Outcome +``` + Here are your current tasks: + 1. [T][ ] Watch recorded lecture videos + 2. [T][X] Submit Essay +``` + +### Sort Tasks - `sort` + +Sorts all Tasks in the TaskList. + +Format: `sort ATTRIBUTE` +* Attribute 1. `type` Sorts in the order of Todo > Deadline > Event +* Attribute 2. `status` Sorts uncompleted Tasks before completed Tasks +* Attribute 3. `description` Sorts by description, in lexicographical order +* Attribute 4. `date` Sorts by date, in chronological order (Todos are last) + +Example Usage + +>`sort status` + +Example Outcome +``` + I have finished sorting your list of tasks! + + Here are your current tasks: + 1. [T][ ] Watch recorded lecture videos + 2. [T][X] Submit Essay +``` + +### Find a Task - `find` + +Finds a Task from the TaskList. + +Format: `find SEARCH_TERM` + +Example Usage + +>`find video` + +Example Outcome +``` + I have found these matching tasks: + 1. [T][ ] Watch recorded lecture videos +``` -Describe the action and its outcome. +### Delete a Task - `delete` -Example of usage: +Deletes a Task from the TaskList. -`keyword (optional arguments)` +Format: `delete INDEX` +* Use index of task from the list of tasks -Expected outcome: +Example Usage -Description of the outcome. +>`delete 1` +Example Outcome ``` -expected output + Understood. I have removed the following task: + [T][ ] Watch recorded lecture videos + You have a total of 1 task(s) left. ``` + +### Empty the TaskList - `empty` + +Deletes all Tasks from the TaskList. + +Format: `empty` + +Example Usage + +>`empty` + +Example Outcome +``` + Understood. I have emptied your list of tasks. +``` + +### Exit Ren - `bye` + +Exits and closes the Ren program. + +Format: `bye` diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..639e1e1da2 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/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/ren/DialogBox.java b/src/main/java/ren/DialogBox.java new file mode 100644 index 0000000000..09c32cd08b --- /dev/null +++ b/src/main/java/ren/DialogBox.java @@ -0,0 +1,90 @@ +package ren; + +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.effect.Reflection; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.shape.Rectangle; + +/** + * 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; + + /** + * Constructor for a DialogBox. + * + * @param text The text of the DialogBox. + * @param img The image of the DialogBox. + */ + 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); + + Rectangle rect = new Rectangle(99, 99); + rect.setArcHeight(30); + rect.setArcWidth(30); + rect.setEffect(new Reflection()); + displayPicture.setClip(rect); + } + + /** + * 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); + } + + /** + * Factory method for a User DialogBox. + * + * @param text String containing command from User. + * @param img Image representing User. + * @return DialogBox. + */ + public static DialogBox getUserDialog(String text, Image img) { + return new DialogBox(text, img); + } + + /** + * Factory method for a Ren DialogBox. + * + * @param text String containing message from Ren. + * @param img Image representing Ren. + * @return DialogBox. + */ + public static DialogBox getRenDialog(String text, Image img) { + var db = new DialogBox(text, img); + db.flip(); + return db; + } +} diff --git a/src/main/java/ren/Launcher.java b/src/main/java/ren/Launcher.java new file mode 100644 index 0000000000..97deeb081e --- /dev/null +++ b/src/main/java/ren/Launcher.java @@ -0,0 +1,12 @@ +package ren; + +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class Launcher { + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/ren/Main.java b/src/main/java/ren/Main.java new file mode 100644 index 0000000000..a390286774 --- /dev/null +++ b/src/main/java/ren/Main.java @@ -0,0 +1,33 @@ +package ren; + +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; + +/** + * A GUI for Duke using FXML. + */ +public class Main extends Application { + /** The Ren bot for this instance of the program. */ + private final Ren ren = new Ren(); + + @Override + public void start(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + fxmlLoader.getController().setRen(ren); + stage.setResizable(false); + stage.setTitle("Ren"); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/ren/MainWindow.java b/src/main/java/ren/MainWindow.java new file mode 100644 index 0000000000..853a4c24ed --- /dev/null +++ b/src/main/java/ren/MainWindow.java @@ -0,0 +1,90 @@ +package ren; + +import java.io.InputStream; + +import javafx.application.Platform; +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; +/** + * 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; + + /** The instance of Ren for this program. */ + private Ren ren; + + /** Profile picture for the user. */ + private Image userImage; + + /** Profile picture for Ren. */ + private Image renImage; + + /** + * Initializes the GUI. Greets the user. + */ + @FXML + public void initialize() { + // Credits for Image + // https://pixabay.com/vectors/blank-profile-picture-mystery-man-973460/ + InputStream userImageLoader = this.getClass().getResourceAsStream("/images/user.png"); + // Credits for Image + // https://www.freepik.com/free-photo/robot-with-clipboard_958202.htm#query=robot&position=9&from_view=author + InputStream renImageLoader = this.getClass().getResourceAsStream("/images/ren.png"); + assert userImageLoader != null : "userImage in MainWindow should not be null"; + assert renImageLoader != null : "renImage in MainWindow should not be null"; + + userImage = new Image(userImageLoader); + renImage = new Image(renImageLoader); + + String greetings = " Greetings! My name is Ren ^_^\n How may I be of service today?\n"; + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + dialogContainer.getChildren().addAll( + DialogBox.getRenDialog(greetings, renImage) + ); + } + + /** + * Setter method for ren. + * + * @param ren Instance of Ren. + */ + public void setRen(Ren ren) { + this.ren = ren; + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing Ren's reply and then appends them to + * the dialog container. Clears the user input after processing. Exits the program if bye command entered. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText().trim(); + String response; + try { + response = ren.interpret(input); + } catch (RenException e) { + response = e.toString(); + } + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getRenDialog(response, renImage) + ); + userInput.clear(); + if (input.equals("bye")) { + Platform.exit(); + } + } +} diff --git a/src/main/java/ren/Parser.java b/src/main/java/ren/Parser.java new file mode 100644 index 0000000000..cd729ff54e --- /dev/null +++ b/src/main/java/ren/Parser.java @@ -0,0 +1,161 @@ +package ren; + +/** + * Parser interprets user commands and executes them. + */ +public class Parser { + private final TaskList tasks; + + /** + * Constructor for Parser. + * + * @param tasks The TaskList to execute commands on. + */ + public Parser(TaskList tasks) { + this.tasks = tasks; + } + + /** + * Interprets user commands and executes them. + * + * @param cmd Command to execute. + * @return String containing a message for the user. + * @throws RenException If command is invalid. + */ + public String parseCommand(String cmd) throws RenException { + String[] firstParse = cmd.split(" ", 2); + boolean hasSecondTerm = firstParse.length > 1; + switch (firstParse[0]) { + case "bye": + return " Farewell!\n"; + case "todo": + return parseTodo(hasSecondTerm, firstParse); + case "deadline": + return parseDeadline(hasSecondTerm, firstParse); + case "event": + return parseEvent(hasSecondTerm, firstParse); + case "delete": + return parseDelete(hasSecondTerm, firstParse); + case "mark": + return parseMark(hasSecondTerm, firstParse); + case "unmark": + return parseUnmark(hasSecondTerm, firstParse); + case "list": + return tasks.listTasks(); + case "find": + return parseFind(hasSecondTerm, firstParse); + case "empty": + return tasks.emptyList(); + case "sort": + return parseSort(hasSecondTerm, firstParse); + default: + throw new RenException("Please enter a supported command."); + } + } + + private String parseTodo(boolean hasSecondTerm, String[] firstParse) throws RenException { + if (hasSecondTerm) { + return tasks.addTask(Ren.TaskType.TODO, firstParse[1], ""); + } else { + throw new RenException("Please provide a description for the todo."); + } + } + + private String parseDeadline(boolean hasSecondTerm, String[] firstParse) throws RenException { + if (hasSecondTerm) { + String[] secondParse = firstParse[1].split("/by", 2); + if (secondParse.length > 1) { + return tasks.addTask(Ren.TaskType.DEADLINE, secondParse[0], secondParse[1]); + } else { + throw new RenException("Please provide a date/time for the deadline."); + } + } else { + throw new RenException("Please provide a description for the deadline."); + } + } + + private String parseEvent(boolean hasSecondTerm, String[] firstParse) throws RenException { + if (hasSecondTerm) { + String[] secondParse = firstParse[1].split("/at", 2); + if (secondParse.length > 1) { + return tasks.addTask(Ren.TaskType.EVENT, secondParse[0], secondParse[1]); + } else { + throw new RenException("Please provide a date/time for the event."); + } + } else { + throw new RenException("Please provide a description for the event."); + } + } + + private String parseDelete(boolean hasSecondTerm, String[] firstParse) throws RenException { + try { + String secondTerm = hasSecondTerm + ? firstParse[1].split(" ", 2)[0] + : "0"; + return tasks.deleteTask(Integer.parseInt(secondTerm)); + } catch (NumberFormatException e) { + // If the second term parsed isn't an integer + throw new RenException("Please indicate the task no. in digits."); + } catch (RenException f) { + // The user entered an invalid number + return f.toString(); + } + } + + private String parseMark(boolean hasSecondTerm, String[] firstParse) throws RenException { + try { + String secondTerm = hasSecondTerm + ? firstParse[1].split(" ", 2)[0] + : "0"; + return tasks.updateTask(true, Integer.parseInt(secondTerm)); + } catch (NumberFormatException e) { + // If the second term parsed isn't an integer + throw new RenException("Please indicate the task no. in digits."); + } catch (RenException f) { + // The user entered an invalid number + return f.toString(); + } + } + + private String parseUnmark(boolean hasSecondTerm, String[] firstParse) throws RenException { + try { + String secondTerm = hasSecondTerm + ? firstParse[1].split(" ", 2)[0] + : "0"; + return tasks.updateTask(false, Integer.parseInt(secondTerm)); + } catch (NumberFormatException e) { + // If the second term parsed isn't an integer + throw new RenException("Please indicate the task no. in digits."); + } catch (RenException f) { + // The user entered an invalid number + return f.toString(); + } + } + + private String parseFind(boolean hasSecondTerm, String[] firstParse) throws RenException { + if (hasSecondTerm) { + return tasks.findTasks(firstParse[1]); + } else { + throw new RenException("Please provide a search term."); + } + } + + private String parseSort(boolean hasSecondTerm, String[] firstParse) throws RenException { + if (!hasSecondTerm) { + throw new RenException("Please specify how you want the list to be sorted."); + } + String secondTerm = firstParse[1].split(" ", 2)[0]; + switch (secondTerm) { + case "type": + return tasks.sortTasks(TaskList.SortType.TYPE); + case "status": + return tasks.sortTasks(TaskList.SortType.STATUS); + case "description": + return tasks.sortTasks(TaskList.SortType.DESCRIPTION); + case "date": + return tasks.sortTasks(TaskList.SortType.DATE); + default: + throw new RenException("Please specify how you want the list to be sorted."); + } + } +} diff --git a/src/main/java/ren/Ren.java b/src/main/java/ren/Ren.java new file mode 100644 index 0000000000..00b54a17e2 --- /dev/null +++ b/src/main/java/ren/Ren.java @@ -0,0 +1,38 @@ +package ren; + +/** + * Ren is a Task Manager program that helps a user keep track of and manage their tasks. + */ +public class Ren { + /** Parser helps the Ren bot to interpret commands from the user. */ + private final Parser parser; + + /** + * Constructor for a Ren bot. + */ + public Ren() { + Storage storage = new Storage("data/list.txt"); + TaskList tasks = new TaskList(storage); + parser = new Parser(tasks); + } + + /** + * Interprets commands from the user. + * + * @param input The command from the user. + * @return String containing message from Ren after attempting to execute the command. + * @throws RenException If the execution failed. + */ + public String interpret(String input) throws RenException { + return parser.parseCommand(input); + } + + /** + * The types of Tasks supported by Ren. + */ + enum TaskType { + TODO, + DEADLINE, + EVENT + } +} diff --git a/src/main/java/ren/RenException.java b/src/main/java/ren/RenException.java new file mode 100644 index 0000000000..867c95e285 --- /dev/null +++ b/src/main/java/ren/RenException.java @@ -0,0 +1,25 @@ +package ren; + +/** + * RenExceptions represent errors in user input when using Ren. + */ +public class RenException extends Exception { + /** + * Constructor for a RenException. + * + * @param message Error Message. + */ + public RenException(String message) { + super(message); + } + + /** + * Returns the string representation of this RenException. + * + * @return String Representation. + */ + @Override + public String toString() { + return " >_< Apologies! " + super.getMessage() + "\n"; + } +} diff --git a/src/main/java/ren/Storage.java b/src/main/java/ren/Storage.java new file mode 100644 index 0000000000..6f85fee3c0 --- /dev/null +++ b/src/main/java/ren/Storage.java @@ -0,0 +1,203 @@ +package ren; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Scanner; + +import ren.task.Deadline; +import ren.task.Event; +import ren.task.Task; +import ren.task.Todo; + +/** + * Storage handles the reading and writing of Tasks to a File. + */ +public class Storage { + /** Path of the File. */ + private final String filePath; + + /** File that will store the list of Tasks. */ + private final File dataFile; + + /** Array that stores each line of the File as an element. */ + private final ArrayList dataArray = new ArrayList<>(); + + /** Counter for number of lines in the File. */ + private int counter = 0; + + /** Indicator for whether an error has occurred in the File. */ + private boolean hasError = false; + + /** + * Constructor for Storage. + * + * @param filePath Path of the File to read from and write to. + */ + public Storage(String filePath) { + this.filePath = filePath; + this.dataFile = new File(filePath); + init(); + } + + /** + * Initialises dataArray using dataFile and creates dataFile if it is missing. + */ + private void init() { + if (!dataFile.exists()) { + try { + // Check if the parent directory exists + if (!dataFile.getParentFile().exists()) { + dataFile.getParentFile().mkdir(); + } + dataFile.createNewFile(); + } catch (IOException e) { + this.hasError = true; + } + } else { + try { + Scanner dataSource = new Scanner(dataFile); + while (dataSource.hasNext()) { + dataArray.add(dataSource.nextLine()); + counter++; + } + dataSource.close(); + } catch (FileNotFoundException e) { + this.hasError = true; + } + } + } + + /** + * Resets the File to a blank File. + */ + private void newFile() { + try { + dataFile.delete(); + dataFile.createNewFile(); + } catch (IOException e) { + this.hasError = true; + } + } + + /** + * Overwrites the File with a new list of Tasks. + */ + private void writeToFile() { + // Disable writing to File if error occurred during creation of file or reading of file. + if (hasError) { + return; + } + try { + FileWriter writer = new FileWriter(filePath); + StringBuilder contents = new StringBuilder(dataArray.get(0)); + for (int i = 1; i < counter; i++) { + contents.append(System.lineSeparator()); + contents.append(dataArray.get(i)); + } + writer.write(contents.toString()); + writer.close(); + } catch (IOException e) { + this.hasError = true; + } + } + + /** + * Appends a new Task's information to the File. + */ + private void appendToFile() { + // Disable writing to File if error occurred during creation of file or reading of file. + if (hasError) { + return; + } + try { + FileWriter writer = new FileWriter(filePath, true); + StringBuilder contents; + if (counter == 1) { + contents = new StringBuilder(dataArray.get(0)); + } else { + contents = new StringBuilder(System.lineSeparator()); + contents.append(dataArray.get(counter - 1)); + } + writer.write(contents.toString()); + writer.close(); + } catch (IOException e) { + this.hasError = true; + } + } + + /** + * Returns a list of Tasks read from File. + * + * @return ArrayList of Tasks. + */ + public ArrayList load() { + ArrayList tasks = new ArrayList<>(); + for (int i = 0; i < this.counter; i++) { + // Split the task information into individual terms + String[] data = dataArray.get(i).split("\\|"); + String taskType = data[0]; + Task newTask = null; + switch (taskType) { + case "T": + newTask = Todo.readData(data); + break; + case "D": + newTask = Deadline.readData(data); + break; + case "E": + newTask = Event.readData(data); + break; + default: + } + assert newTask != null : "newTask in load in Storage should not be null"; + + tasks.add(newTask); + } + return tasks; + } + + /** + * Writes a new Task to File. + * + * @param newTask Task to be written. + */ + public void addTask(Task newTask) { + dataArray.add(newTask.writeData()); + counter++; + appendToFile(); + } + + /** + * Modifies a Task in File. + * + * @param newTask Task that was modified. + * @param index Index of the modified Task. + */ + public void updateTask(Task newTask, int index) { + dataArray.set(index, newTask.writeData()); + writeToFile(); + } + + /** + * Removes a Task from File. + * + * @param index Index of the Task to be removed. + */ + public void deleteTask(int index) { + dataArray.remove(index); + counter--; + writeToFile(); + } + + /** + * Removes all Tasks from File. + */ + public void emptyList() { + dataArray.clear(); + counter = 0; + newFile(); + } +} diff --git a/src/main/java/ren/TaskList.java b/src/main/java/ren/TaskList.java new file mode 100644 index 0000000000..dab8ff154f --- /dev/null +++ b/src/main/java/ren/TaskList.java @@ -0,0 +1,195 @@ +package ren; + +import java.util.ArrayList; + +import ren.task.Deadline; +import ren.task.Event; +import ren.task.Task; +import ren.task.Todo; + +/** + * TaskList contains a list of Tasks as well methods to add, delete, update tasks. + */ +public class TaskList { + /** ArrayList to store all Tasks. */ + private final ArrayList tasks; + + /** Storage to synchronize all changes with. */ + private final Storage storage; + + /** + * Constructor for a TaskList. + * + * @param storage Storage to read and write all Tasks to. + */ + public TaskList(Storage storage) { + this.tasks = storage.load(); + this.storage = storage; + } + + /** + * Adds a Task to the TaskList. + * + * @param type The type of the new Task. + * @param task The information of the new Task. + * @param dateTime The date and time information of the new Task. + * @return String containing a message for the user. + * @throws RenException If task or dateTime is invalid. + */ + public String addTask(Ren.TaskType type, String task, String dateTime) throws RenException { + Task newTask = null; + switch (type) { + case TODO: + newTask = new Todo(task); + break; + case DEADLINE: + newTask = new Deadline(task, dateTime); + break; + case EVENT: + newTask = new Event(task, dateTime); + break; + default: + } + assert newTask != null : "newTask in addTask in TaskList should not be null"; + + tasks.add(newTask); + storage.addTask(newTask); + return " Understood. I have added the following task:\n" + + " " + newTask + + " You now have a total of " + tasks.size() + " task(s).\n"; + } + + /** + * Removes a Task from the TaskList. + * + * @param taskNum The index of the Task to remove. + * @return String containing a message for the user. + * @throws RenException If taskNum is invalid. + */ + public String deleteTask(int taskNum) throws RenException { + if (taskNum <= tasks.size() && taskNum > 0) { + Task removedTask = tasks.remove(taskNum - 1); + storage.deleteTask(taskNum - 1); + return " Understood. I have removed the following task:\n" + + " " + removedTask + + " You have a total of " + tasks.size() + " task(s) left.\n"; + } else if (tasks.size() == 0) { + throw new RenException("You have no tasks to delete."); + } else { + throw new RenException("Please indicate a task no. between 1 to " + tasks.size() + "."); + } + } + + /** + * Updates a Task in the TaskList. + * + * @param status The new status of the Task. + * @param taskNum The index of the Task to update. + * @return String containing a message for the user. + * @throws RenException If taskNum is invalid. + */ + public String updateTask(boolean status, int taskNum) throws RenException { + if (taskNum <= tasks.size() && taskNum > 0) { + Task selectedTask = tasks.get(taskNum - 1); + String message = selectedTask.setDone(status); + storage.updateTask(selectedTask, taskNum - 1); + return message; + } else if (tasks.size() == 0) { + throw new RenException("You have no tasks to mark or unmark."); + } else { + throw new RenException("Please indicate a task no. between 1 to " + tasks.size() + "."); + } + } + + /** + * Lists all Tasks in the TaskList. + * + * @return String containing the list. + */ + public String listTasks() { + if (tasks.size() == 0) { + return " You have not added any tasks!\n"; + } + + StringBuilder result = new StringBuilder(" Here are your current tasks:\n"); + for (int i = 0; i < tasks.size(); i++) { + result.append(" ").append(i + 1).append(". ").append(tasks.get(i).toString()); + } + return result.toString(); + } + + /** + * Sorts the tasks according to sortType. + * For type, the order is Todo, Deadline, Event. + * For status, unmarked tasks are before marked tasks. + * For description, tasks are sorted lexicographically, ignoring case differences. + * For date, Todo are sorted last while Deadline and Event are sorted chronologically. + * + * @param sortType The order to sort the tasks by. + * @return String containing the sorted list of tasks. + */ + public String sortTasks(SortType sortType) { + switch (sortType) { + case TYPE: + tasks.sort(Task::compareType); + break; + case STATUS: + tasks.sort(Task::compareStatus); + break; + case DESCRIPTION: + tasks.sort(Task::compareDescription); + break; + case DATE: + tasks.sort(Task::compareDate); + break; + default: + break; + } + storage.emptyList(); + tasks.forEach(storage::addTask); + return " I have finished sorting your list of tasks!\n\n" + listTasks(); + } + + /** + * Searches TaskList for Tasks matching a search term. + * + * @param term The Search Term to match Tasks with. + * @return String containing the list of matching Tasks. + */ + public String findTasks(String term) { + if (tasks.size() == 0) { + return " Apologies! I have not found any matching tasks.\n"; + } + + int index = 1; + StringBuilder result = new StringBuilder(" I have found these matching tasks:\n"); + for (Task taskToCheck : tasks) { + if (taskToCheck.isMatch(term)) { + result.append(" ").append(index).append(". ").append(taskToCheck); + index++; + } + } + return index != 1 ? result.toString() : " Apologies! I have not found any matching tasks.\n"; + } + + /** + * Removes all Tasks from the TaskList. + * + * @return String containing a message for the user. + */ + public String emptyList() { + tasks.clear(); + storage.emptyList(); + return " Understood. I have emptied your list of tasks."; + } + + /** + * The types of sorting supported by Ren. + */ + enum SortType { + TYPE, + STATUS, + DESCRIPTION, + DATE + } +} diff --git a/src/main/java/ren/TimeStamp.java b/src/main/java/ren/TimeStamp.java new file mode 100644 index 0000000000..706e8616da --- /dev/null +++ b/src/main/java/ren/TimeStamp.java @@ -0,0 +1,73 @@ +package ren; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * TimeStamp stores the date and time information of Deadline Tasks and Events. + */ +public class TimeStamp { + private final LocalDateTime timestamp; + + private TimeStamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + /** + * Factory method for TimeStamps. + * + * @param dateTime Date and Time information in the format of (dd/MM/yyyy-HH:mm). + * @return TimeStamp. + * @throws RenException If dateTime is not formatted correctly. + */ + public static TimeStamp of(String dateTime) throws RenException { + // format of the date and time information in the input + DateTimeFormatter format = DateTimeFormatter.ofPattern("d/M/yyyy-H:mm"); + try { + LocalDateTime timestamp = LocalDateTime.parse(dateTime.strip(), format); + return new TimeStamp(timestamp); + } catch (DateTimeParseException e) { + throw new RenException("Please indicate date and time properly. (20/8/2022-15:37)"); + } + } + + /** + * Factory method for TimeStamps, meant for reading from File. + * + * @param dateTime Date and Time information in the format of (dd/MM/yyyy-HH:mm). + * @return TimeStamp. + */ + public static TimeStamp fromFile(String dateTime) { + // format of the date and time information in the input + DateTimeFormatter format = DateTimeFormatter.ofPattern("E, d MMMM yyyy h:mm a"); + try { + LocalDateTime timestamp = LocalDateTime.parse(dateTime.strip(), format); + return new TimeStamp(timestamp); + } catch (DateTimeParseException e) { + System.out.println("fromFile failed"); + return new TimeStamp(LocalDateTime.now()); + } + } + + /** + * Compares this TimeStamp to another TimeStamp. + * + * @param other The other TimeStamp to compare with. + * @return -1 if this TimeStamp is earlier. 1 if the other TimeStamp is earlier. 0 if both are the same. + */ + public int compareTo(TimeStamp other) { + return this.timestamp.compareTo(other.timestamp); + } + + /** + * Returns String Representation of a TimeStamp. + * + * @return String Representation. + */ + @Override + public String toString() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("E, d MMMM yyyy h:mm a"); + return " " + timestamp.format(formatter); + } +} diff --git a/src/main/java/ren/task/Deadline.java b/src/main/java/ren/task/Deadline.java new file mode 100644 index 0000000000..360cac361c --- /dev/null +++ b/src/main/java/ren/task/Deadline.java @@ -0,0 +1,100 @@ +package ren.task; + +import ren.RenException; +import ren.TimeStamp; + +/** + * Deadline Task represents an action that needs to be done by a certain date or time. + */ +public class Deadline extends Task { + protected TimeStamp dateTime; + + /** + * Constructor for a Deadline Task. + * + * @param description the description of the Task + * @param dateTime the deadline of the Task + */ + public Deadline(String description, String dateTime) throws RenException { + super(description); + this.dateTime = TimeStamp.of(dateTime); + } + + private Deadline(String description, TimeStamp dateTime) { + super(description); + this.dateTime = dateTime; + } + + /** + * Returns a Deadline Task constructed with data read from File. + * + * @param data Data of a Deadline Task. + * @return Deadline Task or null if data is corrupted. + */ + public static Deadline readData(String[] data) { + // Check if data is complete + assert data.length == 4 : "data[] in readData in Deadline should be of size 4"; + + Deadline newDeadline = new Deadline(data[2], TimeStamp.fromFile(data[3])); + if (data[1].equals("X")) { + newDeadline.setDone(true); + } + return newDeadline; + } + + /** + * Returns the Deadline Task information for writing to a File. + * + * @return String with Deadline Task information. + */ + @Override + public String writeData() { + String symbol = this.isDone ? "X" : " "; + return "D|" + symbol + "|" + this.description + "|" + this.dateTime.toString(); + } + + /** + * Compares this Deadline to another Task by their type of task. + * + * @param other The task to compare with. + * @return -1 if other is an Event, 1 if the other task is a Todo, 0 if the other task is a Deadline. + */ + @Override + public int compareType(Task other) { + if (other instanceof Todo) { + return 1; + } else if (other instanceof Deadline) { + return 0; + } + return -1; + } + + /** + * Compares this Deadline to another Task by their date. + * + * @param other The task to compare with. + * @return -1 if this task should be sorted first, 1 if the other task should be sorted first, 0 otherwise. + */ + @Override + public int compareDate(Task other) { + if (other instanceof Todo) { + return -1; + } else if (other instanceof Deadline) { + return this.dateTime.compareTo(((Deadline) other).dateTime); + } else if (other instanceof Event) { + return this.dateTime.compareTo(((Event) other).start); + } + return 0; + } + + /** + * Returns the string representation of this Deadline Task. + * + * @return String Representation. + */ + @Override + public String toString() { + String symbol = this.isDone ? "X" : " "; + return "[D][" + symbol + "] " + this.description + "(by:" + this.dateTime + ")\n"; + } +} diff --git a/src/main/java/ren/task/Event.java b/src/main/java/ren/task/Event.java new file mode 100644 index 0000000000..029cfcfdd5 --- /dev/null +++ b/src/main/java/ren/task/Event.java @@ -0,0 +1,104 @@ +package ren.task; + +import ren.RenException; +import ren.TimeStamp; + +/** + * Event Task represents an event that takes place between certain dates/time. + */ +public class Event extends Task { + protected TimeStamp start; + protected TimeStamp end; + + /** + * Constructor for an Event Task. + * + * @param description the description of the Event + * @param dateTime the start and end dates/time of the Event + */ + public Event(String description, String dateTime) throws RenException { + super(description); + String[] duration = dateTime.split(" ~ ", 2); + this.start = TimeStamp.of(duration[0]); + this.end = TimeStamp.of(duration[1]); + } + + private Event(String description, TimeStamp start, TimeStamp end) { + super(description); + this.start = start; + this.end = end; + } + + /** + * Returns an Event constructed with data read from File. + * + * @param data Data of an Event. + * @return Event or null if data is corrupted. + */ + public static Event readData(String[] data) { + // Check if data is complete + assert data.length == 5 : "data[] in readData in Event should be of size 5"; + + Event newEvent = new Event(data[2], TimeStamp.fromFile(data[3]), TimeStamp.fromFile(data[4])); + if (data[1].equals("X")) { + newEvent.setDone(true); + } + return newEvent; + } + + /** + * Returns the Event information for writing to a File. + * + * @return String with Event information. + */ + @Override + public String writeData() { + String symbol = this.isDone ? "X" : " "; + return "E|" + symbol + "|" + this.description + "|" + this.start + "|" + this.end; + } + + /** + * Compares this Event to another Task by their type of task. + * + * @param other The task to compare with. + * @return 1 if the other task is not an Event, 0 if the other task is an Event. + */ + @Override + public int compareType(Task other) { + if (other instanceof Todo) { + return 1; + } else if (other instanceof Deadline) { + return 1; + } + return 0; + } + + /** + * Compares this Event to another Task by their date. + * + * @param other The task to compare with. + * @return -1 if this task should be sorted first, 1 if the other task should be sorted first, 0 otherwise. + */ + @Override + public int compareDate(Task other) { + if (other instanceof Todo) { + return -1; + } else if (other instanceof Deadline) { + return this.start.compareTo(((Deadline) other).dateTime); + } else if (other instanceof Event) { + return this.start.compareTo(((Event) other).start); + } + return 0; + } + + /** + * Returns the string representation of this Event. + * + * @return String Representation. + */ + @Override + public String toString() { + String symbol = this.isDone ? "X" : " "; + return "[E][" + symbol + "] " + this.description + "(at:" + this.start + " -" + this.end + ")\n"; + } +} diff --git a/src/main/java/ren/task/Task.java b/src/main/java/ren/task/Task.java new file mode 100644 index 0000000000..672544e94f --- /dev/null +++ b/src/main/java/ren/task/Task.java @@ -0,0 +1,91 @@ +package ren.task; + +/** + * Parent Class for all Tasks. + */ +public abstract class Task { + protected String description; + protected boolean isDone; + + /** + * Constructor for a Task. + * + * @param description Description of the Task. + */ + public Task(String description) { + this.description = description; + this.isDone = false; + } + + /** + * Returns the Task information for writing to a File. + * + * @return String with Task information. + */ + public abstract String writeData(); + + /** + * Compares this Task to another Task by their type of task. + * + * @param other The task to compare with. + * @return -1 if this task should be sorted first, 1 if the other task should be sorted first, 0 otherwise. + */ + public abstract int compareType(Task other); + + /** + * Compares this Task to another Task by their date. + * + * @param other The task to compare with. + * @return -1 if this task should be sorted first, 1 if the other task should be sorted first, 0 otherwise. + */ + public abstract int compareDate(Task other); + + /** + * Compares this Task to another Task by their description. + * + * @param other The task to compare with. + * @return -1 if this task should be sorted first, 1 if the other task should be sorted first, 0 otherwise. + */ + public int compareDescription(Task other) { + return this.description.compareToIgnoreCase(other.description); + } + + /** + * Compares this Task to another Task by their status. + * + * @param other The task to compare with. + * @return -1 if this task should be sorted first, 1 if the other task should be sorted first, 0 otherwise. + */ + public int compareStatus(Task other) { + if (!this.isDone && other.isDone) { + return -1; + } else if (this.isDone && !other.isDone) { + return 1; + } else { + return 0; + } + } + + /** + * Sets the completion status of this task. + * + * @param isDone New completion status of the task. + * @return String with message for user. + */ + public String setDone(boolean isDone) { + this.isDone = isDone; + return isDone + ? " Great job! I will mark the task as completed.\n" + " " + this + : " Understood. I will mark the task as uncompleted.\n" + " " + this; + } + + /** + * Checks if the description of this task contains a search term. + * + * @param term The Search Term. + * @return true if it contains the search term, false otherwise. + */ + public boolean isMatch(String term) { + return this.description.contains(term); + } +} diff --git a/src/main/java/ren/task/Todo.java b/src/main/java/ren/task/Todo.java new file mode 100644 index 0000000000..304b89e2f9 --- /dev/null +++ b/src/main/java/ren/task/Todo.java @@ -0,0 +1,76 @@ +package ren.task; + +/** + * Todo Task represents an action that needs to be done. + */ +public class Todo extends Task { + /** + * Constructor for a Todo Task. + * + * @param description Description of the Task. + */ + public Todo(String description) { + super(description); + } + + /** + * Returns a Todo Task constructed with data read from File. + * + * @param data Data of a Todo Task. + * @return Todo Task or null if data is corrupted. + */ + public static Todo readData(String[] data) { + // Check if data is complete + assert data.length == 3 : "data[] in readData in Todo should be of size 3"; + + Todo newTodo = new Todo(data[2]); + if (data[1].equals("X")) { + newTodo.setDone(true); + } + return newTodo; + } + + /** + * Returns the Todo Task information for writing to a File. + * + * @return String with Todo Task information. + */ + @Override + public String writeData() { + String symbol = this.isDone ? "X" : " "; + return "T|" + symbol + "|" + this.description; + } + + /** + * Compares this Todo to another Task by their type of task. + * + * @param other The task to compare with. + * @return -1 if the other task is not a Todo, 0 otherwise. + */ + @Override + public int compareType(Task other) { + return (other instanceof Todo) ? 0 : -1; + } + + /** + * Compares this Todo to another Task by their date. + * + * @param other The task to compare with. + * @return 1 if the other task is not a Todo, 0 otherwise. + */ + @Override + public int compareDate(Task other) { + return (other instanceof Todo) ? 0 : 1; + } + + /** + * Returns the string representation of this Todo Task. + * + * @return String Representation. + */ + @Override + public String toString() { + String symbol = this.isDone ? "X" : " "; + return "[T][" + symbol + "] " + this.description + "\n"; + } +} diff --git a/src/main/resources/images/ren.png b/src/main/resources/images/ren.png new file mode 100644 index 0000000000..1ffcc4df13 Binary files /dev/null and b/src/main/resources/images/ren.png differ diff --git a/src/main/resources/images/user.png b/src/main/resources/images/user.png new file mode 100644 index 0000000000..09a7f13418 Binary files /dev/null and b/src/main/resources/images/user.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..babfb938ac --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..c058f2c766 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/style.css b/src/main/resources/view/style.css new file mode 100644 index 0000000000..f3297709cf --- /dev/null +++ b/src/main/resources/view/style.css @@ -0,0 +1,23 @@ +.root { + -fx-background-color: #708090; +} + +.scroll-pane { + -fx-background-color: #708090; +} + +.scroll-pane .corner { + -fx-background-color: #708090; +} + +.scroll-pane .scroll-bar:vertical { + -fx-background-color: #708090; +} + +.scroll-pane .scroll-bar:horizontal { + -fx-background-color: #708090; +} + +.label .text { + -fx-fill: #000000; +} \ No newline at end of file diff --git a/src/test/java/ren/ParserTest.java b/src/test/java/ren/ParserTest.java new file mode 100644 index 0000000000..2f3d279127 --- /dev/null +++ b/src/test/java/ren/ParserTest.java @@ -0,0 +1,219 @@ +package ren; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; + +public class ParserTest { + @Test + public void parseCommand_bye_success() { + try { + Parser parser = new Parser(new TaskListStub()); + assertEquals(" Farewell!\n", parser.parseCommand("bye")); + } catch (RenException e) { + fail(); + } + } + + @Test + public void parseCommand_todo_success() { + try { + Parser parser = new Parser(new TaskListStub()); + assertEquals("", parser.parseCommand("todo test")); + } catch (RenException e) { + fail(); + } + } + + @Test + public void parseCommand_todo_exceptionThrown() { + try { + Parser parser = new Parser(new TaskListStub()); + parser.parseCommand("todo"); + fail(); + } catch (RenException e) { + assertEquals("Please provide a description for the todo.", e.getMessage()); + } + } + + @Test + public void parseCommand_deadline_success() { + try { + Parser parser = new Parser(new TaskListStub()); + assertEquals("", parser.parseCommand("deadline test /by test")); + } catch (RenException e) { + fail(); + } + } + + @Test + public void parseCommand_deadline_exceptionThrown() { + try { + Parser parser = new Parser(new TaskListStub()); + parser.parseCommand("deadline"); + fail(); + } catch (RenException e) { + assertEquals("Please provide a description for the deadline.", e.getMessage()); + } + } + + @Test + public void parseCommand_deadline2_exceptionThrown() { + try { + Parser parser = new Parser(new TaskListStub()); + parser.parseCommand("deadline test"); + fail(); + } catch (RenException e) { + assertEquals("Please provide a date/time for the deadline.", e.getMessage()); + } + } + + @Test + public void parseCommand_event_success() { + try { + Parser parser = new Parser(new TaskListStub()); + assertEquals("", parser.parseCommand("event test /at test")); + } catch (RenException e) { + fail(); + } + } + + @Test + public void parseCommand_event_exceptionThrown() { + try { + Parser parser = new Parser(new TaskListStub()); + parser.parseCommand("event"); + fail(); + } catch (RenException e) { + assertEquals("Please provide a description for the event.", e.getMessage()); + } + } + + @Test + public void parseCommand_event2_exceptionThrown() { + try { + Parser parser = new Parser(new TaskListStub()); + parser.parseCommand("event test"); + fail(); + } catch (RenException e) { + assertEquals("Please provide a date/time for the event.", e.getMessage()); + } + } + + @Test + public void parseCommand_delete_success() { + try { + Parser parser = new Parser(new TaskListStub()); + assertEquals("", parser.parseCommand("delete 2")); + } catch (RenException e) { + fail(); + } + } + + @Test + public void parseCommand_delete_exceptionThrown() { + try { + Parser parser = new Parser(new TaskListStub()); + parser.parseCommand("delete one"); + fail(); + } catch (RenException e) { + assertEquals("Please indicate the task no. in digits.", e.getMessage()); + } + } + + @Test + public void parseCommand_mark_success() { + try { + Parser parser = new Parser(new TaskListStub()); + assertEquals("", parser.parseCommand("mark 2")); + } catch (RenException e) { + fail(); + } + } + + @Test + public void parseCommand_mark_exceptionThrown() { + try { + Parser parser = new Parser(new TaskListStub()); + parser.parseCommand("unmark one"); + fail(); + } catch (RenException e) { + assertEquals("Please indicate the task no. in digits.", e.getMessage()); + } + } + + @Test + public void parseCommand_unmark_success() { + try { + Parser parser = new Parser(new TaskListStub()); + assertEquals("", parser.parseCommand("unmark 2")); + } catch (RenException e) { + fail(); + } + } + + @Test + public void parseCommand_unmark_exceptionThrown() { + try { + Parser parser = new Parser(new TaskListStub()); + parser.parseCommand("unmark one"); + fail(); + } catch (RenException e) { + assertEquals("Please indicate the task no. in digits.", e.getMessage()); + } + } + + @Test + public void parseCommand_list_success() { + try { + Parser parser = new Parser(new TaskListStub()); + assertEquals("", parser.parseCommand("list")); + } catch (RenException e) { + fail(); + } + } + + @Test + public void parseCommand_find_success() { + try { + Parser parser = new Parser(new TaskListStub()); + assertEquals("", parser.parseCommand("find test")); + } catch (RenException e) { + fail(); + } + } + + @Test + public void parseCommand_find_exceptionThrown() { + try { + Parser parser = new Parser(new TaskListStub()); + parser.parseCommand("find"); + fail(); + } catch (RenException e) { + assertEquals("Please provide a search term.", e.getMessage()); + } + } + + @Test + public void parseCommand_empty_success() { + try { + Parser parser = new Parser(new TaskListStub()); + assertEquals("", parser.parseCommand("empty")); + } catch (RenException e) { + fail(); + } + } + + @Test + public void parseCommand_blah_exceptionThrown() { + try { + Parser parser = new Parser(new TaskListStub()); + parser.parseCommand("blah"); + fail(); + } catch (RenException e) { + assertEquals("Please enter a supported command.", e.getMessage()); + } + } +} + diff --git a/src/test/java/ren/RenExceptionTest.java b/src/test/java/ren/RenExceptionTest.java new file mode 100644 index 0000000000..6f62dafe24 --- /dev/null +++ b/src/test/java/ren/RenExceptionTest.java @@ -0,0 +1,18 @@ +package ren; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class RenExceptionTest { + @Test + public void toString_message_success() { + String message = "Test"; + assertEquals(" >_< Apologies! Test\n", new RenException(message).toString()); + } + + @Test + public void toString_empty_success() { + assertEquals(" >_< Apologies! \n", new RenException("").toString()); + } +} diff --git a/src/test/java/ren/TaskListStub.java b/src/test/java/ren/TaskListStub.java new file mode 100644 index 0000000000..0197944271 --- /dev/null +++ b/src/test/java/ren/TaskListStub.java @@ -0,0 +1,43 @@ +package ren; + +/** + * Stub for TaskList. + */ +public class TaskListStub extends TaskList { + /** + * Constructor for a TaskListStub. + */ + public TaskListStub() { + super(new Storage("data/list_test.txt")); + } + + @Override + public String addTask(Ren.TaskType type, String task, String dateTime) { + return ""; + } + + @Override + public String deleteTask(int taskNum) { + return ""; + } + + @Override + public String updateTask(boolean status, int taskNum) { + return ""; + } + + @Override + public String listTasks() { + return ""; + } + + @Override + public String findTasks(String term) { + return ""; + } + + @Override + public String emptyList() { + return ""; + } +} diff --git a/src/test/java/ren/TimeStampTest.java b/src/test/java/ren/TimeStampTest.java new file mode 100644 index 0000000000..b5ca26cf55 --- /dev/null +++ b/src/test/java/ren/TimeStampTest.java @@ -0,0 +1,34 @@ +package ren; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; + +public class TimeStampTest { + @Test + public void of_dateTime_success() { + try { + TimeStamp test = TimeStamp.of("20/8/2022-22:04"); + assertEquals(" Sat, 20 August 2022 10:04 PM", test.toString()); + } catch (RenException e) { + fail(); + } + } + + @Test + public void of_invalidDateTime_exceptionThrown() { + try { + TimeStamp.of("lmao"); + fail(); + } catch (RenException e) { + assertEquals("Please indicate date and time properly. (20/8/2022-15:37)", e.getMessage()); + } + } + + @Test + public void fromFile_dateTime_success() { + TimeStamp test = TimeStamp.fromFile("Sat, 20 August 2022 10:04 PM"); + assertEquals(" Sat, 20 August 2022 10:04 PM", test.toString()); + } +} diff --git a/src/test/java/ren/task/DeadlineTest.java b/src/test/java/ren/task/DeadlineTest.java new file mode 100644 index 0000000000..a71cd56b57 --- /dev/null +++ b/src/test/java/ren/task/DeadlineTest.java @@ -0,0 +1,37 @@ +package ren.task; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; + +import ren.RenException; + +public class DeadlineTest { + @Test + public void toString_description_success() { + try { + Deadline test = new Deadline("test", "20/8/2022-11:11"); + assertEquals("[D][ ] test(by: Sat, 20 August 2022 11:11 AM)\n", test.toString()); + } catch (RenException e) { + fail(); + } + } + + @Test + public void writeData_description_success() { + try { + Deadline test = new Deadline("test", "20/8/2022-11:11"); + assertEquals("D| |test| Sat, 20 August 2022 11:11 AM", test.writeData()); + } catch (RenException e) { + fail(); + } + } + + @Test + public void readData_description_success() { + Deadline test = Deadline.readData(new String[] {"D", " ", "test", "Sat, 20 August 2022 11:11 AM"}); + assert test != null; + assertEquals("D| |test| Sat, 20 August 2022 11:11 AM", test.writeData()); + } +} diff --git a/src/test/java/ren/task/EventTest.java b/src/test/java/ren/task/EventTest.java new file mode 100644 index 0000000000..985f710603 --- /dev/null +++ b/src/test/java/ren/task/EventTest.java @@ -0,0 +1,41 @@ +package ren.task; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; + +import ren.RenException; + +public class EventTest { + @Test + public void toString_description_success() { + try { + Event test = new Event("test", "20/8/2022-11:11 ~ 20/8/2022-11:11"); + String expected = "[E][ ] test(at: Sat, 20 August 2022 11:11 AM - Sat, 20 August 2022 11:11 AM)\n"; + assertEquals(expected, test.toString()); + } catch (RenException e) { + fail(); + } + } + + @Test + public void writeData_description_success() { + try { + Event test = new Event("test", "20/8/2022-11:11 ~ 20/8/2022-11:11"); + String expected = "E| |test| Sat, 20 August 2022 11:11 AM| Sat, 20 August 2022 11:11 AM"; + assertEquals(expected, test.writeData()); + } catch (RenException e) { + fail(); + } + } + + @Test + public void readData_description_success() { + String[] input = {"E", " ", "test", "Sat, 20 August 2022 11:11 AM", "Sat, 20 August 2022 11:11 AM"}; + Event test = Event.readData(input); + assert test != null; + String expected = "E| |test| Sat, 20 August 2022 11:11 AM| Sat, 20 August 2022 11:11 AM"; + assertEquals(expected, test.writeData()); + } +} diff --git a/src/test/java/ren/task/TodoTest.java b/src/test/java/ren/task/TodoTest.java new file mode 100644 index 0000000000..4f2ded8c15 --- /dev/null +++ b/src/test/java/ren/task/TodoTest.java @@ -0,0 +1,24 @@ +package ren.task; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class TodoTest { + @Test + public void toString_description_success() { + assertEquals("[T][ ] test\n", new Todo("test").toString()); + } + + @Test + public void writeData_description_success() { + assertEquals("T| |test", new Todo("test").writeData()); + } + + @Test + public void readData_description_success() { + Todo test = Todo.readData(new String[] {"T", " ", "test"}); + assert test != null; + assertEquals("T| |test", test.writeData()); + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..736be26b02 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,269 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| +======================================================================================= + Greetings! My name is Ren ^_^ + How may I be of service today? + +======================================================================================= +======================================================================================= + + Here are your current tasks: + 1.[T][ ] todoTest + 2.[D][ ] deadlineTest (by: Sat, 8 January 2000 12:00 PM) + 3.[E][ ] eventTest (at: Sat, 8 January 2000 12:00 PM - Sat, 20 August 2022 5:59 PM) + +======================================================================================= +======================================================================================= + + Understood. I have emptied your list of tasks. +======================================================================================= +======================================================================================= + + You have not added any tasks! + +======================================================================================= +======================================================================================= + + Understood. I have emptied your list of tasks. +======================================================================================= +======================================================================================= + + You have not added any tasks! + +======================================================================================= +======================================================================================= + + ☹ Apologies! You have no tasks to mark or unmark. + +======================================================================================= +======================================================================================= + + ☹ Apologies! You have no tasks to mark or unmark. + +======================================================================================= +======================================================================================= + + ☹ Apologies! You have no tasks to delete. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please provide a description for the todo. + +======================================================================================= +======================================================================================= + + Understood. I have added the following task: + [T][ ] todoTest + You now have a total of 1 task(s). + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please provide a description for the deadline. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please provide a date/time for the deadline. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate date and time properly. (20/8/2022-15:37) + +======================================================================================= +======================================================================================= + + Understood. I have added the following task: + [D][ ] deadlineTest (by: Sat, 8 January 2000 12:00 PM) + You now have a total of 2 task(s). + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please provide a description for the event. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please provide a date/time for the event. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate date and time properly. (20/8/2022-15:37) + +======================================================================================= +======================================================================================= + + Understood. I have added the following task: + [E][ ] eventTest (at: Sat, 8 January 2000 12:00 PM - Sat, 20 August 2022 5:59 PM) + You now have a total of 3 task(s). + +======================================================================================= +======================================================================================= + + Here are your current tasks: + 1.[T][ ] todoTest + 2.[D][ ] deadlineTest (by: Sat, 8 January 2000 12:00 PM) + 3.[E][ ] eventTest (at: Sat, 8 January 2000 12:00 PM - Sat, 20 August 2022 5:59 PM) + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate a task no. between 1 to 3. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate a task no. between 1 to 3. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate a task no. between 1 to 3. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate the task no. in digits. + +======================================================================================= +======================================================================================= + + Great job! I will mark the task as completed. + [D][X] deadlineTest (by: Sat, 8 January 2000 12:00 PM) + +======================================================================================= +======================================================================================= + + Great job! I will mark the task as completed. + [E][X] eventTest (at: Sat, 8 January 2000 12:00 PM - Sat, 20 August 2022 5:59 PM) + +======================================================================================= +======================================================================================= + + Great job! I will mark the task as completed. + [D][X] deadlineTest (by: Sat, 8 January 2000 12:00 PM) + +======================================================================================= +======================================================================================= + + Here are your current tasks: + 1.[T][ ] todoTest + 2.[D][X] deadlineTest (by: Sat, 8 January 2000 12:00 PM) + 3.[E][X] eventTest (at: Sat, 8 January 2000 12:00 PM - Sat, 20 August 2022 5:59 PM) + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate a task no. between 1 to 3. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate a task no. between 1 to 3. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate a task no. between 1 to 3. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate the task no. in digits. + +======================================================================================= +======================================================================================= + + Understood. I will mark the task as uncompleted. + [D][ ] deadlineTest (by: Sat, 8 January 2000 12:00 PM) + +======================================================================================= +======================================================================================= + + Understood. I will mark the task as uncompleted. + [E][ ] eventTest (at: Sat, 8 January 2000 12:00 PM - Sat, 20 August 2022 5:59 PM) + +======================================================================================= +======================================================================================= + + Understood. I will mark the task as uncompleted. + [D][ ] deadlineTest (by: Sat, 8 January 2000 12:00 PM) + +======================================================================================= +======================================================================================= + + Here are your current tasks: + 1.[T][ ] todoTest + 2.[D][ ] deadlineTest (by: Sat, 8 January 2000 12:00 PM) + 3.[E][ ] eventTest (at: Sat, 8 January 2000 12:00 PM - Sat, 20 August 2022 5:59 PM) + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please enter a supported command. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate a task no. between 1 to 3. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate a task no. between 1 to 3. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate a task no. between 1 to 3. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate the task no. in digits. + +======================================================================================= +======================================================================================= + + Understood. I have removed the following task: + [E][ ] eventTest (at: Sat, 8 January 2000 12:00 PM - Sat, 20 August 2022 5:59 PM) + You have a total of 2 task(s) left. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate a task no. between 1 to 2. + +======================================================================================= +======================================================================================= + + Here are your current tasks: + 1.[T][ ] todoTest + 2.[D][ ] deadlineTest (by: Sat, 8 January 2000 12:00 PM) + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate a task no. between 1 to 2. + +======================================================================================= +======================================================================================= + + ☹ Apologies! Please indicate a task no. between 1 to 2. + +======================================================================================= +======================================================================================= + + Understood. I have added the following task: + [E][ ] eventTest (at: Sat, 8 January 2000 12:00 PM - Sat, 20 August 2022 5:59 PM) + You now have a total of 3 task(s). + +======================================================================================= +======================================================================================= + + Farewell! + +======================================================================================= diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..b1fc3a9667 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,47 @@ +list +empty +list +empty +list +mark 0 +unmark 0 +delete 0 +todo +todo todoTest +deadline +deadline deadlineTest +deadline deadlineTest /by 99/99/9999-99:99 +deadline deadlineTest /by 08/01/2000-12:00 +event +event eventTest +event eventTest /at 00/00/0000-00:00 ~ 99/99/9999-99:99 +event eventTest /at 08/01/2000-12:00 ~ 20/8/2022-17:59 +list +mark +mark -1 +mark 99 +mark three +mark 2 +mark 3 please +mark 2 +list +unmark +unmark -1 +unmark 99 +unmark three +unmark 2 +unmark 3 please +unmark 2 +list +blah +delete +delete -1 +delete 99 +delete three +delete 3 +delete 3 +list +mark 3 +unmark 3 +event eventTest /at 08/01/2000-12:00 ~ 20/8/2022-17:59 +bye \ No newline at end of file diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 0873744649..140a9bac99 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -7,7 +7,12 @@ REM delete output from previous run if exist ACTUAL.TXT del ACTUAL.TXT REM compile the code into the bin folder -javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\*.java +javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\ren\*.java +IF ERRORLEVEL 1 ( + echo ********** BUILD FAILURE ********** + exit /b 1 +) +javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\ren\task\*.java IF ERRORLEVEL 1 ( echo ********** BUILD FAILURE ********** exit /b 1 @@ -15,7 +20,7 @@ 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 ren.Ren < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh old mode 100644 new mode 100755 index c9ec870033..926f95e305 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,14 +13,19 @@ then fi # compile the code into the bin folder, terminates if error occurred -if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java +if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/ren/*.java +then + echo "********** BUILD FAILURE **********" + exit 1 +fi +if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/ren/task/*.java then echo "********** BUILD FAILURE **********" exit 1 fi # run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ../bin Duke < input.txt > ACTUAL.TXT +java -classpath ../bin ren.Ren < input.txt > ACTUAL.TXT # convert to UNIX format cp EXPECTED.TXT EXPECTED-UNIX.TXT