diff --git a/.github/workflows/Gradle-CI.yml b/.github/workflows/Gradle-CI.yml new file mode 100644 index 00000000..f7a36b0d --- /dev/null +++ b/.github/workflows/Gradle-CI.yml @@ -0,0 +1,30 @@ +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + + workflow_dispatch: + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build diff --git a/.github/workflows/Text-UI-CI.yml b/.github/workflows/Text-UI-CI.yml new file mode 100644 index 00000000..b0e8939a --- /dev/null +++ b/.github/workflows/Text-UI-CI.yml @@ -0,0 +1,28 @@ +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: Java CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + - name: Grant execute permission to text-ui-test + run: chmod +x text-ui-test/runtest.sh + - name: Test with runtest.sh + working-directory: ./text-ui-test + run: ./runtest.sh diff --git a/.gitignore b/.gitignore index f69985ef..3f8bcafa 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT +state.txt +archive.txt \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..1b212c45 --- /dev/null +++ b/build.gradle @@ -0,0 +1,37 @@ +plugins { + id 'java' + id 'application' +} + +group 'com.alexooi.duke' +version '1.0' + +repositories { + mavenCentral() +} +sourceSets { + main { + java { + srcDirs = ['src/main/java'] + } + } +} +jar { + manifest { + attributes( + 'Main-Class': 'com.alexooi.duke.Duke' + ) + } +} +test { + useJUnitPlatform() +} +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2' +} +run { + standardInput = System.in +} +mainClassName = 'com.alexooi.duke.Duke' \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..7454180f 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 00000000..ffed3a25 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..1b6c7873 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..ac1b06f9 --- /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 00000000..4b97cac2 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'Duke-Alex' \ No newline at end of file diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334..00000000 --- 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/com/alexooi/duke/Duke.java b/src/main/java/com/alexooi/duke/Duke.java new file mode 100644 index 00000000..78de2e20 --- /dev/null +++ b/src/main/java/com/alexooi/duke/Duke.java @@ -0,0 +1,85 @@ +package com.alexooi.duke; + +import com.alexooi.duke.commands.Command; +import com.alexooi.duke.exceptions.InvalidFileFormatException; +import com.alexooi.duke.storage.FileStorage; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.ui.CommandLineParser; +import com.alexooi.duke.ui.OutputFormatter; +import com.alexooi.duke.exceptions.InvalidCommandException; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.Parser; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.Scanner; + +public class Duke { + private static Duke instance; + private final OutputFormat prompt; + private final Parser parser; + + private Duke(OutputFormat prompt, Parser parser) { + this.prompt = prompt; + this.parser = parser; + } + + public static Duke getInstance() { + if (instance == null) { + instance = new Duke(new OutputFormatter(), new CommandLineParser()); + } + + return instance; + } + + public static void main(String[] args) { + final String STATE_PATH = "state.txt"; + final String ARCHIVE_PATH = "archive.txt"; + + Duke main = Duke.getInstance(); + FileStorage state = new FileStorage(STATE_PATH); + FileStorage archive = new FileStorage(ARCHIVE_PATH); + + TaskList tasks = TaskList.getInstance(state, archive); + System.out.println(main.prompt.start()); + + // Load previous task list state + BufferedReader stateInput = state.load(); + try { + tasks.load(stateInput); + } catch (IOException ioe) { + main.prompt.error(ioe.getMessage()); + } catch (InvalidFileFormatException iffe) { + main.prompt.error(iffe.getErrorHeader(), iffe.getMessage()); + } + + Scanner in = new Scanner(System.in); + boolean isReceivingInput = true; + + while (isReceivingInput && in.hasNext()) { + try { + String input = in.nextLine(); + Command command = main.parser.parseInput(input); + if (command.isExit()) { + isReceivingInput = false; + } else { + String output = command.execute(tasks, main.prompt); + System.out.println(output); + } + } catch (InvalidCommandException ice) { + System.out.println(main.prompt.error(ice.getErrorHeader(), ice.getMessage())); + } catch (InvalidCommandFormatException icfe) { + System.out.println(main.prompt.error(icfe.getErrorHeader(), icfe.getMessage())); + } catch (NumberFormatException | IndexOutOfBoundsException ex) { + System.out.println(main.prompt.error(ex.getMessage())); + } catch (Exception ex) { + ex.printStackTrace(); + } + tasks.save(); + } + + System.out.println(main.prompt.exit()); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/AddCommand.java b/src/main/java/com/alexooi/duke/commands/AddCommand.java new file mode 100644 index 00000000..7710de20 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/AddCommand.java @@ -0,0 +1,42 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.enums.CommandType; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskFactory; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.util.regex.Pattern; + +public class AddCommand extends Command { + private final String REGEX_PATTERN = "^(\\bdeadline\\b|\\bevent\\b|\\btodo\\b)\\s?(.*)$"; + + public AddCommand() { + super(); + setCommandType(CommandType.ADD); + } + + public AddCommand(String keyword, String args) { + super(keyword, args); + setCommandType(CommandType.ADD); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public AddCommand build() { + return new AddCommand(getKeyword(), getArgs()); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + Task newTask = TaskFactory.getInstance(this); + tasks.add(newTask); + + return prompt.add(newTask, tasks.size()); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java b/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java new file mode 100644 index 00000000..d0fbe503 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/ArchiveCommand.java @@ -0,0 +1,52 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.enums.CommandType; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; + +import java.util.regex.Pattern; + +public class ArchiveCommand extends Command { + private final String REGEX_PATTERN = "^(\\barchive\\b)\\s+(\\d+|all)$"; + private final String ARGS_ALL = "all"; + + public ArchiveCommand() { + setCommandType(CommandType.ARCHIVE); + } + + public ArchiveCommand(String keyword, String args) { + super(keyword, args); + setCommandType(CommandType.ARCHIVE); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + String output; + if (getArgs().equalsIgnoreCase(ARGS_ALL)) { + tasks.archive(); + output = prompt.archive(tasks.get()); + tasks.removeAll(); + } else { + int idx = Integer.parseUnsignedInt(getArgs()) - 1; + if (idx >= tasks.size()) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_NO_SUCH_INDEX); + } + tasks.archive(idx); + Task archived = tasks.remove(idx); + output = prompt.archive(archived); + } + return output; + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public Command build() { + return new ArchiveCommand(getKeyword(), getArgs()); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/Command.java b/src/main/java/com/alexooi/duke/commands/Command.java new file mode 100644 index 00000000..86a24fc9 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/Command.java @@ -0,0 +1,72 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.enums.CommandType; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public abstract class Command { + private String keyword; + private String args; + private CommandType commandType; + + public Command() { + + } + + public Command(String keyword) { + this.setKeyword(keyword); + } + + public Command(String keyword, String args) { + this(keyword); + this.setArgs(args); + } + + public String getKeyword() { + return keyword; + } + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + + public String getArgs() { + return args; + } + + public void setArgs(String args) { + this.args = args; + } + + public CommandType getCommandType() { + return commandType; + } + + public void setCommandType(CommandType commandType) { + this.commandType = commandType; + } + + public Iterable getCommandFormat() { + ArrayList commandFormat = new ArrayList<>(); + commandFormat.add(getCommandType().getFormat()); + if (getCommandType().getNotes() != null) { + commandFormat.add(getCommandType().getNotes()); + } + return commandFormat; + } + + public abstract String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException; + + public abstract Pattern getRegexPattern(); + + public abstract Command build() throws InvalidCommandFormatException; + + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/com/alexooi/duke/commands/CompleteCommand.java b/src/main/java/com/alexooi/duke/commands/CompleteCommand.java new file mode 100644 index 00000000..d74d2b89 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/CompleteCommand.java @@ -0,0 +1,43 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.enums.CommandType; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.util.regex.Pattern; + +public class CompleteCommand extends Command { + private final String REGEX_PATTERN = "^\\b(done)\\b (.+)$"; + + public CompleteCommand() { + setCommandType(CommandType.COMPLETE); + } + + public CompleteCommand(String keyword, String args) { + super(keyword, args); + setCommandType(CommandType.COMPLETE); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public CompleteCommand build() { + return new CompleteCommand(getKeyword(), getArgs()); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + int idx = Integer.parseInt(getArgs()) - 1; + if (idx > tasks.size() - 1) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_NO_SUCH_INDEX); + } + Task doneTask = tasks.getTask(idx); + doneTask.setDone(!doneTask.getDone()); + return prompt.done(doneTask, doneTask.getDone()); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/DeleteCommand.java b/src/main/java/com/alexooi/duke/commands/DeleteCommand.java new file mode 100644 index 00000000..79932d63 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/DeleteCommand.java @@ -0,0 +1,42 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.enums.CommandType; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.util.regex.Pattern; + +public class DeleteCommand extends Command { + private final String REGEX_PATTERN = "^\\b(delete)\\b (.+)$"; + + public DeleteCommand() { + setCommandType(CommandType.REMOVE); + } + + public DeleteCommand(String keyword, String args) { + super(keyword, args); + setCommandType(CommandType.REMOVE); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public DeleteCommand build() { + return new DeleteCommand(getKeyword(), getArgs()); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + int idx = Integer.parseInt(getArgs()) - 1; + if (idx > tasks.size() - 1) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_NO_SUCH_INDEX); + } + Task removedTask = tasks.remove(idx); + return prompt.remove(removedTask, tasks.size()); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/ExitCommand.java b/src/main/java/com/alexooi/duke/commands/ExitCommand.java new file mode 100644 index 00000000..844b4dbc --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/ExitCommand.java @@ -0,0 +1,41 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.enums.CommandType; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.util.regex.Pattern; + +public class ExitCommand extends Command { + private final String REGEX_PATTERN = "^(bye)$"; + + public ExitCommand() { + setCommandType(CommandType.EXIT); + } + + public ExitCommand(String keyword) { + super(keyword); + setCommandType(CommandType.EXIT); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public ExitCommand build() { + return new ExitCommand(getKeyword()); + } + + @Override + public boolean isExit() { + return true; + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) { + return prompt.exit(); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/FindCommand.java b/src/main/java/com/alexooi/duke/commands/FindCommand.java new file mode 100644 index 00000000..f4337b33 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/FindCommand.java @@ -0,0 +1,43 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.enums.CommandType; +import com.alexooi.duke.interfaces.OutputFormat; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class FindCommand extends Command { + private final String REGEX_PATTERN = "^(\\bfind\\b) (.*)$"; + + public FindCommand() { + setCommandType(CommandType.FIND); + } + + public FindCommand(String keyword, String args) { + super(keyword, args); + setCommandType(CommandType.FIND); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) { + ArrayList taskList = tasks.get(); + + List filteredList = taskList.stream().filter((task) -> task.getDescription().contains(getArgs())).collect(Collectors.toList()); + + return prompt.find(filteredList); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public Command build() { + return new FindCommand(getKeyword(), getArgs()); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/HelpCommand.java b/src/main/java/com/alexooi/duke/commands/HelpCommand.java new file mode 100644 index 00000000..53339ad1 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/HelpCommand.java @@ -0,0 +1,45 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.enums.CommandType; +import com.alexooi.duke.interfaces.OutputFormat; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; + +import java.util.ArrayList; +import java.util.ServiceLoader; +import java.util.regex.Pattern; + +public class HelpCommand extends Command { + private final String REGEX_PATTERN = "^(help)$"; + + public HelpCommand() { + setCommandType(CommandType.HELP); + } + + public HelpCommand(String keyword) { + super(keyword); + setCommandType(CommandType.HELP); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) { + ServiceLoader commandLoader = ServiceLoader.load(Command.class); + ArrayList promptInput = new ArrayList<>(); + for (Command cmd : commandLoader) { + ArrayList commandFormat = new ArrayList<>(); + cmd.getCommandFormat().forEach(commandFormat::add); + promptInput.add(String.join(" : ", commandFormat)); + } + return prompt.help(promptInput); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public Command build() { + return new HelpCommand(getKeyword()); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/ListCommand.java b/src/main/java/com/alexooi/duke/commands/ListCommand.java new file mode 100644 index 00000000..94c254a4 --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/ListCommand.java @@ -0,0 +1,36 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.enums.CommandType; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.util.regex.Pattern; + +public class ListCommand extends Command { + private final String REGEX_PATTERN = "^(list)$"; + + public ListCommand() { + setCommandType(CommandType.LIST); + } + + public ListCommand(String keyword) { + super(keyword); + setCommandType(CommandType.LIST); + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public ListCommand build() { + return new ListCommand(getKeyword()); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) { + return prompt.list(tasks.get()); + } +} diff --git a/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java b/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java new file mode 100644 index 00000000..21c90bdb --- /dev/null +++ b/src/main/java/com/alexooi/duke/commands/ViewScheduleCommand.java @@ -0,0 +1,77 @@ +package com.alexooi.duke.commands; + +import com.alexooi.duke.enums.CommandType; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.OutputFormat; +import com.alexooi.duke.interfaces.Parser; +import com.alexooi.duke.tasks.Deadline; +import com.alexooi.duke.tasks.Event; +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.tasks.TaskList; +import com.alexooi.duke.utility.DateParser; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class ViewScheduleCommand extends Command { + private final String REGEX_PATTERN = "^(\\bview\\b) (.*)$"; + final Parser parser; + + public ViewScheduleCommand() { + setCommandType(CommandType.VIEW_SCHEDULE); + this.parser = new DateParser(); + } + + public ViewScheduleCommand(String keyword, String args) { + super(keyword, args); + setCommandType(CommandType.VIEW_SCHEDULE); + this.parser = new DateParser(); + } + + @Override + public String execute(TaskList tasks, OutputFormat prompt) throws InvalidCommandFormatException { + ArrayList taskList = tasks.get(); + try { + LocalDateTime dateTime = parser.parseInput(getArgs()); + + List filteredList = taskList.stream().filter((element) -> { + boolean isScheduled = false; + switch (element.getType()) { + case EVENT: + Event event = (Event) element; + if (event.getTiming().toLocalDate().equals(dateTime.toLocalDate())) { + isScheduled = true; + } + break; + case DEADLINE: + Deadline deadline = (Deadline) element; + if (deadline.getDueDate().toLocalDate().equals(dateTime.toLocalDate())) { + isScheduled = true; + } + break; + } + return isScheduled; + }).collect(Collectors.toList()); + + return prompt.viewSchedule(filteredList, dateTime.toLocalDate()); + } catch (Exception e) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_VIEW_SCHEDULE); + } + } + + @Override + public Pattern getRegexPattern() { + return Pattern.compile(REGEX_PATTERN, Pattern.CASE_INSENSITIVE); + } + + @Override + public ViewScheduleCommand build() throws InvalidCommandFormatException { + if (!parser.isValidInput(getArgs())) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_VIEW_SCHEDULE); + } + return new ViewScheduleCommand(getKeyword(), getArgs()); + } +} diff --git a/src/main/java/com/alexooi/duke/enums/CommandType.java b/src/main/java/com/alexooi/duke/enums/CommandType.java new file mode 100644 index 00000000..4467fd2e --- /dev/null +++ b/src/main/java/com/alexooi/duke/enums/CommandType.java @@ -0,0 +1,41 @@ +package com.alexooi.duke.enums; + +public enum CommandType { + ADD("todo \n deadline /by \n event /at ", " must be in yyyy-mm-dd or yyyy-mm-ddThh:mm format."), + REMOVE("delete ", " must be a number that currently exists in the list."), + COMPLETE("done ", " must be a number that currently exists in the list."), + ARCHIVE("archive /all", " must be a number that currently exists in the list."), + VIEW_SCHEDULE("view ", " must be in yyyy-mm-dd."), + LIST("list"), + FIND("find "), + EXIT("bye"), + HELP("help"); + + private String format; + private String notes; + + CommandType(String format, String notes) { + setFormat(format); + setNotes(notes); + } + + CommandType(String format) { + setFormat(format); + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } +} diff --git a/src/main/java/com/alexooi/duke/enums/TaskType.java b/src/main/java/com/alexooi/duke/enums/TaskType.java new file mode 100644 index 00000000..4924bb0e --- /dev/null +++ b/src/main/java/com/alexooi/duke/enums/TaskType.java @@ -0,0 +1,25 @@ +package com.alexooi.duke.enums; + +public enum TaskType { + TODO("todo"), + EVENT("event"), + DEADLINE("deadline"); + + private String type; + + TaskType(String type) { + setType(type); + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String toString() { + return getType(); + } +} diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandException.java new file mode 100644 index 00000000..f67ffb9c --- /dev/null +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandException.java @@ -0,0 +1,14 @@ +package com.alexooi.duke.exceptions; + +public class InvalidCommandException extends Exception { + private static final String ERROR_PREFIX = "Invalid Command:"; + public static final String ERROR_GENERIC = "I don't know what that command means!"; + + public InvalidCommandException(String errorMessage) { + super(errorMessage); + } + + public String getErrorHeader() { + return ERROR_PREFIX; + } +} diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java new file mode 100644 index 00000000..7e0312bd --- /dev/null +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidCommandFormatException.java @@ -0,0 +1,27 @@ +package com.alexooi.duke.exceptions; + +public class InvalidCommandFormatException extends Exception { + private static final String ERROR_PREFIX = "Invalid Command Format:"; + public static final String ERROR_DEADLINE = + "Deadline should be in the format: deadline /by where is in the format of yyyy-mm-dd."; + public static final String ERROR_EVENT = + "Event should be in the format: event /at where is in the format of yyyy-mm-dd."; + public static final String ERROR_TODO = + "Todo should be in the format: todo ."; + public static final String ERROR_VIEW_SCHEDULE = + "View schedule should be in the format: view ."; + public static final String ERROR_NO_SUCH_INDEX = + "The task number is not valid. Please use the corresponding task number when listing the tasks"; + + public InvalidCommandFormatException(String errorMessage) { + super(errorMessage); + } + + public String getErrorHeader() { + return ERROR_PREFIX; + } + + public String toString() { + return getMessage(); + } +} diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidDateFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidDateFormatException.java new file mode 100644 index 00000000..1206a89e --- /dev/null +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidDateFormatException.java @@ -0,0 +1,4 @@ +package com.alexooi.duke.exceptions; + +public class InvalidDateFormatException extends Exception { +} diff --git a/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java b/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java new file mode 100644 index 00000000..f520b1f0 --- /dev/null +++ b/src/main/java/com/alexooi/duke/exceptions/InvalidFileFormatException.java @@ -0,0 +1,20 @@ +package com.alexooi.duke.exceptions; + +public class InvalidFileFormatException extends Exception { + private static final String ERROR_PREFIX = "Invalid File Format:"; + public static final String ERROR_DATE = "Unable to parse date from file, file may have been modified manually or corrupted."; + + public InvalidFileFormatException() {} + + public InvalidFileFormatException(String errorMessage) { + super(errorMessage); + } + + public String getErrorHeader() { + return ERROR_PREFIX; + } + + public String toString() { + return getMessage(); + } +} diff --git a/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java new file mode 100644 index 00000000..b77b782b --- /dev/null +++ b/src/main/java/com/alexooi/duke/interfaces/OutputFormat.java @@ -0,0 +1,31 @@ +package com.alexooi.duke.interfaces; + +import java.time.LocalDate; + +public interface OutputFormat { + String start(); + + String add(T printable, int length); + + String done(T printable, boolean isDone); + + String remove(T printable, int length); + + String error(String header, String errorMessage); + + String error(String errorMessage); + + String list(Iterable inputs); + + String viewSchedule(Iterable inputs, LocalDate date); + + String find(Iterable inputs); + + String archive(T printable); + + String archive(Iterable inputs); + + String help(Iterable inputs); + + String exit(); +} diff --git a/src/main/java/com/alexooi/duke/interfaces/Parser.java b/src/main/java/com/alexooi/duke/interfaces/Parser.java new file mode 100644 index 00000000..88ffbf0c --- /dev/null +++ b/src/main/java/com/alexooi/duke/interfaces/Parser.java @@ -0,0 +1,6 @@ +package com.alexooi.duke.interfaces; + +public interface Parser { + T parseInput(S input) throws Exception; + boolean isValidInput(S input); +} \ No newline at end of file diff --git a/src/main/java/com/alexooi/duke/interfaces/StorageClient.java b/src/main/java/com/alexooi/duke/interfaces/StorageClient.java new file mode 100644 index 00000000..8ed57e53 --- /dev/null +++ b/src/main/java/com/alexooi/duke/interfaces/StorageClient.java @@ -0,0 +1,6 @@ +package com.alexooi.duke.interfaces; + +public interface StorageClient { + T load(); + void save(String input, boolean isAppend); +} diff --git a/src/main/java/com/alexooi/duke/storage/FileStorage.java b/src/main/java/com/alexooi/duke/storage/FileStorage.java new file mode 100644 index 00000000..90951193 --- /dev/null +++ b/src/main/java/com/alexooi/duke/storage/FileStorage.java @@ -0,0 +1,58 @@ +package com.alexooi.duke.storage; + +import com.alexooi.duke.interfaces.StorageClient; + +import java.io.*; + +/** + * The concrete implementation of the StorageClient for storing in files. + */ +public class FileStorage implements StorageClient { + final String filePath; + + public FileStorage(String filePath) { + this.filePath = filePath; + } + + /** + * Loads the specific file at the file path and returns a reader for use by the parser + * @return A buffered reader that can be used to scan the file. + */ + public BufferedReader load() { + try { + File file = new File(this.filePath); + if (file.exists()) { + FileReader reader = new FileReader(file); + return new BufferedReader(reader); + } else if (file.createNewFile()) { + FileReader reader = new FileReader(file); + return new BufferedReader(reader); + } else { + throw new IOException(); + } + } catch (FileNotFoundException fnfe) { + System.out.println("File Not Found Exception: Shouldn't have hit this!"); + } catch (IOException ioe) { + System.out.println("IOException: Had an issue creating file"); + } + return null; + } + + /** + * Save the input into the file at the file path. + * @param input The string to write into the file + * @param isAppend Whether to append to the file or overwrite the file + */ + @Override + public void save(String input, boolean isAppend) { + try { + File file = new File(this.filePath); + FileWriter writer = new FileWriter(file, isAppend); + BufferedWriter out = new BufferedWriter(writer); + out.write(input); + out.close(); + } catch (IOException ioe) { + System.out.println("Had trouble saving!"); + } + } +} diff --git a/src/main/java/com/alexooi/duke/tasks/Deadline.java b/src/main/java/com/alexooi/duke/tasks/Deadline.java new file mode 100644 index 00000000..c5c99930 --- /dev/null +++ b/src/main/java/com/alexooi/duke/tasks/Deadline.java @@ -0,0 +1,32 @@ +package com.alexooi.duke.tasks; + +import com.alexooi.duke.enums.TaskType; + +import java.time.LocalDateTime; + +public class Deadline extends Task { + private LocalDateTime dueDate; + + public Deadline(String description, LocalDateTime dueDate) { + super(TaskType.DEADLINE, description); + this.setDueDate(dueDate); + } + + public LocalDateTime getDueDate() { + return dueDate; + } + + public void setDueDate(LocalDateTime dueDate) { + this.dueDate = dueDate; + } + + @Override + public String toStatusString() { + return "[D]" + super.toStatusString() + " (by: " + getDueDate() + ")"; + } + + @Override + public String toSaveString() { + return super.toSaveString() + "|" + dueDate; + } +} diff --git a/src/main/java/com/alexooi/duke/tasks/Event.java b/src/main/java/com/alexooi/duke/tasks/Event.java new file mode 100644 index 00000000..c8131457 --- /dev/null +++ b/src/main/java/com/alexooi/duke/tasks/Event.java @@ -0,0 +1,32 @@ +package com.alexooi.duke.tasks; + +import com.alexooi.duke.enums.TaskType; + +import java.time.LocalDateTime; + +public class Event extends Task { + private LocalDateTime timing; + + public Event(String description, LocalDateTime timing) { + super(TaskType.EVENT, description); + this.setTiming(timing); + } + + public LocalDateTime getTiming() { + return timing; + } + + public void setTiming(LocalDateTime timing) { + this.timing = timing; + } + + @Override + public String toStatusString() { + return "[E]" + super.toStatusString() + " (at: " + getTiming() + ")"; + } + + @Override + public String toSaveString() { + return super.toSaveString() + "|" + getTiming(); + } +} diff --git a/src/main/java/com/alexooi/duke/tasks/Task.java b/src/main/java/com/alexooi/duke/tasks/Task.java new file mode 100644 index 00000000..41eefcb1 --- /dev/null +++ b/src/main/java/com/alexooi/duke/tasks/Task.java @@ -0,0 +1,58 @@ +package com.alexooi.duke.tasks; + +import com.alexooi.duke.enums.TaskType; + +public abstract class Task { + private String description; + private boolean isDone; + private TaskType type; + + public Task() { + isDone = false; + } + + public Task(TaskType type, String description) { + this(); + setType(type); + setDescription(description); + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean getDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + public TaskType getType() { + return type; + } + + public void setType(TaskType type) { + this.type = type; + } + + public String toStatusString() { + String doneStr = (getDone()) ? "X" : " "; + return "[" + doneStr + "] " + this; + } + + @Override + public String toString() { + return getDescription(); + } + + public String toSaveString() { + String doneStr = (getDone()) ? "T" : "F"; + return getType() + "|" + doneStr + "|" + getDescription(); + } +} diff --git a/src/main/java/com/alexooi/duke/tasks/TaskFactory.java b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java new file mode 100644 index 00000000..02c9d9a5 --- /dev/null +++ b/src/main/java/com/alexooi/duke/tasks/TaskFactory.java @@ -0,0 +1,120 @@ +package com.alexooi.duke.tasks; + +import com.alexooi.duke.enums.TaskType; +import com.alexooi.duke.commands.Command; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.exceptions.InvalidDateFormatException; +import com.alexooi.duke.exceptions.InvalidFileFormatException; +import com.alexooi.duke.interfaces.Parser; +import com.alexooi.duke.utility.DateParser; + +import java.time.LocalDateTime; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TaskFactory { + private static final String KEY_DESCRIPTION = "description"; + private static final String KEY_DATE = "date"; + private static final String DEADLINE_PATTERN = + "^(?<" + KEY_DESCRIPTION + ">.+) /by (?<" + KEY_DATE + ">.+)$"; + private static final String EVENT_PATTERN = + "^(?<" + KEY_DESCRIPTION + ">.+) /at (?<" + KEY_DATE + ">.+)$"; + private static final Parser dateParser = new DateParser(); + + private static LocalDateTime handleDateInput(String input) throws InvalidDateFormatException { + try { + return dateParser.parseInput(input); + } catch (Exception e) { + throw new InvalidDateFormatException(); + } + } + + /** + * This function takes in a command and outputs the corresponding task based on the command shown. + * @param cmd The command containing the keyword (Task name) and arguments (Date if applicable) + * @return Deadline, Event or Todo task + * @throws InvalidCommandFormatException If the date does is not parsable for Deadline and Event, this exception is thrown + */ + public static Task getInstance(Command cmd) throws InvalidCommandFormatException { + String keyword = cmd.getKeyword(); + String args = cmd.getArgs(); + if (keyword.equalsIgnoreCase(TaskType.DEADLINE.toString())) { + Matcher deadlineMatch = + Pattern.compile(DEADLINE_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); + if (!deadlineMatch.matches()) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); + } + String description = deadlineMatch.group(KEY_DESCRIPTION); + String dueDate = deadlineMatch.group(KEY_DATE); + try { + LocalDateTime dueBy = handleDateInput(dueDate); + return new Deadline(description, dueBy); + } catch (InvalidDateFormatException idfe) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_DEADLINE); + } + } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { + Matcher eventMatch = Pattern.compile(EVENT_PATTERN, Pattern.CASE_INSENSITIVE).matcher(args); + if (!eventMatch.matches()) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_EVENT); + } + String description = eventMatch.group(KEY_DESCRIPTION); + String timing = eventMatch.group(KEY_DATE); + try { + LocalDateTime eventTime = handleDateInput(timing); + return new Event(description, eventTime); + } catch (InvalidDateFormatException idfe) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_EVENT); + } + } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { + if (args.length() <= 0) { + throw new InvalidCommandFormatException(InvalidCommandFormatException.ERROR_TODO); + } + return new Todo(args); + } + throw new InvalidCommandFormatException( + "Cannot find the appropriate task to create."); + } + + /** + * This function parses an input string that is formatted in the save file format and returns the respective Task + * @param s The input string to parse into a task + * @return The Deadline, Event or To do Task + * @throws InvalidFileFormatException This exception gets thrown if the line being parsed does not match any Task. + * It should not occur unless the file has been modified externally. + */ + public static Task getInstance(String s) throws InvalidFileFormatException { + String[] line = s.split("\\|"); + assert line.length > 2; + boolean doneState = line[1].equalsIgnoreCase("t"); + Task currentTask; + + String keyword = line[0]; + if (keyword.equalsIgnoreCase(TaskType.DEADLINE.toString())) { + String description = line[2]; + String dueDate = line[3]; + try { + LocalDateTime dueBy = handleDateInput(dueDate); + currentTask = new Deadline(description, dueBy); + } catch (InvalidDateFormatException idfe) { + throw new InvalidFileFormatException(InvalidFileFormatException.ERROR_DATE); + } + } else if (keyword.equalsIgnoreCase(TaskType.EVENT.toString())) { + String description = line[2]; + String timing = line[3]; + + try { + LocalDateTime eventTime = handleDateInput(timing); + currentTask = new Event(description, eventTime); + } catch (InvalidDateFormatException idfe) { + throw new InvalidFileFormatException(InvalidFileFormatException.ERROR_DATE); + } + } else if (keyword.equalsIgnoreCase(TaskType.TODO.toString())) { + currentTask = new Todo(line[2]); + } else { + throw new InvalidFileFormatException(); + } + + currentTask.setDone(doneState); + return currentTask; + } +} diff --git a/src/main/java/com/alexooi/duke/tasks/TaskList.java b/src/main/java/com/alexooi/duke/tasks/TaskList.java new file mode 100644 index 00000000..09eb1997 --- /dev/null +++ b/src/main/java/com/alexooi/duke/tasks/TaskList.java @@ -0,0 +1,135 @@ +package com.alexooi.duke.tasks; + +import com.alexooi.duke.exceptions.InvalidFileFormatException; +import com.alexooi.duke.interfaces.StorageClient; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.ArrayList; + +/** + * This class follows the Singleton design pattern as it is used extensively throughout the application + */ +public class TaskList { + private static TaskList instance; + private final ArrayList taskList; + private final StorageClient stateClient; + private final StorageClient archiveClient; + + private TaskList(StorageClient stateClient, StorageClient archiveClient) { + assert stateClient != null && archiveClient != null; + taskList = new ArrayList<>(); + this.stateClient = stateClient; + this.archiveClient = archiveClient; + } + + /** + * This function lazily creates the TaskList if not already existing, else it returns the current instance of the TaskList + * @param stateClient The StorageClient corresponding to where the state is stored + * @param archiveClient The StorageClient corresponding to where the task archive is stored + * @return The instance of the TaskList. + */ + public static TaskList getInstance(StorageClient stateClient, StorageClient archiveClient) { + if (instance == null) { + instance = new TaskList(stateClient, archiveClient); + } + + return instance; + } + + /** + * Adds a task to the task list + * @param task The task to add + */ + public void add(Task task) { + taskList.add(task); + } + + /** + * Removes a task from the task list by its index + * @param idx The index of the task to remove + * @return The task that has been removed. + */ + public Task remove(int idx) { + return taskList.remove(idx); + } + + /** + * Removes all tasks from the task list. + */ + public void removeAll() { + taskList.clear(); + } + + /** + * Get the list of tasks + * @return Returns an ArrayList containing all the tasks currently in the task list + */ + public ArrayList get() { + return taskList; + } + + /** + * Get a task by its index + * @param idx The index of the task to retrieve + * @return The task at index idx + */ + public Task getTask(int idx) { + return taskList.get(idx); + } + + /** + * Get the length of the task list + * @return The length of the task list + */ + public int size() { + return taskList.size(); + } + + /** + * Archives a single task in the task list. + * @param idx The index of the task to archive. + */ + public void archive(int idx) { + String output = taskList.get(idx).toSaveString() + System.lineSeparator(); + assert archiveClient != null; + archiveClient.save(output, true); + } + + /** + * Archive all tasks in the task list. + */ + public void archive() { + String output = toSaveListString(); + assert archiveClient != null; + archiveClient.save(output, true); + } + + /** + * Save the state of the task list. + */ + public void save() { + String output = toSaveListString(); + assert stateClient != null; + stateClient.save(output, false); + } + + private String toSaveListString() { + return taskList.stream().reduce("", + (partialOutput, task) -> partialOutput + task.toSaveString() + System.lineSeparator(), + String::concat + ); + } + + public void load(BufferedReader in) throws IOException, InvalidFileFormatException { + boolean isEndOfInput = false; + while (!isEndOfInput) { + String taskStr = in.readLine(); + if (taskStr == null) { + isEndOfInput = true; + } else { + taskList.add(TaskFactory.getInstance(taskStr)); + } + } + } +} diff --git a/src/main/java/com/alexooi/duke/tasks/Todo.java b/src/main/java/com/alexooi/duke/tasks/Todo.java new file mode 100644 index 00000000..67636ff7 --- /dev/null +++ b/src/main/java/com/alexooi/duke/tasks/Todo.java @@ -0,0 +1,14 @@ +package com.alexooi.duke.tasks; + +import com.alexooi.duke.enums.TaskType; + +public class Todo extends Task { + public Todo(String description) { + super(TaskType.TODO, description); + } + + @Override + public String toStatusString() { + return "[T]" + super.toStatusString(); + } +} diff --git a/src/main/java/com/alexooi/duke/ui/CommandLineParser.java b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java new file mode 100644 index 00000000..0c24d646 --- /dev/null +++ b/src/main/java/com/alexooi/duke/ui/CommandLineParser.java @@ -0,0 +1,53 @@ +package com.alexooi.duke.ui; + +import com.alexooi.duke.commands.Command; +import com.alexooi.duke.exceptions.InvalidCommandException; +import com.alexooi.duke.exceptions.InvalidCommandFormatException; +import com.alexooi.duke.interfaces.Parser; + +import java.util.ServiceLoader; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CommandLineParser implements Parser { + final ServiceLoader commands = ServiceLoader.load(Command.class); + + /** + * This function reads the user input and matches it against a list of commands. + * It parses the keyword and arguments of the command and returns the command found for execution. + * @param input The input string to parse + * @return The command that was detected + * @throws InvalidCommandException Reaches this exception if no command is found matching the input + * @throws InvalidCommandFormatException Reaches this exception if a command is found the but the input is invalid + */ + public Command parseInput(String input) + throws Exception { + + for (Command cmd : commands) { + Pattern servicePattern = cmd.getRegexPattern(); + Matcher patternMatcher = servicePattern.matcher(input); + + if (patternMatcher.matches()) { + String keyword = patternMatcher.group(1); + cmd.setKeyword(keyword); + if (patternMatcher.groupCount() > 1) { + String args = patternMatcher.group(2); + cmd.setArgs(args); + } + return cmd.build(); + } + } + + throw new InvalidCommandException(InvalidCommandException.ERROR_GENERIC); + } + + /** + * Note: This function is not in use, but has been added here due to the interface. + * @param input String to validate + * @return false always + */ + @Override + public boolean isValidInput(String input) { + return false; + } +} diff --git a/src/main/java/com/alexooi/duke/ui/OutputFormatter.java b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java new file mode 100644 index 00000000..34583c7f --- /dev/null +++ b/src/main/java/com/alexooi/duke/ui/OutputFormatter.java @@ -0,0 +1,202 @@ +package com.alexooi.duke.ui; + +import com.alexooi.duke.tasks.Task; +import com.alexooi.duke.interfaces.OutputFormat; + +import java.time.LocalDate; + +/** + * This class is the concrete implementation of OutputFormat. It is used for formatting the chatbot output to the + * desired output format. + */ +public class OutputFormatter implements OutputFormat { + private static final String PADDING = " "; + private static final String HORIZONTAL_LINE = "_________________________________________"; + private static final String START = "Hello! I'm Tassie, your virtual assistant.\n What can I do for you?\n\n Note: Enter help to see a list of commands."; + private static final String COMMAND_LIST = "The following commands are available:"; + private static final String END = "Bye. Hope to see you again soon!"; + private static final String DONE = "Nice! I've marked this task as done:"; + private static final String UNDO = "Okay, I've unmarked this task:"; + private static final String ADD = "added: "; + private static final String REMOVE = "Noted. I've removed this task:"; + private static final String VIEW_SCHEDULE = "You have the following tasks scheduled on "; + private static final String FIND = "Here are the matching tasks in your list:"; + private static final String ARCHIVE_SINGLE = "We successfully archived the following task:"; + private static final String ARCHIVE_ALL = "We successfully archived all of your tasks!"; + + /** + * Generates the output when first starting the application. + * @return formatted string containing the output to print. + */ + @Override + public String start() { + return formatOutput(formatLine(START)); + } + + /** + * Generates the output when adding a new Task + * @param printable Task or Child Task that has implemented the toString() method + * @param length Length of the task list after adding the new Task + * @return formatted string containing the output to print + */ + @Override + public String add(Task printable, int length) { + String output = formatLine(ADD + printable.toString()); + output += count(length); + return formatOutput(output); + } + + /** + * Generates the output when marking a Task as done. + * @param printable Task or Child Task that has implemented the toStatusString method + * @return formatted string containing the output to print + */ + @Override + public String done(Task printable, boolean isDone) { + String output = ""; + if (isDone) { + output += formatLine(DONE); + } else { + output += formatLine(UNDO); + } + output += formatLine(" " + printable.toStatusString()); + return formatOutput(output); + } + + /** + * Generates the output when removing a Task. + * @param printable The task that has been removed + * @param length Length of the task list after removing the Task + * @return formatted string containing the output to print + */ + @Override + public String remove(Task printable, int length) { + String output = formatLine(REMOVE); + output += formatLine(" " + printable.toStatusString()); + output += count(length); + return formatOutput(output); + } + + /** + * Generates the output when there is an error that has occurred. + * @param header The header to print for the error + * @param errorMessage The error message of the error that occurred + * @return formatted string containing the error header and message + */ + @Override + public String error(String header, String errorMessage) { + String output = formatLine(header) + formatLine(errorMessage); + return formatOutput(output); + } + + /** + * Generates the output when there is an error that has occurred. + * @param errorMessage The error message of the error that occurred + * @return formatted string containing the error message + */ + @Override + public String error(String errorMessage) { + return formatOutput(formatLine(errorMessage)); + } + + /** + * Generates the output when listing the current task list + * @param inputs An iterable list of Tasks + * @return formatted string containing the list of Tasks + */ + @Override + public String list(Iterable inputs) { + return formatOutput(formatList(inputs)); + } + + /** + * Generates the output when viewing the schedule of a specific date + * @param inputs An iterable list of tasks on that date + * @param date Date that is being viewed + * @return formatted string containing the list of Tasks on that specific date + */ + @Override + public String viewSchedule(Iterable inputs, LocalDate date) { + String output = formatLine(VIEW_SCHEDULE + date.toString()) + + formatList(inputs); + return formatOutput(output); + } + + /** + * Generates the output of a description find + * @param inputs An iterable list of tasks that matches the find + * @return formatted string containing the list of Tasks that match the keyword + */ + @Override + public String find(Iterable inputs) { + String output = formatLine(FIND) + + formatList(inputs); + return formatOutput(output); + } + + /** + * Generates the output of a single Task archival + * @param printable The task that has been archived + * @return Formatted string containing the Task + */ + @Override + public String archive(Task printable) { + String output = formatLine(ARCHIVE_SINGLE) + formatLine(printable.toStatusString()); + return formatOutput(output); + } + + /** + * Generates the output of a complete Task List archival + * @param inputs The list of tasks the have been archived + * @return Formatted string containing all the tasks that are archived + */ + @Override + public String archive(Iterable inputs) { + String output = formatLine(ARCHIVE_ALL) + + formatList(inputs); + return formatOutput(output); + } + + @Override + public String help(Iterable inputs) { + StringBuilder builder = new StringBuilder(); + builder.append(formatLine(COMMAND_LIST)); + for (String input : inputs) { + builder.append(formatLine(input)); + } + return formatOutput(builder.toString()); + } + + /** + * Generates the output when the user is exiting + * @return Formatted string for the exit message + */ + @Override + public String exit() { + return formatOutput(formatLine(END)); + } + + private String count(int length) { + String multipleStr = (length > 1) ? "tasks" : "task"; + String taskStr = length + " " + multipleStr; + return formatLine("Now you have " + taskStr + " in the list."); + } + + private String formatLine(String output) { + return PADDING + output + System.lineSeparator(); + } + + private String formatOutput(String output) { + return formatLine(HORIZONTAL_LINE) + output + PADDING + HORIZONTAL_LINE; + } + + private String formatList(Iterable inputs) { + StringBuilder output = new StringBuilder(); + int count = 1; + for (Task input : inputs) { + output.append(formatLine(count + ". " + input.toStatusString())); + count++; + } + return output.toString(); + } +} diff --git a/src/main/java/com/alexooi/duke/utility/DateParser.java b/src/main/java/com/alexooi/duke/utility/DateParser.java new file mode 100644 index 00000000..8e197c86 --- /dev/null +++ b/src/main/java/com/alexooi/duke/utility/DateParser.java @@ -0,0 +1,94 @@ +package com.alexooi.duke.utility; + +import com.alexooi.duke.exceptions.InvalidDateFormatException; +import com.alexooi.duke.interfaces.Parser; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; + +public class DateParser implements Parser { + private static final DateTimeFormatter[] ALLOWED_DATE_FORMATS = { + DateTimeFormatter.ISO_DATE + }; + private static final DateTimeFormatter[] ALLOWED_DATE_TIME_FORMATS = { + DateTimeFormatter.ISO_DATE_TIME + }; + + /** + * Checks if a string matches some particular date formats, returning true if it does, false if not. + * @param input Date in string format + * @param acceptedFormats Can be a DateTimeFormatter or a list of accepted formats built using DateTimeFormatterBuilder + * @return True if the string is an accepted date format, false if not + */ + public boolean isDate(String input, DateTimeFormatter acceptedFormats) { + try { + LocalDate.parse(input, acceptedFormats); + } catch (DateTimeParseException dtpe) { + return false; + } + return true; + } + + /** + * Checks if a string matches some particular date formats, returning true if it does, false if not + * @param input Date and Time in string format + * @param acceptedFormats Can be a DateTimeFormatter or a list of accepted formats built using DateTimeFormatterBuilder + * @return True if the string is an accepted Date and Time format, false if not + */ + public boolean isDateTime(String input, DateTimeFormatter acceptedFormats) { + try { + LocalDateTime.parse(input, acceptedFormats); + } catch (DateTimeParseException dtpe) { + return false; + } + return true; + } + + private DateTimeFormatter formatBuilder(DateTimeFormatter[] formats) { + DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); + for (DateTimeFormatter formatter : formats) { + builder.appendOptional(formatter); + } + return builder.toFormatter(); + } + + /** + * Parses a LocalDateTime object from an input string + * @param input Date or Date and Time in string format + * @return Returns a LocalDateTime object containing the parsed Date and parsed Time if applicable, + * otherwise time defaults to start of day 00:00. If the string cannot be parsed, then returns null + */ + @Override + public LocalDateTime parseInput(String input) throws Exception { + DateTimeFormatter allowedDateTimeFormats = formatBuilder(ALLOWED_DATE_TIME_FORMATS); + DateTimeFormatter allowedDateFormats = formatBuilder(ALLOWED_DATE_FORMATS); + if (isDateTime(input, allowedDateTimeFormats)) { + return LocalDateTime.parse(input, allowedDateTimeFormats); + } else if (isDate(input, allowedDateFormats)) { + return LocalDate.parse(input, allowedDateFormats).atStartOfDay(); + } else { + throw new InvalidDateFormatException(); + } + } + + /** + * This function validates if a particular date is valid for the allowed date and date time formats. + * @param input String to validate + * @return True if valid, else false + */ + @Override + public boolean isValidInput(String input) { + DateTimeFormatter allowedDateTimeFormats = formatBuilder(ALLOWED_DATE_TIME_FORMATS); + DateTimeFormatter allowedDateFormats = formatBuilder(ALLOWED_DATE_FORMATS); + boolean isValid = false; + if (isDateTime(input, allowedDateTimeFormats)) { + isValid = true; + } else if (isDate(input, allowedDateFormats)) { + isValid = true; + } + return isValid; + } +} diff --git a/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command b/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command new file mode 100644 index 00000000..02058463 --- /dev/null +++ b/src/main/resources/META-INF/services/com.alexooi.duke.commands.Command @@ -0,0 +1,9 @@ +com.alexooi.duke.commands.AddCommand +com.alexooi.duke.commands.CompleteCommand +com.alexooi.duke.commands.DeleteCommand +com.alexooi.duke.commands.ExitCommand +com.alexooi.duke.commands.ListCommand +com.alexooi.duke.commands.ViewScheduleCommand +com.alexooi.duke.commands.FindCommand +com.alexooi.duke.commands.ArchiveCommand +com.alexooi.duke.commands.HelpCommand \ No newline at end of file diff --git a/src/test/java/com/alexooi/duke/tasks/TaskTest.java b/src/test/java/com/alexooi/duke/tasks/TaskTest.java new file mode 100644 index 00000000..abbeab70 --- /dev/null +++ b/src/test/java/com/alexooi/duke/tasks/TaskTest.java @@ -0,0 +1,12 @@ +package com.alexooi.duke.tasks; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TaskTest { + @Test + void getDescriptionSuccess() { + Task task = new Todo("read book"); + Assertions.assertEquals("read book", task.getDescription()); + } +} diff --git a/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java b/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java new file mode 100644 index 00000000..1cfa7a08 --- /dev/null +++ b/src/test/java/com/alexooi/duke/ui/CommandLineParserTest.java @@ -0,0 +1,53 @@ +package com.alexooi.duke.ui; + +import com.alexooi.duke.commands.Command; +import com.alexooi.duke.exceptions.InvalidCommandException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CommandLineParserTest { + private static final CommandLineParser COMMAND_LINE_PARSER = new CommandLineParser(); + + @Test + void readInput_CommandTodoSuccess() throws Exception { + String input = "todo read book"; + Command cmd = COMMAND_LINE_PARSER.parseInput(input); + assertEquals("todo", cmd.getKeyword(), "Parser assertEquals Todo Keyword failed."); + assertEquals("read book", cmd.getArgs(), "Parser assertEquals Todo Args failed."); + } + + @Test + void readInput_CommandTodoFail() { + String input = "todo1"; + assertThrows(InvalidCommandException.class, () -> COMMAND_LINE_PARSER.parseInput(input)); + } + + @Test + void readInput_CommandDeadlineSuccess() throws Exception { + String input = "deadline return book /by 2021-09-15"; + Command cmd = COMMAND_LINE_PARSER.parseInput(input); + assertEquals("deadline", cmd.getKeyword(), "Parser assertEquals Deadline Keyword failed."); + assertEquals("return book /by 2021-09-15", cmd.getArgs(), "Parser assertEquals Deadline Args failed."); + } + + @Test + void readInput_CommandDeadlineFail() { + String input = "deadlinereturn book /by 2021-09-15"; + assertThrows(InvalidCommandException.class, () -> COMMAND_LINE_PARSER.parseInput(input)); + } + + @Test + void readInput_CommandEventSuccess() throws Exception { + String input = "event meet friend /at 2021-09-15"; + Command cmd = COMMAND_LINE_PARSER.parseInput(input); + assertEquals("event", cmd.getKeyword(), "Parser assertEquals Event Keyword failed."); + assertEquals("meet friend /at 2021-09-15", cmd.getArgs(), "Parser assertEquals Event Args failed."); + } + + @Test + void readInput_CommandEventFail() { + String input = "eventmeet friend /at 2021-09-15"; + assertThrows(InvalidCommandException.class, () -> COMMAND_LINE_PARSER.parseInput(input)); + } +} \ No newline at end of file diff --git a/src/test/resources/META-INF/services/com.alexooi.duke.commands.Command b/src/test/resources/META-INF/services/com.alexooi.duke.commands.Command new file mode 100644 index 00000000..8f97627e --- /dev/null +++ b/src/test/resources/META-INF/services/com.alexooi.duke.commands.Command @@ -0,0 +1,5 @@ +com.alexooi.duke.commands.AddCommand +com.alexooi.duke.commands.CompleteCommand +com.alexooi.duke.commands.DeleteCommand +com.alexooi.duke.commands.ExitCommand +com.alexooi.duke.commands.ListCommand \ No newline at end of file diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6..97deaa1c 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,79 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| + _________________________________________ + Hello! I'm Tassie, your virtual assistant. + What can I do for you? + Note: Enter help to see a list of commands. + _________________________________________ + _________________________________________ + The following commands are available: + todo + deadline /by + event /at : must be in yyyy-mm-dd or yyyy-mm-ddThh:mm format. + done : must be a number that currently exists in the list. + delete : must be a number that currently exists in the list. + bye + list + view : must be in yyyy-mm-dd. + find + archive /all : must be a number that currently exists in the list. + help + _________________________________________ + _________________________________________ + added: read book + Now you have 1 task in the list. + _________________________________________ + _________________________________________ + added: return book + Now you have 2 tasks in the list. + _________________________________________ + _________________________________________ + 1. [T][ ] read book + 2. [D][ ] return book (by: 2021-09-20T00:00) + _________________________________________ + _________________________________________ + added: meet friend + Now you have 3 tasks in the list. + _________________________________________ + _________________________________________ + Nice! I've marked this task as done: + [D][X] return book (by: 2021-09-20T00:00) + _________________________________________ + _________________________________________ + 1. [T][ ] read book + 2. [D][X] return book (by: 2021-09-20T00:00) + 3. [E][ ] meet friend (at: 2021-09-15T00:00) + _________________________________________ + _________________________________________ + You have the following tasks scheduled on 2021-09-20 + 1. [D][X] return book (by: 2021-09-20T00:00) + _________________________________________ + _________________________________________ + Here are the matching tasks in your list: + 1. [T][ ] read book + 2. [D][X] return book (by: 2021-09-20T00:00) + _________________________________________ + _________________________________________ + Noted. I've removed this task: + [D][X] return book (by: 2021-09-20T00:00) + Now you have 2 tasks in the list. + _________________________________________ + _________________________________________ + 1. [T][ ] read book + 2. [E][ ] meet friend (at: 2021-09-15T00:00) + _________________________________________ + _________________________________________ + We successfully archived the following task: + [E][ ] meet friend (at: 2021-09-15T00:00) + _________________________________________ + _________________________________________ + 1. [T][ ] read book + _________________________________________ + _________________________________________ + We successfully archived all of your tasks! + 1. [T][ ] read book + _________________________________________ + _________________________________________ + _________________________________________ + _________________________________________ + Bye. Hope to see you again soon! + _________________________________________ diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29b..f91f93f9 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,15 @@ +help +todo read book +deadline return book /by 2021-09-20 +list +event meet friend /at 2021-09-15 +done 2 +list +view 2021-09-20 +find book +delete 2 +list +archive 2 +list +archive all +list \ No newline at end of file diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 08737446..dcd80e64 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 com.alexooi.duke.Duke.com.alexooi.duke.Duke < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh old mode 100644 new mode 100755 index c9ec8700..21d43df7 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -13,18 +13,25 @@ then fi # compile the code into the bin folder, terminates if error occurred -if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java +cp -R ../src/main/resources/META-INF ../bin +if ! find ../src/main/java -name "*.java" -print | xargs javac -cp ../src/main/java -Xlint:none -d ../bin then echo "********** BUILD FAILURE **********" exit 1 fi # run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ../bin Duke < input.txt > ACTUAL.TXT +java -cp ../bin com.alexooi.duke.Duke < input.txt > ACTUAL.TXT # convert to UNIX format cp EXPECTED.TXT EXPECTED-UNIX.TXT -dos2unix ACTUAL.TXT EXPECTED-UNIX.TXT + +if type dos2unix &> /dev/null +then + dos2unix ACTUAL.TXT EXPECTED-UNIX.TXT +else + echo "dos2unix command not found. To prevent potential issues, please install dos2unix." +fi # compare the output to the expected output diff ACTUAL.TXT EXPECTED-UNIX.TXT