diff --git a/.classpath b/.classpath new file mode 100644 index 0000000000..bfdccfd000 --- /dev/null +++ b/.classpath @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..00a51aff5e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore index f69985ef1f..f803960d66 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,12 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT + +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build + +# Data Storage File +data/checklst.txt \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 0000000000..d0aba217ed --- /dev/null +++ b/.project @@ -0,0 +1,34 @@ + + + ip + Project ip created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + + 1613581073555 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000000..5e18705066 --- /dev/null +++ b/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.project.dir= +eclipse.preferences.version=1 +gradle.user.home= +java.home=/Library/Java/JavaVirtualMachines/jdk-14.0.1.jdk/Contents/Home +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..18ad895236 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.source=11 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..0aeeaea035 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Launch External Terminal", + "request": "launch", + "console": "externalTerminal", + "mainClass": "checklst.main.Loader" + }, + { + "type": "java", + "name": "Launch Loader", + "request": "launch", + "mainClass": "checklst.main.Loader", + "projectName": "ip_11f7286" + }, + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..f54101a703 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "java.project.referencedLibraries": [ + "lib/**/*.jar", + "/Users/glenn/.gradle/caches/modules-2/files-2.1/org.openjfx/**/*.jar" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 0dd8f7fb21..1ca9140013 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,84 @@ -# 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. - -## Setting up in Intellij - -Prerequisites: JDK 11, update Intellij to the most recent version. - -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). -1. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` +# Checklst + +Checklst is a basic Todo app, allowing users to store and track events, deadlines and todos. Checklst has gradually evolved from a basic CLI application to a fully ready GUI. + +![](docs/main_ui.png) + +# Command Syntax + +| Command | Description | Example | +|--|--|--| +| `list` | Lists all Tasks | `list` | +| `todo NAME` | Creates a Todo | `todo Read Books` | +| `event NAME /at EVENT_DATE` | Creates an Event with a specific occurrence date| `event Concert /at 2020-12-04` | +| `deadline NAME /by DUE_DATE` | Creates a Deadline with a specific due date | `deadline Assignment /by 2020-12-03` | +| `done INDEX` | Completes the Task | `done 1` | +| `delete INDEX` | Deletes the Task | `delete 1` | +| `find KEYWORD` | Finds all Tasks whose name contains the Keyword | `find CS2103` | +| `sort` | Sorts all Tasks by Complete/Incomplete and then Task Type | `sort` | +| `help` | Shows all Commands | `help` | +| `bye` | Exits Checklst | `bye` | + + +# Features + +## Creating a Task +Tasks can be one of 3 types - Todo, Event and Deadline. Todos are basic named Tasks while Events and Deadlines both contain a date which corresponds to their event date and due date respectively. + +Tasks can be made using commands `todo`, `event`, `deadline` to create each type of task respectively. Tasks will be labelled according to their task type in the Checklst application as follows: + +| Task Command | Task Representation +|--|--| +| `todo Read Books` | [T][ ] Read Books | +| `event Concert /at 2020-12-04` | [E][ ] Concert (at: Dec 4 2020) | +| `deadline Assignment /by 2020-12-03` | [D][ ] Assignment (by: Dec 3 2020) | + + +## Listing Tasks and Completing a Task +Tasks can be listed using the `list` command. + +Once a Task is made, it will be by default labelled as incomplete. A task can then be completed using the `done` command to mark it as completed. The index value of the task starts from index 1 and can be referenced from the `list` command. + +| Task Command | Expected Output +|--|--| +| `list` | These are your current tasks:
1. [T][ ] Read Books | +| `done 1` | [T][X] Read Books | +| `list` | These are your current tasks:
1. [T][X] Read Books | + +## Deleting a Task +Tasks can be removed from Checklst by using the `delete` command. Similarly, the index value starts from index 1. + +| Task Command | Expected Output +|--|--| +| `list` | These are your current tasks:
1. [T][X] Read Books | +| `delete 1` | [T][X] Read Books | +| `list` | Task list is currently empty! | + + +## Finding Tasks +To easily retrieve tasks when the Task List gets convoluted, the `find` command may be used with a keyword that will be matched to tasks. As long as some part of a Task's name contains the keyword, it will be returned. + +| Task Command | Expected Output +|--|--| +| `list` | These are your current tasks:
1. [T][ ] Read Books
2. [T][ ] Return Books
3. [T][ ] Eat Lunch| +| `find books` | Here are the matching tasks in your list!
1. [T][ ] Read Books
2. [T][ ] Return Books | + + +## Sorting Tasks +Tasks are added to the bottom of the Task List via a Chronological input order. However, the tasks may be deliberately sorted on will via the `sort` command. + +The sorting will be done via 2 conditions. Firstly, the tasks will be split into complete and incomplete. Secondly, the tasks will be sorted by Task type. + +| Task Command | Expected Output +|--|--| +| `list` | These are your current tasks:
1. [T][ ] Read Books
2. [E][ ] Movie (at: Apr 24 2020)
3. [T][X] Return Books
4. [E][ ] Concert (at: Feb 13 2020) | +| `sort` | These are your current tasks:
1. [T][ ] Read Books
2. [E][ ] Movie (at: Apr 24 2020)
3. [E][ ] Concert (at: Feb 13 2020)
4. [T][X] Return Books | + +## Errors +Checklst is is capable of handling errors in command or keyword inputs. Whenever an error is encounted, Checklst will return the error message with a clear red indicator. + +![](docs/Ui.png) + +## Credits +Icons in Checklst are made by and used with courtesy from Flat Icons. +
"https://www.flaticon.com/" \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..9d9dd966e7 --- /dev/null +++ b/build.gradle @@ -0,0 +1,71 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'java-library' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +repositories { + mavenCentral() +} + +dependencies { + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' + + String javaFxVersion = '11' + + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "checklst.main.Loader" +} + +shadowJar { + archiveBaseName = "checklst" + archiveClassifier = null +} + +checkstyle { + toolVersion = '8.32' +} + +run { + enableAssertions = true + standardInput = System.in +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + +version = '1.2.1' \ No newline at end of file diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..502fbd3a58 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,398 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..dcaa1af3c3 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/data/checklst.txt b/data/checklst.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/README.md b/docs/README.md index fd44069597..665c0b248f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,87 @@ -# User Guide +# Checklst -## Features +Checklst is a basic Todo app, allowing users to store and track events, deadlines and todos. Checklst has gradually evolved from a basic CLI application to a fully ready GUI. -### Feature 1 -Description of feature. +#### Github Link +https://github.com/glennljs/ip -## Usage +![](main_ui.png) -### `Keyword` - Describe action +# Command Syntax -Describe action and its outcome. +| Command | Description | Example | +|--|--|--| +| `list` | Lists all Tasks | `list` | +| `todo NAME` | Creates a Todo | `todo Read Books` | +| `event NAME /at EVENT_DATE` | Creates an Event with a specific occurrence date| `event Concert /at 2020-12-04` | +| `deadline NAME /by DUE_DATE` | Creates a Deadline with a specific due date | `deadline Assignment /by 2020-12-03` | +| `done INDEX` | Completes the Task | `done 1` | +| `delete INDEX` | Deletes the Task | `delete 1` | +| `find KEYWORD` | Finds all Tasks whose name contains the Keyword | `find CS2103` | +| `sort` | Sorts all Tasks by Complete/Incomplete and then Task Type | `sort` | +| `help` | Shows all Commands | `help` | +| `bye` | Exits Checklst | `bye` | -Example of usage: -`keyword (optional arguments)` +# Features -Expected outcome: +## Creating a Task +Tasks can be one of 3 types - Todo, Event and Deadline. Todos are basic named Tasks while Events and Deadlines both contain a date which corresponds to their event date and due date respectively. -`outcome` +Tasks can be made using commands `todo`, `event`, `deadline` to create each type of task respectively. Tasks will be labelled according to their task type in the Checklst application as follows: + +| Task Command | Task Representation +|--|--| +| `todo Read Books` | [T][ ] Read Books | +| `event Concert /at 2020-12-04` | [E][ ] Concert (at: Dec 4 2020) | +| `deadline Assignment /by 2020-12-03` | [D][ ] Assignment (by: Dec 3 2020) | + + +## Listing Tasks and Completing a Task +Tasks can be listed using the `list` command. + +Once a Task is made, it will be by default labelled as incomplete. A task can then be completed using the `done` command to mark it as completed. The index value of the task starts from index 1 and can be referenced from the `list` command. + +| Task Command | Expected Output +|--|--| +| `list` | These are your current tasks:
1. [T][ ] Read Books | +| `done 1` | [T][X] Read Books | +| `list` | These are your current tasks:
1. [T][X] Read Books | + +## Deleting a Task +Tasks can be removed from Checklst by using the `delete` command. Similarly, the index value starts from index 1. + +| Task Command | Expected Output +|--|--| +| `list` | These are your current tasks:
1. [T][X] Read Books | +| `delete 1` | [T][X] Read Books | +| `list` | Task list is currently empty! | + + +## Finding Tasks +To easily retrieve tasks when the Task List gets convoluted, the `find` command may be used with a keyword that will be matched to tasks. As long as some part of a Task's name contains the keyword, it will be returned. + +| Task Command | Expected Output +|--|--| +| `list` | These are your current tasks:
1. [T][ ] Read Books
2. [T][ ] Return Books
3. [T][ ] Eat Lunch| +| `find books` | Here are the matching tasks in your list!
1. [T][ ] Read Books
2. [T][ ] Return Books | + + +## Sorting Tasks +Tasks are added to the bottom of the Task List via a Chronological input order. However, the tasks may be deliberately sorted on will via the `sort` command. + +The sorting will be done via 2 conditions. Firstly, the tasks will be split into complete and incomplete. Secondly, the tasks will be sorted by Task type. + +| Task Command | Expected Output +|--|--| +| `list` | These are your current tasks:
1. [T][ ] Read Books
2. [E][ ] Movie (at: Apr 24 2020)
3. [T][X] Return Books
4. [E][ ] Concert (at: Feb 13 2020) | +| `sort` | These are your current tasks:
1. [T][ ] Read Books
2. [E][ ] Movie (at: Apr 24 2020)
3. [E][ ] Concert (at: Feb 13 2020)
4. [T][X] Return Books | + +## Errors +Checklst is is capable of handling errors in command or keyword inputs. Whenever an error is encounted, Checklst will return the error message with a clear red indicator. + +![](Ui.png) + +## Credits +Icons in Checklst are made by and used with courtesy from Flat Icons. +
"https://www.flaticon.com/" \ No newline at end of file diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..ace7bdc6ec Binary files /dev/null and b/docs/Ui.png differ diff --git a/docs/main_ui.png b/docs/main_ui.png new file mode 100644 index 0000000000..23f8a2af9b Binary files /dev/null and b/docs/main_ui.png differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..e708b1c023 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..28ff446a21 --- /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.8.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..4f906e0c81 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/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..107acd32c4 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..aec1281716 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/6.8.1/userguide/multi_project_builds.html + */ + +rootProject.name = 'ip' 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/checklst/exception/ChecklstException.java b/src/main/java/checklst/exception/ChecklstException.java new file mode 100644 index 0000000000..3c7ca1ff1a --- /dev/null +++ b/src/main/java/checklst/exception/ChecklstException.java @@ -0,0 +1,21 @@ +package checklst.exception; + +/** + * Custom exception to represent Exceptions in the Checklst program. + */ +public class ChecklstException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 4447981462872622989L; + + /** + * Creates instance for Checklst Exceptions. + * @param errMessage Error message to be displayed. + */ + public ChecklstException(String errMessage) { + super(errMessage); + } + +} diff --git a/src/main/java/checklst/gui/ChecklstDialog.java b/src/main/java/checklst/gui/ChecklstDialog.java new file mode 100644 index 0000000000..b581cd9306 --- /dev/null +++ b/src/main/java/checklst/gui/ChecklstDialog.java @@ -0,0 +1,30 @@ +package checklst.gui; + +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.paint.Color; + +public class ChecklstDialog extends DialogBox { + + private static final String NAME = "Checklst"; + + /** + * Creates a message dialog from the Checklst bot. + * @param l Message to be shown. + * @param iv Image of Checklst. + */ + public ChecklstDialog(Label l, ImageView iv) { + super(NAME, l, iv); + + Color dialogColor = new Color(0, 0.7, 1, 1); + this.text.setBackground( + new Background(new BackgroundFill(dialogColor, new CornerRadii(7.0), Insets.EMPTY))); + + this.flip(); + } + +} diff --git a/src/main/java/checklst/gui/DialogBox.java b/src/main/java/checklst/gui/DialogBox.java new file mode 100644 index 0000000000..1e78b41beb --- /dev/null +++ b/src/main/java/checklst/gui/DialogBox.java @@ -0,0 +1,55 @@ +package checklst.gui; + +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.layout.VBox; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; + +public class DialogBox extends HBox { + + protected Label name; + protected Label text; + protected ImageView displayPicture; + + protected DialogBox(String name, Label l, ImageView iv) { + this.text = l; + this.text.setFont(new Font("Arial", 14)); + this.displayPicture = iv; + this.name = new Label(name); + + this.text.setWrapText(true); + this.text.setPadding(new Insets(10)); + + this.displayPicture.setFitWidth(50.0); + this.displayPicture.setFitHeight(50.0); + DialogBox.setMargin(this.displayPicture, new Insets(21, 5, 3, 5)); + + this.name.setFont(Font.font("Arial", FontWeight.BOLD, 14)); + this.name.setPadding(new Insets(2)); + + VBox output = new VBox(this.name, this.text); + + this.setPadding(new Insets(5)); + + this.setAlignment(Pos.TOP_RIGHT); + this.getChildren().addAll(output, displayPicture); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + protected void flip() { + this.setAlignment(Pos.TOP_LEFT); + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + FXCollections.reverse(tmp); + this.getChildren().setAll(tmp); + } + +} diff --git a/src/main/java/checklst/gui/ErrorDialog.java b/src/main/java/checklst/gui/ErrorDialog.java new file mode 100644 index 0000000000..0ddb4a0a17 --- /dev/null +++ b/src/main/java/checklst/gui/ErrorDialog.java @@ -0,0 +1,30 @@ +package checklst.gui; + +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.paint.Color; + +public class ErrorDialog extends DialogBox { + + private static final String NAME = "Checklst - Error"; + + /** + * Creates an error dialog from the Checklst bot. + * @param l Error to be shown. + * @param iv Image of Checklst. + */ + public ErrorDialog(Label l, ImageView iv) { + super(NAME, l, iv); + + Color dialogColor = new Color(1, 0, 0, 1); + this.text.setBackground( + new Background(new BackgroundFill(dialogColor, new CornerRadii(7.0), Insets.EMPTY))); + + this.flip(); + } + +} diff --git a/src/main/java/checklst/gui/UserDialog.java b/src/main/java/checklst/gui/UserDialog.java new file mode 100644 index 0000000000..a5bf079bd9 --- /dev/null +++ b/src/main/java/checklst/gui/UserDialog.java @@ -0,0 +1,28 @@ +package checklst.gui; + +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.paint.Color; + +public class UserDialog extends DialogBox { + + private static final String NAME = "You"; + + /** + * Creates a message dialog from the user. + * @param l Message to be shown. + * @param iv Image of User. + */ + public UserDialog(Label l, ImageView iv) { + super(NAME, l, iv); + + Color dialogColor = new Color(1, 0.7, 0, 1); + this.text.setBackground( + new Background(new BackgroundFill(dialogColor, new CornerRadii(7.0), Insets.EMPTY))); + } + +} diff --git a/src/main/java/checklst/main/Checklst.java b/src/main/java/checklst/main/Checklst.java new file mode 100644 index 0000000000..90b11b633e --- /dev/null +++ b/src/main/java/checklst/main/Checklst.java @@ -0,0 +1,173 @@ +package checklst.main; + +import checklst.exception.ChecklstException; +import checklst.gui.ChecklstDialog; +import checklst.gui.DialogBox; +import checklst.gui.ErrorDialog; +import checklst.gui.UserDialog; +import checklst.parser.Parser; +import checklst.storage.Storage; +import checklst.task.TaskList; +import checklst.ui.Ui; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.geometry.Insets; +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.scene.text.Font; +import javafx.stage.Stage; + +/** + * The Checklst Class represents the entry point of the Checklst Program. + */ +public class Checklst extends Application { + + private static final Font STANDARD_FONT = new Font("Arial", 14); + + private final Ui ui = new Ui(); + private final TaskList taskList = new TaskList(); + private final Parser parser = new Parser(this.taskList); + private final Storage storage = new Storage(); + + private ScrollPane scrollPane; + private VBox dialogContainer; + private TextField userInput; + private Button sendButton; + private Scene scene; + private Image userIcon = new Image(this.getClass().getResourceAsStream("/images/UserIcon.png")); + private Image checklstIcon = new Image(this.getClass().getResourceAsStream("/images/ChecklstIcon.png")); + + @Override + public void start(Stage stage) { + + //Step 1. Formatting the window to look as expected. + scrollPane = new ScrollPane(); + dialogContainer = new VBox(); + scrollPane.setContent(dialogContainer); + + this.userInput = new TextField(); + this.userInput.setFont(STANDARD_FONT); + this.sendButton = new Button("Send"); + this.sendButton.setFont(STANDARD_FONT); + + AnchorPane mainLayout = new AnchorPane(); + mainLayout.getChildren().addAll(scrollPane, userInput, sendButton); + + scene = new Scene(mainLayout); + + stage.setScene(scene); + stage.show(); + + //Step 2. Formatting the window to look as expected + stage.setTitle("Checklst"); + stage.setResizable(false); + stage.setMinHeight(600.0); + stage.setMinWidth(400.0); + + mainLayout.setPrefSize(400.0, 600.0); + + this.scrollPane.setPrefSize(385, 535); + this.scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + this.scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); + + this.scrollPane.setVvalue(1.0); + this.scrollPane.setFitToWidth(true); + this.scrollPane.setStyle("-fx-background-color: transparent; -fx-background: rgb(207, 232, 243);"); + + this.dialogContainer.setPrefHeight(Region.USE_COMPUTED_SIZE); + + this.userInput.setPrefWidth(325.0); + this.userInput.setPrefHeight(32.0); + this.sendButton.setPrefWidth(70.0); + this.sendButton.setPrefHeight(32.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); + + // Step 3. Add functionality to handle user input. + this.sendButton.setOnMouseClicked((event) -> { + this.handleUserInput(); + }); + + this.userInput.setOnAction((event) -> { + this.handleUserInput(); + }); + + //Scroll down to the end every time dialogContainer's height changes. + this.dialogContainer.heightProperty().addListener((observable) -> scrollPane.setVvalue(1.0)); + + // Get History + try { + this.storage.parseHistory(this.taskList); + this.addChecklstMessage("History successfully parsed!"); + } catch (ChecklstException e) { + this.addErrorMessage(e.getMessage()); + } + + this.addChecklstMessage(this.ui.sendWelcome()); + + } + + /** + * Iteration 2: + * 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. + */ + private void handleUserInput() { + if (userInput.getText().equals("bye")) { + try { + Platform.exit(); + } catch (Exception e) { + // Do nothing + } + } + + this.addUserMessage(userInput.getText()); + + String response = ""; + try { + response = this.parser.parse(userInput.getText().split(" ", 2)); + this.addChecklstMessage(response); + } catch (ChecklstException e) { + this.addErrorMessage(e.getMessage()); + } + + userInput.clear(); + } + + private void addChecklstMessage(String input) { + DialogBox newDialogBox = new ChecklstDialog(new Label(input), new ImageView(checklstIcon)); + this.dialogContainer.getChildren().add(newDialogBox); + VBox.setMargin(newDialogBox, new Insets(3)); + } + + private void addUserMessage(String input) { + DialogBox newDialogBox = new UserDialog(new Label(input), new ImageView(userIcon)); + this.dialogContainer.getChildren().add(newDialogBox); + VBox.setMargin(newDialogBox, new Insets(3)); + } + + private void addErrorMessage(String input) { + DialogBox newDialogBox = new ErrorDialog(new Label(input), new ImageView(checklstIcon)); + this.dialogContainer.getChildren().add(newDialogBox); + VBox.setMargin(newDialogBox, new Insets(3)); + } + + @Override + public void stop() throws Exception { + this.storage.saveToFile(this.taskList); + } + +} diff --git a/src/main/java/checklst/main/Loader.java b/src/main/java/checklst/main/Loader.java new file mode 100644 index 0000000000..4f7a598357 --- /dev/null +++ b/src/main/java/checklst/main/Loader.java @@ -0,0 +1,14 @@ +package checklst.main; + +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class Loader { + + public static void main(String[] args) { + Application.launch(Checklst.class, args); + } + +} diff --git a/src/main/java/checklst/parser/CheckedFunction.java b/src/main/java/checklst/parser/CheckedFunction.java new file mode 100644 index 0000000000..d9325ebf57 --- /dev/null +++ b/src/main/java/checklst/parser/CheckedFunction.java @@ -0,0 +1,8 @@ +package checklst.parser; + +import checklst.exception.ChecklstException; + +@FunctionalInterface +public interface CheckedFunction { + R apply(T t) throws ChecklstException; +} diff --git a/src/main/java/checklst/parser/MethodPair.java b/src/main/java/checklst/parser/MethodPair.java new file mode 100644 index 0000000000..e8df995681 --- /dev/null +++ b/src/main/java/checklst/parser/MethodPair.java @@ -0,0 +1,26 @@ +package checklst.parser; + +public class MethodPair { + + private final String description; + private final CheckedFunction method; + + /** + * Creates a new Method Pair of Description and Function to be run. + * @param description Description of method. + * @param method Functional Interface method to be run on call. + */ + public MethodPair(String description, CheckedFunction method) { + this.description = description; + this.method = method; + } + + public String getDescription() { + return this.description; + } + + public CheckedFunction getMethod() { + return this.method; + } + +} diff --git a/src/main/java/checklst/parser/Parser.java b/src/main/java/checklst/parser/Parser.java new file mode 100644 index 0000000000..405fe24603 --- /dev/null +++ b/src/main/java/checklst/parser/Parser.java @@ -0,0 +1,106 @@ +package checklst.parser; + +import java.util.Map; +import java.util.stream.Collectors; + +import checklst.exception.ChecklstException; +import checklst.task.Deadline; +import checklst.task.Event; +import checklst.task.Task; +import checklst.task.TaskList; +import checklst.task.Todo; + +/** + * The Parser class makes sense of inputs and runs the respective follow up methods. + */ +public class Parser { + + private final TaskList taskList; + private final Map methodMap = Map.of( + "list", new MethodPair("Lists all Tasks", x -> list(x)), + "todo", new MethodPair("Creates a Todo", x -> todo(x)), + "event", new MethodPair("Creates an Event", x -> event(x)), + "deadline", new MethodPair("Creates a Deadline", x -> deadline(x)), + "done", new MethodPair("Completes a Task", x -> done(x)), + "delete", new MethodPair("Deletes a Task", x -> delete(x)), + "find", new MethodPair("Finds a Task", x -> find(x)), + "sort", new MethodPair("Sorts Taks", x -> sort(x)), + "help", new MethodPair("Get all Commands", x -> help(x)), + "bye", new MethodPair("Exits Checklst", x -> null) + ); + + public Parser(TaskList taskList) { + this.taskList = taskList; + } + + /** + * Parses the input string and returns the corresponding command. + * @param input Input string to be parsed. + * @return String representing the output. + * @throws ChecklstException Exception when command is invalid. + */ + public String parse(String[] input) throws ChecklstException { + MethodPair method = methodMap.get(input[0]); + + if (method == null) { + throw new ChecklstException("Sorry I didn't understand that command!"); + } + + return method.getMethod().apply(input); + } + + private String list(String[] input) { + return this.taskList.toString(); + } + + private String todo(String[] input) throws ChecklstException { + assert input.length >= 1 : "No Todo Input"; + Task newTodo = Todo.makeTodo(input[1]); + return this.taskList.add(newTodo); + } + + private String event(String[] input) throws ChecklstException { + assert input.length >= 1 : "No Event Input"; + Task newEvent = Event.makeEvent(input[1]); + return this.taskList.add(newEvent); + } + + private String deadline(String[] input) throws ChecklstException { + assert input.length >= 1 : "No Deadline Input"; + Task newDeadline = Deadline.makeDeadline(input[1]); + return this.taskList.add(newDeadline); + } + + private String done(String[] input) throws ChecklstException { + assert input.length >= 1 : "No Done Input"; + int doneIndex = Integer.parseInt(input[1]); + return "Nice! I've marked this task as done!\n" + this.taskList.completeTask(doneIndex); + } + + private String delete(String[] input) throws ChecklstException { + assert input.length >= 1 : "No Delete Input"; + int deleteIndex = Integer.parseInt(input[1]); + return "Alright! I've deleted this task!\n" + this.taskList.deleteTask(deleteIndex); + } + + private String find(String[] input) throws ChecklstException { + assert input.length >= 1 : "No Find Input"; + return "Here are the matching tasks in your list!\n" + this.taskList.findTask(input[1]); + } + + private String sort(String[] input) { + return this.taskList.sort(); + } + + private String help(String[] input) { + return "These are the Commands Available:\n" + + this.methodMap.entrySet().stream() + .map(x -> x.getKey() + ": " + x.getValue().getDescription()) + .collect(Collectors.toList()) + .toString() + .replace("[", "") + .replace("]", "") + .replace(", ", "\n"); + } + +} diff --git a/src/main/java/checklst/storage/Storage.java b/src/main/java/checklst/storage/Storage.java new file mode 100644 index 0000000000..92f40359bb --- /dev/null +++ b/src/main/java/checklst/storage/Storage.java @@ -0,0 +1,81 @@ +package checklst.storage; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import checklst.exception.ChecklstException; +import checklst.task.Deadline; +import checklst.task.Event; +import checklst.task.TaskList; +import checklst.task.Todo; + +/** + * The Storage class stores and processes the saved commands. + */ +public class Storage { + + private List exportList = new ArrayList<>(); + + /** + * Saves the command history to the save file. + * @throws ChecklstException Exception when unable to save or write to file. + */ + public void saveToFile(TaskList taskList) throws ChecklstException { + File history = new File("./data/checklst.txt"); + history.getParentFile().mkdirs(); + + assert history.canWrite() : "Can't Write to file!"; + + try { + history.createNewFile(); + } catch (IOException | SecurityException e) { + throw new ChecklstException("Unable to make file!"); + } + + try (PrintStream out = new PrintStream(new FileOutputStream("./data/checklst.txt"))) { + exportList = taskList.getTaskList().stream().map(x -> x.export()).collect(Collectors.toList()); + out.print(this.exportList.toString().replace("[", "").replace("]", "").replace(", ", "\n")); + } catch (FileNotFoundException e) { + throw new ChecklstException("File not found! Please create a file at path ./data/checklst.txt"); + } + } + + /** + * Processes history commands and run the relevant Task parser. + * @param taskList TaskList to add Tasks to. + * @throws ChecklstException Exception if strings are corrupted. + */ + public void parseHistory(TaskList taskList) throws ChecklstException { + try { + String[] history = Files.readString(Paths.get("./data/checklst.txt")).split("\n"); + for (String task: history) { + if (task.isBlank()) { + continue; + } + + if (task.charAt(0) == 'T') { + taskList.add(Todo.parseTodo(task)); + } else if (task.charAt(0) == 'D') { + taskList.add(Deadline.parseDeadline(task)); + } else if (task.charAt(0) == 'E') { + taskList.add(Event.parseEvent(task)); + } else { + throw new ChecklstException("History corrupted!"); + } + } + } catch (InvalidPathException | IOException e) { + throw new ChecklstException("No history found... Initializing from blank state!"); + } + + } + +} diff --git a/src/main/java/checklst/task/Deadline.java b/src/main/java/checklst/task/Deadline.java new file mode 100644 index 0000000000..860f6acbeb --- /dev/null +++ b/src/main/java/checklst/task/Deadline.java @@ -0,0 +1,92 @@ +package checklst.task; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import checklst.exception.ChecklstException; + +/** + * The Deadline class represents a Task with a specific Deadline. + */ +public class Deadline extends Task { + + protected final LocalDate dueDate; + + protected Deadline(String name, LocalDate dueDate) { + super(name); + this.dueDate = dueDate; + } + + protected Deadline(String name, boolean completed, LocalDate dueDate) { + super(name, completed); + this.dueDate = dueDate; + } + + /** + * Creates a new Deadline object. + * @param input Input for the Deadline in the form "{ name } /by { date }". + * @return New Deadline object. + */ + public static Deadline makeDeadline(String input) throws ChecklstException { + String[] splitInput = input.split(" /by "); + if (splitInput.length == 1) { + throw new ChecklstException("Inproper Deadline format used! Please use { name } /by { deadline }"); + } else if (splitInput[0].equals("")) { + throw new ChecklstException("Deadline needs a name!"); + } else if (splitInput[0].contains(";") || splitInput[1].contains(";")) { + throw new ChecklstException("Sorry, Deadline input cannot contain a \";\"!"); + } + + + LocalDate dueDate; + try { + dueDate = LocalDate.parse(splitInput[1]); + } catch (DateTimeParseException e) { + throw new ChecklstException("Incorrect DateTime format! Please use YYYY-MM-DD"); + } + + return new Deadline(splitInput[0], dueDate); + } + + /** + * Parses a saved Deadline string. + * @param input Deadline string. + * @return Deadline object. + */ + public static Deadline parseDeadline(String input) { + String[] splitInput = input.split(" ; "); + String name = splitInput[2]; + boolean completed = splitInput[1].equals("X") ? true : false; + LocalDate dueDate = LocalDate.parse(splitInput[3]); + + return new Deadline(name, completed, dueDate); + } + + @Override + public String export() { + String output = "D ; "; + + if (this.isCompleted) { + output += "X ; "; + } else { + output += "O ; "; + } + + output += this.name + " ; " + this.dueDate.toString(); + + return output; + } + + @Override + public Task complete() { + return new Deadline(this.name, true, this.dueDate); + } + + @Override + public String toString() { + return "[D]" + super.toString() + " (by: " + + this.dueDate.format(DateTimeFormatter.ofPattern("MMM dd yyyy")) + ")"; + } + +} diff --git a/src/main/java/checklst/task/Event.java b/src/main/java/checklst/task/Event.java new file mode 100644 index 0000000000..41f7274473 --- /dev/null +++ b/src/main/java/checklst/task/Event.java @@ -0,0 +1,91 @@ +package checklst.task; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +import checklst.exception.ChecklstException; + +/** + * The Event class represents a Task which has a specific DateTime. + */ +public class Event extends Task { + + protected final LocalDate eventDate; + + protected Event(String name, LocalDate eventDate) { + super(name); + this.eventDate = eventDate; + } + + protected Event(String name, boolean completed, LocalDate eventDate) { + super(name, completed); + this.eventDate = eventDate; + } + + /** + * Creates a new Event object. + * @param input Input for the Event in the form "{ name } /at { date }". + * @return New Event object. + */ + public static Event makeEvent(String input) throws ChecklstException { + String[] splitInput = input.split(" /at "); + if (splitInput.length == 1) { + throw new ChecklstException("Inproper Event format used! Please use { name } /at { event }"); + } else if (splitInput[0].equals("")) { + throw new ChecklstException("Event needs a name!"); + } else if (splitInput[0].contains(";") || splitInput[1].contains(";")) { + throw new ChecklstException("Sorry, Event input cannot contain a \";\"!"); + } + + LocalDate eventDate; + try { + eventDate = LocalDate.parse(splitInput[1]); + } catch (DateTimeParseException e) { + throw new ChecklstException("Incorrect Event Date format! Please use YYYY-MM-DD"); + } + + return new Event(splitInput[0], eventDate); + } + + /** + * Parses a saved Event string. + * @param input Event string. + * @return Event object. + */ + public static Event parseEvent(String input) { + String[] splitInput = input.split(" ; "); + String name = splitInput[2]; + boolean completed = splitInput[1].equals("X") ? true : false; + LocalDate dateTime = LocalDate.parse(splitInput[3]); + + return new Event(name, completed, dateTime); + } + + @Override + public String export() { + String output = "E ; "; + + if (this.isCompleted) { + output += "X ; "; + } else { + output += "O ; "; + } + + output += this.name + " ; " + this.eventDate.toString(); + + return output; + } + + @Override + public Task complete() { + return new Event(this.name, true, this.eventDate); + } + + @Override + public String toString() { + return "[E]" + super.toString() + " (at: " + + this.eventDate.format(DateTimeFormatter.ofPattern("MMM dd yyyy")) + ")"; + } + +} diff --git a/src/main/java/checklst/task/Task.java b/src/main/java/checklst/task/Task.java new file mode 100644 index 0000000000..25e502967c --- /dev/null +++ b/src/main/java/checklst/task/Task.java @@ -0,0 +1,40 @@ +package checklst.task; + +/** + * The Task class is an abstract skeleton that all Tasks (Todo, Event, Deadline) inherit from. + */ +public abstract class Task { + + protected final String name; + protected final boolean isCompleted; + + protected Task(String name) { + this.name = name; + this.isCompleted = false; + } + + protected Task(String name, boolean isCompleted) { + this.name = name; + this.isCompleted = isCompleted; + } + + /** + * Returns a String representation of the Task for exporting. + * Format: "Type | Completed | Name | Optional: Date" + * @return String representation of Task. + */ + public abstract String export(); + + /** + * Returns a new Task object which has been completed. + * @return New completed Task. + */ + public abstract Task complete(); + + @Override + public String toString() { + String isCompleted = this.isCompleted ? "[X]" : "[ ]"; + return isCompleted + " " + this.name; + } + +} diff --git a/src/main/java/checklst/task/TaskList.java b/src/main/java/checklst/task/TaskList.java new file mode 100644 index 0000000000..30813a041a --- /dev/null +++ b/src/main/java/checklst/task/TaskList.java @@ -0,0 +1,144 @@ +package checklst.task; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import checklst.exception.ChecklstException; + +/** + * The TaskList class wraps a list of Tasks and has various methods to process them. + */ +public class TaskList { + + private final List taskList; + + /** + * Creates a new TaskList object. + */ + public TaskList() { + this.taskList = new ArrayList<>(); + } + + private TaskList(List taskList) { + this.taskList = taskList; + } + + /** + * Adds a Task to the TaskList. + * @param task The Task to be added. + * @return A String indicating the number of items in the TaskList. + * @throws CheckLstException Exception when duplicate unfinished Task. + */ + public String add(Task task) throws ChecklstException { + List listNames = this.taskList.stream() + .filter(x -> !x.isCompleted) + .filter(x -> x.getClass().equals(task.getClass())) + .map(x -> x.name) + .collect(Collectors.toList()); + + if (listNames.contains(task.name)) { + throw new ChecklstException("You have an uncompleted Task of a similar name and type already!"); + } + + this.taskList.add(task); + + return String.format("Added: %s\nYou now have %d task(s) in the list!", task, this.taskList.size()); + } + + /** + * Completes a Task in the TaskList. + * @param index The index (1-based) of the Task to be completed. + * @return The completed Task. + * @throws ChecklstException Exception thrown if index is out of bounds. + */ + public Task completeTask(int index) throws ChecklstException { + index--; + + if (index < 0 || index >= this.taskList.size()) { + throw new ChecklstException("The task index you have indicated does not exist!"); + } + + Task newTask = taskList.get(index).complete(); + this.taskList.set(index, newTask); + + return newTask; + } + + /** + * Deletes a Task from the TaskList based on index. + * @param index The index (1-based) of the Task to be deleted. + * @return The deleted Task. + * @throws ChecklstException Exception thrown if index is out of bounds. + */ + public Task deleteTask(int index) throws ChecklstException { + index--; + + if (index < 0 || index >= this.taskList.size()) { + throw new ChecklstException("The task index you have indicated does not exist!"); + } + + Task newTask = taskList.get(index); + this.taskList.remove(index); + + return newTask; + } + + /** + * Returns a TaskList with the filtered tasks. + * @param input String to filter by. + * @return Filtered TaskList + * @throws ChecklstException Exception when no items are found. + */ + public TaskList findTask(String input) throws ChecklstException { + List filteredList = this.taskList.stream() + .filter(x -> x.name.contains(input)) + .collect(Collectors.toList()); + + if (filteredList.size() == 0) { + throw new ChecklstException("No results found!!"); + } + + return new TaskList(filteredList); + } + + /** + * Sorts the TaskList by completed and Task Type + * @return String of new Sorted List + */ + public String sort() { + Map, Integer> sortMap = + Map.of(Todo.class, 0, Deadline.class, 1, Event.class, 2); + + this.taskList.sort((x, y) -> { + if (x.isCompleted && y.isCompleted) { + return sortMap.get(x.getClass()).compareTo(sortMap.get(y.getClass())); + } + + return x.isCompleted ? 1 : -1; + }); + + return "Your list is now sorted!\nYour new list is:\n" + this; + } + + @Override + public String toString() { + if (this.taskList.size() == 0) { + return "Task list is currently empty!"; + } + + String taskListOutput = "These are your current task"; + taskListOutput += this.taskList.size() == 1 ? ":" : "s:"; + + for (int i = 0; i < this.taskList.size(); i++) { + taskListOutput += "\n" + String.valueOf(i + 1) + ". " + this.taskList.get(i); + } + return taskListOutput; + } + + public List getTaskList() { + return this.taskList; + } + +} diff --git a/src/main/java/checklst/task/Todo.java b/src/main/java/checklst/task/Todo.java new file mode 100644 index 0000000000..11100343a4 --- /dev/null +++ b/src/main/java/checklst/task/Todo.java @@ -0,0 +1,70 @@ +package checklst.task; + +import checklst.exception.ChecklstException; + +/** + * The Deadline class represents a basic Task. + */ +public class Todo extends Task { + + protected Todo(String name) { + super(name); + } + + protected Todo(String name, boolean completed) { + super(name, completed); + } + + /** + * Creates a Todo Object. + * @param name - Name of Todo. + * @return - Todo Object. + */ + public static Todo makeTodo(String name) throws ChecklstException { + if (name.equals("")) { + throw new ChecklstException("Todo needs a name!"); + } else if (name.contains(";")) { + throw new ChecklstException("Sorry, Todo name cannot contain a \";\"!"); + } + return new Todo(name); + } + + /** + * Parses a saved Todo String. + * @param input Todo String. + * @return Todo Object. + */ + public static Todo parseTodo(String input) { + String[] splitInput = input.split(" ; "); + String name = splitInput[2]; + boolean completed = splitInput[1].equals("X") ? true : false; + + return new Todo(name, completed); + } + + @Override + public String export() { + String output = "T ; "; + + if (this.isCompleted) { + output += "X ; "; + } else { + output += "O ; "; + } + + output += this.name; + + return output; + } + + @Override + public Task complete() { + return new Todo(this.name, true); + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } + +} diff --git a/src/main/java/checklst/ui/Ui.java b/src/main/java/checklst/ui/Ui.java new file mode 100644 index 0000000000..8c30ac2a23 --- /dev/null +++ b/src/main/java/checklst/ui/Ui.java @@ -0,0 +1,19 @@ +package checklst.ui; + +/** + * The Ui class handles direct I/O from the user. + */ +public class Ui { + + private static final String WELCOME_MESSAGE = "Hello I'm Checklst! What can I do for you?"; + + /** + * Prints Welcome output. + * @return Welcome output. + */ + public String sendWelcome() { + return WELCOME_MESSAGE; + } + + +} diff --git a/src/main/resources/images/ChecklstIcon.png b/src/main/resources/images/ChecklstIcon.png new file mode 100644 index 0000000000..aa34f0df60 Binary files /dev/null and b/src/main/resources/images/ChecklstIcon.png differ diff --git a/src/main/resources/images/UserIcon.png b/src/main/resources/images/UserIcon.png new file mode 100644 index 0000000000..9b42c9d1ec Binary files /dev/null and b/src/main/resources/images/UserIcon.png differ diff --git a/src/test/java/checklst/task/DeadlineTest.java b/src/test/java/checklst/task/DeadlineTest.java new file mode 100644 index 0000000000..eadeb8eaf4 --- /dev/null +++ b/src/test/java/checklst/task/DeadlineTest.java @@ -0,0 +1,21 @@ +package checklst.task; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import checklst.exception.ChecklstException; + +public class DeadlineTest { + + @Test + public void createDeadlineTest() { + assertThrows(ChecklstException.class, () -> Deadline.makeDeadline("")); + assertThrows(ChecklstException.class, () -> Deadline.makeDeadline("test ")); + assertThrows(ChecklstException.class, () -> Deadline.makeDeadline("test /by")); + assertThrows(ChecklstException.class, () -> Deadline.makeDeadline("test /by ")); + assertThrows(ChecklstException.class, () -> Deadline.makeDeadline("test /by 23rd Feb")); + assertThrows(ChecklstException.class, () -> Deadline.makeDeadline("test/by2020-11-20")); + } + +} diff --git a/src/test/java/checklst/task/EventTest.java b/src/test/java/checklst/task/EventTest.java new file mode 100644 index 0000000000..3f7fa8f91e --- /dev/null +++ b/src/test/java/checklst/task/EventTest.java @@ -0,0 +1,21 @@ +package checklst.task; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import checklst.exception.ChecklstException; + +public class EventTest { + + @Test + public void createEventTest() { + assertThrows(ChecklstException.class, () -> Event.makeEvent("")); + assertThrows(ChecklstException.class, () -> Event.makeEvent("test")); + assertThrows(ChecklstException.class, () -> Event.makeEvent("test /at")); + assertThrows(ChecklstException.class, () -> Event.makeEvent("test /at ")); + assertThrows(ChecklstException.class, () -> Event.makeEvent("test /at 23rd Feb")); + assertThrows(ChecklstException.class, () -> Event.makeEvent("test/at2020-11-20")); + } + +} diff --git a/src/test/java/checklst/task/TaskListTest.java b/src/test/java/checklst/task/TaskListTest.java new file mode 100644 index 0000000000..63d7810eb5 --- /dev/null +++ b/src/test/java/checklst/task/TaskListTest.java @@ -0,0 +1,45 @@ +package checklst.task; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import checklst.exception.ChecklstException; + +public class TaskListTest { + + private final TaskList taskList = new TaskList(); + + @Test + public void addTaskTest() throws ChecklstException { + Task task = new Todo("Hello"); + this.taskList.add(task); + assertEquals(task, this.taskList.getTaskList().get(0)); + } + + @Test + public void completeTaskTest() throws ChecklstException { + assertThrows(ChecklstException.class, () -> this.taskList.completeTask(-1)); + assertThrows(ChecklstException.class, () -> this.taskList.completeTask(0)); + assertThrows(ChecklstException.class, () -> this.taskList.completeTask(1)); + } + + @Test + public void deleteTaskTest() throws ChecklstException { + assertThrows(ChecklstException.class, () -> this.taskList.deleteTask(-1)); + assertThrows(ChecklstException.class, () -> this.taskList.deleteTask(0)); + assertThrows(ChecklstException.class, () -> this.taskList.deleteTask(1)); + } + + @Test + public void findTaskTest() throws ChecklstException { + Task task = new Todo("hello"); + this.taskList.add(task); + + assertTrue(this.taskList.findTask("hello").getTaskList().size() > 0); + assertThrows(ChecklstException.class, () -> this.taskList.findTask("abc")); + } + +} diff --git a/src/test/java/checklst/task/TodoTest.java b/src/test/java/checklst/task/TodoTest.java new file mode 100644 index 0000000000..b4709d11b1 --- /dev/null +++ b/src/test/java/checklst/task/TodoTest.java @@ -0,0 +1,16 @@ +package checklst.task; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +import checklst.exception.ChecklstException; + +public class TodoTest { + + @Test + public void createTodoTest() { + assertThrows(ChecklstException.class, () -> Todo.makeTodo("")); + } + +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..d5ef1aa9db 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,61 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - + ---------------------------------------- + No history found... Initializing from blank state! + ---------------------------------------- + ---------------------------------------- + Hello I'm Checklst! What can I do for you? + ---------------------------------------- + ---------------------------------------- + Added: [T][] Todo1 + You now have 1 task(s) in the list! + ---------------------------------------- + ---------------------------------------- + Added: [E][] Event1 (at: May 12 2020) + You now have 2 task(s) in the list! + ---------------------------------------- + ---------------------------------------- + Added: [D][] Deadline1 (by: Jul 24 2020) + You now have 3 task(s) in the list! + ---------------------------------------- + ---------------------------------------- + Added: [T][] Todo2 + You now have 4 task(s) in the list! + ---------------------------------------- + ---------------------------------------- + 1. [T][] Todo1 + 2. [E][] Event1 (at: May 12 2020) + 3. [D][] Deadline1 (by: Jul 24 2020) + 4. [T][] Todo2 + ---------------------------------------- + ---------------------------------------- + Nice! I've marked this task as done! + [E][X] Event1 (at: May 12 2020) + ---------------------------------------- + ---------------------------------------- + Alright! I've deleted this task! + [E][X] Event1 (at: May 12 2020) + ---------------------------------------- + ---------------------------------------- + Nice! I've marked this task as done! + [T][X] Todo2 + ---------------------------------------- + ---------------------------------------- + Nice! I've marked this task as done! + [T][X] Todo1 + ---------------------------------------- + ---------------------------------------- + 1. [T][X] Todo1 + 2. [D][] Deadline1 (by: Jul 24 2020) + 3. [T][X] Todo2 + ---------------------------------------- + ---------------------------------------- + Inproper Event format used! Please use { name } /at { event } + ---------------------------------------- + ---------------------------------------- + Inproper Deadline format used! Please use { name } /by { deadline } + ---------------------------------------- + ---------------------------------------- + Sorry I didn't understand that command!! + ---------------------------------------- + ---------------------------------------- + Bye! Hope to see you again! + ---------------------------------------- diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..491f52e499 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,14 @@ +todo Todo1 +event Event1 /at 2020-05-12 +deadline Deadline1 /by 2020-07-24 +todo Todo2 +list +done 2 +delete 2 +done 3 +done 1 +list +event ErrorEvent +deadline ErrorDeadline +gibberish +bye \ No newline at end of file diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh old mode 100644 new mode 100755 index c9ec870033..179c820de1 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -20,7 +20,7 @@ then fi # run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ../bin Duke < input.txt > ACTUAL.TXT +java -classpath ../bin Checklst < input.txt > ACTUAL.TXT # convert to UNIX format cp EXPECTED.TXT EXPECTED-UNIX.TXT