diff --git a/.gitignore b/.gitignore index 2873e189e1..481f99c1b9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +/src/main/java/*.class \ No newline at end of file diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..6e864153e8 --- /dev/null +++ b/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: duke.Duke + diff --git a/README.md b/README.md index 8715d4d915..0f2208ab64 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Duke project template +# duke.Duke project template 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. @@ -13,7 +13,7 @@ Prerequisites: JDK 11, update Intellij to the most recent version. 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: +3. After that, locate the `src/main/java/duke.Duke.java` file, right-click it, and choose `Run duke.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 ____ _ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..c43dfe58b0 --- /dev/null +++ b/build.gradle @@ -0,0 +1,64 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '7.1.2' +} + +checkstyle { + toolVersion = '10.2' +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' + + String javaFxVersion = '17.0.7' + + 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 { + mainClass.set("duke.controllers.Launcher") +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null + dependsOn("distZip", "distTar") +} + +run { + standardInput = System.in + enableAssertions = true +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..d732949de8 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,435 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..4fa31dfed4 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/data/duke.txt b/data/duke.txt new file mode 100644 index 0000000000..8bb8cae305 --- /dev/null +++ b/data/duke.txt @@ -0,0 +1,4 @@ +T | 0 | buy flour and brown sugar +D | 1 | bake cookies | 2023-10-01 12:00 +E | 1 | cookie fundraiser | 2023-10-10 11:00 | 2023-10-11 18:00 +D | 0 | ip | 2023-09-22 16:00 diff --git a/docs/README.md b/docs/README.md index 8077118ebe..a04681a5a8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,170 @@ # User Guide -## Features +## Key Features -### Feature-ABC +### Add tasks +Easily create new tasks, whether it is a todo, deadline or an event. -Description of the feature. +### Keep track of them +Too many tasks? Start on one now and get the satisfaction of marking them done. -### Feature-XYZ +### Get reminders +View the tasks that are due today, next week, or the next two weeks. -Description of the feature. +## Note on command format +- Words in `UPPER_CASE` are parameters to be supplied by the user. + + e.g. in `todo TASK_INFO`, `TASK_INFO` is a parameter that can be used as `todo bake cookies`. +- Input dates must be of the format `YYYY-MM-DD HH:mm` format. + + e.g. the `DATE` parameter can be`2023-09-09 07:00` +- Items in square brackets are optional. + + e.g. in `list [FLAG]`, the `[FLAG]` can be omitted. Hence, `list` is also a valid command that +lists all tasks. ## Usage -### `Keyword` - Describe action +### `todo` - Adding a todo + +Adds a todo task to the list. + +Format: `todo TASK_INFO` + +Example of usage: `todo bake cookies` + +Expected outcome: + +``` +(`・ω・´)ノ New task added: +[T] [] bake cookies +Now you have 1 task in the list! +``` + +### `deadline` - Adding a deadline + +Adds a deadline task to the list. + +Format: `deadline TASK_INFO /by DATE` + +Example of usage: `deadline bake 2 cookie batches /by 2023-10-10 09:00` + +Expected outcome: + +``` +(`・ω・´)ノ New task added: +[D] [] bake 2 cookie batches (by: 2023-10-10 09:00) +Now you have 2 tasks in the list! +``` + +### `event` - Adding an event + +Adds an event task to the list. + +Format: `event TASK_INFO /from START_DATE /to END_DATE` + +Example of usage: `event cookie fundraiser /from 2023-10-10 14:00 /to 2023-10-11 11:00` + +Expected outcome: + +``` +(`・ω・´)ノ New task added: +[E] [] cookie fundraiser (from: 2023-10-10 14:00 to: 2023-10-11 11:00) +Now you have 3 tasks in the list! +``` + +### `list` - Listing tasks + +Lists the tasks in the list. + +Format: `list [FLAG]` + +`[FLAG]` can be used to list tasks that are due/happen during a certain period. +- `list` : lists all tasks +- `list today` : lists tasks that are due/happen today +- `list week` : lists tasks that are due/happen this week +- `list fort` : lists tasks that are due/happen in the next 2 weeks + +Example of usage: `list` + +Expected outcome: + +``` +(⇀‸↼‶)⊃━☆゚.*・。゚Here are your tasks for the day: +1. [T] [] bake cookies +2. [D] [] bake 2 cookie batches (by: 2023-10-10 09:00) +3. [E] [] cookie fundraiser (from: 2023-10-10 14:00 to: 2023-10-11 11:00) +``` + +### `mark` - Marking a task as done + +Marks the task identified by the input task ID as completed. + +Format: `mark TASK_ID` + +Example of usage: `mark 2` + +Expected outcome: + +``` +ଘ(੭ˊᵕˋ)੭ Yay! This task is completed: +[D] [X] bake 2 cookie batches (by: 2023-10-10 09:00) +``` + +### `unmark` - Unmarking a task + +Marks the task identified by the input task ID as uncompleted. + +Format: `unmark TASK_ID` + +Example of usage: `unmark 2` + +Expected outcome: + +``` +"໒( ̿・ ᴥ ̿・ )ʋ All righty, I've marked this task as uncompleted: +[D] [] bake 2 cookie batches (by: 2023-10-10 09:00) +``` + +### `find` - Finding a task -Describe the action and its outcome. +Finds tasks that contain the input keyword. -Example of usage: +Format: `find KEYWORD` -`keyword (optional arguments)` +Example of usage: `find batches` Expected outcome: -Description of the outcome. +``` +(⇀‸↼‶)⊃━☆゚.*・。゚ Here are the matching tasks in your list: +1. [D] [] bake 2 cookie batches (by: 2023-10-10 09:00) +``` + +### `delete` - Deleting a task + +Deletes the task identified by the input task ID. + +Format: `delete TASK_ID` + +Example of usage: `delete 1` +Expected outcome: + +``` +(/ˊuˋ)/ Ok! I've removed this task: +[T] [] bake cookies +Now you have 2 tasks in the list! ``` -expected output + +### `bye` - Saying goodbye to BUTTER + +Terminates the BUTTER chatbot program. + +Format: `bye` + +Expected outcome: + ``` +໒(⊙ᴗ⊙)७ Signing off, see you later! +``` \ No newline at end of file diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..34f2980655 Binary files /dev/null and b/docs/Ui.png differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..033e24c4cd 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..66c01cfeba --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..fcb6fca147 --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/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/HEAD/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 + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + 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 + + +# 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"' + +# 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 \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# 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..6689b85bee --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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=. +@rem This is normally unused +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% equ 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% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +: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..704a325793 --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,50 @@ +package duke; + +import java.time.format.DateTimeParseException; + +import duke.components.Parser; +import duke.components.Storage; +import duke.components.TaskList; +import duke.components.Ui; +import duke.exceptions.DukeException; +import javafx.util.Pair; + +/** + * Main class to run BUTTER. + */ +public class Duke { + private Storage storage; + private TaskList tasks; + private Ui ui; + private Parser parser; + + /** + * Class constructor for Duke. + * Initialises the ui, storage and tasks used in for the BUTTER chatbot program. + * + * @param filePath the path of the file containing results from previous interactions. + */ + public Duke(String filePath) { + ui = new Ui(); + storage = new Storage(filePath); + tasks = new TaskList(storage.loadTasks(), storage, ui); + parser = new Parser(tasks, ui); + } + + /** + * Returns a pair of the form [message, isErrorMessage]. + * isErrorMessage is true if the response from the chatbot is an error message. + * + * @param input user input. + * @return the response to the user's command. + */ + public Pair getResponse(String input) { + try { + return new Pair<>(parser.parseInput(input), false); + } catch (DukeException e) { + return new Pair<>(e.getMessage(), true); + } catch (DateTimeParseException e) { + return new Pair<>(this.ui.showInvalidDateFormat(), true); + } + } +} diff --git a/src/main/java/duke/Main.java b/src/main/java/duke/Main.java new file mode 100644 index 0000000000..507a1d8658 --- /dev/null +++ b/src/main/java/duke/Main.java @@ -0,0 +1,42 @@ +package duke; + +import java.io.IOException; + +import duke.controllers.MainWindow; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +/** + * A GUI for Duke using FXML. + */ +public class Main extends Application { + + private Duke duke = new Duke("./data/duke.txt"); + + /** + * Creates the primary stage for this application. + * + * @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")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + stage.setTitle("BUTTER"); + fxmlLoader.getController().setDuke(duke); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} + diff --git a/src/main/java/duke/components/Parser.java b/src/main/java/duke/components/Parser.java new file mode 100644 index 0000000000..5a27d6d2da --- /dev/null +++ b/src/main/java/duke/components/Parser.java @@ -0,0 +1,355 @@ +package duke.components; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import duke.exceptions.DukeException; +import duke.exceptions.InvalidCommandException; +import duke.exceptions.InvalidDeadlineException; +import duke.exceptions.InvalidEventException; +import duke.exceptions.InvalidFindTaskException; +import duke.exceptions.InvalidListFlagException; +import duke.exceptions.InvalidStartEndException; +import duke.exceptions.InvalidTaskIdException; +import duke.exceptions.NoDescException; +import duke.exceptions.NoEndException; +import duke.exceptions.NoStartException; +import duke.exceptions.NoTaskIdException; +import duke.tasks.Deadline; +import duke.tasks.Event; +import duke.tasks.ToDo; + +/** + * Interprets user commands, and controls what the user can or cannot do. An object acts as a + * parser to parse commands and carry them out. + */ +public class Parser { + private TaskList tasks; + private Ui ui; + + /** + * Class constructor for Parser. + * + * @param tasks tasks in the list. + * @param ui ui to be used. + */ + public Parser(TaskList tasks, Ui ui) { + this.tasks = tasks; + this.ui = ui; + } + + /** + * Returns true if the string can be parsed as an integer. + * + * @param s the string to be tested. + * @return true if s can be parsed as an integer. + */ + //credit: https://www.freecodecamp.org/news/java-string-to-int-how-to-convert-a-string-to-an-integer/ + private static boolean isNumber(String s) { + return s != null && s.matches("[0-9.]+"); + } + + /** + * Converts a string of the format YYYY-MM-dd HH:mm to a LocalDateTime object. + * + * @param str a datetime string. + * @return the corresponding LocalDateTime object. + * @throws DateTimeParseException if str is not of the correct format. + */ + public static LocalDateTime convertToDateTime(String str) throws DateTimeParseException { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + LocalDateTime dateTime = LocalDateTime.parse(str, formatter); + return dateTime; + } + + /** + * Parses the input by user, and carries out valid commands. + * + * @param input user input. + * @return a String message describing the action taken. + * @throws DukeException when there are invalid commands. + * @throws DateTimeParseException when a date inputted by user is of the incorrect format. + */ + public String parseInput(String input) throws DukeException, DateTimeParseException { + String[] inputs = input.split(" "); + String command = inputs[0]; + + switch (command) { + case "todo": + return parseToDo(input); + case "deadline": + return parseDeadline(input); + case "event": + return parseEvent(input); + case "list": + return parseList(input); + case "delete": + return parseDelete(input); + case "mark": + return parseMark(input); + case "unmark": + return parseUnMark(input); + case "find": + return findTask(input); + case "bye": + return parseBye(); + default: + throw new InvalidCommandException(); + } + } + + /** + * Returns the list of tasks that are within a certain period. + * User can specify 'today', 'week' or 'fort' after the 'soon' command. + * 'today' will list the tasks within/due today,'week' will list + * the tasks within/due this week, while 'fort' will + * list the tasks within/ due in 2 weeks. + * + * @param input user input. + * @throws InvalidListFlagException if command is not of the correct format. + */ + public String parseList(String input) throws InvalidListFlagException { + String[] inputs = input.split(" "); + if (inputs.length == 1) { + return tasks.listTasks(); + } + if (inputs.length != 2) { + throw new InvalidListFlagException(); + } + String when = inputs[1]; + String upcoming = this.tasks.getUpcoming(when); + return upcoming; + } + + /** + * Handles the deleting of a specified task. + * + * @param input user input. + * @throws NoTaskIdException if no taskID is provided. + * @throws InvalidTaskIdException if a non-numerical id is provided. + */ + public String parseDelete(String input) throws NoTaskIdException, + InvalidTaskIdException { + String[] inputArr = input.split(" "); + if (inputArr.length == 1) { + throw new NoTaskIdException(); + } + String strIndex = inputArr[1]; + if (!isNumber(strIndex)) { + throw new InvalidTaskIdException(); + } + + int index = Integer.parseInt(strIndex) - 1; // index starts from 1 + return tasks.deleteTask(index); + } + + /** + * Handles the marking of a specified task. + * + * @param input user input. + * @throws NoTaskIdException if no taskID is provided. + * @throws InvalidTaskIdException If a non-numerical id is provided. + */ + public String parseMark(String input) throws NoTaskIdException, + InvalidTaskIdException { + String[] inputArr = input.split(" ", 2); + if (inputArr.length == 1) { + throw new NoTaskIdException(); + } + String strIndex = inputArr[1]; + if (!isNumber(strIndex)) { + throw new InvalidTaskIdException(); + } + + int index = Integer.parseInt(strIndex) - 1; + return tasks.markTask(index); + } + + /** + * Handles the unmarking of a specified task. + * + * @param input user input. + * @throws NoTaskIdException if no taskID is provided. + * @throws InvalidTaskIdException If a non-numerical id is provided. + */ + public String parseUnMark(String input) throws NoTaskIdException, + InvalidTaskIdException { + String[] inputArr = input.split(" ", 2); + if (inputArr.length == 1) { + throw new NoTaskIdException(); + } + String strIndex = inputArr[1]; + if (!isNumber(strIndex)) { + throw new InvalidTaskIdException(); + } + + int index = Integer.parseInt(strIndex) - 1; + return tasks.unMarkTask(index); + } + + /** + * Creates a uncompleted ToDo with the details provided. + * + * @param task task description. + * @return a ToDo. + */ + public String createToDo(String task) { + ToDo toDo = new ToDo(Status.NOT_DONE, task); + return tasks.addTask(toDo); + } + + /** + * Creates an uncompleted deadline with the details provided. + * + * @param task task description. + * @param date deadline. + * @return a Deadline. + * @throws DateTimeParseException when the date provided is of the incorrect format. + */ + public String createDeadline(String task, String date) throws DateTimeParseException { + LocalDateTime dateTime = convertToDateTime(date); + Deadline deadline = new Deadline(Status.NOT_DONE, task, dateTime); + return tasks.addTask(deadline); + } + + /** + * Creates an uncompleted event with the details provided. + * + * @param task task description. + * @param start start date. + * @param end end date. + * @return an Event. + * @throws DateTimeParseException when the dates provided are of the incorrect format. + * @throws InvalidStartEndException when start date greater than end date. + */ + public String createEvent(String task, String start, String end) throws DateTimeParseException, + InvalidStartEndException { + LocalDateTime startDateTime = convertToDateTime(start); + LocalDateTime endDateTime = convertToDateTime(end); + Event event = new Event(Status.NOT_DONE, task, startDateTime, endDateTime); + return tasks.addTask(event); + } + + /** + * Handles the creation of a ToDo if format is correct. + * + * @param input user input. + * @return a ToDo if format is correct. + * @throws NoDescException if no description is provided. + */ + public String parseToDo(String input) throws DukeException { + String[] inputs = input.split(" ", 2); + if (inputs.length == 1) { + throw new NoDescException(); + } + String task = inputs[1].trim(); + if (task.isBlank()) { + throw new NoDescException(); + } + return createToDo(task); + } + + /** + * Handles the creation of a Deadline if format is correct. + * + * @param input user input. + * @return a Deadline if format is correct. + * @throws NoDescException when no description is provided. + * @throws DateTimeParseException when date is of the incorrect format. + * @throws InvalidDeadlineException when deadline is of the incorrect format. + */ + public String parseDeadline(String input) throws NoDescException, + DateTimeParseException, InvalidDeadlineException { + String[] inputs = input.split(" ", 2); + if (inputs.length == 1) { + throw new NoDescException(); + } + + String afterCommand = inputs[1]; + String[] details = afterCommand.split(" /by ", 2); + if (details.length < 2) { + throw new InvalidDeadlineException(); + } + + String task = details[0].trim(); + String date = details[1]; + if (task.isBlank()) { + throw new NoDescException(); + } + + return createDeadline(task, date); + } + + /** + * Handles the creation of an event if format is correct. + * + * @param input user input. + * @return an Event if format is correct. + * @throws NoDescException when no description is provided. + * @throws InvalidEventException when event is of the incorrect format. + * @throws NoStartException when no start is provided. + * @throws NoEndException when no end is provided. + * @throws DateTimeParseException when date is of the incorrect format. + * @throws InvalidStartEndException when start greater than end date. + */ + public String parseEvent(String input) throws NoDescException, + InvalidEventException, NoStartException, NoEndException, + DateTimeParseException, InvalidStartEndException { + String[] inputs = input.split(" ", 2); + if (inputs.length == 1) { + throw new NoDescException(); + } + if (inputs[1].isBlank()) { + throw new NoDescException(); + } + + String afterCommand = inputs[1]; + String[] details = afterCommand.split("/from", 2); + if (details[0].isBlank()) { + throw new NoDescException(); + } + if (details.length == 1) { + throw new InvalidEventException(); //can either be no desc or no start + } + + String task = details[0].trim(); + String[] dateDetails = details[1].split("/to"); + String start = dateDetails[0].trim(); + if (start.isBlank()) { + throw new NoStartException(); + } + if (dateDetails.length == 1) { + throw new NoEndException(); + } + String end = dateDetails[1].trim(); + if (end.isBlank()) { + throw new NoEndException(); + } + + return createEvent(task, start, end); + } + + /** + * Handles the finding of tasks that match the specified keyword. + * + * @param input user input. + * @throws InvalidFindTaskException if 0 or more than 1 keyword is specified. + */ + public String findTask(String input) throws InvalidFindTaskException { + String[] inputs = input.split(" "); + if (inputs.length != 2) { + throw new InvalidFindTaskException(); + } + String keyword = inputs[1]; + String matches = tasks.findMatches(keyword); + return matches; + } + + /** + * Returns a goodbye to the user. + */ + public String parseBye() { + return ui.bye(); + } +} diff --git a/src/main/java/duke/components/Status.java b/src/main/java/duke/components/Status.java new file mode 100644 index 0000000000..01c8a02887 --- /dev/null +++ b/src/main/java/duke/components/Status.java @@ -0,0 +1,33 @@ +package duke.components; + +/** + * Enum is used to show the status of a Task. A task can either be done, or not done. + */ +public enum Status { + DONE { + @Override + public String toString() { + return "1"; + } + }, + NOT_DONE { + @Override + public String toString() { + return "0"; + } + }; + + /** + * Converts the status value stored in the data file to its enum equivalent. + * + * @param status the Status to convert. + * @return the enum equivalent of the Status. + */ + public static Status convertToStatus(int status) { + if (status == 0) { + return Status.NOT_DONE; + } else { + return Status.DONE; + } + } +} diff --git a/src/main/java/duke/components/Storage.java b/src/main/java/duke/components/Storage.java new file mode 100644 index 0000000000..7a1cbbf9d9 --- /dev/null +++ b/src/main/java/duke/components/Storage.java @@ -0,0 +1,151 @@ +package duke.components; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Scanner; + +import duke.exceptions.DataCorruptedException; +import duke.exceptions.DukeException; +import duke.exceptions.InvalidStartEndException; +import duke.tasks.Deadline; +import duke.tasks.Event; +import duke.tasks.Task; +import duke.tasks.ToDo; + +/** + * Handles interactions between task list and hard drive. + */ +public class Storage { + private String path; + + /** + * Constructor for Storage class. + * + * @param path path of file where data is stored. + */ + public Storage(String path) { + this.path = path; + } + + /** + * Creates a file with the path .data/duke.txt (hardcoded value). + * + * @return a File object to store tasks. + */ + public File createDataFile() { + File dataFile = new File(this.path); + try { + Path dirPath = Paths.get("./data/"); + if (!Files.exists(dirPath)) { + Files.createDirectory(dirPath); + } + if (!dataFile.exists()) { + dataFile.createNewFile(); + } + } catch (IOException e) { + System.out.println("Unable to create file!!"); + } + assert dataFile.exists() : "Data file does not exist"; + return dataFile; + } + + /** + * Converts the task text in the data file to a Task. + * + * @param taskDetails details of the task. + * @return the Task equivalent. + * @throws DataCorruptedException when text is not of the task format. + * @throws InvalidStartEndException when start greater than end time in event. + */ + public Task convertToTask(String[] taskDetails) throws DataCorruptedException, + InvalidStartEndException { + String type = taskDetails[0]; + int intStatus = Integer.parseInt(taskDetails[1]); + Status status = Status.convertToStatus(intStatus); + String desc = taskDetails[2]; + + switch (type) { + case "T": + return new ToDo(status, desc); + + case "D": + LocalDateTime date = Parser.convertToDateTime(taskDetails[3]); + return new Deadline(status, desc, date); + + case "E": + LocalDateTime start = Parser.convertToDateTime(taskDetails[3]); + LocalDateTime end = Parser.convertToDateTime(taskDetails[4]); + return new Event(status, desc, start, end); + + default: + throw new DataCorruptedException(); + } + } + + /** + * Retrieves the tasks stored in the data file, and returns them in an arraylist. + * + * @return an ArrayList of Task objects. + */ + public ArrayList loadTasks() { + File dataFile = new File(this.path); + if (!dataFile.exists()) { + dataFile = createDataFile(); + } + ArrayList list = new ArrayList<>(); + + try { + Scanner sc = new Scanner(dataFile); + while (sc.hasNextLine()) { + String task = sc.nextLine(); + String[] taskDetails = task.split(" " + "\\|" + " "); + Task toAdd = convertToTask(taskDetails); + list.add(toAdd); + } + sc.close(); + } catch (IOException | DukeException e) { + System.out.println(e.getMessage()); + } + return list; + } + + /** + * Writes the tasks in the given list to the data file. + * This overwrites the existing data in the file. + * + * @param list list of Task objects. + */ + public void updateFile(ArrayList list) { + try { + //check if file exists, else create + File dataFile = new File("./data/duke.txt"); + if (!dataFile.exists()) { + dataFile = createDataFile(); + } + + //create a FileWriter object to write to file. Note that this overwrites the existing data! + FileWriter file = new FileWriter("./data/duke.txt"); + BufferedWriter writer = new BufferedWriter(file); + + for (int i = 0; i < list.size(); i++) { + Task task = list.get(i); + String taskStr = task.convertTask(); + writer.write(taskStr); + writer.newLine(); + writer.flush(); + } + + writer.close(); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + +} diff --git a/src/main/java/duke/components/TaskList.java b/src/main/java/duke/components/TaskList.java new file mode 100644 index 0000000000..cbb0daff5b --- /dev/null +++ b/src/main/java/duke/components/TaskList.java @@ -0,0 +1,206 @@ +package duke.components; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; + +import duke.exceptions.InvalidListFlagException; +import duke.exceptions.InvalidTaskIdException; +import duke.tasks.Task; +import duke.tasks.ToDo; + + +/** + * Contains the task list, and methods to modify the tasks in the list + */ +public class TaskList { + private ArrayList list; + private Storage storage; + private Ui ui; + + /** + * Class constructor for TaskList. + * + * @param list list to be initialised. + * @param storage storage to be used. + * @param ui ui to be used. + */ + public TaskList(ArrayList list, Storage storage, Ui ui) { + this.list = list; + this.storage = storage; + this.ui = ui; + } + + /** + * Returns true if the input taskID is in the range [0, list.size() -1]. + * + * @param taskId id to be checked. + * @return true if the taskID is valid. + */ + public boolean isValidTaskId(int taskId) { + if (taskId > this.list.size() - 1 || taskId < 0) { + return false; + } + return true; + } + + /** + * Appends all the current tasks into a string, and passes it to ui object for printing. + * + * @return the tasks the user currently has. + */ + public String listTasks() { + if (this.list.size() == 0) { + return ui.showNumberOfTasks(0); + } + String result = ""; + for (int i = 0; i < this.list.size(); i++) { + int index = i + 1; + Task task = this.list.get(i); + result += index + ". " + task.toString() + "\n"; + } + return ui.showTasks(result); + } + + /** + * Appends all tasks in the given list into a single string. + * + * @param tasks list of Tasks to be printed. + * @return a String of tasks. + */ + public String listTasks(ArrayList tasks) { + String result = ""; + for (int i = 0; i < tasks.size(); i++) { + int index = i + 1; + Task task = tasks.get(i); + result += index + ". " + task.toString() + "\n"; + } + return result; + } + + /** + * Adds a task to the list, and updates storage. + * + * @param task id of task to be added. + */ + public String addTask(Task task) { + this.list.add(task); + this.storage.updateFile(this.list); + return ui.showTaskAdded(task, this.list.size()); + } + + /** + * Deletes a task from the list if the input taskID is valid. + * Updates storage. + * + * @param taskId if of task to delete. + * @throws InvalidTaskIdException if taskId is invalid. + */ + public String deleteTask(int taskId) throws InvalidTaskIdException { + if (!isValidTaskId(taskId)) { + throw new InvalidTaskIdException(); + } + + Task toRemove = this.list.get(taskId); + this.list.remove(taskId); + this.storage.updateFile(this.list); + return ui.showDeleteTask(toRemove, this.list.size()); + } + + /** + * Marks a task as completed, and updates storage. + * + * @param taskId id of task to be marked. + * @return a success or failure message. + * @throws InvalidTaskIdException if taskId is invalid. + */ + public String markTask(int taskId) throws InvalidTaskIdException { + if (!isValidTaskId(taskId)) { + throw new InvalidTaskIdException(); + } + + Task task = this.list.get(taskId); + if (task.canMark()) { + this.storage.updateFile(this.list); + return ui.showMarkTask(false, task); + } else { + return ui.showMarkTask(true, task); + } + } + + /** + * Marks a task as uncompleted, and updates storage. + * + * @param taskId id of task to be unmarked. + * @return a success or failure message. + * @throws InvalidTaskIdException if taskId is invalid. + */ + public String unMarkTask(int taskId) throws InvalidTaskIdException { + if (!isValidTaskId(taskId)) { + throw new InvalidTaskIdException(); + } + + Task task = this.list.get(taskId); + if (task.canUnMark()) { + this.storage.updateFile(this.list); + return ui.showUnMarkTask(true, task); + } else { + return ui.showUnMarkTask(false, task); + } + } + + /** + * Returns a list of tasks that contains the keyword. + * + * @param keyword specified keyword to be searched for. + * @return list of tasks that contain keyword. + */ + public String findMatches(String keyword) { + ArrayList matches = new ArrayList<>(); + for (Task task : this.list) { + String desc = task.getTask(); + assert !desc.isEmpty() : "Description cannot be empty"; + if (desc.contains(keyword)) { + matches.add(task); + } + } + return ui.showMatches(listTasks(matches)); + } + + /** + * Returns the tasks that are either due/ within the specified period. + * Today refers to the start of today, at 00:00. + * + * @param when specified period. + * @return tasks that are due/ within the specified period. + * @throws InvalidListFlagException when list flag is of the incorrect format. + */ + public String getUpcoming(String when) throws InvalidListFlagException { + LocalDate today = LocalDate.now(); + LocalDateTime start = today.atStartOfDay(); + + LocalDateTime endOfPeriod; + + switch (when) { + case "today": + endOfPeriod = start.plusDays(1); + break; + case "week": + endOfPeriod = start.plusWeeks(1); + break; + case "fort": + endOfPeriod = start.plusWeeks(2); + break; + default: + throw new InvalidListFlagException(); + } + + ArrayList upcoming = new ArrayList<>(); + for (Task task : this.list) { + if (task.isWithin(start, endOfPeriod) || task instanceof ToDo) { + upcoming.add(task); + } + } + return ui.showUpcoming(listTasks(upcoming)); + } +} diff --git a/src/main/java/duke/components/Ui.java b/src/main/java/duke/components/Ui.java new file mode 100644 index 0000000000..e750846933 --- /dev/null +++ b/src/main/java/duke/components/Ui.java @@ -0,0 +1,139 @@ +package duke.components; + +import duke.tasks.Task; + +/** + * Handles interactions with the user. + */ +public class Ui { + + /** + * Prints goodbye words to the user. + * + * @return a string of goodbyes. + */ + public String bye() { + String bye = "໒(⊙ᴗ⊙)७ Signing off, see you later!"; + return bye; + } + + /** + * Prints the number of tasks the user has in their list. + * + * @param size the size of the list. + */ + public String showNumberOfTasks(int size) { + return size == 0 + ? "(o´ω`o)ノ You have no upcoming tasks!" + : size == 1 + ? "Now you have " + size + " task in the list!" + : "Now you have " + size + " tasks in the list!"; + } + + /** + * Prints when list is the command, and there is at least one task in the list. + * + * @param tasks tasks to be printed. + * @return a list of tasks in the user's list. + */ + public String showTasks(String tasks) { + String header = "(⇀‸↼‶)⊃━☆゚.*・。゚Here are your tasks for the day:\n"; + return header + tasks; + } + + /** + * Prints when the user adds a tasks successfully. + * + * @param task task that was added. + * @param listSize the new list size. + * @return an update that task was added successfully. + */ + public String showTaskAdded(Task task, int listSize) { + String header = "(`・ω・´)ノ New task added:\n" + task + "\n"; + String numberOfTasksLeft = this.showNumberOfTasks(listSize); + return header + numberOfTasksLeft; + } + + /** + * Prints when the user deletes a task successfully. + * + * @param removed task that was removed + * @param listSize the new list size. + * @return an update that task was deleted successfully. + */ + public String showDeleteTask(Task removed, int listSize) { + String header = "(/ˊuˋ)/ Ok! I've removed this task:\n" + removed + "\n"; + String numberOfTasksLeft = this.showNumberOfTasks(listSize); + return header + numberOfTasksLeft; + } + + /** + * Prints when the user attempts to mark a task as completed. + * If task is not marked, success message is printed out. Else, + * unsuccessful message printed. + * + * @param isMarked true if the task was already marked. + * @param task task to be marked. + * @return either a success or failure message. + */ + public String showMarkTask(boolean isMarked, Task task) { + if (!isMarked) { + return "ଘ(੭ˊᵕˋ)੭ Yay! This task is completed:\n" + task.toString(); + } else { + return "┐(´~`)┌ This task is already marked as completed!"; + } + } + + /** + * Prints when the user attempts to unmark a task. + * If task is marked, success message is printed out. Else, + * an unsuccessful message is printed. + * + * @param isMarked true if the task was already marked. + * @param task task to be unmarked. + * @return either a success or failure message. + */ + public String showUnMarkTask(boolean isMarked, Task task) { + if (isMarked) { + return "໒( ̿・ ᴥ ̿・ )ʋ All righty, I've marked this task as uncompleted:\n" + + task.toString(); + } else { + return "┐(´~`)┌ This task is already marked as uncompleted!"; + } + } + + /** + * Prints a custom error message for invalid datetime formats. + */ + public String showInvalidDateFormat() { + return "(・´з`・) Uh oh...dates must be of YYYY-MM-DD HH:mm format"; + } + + /** + * Prints the given list of tasks, which contain tasks matching the + * keyword specified by user. + * + * @param tasks String format of tasks that match keyword. + */ + public String showMatches(String tasks) { + if (!tasks.isEmpty()) { + String header = "(⇀‸↼‶)⊃━☆゚.*・。゚ Here are the matching tasks in your list:\n"; + return header + tasks; + } else { + return "(・´з`・) Uh oh...there are no matching tasks!"; + } + } + + /** + * Prints upcoming tasks. + * + * @param tasks input upcoming tasks. + */ + public String showUpcoming(String tasks) { + if (tasks.isBlank()) { + return "ヾ(´〇`)ノ♪♪♪ You have no upcoming tasks!"; + } + String header = "(*・ω・)ノ Here are some upcoming tasks!\n"; + return header + tasks; + } +} diff --git a/src/main/java/duke/controllers/DialogBox.java b/src/main/java/duke/controllers/DialogBox.java new file mode 100644 index 0000000000..82189124a7 --- /dev/null +++ b/src/main/java/duke/controllers/DialogBox.java @@ -0,0 +1,72 @@ +package duke.controllers; + +import java.io.IOException; +import java.util.Collections; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.util.Pair; + +/** + * An example of a custom control using FXML. + * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ +public class DialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + private DialogBox(String text, Image img) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + dialog.setText(text); + displayPicture.setImage(img); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + public static DialogBox getUserDialog(String text, Image img) { + var db = new DialogBox(text, img); + return db; + } + + public static DialogBox getButterDialog(Pair response, Image img) { + String text = response.getKey(); + boolean isErrorMessage = response.getValue(); + + var db = new DialogBox(text, img); + if (isErrorMessage) { + //sets background color to red + db.dialog.setStyle("-fx-background-color: #E8CBD0; -fx-background-radius: 12"); + } else { + //sets background color to green + db.dialog.setStyle("-fx-background-color: #D9DDBB; -fx-background-radius: 12"); + } + db.flip(); + return db; + } +} diff --git a/src/main/java/duke/controllers/Launcher.java b/src/main/java/duke/controllers/Launcher.java new file mode 100644 index 0000000000..aa0b697775 --- /dev/null +++ b/src/main/java/duke/controllers/Launcher.java @@ -0,0 +1,13 @@ +package duke.controllers; + +import duke.Main; +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class Launcher { + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/duke/controllers/MainWindow.java b/src/main/java/duke/controllers/MainWindow.java new file mode 100644 index 0000000000..7819baad3e --- /dev/null +++ b/src/main/java/duke/controllers/MainWindow.java @@ -0,0 +1,66 @@ +package duke.controllers; + +import duke.Duke; +import javafx.animation.PauseTransition; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.util.Duration; +import javafx.util.Pair; + +/** + * Controller for MainWindow. Provides the layout for the other controls. + */ +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private Duke duke; + + private Image userImage = new Image(this.getClass().getResourceAsStream( + "/images/bread.png")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream( + "/images/butter.png")); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + + public void setDuke(Duke d) { + duke = d; + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to + * the dialog container. Clears the user input after processing. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + Pair response = duke.getResponse(input); + String text = response.getKey(); + + if (text.equals("໒(⊙ᴗ⊙)७ Signing off, see you later!")) { + PauseTransition delay = new PauseTransition(Duration.seconds(1.5)); + delay.setOnFinished(event -> Platform.exit()); + delay.play(); + } + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getButterDialog(response, dukeImage) + ); + userInput.clear(); + } +} diff --git a/src/main/java/duke/exceptions/DataCorruptedException.java b/src/main/java/duke/exceptions/DataCorruptedException.java new file mode 100644 index 0000000000..1dfb114be0 --- /dev/null +++ b/src/main/java/duke/exceptions/DataCorruptedException.java @@ -0,0 +1,10 @@ +package duke.exceptions; + +/** + * Exception is thrown when the data file contains text not of the Task format. + */ +public class DataCorruptedException extends DukeException { + public DataCorruptedException() { + super("(・´з`・) Uh oh... data is corrupted. Please delete the file and retry."); + } +} diff --git a/src/main/java/duke/exceptions/DukeException.java b/src/main/java/duke/exceptions/DukeException.java new file mode 100644 index 0000000000..17c2ed61bb --- /dev/null +++ b/src/main/java/duke/exceptions/DukeException.java @@ -0,0 +1,14 @@ +package duke.exceptions; + +/** + * This is the parent exception of all exceptions created in this duke program. + */ +public class DukeException extends Exception { + + public DukeException() { + super("(・´з`・) Uh oh...smt went wrong"); + } + public DukeException(String message) { + super(message); + } +} diff --git a/src/main/java/duke/exceptions/InvalidCommandException.java b/src/main/java/duke/exceptions/InvalidCommandException.java new file mode 100644 index 0000000000..78f6840113 --- /dev/null +++ b/src/main/java/duke/exceptions/InvalidCommandException.java @@ -0,0 +1,10 @@ +package duke.exceptions; + +/** + * This exception is thrown when the user input does not match valid command. + */ +public class InvalidCommandException extends DukeException { + public InvalidCommandException() { + super("Sorry, I don't understand what you mean ><"); + } +} diff --git a/src/main/java/duke/exceptions/InvalidDeadlineException.java b/src/main/java/duke/exceptions/InvalidDeadlineException.java new file mode 100644 index 0000000000..f84089112e --- /dev/null +++ b/src/main/java/duke/exceptions/InvalidDeadlineException.java @@ -0,0 +1,10 @@ +package duke.exceptions; + +/** + * This exception is thrown when the deadline command is of the incorrect format. + */ +public class InvalidDeadlineException extends DukeException { + public InvalidDeadlineException() { + super("(・´з`・) Uh oh... make sure your deadline has a description and date"); + } +} diff --git a/src/main/java/duke/exceptions/InvalidEventException.java b/src/main/java/duke/exceptions/InvalidEventException.java new file mode 100644 index 0000000000..df357fe101 --- /dev/null +++ b/src/main/java/duke/exceptions/InvalidEventException.java @@ -0,0 +1,11 @@ +package duke.exceptions; + +/** + * This exception is thrown when the event command is of the incorrect format. + */ +public class InvalidEventException extends DukeException { + + public InvalidEventException() { + super("(・´з`・) Uh oh... improper event format!"); + } +} diff --git a/src/main/java/duke/exceptions/InvalidFindTaskException.java b/src/main/java/duke/exceptions/InvalidFindTaskException.java new file mode 100644 index 0000000000..adaea918e7 --- /dev/null +++ b/src/main/java/duke/exceptions/InvalidFindTaskException.java @@ -0,0 +1,11 @@ +package duke.exceptions; + +/** + * This exception is thrown when no/more than one keyword is provided + * when user enters the find command. + */ +public class InvalidFindTaskException extends DukeException { + public InvalidFindTaskException() { + super("(・´з`・) Uh oh...please provide exactly one keyword"); + } +} diff --git a/src/main/java/duke/exceptions/InvalidListFlagException.java b/src/main/java/duke/exceptions/InvalidListFlagException.java new file mode 100644 index 0000000000..5d8e66e525 --- /dev/null +++ b/src/main/java/duke/exceptions/InvalidListFlagException.java @@ -0,0 +1,11 @@ +package duke.exceptions; + +/** + * Exception is thrown when the list command is of the incorrect format. + */ +public class InvalidListFlagException extends DukeException { + + public InvalidListFlagException() { + super("(・´з`・) Uh oh... please ensure format is 'list today/week/fort'"); + } +} diff --git a/src/main/java/duke/exceptions/InvalidStartEndException.java b/src/main/java/duke/exceptions/InvalidStartEndException.java new file mode 100644 index 0000000000..1df6d232d7 --- /dev/null +++ b/src/main/java/duke/exceptions/InvalidStartEndException.java @@ -0,0 +1,11 @@ +package duke.exceptions; + +/** + * This exception is thrown when start datetime is after end datetime when creating + * an Event. + */ +public class InvalidStartEndException extends DukeException { + public InvalidStartEndException() { + super("(・´з`・) Uh oh... start must be after end!"); + } +} diff --git a/src/main/java/duke/exceptions/InvalidTaskIdException.java b/src/main/java/duke/exceptions/InvalidTaskIdException.java new file mode 100644 index 0000000000..be12893d90 --- /dev/null +++ b/src/main/java/duke/exceptions/InvalidTaskIdException.java @@ -0,0 +1,12 @@ +package duke.exceptions; + +/** + * This exception is thrown when the task id provided is out of range + * when attempting actions such as delete, mark and unmark. + */ +public class InvalidTaskIdException extends DukeException { + + public InvalidTaskIdException() { + super("(・´з`・) Uh oh... invalid taskID"); + } +} diff --git a/src/main/java/duke/exceptions/NoDescException.java b/src/main/java/duke/exceptions/NoDescException.java new file mode 100644 index 0000000000..ec4d7ad5fa --- /dev/null +++ b/src/main/java/duke/exceptions/NoDescException.java @@ -0,0 +1,11 @@ +package duke.exceptions; + +/** + * This exception is thrown when no description is provided when attempting to + * create any Task object. + */ +public class NoDescException extends DukeException { + public NoDescException() { + super("(・´з`・) Uh oh... please add a description"); + } +} diff --git a/src/main/java/duke/exceptions/NoEndException.java b/src/main/java/duke/exceptions/NoEndException.java new file mode 100644 index 0000000000..07b66ddf73 --- /dev/null +++ b/src/main/java/duke/exceptions/NoEndException.java @@ -0,0 +1,11 @@ +package duke.exceptions; + +/** + * This exception is thrown when no end datetime is provided when attempting + * to create an Event. + */ +public class NoEndException extends DukeException { + public NoEndException() { + super("(・´з`・) Uh oh... please add an end date"); + } +} diff --git a/src/main/java/duke/exceptions/NoStartException.java b/src/main/java/duke/exceptions/NoStartException.java new file mode 100644 index 0000000000..392d2f1835 --- /dev/null +++ b/src/main/java/duke/exceptions/NoStartException.java @@ -0,0 +1,11 @@ +package duke.exceptions; + +/** + * This exception is thrown when no start datetime is provided when attempting + * to create an Event. + */ +public class NoStartException extends DukeException { + public NoStartException() { + super("(・´з`・) Uh oh... please add a start date"); + } +} diff --git a/src/main/java/duke/exceptions/NoTaskIdException.java b/src/main/java/duke/exceptions/NoTaskIdException.java new file mode 100644 index 0000000000..d8c2632af5 --- /dev/null +++ b/src/main/java/duke/exceptions/NoTaskIdException.java @@ -0,0 +1,11 @@ +package duke.exceptions; + +/** + * This exception is thrown when no task id is provided when attempting actions + * such as delete, mark and unmark. + */ +public class NoTaskIdException extends DukeException { + public NoTaskIdException() { + super("(・´з`・) Uh oh... please provide a taskID"); + } +} diff --git a/src/main/java/duke/stubs/StorageStub.java b/src/main/java/duke/stubs/StorageStub.java new file mode 100644 index 0000000000..e4f5bbfe6a --- /dev/null +++ b/src/main/java/duke/stubs/StorageStub.java @@ -0,0 +1,51 @@ +package duke.stubs; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; + +import duke.components.Storage; +import duke.tasks.Task; + +/** + * Stub for Storage class, created for testing purposes. + */ +public class StorageStub extends Storage { + + /** + * Class constructor for StorageStub. + * + * @param path path of file where data is stored. + */ + public StorageStub(String path) { + super(path); + } + + @Override + public void updateFile(ArrayList list) { + try { + //check if file exists, else create + File dataFile = new File("./src/test/testdata.txt"); + if (!dataFile.exists()) { + dataFile = createDataFile(); + } + + //create a FileWriter object to write to file. Note that this overwrites the existing data! + FileWriter file = new FileWriter("./src/test/testdata.txt"); + BufferedWriter writer = new BufferedWriter(file); + + for (int i = 0; i < list.size(); i++) { + Task task = list.get(i); + String taskStr = task.convertTask(); + writer.write(taskStr); + writer.newLine(); + writer.flush(); + } + writer.close(); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/duke/stubs/TaskListStub.java b/src/main/java/duke/stubs/TaskListStub.java new file mode 100644 index 0000000000..98bdba263e --- /dev/null +++ b/src/main/java/duke/stubs/TaskListStub.java @@ -0,0 +1,47 @@ +package duke.stubs; + +import java.util.ArrayList; + +import duke.components.TaskList; +import duke.tasks.Task; + +/** + * Stub for TaskList created for testing purposes. + */ +public class TaskListStub extends TaskList { + private ArrayList listStub; + private UiStub uiStub; + private StorageStub storageStub; + + /** + * Class constructor for TaskListStub. + * + * @param list list to be initialised. + * @param storage storage to be used. + * @param ui ui to be used. + */ + public TaskListStub(ArrayList list, StorageStub storage, UiStub ui) { + super(list, storage, ui); + this.listStub = list; + this.uiStub = ui; + this.storageStub = storage; + } + + @Override + public String addTask(Task task) { + this.listStub.add(task); + this.storageStub.updateFile(listStub); + return this.uiStub.showTaskAdded(task, this.listStub.size()); + } + + @Override + public String listTasks(ArrayList tasks) { + String result = ""; + for (int i = 0; i < tasks.size(); i++) { + int index = i + 1; + Task task = tasks.get(i); + result += index + ". " + task.toString() + "\n"; + } + return result; + } +} diff --git a/src/main/java/duke/stubs/UiStub.java b/src/main/java/duke/stubs/UiStub.java new file mode 100644 index 0000000000..091d96cf6b --- /dev/null +++ b/src/main/java/duke/stubs/UiStub.java @@ -0,0 +1,22 @@ +package duke.stubs; + +import duke.components.Ui; +import duke.tasks.Task; + +/** + * Stub for Ui class, created for testing purposes. + */ +public class UiStub extends Ui { + + @Override + public String showInvalidDateFormat() { + return "(・´з`・) Uh oh...dates must be of YYYY-MM-DD HH:mm format"; + } + + @Override + public String showTaskAdded(Task task, int listSize) { + String header = "(`・ω・´)ノ New task added:\n" + task + "\n"; + String numberOfTasksLeft = this.showNumberOfTasks(listSize); + return header + numberOfTasksLeft; + } +} diff --git a/src/main/java/duke/tasks/Deadline.java b/src/main/java/duke/tasks/Deadline.java new file mode 100644 index 0000000000..7310325f1e --- /dev/null +++ b/src/main/java/duke/tasks/Deadline.java @@ -0,0 +1,63 @@ +package duke.tasks; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import duke.components.Status; + +/** + * Encapsulates a Deadline. Contains the task description, completion status + * and date of deadline. + */ +public class Deadline extends Task { + private LocalDateTime date; + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + /** + * Class constructor for Deadline. + * + * @param status either DONE or NOT_DONE. + * @param task task description. + * @param date deadline of the task. + */ + public Deadline(Status status, String task, LocalDateTime date) { + super(status, task); + this.date = date; + } + + /** + * Returns true if the deadline is within the desired period. + * + * @param start start date to compare to. + * @param end end date to compare to. + * @return true if start is before the input date. + */ + public boolean isWithin(LocalDateTime start, LocalDateTime end) { + boolean isEqualStart = this.date.isEqual(start); + boolean isAfterStart = this.date.isAfter(start); + boolean isGreaterEqual = isEqualStart || isAfterStart; + boolean isBeforeEnd = this.date.isBefore(end); + return isGreaterEqual && isBeforeEnd; + } + + /** + * Converts Deadline to the correct string format to write to data file. + * + * @return string to write to data file. + */ + @Override + public String convertTask() { + return "D | " + super.getStatus() + " | " + super.getTask() + + " | " + this.date.format(formatter); + } + + /** + * Returns string representation of a Deadline object. + * + * @return string Deadline. + */ + @Override + public String toString() { + return "[D] " + super.toString() + " (by: " + this.date.format(formatter) + ")"; + } +} diff --git a/src/main/java/duke/tasks/Event.java b/src/main/java/duke/tasks/Event.java new file mode 100644 index 0000000000..aa8c3685e3 --- /dev/null +++ b/src/main/java/duke/tasks/Event.java @@ -0,0 +1,73 @@ +package duke.tasks; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import duke.components.Status; +import duke.exceptions.InvalidStartEndException; + +/** + * Encapsulates an Event. Contains the task description, completion status, + * start and end date times. + */ +public class Event extends Task { + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + private LocalDateTime start; + private LocalDateTime end; + + /** + * Class constructor for Event. + * + * @param status either DONE or NOT_DONE. + * @param task task description. + * @param start start datetime. + * @param end end datetime. + * @throws InvalidStartEndException if start after end. + */ + public Event(Status status, String task, LocalDateTime start, LocalDateTime end) throws InvalidStartEndException { + super(status, task); + if (start.isAfter(end)) { + throw new InvalidStartEndException(); + } else { + this.start = start; + this.end = end; + } + } + + /** + * Returns true if the start of the event is within the desired period. + * + * @param start start date to compare to. + * @param end end date to compare to. + * @return true if start is before the input date. + */ + public boolean isWithin(LocalDateTime start, LocalDateTime end) { + boolean isEqualStart = this.start.isEqual(start); + boolean isAfterStart = this.start.isAfter(start); + boolean isGreaterEqual = isEqualStart || isAfterStart; + boolean isBeforeEnd = this.start.isBefore(end); + return isGreaterEqual && isBeforeEnd; + } + + /** + * Converts Event to the correct string format to write to data file. + * + * @return string to write to data file. + */ + @Override + public String convertTask() { + return "E | " + super.getStatus() + " | " + super.getTask() + + " | " + this.start.format(formatter) + " | " + this.end.format(formatter); + } + + /** + * Returns string representation of an Event object. + * + * @return string Event. + */ + @Override + public String toString() { + return "[E] " + super.toString() + " (from: " + this.start.format(formatter) + + " to: " + this.end.format(formatter) + ")"; + } +} diff --git a/src/main/java/duke/tasks/Task.java b/src/main/java/duke/tasks/Task.java new file mode 100644 index 0000000000..4006fc2bb9 --- /dev/null +++ b/src/main/java/duke/tasks/Task.java @@ -0,0 +1,87 @@ +package duke.tasks; + +import java.time.LocalDateTime; + +import duke.components.Status; + +/** + * Encapsulates a Task. Contains methods that allows users to interact with a Task + * object, such as marking, unmarking or deleting tasks. + */ +public abstract class Task { + private Status status; + private String task; + + /** + * Class constructor for Task. + * + * @param status either DONE or NOT_DONE + * @param task task description. + */ + public Task(Status status, String task) { + this.status = status; + this.task = task; + } + + public abstract String convertTask(); + + public abstract boolean isWithin(LocalDateTime start, LocalDateTime end); + + /** + * Marks this task as completed. + * + * @return true if the task is marked successfully. + */ + public boolean canMark() { + if (this.status == Status.NOT_DONE) { + this.status = Status.DONE; + return true; + } + return false; + } + + /** + * Marks this task as uncompleted. + * + * @return true if task is unmarked successfully. + */ + public boolean canUnMark() { + if (this.status == Status.DONE) { + this.status = Status.NOT_DONE; + return true; + } + return false; + } + + /** + * Returns the status of the task. + * + * @return a task is either DONE or NOT_DONE. + */ + public Status getStatus() { + return this.status; + } + + /** + * Returns the task description. + * + * @return task description. + */ + public String getTask() { + return this.task; + } + + /** + * Returns the string representation of a task object. + * + * @return a string task. + */ + @Override + public String toString() { + if (this.status == Status.NOT_DONE) { + return "[ ] " + task; + } else { + return "[X] " + task; + } + } +} diff --git a/src/main/java/duke/tasks/ToDo.java b/src/main/java/duke/tasks/ToDo.java new file mode 100644 index 0000000000..1c465b90ad --- /dev/null +++ b/src/main/java/duke/tasks/ToDo.java @@ -0,0 +1,54 @@ +package duke.tasks; + +import java.time.LocalDateTime; + +import duke.components.Status; + +/** + * Encapsulates a ToDo. Contains the task description, and + * completion status. + */ +public class ToDo extends Task { + + /** + * Class constructor for ToDo. + * + * @param status either DONE or NOT_DONE. + * @param task task description. + */ + public ToDo(Status status, String task) { + super(status, task); + } + + /** + * Always returns false as ToDo does not have a date for comparison. + * + * @param start start of period to check. + * @param end end of period to check. + * @return false. + */ + @Override + public boolean isWithin(LocalDateTime start, LocalDateTime end) { + return false; + } + + /** + * Converts ToDo to the correct string format to write to the data file. + * + * @return string to write to data file. + */ + @Override + public String convertTask() { + return "T | " + super.getStatus() + " | " + super.getTask(); + } + + /** + * Returns string representation of a ToDo object. + * + * @return string ToDo. + */ + @Override + public String toString() { + return "[T] " + super.toString(); + } +} diff --git a/src/main/resources/images/bread.png b/src/main/resources/images/bread.png new file mode 100644 index 0000000000..6aa6512428 Binary files /dev/null and b/src/main/resources/images/bread.png differ diff --git a/src/main/resources/images/butter.png b/src/main/resources/images/butter.png new file mode 100644 index 0000000000..e8f8717a45 Binary files /dev/null and b/src/main/resources/images/butter.png differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..82009a39ed --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..85d900575d --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/duke/components/ParserTest.java b/src/test/java/duke/components/ParserTest.java new file mode 100644 index 0000000000..4b2e1456d9 --- /dev/null +++ b/src/test/java/duke/components/ParserTest.java @@ -0,0 +1,247 @@ +package duke.components; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.time.format.DateTimeParseException; +import java.util.ArrayList; + +import org.junit.jupiter.api.Test; + +import duke.exceptions.DukeException; +import duke.exceptions.InvalidEventException; +import duke.exceptions.InvalidStartEndException; +import duke.exceptions.NoDescException; +import duke.exceptions.NoEndException; +import duke.exceptions.NoStartException; +import duke.stubs.StorageStub; +import duke.stubs.TaskListStub; +import duke.stubs.UiStub; +import duke.tasks.Task; + +public class ParserTest { + + @Test + public void parseEvent_noDescriptionExceptionThrown_eventCommandOnly() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event"; + String expected = "(・´з`・) Uh oh... please add a description"; + + NoDescException e = assertThrows( + NoDescException.class, () -> parser.parseEvent(input)); + + assertEquals(expected, e.getMessage()); + } + + @Test + public void parseEvent_noDescriptionExceptionThrown_whiteSpaceDesc() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event "; + String expected = "(・´з`・) Uh oh... please add a description"; + + NoDescException e = assertThrows( + NoDescException.class, () -> parser.parseEvent(input)); + + assertEquals(expected, e.getMessage()); + } + + @Test + public void parseEvent_noDescriptionExceptionThrown_whiteSpaceFromToCorrect() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event /from 2023-09-09 18:00 /to 2023-09-09 19:00"; + String expected = "(・´з`・) Uh oh... please add a description"; + + NoDescException e = assertThrows( + NoDescException.class, () -> parser.parseEvent(input)); + + assertEquals(expected, e.getMessage()); + } + + @Test + public void parseEvent_invalidEventExceptionThrown_noDesc() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event/from 2023-09-09 19:00 /to 2023-09-09 19:00"; + String expected = "(・´з`・) Uh oh... improper event format!"; + + InvalidEventException e = assertThrows( + InvalidEventException.class, () -> parser.parseEvent(input)); + + assertEquals(expected, e.getMessage()); + } + + @Test + public void parseEvent_invalidEventExceptionThrown_noFrom() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event live lecture /to 2023-09-09 19:00"; + String expected = "(・´з`・) Uh oh... improper event format!"; + + InvalidEventException e = assertThrows( + InvalidEventException.class, () -> parser.parseEvent(input)); + + assertEquals(expected, e.getMessage()); + } + + @Test + public void parseEvent_noStartException_fromWithNoSpaces() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event live lecture /from/to 2023-09-09 19:00"; + String expected = "(・´з`・) Uh oh... please add a start date"; + + NoStartException e = assertThrows( + NoStartException.class, () -> parser.parseEvent(input)); + + assertEquals(expected, e.getMessage()); + } + + @Test + public void parseEvent_noStartException_fromWithWhiteSpaces() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event live lecture /from /to 2023-09-09 19:00"; + String expected = "(・´з`・) Uh oh... please add a start date"; + + NoStartException e = assertThrows( + NoStartException.class, () -> parser.parseEvent(input)); + + assertEquals(expected, e.getMessage()); + } + + @Test + public void parseEvent_noEndException_noTo() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event live lecture /from 2023-09-09 19:00"; + String expected = "(・´з`・) Uh oh... please add an end date"; + + NoEndException e = assertThrows( + NoEndException.class, () -> parser.parseEvent(input)); + + assertEquals(expected, e.getMessage()); + } + + @Test + public void parseEvent_noEndException_toWithNoSpace() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event live lecture /from 2023-09-09 19:00 /to"; + String expected = "(・´з`・) Uh oh... please add an end date"; + + NoEndException e = assertThrows( + NoEndException.class, () -> parser.parseEvent(input)); + + assertEquals(expected, e.getMessage()); + } + + @Test + public void parseEvent_noEndException_toWithWhiteSpaces() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event live lecture /from 2023-09-09 19:00 /to "; + String expected = "(・´з`・) Uh oh... please add an end date"; + + NoEndException e = assertThrows( + NoEndException.class, () -> parser.parseEvent(input)); + + assertEquals(expected, e.getMessage()); + } + + @Test + public void parseEvent_invalidStartEndException() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event live lecture /from 2023-09-09 19:00 /to 2023-09-09 14:00"; + String expected = "(・´з`・) Uh oh... start must be after end!"; + + InvalidStartEndException e = assertThrows( + InvalidStartEndException.class, () -> parser.parseEvent(input)); + + assertEquals(expected, e.getMessage()); + } + + @Test + public void parseEvent_wrongDateFormat_words() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event live lecture /from now /to tmr"; + + DateTimeParseException e = assertThrows( + DateTimeParseException.class, () -> parser.parseEvent(input)); + } + + @Test + public void parseEvent_wrongDateFormat_invalidDates() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event live lecture /from 2023-13-21 /to 2023-13-90"; + + DateTimeParseException e = assertThrows( + DateTimeParseException.class, () -> parser.parseEvent(input)); + } + + @Test + public void parseEvent_addToListSuccessfully() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(new ArrayList<>(), storage, ui); + Parser parser = new Parser(list, ui); + + String input = "event live lecture /from 2023-09-09 10:00 /to 2023-09-09 14:00"; + String expectedEvent = "[E] [ ] live lecture (from: 2023-09-09 10:00 to: 2023-09-09 14:00)" + "\n"; + String expectedMessage = "(`・ω・´)ノ New task added:\n" + expectedEvent + + "Now you have 1 task in the list!"; + try { + String result = parser.parseEvent(input); + assertEquals(expectedMessage, result); //check message to user + ArrayList tasks = storage.loadTasks(); + assertEquals("1. " + expectedEvent, list.listTasks(tasks)); //check storage + + } catch (DukeException e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/test/java/duke/components/TaskListTest.java b/src/test/java/duke/components/TaskListTest.java new file mode 100644 index 0000000000..62c170798c --- /dev/null +++ b/src/test/java/duke/components/TaskListTest.java @@ -0,0 +1,146 @@ +package duke.components; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDateTime; +import java.util.ArrayList; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import duke.exceptions.InvalidStartEndException; +import duke.stubs.StorageStub; +import duke.stubs.TaskListStub; +import duke.stubs.UiStub; +import duke.tasks.Deadline; +import duke.tasks.Event; +import duke.tasks.Task; +import duke.tasks.ToDo; + +public class TaskListTest { + private ArrayList tasks = new ArrayList<>(); + + @BeforeEach + public void loadTasks() { + try { + tasks.add(new ToDo(Status.NOT_DONE, "cookies")); + tasks.add(new ToDo(Status.NOT_DONE, "bake cookies")); + tasks.add(new ToDo(Status.NOT_DONE, "eat cookies")); + tasks.add(new Deadline(Status.NOT_DONE, "bake more cookies", LocalDateTime.now())); + tasks.add(new Deadline(Status.NOT_DONE, "watch lecture", LocalDateTime.now())); + tasks.add(new Deadline(Status.NOT_DONE, "lecture quiz", LocalDateTime.now())); + tasks.add(new Event(Status.NOT_DONE, "cookie marathon", LocalDateTime.now(), + LocalDateTime.now().plusHours(10))); + tasks.add(new Event(Status.NOT_DONE, "cookie baking class", LocalDateTime.now(), + LocalDateTime.now().plusHours(10))); + } catch (InvalidStartEndException e) { + System.out.println(e.getMessage()); + } + + } + + @Test + public void findMatches_emptyString() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(this.tasks, storage, ui); + + String result = list.findMatches(""); + ArrayList expectedList = tasks; + String expected = ui.showMatches(list.listTasks(expectedList)); + + assertEquals(expected, result); + } + + @Test + public void findMatches_keywordFound() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(this.tasks, storage, ui); + + String result = list.findMatches("cookies"); + ArrayList expectedList = new ArrayList<>(); + expectedList.add(tasks.get(0)); + expectedList.add(tasks.get(1)); + expectedList.add(tasks.get(2)); + expectedList.add(tasks.get(3)); + + String expected = ui.showMatches(list.listTasks(expectedList)); + + assertEquals(expected, result); + } + + @Test + public void findMatches_keywordNotFound() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(this.tasks, storage, ui); + + String result = list.findMatches("math"); + ArrayList expectedList = new ArrayList<>(); + String expected = ui.showMatches(list.listTasks(expectedList)); + + assertEquals(expected, result); + } + + @Test + public void findMatches_keywordWithSpaces() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(this.tasks, storage, ui); + + String result = list.findMatches("cookies "); + ArrayList expectedList = new ArrayList<>(); + String expected = ui.showMatches(list.listTasks(expectedList)); + + assertEquals(expected, result); + } + + @Test + public void findMatches_keywordContainedWithinWord() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(this.tasks, storage, ui); + + String result = list.findMatches("cookie"); + ArrayList expectedList = new ArrayList<>(); + expectedList.add(tasks.get(0)); + expectedList.add(tasks.get(1)); + expectedList.add(tasks.get(2)); + expectedList.add(tasks.get(3)); + expectedList.add(tasks.get(6)); + expectedList.add(tasks.get(7)); + + String expected = ui.showMatches(list.listTasks(expectedList)); + + assertEquals(expected, result); + } + + @Test + public void findMatches_moreThanOneKeyword() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(this.tasks, storage, ui); + + String result = list.findMatches("bake cookies"); + ArrayList expectedList = new ArrayList<>(); + expectedList.add(tasks.get(1)); + String expected = ui.showMatches(list.listTasks(expectedList)); + + assertEquals(expected, result); + } + + @Test + public void findMatches_byDate() { + UiStub ui = new UiStub(); + StorageStub storage = new StorageStub("./src/test/testdata.txt"); + TaskListStub list = new TaskListStub(this.tasks, storage, ui); + + String result = list.findMatches(LocalDateTime.now().toString()); + ArrayList expectedList = new ArrayList<>(); + + String expected = ui.showMatches(list.listTasks(expectedList)); + + assertEquals(expected, result); + } +} diff --git a/src/test/java/duke/tasks/DeadlineTest.java b/src/test/java/duke/tasks/DeadlineTest.java new file mode 100644 index 0000000000..3a2b518997 --- /dev/null +++ b/src/test/java/duke/tasks/DeadlineTest.java @@ -0,0 +1,42 @@ +package duke.tasks; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +import duke.components.Status; + +public class DeadlineTest { + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + @Test + public void convertTask_uncompleted() { + Deadline deadline = new Deadline(Status.NOT_DONE, "bake cookies", LocalDateTime.now()); + String expected = "D | 0 | bake cookies | " + LocalDateTime.now().format(formatter); + assertEquals(expected, deadline.convertTask()); + } + + @Test + public void convertTask_completed() { + Deadline deadline = new Deadline(Status.DONE, "bake cookies", LocalDateTime.now()); + String expected = "D | 1 | bake cookies | " + LocalDateTime.now().format(formatter); + assertEquals(expected, deadline.convertTask()); + } + + @Test + public void toString_uncompleted() { + Deadline deadline = new Deadline(Status.NOT_DONE, "bake cookies", LocalDateTime.now()); + String expected = "[D] [ ] bake cookies (by: " + LocalDateTime.now().format(formatter) + ")"; + assertEquals(expected, deadline.toString()); + } + + @Test + public void toString_completed() { + Deadline deadline = new Deadline(Status.DONE, "bake cookies", LocalDateTime.now()); + String expected = "[D] [X] bake cookies (by: " + LocalDateTime.now().format(formatter) + ")"; + assertEquals(expected, deadline.toString()); + } +} diff --git a/src/test/java/duke/tasks/EventTest.java b/src/test/java/duke/tasks/EventTest.java new file mode 100644 index 0000000000..dfcde12567 --- /dev/null +++ b/src/test/java/duke/tasks/EventTest.java @@ -0,0 +1,74 @@ +package duke.tasks; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.Test; + +import duke.components.Status; +import duke.exceptions.InvalidStartEndException; + +public class EventTest { + private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + + @Test + public void event_exceptionThrown_hours() { + LocalDateTime end = LocalDateTime.now(); + LocalDateTime start = end.plusHours(9); + InvalidStartEndException ex = assertThrows( + InvalidStartEndException.class, () -> new Event(Status.NOT_DONE, "bake cookies", start, end)); + assertEquals("(・´з`・) Uh oh... start must be after end!", ex.getMessage()); + } + + @Test + public void event_exceptionThrown_minutes() { + LocalDateTime end = LocalDateTime.now(); + LocalDateTime start = end.plusMinutes(1); + InvalidStartEndException ex = assertThrows( + InvalidStartEndException.class, () -> new Event(Status.NOT_DONE, "bake cookies", start, end)); + assertEquals("(・´з`・) Uh oh... start must be after end!", ex.getMessage()); + } + + @Test + public void convertTask_uncompleted() throws InvalidStartEndException { + LocalDateTime start = LocalDateTime.now(); + LocalDateTime end = start.plusHours(9); + Event event = new Event(Status.NOT_DONE, "bake cookies", start, end); + String expected = "E | 0 | bake cookies | " + start.format(formatter) + + " | " + end.format(formatter); + assertEquals(expected, event.convertTask()); + } + + @Test + public void convertTask_completed() throws InvalidStartEndException { + LocalDateTime start = LocalDateTime.now(); + LocalDateTime end = start.plusHours(9); + Event event = new Event(Status.DONE, "bake cookies", start, end); + String expected = "E | 1 | bake cookies | " + start.format(formatter) + + " | " + end.format(formatter); + assertEquals(expected, event.convertTask()); + } + + @Test + public void toString_uncompleted() throws InvalidStartEndException { + LocalDateTime start = LocalDateTime.now(); + LocalDateTime end = start.plusHours(9); + Event event = new Event(Status.NOT_DONE, "bake cookies", start, end); + String expected = "[E] [ ] bake cookies (from: " + start.format(formatter) + + " to: " + end.format(formatter) + ")"; + assertEquals(expected, event.toString()); + } + + @Test + public void toString_completed() throws InvalidStartEndException { + LocalDateTime start = LocalDateTime.now(); + LocalDateTime end = start.plusHours(9); + Event event = new Event(Status.DONE, "bake cookies", start, end); + String expected = "[E] [X] bake cookies (from: " + start.format(formatter) + + " to: " + end.format(formatter) + ")"; + assertEquals(expected, event.toString()); + } +} diff --git a/src/test/java/duke/tasks/TaskTest.java b/src/test/java/duke/tasks/TaskTest.java new file mode 100644 index 0000000000..7aacdec0a1 --- /dev/null +++ b/src/test/java/duke/tasks/TaskTest.java @@ -0,0 +1,87 @@ +package duke.tasks; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.Test; + +import duke.components.Status; + +public class TaskTest { + + //this is the concrete implementation of Task, used for testing + public class ConcreteTask extends Task { + public ConcreteTask(Status status, String task) { + super(status, task); + } + + @Override + public boolean isWithin(LocalDateTime start, LocalDateTime end) { + return false; + } + + @Override + public String convertTask() { + return "task converted"; + } + } + + @Test + public void canMark_successful() { + ConcreteTask task = new ConcreteTask(Status.NOT_DONE, "Set up unit tests"); + task.canMark(); + assertEquals(Status.DONE, task.getStatus()); + } + + @Test + public void canMark_unsuccessful() { + ConcreteTask task = new ConcreteTask(Status.DONE, "Set up unit tests"); + task.canMark(); + assertEquals(Status.DONE, task.getStatus()); + } + + @Test + public void canUnMark_successful() { + ConcreteTask task = new ConcreteTask(Status.DONE, "Set up unit tests"); + task.canUnMark(); + assertEquals(Status.NOT_DONE, task.getStatus()); + } + + @Test + public void canUnMark_unsuccessful() { + ConcreteTask task = new ConcreteTask(Status.NOT_DONE, "Set up unit tests"); + task.canUnMark(); + assertEquals(Status.NOT_DONE, task.getStatus()); + } + + @Test + public void getStatus_uncompleted() { + ConcreteTask task = new ConcreteTask(Status.NOT_DONE, "Set up unit tests"); + assertEquals(Status.NOT_DONE, task.getStatus()); + } + + @Test + public void getStatus_completed() { + ConcreteTask task = new ConcreteTask(Status.DONE, "Set up unit tests"); + assertEquals(Status.DONE, task.getStatus()); + } + + @Test + public void getTask_completed() { + ConcreteTask task = new ConcreteTask(Status.DONE, "Set up unit tests"); + assertEquals("Set up unit tests", task.getTask()); + } + + @Test + public void toString_uncompleted() { + ConcreteTask task = new ConcreteTask(Status.NOT_DONE, "Set up unit tests"); + assertEquals("[ ] " + "Set up unit tests", task.toString()); + } + + @Test + public void toString_completed() { + ConcreteTask task = new ConcreteTask(Status.DONE, "Set up unit tests"); + assertEquals("[X] " + "Set up unit tests", task.toString()); + } +} diff --git a/src/test/java/duke/tasks/ToDoTest.java b/src/test/java/duke/tasks/ToDoTest.java new file mode 100644 index 0000000000..e45a2d679f --- /dev/null +++ b/src/test/java/duke/tasks/ToDoTest.java @@ -0,0 +1,38 @@ +package duke.tasks; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import duke.components.Status; + +public class ToDoTest { + @Test + public void convertTask_uncompleted() { + ToDo todo = new ToDo(Status.NOT_DONE, "bake cookies!"); + String expected = "T | 0 | bake cookies!"; + assertEquals(expected, todo.convertTask()); + } + + @Test + public void convertTask_completed() { + ToDo todo = new ToDo(Status.DONE, "bake cookies!"); + String expected = "T | 1 | bake cookies!"; + assertEquals(expected, todo.convertTask()); + } + + @Test + public void toString_uncompleted() { + ToDo todo = new ToDo(Status.NOT_DONE, "bake cookies!"); + String expected = "[T] [ ] bake cookies!"; + assertEquals(expected, todo.toString()); + } + + @Test + public void toString_completed() { + ToDo todo = new ToDo(Status.DONE, "bake cookies!"); + String expected = "[T] [X] bake cookies!"; + assertEquals(expected, todo.toString()); + } + +} diff --git a/src/test/testdata.txt b/src/test/testdata.txt new file mode 100644 index 0000000000..9b3c949729 --- /dev/null +++ b/src/test/testdata.txt @@ -0,0 +1 @@ +E | 0 | live lecture | 2023-09-09 10:00 | 2023-09-09 14:00 diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..c16c7104cc 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,119 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - +-------------------------------------------------------------------- +(。・o・。)ノ Hey there! I'm BUTTER. +How can I help you today? +-------------------------------------------------------------------- +(o´ω`o)ノ You have no upcoming tasks! +-------------------------------------------------------------------- +Sorry, I don't understand what you mean >< +-------------------------------------------------------------------- +Sorry, I don't understand what you mean >< +-------------------------------------------------------------------- +(・´з`・) Uh oh... please add a description +-------------------------------------------------------------------- +(・´з`・) Uh oh... please add a description +-------------------------------------------------------------------- +(・´з`・) Uh oh... please add a description +-------------------------------------------------------------------- +(`・ω・´)ノ New task added: +[T] [ ] watch lecture +Now you have 1 task in the list! +-------------------------------------------------------------------- +(`・ω・´)ノ New task added: +[T] [ ] eat lunch +Now you have 2 tasks in the list! +-------------------------------------------------------------------- +(・´з`・) Uh oh...dates must be of YYYY-MM-DD HH:mm format +-------------------------------------------------------------------- +(`・ω・´)ノ New task added: +[D] [ ] bake cookies (by: 2023-09-03 18:00) +Now you have 3 tasks in the list! +-------------------------------------------------------------------- +(・´з`・) Uh oh... make sure your deadline has a description and date +-------------------------------------------------------------------- +(・´з`・) Uh oh...dates must be of YYYY-MM-DD HH:mm format +-------------------------------------------------------------------- +(・´з`・) Uh oh...dates must be of YYYY-MM-DD HH:mm format +-------------------------------------------------------------------- +(・´з`・) Uh oh...dates must be of YYYY-MM-DD HH:mm format +-------------------------------------------------------------------- +(⇀‸↼‶)⊃━☆゚.*・。゚ Here are your tasks for the day: +1. [T] [ ] watch lecture +2. [T] [ ] eat lunch +3. [D] [ ] bake cookies (by: 2023-09-03 18:00) +-------------------------------------------------------------------- +(・´з`・) Uh oh...dates must be of YYYY-MM-DD HH:mm format +-------------------------------------------------------------------- +(・´з`・) Uh oh...dates must be of YYYY-MM-DD HH:mm format +-------------------------------------------------------------------- +(`・ω・´)ノ New task added: +[E] [ ] math meeting! (from: 2023-08-25 07:00 to: 2023-08-25 23:00) +Now you have 4 tasks in the list! +-------------------------------------------------------------------- +(・´з`・) Uh oh...improper event format! +-------------------------------------------------------------------- +(・´з`・) Uh oh... please add an end date +-------------------------------------------------------------------- +(・´з`・) Uh oh...improper event format! +-------------------------------------------------------------------- +(・´з`・) Uh oh...improper event format! +-------------------------------------------------------------------- +(⇀‸↼‶)⊃━☆゚.*・。゚ Here are your tasks for the day: +1. [T] [ ] watch lecture +2. [T] [ ] eat lunch +3. [D] [ ] bake cookies (by: 2023-09-03 18:00) +4. [E] [ ] math meeting! (from: 2023-08-25 07:00 to: 2023-08-25 23:00) +-------------------------------------------------------------------- +(・´з`・) Uh oh...start must be after end! +-------------------------------------------------------------------- +(`・ω・´)ノ New task added: +[E] [ ] dinner w choir gals! (from: 2023-09-25 17:00 to: 2023-09-25 23:00) +Now you have 5 tasks in the list! +-------------------------------------------------------------------- +(⇀‸↼‶)⊃━☆゚.*・。゚ Here are your tasks for the day: +1. [T] [ ] watch lecture +2. [T] [ ] eat lunch +3. [D] [ ] bake cookies (by: 2023-09-03 18:00) +4. [E] [ ] math meeting! (from: 2023-08-25 07:00 to: 2023-08-25 23:00) +5. [E] [ ] dinner w choir gals! (from: 2023-09-25 17:00 to: 2023-09-25 23:00) +-------------------------------------------------------------------- +ଘ(੭ˊᵕˋ)੭ Yay! This task is completed: +[T] [X] watch lecture +-------------------------------------------------------------------- +ଘ(੭ˊᵕˋ)੭ Yay! This task is completed: +[D] [X] bake cookies (by: 2023-09-03 18:00) +-------------------------------------------------------------------- +(⇀‸↼‶)⊃━☆゚.*・。゚ Here are your tasks for the day: +1. [T] [X] watch lecture +2. [T] [ ] eat lunch +3. [D] [X] bake cookies (by: 2023-09-03 18:00) +4. [E] [ ] math meeting! (from: 2023-08-25 07:00 to: 2023-08-25 23:00) +5. [E] [ ] dinner w choir gals! (from: 2023-09-25 17:00 to: 2023-09-25 23:00) +-------------------------------------------------------------------- +┐(´~`)┌ This task is already marked as completed! +-------------------------------------------------------------------- +໒( ̿・ ᴥ ̿・ )ʋ All righty, I've marked this task as uncompleted: +[T] [ ] watch lecture +-------------------------------------------------------------------- +┐(´~`)┌ This task is already marked as uncompleted! +-------------------------------------------------------------------- +(⇀‸↼‶)⊃━☆゚.*・。゚ Here are your tasks for the day: +1. [T] [ ] watch lecture +2. [T] [ ] eat lunch +3. [D] [X] bake cookies (by: 2023-09-03 18:00) +4. [E] [ ] math meeting! (from: 2023-08-25 07:00 to: 2023-08-25 23:00) +5. [E] [ ] dinner w choir gals! (from: 2023-09-25 17:00 to: 2023-09-25 23:00) +-------------------------------------------------------------------- +ଘ(੭ˊᵕˋ)੭ Ok! I've removed this task: +[T] [ ] watch lecture +Now you have 4 tasks in the list! +-------------------------------------------------------------------- +(・´з`・) Uh oh... invalid taskID +-------------------------------------------------------------------- +(⇀‸↼‶)⊃━☆゚.*・。゚ Here are your tasks for the day: +1. [T] [ ] eat lunch +2. [D] [X] bake cookies (by: 2023-09-03 18:00) +3. [E] [ ] math meeting! (from: 2023-08-25 07:00 to: 2023-08-25 23:00) +4. [E] [ ] dinner w choir gals! (from: 2023-09-25 17:00 to: 2023-09-25 23:00) +-------------------------------------------------------------------- +彡໒(⊙ᴗ⊙)७彡 Signing off, see you later! +-------------------------------------------------------------------- diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..740973094b 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,45 @@ +list +hahahhah +wow so cool +todo +deadline +event +todo watch lecture +todo eat lunch +deadline bake cookies /by tmr +deadline bake cookies /by 2023-09-03 18:00 +deadline /by 2023-09-03 18:00 +deadline bake cookies /by 2023-20-03 18:00 +deadline bake cookies /by 2023-09-03 29:00 +deadline go fishing /by 2024-02-05 +list +event math meeting! /from now /to tmr +event math meeting! /from 2023-08-25 7:00 /to 2023-08-25 23:00 +event math meeting! /from 2023-08-25 07:00 /to 2023-08-25 23:00 +event /from 2023-08-25 7:00 /to 2023-08-25 23:00 +event math meeting! /from 2023-08-25 07:00 +event math meeting! /to 2023-08-25 23:00 +event math meeting! 2023-08-25 7:00 2023-08-25 23:00 +list +event dinner w choir gals! /from 2023-09-25 23:00 /to 2023-09-25 17:00 +event dinner w choir gals! /from 2023-09-25 17:00 /to 2023-09-25 23:00 +list +mark 1 +mark 3 +list +mark 3 +unmark 1 +unmark 4 +list +delete 1 +delete 7 +list +bye + + + + + + + + diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 0873744649..62752b8814 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -15,7 +15,7 @@ IF ERRORLEVEL 1 ( REM no error here, errorlevel == 0 REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ..\bin Duke < input.txt > ACTUAL.TXT +java -classpath ..\bin duke.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 old mode 100644 new mode 100755 index c9ec870033..542d1df09d --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,7 +13,7 @@ then fi # compile the code into the bin folder, terminates if error occurred -if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java +if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/duke/*.java then echo "********** BUILD FAILURE **********" exit 1 @@ -35,4 +35,4 @@ then else echo "Test result: FAILED" exit 1 -fi \ No newline at end of file +fi