diff --git a/README.md b/README.md index 8715d4d915..760ed90eea 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,13 @@ -# Duke project template +# Welcome to Duke +## A Personal Assistant Chatbot that helps a person to keep track of various tasks -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. +![](https://zhongli5712.github.io/ip/Ui.png) -## Setting up in Intellij +> User interface example -Prerequisites: JDK 11, update Intellij to the most recent version. +You can view the user guide [here](https://zhongli5712.github.io/ip/). -1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first) -1. Open the project into Intellij as follows: - 1. Click `Open`. - 1. Select the project directory, and click `OK`. - 1. If there are any further prompts, accept the defaults. -1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
- In the same dialog, set the **Project language level** field to the `SDK default` option. -3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` +You can download jar file in the latest release and run the file by command ```java -jar duke.jar```. + +## Acknowledgement +This project is referencing resources on the Duke project created by the [SE-EDU Initiative](https://se-education.org/) diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..074ffc161b --- /dev/null +++ b/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'org.openjfx.javafxplugin' version '0.0.13' +} + +repositories { + mavenCentral() +} +/* +javafx { + version = "19" + modules = [ 'javafx.controls' ] +} +*/ +checkstyle { + toolVersion = '10.2' + ignoreFailures = true +} + + + +dependencies { + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' + String javaFxVersion = '11' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = true + } +} + +application { + mainClassName = "duke.Duke" +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + +run{ + standardInput = System.in + enableAssertions = true +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..d618671b83 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..39efb6e4ac --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/docs/README.md b/docs/README.md index 8077118ebe..7b4ad14087 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,28 +2,166 @@ ## Features -### Feature-ABC +### Feature Add -Description of the feature. +Add one of 4 types of task: +1. Todo task +2. Deadline task +3. Event task +4. Duration task -### Feature-XYZ +### Feature Mark/Unmark -Description of the feature. +Mark a task done or unmark it. + +### Feature Delete + +Delete a task . + +### Feature Find + +Find a task with keywords. ## Usage -### `Keyword` - Describe action +### `todo description` - Add general task -Describe the action and its outcome. +Add a task with the given description. Example of usage: -`keyword (optional arguments)` +`todo read books` + +Expected outcome: + +``` +Got it. I've added this task: +[T][ ] read book +Now I have 1 tasks in the list. +``` + +### `deadline description /by time` - Add task with deadline + +Add a task with the given description and deadline. + +Example of usage: + +`deadline return book /by Sunday` + +Expected outcome: + +``` +Got it. I've added this task: +[D][ ] return book (by: Sunday) +Now I have 2 tasks in the list. +``` + +### `event description /from start time /to end time` - Add task with begin and end time + +Add a task with the given description and begin and end time. + +Example of usage: + +`event programming study /from 5pm /to 9pm` Expected outcome: -Description of the outcome. +``` +Got it. I've added this task: +[E][ ] programming study (from: 5pm to: 9pm) +Now I have 3 tasks in the list. +``` + +### `duration description /needs start time` - Add task with duration + +Add a task with the given description and duration. + +Example of usage: + +`duration practice singing /needs 2 days` + +Expected outcome: + +``` +Got it. I've added this task: +[Du][ ] practice singing (needs: 2 days) +Now I have 4 tasks in the list. +``` + +### `list` - show list of tasks + +Display list of tasks so far. + +Example of usage: + +`list` + +Expected outcome: + +``` +1. [T][ ] read book +2. [D][ ] return book (by: Sunday) +3. [E][ ] programming study (from: 5pm to: 9pm) +4. [Du][ ] practice singing (needs: 2 days) +``` + +### `mark index` - mark task done + +Mark task at the given index as done. + +Example of usage: + +`mark 1` + +Expected outcome: + +``` +Nice! I've marked this task as done: +[T][X] read book +``` + +### `unmark index` - unmark task done + +unMark task at the given index as done. + +Example of usage: + +`unmark 1` + +Expected outcome: + +``` +OK, I've marked this task as not done yet: +[T][ ] read book +``` + +### `delete index` - delete task from the list + +Delete task at the given index. + +Example of usage: + +`delete 1` + +Expected outcome: + +``` +Noted. I've removed this task: +[T][ ] read book +Now I have 3 tasks in the list. +``` + +### `find keyword` - find task + +Find task containing keyword in the list. + +Example of usage: + +`find book` + +Expected outcome: ``` -expected output +Here are the matching tasks in your list: +1. [D][ ] return book (by: Sunday) ``` diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..c04e32029d Binary files /dev/null and b/docs/Ui.png differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..f3d88b1c2f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..b7c8c5dbf5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..2fe81a7d95 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..62bd9b9cce --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java new file mode 100644 index 0000000000..c50f32f54d --- /dev/null +++ b/src/main/java/duke/Duke.java @@ -0,0 +1,15 @@ +package duke; + +import duke.ui.Ui; +import javafx.application.Application; + +/** + * + * Represents the chat bot. + * + */ +public class Duke { + public static void main(String[] args) { + Application.launch(Ui.class, args); + } +} diff --git a/src/main/java/duke/DukeExceptions.java b/src/main/java/duke/DukeExceptions.java new file mode 100644 index 0000000000..94df0bdb17 --- /dev/null +++ b/src/main/java/duke/DukeExceptions.java @@ -0,0 +1,15 @@ +package duke; + +/** + * Represents Exceptions caused by input syntax. + */ +public class DukeExceptions extends Exception{ + private String error; + public DukeExceptions(String error) { + this.error = error; + } + + public String getErrorMessage() { + return this.error; + } +} diff --git a/src/main/java/duke/Parser.java b/src/main/java/duke/Parser.java new file mode 100644 index 0000000000..9d1c3c3f88 --- /dev/null +++ b/src/main/java/duke/Parser.java @@ -0,0 +1,123 @@ +package duke; + +import duke.command.*; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * Parses the command. + */ +public class Parser { + private TodoList todoList; + + private enum Instructions {todo, deadline, event, duration, mark, unmark, delete, find} + + public Parser(TodoList todoList) { + this.todoList = todoList; + } + + /** + * Returns a Command object (can be ListCommand, ...) of the input String. + * + * @param command String input. + * @return Command Object corresponding to the input String command. + * @throws DukeExceptions If input syntax is incorrect. + */ + public Command parse(String command) throws DukeExceptions { + String[] split_command = command.split(" ", 2); + String instruction = split_command[0]; + if (instruction.equals("list")) { + if (split_command.length == 1) { + return new ListCommand(todoList); + } else { + throw new DukeExceptions("OOPS!!! The description of a list " + + "cannot have other parameters"); + } + } + + //check for valid instructions + for (Instructions validInstruction : Instructions.values()) { + if (validInstruction.name().equals(instruction)) { + if (split_command.length == 1) { + throw new DukeExceptions(String.format("OOPS!!! The description of a %s cannot be empty.", + instruction)); + } else if (instruction.equals("mark")) { + int digit = Integer.parseInt(split_command[1]); + return new MarkCommand(todoList, digit); + } else if (instruction.equals("unmark")) { + int digit = Integer.parseInt(split_command[1]); + return new UnmarkCommand(todoList, digit); + } else if (instruction.equals("delete")) { + int digit = Integer.parseInt(split_command[1]); + return new DeleteCommand(todoList, digit); + } else if (instruction.equals("find")) { + return new FindCommand(todoList, split_command[1]); + } else { + return new AddTaskCommand(todoList, instruction, split_command[1]); + } + } + } + throw new DukeExceptions("OOPS!!! I'm sorry, but I don't know what that means."); + } + + /** + * Returns LocalDate object capture the date of an event. + * + * @param possibleDateTime A string array may contain date information. + * @return LocalDate object capture the date or null if there is no information. + * @throws DateTimeParseException If there is no information. + */ + public static LocalDate parseDate(String[] possibleDateTime) throws DateTimeParseException { + try { + //parse date + LocalDate possibleDeadlineDate = LocalDate.parse(possibleDateTime[0]); + return possibleDeadlineDate; + } catch (DateTimeParseException ex) { + //can not parse date + return null; + } + } + + /** + * Returns String representation of the date of an event. + * + * @param possibleDeadlineDate LocalDate object capture the date of an event. + * @return String representation of the date of an event. + */ + public static String parseStringDate(LocalDate possibleDeadlineDate) { + return possibleDeadlineDate.format(DateTimeFormatter.ofPattern("MMM d yyyy")); + } + + /** + * Returns LocalTime object capture the time of an event. + * + * @param possibleDateTime A string array may contain time information. + * @return LocalTime object capture the time or null if there is no information. + * @throws DateTimeParseException If there is no information. + */ + public static LocalTime parseTime(String[] possibleDateTime) throws DateTimeParseException { + try { + //parse date + LocalTime possibleDeadlineTime = (possibleDateTime.length == 1) + ? LocalTime.parse(possibleDateTime[0]) + : LocalTime.parse(possibleDateTime[1]); + return possibleDeadlineTime; + } catch (DateTimeParseException ex) { + //can not parse date + return null; + } + } + + /** + * Returns String representation of the time of an event. + * + * @param possibleDeadlineTime LocalTime object capture the time. + * @return String representation of the time of an event. + */ + public static String parseStringTime(LocalTime possibleDeadlineTime) { + return possibleDeadlineTime.format(DateTimeFormatter.ofPattern("hh:mm a")); + } +} diff --git a/src/main/java/duke/Storage.java b/src/main/java/duke/Storage.java new file mode 100644 index 0000000000..b8a238dc5c --- /dev/null +++ b/src/main/java/duke/Storage.java @@ -0,0 +1,68 @@ +package duke; + +import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Represents a storage that load previous to do list and save current to do list. + */ + +public class Storage { + private Path path; + private boolean doesDirectoryExist; + + public Storage() { + this.path = Paths.get("src", "main", "ToDoListCS2103.txt"); + this.doesDirectoryExist = java.nio.file.Files.exists(path); + } + + /** + * Loads previous to do list stored either in main folder or in current folder. + * If there is no file, create one. + * + * @return A TodoList object contain information of previous to do list. + */ + public TodoList load() { + try { + File previousToDoList = new File(path.toString()); + if (doesDirectoryExist) { + FileInputStream fis = new FileInputStream(previousToDoList); + ObjectInputStream ois = new ObjectInputStream(fis); + TodoList savedTodoList = (TodoList) ois.readObject(); + ois.close(); + fis.close(); + return savedTodoList; + } else { + previousToDoList.createNewFile(); + return new TodoList(); + } + } catch (IOException ex) { + this.path = Paths.get("ToDoListCS2103.txt"); + doesDirectoryExist = java.nio.file.Files.exists(path); + return load(); + } catch (Exception ex) { + ex.printStackTrace(); + } + return new TodoList(); + } + + /** + * Saves current to do list in the same file in load. + * + * @param todoList TodoList object contain information needed to save. + */ + public void save(TodoList todoList) { + try{ + + FileOutputStream fos = new FileOutputStream(path.toFile()); + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(todoList); + oos.flush(); + oos.close(); + fos.close(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } +} diff --git a/src/main/java/duke/Task.java b/src/main/java/duke/Task.java new file mode 100644 index 0000000000..4ec4ddecc0 --- /dev/null +++ b/src/main/java/duke/Task.java @@ -0,0 +1,244 @@ +package duke; + +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeParseException; + +/** + * Represents a task (to do task or task with deadline,...). + */ +public class Task implements Serializable { + private Boolean isDone; + private String description; + + public Task() { + this.isDone = false; + this.description = ""; + } + + public Task(String task) { + this.isDone = false; + this.description = task; + } + + /** + * Returns a subclass of a task corresponding to the input command. + * + * @param type Type of task. + * @param task Description of task. + * @return A Task Object with corresponding information. + * @throws DukeExceptions If the input syntax is incorrect. + */ + public Task createNewTask(String type, String task) throws DukeExceptions{ + if (type.equals("todo")) { + return new Todo(task); + } else if (type.equals("deadline")) { + return new Deadline(task); + } else if (type.equals("event")) { + return new Event(task); + } else if (type.equals("duration")) { + return new Duration(task); + } else { + assert false : "Should not allow creating " + type; + return null; + } + } + + /** + * Marks a task is done. + */ + public void markTask() { + this.isDone = true; + } + + /** + * Unmarks a task is done. + */ + public void unmarkTask() { + this.isDone = false; + } + + /** + * Returns true if the description contains keywords, false otherwise. + * + * @param keyword Keywords needed to find. + * @return true if the description contains keywords, false otherwise. + */ + public boolean doesContainKeyword(String keyword) { + return this.description.contains(keyword); + } + + private class Todo extends Task { + private String task; + + public Todo(String task) { + super(task); + this.task = task; + } + + @Override + public String toString() { + String name = super.isDone ? "[T][X] " + task : "[T][ ] " + task; + return name; + } + } + + private class Deadline extends Task { + private static final long serialVersionUID = -7531925916726747642L; + private String task; + private String deadline; + private Boolean isDate = false; + private Boolean isTime = false; + private LocalDate deadlineDate; + private LocalTime deadlineTime; + private String deadlineDateString; + private String deadlineTimeString; + + public Deadline(String task) throws DukeExceptions, DateTimeParseException { + super(task); + String[] commands = task.split(" /by "); + if (commands.length == 1) { + throw new DukeExceptions("OOPS!!! Looks like someone forget his/her deadline :)\n Please use /by to indicate deadline"); + } + this.task = commands[0]; + this.deadline = commands[1]; + String[] possibleDateTime = this.deadline.split((" ")); + LocalDate possibleDeadlineDate = Parser.parseDate(possibleDateTime); + if (possibleDeadlineDate != null) { + this.isDate = true; + this.deadlineDate = possibleDeadlineDate; + this.deadlineDateString = Parser.parseStringDate(possibleDeadlineDate); + } + LocalTime possibleDeadlineTime = Parser.parseTime(possibleDateTime); + if (possibleDeadlineTime != null) { + this.isTime = true; + this.deadlineTime = possibleDeadlineTime; + this.deadlineTimeString = Parser.parseStringTime(possibleDeadlineTime); + } + } + + @Override + public String toString() { + if (isDate && isTime) { + String name = super.isDone ? "[D][X] " + task + " (by: " + this.deadlineDateString + " " + + this.deadlineTimeString +")" : "[D][ ] " + task + " (by: " + this.deadlineDateString + " " + + this.deadlineTimeString + ")"; + return name; + } else if (isDate) { + String name = super.isDone ? "[D][X] " + task + " (by: " + this.deadlineDateString + " " + +")" : "[D][ ] " + task + " (by: " + this.deadlineDateString + ")"; + return name; + } else if (isTime) { + String name = super.isDone ? "[D][X] " + task + " (by: " + this.deadlineTimeString + " " + +")" : "[D][ ] " + task + " (by: " + this.deadlineTimeString + ")"; + return name; + } + String name = super.isDone ? "[D][X] " + task + " (by: " + this.deadline +")" : "[D][ ] " + task + " (by: " + this.deadline +")"; + return name; + } + } + + private class Event extends Task { + private static final long serialVersionUID = 6827458563349815922L; + private String task; + private String from; + private String to; + private Boolean isFromDate = false; + private Boolean isFromTime = false; + private Boolean isToDate = false; + private Boolean isToTime = false; + private LocalDate fromDate; + private LocalTime fromTime; + private LocalDate toDate; + private LocalTime toTime; + private String fromDateString; + private String fromTimeString; + private String toDateString; + private String toTimeString; + + public Event(String task) throws DukeExceptions { + super(task); + String[] commands = task.split(" /from "); + if (commands.length == 1) { + throw new DukeExceptions("OOPS!!! Looks like someone forget when the event begins :)\n Please use /from to indicate begin time"); + } + this.task = commands[0]; + String[] from_to_timeline = commands[1].split(" /to "); + if (from_to_timeline.length == 1) { + throw new DukeExceptions("OOPS!!! Looks like someone forget when the event ends :)\n Please use /to to indicate end time"); + } + this.from = from_to_timeline[0]; + this.to = from_to_timeline[1]; + //parse date and time of /from + String[] possibleDateTime = this.from.split((" ")); + LocalDate possibleFromDate = Parser.parseDate(possibleDateTime); + if (possibleFromDate != null) { + this.isFromDate = true; + this.fromDate = possibleFromDate; + this.fromDateString = Parser.parseStringDate(possibleFromDate); + } + LocalTime possibleFromTime = Parser.parseTime(possibleDateTime); + if (possibleFromTime != null) { + this.isFromTime = true; + this.fromTime = possibleFromTime; + this.fromTimeString = Parser.parseStringTime(possibleFromTime); + } + + //parse date and time of /to + possibleDateTime = this.to.split((" ")); + LocalDate possibleToDate = Parser.parseDate(possibleDateTime); + if (possibleToDate != null) { + this.isToDate = true; + this.toDate = possibleToDate; + this.toDateString = Parser.parseStringDate(possibleToDate); + } + LocalTime possibleToTime = Parser.parseTime(possibleDateTime); + if (possibleToTime != null) { + this.isToTime = true; + this.toTime = possibleToTime; + this.toTimeString = Parser.parseStringTime(possibleToTime); + } + } + + @Override + public String toString() { + String fromString = this.from; + if (isFromDate || isFromTime) { + fromString = isFromDate ? fromDateString : ""; + fromString += isFromTime ? " " + fromTimeString : ""; + } + + String toString = this.to; + if (isToDate || isToTime) { + toString = isToDate ? toDateString : ""; + toString += isToTime ? " " + toTimeString : ""; + } + String name = super.isDone ? "[E][X] " + task + " (from: " + fromString +" to: " + toString + ")" + : "[E][ ] " + task + " (from: " + fromString +" to: " + toString + ")"; + return name; + } + } + + private class Duration extends Task { + private static final long serialVersionUID = -7531925916726747641L; + private String task; + private String duration; + + public Duration(String task) throws DukeExceptions { + super(task); + String[] commands = task.split(" /needs "); + if (commands.length == 1) { + throw new DukeExceptions("OOPS!!! Looks like someone forget his/her duration :)\n Please use /needs to indicate duration"); + } + this.task = commands[0]; + this.duration = commands[1]; + } + + @Override + public String toString() { + String name = super.isDone ? "[Du][X] " + task + " (needs: " + this.duration +")" : "[Du][ ] " + task + " (needs: " + this.duration +")"; + return name; + } + } +} diff --git a/src/main/java/duke/TodoList.java b/src/main/java/duke/TodoList.java new file mode 100644 index 0000000000..cfc883b246 --- /dev/null +++ b/src/main/java/duke/TodoList.java @@ -0,0 +1,111 @@ +package duke; + +import java.io.Serializable; +import java.util.ArrayList; + +/** + * Represents a list containing all tasks. + */ +public class TodoList implements Serializable { + private ArrayList todoList; + + public TodoList() { + this.todoList = new ArrayList<>(100); + } + + /** + * Creates and adds new task corresponding to the input. + * + * @param type Type of task. + * @param task Description of task. + * @throws DukeExceptions If the input syntax is incorrect. + */ + public String add(String type, String task) throws DukeExceptions{ + Task taskObject = new Task(); + Task newTask = taskObject.createNewTask(type, task); + todoList.add(newTask); + return ("Got it. I've added this task:\n" + newTask.toString()); + } + + /** + * Marks a numbered task is done. + * + * @param index The number order of the task. + * @throws DukeExceptions If the input syntax is incorrect. + */ + public String mark(int index) throws DukeExceptions{ + int todo_list_length = todoList.size(); + boolean isOutOfBound = index < 0 || index > todo_list_length; + if (isOutOfBound) { + throw new DukeExceptions("Please use list command to check the index!"); + } + Task task = todoList.get(index - 1); + task.markTask(); + return String.format("Nice! I've marked this task as done:\n%s", todoList.get(index-1)); + } + + /** + * Unmarks a numbered task is done. + * + * @param index The number order of the task. + * @throws DukeExceptions If the input syntax is incorrect. + */ + public String unmark(int index) throws DukeExceptions{ + int todo_list_length = todoList.size(); + boolean isOutOfBound = index < 0 || index > todo_list_length; + if (isOutOfBound) { + throw new DukeExceptions("Please use list command to check the index!"); + } + Task task = todoList.get(index - 1); + task.unmarkTask(); + return String.format("OK, I've marked this task as not done yet:\n%s", todoList.get(index-1)); + } + + /** + * Deletes a numbered task is done. + * + * @param index The number order of the task. + * @throws DukeExceptions If the input syntax is incorrect. + */ + public String delete(int index) throws DukeExceptions { + int todo_list_length = todoList.size(); + boolean isOutOfBound = index < 0 || index > todo_list_length; + if (isOutOfBound) { + throw new DukeExceptions("Please use list command to check the index!"); + } + Task task = todoList.remove(index - 1); + return String.format("Noted. I've removed this task:\n%s", task); + } + + /** + * Returns how many tasks in the list. + * + * @return How many tasks in the list. + */ + public int getNumberOfTasks() { + return todoList.size(); + } + + public String find(String keyword) { + String resultList = ""; + int todoListLength = todoList.size(); + int resultIndex = 1; + for (int i = 1; i <= todoListLength; i++) { + Task task = todoList.get(i-1); + if (task.doesContainKeyword(keyword)) { + resultList += String.format("%d. %s\n", resultIndex++, task); + } + } + return("Here are the matching tasks in your list:\n" + resultList); + } + + @Override + public String toString() { + String shownList = ""; + int todoListLength = todoList.size(); + for (int i = 1; i <= todoListLength; i++) { + shownList += String.format("%d. %s\n", i, todoList.get(i-1)); + } + return shownList; + } +} diff --git a/src/main/java/duke/command/AddTaskCommand.java b/src/main/java/duke/command/AddTaskCommand.java new file mode 100644 index 0000000000..b4c215665a --- /dev/null +++ b/src/main/java/duke/command/AddTaskCommand.java @@ -0,0 +1,25 @@ +package duke.command; + +import duke.DukeExceptions; +import duke.TodoList; + +/** + * Represents an add command. + */ +public class AddTaskCommand extends Command{ + private TodoList todoList; + private String instruction; + private String description; + + public AddTaskCommand(TodoList todoList, String instruction, String description) { + this.todoList = todoList; + this.instruction = instruction; + this.description = description; + } + + @Override + public String execute() throws DukeExceptions { + String result = todoList.add(instruction, description); + return(String.format("%s\nNow I have %d tasks in the list.", result, todoList.getNumberOfTasks())); + } +} diff --git a/src/main/java/duke/command/Command.java b/src/main/java/duke/command/Command.java new file mode 100644 index 0000000000..f0b5ca09b1 --- /dev/null +++ b/src/main/java/duke/command/Command.java @@ -0,0 +1,10 @@ +package duke.command; + +import duke.DukeExceptions; + +/** + * Represents a command. + */ +public abstract class Command { + public abstract String execute() throws DukeExceptions; +} \ No newline at end of file diff --git a/src/main/java/duke/command/DeleteCommand.java b/src/main/java/duke/command/DeleteCommand.java new file mode 100644 index 0000000000..865665ae67 --- /dev/null +++ b/src/main/java/duke/command/DeleteCommand.java @@ -0,0 +1,20 @@ +package duke.command; + +import duke.DukeExceptions; +import duke.TodoList; + +public class DeleteCommand extends Command{ + private TodoList todoList; + private int index; + + public DeleteCommand(TodoList todoList, int index) { + this.todoList = todoList; + this.index = index; + } + + @Override + public String execute() throws DukeExceptions { + String message = todoList.delete(index); + return(String.format("%s\nNow I have %d tasks in the list.", message, todoList.getNumberOfTasks())); + } +} diff --git a/src/main/java/duke/command/FindCommand.java b/src/main/java/duke/command/FindCommand.java new file mode 100644 index 0000000000..cb37c66391 --- /dev/null +++ b/src/main/java/duke/command/FindCommand.java @@ -0,0 +1,22 @@ +package duke.command; + +import duke.DukeExceptions; +import duke.TodoList; + +/** + * Represents find command. + */ +public class FindCommand extends Command{ + private TodoList todoList; + private String description; + + public FindCommand(TodoList todoList, String description) { + this.todoList = todoList; + this.description = description; + } + + @Override + public String execute() throws DukeExceptions { + return (todoList.find(description)); + } +} diff --git a/src/main/java/duke/command/ListCommand.java b/src/main/java/duke/command/ListCommand.java new file mode 100644 index 0000000000..51cb543d7f --- /dev/null +++ b/src/main/java/duke/command/ListCommand.java @@ -0,0 +1,19 @@ +package duke.command; + +import duke.DukeExceptions; +import duke.TodoList; + +public class ListCommand extends Command{ + private TodoList todoList; + + public ListCommand(TodoList todoList) { + this.todoList = todoList; + } + + @Override + public String execute() throws DukeExceptions { + String shown_list = todoList.toString(); + return(shown_list); + } +} + diff --git a/src/main/java/duke/command/MarkCommand.java b/src/main/java/duke/command/MarkCommand.java new file mode 100644 index 0000000000..c2c93d886e --- /dev/null +++ b/src/main/java/duke/command/MarkCommand.java @@ -0,0 +1,17 @@ +package duke.command; + +import duke.DukeExceptions; +import duke.TodoList; + +public class MarkCommand extends Command{ + private TodoList todoList; + private int index; + + public MarkCommand(TodoList todoList, int index) { + this.todoList = todoList; + this.index = index; + } + + @Override + public String execute() throws DukeExceptions { return todoList.mark(index); } +} diff --git a/src/main/java/duke/command/UnmarkCommand.java b/src/main/java/duke/command/UnmarkCommand.java new file mode 100644 index 0000000000..3def595042 --- /dev/null +++ b/src/main/java/duke/command/UnmarkCommand.java @@ -0,0 +1,19 @@ +package duke.command; + +import duke.DukeExceptions; +import duke.TodoList; + +public class UnmarkCommand extends Command{ + private TodoList todoList; + private int index; + + public UnmarkCommand(TodoList todoList, int index) { + this.todoList = todoList; + this.index = index; + } + + @Override + public String execute() throws DukeExceptions { + return todoList.unmark(index); + } +} diff --git a/src/main/java/duke/ui/DialogBox.java b/src/main/java/duke/ui/DialogBox.java new file mode 100644 index 0000000000..81e6835a16 --- /dev/null +++ b/src/main/java/duke/ui/DialogBox.java @@ -0,0 +1,53 @@ +package duke.ui; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.text.Font; + +/** + * Represents a dialog, use code from https://se-education.org/guides/tutorials/javaFx.html + * with some modification in format setting. + */ +public class DialogBox extends HBox { + private Label text; + private ImageView displayPicture; + + public DialogBox(Label l, ImageView iv) { + text = l; + displayPicture = iv; + + text.setWrapText(true); + text.setFont(Font.font("Comic Sans MS")); + text.setPadding(new Insets(5.0, 10.0, 10.0, 10.0)); + displayPicture.setFitHeight(50.0); + displayPicture.setFitWidth(50.0); + + this.setAlignment(Pos.TOP_RIGHT); + this.setPadding(new Insets(5.0)); + this.getChildren().addAll(text, displayPicture); + } + + private void flip() { + this.setAlignment(Pos.TOP_LEFT); + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + FXCollections.reverse(tmp); + this.getChildren().setAll(tmp); + } + + public static DialogBox getUserDialog(Label l, ImageView iv) { + return new DialogBox(l, iv); + } + + public static DialogBox getDukeDialog(Label l, ImageView iv) { + var db = new DialogBox(l, iv); + db.flip(); + return db; + } +} + diff --git a/src/main/java/duke/ui/Ui.java b/src/main/java/duke/ui/Ui.java new file mode 100644 index 0000000000..7ae0e9cafd --- /dev/null +++ b/src/main/java/duke/ui/Ui.java @@ -0,0 +1,148 @@ +package duke.ui; + +import java.io.IOException; + +import duke.DukeExceptions; +import duke.Parser; +import duke.Storage; +import duke.TodoList; +import duke.command.Command; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +/** + * Represents User Interface, handle interaction with users, use code from + * https://se-education.org/guides/tutorials/javaFx.html with modification in format, + * some additional method and fields, some logic handling. + */ +public class Ui extends Application{ + private final Image user = new Image(this.getClass().getResourceAsStream("/images/user.png")); + private final Image duke = new Image(this.getClass().getResourceAsStream("/images/duke.png")); + private Parser bot; + private Storage storage; + private TodoList todoList; + + @Override + public void init() { + this.storage = new Storage(); + this.todoList = storage.load(); + this.bot = new Parser(todoList); + } + + @Override + public void start(Stage stage) throws IOException { + + //Step 1: Setting + AnchorPane mainLayout = new AnchorPane(); + + ScrollPane scrollPane = new ScrollPane(); + VBox dialogContainer = new VBox(); + scrollPane.setContent(dialogContainer); + + TextField userInput = new TextField("Type your command here"); + Button sendButton = new Button("Send"); + mainLayout.getChildren().addAll(scrollPane, userInput, sendButton); + + //Step 2: Formatting + double height = 600.0; + double width = 400.0; + stage.setMinHeight(height); + stage.setMinWidth(width); + stage.setResizable(false); + + mainLayout.setPrefSize(width, height); + + scrollPane.setPrefSize(width - 15, height - 65); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); + + scrollPane.setVvalue(1.0); + scrollPane.setFitToWidth(true); + + dialogContainer.setPrefHeight(Region.USE_COMPUTED_SIZE); + + userInput.setPrefWidth(325.0); + + sendButton.setPrefWidth(55.0); + + AnchorPane.setTopAnchor(scrollPane, 1.0); + + AnchorPane.setBottomAnchor(sendButton, 1.0); + AnchorPane.setRightAnchor(sendButton, 1.0); + + AnchorPane.setLeftAnchor(userInput , 1.0); + AnchorPane.setBottomAnchor(userInput, 1.0); + + dialogContainer.heightProperty().addListener((observable -> {scrollPane.setVvalue(1.0);})); + + //Step 3: Handle action + sendButton.setOnMouseClicked((event) -> { + String userInputCommand = userInput.getText(); + Label userText = new Label(userInputCommand); + String response = ""; + + if (userInputCommand.equals("bye")) { + userInput.clear(); + Platform.exit(); + stage.close(); + } + try { + Command command = bot.parse(userInputCommand); + response = command.execute(); + } catch (DukeExceptions error) { + response = error.getErrorMessage(); + } + Label dukeText = new Label(response); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(userText, new ImageView(user)), + DialogBox.getDukeDialog(dukeText, new ImageView(duke)) + ); + userInput.clear(); + }); + + userInput.setOnAction((event) -> { + String userInputCommand = userInput.getText(); + Label userText = new Label(userInputCommand); + String response = ""; + if (userInputCommand.equals("bye")) { + userInput.clear(); + Platform.exit(); + stage.close(); + } + try { + Command command = bot.parse(userInputCommand); + response = command.execute(); + } catch (DukeExceptions error) { + response = error.getErrorMessage(); + } + Label dukeText = new Label(response); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(userText, new ImageView(user)), + DialogBox.getDukeDialog(dukeText, new ImageView(duke)) + ); + userInput.clear(); + }); + + Scene scene = new Scene(mainLayout); + stage.setTitle("Duke"); + stage.setScene(scene); + stage.show(); + } + + @Override + public void stop() { + storage.save(todoList); + } + +} diff --git a/src/main/resources/images/duke.png b/src/main/resources/images/duke.png new file mode 100644 index 0000000000..6ad766272b Binary files /dev/null and b/src/main/resources/images/duke.png differ diff --git a/src/main/resources/images/user.png b/src/main/resources/images/user.png new file mode 100644 index 0000000000..e37f01b24b Binary files /dev/null and b/src/main/resources/images/user.png differ diff --git a/src/test/java/duke/TaskTest.java b/src/test/java/duke/TaskTest.java new file mode 100644 index 0000000000..dfb2d11da9 --- /dev/null +++ b/src/test/java/duke/TaskTest.java @@ -0,0 +1,29 @@ +package duke; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TaskTest { + @Test + public void createNewToDoTask(){ + try { + String newTodoTask = new Task().createNewTask("todo", + "read book").toString(); + assertEquals(newTodoTask, "[T][ ] read book"); + } catch (DukeExceptions ex) { + System.out.println(ex.getErrorMessage()); + } + } + + @Test + public void createNewDeadlineTask(){ + try { + String newDeadlineTask = new Task().createNewTask("deadline", + "return book /by 2023-02-10 18:00").toString(); + assertEquals(newDeadlineTask, "[D][ ] return book (by: Feb 10 2023 06:00 PM)"); + } catch (DukeExceptions ex) { + System.out.println(ex.getErrorMessage()); + } + } +} diff --git a/src/test/java/duke/TodoListTest.java b/src/test/java/duke/TodoListTest.java new file mode 100644 index 0000000000..a2af92b003 --- /dev/null +++ b/src/test/java/duke/TodoListTest.java @@ -0,0 +1,18 @@ +package duke; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TodoListTest { + @Test + public void addToDoTask(){ + try { + TodoList newTodoList = new TodoList(); + newTodoList.add("todo", "read book"); + assertEquals(newTodoList.getNumberOfTasks(), 1); + } catch (DukeExceptions ex) { + System.out.println(ex.getErrorMessage()); + } + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..b6cfb7e3d2 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,90 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| +____________________________________________________________ +Hello from duke.Duke +What can I do for you? +____________________________________________________________ +OOPS!!! I'm sorry, but I don't know what that means. +____________________________________________________________ +OOPS!!! The description of a todo cannot be empty. +____________________________________________________________ +Got it. I've added this task: +[T][ ] read book +Now I have 1 tasks in the list. +____________________________________________________________ +OOPS!!! The description of a list cannot have other parameters +____________________________________________________________ +1. [T][ ] read book +____________________________________________________________ +OOPS!!! The description of a deadline cannot be empty. +____________________________________________________________ +OOPS!!! Looks like someone forget his/her deadline :) + Please use /by to indicate deadline +____________________________________________________________ +Got it. I've added this task: +[D][ ] return book (by: Sunday 2pm) +Now I have 2 tasks in the list. +____________________________________________________________ +1. [T][ ] read book +2. [D][ ] return book (by: Sunday 2pm) + +____________________________________________________________ +OOPS!!! The description of a event cannot be empty. +____________________________________________________________ +OOPS!!! Looks like someone forget when the event begins :) + Please use /from to indicate begin time +____________________________________________________________ +OOPS!!! Looks like someone forget when the event ends :) + Please use /to to indicate end time +____________________________________________________________ +Got it. I've added this task: +[E][ ] programming study (from: 5pm to: 9pm) +Now I have 3 tasks in the list. +____________________________________________________________ +1. [T][ ] read book +2. [D][ ] return book (by: Sunday 2pm) +3. [E][ ] programming study (from: 5pm to: 9pm) + +____________________________________________________________ +Nice! I've marked this task as done: +[T][X] read book +____________________________________________________________ +1. [T][X] read book +2. [D][ ] return book (by: Sunday 2pm) +3. [E][ ] programming study (from: 5pm to: 9pm) + +____________________________________________________________ +Nice! I've marked this task as done: +[D][X] return book (by: Sunday 2pm) +____________________________________________________________ +1. [T][X] read book +2. [D][X] return book (by: Sunday 2pm) +3. [E][ ] programming study (from: 5pm to: 9pm) + +____________________________________________________________ +OK, I've marked this task as not done yet: +[D][ ] return book (by: Sunday 2pm) +____________________________________________________________ +1. [T][X] read book +2. [D][ ] return book (by: Sunday 2pm) +3. [E][ ] programming study (from: 5pm to: 9pm) + +____________________________________________________________ +OOPS!!! The description of a delete cannot be empty. +____________________________________________________________ +Please use list command to check the index! +____________________________________________________________ +1. [T][X] read book +2. [D][ ] return book (by: Sunday 2pm) +3. [E][ ] programming study (from: 5pm to: 9pm) + +____________________________________________________________ +Noted. I've removed this task: +[T][X] read book +Now I have 2 tasks in the list. +____________________________________________________________ +1. [D][ ] return book (by: Sunday 2pm) +2. [E][ ] programming study (from: 5pm to: 9pm) + +____________________________________________________________ +Bye. Hope to see you again soon! +____________________________________________________________ diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..eee73bcc55 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,42 @@ +tola +todo +todo read book +list appear +list +deadline +deadline return book +deadline return book /by Sunday 2pm +list +event +event programming study +event programming study /from 5pm +event programming study /from 5pm /to 9pm +list +mark 1 +list +mark 2 +list +unmark 2 +list +delete +delete 4 +list +delete 1 +list +delete 1 +delete 1 +deadline return book /by 2023-02-10 18:00 +deadline assignment /by 2023-02-10 +deadline cooking /by 18:00 +list +event dance /from 2023-02-10 18:00 /to 2023-02-10 19:00 +list +event dance /from 2023-02-10 /to 2023-02-10 19:00 +list +event dance /from 2023-02-10 18:00 /to 2023-02-10 +list +event dance /from 2023-02-10 /to 2023-02-10 +list +event dance /from 18:00 /to 19:00 +list +bye \ No newline at end of file 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