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 f69985ef1f..bebac7b71e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,5 @@ bin/
/text-ui-test/ACTUAL.txt
text-ui-test/EXPECTED-UNIX.TXT
+/data
+/text-ui-test/data/
diff --git a/README.md b/README.md
index 8715d4d915..23e9b152b9 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,20 @@
-# Duke project template
+# Little Duke
-This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it.
+Little Duke is **great** for anyone who keeps a list of tasks! It
-## Setting up in Intellij
+* has text-based commands
+* has a minimalistic user interface
+* is simple but *lightning* fast
-Prerequisites: JDK 11, update Intellij to the most recent version.
+All you need to is
-1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first)
-1. Open the project into Intellij as follows:
- 1. Click `Open`.
- 1. Select the project directory, and click `OK`.
- 1. If there are any further prompts, accept the defaults.
-1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
- In the same dialog, set the **Project language level** field to the `SDK default` option.
-3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output:
- ```
- Hello from
- ____ _
- | _ \ _ _| | _____
- | | | | | | | |/ / _ \
- | |_| | |_| | < __/
- |____/ \__,_|_|\_\___|
- ```
+1. have Java 11 installed.
+1. download the latest release from [here](https://github.com/seetohjinwei/ip/releases)!
+1. double-click it (or run `java -jar duke.jar`)
+1. add your tasks!
+1. ...
+1. profit!
+
+And best of all, it is **FREE**!
+
+Check out the online User Guide [here](https://seetohjinwei.github.io/ip/).
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..f4da540c51
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,61 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'com.github.johnrengelman.shadow' version "5.1.0"
+ id 'checkstyle'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ 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'
+
+ 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'
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed"
+
+ showExceptions true
+ exceptionFormat "full"
+ showCauses true
+ showStackTraces true
+ showStandardStreams = false
+ }
+}
+
+application {
+ mainClassName = "duke.Launcher"
+}
+
+shadowJar {
+ archiveFileName.set("duke.jar")
+}
+
+checkstyle {
+ toolVersion = '10.2'
+}
+
+run{
+ standardInput = System.in
+ enableAssertions = true
+}
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..e3c0256c59
--- /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..6423654db1
--- /dev/null
+++ b/config/checkstyle/suppressions.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/docs/README.md b/docs/README.md
index 8077118ebe..5f6bbb54ee 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,29 +1,257 @@
# User Guide
+Little Duke is a great companion in helping you manage your tasks!
+
+![Image](Ui.png)
+
## Features
-### Feature-ABC
+### Tasks
+
+Add various types of tasks to your task list.
+
+#### Todo Task
+
+Add a Todo to your task list.
+
+#### Deadline Task
+
+Add a Deadline to your task list. Deadline is a task with a deadline.
+
+#### Event Task
+
+Add an event to your task list. Event is a task with a event time.
+
+### List
+
+Lists all your tasks.
+
+### Mark
+
+Mark a task as done.
+
+### Un-mark
+
+Un-mark a task as done.
+
+### Find
-Description of the feature.
+Find tasks by keyword.
-### Feature-XYZ
+### Undo
-Description of the feature.
+Undo previous commands.
+
+### Command History
+
+Navigate through your command history with up and down arrow keys.
+
+### Delete
+
+Delete a task.
+
+### Exit
+
+Exits the application after a short delay.
+
+### Resizable
+
+Application is resizable! Just re-size the window as you would do with any other application.
## Usage
-### `Keyword` - Describe action
+### `todo` - Create a Todo task
-Describe the action and its outcome.
+Creates a Todo task with the specified description.
+
+`todo `
Example of usage:
-`keyword (optional arguments)`
+`todo Nice task.`
+
+Expected outcome:
+
+A Todo is created with the description and date/time provided.
+
+```
+*beep* I've added this todo for you!
+ [T][ ] Nice task.
+Now you have tasks!
+```
+
+### `deadline` - Create a Deadline task
+
+Creates a Deadline task with the specified description and deadline.
+
+`deadline /by ` (date is YYYY-MM-DD HH:mm)
+
+Example of usage:
+
+`deadline Submit XXX. /by 2022-09-11 2359`
+
+Expected outcome:
+
+A Deadline is created with the description and date/time provided.
+
+```
+*beep* I've added this deadline for you!
+ [D][ ] Submit XXX. (by: 11 Sep 22 23:59)
+Now you have tasks!
+```
+
+### `event` - Create an Event task
+
+Creates an Event task with the specified description and date/time.
+
+`event /by ` (date is YYYY-MM-DD HH:mm)
+
+Example of usage:
+
+`event Nice Event. /at 2022-09-11 2359`
+
+Expected outcome:
+
+An Event is created with the description and date/time provided.
+
+```
+*beep* I've added this event for you!
+ [E][ ] Nice Event. (at: 11 Sep 22 23:59)
+Now you have tasks!
+```
+
+### `list` - List all tasks
+
+List all your tasks.
+
+`list` (any arguments are ignored)
+
+Example of usage:
+
+`list`
+
+Expected outcome:
+
+A list of all the tasks.
+
+```
+*beeeeep* I've got your tasks!!
+1. [T][ ] Nice task.
+2. [D][ ] Submit XXX (by: 11 Sep 22 23:59).
+3. [E][ ] Nice Event. (at: 11 Sep 22 23:59).
+```
+
+### `mark` - Mark task as done
+
+Marks a task as done.
+
+`mark `
+
+Example of usage:
+
+`mark 2`
+
+Expected outcome:
+
+Task 2 will be marked as done.
+
+```
+*beep beep* Roger, task 2 is done!
+ [D][X] Submit XXX. (by: 11 Sep 22 23:59).
+```
+
+### `unmark` - Un-mark task as done
+
+Marks a task as done.
+
+`unmark `
+
+Example of usage:
+
+`unmark 2`
+
+Expected outcome:
+
+Task 2 will be marked as not done.
+
+```
+*beep beep* Roger, task 2 is not done!
+ [D][ ] Submit XXX. (by: 11 Sep 22 23:59).
+```
+
+### `find` - Find tasks by keywords
+
+Finds tasks by keywords.
+
+`find `
+
+Example of usage:
+
+`find Nice`
+
+Expected outcome:
+
+A list of tasks that match the keywords provided.
+
+```
+*beeeeep* I've found some tasks!
+1. [T][ ] Nice task.
+2. [E][ ] Nice Event. (at: 11 Sep 22 23:59).
+```
+
+### `undo` - Undoes the previous command
+
+Undoes the previous command.
+
+`undo` (any arguments are ignored)
+
+Example of usage:
+
+`undo`
+
+Expected outcome:
+
+The previous command will be undone, if there are any.
+
+```
+Whoops, undoing previous command!!
+```
+
+### `delete` - Delete a task
+
+Deletes a task.
+
+`delete `
+
+Example of usage:
+
+`delete 1`
+
+Expected outcome:
+
+Task at index 1 is deleted.
+
+```
+*beeeeep* I've removed the task!
+ [T][ ] Nice task.
+Now you have tasks left.
+```
+
+### `bye` - Exit the application
+
+Exits the application.
+
+`bye` (any arguments are ignored)
+
+Example of usage:
+
+`bye`
Expected outcome:
-Description of the outcome.
+The application closes after a short delay.
```
-expected output
+Bye! *sad beep*
+Hope to see you soon!
```
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..d7e7b5577f
Binary files /dev/null and b/docs/Ui.png differ
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000000..50a57557df
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1,3 @@
+remote_theme: pages-themes/midnight@v0.2.0
+plugins:
+ - jekyll-remote-theme # add this line to the plugins list if you already have one
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..41d9927a4d
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..41dfb87909
--- /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-7.4-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000000..1b6c787337
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${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 "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# 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 ;; #(
+ MSYS* | 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" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000000..ac1b06f938
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@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 execute
+
+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 execute
+
+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
+
+: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 %*
+
+: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/duke/Duke.java b/src/main/java/duke/Duke.java
new file mode 100644
index 0000000000..7d99db024a
--- /dev/null
+++ b/src/main/java/duke/Duke.java
@@ -0,0 +1,186 @@
+package duke;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import duke.commands.Command;
+import duke.commands.CommandResult;
+import duke.exceptions.DukeException;
+import duke.exceptions.NoUndoActionsException;
+import duke.parser.Parser;
+import duke.storage.Storage;
+import duke.task.TaskList;
+import duke.undo.UndoAction;
+import javafx.application.Platform;
+
+/**
+ * Represents the Duke application.
+ * Responsible for user interaction.
+ */
+public class Duke {
+ private static final String WELCOME_MESSAGE = "Beep boop!\n"
+ + "I'm your friendly neighbourhood bot, Little Duke!";
+ // Delay is in milliseconds
+ private static final int DELAY_ON_EXIT = 1500;
+
+ private final Parser parser;
+ private final Storage storage;
+ private final TaskList tasks;
+ /* Index of current command history location. */
+ private int commandsHistoryPointer;
+ /* History of commands. */
+ private final ArrayList commandsHistory;
+ /* Actions to undo. */
+ private final LinkedList undoActions;
+
+ /**
+ * Constructs a Duke application instance.
+ */
+ public Duke() {
+ parser = new Parser();
+ storage = new Storage("data", "data/tasks");
+ tasks = retrieveTasks();
+ assert this.tasks != null : "Tasks should not be null after being loaded.";
+ commandsHistoryPointer = 0;
+ commandsHistory = new ArrayList<>();
+ undoActions = new LinkedList<>();
+ }
+
+ /**
+ * Retrieves tasks from storage.
+ * If loading from storage fails, empty list is returned.
+ *
+ * @return Tasks from storage or empty list.
+ */
+ private TaskList retrieveTasks() {
+ TaskList tasks = new TaskList();
+ try {
+ tasks = storage.load();
+ } catch (DukeException e) {
+ e.printStackTrace();
+ }
+ return tasks;
+ }
+
+ /**
+ * Gets the previous command and moves the pointer of the history up by 1.
+ * If history is empty, null is returned.
+ *
+ * @return Previous command or null.
+ */
+ public String getPreviousCommand() {
+ if (commandsHistory.isEmpty()) {
+ return null;
+ }
+ commandsHistoryPointer = Math.max(0, commandsHistoryPointer - 1);
+ return commandsHistory.get(commandsHistoryPointer);
+ }
+
+ /**
+ * Gets the next command and moves the pointer of the history down by 1.
+ * If history is empty, null is returned.
+ *
+ * @return Next command or null.
+ */
+ public String getNextCommand() {
+ if (commandsHistory.isEmpty()) {
+ return null;
+ }
+ commandsHistoryPointer = Math.min(commandsHistory.size(), commandsHistoryPointer + 1);
+ if (commandsHistoryPointer == commandsHistory.size()) {
+ // Pointing to new "empty" command.
+ return "";
+ }
+ return commandsHistory.get(commandsHistoryPointer);
+ }
+
+ private void addToCommandHistory(String input) {
+ commandsHistory.add(input);
+ // Set the command history pointer to point to the newest command.
+ commandsHistoryPointer = commandsHistory.size();
+ }
+
+ private CommandResult parseAndExecuteInput(String input) throws DukeException {
+ Command command = parser.parseCommand(input);
+ assert command != null : "Command returned from parseCommand should never be null.";
+ command.setData(tasks);
+ return command.execute();
+ }
+
+ private void exitIfRequired(CommandResult result) {
+ if (!result.shouldExit()) {
+ return;
+ }
+ // Timer solution below adapted from: https://stackoverflow.com/a/21974490/
+ new Timer().schedule(new TimerTask() {
+ @Override
+ public void run() {
+ Platform.exit();
+ System.exit(0);
+ }
+ }, DELAY_ON_EXIT);
+ }
+
+ private void updateFileIfRequired(CommandResult result) throws IOException {
+ if (!result.shouldUpdateFile()) {
+ return;
+ }
+ storage.save(tasks);
+ }
+
+ private void addToUndoActions(CommandResult result) {
+ UndoAction undoAction = result.getUndoAction();
+ if (undoAction == null) {
+ return;
+ }
+ undoActions.push(undoAction);
+ }
+
+ private void useUndoActionIfRequired(CommandResult result) throws DukeException {
+ if (!result.shouldUndo()) {
+ return;
+ } else if (undoActions.isEmpty()) {
+ throw new NoUndoActionsException();
+ }
+ UndoAction undoAction = undoActions.pop();
+ assert undoAction != null : "Null action should not have been added to undoActions.";
+ undoAction.perform(tasks);
+ }
+
+ private String handleUserInput(String input) {
+ try {
+ CommandResult result = parseAndExecuteInput(input);
+ assert result != null : "Result from the execution of a command should never be null.";
+ exitIfRequired(result);
+ addToUndoActions(result);
+ useUndoActionIfRequired(result);
+ updateFileIfRequired(result);
+ return result.getUserMessage();
+ } catch (DukeException | IOException e) {
+ return e.getMessage();
+ }
+ }
+
+ /**
+ * Gets the welcome message from the Duke application.
+ *
+ * @return Gets the welcome message for the Duke application.
+ */
+ public String getWelcomeMessage() {
+ return WELCOME_MESSAGE;
+ }
+
+ /**
+ * Gets the response from the Duke application based on a particular input.
+ *
+ * @param input User input provided.
+ * @return Response or error message from the Duke application.
+ */
+ public String getResponse(String input) {
+ addToCommandHistory(input);
+ return handleUserInput(input);
+ }
+}
diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java
new file mode 100644
index 0000000000..961db09e33
--- /dev/null
+++ b/src/main/java/duke/Launcher.java
@@ -0,0 +1,18 @@
+package duke;
+
+import javafx.application.Application;
+
+/**
+ * Represents the launcher for launching the Duke application.
+ * Used as a workaround for classpath issues.
+ */
+public class Launcher {
+ /**
+ * Enters the Duke application.
+ *
+ * @param args Arguments for duke application.
+ */
+ public static void main(String[] args) {
+ Application.launch(Main.class, args);
+ }
+}
diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java
new file mode 100644
index 0000000000..866fff5066
--- /dev/null
+++ b/src/main/java/duke/Main.java
@@ -0,0 +1,46 @@
+package duke;
+
+import java.io.IOException;
+
+import duke.ui.MainWindow;
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+
+/**
+ * Represents the GUI for Duke using FXML.
+ */
+public class Main extends Application {
+ private static final String APPLICATION_TITLE = "Little Duke";
+ private static final double minWidth = 300.0;
+ private static final double minHeight = 200.0;
+ private final Duke duke = new Duke();
+
+ /**
+ * Starts the GUI for the Stage that takes in a stage.
+ *
+ * @param stage the primary stage for this application, onto which
+ * the application scene can be set.
+ * Applications may create other stages, if needed, but they will not be
+ * primary stages.
+ */
+ @Override
+ public void start(Stage stage) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
+ VBox vBox = fxmlLoader.load();
+ Scene scene = new Scene(vBox);
+ scene.getStylesheets().add("/styles/stylesheet.css");
+ stage.setScene(scene);
+ stage.setTitle(APPLICATION_TITLE);
+ stage.setMinWidth(minWidth);
+ stage.setMinHeight(minHeight);
+ fxmlLoader.getController().setDuke(duke);
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/duke/commands/Command.java b/src/main/java/duke/commands/Command.java
new file mode 100644
index 0000000000..e1e53105f8
--- /dev/null
+++ b/src/main/java/duke/commands/Command.java
@@ -0,0 +1,28 @@
+package duke.commands;
+
+import duke.exceptions.DukeException;
+import duke.task.TaskList;
+
+/**
+ * Represents a command in the Duke application.
+ */
+public abstract class Command {
+ protected TaskList tasks;
+
+ /**
+ * Sets the data for the command.
+ *
+ * @param tasks Tasks associated with the command.
+ */
+ public void setData(TaskList tasks) {
+ this.tasks = tasks;
+ }
+
+ /**
+ * Executes the command.
+ *
+ * @return Result of the execution.
+ * @throws DukeException Exception that occurred during the execution of the command.
+ */
+ public abstract CommandResult execute() throws DukeException;
+}
diff --git a/src/main/java/duke/commands/CommandResult.java b/src/main/java/duke/commands/CommandResult.java
new file mode 100644
index 0000000000..a842b0d5c7
--- /dev/null
+++ b/src/main/java/duke/commands/CommandResult.java
@@ -0,0 +1,104 @@
+package duke.commands;
+
+import duke.undo.UndoAction;
+
+/**
+ * Represents the result of a command's execution.
+ */
+public class CommandResult {
+ /**
+ * Action to be performed based on the command.
+ */
+ protected enum Action {
+ NONE,
+ UPDATE_FILE,
+ EXIT,
+ UNDO,
+ }
+
+ private final String userMessage;
+ private final Action action;
+ private final UndoAction undoAction;
+
+ /**
+ * Constructs a command result.
+ *
+ * @param userMessage User message to be displayed to the user.
+ * @param action Action to be performed.
+ */
+ public CommandResult(String userMessage, Action action) {
+ this.userMessage = userMessage;
+ this.action = action;
+ this.undoAction = null;
+ }
+
+ /**
+ * Constructs a command result.
+ *
+ * @param userMessage User message to be displayed to the user.
+ * @param undoAction Undo action to be performed.
+ */
+ public CommandResult(String userMessage, Action action, UndoAction undoAction) {
+ this.userMessage = userMessage;
+ this.action = action;
+ this.undoAction = undoAction;
+ }
+
+ /**
+ * Constructs a command result.
+ *
+ * @param userMessage User message to be displayed to the user.
+ */
+ public CommandResult(String userMessage) {
+ this.userMessage = userMessage;
+ this.action = Action.NONE;
+ this.undoAction = null;
+ }
+
+ /**
+ * Gets the user message to be displayed to the user.
+ *
+ * @return User message.
+ */
+ public String getUserMessage() {
+ return userMessage;
+ }
+
+ /**
+ * Gets whether the command resulted in any changes to the task list.
+ * Undo action will require an update to the file.
+ *
+ * @return Whether there are any updates to the file.
+ */
+ public boolean shouldUpdateFile() {
+ return action == Action.UPDATE_FILE || action == Action.UNDO;
+ }
+
+ /**
+ * Gets whether the command requests for the termination of the application.
+ *
+ * @return Whether the application should exit.
+ */
+ public boolean shouldExit() {
+ return action == Action.EXIT;
+ }
+
+ /**
+ * Gets the undo action.
+ * Null if there is no undo action.
+ *
+ * @return Undo action or null.
+ */
+ public UndoAction getUndoAction() {
+ return undoAction;
+ }
+
+ /**
+ * Gets whether the command requests for the undoing the previous command.
+ *
+ * @return Whether the application should undo the previous command.
+ */
+ public boolean shouldUndo() {
+ return action == Action.UNDO;
+ }
+}
diff --git a/src/main/java/duke/commands/DeadlineCommand.java b/src/main/java/duke/commands/DeadlineCommand.java
new file mode 100644
index 0000000000..a3bbb8347b
--- /dev/null
+++ b/src/main/java/duke/commands/DeadlineCommand.java
@@ -0,0 +1,65 @@
+package duke.commands;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import duke.exceptions.DukeException;
+import duke.exceptions.InvalidDateException;
+import duke.exceptions.WrongDeadlineFormatException;
+import duke.task.Deadline;
+import duke.undo.TaskUndo;
+
+/**
+ * Represents a deadline command in the Duke application.
+ */
+public class DeadlineCommand extends Command {
+ /** Command word of the deadline command. */
+ public static final String COMMAND_WORD = "deadline";
+ private static final Pattern ARGUMENTS_FORMAT =
+ Pattern.compile("(?.+)\\s+/by\\s+(?\\d{4}-\\d{2}-\\d{2}\\s+\\d{4})");
+ private static final String USER_MESSAGE_FORMAT = "*beep* I've added this deadline for you!\n"
+ + " %s\n"
+ + "Now you have %d tasks!";
+ private final Deadline deadline;
+
+ /**
+ * Constructs a deadline command with arguments.
+ *
+ * @param arguments Arguments string is to be of the format "description /by YYYY-MM-DD".
+ * @throws DukeException Exception due to invalid arguments.
+ */
+ public DeadlineCommand(String arguments) throws DukeException {
+ Matcher matcher = ARGUMENTS_FORMAT.matcher(arguments);
+ String description;
+ String deadline;
+ if (matcher.matches()) {
+ description = matcher.group("description");
+ deadline = matcher.group("deadline");
+ } else {
+ throw new WrongDeadlineFormatException();
+ }
+
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm");
+ LocalDateTime localDateTime;
+ try {
+ localDateTime = LocalDateTime.parse(deadline, formatter);
+ } catch (DateTimeParseException e) {
+ throw new InvalidDateException();
+ }
+
+ this.deadline = new Deadline(description, localDateTime);
+ }
+
+ @Override
+ public CommandResult execute() {
+ assert tasks != null : "Should setData() before calling execute().";
+ tasks.addTask(deadline);
+ TaskUndo undoAction = new TaskUndo(deadline);
+ int numberOfTasks = tasks.size();
+ String userMessage = String.format(USER_MESSAGE_FORMAT, deadline, numberOfTasks);
+ return new CommandResult(userMessage, CommandResult.Action.UPDATE_FILE, undoAction);
+ }
+}
diff --git a/src/main/java/duke/commands/DeleteCommand.java b/src/main/java/duke/commands/DeleteCommand.java
new file mode 100644
index 0000000000..0ec6cc0e91
--- /dev/null
+++ b/src/main/java/duke/commands/DeleteCommand.java
@@ -0,0 +1,47 @@
+package duke.commands;
+
+import duke.exceptions.DukeException;
+import duke.exceptions.InvalidIndexException;
+import duke.task.Task;
+import duke.undo.DeleteUndo;
+import duke.undo.UndoAction;
+
+/**
+ * Represents a delete command in the Duke application.
+ */
+public class DeleteCommand extends Command {
+ /** Command word of the delete command. */
+ public static final String COMMAND_WORD = "delete";
+ private static final String USER_MESSAGE_FORMAT = "*beeeeep* I've removed the task!\n %s\n"
+ + "Now you have %d tasks left.";
+ private final int index;
+
+ /**
+ * Constructs for a delete command with arguments.
+ *
+ * @param arguments Arguments string is to be of the format "N".
+ */
+ public DeleteCommand(String arguments) throws DukeException {
+ try {
+ index = Integer.parseInt(arguments);
+ } catch (NumberFormatException e) {
+ throw new InvalidIndexException();
+ }
+ }
+
+ @Override
+ public CommandResult execute() throws DukeException {
+ assert tasks != null : "Should setData() before calling execute().";
+ // Check if index is out of bounds.
+ if (index <= 0 || index > tasks.size()) {
+ throw new InvalidIndexException();
+ }
+ // Subtract 1 to account for 0-index data structure.
+ Task task = tasks.getTask(index - 1);
+ UndoAction undoAction = new DeleteUndo(task);
+ tasks.removeTask(index - 1);
+ int numberOfTasks = tasks.size();
+ String userMessage = String.format(USER_MESSAGE_FORMAT, task, numberOfTasks);
+ return new CommandResult(userMessage, CommandResult.Action.UPDATE_FILE, undoAction);
+ }
+}
diff --git a/src/main/java/duke/commands/EventCommand.java b/src/main/java/duke/commands/EventCommand.java
new file mode 100644
index 0000000000..c60d66e936
--- /dev/null
+++ b/src/main/java/duke/commands/EventCommand.java
@@ -0,0 +1,65 @@
+package duke.commands;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import duke.exceptions.DukeException;
+import duke.exceptions.InvalidDateException;
+import duke.exceptions.WrongEventFormatException;
+import duke.task.Event;
+import duke.undo.TaskUndo;
+
+/**
+ * Represents an event command in the Duke application.
+ */
+public class EventCommand extends Command {
+ /** Command word of the event command. */
+ public static final String COMMAND_WORD = "event";
+ private static final Pattern ARGUMENTS_FORMAT =
+ Pattern.compile("(?.+)\\s+/at\\s+(?\\d{4}-\\d{2}-\\d{2}\\s+\\d{4})");
+ private static final String USER_MESSAGE_FORMAT = "*beep* I've added this event for you!\n"
+ + " %s\n"
+ + "Now you have %d tasks!";
+ private final Event event;
+
+ /**
+ * Constructs an event command with arguments.
+ *
+ * @param arguments Arguments string is to be of the format "description /at YYYY-MM-DD".
+ * @throws DukeException Exception due to invalid arguments.
+ */
+ public EventCommand(String arguments) throws DukeException {
+ Matcher matcher = ARGUMENTS_FORMAT.matcher(arguments);
+ String description;
+ String date;
+ if (matcher.matches()) {
+ description = matcher.group("description");
+ date = matcher.group("date");
+ } else {
+ throw new WrongEventFormatException();
+ }
+
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm");
+ LocalDateTime localDateTime;
+ try {
+ localDateTime = LocalDateTime.parse(date, formatter);
+ } catch (DateTimeParseException e) {
+ throw new InvalidDateException();
+ }
+
+ event = new Event(description, localDateTime);
+ }
+
+ @Override
+ public CommandResult execute() {
+ assert tasks != null : "Should setData() before calling execute().";
+ tasks.addTask(event);
+ TaskUndo undoAction = new TaskUndo(event);
+ int numberOfTasks = tasks.size();
+ String userMessage = String.format(USER_MESSAGE_FORMAT, event, numberOfTasks);
+ return new CommandResult(userMessage, CommandResult.Action.UPDATE_FILE, undoAction);
+ }
+}
diff --git a/src/main/java/duke/commands/ExitCommand.java b/src/main/java/duke/commands/ExitCommand.java
new file mode 100644
index 0000000000..701ee2f795
--- /dev/null
+++ b/src/main/java/duke/commands/ExitCommand.java
@@ -0,0 +1,15 @@
+package duke.commands;
+
+/**
+ * Represents an exit command in the Duke application.
+ */
+public class ExitCommand extends Command {
+ /** Command word of the exit command. */
+ public static final String COMMAND_WORD = "bye";
+ public static final String EXIT_MESSAGE = "Bye! *sad beep*\n"
+ + "Hope to see you soon!";
+
+ public CommandResult execute() {
+ return new CommandResult(EXIT_MESSAGE, CommandResult.Action.EXIT);
+ }
+}
diff --git a/src/main/java/duke/commands/FindCommand.java b/src/main/java/duke/commands/FindCommand.java
new file mode 100644
index 0000000000..a4c1f16157
--- /dev/null
+++ b/src/main/java/duke/commands/FindCommand.java
@@ -0,0 +1,62 @@
+package duke.commands;
+
+import java.util.ArrayList;
+
+import duke.exceptions.DukeException;
+import duke.exceptions.WrongFindFormatException;
+import duke.task.Task;
+import duke.task.TaskList;
+
+/**
+ * Represents a find command in the Duke application.
+ */
+public class FindCommand extends Command {
+ /** Command word of the find command. */
+ public static final String COMMAND_WORD = "find";
+ private static final String TASK_FORMAT = "%d: %s";
+ private static final String USER_MESSAGE_FORMAT = "*beeeeep* I've found some tasks!\n%s";
+ private static final String NO_TASKS_FOUND = "I didn't find any tasks... *sad beep*";
+ private final String keyword;
+
+ /**
+ * Constructs a delete command with arguments.
+ *
+ * @param arguments Arguments string is the find keyword.
+ * @throws DukeException Exception due to invalid arguments.
+ */
+ public FindCommand(String arguments) throws DukeException {
+ if (arguments.isEmpty()) {
+ throw new WrongFindFormatException();
+ }
+ keyword = arguments;
+ }
+
+ private ArrayList getFoundTasks() {
+ ArrayList foundTasks = new ArrayList<>();
+ TaskList filteredTasks = tasks.filter(task -> task.containsKeyword(keyword));
+
+ for (int i = 0; i < filteredTasks.size(); i++) {
+ Task task = filteredTasks.getTask(i);
+ String taskString = String.format(TASK_FORMAT, i + 1, task);
+ foundTasks.add(taskString);
+ }
+
+ return foundTasks;
+ }
+
+ private String getUserMessage(ArrayList foundTasks) {
+ if (foundTasks.isEmpty()) {
+ return NO_TASKS_FOUND;
+ }
+ String tasksString = String.join("\n", foundTasks);
+ return String.format(USER_MESSAGE_FORMAT, tasksString);
+ }
+
+ @Override
+ public CommandResult execute() {
+ assert tasks != null : "Should setData() before calling execute().";
+ ArrayList foundTasks = getFoundTasks();
+ String userMessage = getUserMessage(foundTasks);
+ return new CommandResult(userMessage);
+ }
+}
diff --git a/src/main/java/duke/commands/ListCommand.java b/src/main/java/duke/commands/ListCommand.java
new file mode 100644
index 0000000000..1fa75072a1
--- /dev/null
+++ b/src/main/java/duke/commands/ListCommand.java
@@ -0,0 +1,31 @@
+package duke.commands;
+
+/**
+ * Represents a list command in the Duke application.
+ */
+public class ListCommand extends Command {
+ /** Command word of the list command. */
+ public static final String COMMAND_WORD = "list";
+ private static final String LIST_MESSAGE = "*beeeeep* I've got your tasks!!\n";
+ private static final String NO_TASKS = "You have no tasks!!! :D";
+ private static final String LINE_FORMAT = "%d: %s";
+
+ private String getMessage() {
+ if (tasks.size() == 0) {
+ return NO_TASKS;
+ }
+
+ String[] lines = new String[tasks.size()];
+ for (int i = 0; i < tasks.size(); i++) {
+ lines[i] = String.format(LINE_FORMAT, i + 1, tasks.getTask(i));
+ }
+ return LIST_MESSAGE + String.join("\n", lines);
+ }
+
+ @Override
+ public CommandResult execute() {
+ assert tasks != null : "Should setData() before calling execute().";
+ String userMessage = getMessage();
+ return new CommandResult(userMessage);
+ }
+}
diff --git a/src/main/java/duke/commands/MarkCommand.java b/src/main/java/duke/commands/MarkCommand.java
new file mode 100644
index 0000000000..7629c9ee57
--- /dev/null
+++ b/src/main/java/duke/commands/MarkCommand.java
@@ -0,0 +1,41 @@
+package duke.commands;
+
+import duke.exceptions.DukeException;
+import duke.exceptions.InvalidIndexException;
+import duke.task.Task;
+import duke.undo.MarkUndo;
+import duke.undo.UndoAction;
+
+/**
+ * Represents a mark command in the Duke application.
+ */
+public class MarkCommand extends Command {
+ /** Command word of the mark command. */
+ public static final String COMMAND_WORD = "mark";
+ private static final String USER_MESSAGE_FORMAT = "*beep beep* Roger, task %d is done!\n %s";
+ private final int index;
+
+ /**
+ * Constructs a mark command with arguments.
+ *
+ * @param arguments Arguments string is to be of the format "N".
+ */
+ public MarkCommand(String arguments) {
+ index = Integer.parseInt(arguments);
+ }
+
+ @Override
+ public CommandResult execute() throws DukeException {
+ assert tasks != null : "Should setData() before calling execute().";
+ // Check if index is out of bounds.
+ if (index <= 0 || index > tasks.size()) {
+ throw new InvalidIndexException();
+ }
+ // Subtract 1 to account for 0-index data structure.
+ Task task = tasks.getTask(index - 1);
+ UndoAction undoAction = new MarkUndo(task, true);
+ task.markAsDone();
+ String userMessage = String.format(USER_MESSAGE_FORMAT, index, task);
+ return new CommandResult(userMessage, CommandResult.Action.UPDATE_FILE, undoAction);
+ }
+}
diff --git a/src/main/java/duke/commands/TodoCommand.java b/src/main/java/duke/commands/TodoCommand.java
new file mode 100644
index 0000000000..05f7c9cbcc
--- /dev/null
+++ b/src/main/java/duke/commands/TodoCommand.java
@@ -0,0 +1,42 @@
+package duke.commands;
+
+import duke.exceptions.DukeException;
+import duke.exceptions.WrongTodoFormatException;
+import duke.task.Todo;
+import duke.undo.TaskUndo;
+
+/**
+ * Represents a todo command in the Duke application.
+ */
+public class TodoCommand extends Command {
+ /** Command word of the todo command. */
+ public static final String COMMAND_WORD = "todo";
+ private static final String USER_MESSAGE_FORMAT = "*beep* I've added this todo for you!\n"
+ + " %s\n"
+ + "Now you have %d tasks!";
+ private final Todo todo;
+
+ /**
+ * Constructs a todo command with arguments.
+ *
+ * @param arguments Arguments string is to be of the format "description".
+ * @throws DukeException Exception due to invalid arguments.
+ */
+ public TodoCommand(String arguments) throws DukeException {
+ if (arguments.length() < 1) {
+ throw new WrongTodoFormatException();
+ }
+
+ todo = new Todo(arguments);
+ }
+
+ @Override
+ public CommandResult execute() {
+ assert tasks != null : "Should setData() before calling execute().";
+ tasks.addTask(todo);
+ TaskUndo undoAction = new TaskUndo(todo);
+ int numberOfTasks = tasks.size();
+ String userMessage = String.format(USER_MESSAGE_FORMAT, todo, numberOfTasks);
+ return new CommandResult(userMessage, CommandResult.Action.UPDATE_FILE, undoAction);
+ }
+}
diff --git a/src/main/java/duke/commands/UndoCommand.java b/src/main/java/duke/commands/UndoCommand.java
new file mode 100644
index 0000000000..652e03ad8f
--- /dev/null
+++ b/src/main/java/duke/commands/UndoCommand.java
@@ -0,0 +1,15 @@
+package duke.commands;
+
+/**
+ * Represents an undo command in the Duke application.
+ */
+public class UndoCommand extends Command {
+ public static final String COMMAND_WORD = "undo";
+ private static final String USER_MESSAGE_FORMAT = "Whoops, undoing previous command!!";
+
+ @Override
+ public CommandResult execute() {
+ assert tasks != null : "Should setData() before calling execute().";
+ return new CommandResult(USER_MESSAGE_FORMAT, CommandResult.Action.UNDO);
+ }
+}
diff --git a/src/main/java/duke/commands/UnmarkCommand.java b/src/main/java/duke/commands/UnmarkCommand.java
new file mode 100644
index 0000000000..ca744d364f
--- /dev/null
+++ b/src/main/java/duke/commands/UnmarkCommand.java
@@ -0,0 +1,40 @@
+package duke.commands;
+
+import duke.exceptions.DukeException;
+import duke.exceptions.InvalidIndexException;
+import duke.task.Task;
+import duke.undo.MarkUndo;
+import duke.undo.UndoAction;
+
+/**
+ * Represents an unmark command in the Duke application.
+ */
+public class UnmarkCommand extends Command {
+ public static final String COMMAND_WORD = "unmark";
+ private static final String USER_MESSAGE_FORMAT = "*beep beep* Roger, task %d is not done!\n %s";
+ private final int index;
+
+ /**
+ * Constructs an unmark command with arguments.
+ *
+ * @param arguments Arguments string is to be of the format "N".
+ */
+ public UnmarkCommand(String arguments) {
+ index = Integer.parseInt(arguments);
+ }
+
+ @Override
+ public CommandResult execute() throws DukeException {
+ assert tasks != null : "Should setData() before calling execute().";
+ // Check if index is out of bounds.
+ if (index <= 0 || index > tasks.size()) {
+ throw new InvalidIndexException();
+ }
+ // Subtract 1 to account for 0-index data structure.
+ Task task = tasks.getTask(index - 1);
+ UndoAction undoAction = new MarkUndo(task, false);
+ task.markAsUndone();
+ String userMessage = String.format(USER_MESSAGE_FORMAT, index, task);
+ return new CommandResult(userMessage, CommandResult.Action.UPDATE_FILE, undoAction);
+ }
+}
diff --git a/src/main/java/duke/exceptions/BadDataException.java b/src/main/java/duke/exceptions/BadDataException.java
new file mode 100644
index 0000000000..cca1b89117
--- /dev/null
+++ b/src/main/java/duke/exceptions/BadDataException.java
@@ -0,0 +1,15 @@
+package duke.exceptions;
+
+/**
+ * Represents an exception due to bad save data.
+ */
+public class BadDataException extends DukeException {
+ private static final String MESSAGE = "ERROR: Bad tasks data!";
+
+ /**
+ * Constructs a bad save data exception.
+ */
+ public BadDataException() {
+ super(MESSAGE);
+ }
+}
diff --git a/src/main/java/duke/exceptions/DukeException.java b/src/main/java/duke/exceptions/DukeException.java
new file mode 100644
index 0000000000..895c3a00fc
--- /dev/null
+++ b/src/main/java/duke/exceptions/DukeException.java
@@ -0,0 +1,15 @@
+package duke.exceptions;
+
+/**
+ * Represents an exception in the Duke application.
+ */
+public abstract class DukeException extends Exception {
+ /**
+ * Constructs a Duke Exception with a message.
+ *
+ * @param message Message for the Duke Exception.
+ */
+ public DukeException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/duke/exceptions/InvalidDateException.java b/src/main/java/duke/exceptions/InvalidDateException.java
new file mode 100644
index 0000000000..32bbfe7e6f
--- /dev/null
+++ b/src/main/java/duke/exceptions/InvalidDateException.java
@@ -0,0 +1,15 @@
+package duke.exceptions;
+
+/**
+ * Represents an exception due to invalid date.
+ */
+public class InvalidDateException extends DukeException {
+ private static final String MESSAGE = "ERROR: Invalid date!";
+
+ /**
+ * Constructs an invalid date exception.
+ */
+ public InvalidDateException() {
+ super(MESSAGE);
+ }
+}
diff --git a/src/main/java/duke/exceptions/InvalidIndexException.java b/src/main/java/duke/exceptions/InvalidIndexException.java
new file mode 100644
index 0000000000..f047562ba6
--- /dev/null
+++ b/src/main/java/duke/exceptions/InvalidIndexException.java
@@ -0,0 +1,15 @@
+package duke.exceptions;
+
+/**
+ * Represents an exception due to an invalid index.
+ */
+public class InvalidIndexException extends DukeException {
+ private static final String MESSAGE = "ERROR: Invalid index!";
+
+ /**
+ * Constructs an invalid index exception.
+ */
+ public InvalidIndexException() {
+ super(MESSAGE);
+ }
+}
diff --git a/src/main/java/duke/exceptions/NoUndoActionsException.java b/src/main/java/duke/exceptions/NoUndoActionsException.java
new file mode 100644
index 0000000000..55c8103829
--- /dev/null
+++ b/src/main/java/duke/exceptions/NoUndoActionsException.java
@@ -0,0 +1,15 @@
+package duke.exceptions;
+
+/**
+ * Represents an exception due to no undo actions.
+ */
+public class NoUndoActionsException extends DukeException {
+ private static final String MESSAGE = "ERROR: No actions to undo!";
+
+ /**
+ * Constructs a no undo actions left exception.
+ */
+ public NoUndoActionsException() {
+ super(MESSAGE);
+ }
+}
diff --git a/src/main/java/duke/exceptions/UnknownCommandException.java b/src/main/java/duke/exceptions/UnknownCommandException.java
new file mode 100644
index 0000000000..9b44d0f54c
--- /dev/null
+++ b/src/main/java/duke/exceptions/UnknownCommandException.java
@@ -0,0 +1,16 @@
+package duke.exceptions;
+
+/**
+ * Represents an exception due to an unknown command.
+ */
+public class UnknownCommandException extends DukeException {
+ private static final String MESSAGE = "Whoops, I didn't quite understand that!\n"
+ + "Try pressing arrow key and fixing the command!";
+
+ /**
+ * Constructs an unknown command exception.
+ */
+ public UnknownCommandException() {
+ super(MESSAGE);
+ }
+}
diff --git a/src/main/java/duke/exceptions/WrongDeadlineFormatException.java b/src/main/java/duke/exceptions/WrongDeadlineFormatException.java
new file mode 100644
index 0000000000..01e011865c
--- /dev/null
+++ b/src/main/java/duke/exceptions/WrongDeadlineFormatException.java
@@ -0,0 +1,15 @@
+package duke.exceptions;
+
+/**
+ * Represents an exception due to a wrong deadline format.
+ */
+public class WrongDeadlineFormatException extends WrongFormatException {
+ private static final String CORRECT_DEADLINE_SYNTAX = "deadline /by YYYY-MM-DD HHmm";
+
+ /**
+ * Constructs a wrong deadline format exception.
+ */
+ public WrongDeadlineFormatException() {
+ super(CORRECT_DEADLINE_SYNTAX);
+ }
+}
diff --git a/src/main/java/duke/exceptions/WrongEventFormatException.java b/src/main/java/duke/exceptions/WrongEventFormatException.java
new file mode 100644
index 0000000000..b0499e5b53
--- /dev/null
+++ b/src/main/java/duke/exceptions/WrongEventFormatException.java
@@ -0,0 +1,15 @@
+package duke.exceptions;
+
+/**
+ * Represents an exception due to a wrong event format.
+ */
+public class WrongEventFormatException extends WrongFormatException {
+ private static final String CORRECT_EVENT_SYNTAX = "event /at YYYY-MM-DD HHmm";
+
+ /**
+ * Constructs a wrong event format exception.
+ */
+ public WrongEventFormatException() {
+ super(CORRECT_EVENT_SYNTAX);
+ }
+}
diff --git a/src/main/java/duke/exceptions/WrongFindFormatException.java b/src/main/java/duke/exceptions/WrongFindFormatException.java
new file mode 100644
index 0000000000..4c7375bdeb
--- /dev/null
+++ b/src/main/java/duke/exceptions/WrongFindFormatException.java
@@ -0,0 +1,15 @@
+package duke.exceptions;
+
+/**
+ * Represents an exception due to a wrong find format.
+ */
+public class WrongFindFormatException extends WrongFormatException {
+ private static final String CORRECT_FIND_SYNTAX = "find ";
+
+ /**
+ * Constructs a wrong find format exception.
+ */
+ public WrongFindFormatException() {
+ super(CORRECT_FIND_SYNTAX);
+ }
+}
diff --git a/src/main/java/duke/exceptions/WrongFormatException.java b/src/main/java/duke/exceptions/WrongFormatException.java
new file mode 100644
index 0000000000..a5bf7a978c
--- /dev/null
+++ b/src/main/java/duke/exceptions/WrongFormatException.java
@@ -0,0 +1,19 @@
+package duke.exceptions;
+
+/**
+ * Represents an exception due to a wrong format.
+ */
+public abstract class WrongFormatException extends DukeException {
+ private static final String MESSAGE_FORMAT = "Whoops, I didn't quite understand that!\n"
+ + "Maybe try this format:\n"
+ + " '%s'.";
+
+ /**
+ * Constructs a wrong format exception with a message.
+ *
+ * @param correctSyntax Correct syntax for the Wrong Format Exception.
+ */
+ public WrongFormatException(String correctSyntax) {
+ super(String.format(MESSAGE_FORMAT, correctSyntax));
+ }
+}
diff --git a/src/main/java/duke/exceptions/WrongTodoFormatException.java b/src/main/java/duke/exceptions/WrongTodoFormatException.java
new file mode 100644
index 0000000000..d3e7bc31d7
--- /dev/null
+++ b/src/main/java/duke/exceptions/WrongTodoFormatException.java
@@ -0,0 +1,15 @@
+package duke.exceptions;
+
+/**
+ * Represents an exception due to a wrong todo format.
+ */
+public class WrongTodoFormatException extends WrongFormatException {
+ private static final String CORRECT_TODO_SYNTAX = "todo ";
+
+ /**
+ * Constructs a wrong todo format exception.
+ */
+ public WrongTodoFormatException() {
+ super(CORRECT_TODO_SYNTAX);
+ }
+}
diff --git a/src/main/java/duke/parser/Parser.java b/src/main/java/duke/parser/Parser.java
new file mode 100644
index 0000000000..6660beb699
--- /dev/null
+++ b/src/main/java/duke/parser/Parser.java
@@ -0,0 +1,89 @@
+package duke.parser;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import duke.commands.Command;
+import duke.commands.DeadlineCommand;
+import duke.commands.DeleteCommand;
+import duke.commands.EventCommand;
+import duke.commands.ExitCommand;
+import duke.commands.FindCommand;
+import duke.commands.ListCommand;
+import duke.commands.MarkCommand;
+import duke.commands.TodoCommand;
+import duke.commands.UndoCommand;
+import duke.commands.UnmarkCommand;
+import duke.exceptions.DukeException;
+import duke.exceptions.UnknownCommandException;
+
+/**
+ * Represents a parser in the Duke application.
+ */
+public class Parser {
+ private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)\\s?(?.*)");
+
+ /**
+ * Represents the command along with its arguments, before parsing.
+ */
+ private static class CommandArguments {
+ public final String commandWord;
+ public final String arguments;
+
+ public CommandArguments(String commandWord, String arguments) {
+ this.commandWord = commandWord;
+ this.arguments = arguments;
+ }
+ }
+
+ private CommandArguments getArguments(String userInput) throws DukeException {
+ Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim());
+ if (!matcher.matches()) {
+ throw new UnknownCommandException();
+ }
+ String commandWord = matcher.group("commandWord");
+ String arguments = matcher.group("arguments");
+ return new CommandArguments(commandWord, arguments);
+ }
+
+ private Command createCommand(CommandArguments commandArguments) throws DukeException {
+ String arguments = commandArguments.arguments;
+
+ switch (commandArguments.commandWord) {
+ case ExitCommand.COMMAND_WORD:
+ return new ExitCommand();
+ case ListCommand.COMMAND_WORD:
+ return new ListCommand();
+ case MarkCommand.COMMAND_WORD:
+ return new MarkCommand(arguments);
+ case UnmarkCommand.COMMAND_WORD:
+ return new UnmarkCommand(arguments);
+ case DeleteCommand.COMMAND_WORD:
+ return new DeleteCommand(arguments);
+ case TodoCommand.COMMAND_WORD:
+ return new TodoCommand(arguments);
+ case DeadlineCommand.COMMAND_WORD:
+ return new DeadlineCommand(arguments);
+ case EventCommand.COMMAND_WORD:
+ return new EventCommand(arguments);
+ case FindCommand.COMMAND_WORD:
+ return new FindCommand(arguments);
+ case UndoCommand.COMMAND_WORD:
+ return new UndoCommand();
+ default:
+ throw new UnknownCommandException();
+ }
+ }
+
+ /**
+ * Parses a user input into a command.
+ *
+ * @param userInput Input that the user has provided.
+ * @return Command parsed from the user input.
+ * @throws DukeException Exception that occurred during the parsing of the command.
+ */
+ public Command parseCommand(String userInput) throws DukeException {
+ CommandArguments commandArguments = getArguments(userInput);
+ return createCommand(commandArguments);
+ }
+}
diff --git a/src/main/java/duke/storage/Storage.java b/src/main/java/duke/storage/Storage.java
new file mode 100644
index 0000000000..29a6365b9d
--- /dev/null
+++ b/src/main/java/duke/storage/Storage.java
@@ -0,0 +1,138 @@
+package duke.storage;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Scanner;
+
+import duke.exceptions.BadDataException;
+import duke.exceptions.DukeException;
+import duke.task.Deadline;
+import duke.task.Event;
+import duke.task.Task;
+import duke.task.TaskList;
+import duke.task.Todo;
+
+/**
+ * Represents a storage in the Duke application.
+ * Responsible for loading and saving tasks to the file system.
+ */
+public class Storage {
+ private final String directoryPath;
+ private final String filePath;
+
+ /**
+ * Constructs a storage with a directory path and a file path of the storage.
+ *
+ * @param directoryPath Directory path that the storage file is located in.
+ * @param filePath File path of the storage file.
+ */
+ public Storage(String directoryPath, String filePath) {
+ this.directoryPath = directoryPath;
+ this.filePath = filePath;
+ }
+
+ private void createDirectoryIfNotExist() {
+ Path directoryPath = Paths.get(this.directoryPath);
+ File directory = new File(directoryPath.toUri());
+ // noinspection ResultOfMethodCallIgnored because only making use of the side effect
+ directory.mkdir();
+ }
+
+ private File createFileIfNotExist() throws DukeException {
+ Path filePath = Paths.get(this.filePath);
+ File file = new File(filePath.toUri());
+
+ try {
+ // noinspection ResultOfMethodCallIgnored because only making use of the side effect
+ file.createNewFile();
+ } catch (IOException e) {
+ throw new BadDataException();
+ }
+
+ return file;
+ }
+
+ /**
+ * Parses line into task.
+ *
+ * @param line Line in save file.
+ * @return Parsed task.
+ * @throws DukeException Exception thrown while parsing line.
+ */
+ private Task parseLine(String line) throws DukeException {
+ String[] split = line.split(" \\| ");
+ String tag = split[0];
+
+ switch (tag) {
+ case "T": {
+ String done = split[1];
+ String description = split[2];
+ return Todo.create(done, description);
+ }
+ case "D": {
+ String done = split[1];
+ String description = split[2];
+ String date = split[3];
+ return Deadline.create(done, description, date);
+ }
+ case "E": {
+ String done = split[1];
+ String description = split[2];
+ String date = split[3];
+ return Event.create(done, description, date);
+ }
+ default:
+ throw new BadDataException();
+ }
+ }
+
+ /**
+ * Loads tasks from file.
+ *
+ * @return The tasks loaded from the file.
+ * @throws DukeException Exception that occurred during the loading of the tasks.
+ */
+ public TaskList load() throws DukeException {
+ createDirectoryIfNotExist();
+ File file = createFileIfNotExist();
+ TaskList tasks = new TaskList();
+
+ try (Scanner scanner = new Scanner(file)) {
+ while (scanner.hasNext()) {
+ String line = scanner.nextLine();
+ Task task = parseLine(line);
+ tasks.addTask(task);
+ }
+ } catch (FileNotFoundException e) {
+ // File creation failed, ignore and don't load tasks.
+ return tasks;
+ }
+
+ return tasks;
+ }
+
+ /**
+ * Saves tasks to file.
+ *
+ * @param tasks Tasks to be saved to the file.
+ * @throws IOException Exception that occurred during the saving of the tasks.
+ */
+ public void save(TaskList tasks) throws IOException {
+ Path filePath = Paths.get(this.filePath);
+ File file = new File(filePath.toUri());
+ FileWriter fw = new FileWriter(file);
+
+ for (int i = 0; i < tasks.size(); i++) {
+ Task task = tasks.getTask(i);
+ assert task != null : "Task should not be null because i < tasks.size().";
+ String line = task.getFileFormat();
+ fw.write(line + "\n");
+ }
+
+ fw.close();
+ }
+}
diff --git a/src/main/java/duke/task/Deadline.java b/src/main/java/duke/task/Deadline.java
new file mode 100644
index 0000000000..83df61eb67
--- /dev/null
+++ b/src/main/java/duke/task/Deadline.java
@@ -0,0 +1,62 @@
+package duke.task;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Represents a deadline task in the Duke application.
+ * Deadline is a Task that needs to be done before a specific date/time.
+ */
+public class Deadline extends Task {
+ /** Deadline of the deadline. */
+ private final LocalDateTime deadline;
+
+ /**
+ * Constructs a deadline with description and deadline.
+ * Deadline is set as "not done" when created.
+ *
+ * @param description Description of a deadline.
+ * @param deadline Deadline of a deadline.
+ */
+ public Deadline(String description, LocalDateTime deadline) {
+ super(description);
+ this.deadline = deadline;
+ }
+
+ /**
+ * Creates a Deadline with done, description and date.
+ *
+ * @param done Whether the Deadline is done.
+ * @param description Description of Deadline.
+ * @param date Date of Deadline.
+ * @return Deadline object with the given parameters.
+ */
+ public static Deadline create(String done, String description, String date) {
+ Deadline deadline = new Deadline(description, LocalDateTime.parse(date));
+ if (done.equals("1")) {
+ deadline.markAsDone();
+ }
+ return deadline;
+ }
+
+ /**
+ * Gets the Deadline in a format for file saving.
+ *
+ * @return Deadline in file saving format.
+ */
+ public String getFileFormat() {
+ return String.format("D | %s | %s", super.getFileFormat(), deadline);
+ }
+
+ /**
+ * Gets the string representation of a Deadline.
+ *
+ * @return String representation of a Deadline.
+ */
+ @Override
+ public String toString() {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM yy HH:mm");
+ String deadline = this.deadline.format(formatter);
+ return String.format("[D]%s (by: %s)", super.toString(), deadline);
+ }
+}
diff --git a/src/main/java/duke/task/Event.java b/src/main/java/duke/task/Event.java
new file mode 100644
index 0000000000..64de14e807
--- /dev/null
+++ b/src/main/java/duke/task/Event.java
@@ -0,0 +1,62 @@
+package duke.task;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Represents an event task in the Duke application.
+ * Event is a Task that starts at a specific time and ends at a specific time.
+ */
+public class Event extends Task {
+ /** Date of the event. */
+ private final LocalDateTime date;
+
+ /**
+ * Constructs an event with description and date.
+ * Event is set as "not done" when created.
+ *
+ * @param description Description of an event.
+ * @param date Date of an event.
+ */
+ public Event(String description, LocalDateTime date) {
+ super(description);
+ this.date = date;
+ }
+
+ /**
+ * Creates an Event with done, description and date.
+ *
+ * @param done Whether the Event is done.
+ * @param description Description of Event.
+ * @param date Date of Event.
+ * @return Event object with the given parameters.
+ */
+ public static Event create(String done, String description, String date) {
+ Event event = new Event(description, LocalDateTime.parse(date));
+ if (done.equals("1")) {
+ event.markAsDone();
+ }
+ return event;
+ }
+
+ /**
+ * Gets the Event in a format for file saving.
+ *
+ * @return Event in file saving format.
+ */
+ public String getFileFormat() {
+ return String.format("E | %s | %s", super.getFileFormat(), date);
+ }
+
+ /**
+ * Gets the string representation of an Event.
+ *
+ * @return String representation of an Event.
+ */
+ @Override
+ public String toString() {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM yy HH:mm");
+ String date = this.date.format(formatter);
+ return String.format("[E]%s (at: %s)", super.toString(), date);
+ }
+}
diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java
new file mode 100644
index 0000000000..05b55e48bc
--- /dev/null
+++ b/src/main/java/duke/task/Task.java
@@ -0,0 +1,66 @@
+package duke.task;
+
+/**
+ * Represents a task in the Duke application.
+ */
+public abstract class Task {
+ /** Description of the task. */
+ private final String description;
+ /** Whether the task is done. */
+ private boolean isDone;
+
+ /**
+ * Constructs a task with description.
+ * Task is set as "not done" when created.
+ *
+ * @param description Description of a task.
+ */
+ protected Task(String description) {
+ this.description = description;
+ isDone = false;
+ }
+
+ /**
+ * Marks a task as done.
+ */
+ public void markAsDone() {
+ isDone = true;
+ }
+
+ /**
+ * Marks a task as undone.
+ */
+ public void markAsUndone() {
+ isDone = false;
+ }
+
+ /**
+ * Checks if the description of the task contains the keyword.
+ *
+ * @param keyword Keyword to be checked against.
+ * @return True if task contains keyword.
+ */
+ public boolean containsKeyword(String keyword) {
+ return description.contains(keyword);
+ }
+
+ /**
+ * Gets the task in a format for file saving.
+ *
+ * @return The task in file saving format.
+ */
+ public String getFileFormat() {
+ return String.format("%s | %s", isDone ? "1" : "0", description);
+ }
+
+ /**
+ * Gets the string representation of a task.
+ *
+ * @return String representation of a task.
+ */
+ @Override
+ public String toString() {
+ String doneIcon = isDone ? "X" : " ";
+ return String.format("[%s] %s", doneIcon, description);
+ }
+}
diff --git a/src/main/java/duke/task/TaskList.java b/src/main/java/duke/task/TaskList.java
new file mode 100644
index 0000000000..e47d8ad1e7
--- /dev/null
+++ b/src/main/java/duke/task/TaskList.java
@@ -0,0 +1,76 @@
+package duke.task;
+
+import java.util.ArrayList;
+import java.util.function.Predicate;
+
+/**
+ * Represents a list of tasks in the Duke application.
+ */
+public class TaskList {
+ private final ArrayList tasks;
+
+ /**
+ * Constructs an empty task list.
+ */
+ public TaskList() {
+ tasks = new ArrayList<>();
+ }
+
+ /**
+ * Adds a task to the list.
+ *
+ * @param task Task to be added.
+ */
+ public void addTask(Task task) {
+ tasks.add(task);
+ }
+
+ /**
+ * Removes a task from the list by index.
+ *
+ * @param index Index of task to be removed.
+ */
+ public void removeTask(int index) {
+ tasks.remove(index);
+ }
+
+ /**
+ * Removes a task from the list by reference.
+ *
+ * @param task Task to be removed.
+ */
+ public void removeTask(Task task) {
+ tasks.remove(task);
+ }
+
+ /**
+ * Gets a task from the list.
+ *
+ * @param index Index of the task to be retrieved.
+ * @return The task to be retrieved.
+ */
+ public Task getTask(int index) {
+ return tasks.get(index);
+ }
+
+ /**
+ * Gets the size of the task list.
+ *
+ * @return The size of the task list.
+ */
+ public int size() {
+ return tasks.size();
+ }
+
+ /**
+ * Filters task list based on a predicate.
+ *
+ * @param predicate Predicate to filter on.
+ * @return Filtered task list.
+ */
+ public TaskList filter(Predicate super Task> predicate) {
+ TaskList filteredTaskList = new TaskList();
+ tasks.stream().filter(predicate).forEach(filteredTaskList::addTask);
+ return filteredTaskList;
+ }
+}
diff --git a/src/main/java/duke/task/Todo.java b/src/main/java/duke/task/Todo.java
new file mode 100644
index 0000000000..5fdf9a7c24
--- /dev/null
+++ b/src/main/java/duke/task/Todo.java
@@ -0,0 +1,51 @@
+package duke.task;
+
+/**
+ * Represents a todo task in the Duke application.
+ * Todo is a Task without any date/time attached to it.
+ */
+public class Todo extends Task {
+ /**
+ * Constructs a todo with description.
+ * Todo is set as "not done" when created.
+ *
+ * @param description Description of a todo.
+ */
+ public Todo(String description) {
+ super(description);
+ }
+
+ /**
+ * Creates a Todo with done and description.
+ *
+ * @param done Whether the Todo is done.
+ * @param description Description of Todo.
+ * @return Todo object with the given parameters.
+ */
+ public static Todo create(String done, String description) {
+ Todo todo = new Todo(description);
+ if (done.equals("1")) {
+ todo.markAsDone();
+ }
+ return todo;
+ }
+
+ /**
+ * Gets the Todo in a format for file saving.
+ *
+ * @return Todo in file saving format.
+ */
+ public String getFileFormat() {
+ return String.format("T | %s", super.getFileFormat());
+ }
+
+ /**
+ * Gets the string representation of a Todo.
+ *
+ * @return String representation of a Todo.
+ */
+ @Override
+ public String toString() {
+ return "[T]" + super.toString();
+ }
+}
diff --git a/src/main/java/duke/ui/DialogBox.java b/src/main/java/duke/ui/DialogBox.java
new file mode 100644
index 0000000000..5825404b48
--- /dev/null
+++ b/src/main/java/duke/ui/DialogBox.java
@@ -0,0 +1,75 @@
+package duke.ui;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+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;
+
+/**
+ * Represents the controller for the dialog box of the Duke application.
+ */
+public class DialogBox extends HBox {
+ private static final String DUKE_BACKGROUND_STYLE = "-fx-background-color: #333333;";
+ @FXML
+ private final Label text;
+ @FXML
+ private final ImageView displayImage;
+
+ /**
+ * Constructs a dialog box that takes in a label and an image.
+ *
+ * @param label Label to be shown in the dialog box.
+ * @param image Image to be shown in the dialog box.
+ */
+ private DialogBox(String label, Image image) {
+ text = new Label(label);
+ displayImage = new ImageView(image);
+
+ text.setWrapText(true);
+ displayImage.setFitWidth(50.0f);
+ displayImage.setFitHeight(50.0f);
+
+ setAlignment(Pos.CENTER_RIGHT);
+ getChildren().addAll(text, displayImage);
+ }
+
+ /**
+ * Flips the dialog box so that the image is to the left of the text.
+ */
+ private void flip() {
+ setAlignment(Pos.CENTER_LEFT);
+ ObservableList nodes = FXCollections.observableArrayList(this.getChildren());
+ FXCollections.reverse(nodes);
+ getChildren().setAll(nodes);
+ }
+
+ /**
+ * Creates a user dialog box with label and image.
+ *
+ * @param label User's text to be shown.
+ * @param image User's image to be shown.
+ * @return User dialog box.
+ */
+ public static DialogBox getUserDialog(String label, Image image) {
+ return new DialogBox(label, image);
+ }
+
+ /**
+ * Create a duke dialog box with label and image.
+ *
+ * @param label Duke's text to be shown.
+ * @param image Duke's image to be shown.
+ * @return Duke dialog box.
+ */
+ public static DialogBox getDukeDialog(String label, Image image) {
+ DialogBox dialogBox = new DialogBox(label, image);
+ dialogBox.flip();
+ dialogBox.setStyle(DUKE_BACKGROUND_STYLE);
+ return dialogBox;
+ }
+}
diff --git a/src/main/java/duke/ui/MainWindow.java b/src/main/java/duke/ui/MainWindow.java
new file mode 100644
index 0000000000..176dc35d4c
--- /dev/null
+++ b/src/main/java/duke/ui/MainWindow.java
@@ -0,0 +1,95 @@
+package duke.ui;
+
+import java.util.Objects;
+
+import duke.Duke;
+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.input.KeyCode;
+import javafx.scene.layout.VBox;
+
+/**
+ * Represents the controller for the main window of the Duke application.
+ * This provides the layout for the other controls.
+ */
+public class MainWindow extends VBox {
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
+ @FXML
+ private TextField userInput;
+ @FXML
+ private Button sendButton;
+
+ private Duke duke;
+
+ private final Image userImage = new Image(
+ Objects.requireNonNull(this.getClass().getResourceAsStream("/images/DaUser.png"))
+ );
+ private final Image dukeImage = new Image(
+ Objects.requireNonNull(this.getClass().getResourceAsStream("/images/DaDuke.png"))
+ );
+
+ /**
+ * Initializes the main window.
+ */
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ scrollPane.setFitToWidth(true);
+
+ // Detect if the user clicks up and down arrow keys for command history navigation.
+ userInput.setOnKeyPressed(event -> {
+ String commandToBeChangedTo = null;
+ KeyCode keyCode = event.getCode();
+ if (keyCode.equals(KeyCode.UP) || keyCode.equals(KeyCode.KP_UP)) {
+ commandToBeChangedTo = duke.getPreviousCommand();
+ } else if (keyCode.equals(KeyCode.DOWN) || keyCode.equals(KeyCode.KP_DOWN)) {
+ commandToBeChangedTo = duke.getNextCommand();
+ }
+ if (commandToBeChangedTo != null) {
+ userInput.setText(commandToBeChangedTo);
+ // Change caret / cursor position.
+ userInput.positionCaret(commandToBeChangedTo.length());
+ }
+ });
+ }
+
+ /**
+ * Sets the Duke instance for the main window.
+ * Also shows the welcome message to the user.
+ *
+ * @param duke Duke instance associated with the main window.
+ */
+ public void setDuke(Duke duke) {
+ this.duke = duke;
+ String welcomeMessage = duke.getWelcomeMessage();
+ dialogContainer.getChildren().addAll(
+ DialogBox.getDukeDialog(welcomeMessage, dukeImage)
+ );
+ }
+
+ /**
+ * If user input is empty, do not parse anything.
+ * Creates two dialog boxes, one for the user and one for Duke application
+ * and then appends them to the dialog container. Clears the user input after processing.
+ */
+ @FXML
+ private void handleUserInput() {
+ String input = userInput.getText();
+ if (input.isEmpty()) {
+ return;
+ }
+ String response = duke.getResponse(input);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getDukeDialog(response, dukeImage)
+ );
+ // Clear user input
+ userInput.clear();
+ }
+}
diff --git a/src/main/java/duke/undo/DeleteUndo.java b/src/main/java/duke/undo/DeleteUndo.java
new file mode 100644
index 0000000000..da6478914d
--- /dev/null
+++ b/src/main/java/duke/undo/DeleteUndo.java
@@ -0,0 +1,29 @@
+package duke.undo;
+
+import duke.task.Task;
+import duke.task.TaskList;
+
+/**
+ * Represents the action of undoing a delete task command.
+ */
+public class DeleteUndo extends UndoAction {
+ private final Task task;
+
+ /**
+ * Constructs a delete undo action with a task.
+ *
+ * @param task Task deleted.
+ */
+ public DeleteUndo(Task task) {
+ this.task = task;
+ }
+
+ /**
+ * {@inheritDoc}
+ * Adds the task back.
+ */
+ @Override
+ public void perform(TaskList tasks) {
+ tasks.addTask(task);
+ }
+}
diff --git a/src/main/java/duke/undo/MarkUndo.java b/src/main/java/duke/undo/MarkUndo.java
new file mode 100644
index 0000000000..d42b46065b
--- /dev/null
+++ b/src/main/java/duke/undo/MarkUndo.java
@@ -0,0 +1,36 @@
+package duke.undo;
+
+import duke.task.Task;
+import duke.task.TaskList;
+
+/**
+ * Represents the action of undoing a mark or unmark task command.
+ */
+public class MarkUndo extends UndoAction {
+ private final Task task;
+ private final boolean shouldMark;
+
+ /**
+ * Constructs a mark / unmark undo action with task and is mark.
+ *
+ * @param task Task marked or unmarked.
+ * @param isMark True if triggered by mark command.
+ */
+ public MarkUndo(Task task, boolean isMark) {
+ this.task = task;
+ shouldMark = !isMark;
+ }
+
+ /**
+ * {@inheritDoc}
+ * Reverses the mark / unmark action.
+ */
+ @Override
+ public void perform(TaskList tasks) {
+ if (shouldMark) {
+ task.markAsDone();
+ } else {
+ task.markAsUndone();
+ }
+ }
+}
diff --git a/src/main/java/duke/undo/TaskUndo.java b/src/main/java/duke/undo/TaskUndo.java
new file mode 100644
index 0000000000..a35182b646
--- /dev/null
+++ b/src/main/java/duke/undo/TaskUndo.java
@@ -0,0 +1,29 @@
+package duke.undo;
+
+import duke.task.Task;
+import duke.task.TaskList;
+
+/**
+ * Represents the action of undoing a add task command.
+ */
+public class TaskUndo extends UndoAction {
+ private final Task task;
+
+ /**
+ * Constructs a task undo action with task.
+ *
+ * @param task Task added.
+ */
+ public TaskUndo(Task task) {
+ this.task = task;
+ }
+
+ /**
+ * {@inheritDoc}
+ * Removes the added task.
+ */
+ @Override
+ public void perform(TaskList tasks) {
+ tasks.removeTask(task);
+ }
+}
diff --git a/src/main/java/duke/undo/UndoAction.java b/src/main/java/duke/undo/UndoAction.java
new file mode 100644
index 0000000000..f6695a9750
--- /dev/null
+++ b/src/main/java/duke/undo/UndoAction.java
@@ -0,0 +1,15 @@
+package duke.undo;
+
+import duke.task.TaskList;
+
+/**
+ * Represents the action of undoing a command.
+ */
+public abstract class UndoAction {
+ /**
+ * Undoes the effect of a command.
+ *
+ * @param tasks Tasks to be performed on.
+ */
+ public abstract void perform(TaskList tasks);
+}
diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png
new file mode 100644
index 0000000000..2037c70ce3
Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ
diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png
new file mode 100644
index 0000000000..9a0a7e6ac2
Binary files /dev/null and b/src/main/resources/images/DaUser.png differ
diff --git a/src/main/resources/styles/stylesheet.css b/src/main/resources/styles/stylesheet.css
new file mode 100644
index 0000000000..741fd5c635
--- /dev/null
+++ b/src/main/resources/styles/stylesheet.css
@@ -0,0 +1,28 @@
+#app {
+ -fx-base: #000000;
+ -fx-font-size: 16px;
+}
+
+.scroll-bar {
+ -fx-background-color: #D3D3D3;
+}
+
+.scroll-bar .thumb {
+ -fx-background-color: #808080;
+}
+
+Label {
+ -fx-background-color: #565656;
+ -fx-padding: 10px;
+ -fx-border-insets: 5px;
+ -fx-background-insets: 5px;
+ -fx-background-radius: 6;
+}
+
+TextField {
+ -fx-text-fill: white;
+}
+
+DialogBox {
+ -fx-padding: 10px;
+}
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..bc8c2f8e33
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
new file mode 100644
index 0000000000..970ef9629c
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/duke/parser/ParserTest.java b/src/test/java/duke/parser/ParserTest.java
new file mode 100644
index 0000000000..bdf087ec32
--- /dev/null
+++ b/src/test/java/duke/parser/ParserTest.java
@@ -0,0 +1,113 @@
+package duke.parser;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import duke.commands.Command;
+import duke.commands.DeadlineCommand;
+import duke.commands.DeleteCommand;
+import duke.commands.EventCommand;
+import duke.commands.ExitCommand;
+import duke.commands.FindCommand;
+import duke.commands.ListCommand;
+import duke.commands.MarkCommand;
+import duke.commands.TodoCommand;
+import duke.commands.UnmarkCommand;
+import duke.exceptions.UnknownCommandException;
+import duke.exceptions.WrongDeadlineFormatException;
+import duke.exceptions.WrongEventFormatException;
+
+
+public class ParserTest {
+ @Test
+ public void parseCommand_exitCommand_parsedCorrectly() {
+ parseCommandAndAssert("bye", ExitCommand.class);
+ }
+
+ @Test
+ public void parseCommand_listCommand_parsedCorrectly() {
+ parseCommandAndAssert("list", ListCommand.class);
+ }
+
+ @Test
+ public void parseCommand_markCommand_parsedCorrectly() {
+ parseCommandAndAssert("mark 1", MarkCommand.class);
+ }
+
+ @Test
+ public void parseCommand_unmarkCommand_parsedCorrectly() {
+ parseCommandAndAssert("unmark 1", UnmarkCommand.class);
+ }
+
+ @Test
+ public void parseCommand_deleteCommand_parsedCorrectly() {
+ parseCommandAndAssert("delete 1", DeleteCommand.class);
+ }
+
+ @Test
+ public void parseCommand_todoCommand_parsedCorrectly() {
+ parseCommandAndAssert("todo abc", TodoCommand.class);
+ }
+
+ @Test
+ public void parseCommand_deadlineCommand_parsedCorrectly() {
+ parseCommandAndAssert("deadline abc /by 2100-01-01 0000", DeadlineCommand.class);
+ }
+
+ @Test
+ public void parseCommand_deadlineCommandMissingDate_errorMessage() {
+ parseCommandAndExpectException("deadline abc", WrongDeadlineFormatException.class);
+ }
+
+ @Test
+ public void parseCommand_eventCommand_parsedCorrectly() {
+ parseCommandAndAssert("event abc /at 2100-01-01 2359", EventCommand.class);
+ }
+
+ @Test
+ public void parseCommand_eventCommandMissingDate_errorMessage() {
+ parseCommandAndExpectException("event abc", WrongEventFormatException.class);
+ }
+
+ @Test
+ public void parseCommand_findCommand_parsedCorrectly() {
+ parseCommandAndAssert("find x", FindCommand.class);
+ }
+
+ @Test
+ public void parseCommand_invalidCommand_errorMessage() {
+ parseCommandAndExpectException("invalidcommand", UnknownCommandException.class);
+ }
+
+ /**
+ * Checks that parseCommand returns the correct class for a particular input.
+ *
+ * @param userInput Input to be tested for.
+ * @param expectedClass The expected class to be returned.
+ * @param The type of command.
+ */
+ public void parseCommandAndAssert(String userInput, Class expectedClass) {
+ Parser parser = new Parser();
+ assertDoesNotThrow(() -> {
+ Command command = parser.parseCommand(userInput);
+ // Ensures that command is the correct command.
+ assertTrue(command.getClass().isAssignableFrom(expectedClass));
+ });
+ }
+
+ /**
+ * Checks that parseCommand throws an exception for a particular input.
+ *
+ * @param userInput Input to be tested for.
+ * @param expectedException The expected exception to be returned.
+ * @param The type of command.
+ */
+ public void parseCommandAndExpectException(String userInput, Class expectedException) {
+ Parser parser = new Parser();
+ // Ensures that exception thrown is the correct.
+ assertThrows(expectedException, () -> parser.parseCommand(userInput));
+ }
+}
diff --git a/src/test/java/duke/storage/StorageTest.java b/src/test/java/duke/storage/StorageTest.java
new file mode 100644
index 0000000000..02af021c40
--- /dev/null
+++ b/src/test/java/duke/storage/StorageTest.java
@@ -0,0 +1,26 @@
+package duke.storage;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+
+import duke.task.TaskList;
+
+public class StorageTest {
+ private static final String DIRECTORY_PATH = "data";
+ private static final String FILE_PATH = "data/tasks";
+
+ @Test
+ public void load_loadTasks_noException() {
+ Storage storage = new Storage(DIRECTORY_PATH, FILE_PATH);
+ assertDoesNotThrow(() -> {
+ storage.load();
+ });
+ }
+
+ @Test
+ public void save_saveTasks_noException() {
+ Storage storage = new Storage(DIRECTORY_PATH, FILE_PATH);
+ assertDoesNotThrow(() -> storage.save(new TaskList()));
+ }
+}
diff --git a/src/test/java/duke/task/DeadlineTest.java b/src/test/java/duke/task/DeadlineTest.java
new file mode 100644
index 0000000000..c516789b33
--- /dev/null
+++ b/src/test/java/duke/task/DeadlineTest.java
@@ -0,0 +1,45 @@
+package duke.task;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class DeadlineTest {
+ @Test
+ public void create_notDoneString_createdCorrectly() {
+ Deadline deadline = Deadline.create("0", "abc", "2040-01-01T00:00");
+ assertEquals("[D][ ] abc (by: 01 Jan 40 00:00)", deadline.toString());
+ }
+
+ @Test
+ public void create_doneString_createdCorrectly() {
+ Deadline deadline = Deadline.create("1", "abc", "2040-01-01T00:00");
+ assertEquals("[D][X] abc (by: 01 Jan 40 00:00)", deadline.toString());
+ }
+
+ @Test
+ public void fileFormat_notDone_formattedCorrectly() {
+ Deadline deadline = Deadline.create("0", "abc", "2040-01-01T00:00");
+ assertEquals("D | 0 | abc | 2040-01-01T00:00", deadline.getFileFormat());
+ }
+
+ @Test
+ public void fileFormat_done_formattedCorrectly() {
+ Deadline deadline = Deadline.create("1", "abc", "2040-01-01T00:00");
+ assertEquals("D | 1 | abc | 2040-01-01T00:00", deadline.getFileFormat());
+ }
+
+ @Test
+ public void markAsDone_done_changedCorrectly() {
+ Deadline deadline = Deadline.create("0", "abc", "2040-01-01T00:00");
+ deadline.markAsDone();
+ assertEquals("[D][X] abc (by: 01 Jan 40 00:00)", deadline.toString());
+ }
+
+ @Test
+ public void markAsUndone_undone_changedCorrectly() {
+ Deadline deadline = Deadline.create("1", "abc", "2040-01-01T00:00");
+ deadline.markAsUndone();
+ assertEquals("[D][ ] abc (by: 01 Jan 40 00:00)", deadline.toString());
+ }
+}
diff --git a/src/test/java/duke/task/EventTest.java b/src/test/java/duke/task/EventTest.java
new file mode 100644
index 0000000000..e3b87a5813
--- /dev/null
+++ b/src/test/java/duke/task/EventTest.java
@@ -0,0 +1,45 @@
+package duke.task;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class EventTest {
+ @Test
+ public void create_notDoneString_createdCorrectly() {
+ Event event = Event.create("0", "abc", "2040-01-01T00:00");
+ assertEquals("[E][ ] abc (at: 01 Jan 40 00:00)", event.toString());
+ }
+
+ @Test
+ public void create_doneString_createdCorrectly() {
+ Event event = Event.create("1", "abc", "2040-01-01T00:00");
+ assertEquals("[E][X] abc (at: 01 Jan 40 00:00)", event.toString());
+ }
+
+ @Test
+ public void fileFormat_notDone_formattedCorrectly() {
+ Event event = Event.create("0", "abc", "2040-01-01T00:00");
+ assertEquals("E | 0 | abc | 2040-01-01T00:00", event.getFileFormat());
+ }
+
+ @Test
+ public void fileFormat_done_formattedCorrectly() {
+ Event event = Event.create("1", "abc", "2040-01-01T00:00");
+ assertEquals("E | 1 | abc | 2040-01-01T00:00", event.getFileFormat());
+ }
+
+ @Test
+ public void markAsDone_done_changedCorrectly() {
+ Event event = Event.create("0", "abc", "2040-01-01T00:00");
+ event.markAsDone();
+ assertEquals("[E][X] abc (at: 01 Jan 40 00:00)", event.toString());
+ }
+
+ @Test
+ public void markAsUndone_undone_changedCorrectly() {
+ Event event = Event.create("1", "abc", "2040-01-01T00:00");
+ event.markAsUndone();
+ assertEquals("[E][ ] abc (at: 01 Jan 40 00:00)", event.toString());
+ }
+}
diff --git a/src/test/java/duke/task/TodoTest.java b/src/test/java/duke/task/TodoTest.java
new file mode 100644
index 0000000000..5b9184b71c
--- /dev/null
+++ b/src/test/java/duke/task/TodoTest.java
@@ -0,0 +1,45 @@
+package duke.task;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class TodoTest {
+ @Test
+ public void create_notDoneString_createdCorrectly() {
+ Todo todo = Todo.create("0", "abc");
+ assertEquals("[T][ ] abc", todo.toString());
+ }
+
+ @Test
+ public void create_doneString_createdCorrectly() {
+ Todo todo = Todo.create("1", "abc");
+ assertEquals("[T][X] abc", todo.toString());
+ }
+
+ @Test
+ public void fileFormat_notDone_formattedCorrectly() {
+ Todo todo = Todo.create("0", "abc");
+ assertEquals("T | 0 | abc", todo.getFileFormat());
+ }
+
+ @Test
+ public void fileFormat_done_formattedCorrectly() {
+ Todo todo = Todo.create("1", "abc");
+ assertEquals("T | 1 | abc", todo.getFileFormat());
+ }
+
+ @Test
+ public void markAsDone_done_changedCorrectly() {
+ Todo todo = Todo.create("0", "abc");
+ todo.markAsDone();
+ assertEquals("[T][X] abc", todo.toString());
+ }
+
+ @Test
+ public void markAsUndone_undone_changedCorrectly() {
+ Todo todo = Todo.create("1", "abc");
+ todo.markAsUndone();
+ assertEquals("[T][ ] abc", todo.toString());
+ }
+}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
deleted file mode 100644
index 657e74f6e7..0000000000
--- a/text-ui-test/EXPECTED.TXT
+++ /dev/null
@@ -1,7 +0,0 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
deleted file mode 100644
index 0873744649..0000000000
--- a/text-ui-test/runtest.bat
+++ /dev/null
@@ -1,21 +0,0 @@
-@ECHO OFF
-
-REM create bin directory if it doesn't exist
-if not exist ..\bin mkdir ..\bin
-
-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
-IF ERRORLEVEL 1 (
- echo ********** BUILD FAILURE **********
- exit /b 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
-
-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
deleted file mode 100644
index c9ec870033..0000000000
--- a/text-ui-test/runtest.sh
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/env bash
-
-# create bin directory if it doesn't exist
-if [ ! -d "../bin" ]
-then
- mkdir ../bin
-fi
-
-# delete output from previous run
-if [ -e "./ACTUAL.TXT" ]
-then
- rm ACTUAL.TXT
-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
-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
-
-# convert to UNIX format
-cp EXPECTED.TXT EXPECTED-UNIX.TXT
-dos2unix ACTUAL.TXT EXPECTED-UNIX.TXT
-
-# compare the output to the expected output
-diff ACTUAL.TXT EXPECTED-UNIX.TXT
-if [ $? -eq 0 ]
-then
- echo "Test result: PASSED"
- exit 0
-else
- echo "Test result: FAILED"
- exit 1
-fi
\ No newline at end of file