diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index fd8c44d086..391c46b4fe 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -32,19 +32,3 @@ jobs: - name: Build and check with Gradle run: ./gradlew check - - - name: Perform IO redirection test (*NIX) - if: runner.os == 'Linux' - working-directory: ${{ github.workspace }}/text-ui-test - run: ./runtest.sh - - - name: Perform IO redirection test (MacOS) - if: always() && runner.os == 'macOS' - working-directory: ${{ github.workspace }}/text-ui-test - run: ./runtest.sh - - - name: Perform IO redirection test (Windows) - if: always() && runner.os == 'Windows' - working-directory: ${{ github.workspace }}/text-ui-test - shell: cmd - run: runtest.bat \ No newline at end of file diff --git a/.gitignore b/.gitignore index f69985ef1f..27959d16d8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,5 @@ src/main/resources/docs/ *.iml bin/ -/text-ui-test/ACTUAL.txt -text-ui-test/EXPECTED-UNIX.TXT +text-ui-test/ACTUAL.txt +data/* \ No newline at end of file diff --git a/README.md b/README.md index 46933fbeb2..318bd5d99b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# Duke project template +# Duke project template. +test 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. @@ -9,7 +10,7 @@ Prerequisites: JDK 11 (use the exact version), update Intellij to the most recen 1. **Ensure Intellij JDK 11 is defined as an SDK**, as described [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk) -- this step is not needed if you have used JDK 11 in a previous Intellij project. * In the same dialog, you _may have to set the Project language level_ field to the SDK default option. 2. **Import the project _as a Gradle project_**, as described [here](https://se-education.org/guides/tutorials/intellijImportGradleProject.html). -3. **Verify the set up**: After the importing is complete, locate the `src/main/java/seedu/duke/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below: +3. **Verify the setup**: After the importing is complete, locate the `src/main/java/seedu/duke/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below: ``` > Task :compileJava > Task :processResources NO-SOURCE @@ -36,7 +37,7 @@ Prerequisites: JDK 11 (use the exact version), update Intellij to the most recen ### I/O redirection tests -* To run _I/O redirection_ tests (aka _Text UI tests_), navigate to the `text-ui-test` and run the `runtest(.bat/.sh)` script. +* To run _I/O redirection_ tests (aka _Text UI tests_), navigate to the `text-seedu.duke.ui-test` and run the `runtest(.bat/.sh)` script. ### JUnit tests diff --git a/build.gradle b/build.gradle index b0c5528fb5..c851ef588f 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,7 @@ repositories { 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' + implementation 'com.google.code.gson:gson:2.8.5' } test { @@ -29,7 +30,7 @@ test { } application { - mainClassName = "seedu.duke.Duke" + mainClassName = "seedu.duke.Main" } shadowJar { @@ -43,4 +44,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..088991367e 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,9 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +| Display | Name | Github Profile | Portfolio | +|-----------------------------------------------------|:-------------:|:-----------------------------------------:|:--------------------------------:| +| ![](https://via.placeholder.com/100.png?text=Photo) | Mu Changrui | [Github](https://github.com/Ch40gRv1-Mu) | [Portfolio](team/ch40grv1-mu.md) | +| ![](https://via.placeholder.com/100.png?text=Photo) | Yang Zikun | [Github](https://github.com/Yzkkk) | [Portfolio](team/yzkkk.md) | +| ![](https://via.placeholder.com/100.png?text=Photo) | Ng Yong Sheng | [Github](https://github.com/ngys117) | [Portfolio](team/ngys117.md) | +| ![](https://via.placeholder.com/100.png?text=Photo) | Choo Yi Kai | [Github](https://github.com/chooyikai/) | [Portfolio](team/chooyikai.md) | +| ![](https://via.placeholder.com/100.png?text=Photo) | Bang Hee Kit | [Github](https://github.com/heekit73098/) | [Portfolio](team/heekit73098.md) | diff --git a/docs/ClassDiagrams/CommandClassDiagram.puml b/docs/ClassDiagrams/CommandClassDiagram.puml new file mode 100644 index 0000000000..2252cdc83b --- /dev/null +++ b/docs/ClassDiagrams/CommandClassDiagram.puml @@ -0,0 +1,66 @@ +@startuml +'https://plantuml.com/class-diagram + +!define ABSTRACT {abstract} + +skinparam classAttributeIconSize 0 +skinparam shadowing false +skinparam classFontSize 12 +skinparam classAttributeFontSize 12 +hide circle + +package command { + class "ABSTRACT\nCommand" as Command + class DeleteCommand + class TextUi + class ExitCommand + + Command <|-- DeleteCommand + Command <|-- ExitCommand + DeleteCommand ..> CommandResult :creates > + ExitCommand ..> CommandResult :creates > + + DeleteCommand ..> TextUi :uses > + + class Command { + +execute(): CommandResult ABSTRACT + } + + class DeleteCommand { + -moduleCode: String + -taskModule: String + -taskIndex: int + +DeleteCommand(moduleCode: String) + +DeleteCommand(taskModule: String, index: int) + +execute(): CommandResult + +getUserConfirmation(): String {static} + } + + class TextUi { + +getUserCommand(): String {static} + +showMessage(): void {static} + } + + class ExitCommand { + +execute(): CommandResult + } + + class CommandResult { + +CommandResult(result: String) + +toString(): String + } +} + +note top of command +Note: +This class diagram only illustrates two +example command types for simplicity. +end note + +note left of DeleteCommand +Note: +Dependency on TextUi is +unique to DeleteCommand. +end note + +@enduml \ No newline at end of file diff --git a/docs/ClassDiagrams/Components.puml b/docs/ClassDiagrams/Components.puml new file mode 100644 index 0000000000..e2b9dc212a --- /dev/null +++ b/docs/ClassDiagrams/Components.puml @@ -0,0 +1,13 @@ +@startuml + +actor user +user --> [Ui] +[Ui] --> [Main] +[Main] --> [Parser] +[Parser] --> [Command]: produces > +[Main] --> [Command]: executes > +[Command] --> [Main]: returns feedback to > +[Command] --> [Storage]: writes to > +[Command] --> [Data]: manipulates > +[Main] --> [Storage]: reads from > +@enduml \ No newline at end of file diff --git a/docs/ClassDiagrams/Data.puml b/docs/ClassDiagrams/Data.puml new file mode 100644 index 0000000000..c78aa2da02 --- /dev/null +++ b/docs/ClassDiagrams/Data.puml @@ -0,0 +1,87 @@ +@startuml + +!define ABSTRACT {abstract} + +skinparam classAttributeIconSize 0 +skinparam shadowing false +skinparam classFontSize 12 +skinparam classAttributeFontSize 12 +hide circle + +package data { + class ModuleList { + -- + + addModule(m: Module): Module + + removeModule(moduleCode: String): Module + + getModule(moduleCode: String): Module + } + + class Module { + - moduleCode: String + - moduleDescription: String + - isGeneralTask: boolean + - modularCredit: int + -- + + addTask(task: Task): void + } + + class TaskList { + -- + + addTask(task: Task): Task + + removeTask(index: int): Task + + addTag(tagDescription: String, index: int): Task + + deleteTag(tagDescription: String, index: int): Task + } + + class Task { + - taskName: String + - taskDescription: String + - isTaskDone: boolean + - workingTime: String + - tags: ArrayList + -- + + getTaskParameterStatus(): TaskParameters + + toString(): String + } + + enum "<>\nTaskParameters" as TaskParameters { + DESCRIPTION_AND_WORKING_TIME + DESCRIPTION_ONLY + WORKING_TIME_ONLY + NO_DESCRIPTION_OR_WORKING_TIME + } +} + +note top of data +Note: +Some methods have been +omitted from this class diagram. +end note + +ModuleList --> "1..*" Module + +Module -r> "1" TaskList +TaskList -r> "*" Task +Task --u> "1" TaskParameters + +class Main +hide Main attributes +hide Main methods + +Main --> "1" ModuleList + +class Command +hide Command attributes +hide Command methods + +Command ..> ModuleList + +class Storage +hide Storage attributes +hide Storage methods + +Storage ..> ModuleList + +hide TaskParameters methods + +@enduml \ No newline at end of file diff --git a/docs/ClassDiagrams/DataAlternative.puml b/docs/ClassDiagrams/DataAlternative.puml new file mode 100644 index 0000000000..f0913bb37e --- /dev/null +++ b/docs/ClassDiagrams/DataAlternative.puml @@ -0,0 +1,16 @@ +@startuml + +skinparam classAttributeIconSize 0 +skinparam shadowing false +skinparam classFontSize 12 +skinparam classAttributeFontSize 12 +hide circle +hide attributes +hide methods + +ModuleList --> "*" Module +ModuleList --> "1" TaskList +Module -l> "1" TaskList +TaskList -l> "*" Task + +@enduml \ No newline at end of file diff --git a/docs/ClassDiagrams/Parser.puml b/docs/ClassDiagrams/Parser.puml new file mode 100644 index 0000000000..61b6a6a464 --- /dev/null +++ b/docs/ClassDiagrams/Parser.puml @@ -0,0 +1,60 @@ +@startuml + +!define ABSTRACT {abstract} + +skinparam classAttributeIconSize 0 +skinparam shadowing false +skinparam classFontSize 12 +skinparam classAttributeFontSize 12 +hide circle + +package parsers { + class "ABSTRACT\nParser" as Parser { + commandFormat: String + groupNames: HashSet + parsedCommand: HashMap + -- + + parseString(str: String): HashMap + } + + class XYZParser { + + parseCommand(userInput: String): Command + } + + class ModHappyParser { + - getCommandParser(commandWord: String): Parser + + parseCommand(userInput: String): Command + } +} + +note top of XYZParser + XYZParser is any command-specific + parser (e.g. AddParser, TagParser) +end note + +Parser <|-u- ModHappyParser +XYZParser <. ModHappyParser: uses < +ModHappyParser <-u- Main + +hide Main methods +hide Main attributes + +Parser <|-- XYZParser + +class "ABSTRACT\nCommand" as Command { +} +hide Command methods +hide Command attributes + +XYZCommand <.u. ModHappyParser: returns < +Command <|-- XYZCommand + +hide XYZCommand methods +hide XYZCommand attributes + +note bottom of XYZCommand + XYZCommand is any command + (e.g. AddCommand, TagCommand) +end note + +@enduml \ No newline at end of file diff --git a/docs/ClassDiagrams/Storage.puml b/docs/ClassDiagrams/Storage.puml new file mode 100644 index 0000000000..84f6d7b11b --- /dev/null +++ b/docs/ClassDiagrams/Storage.puml @@ -0,0 +1,75 @@ +@startuml + +!define ABSTRACT {abstract} + +skinparam classAttributeIconSize 0 +skinparam shadowing false +skinparam classFontSize 12 +skinparam classAttributeFontSize 12 +hide circle + +'I wasn't able to find a consistent standard for bound elements, so I just picked one +package storage { + class "<>\nStorage" as Storage { + -- + + writeData(object: T, path: String): void + + loadData(path: String): T + } + + class "ABSTRACT\n JsonStorage" as JsonStorage { + -- + + writeData(object: T, path: String): void + + loadData(path: String): T ABSTRACT + + createTargetFile(path: String): void + } + + class "ABSTRACT\n ListStorage" as ListStorage { + -- + + loadData(path: String): ArrayList ABSTRACT + } + + class ConfigurationStorage { + -- + + loadData(path: String): Configuration + } + + class ModuleListStorage { + -- + + loadData(path: String): ArrayList + } + + class TaskListStorage { + -- + + loadData(path: String):ArrayList + } +} + +JsonStorage ..|> Storage +ListStorage --|> JsonStorage : <>\nT -> ArrayList +ConfigurationStorage --|> JsonStorage : <>\nT -> Configuration +ModuleListStorage --|> ListStorage : <>\nModHappyT -> Module +TaskListStorage --|> ListStorage : <>\nModHappyT -> Task + +note top of storage +To avoid clutter, inherited methods +are not displayed in child classes. +end note + +Class Main +hide Main circle +hide Main attributes +hide Main methods + +Class SaveCommand +hide SaveCommand circle +hide SaveCommand attributes +hide SaveCommand methods + +ModHappyStorageManager --> Storage +hide ModHappyStorageManager circle +hide ModHappyStorageManager attributes +hide ModHappyStorageManager methods +Main..> ModHappyStorageManager +SaveCommand ..> ModHappyStorageManager + +@enduml \ No newline at end of file diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..ca1fac89a4 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,528 @@ -# Developer Guide +# Mod Happy: Developer Guide -## Acknowledgements +## Contents +1. [Introduction](#1-introduction) +2. [Product Scope](#2-product-scope) +3. [About this developer guide](#3-about-this-developer-guide) +
3.1. [Purpose](#31-purpose) +
3.2. [Explanation of Notation](#32-explanation-of-notation) +4. [Acknowledgements](#4-acknowledgements) +5. [Design](#5-design) +
5.1. [UI Component](#51-ui-component) +
5.2. [Parser Component](#52-parser-component) +
5.3. [Data Component](#53-data-component) +
5.4. [Command Component](#54-command-component) +
5.5. [Storage Component](#55-storage-component) +6. [Implementation](#6-implementation) +
6.1. [Edit Feature](#61-edit-feature) +
6.2. [Tag Feature](#62-tag-feature) +
6.3. [Grade Feature](#63-grade-feature) +
6.4. [GPA Feature](#64-gpa-feature) +
6.5. [Storage Feature](#65-storage-feature) +7. [User Stories](#7-user-stories) +8. [Non-Functional Requirements](#8-non-functional-requirements) +9. [Glossary](#9-glossary) +10. [Instructions for manual testing](#10-instructions-for-manual-testing) -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} -## Design & implementation +## 1. Introduction +Mod Happy is a command-line-based application that helps students manage their academics. Users are able to add modules and tasks, and calculate their Grade Point Average (GPA). -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +


+## 2. Product Scope -## Product scope -### Target user profile +### 2.1. Target User Profile -{Describe the target user profile} +- Undergraduate Students +- Comfortable using CLI applications +- Able to type relatively quickly -### Value proposition +
-{Describe the value proposition: what problem does it solve?} +### 2.2. Value proposition +This application seeks to help the target users to keep track of and manage their module components and deadlines, as it can be confusing to juggle so many deliverables at once. -## User Stories +


-|Version| As a ... | I want to ... | So that I can ...| -|--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +## 3. About this developer guide -## Non-Functional Requirements +### 3.1. Purpose +This developer guide aims to allow you to understand the design and implementation considerations for Mod Happy. With this guide, you will be able to add on or modify any existing implementations for your own purposes. -{Give non-functional requirements} +
-## Glossary +### 3.2. Explanation of notation +`Text` formatted as such represent classes and functions occurring in the code, as well as user input examples. -* *glossary item* - Definition +> πŸ“” **NOTE:** +> +> Callouts like this one contain additional important information about the component / implementation being discussed. Pay attention to them! -## Instructions for manual testing +


-{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +## 4. Acknowledgements + +- Some foundational source code was adapted from [addressbook-level2](https://github.com/se-edu/addressbook-level2). +- Google's [GSON library](https://github.com/google/gson) was used to facilitate serialisation and deserialisation of data as part of the save/load feature. + +


+ +## 5. Design + +The following architecture diagram provides a high level overview of the main components of Mod Happy and how they interact with one another. + +![Class Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/ClassDiagrams/Components.puml) + +The `Main` class is responsible for handling program initialisation, termination, as well as the application's main execution logic. + +Mod Happy's components are summarised below: + +* `UI`: Manages the application's text UI. +* `Parser`: Interprets user input and returns corresponding `Command` objects. +* `Command`: Handles command execution logic. +* `Data`: Manages module and task data in program memory. +* `Storage`: Reads data from, and writes data to Mod Happy's data storage files. + +
+ +### 5.1. UI Component + +The `TextUi` class serves strictly as intermediary between the user and the program, and does not directly interact with any components other than the `Main` class. It fulfils the following roles: + +- Listens for and grabs the user's input using a `Scanner`, and returns it to `Main` as a string for further processing. +- Displays any command results or status and error messages to the user. + +
+ +### 5.2. Parser Component + +![Class Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/ClassDiagrams/Parser.puml) + +The `Parser` component serves to interpret user input and construct the relevant `Command` objects to be returned to `Main` for execution later on. This component comprises the following classes: + +* `Parser`, which is an abstract class serving as the parent of all other classes in this component. This class implements basic parsing functionality through the use of regular expressions and the built-in `Pattern` class. This functionality is modified by its child classes to suit their own purposes. +* `ModHappyParser`, which identifies the command word present in the user input and invokes the relevant command-specific parser. +* A variety of command-specific parsers (e.g. `AddParser` for the `add` command), referred to in this guide as `XYZParser` for simplicity. These classes perform further parsing on any command-specific arguments, constructing and returning the corresponding `XYZCommand` object. + +> πŸ“” **NOTE:** +> +> `NoArgumentParser` is an exception to the above; instead of being associated with a single command type, it is responsible for handling all commands which do not accept any arguments. + +The following details how the `Parser` component works at runtime: + +![Sequence Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/SequenceDiagrams/ParserSequenceDiagram.puml) + +1. A single `ModHappyParser` instance is initialised by `Main` during at the start of the program. +2. Each time the user inputs a command, `ModHappyParser`'s `parseCommand()` method with the input as the parameter. +3. `ModHappyParser` identifies the relevant command-specific parser `XYZParser` and passes on the remaining unparsed arguments to its `parseCommand()` method. +4. `XYZParser` parses the command arguments subsequently, and finally construct an `XYZCommand` instance, which is subsequently returned to `Main`. + +
+ +### 5.3. Data Component + +The `Data` component is responsible for the storage and manipulation of tasks and modules as well as their associated attributes in program memory. + +The following partial class diagram illustrates the structure of this component. + +![Class Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/ClassDiagrams/Data.puml) + +The `ModuleList` class serves as the main data storage class for the program, and is always instantiated when the program is started. It holds: +* A `Module` object representing the General Tasks list. This `Module` is instantiated upon `ModuleList`'s creation and is meant to be the "default" module for all uncategorised or miscellaneous tasks. +* An `ArrayList` containing all user-created modules. + +The `Module` class serves as a wrapper around a `TaskList`, providing additional attributes including the module code and module description. Within the context of Mod Happy, modules can be viewed as task categories with names, descriptions and other attributes; for this reason, the General Tasks list is implemented as a `Module` under the hood. + +> πŸ“” **NOTE:** +> +> An alternative method of implementing `ModuleList` is shown below, where the default General Tasks +list is simply represented as a `TaskList` instead of a full-fledged `Module`. +> +> ![Class Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/ClassDiagrams/DataAlternative.puml) +> +> While this model is arguably closer to real life, the program logic would have to operate on different object types depending on whether a given `Task` belongs to a user-created Module, or the default General Tasks list. This was deemed to increase coupling and introduce too much unnecessary clutter to the code, hence it was not used. + +
+ +### 5.4. Command Component + +The `Command` component is in charge of actually executing the operations requested by the user. + +The following partial class diagram illustrates the structure of this component: + +![Class Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/ClassDiagrams/CommandClassDiagram.puml) + +All commands inherit the abstract `Command` class and must contain an `execute()` method. The program logic that must be executed to fulfil the requested command is implemented in this method. Additionally, `execute()` returns any command output to be displayed to the user as feedback. + +`Command` instances are generated by their corresponding `Parser` classes (e.g. `AddCommand` is constructed by `AddParser`) and executed by `Main`. + +
+ +### 5.5. Storage Component + +The `Storage` component is responsible for the saving and loading of program data to and from its data files. The following class diagram illustrates the structure of this component: + +![Class Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/ClassDiagrams/Storage.puml) + +* `Storage` is an interface supporting the reading and writing of data to a file. +* `Storage` is implemented by `JsonStorage`, an abstract class that reads and writes objects to a file in JSON format. +* `ListStorage` is an abstract class inheriting from `JsonStorage`, which is specifically used for the serialisation and deserialisation of `ArrayList` instances. `ModuleListStorage` and `TaskListStorage` both inherit from `ListStorage`. +


+ +## 6. Implementation + +This section describes some details on how some features are implemented. + +
+ +### 6.1. Edit Feature + +The edit feature allows the user to change a parameter of a task/module. The parameters of a module is its module description while the parameters of a task are its task name, task description and estimated working time. + +The following sequence diagram illustrates the process: + +![Sequence Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/SequenceDiagrams/EditSeqDiagrams/Edit.puml) + +![Sequence Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/SequenceDiagrams/EditSeqDiagrams/GetModule.puml) + +![Sequence Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/SequenceDiagrams/EditSeqDiagrams/GetTask.puml) + +Here is an example of editing the description of a task (First task of the module CS2113T): + +1. User inputs `edit task 1 -m CS2113T -d "Changed"`. +2. `ModHappyParser` identifies the command word as `edit` and invokes `EditParser.getParser()`. +3. `EditParser.getParser()` will identify `task` and returns `EditTaskParser` +4. `ModHappyParser` passes `task 1 -m CS2113T -d "Changed"` to `EditTaskParser`. +5. `EditTaskParser` instantiates a `EditCommand` with `taskModule = "CS2113T"`, `taskIndex = 0`, `description = "Changed"`, `workingTime = null`, `taskname = null`. This is returned to `Main`. +6. `Main` calls the `execute()` method of the `EditCommand` instance. +7. `EditCommand` first gets the relevant `Module` and invokes `editTaskFromModule(targetModule)`. +8. `editTaskFromModule(targetModule)` retrieves the task `targetTask` specified by the index and invokes `targetTask.setTaskDescription(description)` to change the description. + +
+ +### 6.2. Tag Feature + +The tag feature allows the user to add user-created one-word tags to each task, so that tasks can be filtered for easily. Each task stores its tags in an `ArrayList`. + +The following sequence diagram illustrates the process: + +![Sequence Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/SequenceDiagrams/TagSeqDiagrams/Tag.puml) + +![Sequence Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/SequenceDiagrams/TagSeqDiagrams/GetModule.puml) + +![Sequence Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/SequenceDiagrams/TagSeqDiagrams/CheckAndRunTagOperation.puml) + +Here is an example on adding a tag to a general task: + +1. User inputs `tag add 2 testTag`. +2. `ModHappyParser` identifies the command word as `tag` and passes `add 2 "testTag"` to `TagParser`. +3. `TagParser` instantiates a `TagCommand` with `tagOperation = "add"`, `taskIndex = 2`, `tagDescription = "testTag"` and `taskModule = null`. This is returned to `Main`. +4. `Main` calls the `execute()` method of the `TagCommand` instance. +5. `TagCommand` first gets the relevant `Module`. Since `taskModule` is null, `getGeneralTasks()` is called and the General Tasks `Module` is retrieved. +6. Next, `TagCommand` checks the `tagOperation`. As its value is `add`, `addTag(targetModule)` is called. +7. Finally, command feedback is returned to `Main`, indicating that the operation was successful. + +
+ +### 6.3. Grade Feature + +The grade feature allows the user to input their predicted/actual grade, according to the official grades that NUS supports. + +The following sequence diagram illustrates the process: + +![Sequence Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/SequenceDiagrams/Grade.puml) + +Here is an example on how to assign a grade to a module: + +1. User inputs `grade CS2113T A+` +2. `ModHappyParser` identifies the command word as `grade` and passes `CS2113T A+` to `GradeParser`. +3. `GradeParser` instantiates a `GradeCommand` with `moduleCode = "CS2113T"`, `moduleGrade = "A+"`. This is returned to `Main`. +4. `Main` calls the `execute()` method of the `GradeCommand` instance. +5. `execute()` retrieves the `Module` instance of `CS2113T` if it exists and invokes `addGradeToModule(m)`. +6. `addGradeToModule(m)` then invokes `m.setModuleGrade(moduleGrade)` to assign the input grade to the specified module. + +
+ +### 6.4. GPA Feature + +The GPA feature computes the user's GPA to 2 decimal places, based on the inputted grades and modular credits of each module currently stored in the program. + +The following sequence diagram illustrates the process: + +![Sequence Diagram](http://www.plantuml.com/plantuml/proxy?src=https://raw.githubusercontent.com/AY2122S2-CS2113T-T10-3/tp/master/docs/SequenceDiagrams/GPA.puml) + +Here is an example on how to calculate GPA: + +1. User inputs `gpa`. +2. `ModHappyParser` identifies the command word as `gpa`. Since `gpa` takes no arguments, `gpa` is passed to `NoArgumentParser`. +3. `NoArgumentParser` returns an instance of `GpaCommand` to `Main`. +4. `Main` calls the `execute()` method of the `GpaCommand` instance. +5. `execute()` invokes `calculateGpa()`, which performs the actual GPA computation by iterating through the provided `moduleList`. +6. After calculating the GPA, a command feedback string is generated and returned as a string. + +
+ +### 6.5. Storage Feature + +This component makes use of the [Gson](https://sites.google.com/site/gson/gson-user-guide) library, which can be used to convert Java Objects to and from their JSON representation. + +Several type-specific classes exist, each overseeing the storage of a different type of user data: + +* `ConfigurationStorage` handles the saving and loading of user preferences. This data is stored in the `data/configuration.json` file. +* `TaskListStorage` handles the saving and loading of the General Tasks list as an `ArrayList` instance. This data is stored in the `data/tasks.json` file. +* `ModuleListStorage` handles the saving and loading of all user-created modules as well as the tasks associated with them as an `ArrayList` instance. This data is stored in the `data/modules.json` file. +* `ModHappyStorageManager` keeps a reference of storage and provides other components with easy and encapsulated access to write and load all kinds of data in Mod Happy. + +It is worth noting that `ModuleListStorage` and `TaskListStorage` are implemented separately despite having mostly similar code, as the `gson.fromJson` method takes in the class of the object to be constructed, and `.class` cannot be used with generic types. + +After data is loaded from the data file, some verification checks are performed to ensure that the data is valid. The following table details some actions taken by Mod Happy in response to various types of data errors: + +| Type of error | Action taken | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| +| Malformed JSON
Multiple modules with the same module code
Module's `moduleCode` attribute is missing
Task's `taskName` attribute is missing
Invalid configuration name
Illegal or no value for configuration | Loading from the file is aborted.
A blank file is loaded in its place. | +| Module's `modularCredit` attribute is missing or contains illegal value | The missing value is initialised to `0`. | +| Module's `moduleGrade` attribute is missing or contains illegal value | The `moduleGrade` is set to `NOT_ENTERED` (the default value when unset). | +| Task's `isTaskDone` attribute is missing or contains illegal value | The missing value is initialised to `false`. | +| Task's `taskDuration` attribute is missing or contains illegal value | The `taskDuration` is set to `null` (the default value when unset). | + +


+ +## 7. User Stories + +| Version | As a ... | I want to ... | So that ... | +|---------|----------|-----------------------------------------------|-----------------------------------------------------------------------------| +| v1.0 | new user | see usage instructions | I can refer to them when I forget how to use the application | +| v1.0 | user | add a module | I can track the progress of the module | +| v1.0 | user | remove a module | I can keep track in case I decide to drop the module | +| v1.0 | new user | save and load tasks | I can restore from backups | +| v1.0 | user | delete a task | I can remove the task when I don't need it anymore | +| v1.0 | user | edit any existing task to suit my needs | I can update the information of tasks easily | +| v1.0 | user | add tasks | I can track the tasks later | +| v1.0 | user | list all tasks | I can check all tasks | +| v1.0 | user | add descriptions and notes to a task | I can reference them in the future | +| v1.0 | user | edit descriptions and notes | I can change them | +| v1.0 | user | add expected working time for each task | I can estimate the amount of time I should put in | +| v1.0 | user | mark a task as completed or uncompleted | I can manage the completeness of tasks easily | +| v1.0 | user | reset the program | I can start from scratch in a new semester | +| v1.0 | new user | see what commands I can use in the app | I can use the app more easily | +| v1.0 | user | list all modules | I can view all modules that I have added | +| v2.0 | user | be prompted to confirm when deleting any task | I won’t delete the wrong task accidentally | +| v2.0 | user | show or hide completed tasks in the task list | I can check the uncompleted tasks only | +| v2.0 | user | create tags for tasks | I can categorise them more easily (e.g. tutorial, project, assignment, etc) | +| v2.0 | user | mark tasks as important | I can know what tasks to prioritise | +| v2.0 | user | list tasks by tag | I can filter tasks I’m looking for | +| v2.0 | user | input my grades | I can estimate my final GPA | +| v2.0 | user | estimate my GPA | I can gauge my performance | + +


+ +## 8. Non-Functional Requirements + +1. Should work on any mainstream OS as long as it has _Java 11_ installed. +2. Should be able to hold up to 1000 tasks and modules combined without a noticeable sluggishness in performance for typical usage. +3. Should be able to save up to 1000 tasks and modules without taking up noticeable disk space. + +


+ +## 9. Glossary + +* *CLI* - Command-line interface +* *Mainstream OS* - Windows, Linux, Unix, OS-X +* *GPA* - Grade Point Average +* *Tag* - a single word to identify a group of tasks + +


+ +## 10. Instructions for manual testing + +Below are instructions to perform manual testing of the application. Please refer to the User Guide for more details on the usage of the various commands. + +
+ +### Launch and exit +1. Download the JAR file and copy the file to an empty folder. +2. Launch a command terminal and start the application by typing `java -jar FILENAME`, where `FILENAME` is the name of the JAR file (e.g. `tp.jar`). +3. Exit the application by typing `exit`. + +
+ +### Adding a module +* Test Case: `add mod CS2113T 4 -d "Software Engineering and OOP"`
+ Expected: A module named `CS2113T` with `4` mc is added, with a description of `Software Engineering and OOP`. +* Test Case: `add mod CS2113T 4` (Continuation from Test Case 1)
+ Expected: No new module is added. Error details in the message shows that a module of the same name already exists. +* Test Case: `add mod CS2101`
+ Expected: No new module is added. Error details in the message shows that there are missing modular credits. + +
+ +### Adding a task +* Prerequisite: There are existing modules in the application. +* Assumption: You have a module `CS2113T` added. + + +* Test Case: `add task "start PE" -m CS2113T`
+ Expected: A task with name `start PE` is added under the module `CS2113T`. +* Test Case: `add task Invalid Name`
+ Expected: No task is added. Error details in the message shows that the task name is invalid. + +
+ +### Deleting a module +* Prerequisite: There are existing modules in the application. +* Assumption: You have a module `CS2113T` added, but not `CS2101`. + + +* Test Case: `del mod CS2113T`
+ Expected: The module `CS2113T` is deleted. +* Test Case: `del mod CS2101`
+ Expected: No module is deleted. Error details in the message shows that there are no such module. + +> πŸ“” **NOTE:** +> +> If you have already added tasks to a module, deleting that module will present you with a confirmation request. Type `yes` to proceed with deletion. + +
+ +### Deleting a task +* Prerequisite: There are existing tasks in the application. +* Assumption: You have the module `CS2113T` added with at least one task added. + + +* Test Case: `del task 1 -m CS2113T`
+ Expected: The first task in `CS2113T` will be deleted. +* Test Case: `del task -1`
+ Expected: No task is deleted. Error details in the message shows that the task number is invalid. + +
+ +### Editing a module +* Prerequisite: There are existing modules in the application. +* Assumption: You have the module `CS2113T` added. + + +* Test Case: `edit mod CS2113T -d "Changed"`
+ Expected: The description of `CS2113T` is set to `Changed`. +* Test Case: `edit mod CS2113T -t "2 hours"`
+ Expected: The module remains unchanged. Error details in the message shows that `-t` is an invalid flag. + +
+ +### Editing a task +* Prerequisite: There are existing tasks in the application. +* Assumption: You have at least one task in your `General Tasks`. + + +* Test Case: `edit task 1 -d "Changed"`
+ Expected: The description of the first task in `General Tasks` is set to `Changed`. +* Test Case: `edit task 1 -d "Changed" -t "2 hours"`
+ Expected: The task remains unchanged. Error details in the message shows that there is an excess argument of `-t "2 hours"`. + +
+ +### Setting grade for a module +* Prerequisite: There are existing modules in the application. +* Assumption: You have a module `CS2113T`. + +* Test Case: `grade CS2113T A+`
+ Expected: The grade of `CS2113T` has been set to `A+`. +* Test Case: `grade CS2113T E`
+ Expected: No grade is set. Error details in the message shows that `E` is an invalid module grade. +* Test Case: `grade CS2113T -`
+ Expected: The grade of `CS2113T` has been removed. + +
+ +### Calculating GPA +* Prerequisite: There are existing modules in the application. +* Assumption: You have a module `CS2113T` of `4` modular credits and grade `A+` and a module `CS2101` of `4` modular credits and grade `B`. + + +* Test Case: `gpa`
+ Expected: Your `gpa` is calculated as `4.25`. + +
+ +### Showing Help +* Test Case: `help`
+ Expected: A message for format of the `help` command is shown. +* Test Case: `help add`
+ Expected: A message for the format of the `add` command is shown. + +
+ +### Setting options +* Test Case: `option`
+ Expected: Available configuration settings is shown with its corresponding value. +* Test Case: `option INVALID_CONFIG`
+ Expected: An error message is shown detailing that there is no configuration called `INVALID_CONFIG`. +* Test Case: `option SHOW_COMPLETED_TASKS=invalid`
+ Expected: No configuration is changed. Error details in the message shows that the value `invalid` is not supported for configuration `SHOW_COMPLETED_TASKS`. + +
+ +### Adding tags to a task +* Prerequisite: There are existing tasks in the application. +* Assumption: You have at least one task in `General Tasks`. + + +* Test Case: `tag add 1 IMPT`
+ Expected: The tag `IMPT` is added to your first task in `General Tasks`. +* Test Case: `tag add 1 .invalid`
+ Expected: No tag is added. Error details in the message shows that `.invalid` is an invalid tag name. + +
+ +### Deleting tags from a task +* Prerequisite: There are tasks with tags in the application. +* Assumption: The first task in `General Tasks` is tagged as `IMPT` only. + + +* Test Case: `tag del 1 IMPT`
+ Expected: The tag `IMPT` is removed from the first task in `General Tasks`. +* Test Case: `tag del 1 OTHERS`
+ Expected: No tag is deleted. Error details in the message shows that no such tag exists. + +
+ +### Listing all modules and tasks +* Prerequisite: There are tasks that were tagged. +* Assumption: There are tasks that were tagged as `IMPT`. + +* Test Case: `list`
+ Expected: All of your modules and tasks are shown. +* Test Case: `list IMPT`
+ Expected: Only tasks tagged as `IMPT` are shown. All of your modules are still shown regardless. + +
+ +### Marking a task as completed or uncompleted +* Prerequisite: There are existing tasks in the application. +* Assumption: There are at least one task in `General Tasks`. + + +* Test Case: `mark c 1`
+ Expected: The first task in `General Tasks` is marked as completed. +* Test Case: `mark u 1`
+ Expected: The first task in `General Tasks` is marked as uncompleted. +* Test Case: `mark t 1`
+ Expected: No tasks are marked. Error details in the message shows that `t` is an invalid flag. + +
+ +### Saving the data in the application +* Prerequisite: There is some data (modules, tasks and options) in the application. + + +* Test Case: `save`
+ Expected: Your data will be saved. You can view them directly as JSON files in the `data` directory. + +
+ +### Resetting the modules and tasks in the application +* Prerequisite: There are modules and tasks added in the application. + + +* Test Case: `reset`
+ Expected: All of your modules and tasks will be removed. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..ad6ad494e4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ -# Duke +# Mod Happy + -{Give product intro here} Useful links: * [User Guide](UserGuide.md) diff --git a/docs/SequenceDiagrams/EditSeqDiagrams/Edit.puml b/docs/SequenceDiagrams/EditSeqDiagrams/Edit.puml new file mode 100644 index 0000000000..db5a6dd181 --- /dev/null +++ b/docs/SequenceDiagrams/EditSeqDiagrams/Edit.puml @@ -0,0 +1,64 @@ +@startuml +'https://plantuml.com/sequence-diagram + +skinparam shadowing false +participant ":ModHappyParser" as ModHappyParser +participant "<>\n//EditParser//" as EditParser +participant ":EditTaskParser" as EditTaskParser +participant ":EditModuleParser" as EditModuleParser +participant ":EditCommand" as EditCommand +participant ":ModuleList" as ModuleList +participant ":Module" as Module +participant ":TaskList" as TaskList +participant ":Task" as Task +hide footbox + +note right of EditParser +Polymorphism of EditParser. +end note + +[->ModHappyParser:parseCommand(userInput) +activate ModHappyParser + +ModHappyParser -> EditParser: getParser() +activate EditParser +alt commandType==TASK +create EditTaskParser +EditParser->EditTaskParser: EditTaskParser() +activate EditTaskParser +return EditTaskParser +else commandType==MODULE +create EditModuleParser +EditParser->EditModuleParser: EditModuleParser() +activate EditModuleParser +return EditModuleParser +end +return EditParser +ModHappyParser -> EditTaskParser: parseCommand(arguments) +activate EditTaskParser +create EditCommand +EditTaskParser -> EditCommand: EditCommand(arguments) +activate EditCommand +return +return +return + +destroy EditTaskParser + +[->EditCommand:execute(moduleList, configuration) +activate EditCommand +ref over EditCommand, ModuleList: Get Module +EditCommand -> EditCommand: editTaskFromModule(targetModule) +activate EditCommand +ref over EditCommand, TaskList: Get Task +EditCommand -> Task: setTaskDescription(description) +activate Task +return +deactivate Task +return CommandResult(result) +deactivate EditCommand + +destroy EditCommand + + +@enduml \ No newline at end of file diff --git a/docs/SequenceDiagrams/EditSeqDiagrams/GetModule.puml b/docs/SequenceDiagrams/EditSeqDiagrams/GetModule.puml new file mode 100644 index 0000000000..5d0c26bdb1 --- /dev/null +++ b/docs/SequenceDiagrams/EditSeqDiagrams/GetModule.puml @@ -0,0 +1,34 @@ +@startuml +'https://plantuml.com/sequence-diagram + +skinparam shadowing false +participant ":EditCommand" as EditCommand +participant ":ModuleList" as ModuleList +hide footbox + +mainframe **sd** Get Module + +activate EditCommand +EditCommand -> EditCommand: getTargetModule(moduleList) +activate EditCommand +alt Objects.isNull(taskModule) + EditCommand -> ModuleList: getGeneralTasks() + activate ModuleList + return + +else else + EditCommand -> ModuleList: getModule(taskModule) + activate ModuleList + + alt Objects.isNull(taskModule) + [<-- ModuleList: throw NoSuchModuleException + + else else + return targetModule + + end +end +return +deactivate EditCommand + +@enduml \ No newline at end of file diff --git a/docs/SequenceDiagrams/EditSeqDiagrams/GetTask.puml b/docs/SequenceDiagrams/EditSeqDiagrams/GetTask.puml new file mode 100644 index 0000000000..a9e8b6b423 --- /dev/null +++ b/docs/SequenceDiagrams/EditSeqDiagrams/GetTask.puml @@ -0,0 +1,22 @@ +@startuml +'https://plantuml.com/sequence-diagram + +skinparam shadowing false +participant ":EditCommand" as EditCommand +participant ":Module" as Module +participant ":TaskList" as TaskList +hide footbox + +mainframe **sd** Get Task + +activate EditCommand +EditCommand -> Module: getTaskList() +activate Module +return taskList +deactivate Module +EditCommand -> TaskList: getTask(taskIndex) +activate TaskList +return targetTask +deactivate TaskList + +@enduml \ No newline at end of file diff --git a/docs/SequenceDiagrams/GPA.puml b/docs/SequenceDiagrams/GPA.puml new file mode 100644 index 0000000000..bde5889286 --- /dev/null +++ b/docs/SequenceDiagrams/GPA.puml @@ -0,0 +1,41 @@ +@startuml +'https://plantuml.com/sequence-diagram + +skinparam shadowing false +participant ":ModHappyParser" as ModHappyParser +participant ":NoArgumentParser" as NoArgumentParser +participant ":GpaCommand" as GpaCommand +hide footbox + +note right of ModHappyParser +Some methods are omitted from this diagram. +end note + +[->ModHappyParser:parseCommand(userInput) +activate ModHappyParser +create NoArgumentParser +ModHappyParser -> NoArgumentParser: NoArgumentParser() +activate NoArgumentParser +return + +ModHappyParser -> NoArgumentParser: parseCommand(commandWord) +activate NoArgumentParser +create GpaCommand +NoArgumentParser -> GpaCommand: GpaCommand() +activate GpaCommand +return +return +return + +destroy NoArgumentParser + +[->GpaCommand:execute(moduleList, configuration) +activate GpaCommand + +GpaCommand -> GpaCommand: calculateGpa() +activate GpaCommand +deactivate GpaCommand + +return CommandResult(result) + +@enduml \ No newline at end of file diff --git a/docs/SequenceDiagrams/Grade.puml b/docs/SequenceDiagrams/Grade.puml new file mode 100644 index 0000000000..ad65862f8a --- /dev/null +++ b/docs/SequenceDiagrams/Grade.puml @@ -0,0 +1,52 @@ +@startuml +'https://plantuml.com/sequence-diagram + +skinparam shadowing false +participant ":ModHappyParser" as ModHappyParser +participant ":GradeParser" as GradeParser +participant ":GradeCommand" as GradeCommand +participant ":ModuleList" as ModuleList +participant ":Module" as Module +hide footbox + +note right of ModHappyParser +Some methods are omitted from this diagram. +end note + +[->ModHappyParser:parseCommand(userInput) +activate ModHappyParser +create GradeParser +ModHappyParser -> GradeParser: GradeParser() +activate GradeParser +return + +ModHappyParser -> GradeParser: parseCommand(arguments) +activate GradeParser +create GradeCommand +GradeParser -> GradeCommand: GradeCommand(moduleCode, moduleGrade) +activate GradeCommand +return +return +return + +destroy GradeParser + +[->GradeCommand:execute(moduleList, configuration) +activate GradeCommand +GradeCommand -> ModuleList: getModule(moduleCode) +activate ModuleList +return + +GradeCommand -> GradeCommand: addGradeToModule(m) +activate GradeCommand +GradeCommand -> Module: setModuleGrade(moduleGrade) +activate Module +return +deactivate Module +return CommandResult(result) +deactivate GradeCommand + +destroy GradeCommand + + +@enduml \ No newline at end of file diff --git a/docs/SequenceDiagrams/ParserSequenceDiagram.puml b/docs/SequenceDiagrams/ParserSequenceDiagram.puml new file mode 100644 index 0000000000..9c5917abda --- /dev/null +++ b/docs/SequenceDiagrams/ParserSequenceDiagram.puml @@ -0,0 +1,48 @@ +@startuml +'https://plantuml.com/sequence-diagram +' @@author Kureans + + + + + +box #beige +participant ":ModHappyParser" as A +participant ":XYZParser" as B +participant ":XYZCommand" as C +end box + +note right of B: XYZParser refers to a variety \n of command-specific parsers\n(e.g. AddParser for AddCommand) + + +[-> A: parseCommand(userInput) +activate A +A -> A: parseString(userInput) +activate A +return parsedCommand +A -> A: getCommandParser(commandWord) +activate A +alt commandWord==XYZ_COMMAND_WORD +create B +A -> B: XYZParser() +activate B +return XYZParser +else else +X<-- A: throw ParseException +end +return XYZParser +A -> B: parseCommand(userInput) +activate B +B -> B: parseString(targetModule) +activate B +return parsedCommand +create C +B->C: XYZCommand() +activate C +return XYZCommand +return XYZCommand +[<-- A: XYZCommand + +hide footbox + +@enduml \ No newline at end of file diff --git a/docs/SequenceDiagrams/TagSeqDiagrams/CheckAndRunTagOperation.puml b/docs/SequenceDiagrams/TagSeqDiagrams/CheckAndRunTagOperation.puml new file mode 100644 index 0000000000..a37c674f97 --- /dev/null +++ b/docs/SequenceDiagrams/TagSeqDiagrams/CheckAndRunTagOperation.puml @@ -0,0 +1,27 @@ +@startuml +'https://plantuml.com/sequence-diagram + +skinparam shadowing false +participant ":TagCommand" as TagCommand +hide footbox + +mainframe **sd** Check and Run Tag Operation + +activate TagCommand + +alt tagOperation == "add" +TagCommand -> TagCommand:addTag(targetModule) +activate TagCommand +return result + +else tagOperation == del +TagCommand -> TagCommand:removeTag(targetModule) +activate TagCommand +return result + +else else +[<-- TagCommand: throw ParseException + +end + +@enduml \ No newline at end of file diff --git a/docs/SequenceDiagrams/TagSeqDiagrams/GetModule.puml b/docs/SequenceDiagrams/TagSeqDiagrams/GetModule.puml new file mode 100644 index 0000000000..dde74dd7bc --- /dev/null +++ b/docs/SequenceDiagrams/TagSeqDiagrams/GetModule.puml @@ -0,0 +1,30 @@ +@startuml +'https://plantuml.com/sequence-diagram + +skinparam shadowing false +participant ":TagCommand" as TagCommand +participant ":ModuleList" as ModuleList +hide footbox + +mainframe **sd** Get Module + +activate TagCommand + +alt Objects.isNull(taskMod) + TagCommand -> ModuleList: getGeneralTasks() + activate ModuleList + return + +else else + TagCommand -> ModuleList: getModule(taskModule) + activate ModuleList + + alt Objects.isNull(taskMod) + [<-- ModuleList: throw NoSuchModuleException + + else else + return targetModule + + end +end +@enduml \ No newline at end of file diff --git a/docs/SequenceDiagrams/TagSeqDiagrams/Tag.puml b/docs/SequenceDiagrams/TagSeqDiagrams/Tag.puml new file mode 100644 index 0000000000..f0905898b3 --- /dev/null +++ b/docs/SequenceDiagrams/TagSeqDiagrams/Tag.puml @@ -0,0 +1,40 @@ +@startuml +'https://plantuml.com/sequence-diagram + +skinparam shadowing false +participant ":ModHappyParser" as ModHappyParser +participant ":TagParser" as TagParser +participant ":TagCommand" as TagCommand +participant ":ModuleList" as ModuleList +hide footbox + +note right of ModHappyParser +Some methods are omitted from this diagram. +end note + +[->ModHappyParser:parseCommand(userInput) +activate ModHappyParser +create TagParser +ModHappyParser -> TagParser: TagParser() +activate TagParser +return + +ModHappyParser -> TagParser: parseCommand(arguments) +activate TagParser +create TagCommand +TagParser -> TagCommand: TagCommand(tagOperation, taskIndex, taskModule, tagName) +activate TagCommand +return +return +return + +destroy TagParser + +[->TagCommand:execute(moduleList, configuration) +activate TagCommand + +ref over TagCommand, ModuleList: Get Module + +ref over TagCommand, ModuleList: Check and Run Tag Operation +return commandResult +@enduml \ No newline at end of file diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..2fff7bc909 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,680 @@ -# User Guide +# Mod Happy: User Guide -## Introduction +## Contents +1. [Introduction](#1-introduction) +2. [Quick start](#2-quick-start) +
2.1. [Recommended operating systems](#21-recommended-operating-systems) +3. [About this user guide](#3-about-this-user-guide) +
3.1. [Explanation of notation](#31-explanation-of-notation) +
3.2. [Specifying tasks](#32-specifying-tasks) +
3.3. [Specifying durations](#33-specifying-durations) +4. [Features](#4-features) +
4.1. [Accessing help](#41-accessing-help-help) +
4.2. [Accessing options](#42-accessing-options-option) +
4.3. [Adding a task/module](#43-adding-a-taskmodule-add) +
4.4. [Deleting a task/module](#44-deleting-a-taskmodule-del) +
4.5. [Editing a task/module](#45-editing-a-taskmodule-edit) +
4.6. [Marking a task](#46-marking-a-task-mark) +
4.7. [Managing custom tags](#47-managing-custom-tags-tag) +
4.8. [Listing all tasks](#48-listing-all-tasks-list) +
4.9. [Setting a module's grade](#49-setting-a-modules-grade-grade) +
4.10. [Viewing GPA](#410-viewing-gpa-gpa) +
4.11. [Resetting the program](#411-resetting-the-program-reset) +
4.12. [Saving your data](#412-saving-your-data-save) +5. [FAQ](#5-faq) +6. [Command summary](#6-command-summary) -{Give a product intro} +--- -## Quick Start +## 1. Introduction -{Give steps to get started quickly} +Mod Happy is a command line application geared towards NUS students. Designed to be your personal assistant for all things academic, Mod Happy can **track** your outstanding tasks, **tag** them for easy organisation and **categorise** them according to modules, and even help you **calculate** your projected GPA. -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +


-## Features +## 2. Quick start -{Give detailed description of each feature} +1. Ensure that you have _Java 11_ or above installed. The link to the _Java 11_ installer can be found [here](https://docs.aws.amazon.com/corretto/latest/corretto-11-ug/downloads-list.html). +2. Download the latest version of Mod Happy [here](https://github.com/AY2122S2-CS2113T-T10-3/tp/releases). +3. Copy the JAR file into an empty folder. +4. Open a terminal on your laptop, and navigate to the directory containing the JAR file. +5. Run the command `java -jar FILENAME` to start the program, where `FILENAME` is the name of the downloaded file (e.g. `tp.jar`). -### Adding a todo: `todo` -Adds a new item to the list of todo items. +
-Format: `todo n/TODO_NAME d/DEADLINE` +### 2.1. Recommended Operating Systems -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +Mod Happy has been tested on and is confirmed to work with the following major operating systems: -Example of usage: +| Operating System | Version | +|:------------------|:-------------------------------| +| Microsoft Windows | Windows 7 and above | +| Apple macOS | MacOS 10.15 Catalina and above | +| Ubuntu Linux | Ubuntu 18.04 and above | +| Kali Linux | Debian 10.10 and above | +| CentOS Linux | CentOS 7 and above | -`todo n/Write the rest of the User Guide d/next week` +> πŸ“” **NOTE:** +> +> Although Mod Happy may still work on operating systems not listed in the above table, we cannot guarantee that it will be bug-free or that the performance will be optimal. -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +


-## FAQ +## 3. About this user guide -**Q**: How do I transfer my data to another computer? +This user guide contains detailed information about the various features and commands available within Mod Happy. For each command, you can find its format, a short description of what it does, as well as some example usage scenarios to help illustrate its effects. -**A**: {your answer here} +The following section details the various terminologies and notation used throughout the guide. -## Command Summary +
-{Give a 'cheat sheet' of commands here} +### 3.1. Explanation of notation -* Add todo `todo n/TODO_NAME d/DEADLINE` +- Fully capitalised field names, like `MODULE_CODE`, indicate input parameters which you supply. For instance, in `del mod MODULE_CODE`, you would replace `MODULE_CODE` with the module code of the module you wish to delete (e.g. `del mod CS2113T`). +- When multiple parameters are enclosed within round brackets `()` and separated with `|`, you must choose exactly one of the options presented. For example, `mark (c | u)` means that you must pick either `mark c` or `mark u`. +- Parts of the command indicated within square brackets `[]` are optional, and you may choose to omit the enclosed section if you wish. For example, if the command format is `list [TAG_NAME]`, `list` and `list example_tag` are both valid inputs. + +> πŸ“” **NOTE:** +> +> Pay special attention to whether input parameters are surrounded by double quotes in the command format. Missing or unnecessary double quotes will likely result in Mod Happy not understanding your command. +> +> Example 1: `command EXAMPLE` does not require double quotes around `EXAMPLE`. `command hello` is an example of a valid command. +> +> Example 2: `command "EXAMPLE_2"` requires double quotes around `EXAMPLE_2`. `command "hello"` is an example of a valid command. + +> ⚠ **IMPORTANT:** +> +> All parameters, including optional ones, must appear in the same order shown in the command format provided. Mod Happy may fail to interpret your command if you specify these parameters in a different order. + +
+ +### 3.2. Specifying tasks + +To understand how to specify tasks, it helps for you to have a brief understanding of how Mod Happy organises them. When a task is created, it is associated with a module (or the General Tasks list, if no module is provided) and stored within its list of tasks. In other words, there is no master list of tasks; tasks belonging to two different modules are stored in two entirely separate lists. + +As a result, providing the task's number is not specific enough for Mod Happy to figure out which task you are referring to. Instead, you have to additionally specify the module code associated with the task. For example: +- Task number `3`, module code `CS2113T`: refers to task number 3 stored under the module CS2113T. +- Task number `2`, no module code specified: refers to task number 2 stored under the General Tasks list. + +
+ +### 3.3. Specifying durations + +Some Mod Happy commands require you to provide a duration. You can specify these in the following format: + +#### Format: `DURATION_AMOUNT DURATION_UNIT` + +- `DURATION_AMOUNT`: Any positive number less than or equal to one billion (1000000000), including decimals. +- `DURATION_UNIT`: The time unit that `DURATION_AMOUNT` is specified in. Mod Happy supports the following units: + - Hours: `h`, `H`, `hr`, `Hr`, `hrs`, `Hrs`, `hour`, `Hour`, `hours`, `Hours` + - Minutes: `m`, `M`, `min`, `Min`, `mins`, `Mins`, `minute`, `Minute`, `minutes`, `Minutes` + +> ⚠ **IMPORTANT:** +> +> You can only choose to specify the duration in hours or minutes β€” not both. If you need to specify "2 hours and 45 minutes", for example, try `2.75 hours` instead. + +


+ +## 4. Features + +### 4.1. Accessing help: `help` + +#### 4.1.1. Generic help + +Shows you a generic help message. + +##### Format: `help` + +##### Example: + +``` +> help + +____________________________________________________________ +Displays help and format for selected command. +Format to display help for specific command: help COMMAND +Available commands: exit, add, del, edit, grade, gpa, help, list, mark, option, reset, save, tag + +Compulsory parameters are fully capitalised: e.g. MODULE_CODE. +Optional parameters are in square brackets: e.g. [-d MODULE_DESCRIPTION] +____________________________________________________________ +``` + +
+ +#### 4.1.2. Help for specific command word + +Shows you the help text for the specified command word. + +##### Format: `help [COMMAND_WORD]` + +- `COMMAND_WORD`: The command you wish to view the help message for. + +##### Example: + +``` +> help add + +____________________________________________________________ +Adds a module or task as indicated by the command input. +Format to add module: add mod MODULE_CODE MODULAR_CREDITS [-d "MODULE_DESCRIPTION"] +Format to add task: add task "TASK_NAME" [-m MODULE_CODE] [-d "TASK_DESCRIPTION"] [-t "ESTIMATED_WORKING_TIME"] +____________________________________________________________ +``` + +
+ +### 4.2. Accessing options: `option` + +Allows you to view and change various user preferences which can affect other aspects of Mod Happy's operation. This command has three different formats, each of which serve a different purpose. + +#### 4.2.1. Viewing available configuration options + +Lists the names of all available configuration options, as well as what you have them currently set to. + +##### Format: `option` + +##### Example: + +``` +> option + +____________________________________________________________ +Available config settings: +SHOW_COMPLETED_TASKS: false +____________________________________________________________ +``` + +
+ +#### 4.2.2. Viewing details of a specific configuration option + +Shows you a short description of the supplied configuration option as well as its corresponding valid values. + +##### Format: `option CONFIG_NAME` + +- `CONFIG_NAME`: The name of the configuration option you wish to view the details of.

+ +##### Example: + +``` +> option SHOW_COMPLETED_TASKS + +____________________________________________________________ +SHOW_COMPLETED_TASKS +false: Hide completed tasks +true: Show completed tasks +____________________________________________________________ +``` + +
+ +#### 4.2.3. Editing a specific configuration option + +Allows you to edit the value of a configuration option of your choice. + +##### Format: `option CONFIG_NAME=NEW_VALUE` + +- `CONFIG_NAME`: The name of the configuration option you wish to modify. +- `NEW_VALUE`: The new value of the configuration option. This value must be a value accepted by the target configuration option. + +##### Example: + +``` +> option SHOW_COMPLETED_TASKS=true + +____________________________________________________________ +Preferences updated: SHOW_COMPLETED_TASKS=true +____________________________________________________________ +``` + +
+ +> πŸ“” **NOTE:** +> +> Due to the design of Mod Happy, CONFIG_NAME and NEW_VALUE should be connected only by "=". Whitespaces between keywords will not be accepted by the application. +> +> Common illegal input: +> ``` +> option SHOW_COMPLETED_TASKS = true +> ``` + +
+The following configuration options currently exist: + +| Config name | Description | Accepted values | +|----------------------|----------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------| +| SHOW_COMPLETED_TASKS | Determines whether tasks marked as completed are shown when [listing tasks](#48-listing-all-tasks-list).
**Default value: `false`** | `true`: **All** tasks are shown.
`false`: Only uncompleted tasks are shown. | + +
+ +### 4.3. Adding a task/module: `add` + +#### 4.3.1. Add module: `add mod` + +Adds a module to your module list. You must indicate the number of modular credits and may optionally specify a short description for the module. + +##### Format: `add mod MODULE_CODE MODULAR_CREDITS [-d "MODULE_DESCRIPTION"]` + +- `MODULE_CODE`: The module code of the module. Must be a single word containing only alphanumeric characters and underscore `_`. +- `MODULAR_CREDITS`: The number of modular credits the module has. Must be an integer from 0 to 100, inclusive. +- `MODULE_DESCRIPTION`: A short description of the module. Can contain any characters except double quotes `"`. + +##### Example 1: + +``` +> add mod CS2101 4 + +____________________________________________________________ +Hey! I have added this module! +CS2101 (4MC, Grade: -) +____________________________________________________________ +``` + +##### Example 2: + +``` +> add mod CS2113T 4 -d "Software Engineering" + +____________________________________________________________ +Hey! I have added this module! +CS2113T (Software Engineering) (4MC, Grade: -) +____________________________________________________________ +``` + +
+ +> πŸ“” **NOTE:** +> +> Module codes are case-sensitive. Mod Happy treats `CS2113T` and `cs2113t` as two different modules! + +
+ +#### 4.3.2. Add task: `add task` + +Adds a task to the list of tasks tracked under the specified module code. If you do not specify any module code, the task is added to your General Tasks list, which is not associated with any module.

+ +You may optionally specify a short description for the task, as well as an estimate for the expected time spent working on it. + +##### Format: `add task "TASK_NAME" [-m MODULE_CODE] [-d "TASK_DESCRIPTION"] [-t β€œESTIMATED_WORKING_TIME”]` + +- `TASK_NAME`: The name of the task. Can contain any characters except double quotes `"`. +- `MODULE_CODE`: The module code of the module to be associated with this task. Must be a single word containing only alphanumeric characters and underscore `_`. Furthermore, a module with this module code must currently exist. +- `TASK_DESCRIPTION`: A short description of the task. Can contain any characters except double quotes `"`. +- `ESTIMATED_WORKING_TIME`: The expected duration spent working on the task. The duration must be specified in [this format](#33-specifying-durations). + +##### Example 1: + +``` +> add task "Review PR" + +____________________________________________________________ +Hey! I have added this task under General tasks! +( ) Review PR [] +____________________________________________________________ +``` + +##### Example 2: + +``` +> add task "iP Level-0" -m CS2113T -d "Greet user and exit" -t "1 hour" + +____________________________________________________________ +Hey! I have added this task under CS2113T (Software Engineering) (4MC, Grade: -)! +( ) iP Level-0 (Greet user and exit) (Estimated working time: 1 hour(s)) [] +____________________________________________________________ +``` + +
+ +### 4.4. Deleting a task/module: `del` + +#### 4.4.1. Delete module: `del mod` + +Deletes the specified module from your module list. You will be prompted for confirmation if the module has tasks assigned to it. + +##### Format: `del mod MODULE_CODE` + +- `MODULE_CODE`: The module code of the module to be deleted. Must be a single word containing only alphanumeric characters and underscore `_`. Furthermore, a module with this module code must currently exist. + +##### Example: + +``` +> del mod CS2113T + +____________________________________________________________ +CS2113T (Software Engineering) (4MC, Grade: -) contains task(s). +Are you sure you want to delete this? (yes/no) +____________________________________________________________ + +> no + +____________________________________________________________ +Deletion has been cancelled. +____________________________________________________________ +``` + +
+ +#### 4.4.2. Delete task: `del task` + +Deletes the [specified task](#32-specifying-tasks) from its parent module, or the General Tasks list if you do not specify a module code. + +##### Format: `del task TASK_NUMBER [-m MODULE_CODE]` + +- `TASK_NUMBER`: The number of the task to be deleted. Must be a positive integer. +- `MODULE_CODE`: The module code of the module associated with this task. Must be a single word containing only alphanumeric characters and underscore `_`. Furthermore, a module with this module code must currently exist. + +##### Example 1: + +``` +> del task 1 + +____________________________________________________________ +( ) Review PR [] has been deleted. +____________________________________________________________ +``` + +##### Example 2: + +``` +> del task 1 -m CS2113T + +____________________________________________________________ +( ) iP Level-0 (Greet user and exit) (Estimated working time: 1 hour(s)) [] has been deleted. +____________________________________________________________ +``` + +
+ +### 4.5. Editing a task/module: `edit` + +#### 4.5.1. Edit module: `edit mod` + +Edits an attribute of a module you have previously created. Only the module description is editable after creation. + +##### Format: `edit mod MODULE_CODE -d "MODULE_DESCRIPTION"` + +- `MODULE_CODE`: The module code of the module to be edited. Must be a single word containing only alphanumeric characters and underscore `_`. Furthermore, a module with this module code must currently exist. +- `MODULE_DESCRIPTION`: The new module description for the module. Can contain any characters except double quotes `"`. + +##### Example: + +``` +> edit mod CS2113T -d "Software Engineering & OOP" + +____________________________________________________________ +The description of CS2113T has been changed. +____________________________________________________________ +``` + +
+ +#### 4.5.2. Edit task: `edit task` + +Edits an attribute of the [specified task](#32-specifying-tasks). You can edit the task name, description, and estimated working time, but the task cannot be associated with a different module. + +##### Format: `edit task TASK_NUMBER [-m MODULE_CODE] (-n "TASK_NAME" | -d "TASK_DESCRIPTION" | -t "ESTIMATED_WORKING_TIME")` + +- `TASK_NUMBER`: The number of the task to be edited. Must be a positive integer. +- `MODULE_CODE`: The module code of the module associated with this task. Must be a single word containing only alphanumeric characters and underscore `_`. Furthermore, a module with this module code must currently exist. +- `TASK_NAME`: The name of the task. Can contain any characters except double quotes `"`. +- `TASK_DESCRIPTION`: The new description for the task. Can contain any characters except double quotes `"`. +- `ESTIMATED_WORKING_TIME`: The new expected duration. The duration must be specified in [this format](#33-specifying-durations). + +##### Example: + +``` +> edit task 1 -m CS2113T -n "CS2113T Tutorial 2" + +____________________________________________________________ +The task name of Prepare for tutorial from CS2113T has been changed. +____________________________________________________________ +``` +
+ +> ⚠ **IMPORTANT:** +> +> Only one parameter can be edited per command. You cannot do the following: +> +> `edit task 2 -m CS2113T -n "CS2113T Tutorial 1" -d "Draw class diagram"` + +> πŸ“” **NOTE:** +> +> You can remove optional parameters from tasks or modules by supplying an empty string (`""`). For example: +> +> `edit task 1 -d ""` + +
+ +### 4.6. Marking a task: `mark` + +Allows you to mark the [specified task](#32-specifying-tasks) as completed or uncompleted. + +The `c` flag indicates that the task will be marked as completed, while the `u` flag marks the task as uncompleted. + +##### Format: `mark (c | u) TASK_NUMBER [-m MODULE_CODE]` + +- `TASK_NUMBER`: The number of the task to be marked. Must be a positive integer. +- `MODULE_CODE`: The module code of the module associated with this task. Must be a single word containing only alphanumeric characters and underscore `_`. Furthermore, a module with this module code must currently exist. + +##### Example 1: + +``` +> mark c 1 + +____________________________________________________________ +Nice! I have marked this task as completed! +(X) Reply emails [] +____________________________________________________________ +``` + +##### Example 2: + +``` +> mark u 1 -m CS2113T +____________________________________________________________ +Ok! I have marked this task for you as uncompleted! +( ) CS2113T Tutorial 2 [] +____________________________________________________________ +``` + +
+ +### 4.7. Managing custom tags: `tag` + +Allows you to add or delete a tag from the [specified task](#32-specifying-tasks). + +##### Format: `tag (add | del) TASK_NUMBER [-m MODULE_CODE] TAG_NAME` + +- `TASK_NUMBER`: The number of the task to be deleted. Must be a positive integer. +- `MODULE_CODE`: The module code of the module associated with this task. Must be a single word containing only alphanumeric characters and underscore `_`. Furthermore, a module with this module code must currently exist. +- `TAG_NAME`: The name of the tag to be added or deleted. Only alphanumeric characters and underscore `_` are allowed. + +> ⚠ **IMPORTANT:** +> +> The tag name cannot contain whitespace; it must be a single word. + +##### Example: + +``` +> tag add 1 -m CS2113T project + +____________________________________________________________ +Tag "project" added: +( ) CS2113T Tutorial 2 [project]. +____________________________________________________________ +``` + +
+ +### 4.8. Listing all tasks: `list` + +Shows you your tasks, grouped by module code. General tasks are displayed separately. + +If a [tag name](#47-managing-custom-tags-tag) is provided, only tasks with the associated tag will be shown. + +> πŸ“” **NOTE:** +> +> If the [`SHOW_COMPLETED_TASKS` option](#42-accessing-options-option) is set to `false`, you will only be shown your outstanding tasks. The number of tasks that were hidden will be indicated at the bottom of each group. + +##### Format: `list [TAG_NAME]` + +- `TAG_NAME`: The name of the tag to be filtered for. Must be a single word containing only alphanumeric characters and underscore `_`. + +##### Example 1: + +``` +> list + +____________________________________________________________ +Ok! Here are the task(s) in your list: +CS2113T (Software Engineering & OOP) (4MC, Grade: -) + 1. ( ) CS2113T Tutorial 2 [project] + +CS2101 (4MC, Grade: -) + 1. ( ) Write user guide peer review [] + +General tasks + 1. (X) Reply emails [] +____________________________________________________________ +``` + +##### Example 2: + +``` +> list project + +____________________________________________________________ +Ok! Here are the task(s) in your list: +CS2113T (Software Engineering & OOP) (4MC, Grade: -) + 1. ( ) CS2113T Tutorial 2 [project] + +CS2101 (4MC, Grade: -) + (empty) + +General tasks + (empty) + +____________________________________________________________ +``` + +
+ +### 4.9. Setting a module's grade: `grade` + +Assigns a grade to a module of your choice. + +##### Format: `grade MODULE_CODE MODULE_GRADE` + +- `MODULE_CODE`: The module code of the module to be assigned the grade. Must be a single word containing only alphanumeric characters and underscore `_`. Furthermore, a module with this module code must currently exist. +- `MODULE_GRADE`: The grade to be assigned to the module. + +> πŸ“” **IMPORTANT:** +> +> Only the following grades are supported (case-insensitive): +> +> A+, A, A-, B+, B, B-, C+, C, D+, D, F, S, U, CS, CU + +> πŸ“” **NOTE:** +> +> You can remove your grade for a module by supplying a dash (`-`). For example: +> +> `grade CS2113T -` +> +> You can only remove your grade if the module has a grade set originally. + +##### Example: + +``` +> grade CS2113T A+ + +____________________________________________________________ +Your grade for CS2113T has been added. +____________________________________________________________ +``` + +
+ +### 4.10. Viewing GPA: `gpa` + +Computes your GPA based the [inputted grades](#49-setting-a-modules-grade-grade) of all currently stored modules, and displays it. Modules for which you have not inputted any grade are not factored into the calculation. + +##### Format: `gpa` + +##### Example: + +``` +> gpa + +____________________________________________________________ +Your GPA is 5.00! :) +____________________________________________________________ +``` + +
+ +### 4.11. Resetting the program: `reset` + +Removes all your tasks and modules. + +##### Format: `reset` + +##### Example: + +``` +> reset + +____________________________________________________________ +All modules and tasks have been removed. +____________________________________________________________ +``` + +
+ +### 4.12. Saving your data: `save` + +Saves all your tasks and modules to the data file. + +##### Format: `save` + +##### Example: + +``` +> save + +____________________________________________________________ +General tasks written to file. +Module data written to file. +Config options written to file. +____________________________________________________________ +``` + +> ⚠ **IMPORTANT:** +> +> Mod Happy does **not** auto-save your changes! Do remember to save your work at regular intervals, or before exiting the program. + +


+ +## 5. FAQ + +**Q**: How do I transfer my data to another computer? + +**A**: Your task and module data are stored in the `data` folder, located in the same folder as Mod Happy's JAR file. To transfer data to another computer, simply copy this folder to the new machine. Make sure to place the folder in the same location as the Mod Happy application itself! + +## 6. Command summary + +| Command | Format | +|:------------------------------------------:|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [help](#41-accessing-help-help) | `help [COMMAND_WORD]`

Examples:
`help`
`help add` | +| [option](#42-accessing-options-option) | `option`
`option CONFIG_NAME`
`option CONFIG_NAME=NEW_VALUE`

Examples:
`option`
`option SHOW_COMPLETED_TASKS`
`option SHOW_COMPLETED_TASKS=true` | +| [add](#43-adding-a-taskmodule-add) | `add mod MODULE_CODE MODULAR_CREDITS [-d "MODULE_DESCRIPTION"]`
`add task "TASK_NAME" [-m MODULE_CODE] [-d "TASK_DESCRIPTION"] [-t β€œESTIMATED_WORKING_TIME”]`

Examples:
`add mod CS2101 4`
`add mod CS2113T 4 -d "Software Engineering"`
`add task "Review PR"`
`add task "iP Level-0" -m CS2113T -d "Greet user and exit" -t "1 hour"` | +| [del](#44-deleting-a-taskmodule-del) | `del mod MODULE_CODE`
`del task TASK_NUMBER [-m MODULE_CODE]`

Examples:
`del task 1`
`del task 1 -m CS2113T` | +| [edit](#45-editing-a-taskmodule-edit) | `edit mod MODULE_CODE -d "MODULE_DESCRIPTION"`
edit task TASK_NUMBER [-m MODULE_CODE] (-n "TASK_NAME" | -d "TASK_DESCRIPTION" | -t "ESTIMATED_WORKING_TIME")

Examples:
`edit mod CS2113T -d "Software Engineering & OOP"`
`edit task 1 -m CS2113T -n "CS2113T Tutorial 2"` | +| [mark](#46-marking-a-task-mark) | mark (c | u) TASK_NUMBER [-m MODULE_CODE]

Examples:
`mark c 1`
`mark u 1 -m CS2113T` | +| [tag](#47-managing-custom-tags-tag) | tag (add | del) [-m MODULE_CODE] TAG_NAME

Examples:
`tag add 1 -m CS2113T project` | +| [list](#48-listing-all-tasks-list) | `list [TAG_NAME]`

Examples:
`list`
`list project` | +| [grade](#49-setting-a-modules-grade-grade) | `grade MODULE_CODE MODULE_GRADE`

Examples:
`grade CS2113T A+` | +| [gpa](#410-viewing-gpa-gpa) | `gpa` | +| [reset](#411-resetting-the-program-reset) | `reset` | +| [save](#412-saving-your-data-save) | `save` | \ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..c4192631f2 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/docs/team/ch40grv1-mu.md b/docs/team/ch40grv1-mu.md new file mode 100644 index 0000000000..b5d4c63b84 --- /dev/null +++ b/docs/team/ch40grv1-mu.md @@ -0,0 +1,57 @@ +# Mu Changrui - Project Portfolio Page + +## Overview: Mod Happy + +Mod Happy is a command-line application written in Java which helps students manage their academics by keeping track of their tasks and facilitating GPA calculations. + +The sections below are my contributions to this project. + +## Summary of Contributions +You can view my contributed code [here](https://nus-cs2113-ay2122s2.github.io/tp-dashboard/?search=Ch40gRv1-Mu&breakdown=true). + +- **Foundational code:** + - Wrote `Parser`, which is the parent class of other parsers. I applied java regex to implement the parserString method, that is utilized by all other parsers. [#69](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/69) + - Wrote the skeleton logic structure of `ModHappyParser` and `Main` with referring to [AB3](https://github.com/se-edu/addressbook-level3). [#69](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/69) + - Wrote major classes of storage (`Storage`,`JsonStorage`,`ListStorage`,`ModuleListStorage`,`TaskListStorage`, `ConfigurationStorage`). [#91](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/91) + - Wrote `Configuration`, which standardizes the user options and manages the states of user's customized options in the app. [#102](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/102) + - Wrote `Command`, which is the parent class of other Command, this abstract class defines the logic of Command and is utilized in the current program. [#69](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/69) + - Wrote `CommandResult`, which stores the result of commands and is passed to UI for displaying to users. [#69](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/69) + - Wrote `ModHappyException`, which is the parent class of other exceptions in the app(Only contributed to the skeleton). [#69](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/69) + + +- **New features:** + - Added the ability to save, which will save the state of the program(modules, tasks, user options) and significant enhance the usability of the app, because it enables users to access the data across multiple usage sessions. [#91](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/91) + + - Partially implemented the loading of serialised user data from storage. [#91](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/91). I handled the serialisation and deserialization functions, while instantiation of the relevant data objects in the program based on the data loaded were written by a teammate. + - Added the ability to check and set customized options (e.g.always hide completed tasks). This feature enhances the usability of the app and is an extendable skeleton for user model in MVC. [#102](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/102) + - Wrote `ExitCommand` that will execute pre-ending operations and end the process. [#69](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/69) + +- **Enhancements:** + - Significantly refactoring the estimated time feature of tasks. I made `Duration`, which accepted flexible format of string representing time duration from users, store the task duration as a `java.time.Duration` and display the time in unified format. [#124](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/124). The updated implementation of this feature significant enhance the linguistic and logical meaning of "time" in the app and make it possible for the future features (e.g.sorting by time). + - Contributed the majority of test cases for `TaskDuration`,`Parser`,`OptionParser` and class of storages. [#69](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/69) + + +- **Documentation:** + - User guide: + - Added supported system explaining the operating systems that the app are well tested on. [#173](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/173) + - Added expected output to all sample input and keep the output of the user guide updated when the app is updated. [#173](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/173) + - Added sample input in the command summary. [#173](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/173) + + - Developer guide: + - Added section explaining the format and usage of estimated time. [#118](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/118) + - Added explanation of the overview of the app and created the relevant class diagrams within that section. [#99](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/99), [#109](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/109) + - Added explanations of the parsers and created the relevant class diagrams within that section. [#109](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/109), [#118](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/118) + - Added explanations of the component and implementation of storage, and created the relevant class diagrams within that section. [#109](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/109), [#99](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/99) + +- **Team tasks:** + - Performed code cleanup and fixed bugs. + - Confirmed meeting time and created Zoom for each meeting. + - Checked the releases `v1.0`, `v2.0` and `v2.1` on multiple systems(macOS, Kali Linux, Ubuntu, CentOS) for each release. + - Keep tracking potential bugs and created multiple bug issues after discussing with teammates. [#170](https://github.com/AY2122S2-CS2113T-T10-3/tp/issues/170), [#135](https://github.com/AY2122S2-CS2113T-T10-3/tp/issues/135), [#134](https://github.com/AY2122S2-CS2113T-T10-3/tp/issues/134), [#172](https://github.com/AY2122S2-CS2113T-T10-3/tp/issues/172) , [#136](https://github.com/AY2122S2-CS2113T-T10-3/tp/issues/136) , [#119](https://github.com/AY2122S2-CS2113T-T10-3/tp/issues/119), [#71](https://github.com/AY2122S2-CS2113T-T10-3/tp/issues/71) + - Static analysis on code and improved code quality. [#188](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/188) + + +- **Community:** + - PRs reviewed (most with significant comments): [#176](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/176) , [#73](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/73), [#75](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/75), [#80](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/80), [#86](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/86), [#87](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/87), [#88](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/88), [#89](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/89), [#90](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/90), [#94](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/94), [#105](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/105), [#106](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/106), [#108](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/108), [#111](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/111), [#171](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/171) + - Reported 17 bugs to the community during PE dry run [#ped:issue](https://github.com/Ch40gRv1-Mu/ped/issues) + diff --git a/docs/team/chooyikai.md b/docs/team/chooyikai.md new file mode 100644 index 0000000000..d19fa1a6df --- /dev/null +++ b/docs/team/chooyikai.md @@ -0,0 +1,52 @@ +# Choo Yi Kai - Project Portfolio Page + +## Overview: Mod Happy + +Mod Happy is a command-line application written in Java which helps students manage their academics by keeping track of their tasks and facilitating GPA calculations. + +The section below lists my contributions to this project. + +## Summary of Contributions +You can view my contributed code [here](https://nus-cs2113-ay2122s2.github.io/tp-dashboard/?search=chooyikai&breakdown=true). + +- **Foundational code:** Rewrote basic classes used by the program for data storage (`Task`, `TaskList`, `Module`, `ModuleList`) to be more OOP-compliant. This implementation is currently in use, although it has since been modified to accommodate features that were added later. ([#75](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/75)) + + +- **New features:** + - Added the ability to add modules, which formed part of the minimum functionality for the application. ([#86](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/86)) + - Partially implemented the loading of serialised user data from the saved data file. ([#94](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/94)) + - This feature renders the app significantly more usable it contributes to the persistence of user data across multiple usage sessions. + - I handled the instantiation of the relevant data objects in the program based on the data loaded, while the actual serialisation and deserialisation functions were written by a teammate. + - Added the ability to toggle the visibility of completed tasks from the `list` command output. ([#111](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/111)) + - This feature helps to reduce visual clutter when there are many completed tasks already present in the application. + + +- **Enhancements:** + - Significantly rewrote `add`, `exit`, `list` and `mark` commands to comply with the new data classes, after they were changed to be more OOP-compliant. ([#75](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/75)) + - The updated implementation of these commands set the precedent for all future command additions, and this was generally followed by the rest of the team when adding new features. + - Contributed a significant number of test cases for the parsing of user commands, and refactored some existing ones. ([#86](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/86)) + - Added input validation to the deserialisation functions used for data loading. ([#175](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/175), [#194](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/194)) + - This makes the program inspect and reject / tweak invalid user data loaded from the data file instead of blindly trusting anything inside (which could violate some program assumptions if the file was corrupted or modified manually). + + +- **Documentation:** + - User guide: + - Added explanations for using the user guide, the notation used within, as well as how to specify tasks. ([#105](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/105), [#111](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/111), [#116](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/116)) + - Added documentation for the `option` and `gpa` commands. ([#111](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/111)) + - Added accepted inputs for each parameter in each command format. ([#116](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/116)) + - Generally rewrote command explanations to sound less clinical and more friendly. + - Developer guide: + - Added explanation of the Data component and created the relevant class diagrams within that section. ([#111](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/111)) + - Edited explanations of the Parser and Command components. ([#111](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/111)) + - Edited class diagram for the Storage component to correctly represent binding relationships. ([#111](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/111), [#116](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/116)) + - Added some elaboration for the Storage implementation. ([#194](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/194)) + + +- **Team tasks:** + - Performed code cleanup and fixed bugs throughout the codebase, as well as resolved formatting, language and consistency issues in the user and developer guides. + - Managed releases `v1.0`, `v2.0` and `v2.1` on GitHub. + - Cleaned up issues which were not closed after being addressed, and removed user stories which were no longer in scope due to time constraints or shifting project vision. + + +- **Community:** + - PRs reviewed (with significant comments): [#69](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/69), [#76](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/76), [#84](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/84), [#91](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/91), [#95](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/95), [#101](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/101), [#108](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/108), [#109](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/109) \ No newline at end of file diff --git a/docs/team/heekit73098.md b/docs/team/heekit73098.md new file mode 100644 index 0000000000..4ef119c6ce --- /dev/null +++ b/docs/team/heekit73098.md @@ -0,0 +1,51 @@ +# Bang Hee Kit - Project Portfolio Page + +## Overview: Mod Happy + +Mod Happy is a command-line application written in Java which helps students manage their academics by keeping track of their tasks and facilitating GPA calculations. + +The section below lists my contributions to this project. + +## Summary of Contributions + +You can view my contributed code [here](https://nus-cs2113-ay2122s2.github.io/tp-dashboard/?search=heekit73098&breakdown=true). + +### Code Contributions + +- **New Features** + - Added the ability to edit tasks and modules [#92](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/92) + - Added the ability to input grades to a module [#101](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/101) +- **Enhancements** + - Expanded support for adding modular credits when adding a module [#101](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/101) + - Standardised the command format so that it is easier for the users to type [#113](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/113) + - Expanded exceptions to provide more descriptive error messages [#121](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/121) + - Updated regex and parsers to determine errors in the user command for more descriptive error messages [#182](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/182) + - Handled bug fixes pertaining to the parsers [#182](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/182), [#196](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/196) + - Added support for the removal of optional parameters and grades [#196](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/196), [#197](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/197) + +### Documentation + +- **User Guide** + - Added explanation for `add`, `grade` and `edit` commands [#96](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/96) + - Updated page of contents to fix formatting [#127](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/127) +- **Developer Guide** + - Added explanation and sequence diagrams for `grade` and `edit`[#127](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/127) + - Added explanation for `UI` component [#108](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/108) + +### Team Tasks +- Added introduction, page of contents, target user profile, value proposition, purpose of DG, explanation of notation to the Developer Guide [#127](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/127) +- Fixed formatting inconsistencies in the Developer Guide [#127](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/127) +- Added user stories, non-functional requirements, glossary and instructions for manual testing to the Developer guide [#127](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/127) +- Added significant JavaDoc documentation to describe the methods and implementation [#196](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/196) + +### Community +- Pull Requests Reviewed: + [#86](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/86), + [#100](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/100), + [#107](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/107), + [#115](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/115), + [#118](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/118), + [#123](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/123), + [#124](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/123), + [#171](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/171), + [#173](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/173) \ No newline at end of file diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/ngys117.md b/docs/team/ngys117.md new file mode 100644 index 0000000000..62f71c7c2f --- /dev/null +++ b/docs/team/ngys117.md @@ -0,0 +1,56 @@ +# Project Portfolio - Ng Yong Sheng + +## Overview + +Mod Happy is a command-line application written in Java which helps students manage their academics by keeping track of +their tasks and facilitating GPA calculations. + +The sections below are my contributions to this project. + +## Summary of Contributions +You can view my contributed code +[here](https://nus-cs2113-ay2122s2.github.io/tp-dashboard/?search=ngys117&breakdown=true). + +### Code Contributions + +- **New Features** + - Added the ability to delete modules and tasks, which was one of the minimum functional requirements for the product. +[#76](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/76) + - Added the ability to clear all modules and tasks. [#89](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/89) + - Created the help command, so that users can see valid formats for their specified command. +[#88](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/88) + - Implemented addition and removal of tags from tasks, as well as filtering of tasks with a specific tag, +allowing greater task customization for the user. [#100](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/100) + +- **Enhancements** + - Added prompt for user when deleting modules with tasks in them. +[#100](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/100) + +### Documentation +- **User Guide** + - Added explanations for delete, tag, list and help commands. +[#90](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/90/files), +[#100](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/100/files) +- **Developer Guide** + - Created the sequence diagram and wrote the explanation for the Tag Command. +[#106](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/106) + - Created the class diagram and wrote the explanation for the Command Class. +[#110](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/110) + +### Team Tasks +- Removed unused code and reduced code nesting. [#115](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/115), +[#172](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/115) +- Edited every sequence diagram and class diagram to be compliant with module requirements. +[#115](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/115) +- Linked issues to relevant milestones + +### Community +- Pull Requests Reviewed: +[#80](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/80), +[#83](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/83), +[#86](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/86), +[#92](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/92), +[#102](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/102), +[#108](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/108), +[#121](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/121), +[#123](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/123) diff --git a/docs/team/yzkkk.md b/docs/team/yzkkk.md new file mode 100644 index 0000000000..333c3a9a80 --- /dev/null +++ b/docs/team/yzkkk.md @@ -0,0 +1,52 @@ +# Yang Zikun - Project Portfolio Page + +## Overview: Mod Happy + +Mod Happy is a command-line application written in Java which helps students manage their academics by keeping track of their tasks and facilitating GPA calculations. + +The section below lists my contributions to this project. + +### Summary of Contributions + +You can view my contributed code [here](https://nus-cs2113-ay2122s2.github.io/tp-dashboard/?search=yzkkk&breakdown=true). + +### Code Contributions +- **New Features** + - Added the ability to add tasks, mark tasks and list tasks, although it was later overwritten to be more OOP-compliant and to accommodate more features. + [#73](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/73) + - Added the ability to compute GPA based on modular credits and grades. + [#117](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/107) +- **Enhancements** + - Added significant number of Junit test cases in `ModHappyParserTest`. + [#82](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/82), + [#114](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/114) + - Expanded exceptions and added more descriptive error messages. + [#123](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/123) + - Handled bug fixes pertaining to the parsers. + [#123](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/123) + - Added javadoc for the commands, parsers and exceptions. + [#123](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/123), + [#198](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/198) + +### Documentation +- **User Guide** + - Added Quick Start, `mark` and `list` commands. + [#95](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/95) + +- **Developer Guide** + - Added explanation and sequence diagram for `gpa`. + [#112](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/112) + +### Team Tasks + - Fixed formatting inconsistencies in the code and User guide. + [#179](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/179), + [#183](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/183), + [#198](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/198) + +### Community +- Pull Requests Reviewed: + [#110](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/110), + [#176](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/176), + [#180](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/180), + [#182](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/182), + [#197](https://github.com/AY2122S2-CS2113T-T10-3/tp/pull/197) \ No newline at end of file diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..7a39593554 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: seedu.duke.Main + diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/main/java/seedu/duke/Main.java b/src/main/java/seedu/duke/Main.java new file mode 100644 index 0000000000..ac1422fab9 --- /dev/null +++ b/src/main/java/seedu/duke/Main.java @@ -0,0 +1,98 @@ +package seedu.duke; + + +import seedu.duke.commands.Command; +import seedu.duke.commands.CommandResult; +import seedu.duke.commands.ExitCommand; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.parsers.ModHappyParser; +import seedu.duke.storage.ModHappyStorageManager; +import seedu.duke.data.ModuleList; +import seedu.duke.ui.TextUi; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; + + +public class Main { + + + private ModHappyParser modHappyParser; + private ModuleList moduleList; + private Configuration configuration; + + /** + * Main entry-point for the application. + * See addressbook-level2 + */ + public static void main(String[] args) { + new Main().run(); + } + + //@@author Ch40gRv1-Mu + /** + * Runs the program until termination. + * See addressbook-level2 + **/ + public void run() { + start(); + runCommandLoopUntilExitCommand(); + exit(); + } + + //@@author Ch40gRv1-Mu + /** + * Sets up the required objects. + */ + private void start() { + modHappyParser = new ModHappyParser(); + moduleList = new ModuleList(); + loadDataFromFile(); + TextUi.showHelloMessage(); + } + + //@@author chooyikai + /** + * Initialises the program data by attempting to read from the data files, if possible. + * If a data file is not found or contains invalid data, the file will be treated as blank instead. + */ + private void loadDataFromFile() { + String taskPath = StringConstants.TASK_PATH; + ModHappyStorageManager.loadTaskList(moduleList, taskPath); + String modulePath = StringConstants.MODULE_PATH; + ModHappyStorageManager.loadModuleList(moduleList, modulePath); + String configurationPath = StringConstants.CONFIGURATION_PATH; + configuration = ModHappyStorageManager.loadConfiguration(configurationPath); + } + + //@@author Ch40gRv1-Mu + /** + * Reads the user command and executes it, until the user calls the exit command. + * See addressbook-level2 + **/ + private void runCommandLoopUntilExitCommand() { + Command command = null; + String userCommandText; + do { + try { + userCommandText = TextUi.getUserCommand(); + command = modHappyParser.parseCommand(userCommandText); + CommandResult result = command.execute(moduleList, configuration); + TextUi.showMessage(result.toString()); + } catch (ModHappyException e) { + TextUi.showMessage(e); + } + } while (command == null || !ExitCommand.isExit); + } + + //@@author Ch40gRv1-Mu + /** + * Prints the goodbye message and exits. + * See addressbook-level2 + * */ + private void exit() { + TextUi.showGoodByeMessage(); + System.exit(0); + } + + +} diff --git a/src/main/java/seedu/duke/commands/AddCommand.java b/src/main/java/seedu/duke/commands/AddCommand.java new file mode 100644 index 0000000000..82d0fcd35b --- /dev/null +++ b/src/main/java/seedu/duke/commands/AddCommand.java @@ -0,0 +1,97 @@ +package seedu.duke.commands; + +import java.util.Objects; + +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.data.Module; +import seedu.duke.data.ModuleList; +import seedu.duke.data.Task; +import seedu.duke.data.TaskList; +import seedu.duke.exceptions.NoSuchModuleException; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; + +//@@author chooyikai +public class AddCommand extends Command { + public enum AddObjectType { + TASK, MODULE + } + + private static final String ADD_TASK_MESSAGE = StringConstants.ADD_TASK_MESSAGE_TOP + LS + "%s" + LS + LS; + private static final String ADD_MODULE_MESSAGE = StringConstants.ADD_MODULE_MESSAGE_TOP + LS + "%s"; + + private final AddObjectType typeToAdd; + private Task newTask = null; + private String targetModuleName = null; + private Module newModule = null; + + /** + * Constructs a new AddCommand object to add a new task. + * @param type Represents the type of object to be added + * @param taskName The name of the task to be added + * @param taskDescription The description of the task to be added, null if the description is empty + * @param estimatedWorkingTime The estimated working time of the task, null if it is empty + * @param taskModule The module the task falls under, null if the task falls under General Tasks + * @throws ModHappyException If the estimated working time of the task cannot be parsed correctly + */ + public AddCommand(AddObjectType type, String taskName, String taskDescription, String estimatedWorkingTime, + String taskModule) throws ModHappyException { + assert type == AddObjectType.TASK; + typeToAdd = type; + newTask = new Task(taskName, taskDescription, estimatedWorkingTime); + targetModuleName = taskModule; + } + + /** + * Constructs a new AddCommand object to add a new module. + * @param type Represents the type of object to be added + * @param moduleCode The code of the module to be added + * @param moduleDescription The description of the module to be added, null if it is empty + * @param modularCredit The number of modular credits the module has + */ + public AddCommand(AddObjectType type, String moduleCode, String moduleDescription, int modularCredit) { + assert type == AddObjectType.MODULE; + typeToAdd = type; + newModule = new Module(moduleCode, moduleDescription, modularCredit); + } + + public Task getNewTask() { + return newTask; + } + + public String getTargetModuleName() { + return targetModuleName; + } + + public Module getNewModule() { + return newModule; + } + + /** + * Adds the specified task or module. + * @param moduleList The list of modules + * @param configuration The configuration settings of the application + * @return A new {@code CommandResult} with the result string + * @throws NoSuchModuleException If the module to be added does not exist + */ + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) throws NoSuchModuleException { + String res; + if (typeToAdd == AddObjectType.TASK) { + Module targetModule = moduleList.getGeneralTasks(); + if (!Objects.isNull(targetModuleName)) { + targetModule = moduleList.getModule(targetModuleName); + } + TaskList taskList = targetModule.getTaskList(); + res = String.format(ADD_TASK_MESSAGE, targetModule, taskList.addTask(newTask)); + } else { + assert typeToAdd == AddObjectType.MODULE; + if (!moduleList.isModuleExists(newModule.getModuleCode())) { + res = String.format(ADD_MODULE_MESSAGE, moduleList.addModule(newModule)); + } else { + res = StringConstants.MODULE_ALREADY_EXISTS; + } + } + return new CommandResult(res); + } +} diff --git a/src/main/java/seedu/duke/commands/Command.java b/src/main/java/seedu/duke/commands/Command.java new file mode 100644 index 0000000000..b42eaf95ee --- /dev/null +++ b/src/main/java/seedu/duke/commands/Command.java @@ -0,0 +1,15 @@ +package seedu.duke.commands; + +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.data.ModuleList; +import seedu.duke.util.Configuration; + +//@@author Ch40gRv1-Mu +/** + * Parent class of all commands in Mod Happy. + */ +public abstract class Command { + protected static final String LS = System.lineSeparator(); + + public abstract CommandResult execute(ModuleList moduleList, Configuration configuration) throws ModHappyException; +} diff --git a/src/main/java/seedu/duke/commands/CommandResult.java b/src/main/java/seedu/duke/commands/CommandResult.java new file mode 100644 index 0000000000..6f9d4f25ab --- /dev/null +++ b/src/main/java/seedu/duke/commands/CommandResult.java @@ -0,0 +1,15 @@ +package seedu.duke.commands; + +//@@author Ch40gRv1-Mu +public class CommandResult { + private final String resultString; + + public CommandResult(String result) { + this.resultString = result; + } + + @Override + public String toString() { + return resultString; + } +} diff --git a/src/main/java/seedu/duke/commands/DeleteCommand.java b/src/main/java/seedu/duke/commands/DeleteCommand.java new file mode 100644 index 0000000000..f5d8606344 --- /dev/null +++ b/src/main/java/seedu/duke/commands/DeleteCommand.java @@ -0,0 +1,126 @@ +package seedu.duke.commands; + +import java.util.Objects; + +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.NoSuchModuleException; +import seedu.duke.data.Module; +import seedu.duke.data.ModuleList; +import seedu.duke.data.TaskList; +import seedu.duke.ui.TextUi; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; +import seedu.duke.util.NumberConstants; + +//@@author ngys117 +public class DeleteCommand extends Command { + + private static final String DELETE_MESSAGE = StringConstants.DELETE_MESSAGE; + private static final String DELETE_ABORT = StringConstants.DELETE_ABORT; + private static final String DELETE_CONFIRMATION = StringConstants.DELETE_CONFIRMATION; + private static final String DELETE_CONFIRMATION_INPUT_ERROR = StringConstants.DELETE_CONFIRMATION_INPUT_ERROR; + + private String moduleCode; + private int taskIndex = NumberConstants.INVALID_TASK_INDEX; + private String taskModule; + private String result; + + public String getModuleCode() { + return moduleCode; + } + + public int getTaskIndex() { + return taskIndex; + } + + public String getTaskModule() { + return taskModule; + } + + public DeleteCommand(String moduleCode) { + this.moduleCode = moduleCode; + } + + public DeleteCommand(int taskIndex, String taskModule) { + this.taskIndex = taskIndex; + this.taskModule = taskModule; + } + + /** + * Deletes the specified task or module. + * @param moduleList The list of modules + * @param configuration The configuration settings of the application + * @return A new {@code CommandResult} with the result string + * @throws ModHappyException If the module or task specified does not exist + */ + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) throws ModHappyException { + if (!Objects.isNull(moduleCode)) { + deleteModule(moduleList); + return new CommandResult(result); + } + Module targetModule; + if (Objects.isNull(taskModule)) { + targetModule = moduleList.getGeneralTasks(); + } else { + targetModule = moduleList.getModule(taskModule); + } + if (Objects.isNull(targetModule)) { + throw new NoSuchModuleException(); + } + deleteTaskFromModule(targetModule); + return new CommandResult(result); + } + + /** + * Deletes given module from moduleList. + * + * @param moduleList List from which the module is to be deleted from. + * @throws NoSuchModuleException If the module does not exist + */ + public void deleteModule(ModuleList moduleList) throws NoSuchModuleException { + Module targetModule = moduleList.getModule(moduleCode); + if (targetModule.getTaskList().getSize() > 0) { + Boolean hasDeleteConfirmation = getUserConfirmation(targetModule); + if (!hasDeleteConfirmation) { + result = DELETE_ABORT; + return; + } + } + Module removedModule = moduleList.removeModule(moduleCode); + result = String.format(DELETE_MESSAGE, removedModule); + } + + /** + * Deletes a task from the given module. + * + * @param targetModule The module from which to delete the task + * @throws ModHappyException If the task to be deleted does not exist + */ + public void deleteTaskFromModule(Module targetModule) throws ModHappyException { + TaskList taskList = targetModule.getTaskList(); + result = String.format(DELETE_MESSAGE, taskList.removeTask(taskIndex)); + } + + /** + * Gets confirmation from user to delete given module. + * + * @param module Module to be deleted. + * @return Returns true if user input is "yes", false if "no". + */ + public Boolean getUserConfirmation(Module module) { + String prompt = String.format(DELETE_CONFIRMATION, module); + TextUi.showMessage(prompt); + String userConfirmation; + while (true) { + userConfirmation = TextUi.getUserCommand(); + if (userConfirmation.equals("yes")) { + return true; + } else if (userConfirmation.equals("no")) { + return false; + } else { + TextUi.showMessage(DELETE_CONFIRMATION_INPUT_ERROR); + } + } + } +} diff --git a/src/main/java/seedu/duke/commands/EditCommand.java b/src/main/java/seedu/duke/commands/EditCommand.java new file mode 100644 index 0000000000..d8152409e5 --- /dev/null +++ b/src/main/java/seedu/duke/commands/EditCommand.java @@ -0,0 +1,149 @@ +package seedu.duke.commands; + +import java.util.Objects; + +import seedu.duke.data.TaskParameters; +import seedu.duke.data.ModuleList; +import seedu.duke.data.Task; +import seedu.duke.data.TaskList; +import seedu.duke.data.Module; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.NoSuchModuleException; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; +import seedu.duke.util.NumberConstants; + +//@@author heekit73098 +public class EditCommand extends Command { + + private static final String EDIT_MODULE_SUCCESS = StringConstants.EDIT_MODULE_SUCCESS; + private static final String EDIT_TASK_SUCCESS = StringConstants.EDIT_TASK_SUCCESS; + private static final String EDIT_TASK_WITH_MODULE_SUCCESS = StringConstants.EDIT_TASK_WITH_MODULE_SUCCESS; + private static final String TASK_DESCRIPTION = StringConstants.TASK_DESCRIPTION_STR; + private static final String TASK_ESTIMATED_WORKING_TIME = StringConstants.TASK_ESTIMATED_WORKING_TIME_STR; + private static final String TASK_NAME = StringConstants.TASK_NAME_STR; + + private String moduleCode; + private String taskModule; + private int taskIndex = NumberConstants.INVALID_TASK_INDEX; + private String taskParameter; + private String result = ""; + private boolean isGeneralTask = false; + private final String changedParameter; + + public int getTaskIndex() { + return taskIndex; + } + + public String getModuleCode() { + return moduleCode; + } + + public String getTaskModule() { + return taskModule; + } + + public EditCommand(String moduleCode, String description) { + this.moduleCode = moduleCode; + this.changedParameter = description; + } + + /** + * Constructs a new EditCommand object to edit a task. + * @param taskModule The task that the module belongs to, null if it falls under General Tasks + * @param taskIndex The zero-based index of the task + * @param taskParameter The parameter to be changed + * @param taskParameterType Enumeration of TASK_NAME_ONLY, DESCRIPTION_ONLY and WORKING_TIME_ONLY + */ + public EditCommand(String taskModule, int taskIndex, + String taskParameter, TaskParameters taskParameterType) { + this.taskModule = taskModule; + this.taskIndex = taskIndex; + if (taskParameter.isBlank()) { + this.changedParameter = null; + } else { + this.changedParameter = taskParameter; + } + switch (taskParameterType) { + case DESCRIPTION_ONLY: + this.taskParameter = TASK_DESCRIPTION; + break; + case WORKING_TIME_ONLY: + this.taskParameter = TASK_ESTIMATED_WORKING_TIME; + break; + default: + this.taskParameter = TASK_NAME; + } + } + + /** + * Gets the module that the target task belongs to, or General Tasks if it does not belong to any module. + * @param moduleList List of modules from which the target task belongs to, or General Tasks if it does not + * belong to any module. + * @return The module the target task belongs to, or General Tasks if it does not belong to any module. + * @throws NoSuchModuleException If the target module does not exist + */ + private Module getTargetModule(ModuleList moduleList) throws NoSuchModuleException { + Module targetModule; + if (Objects.isNull(taskModule)) { + isGeneralTask = true; + targetModule = moduleList.getGeneralTasks(); + } else { + targetModule = moduleList.getModule(taskModule); + } + return targetModule; + } + + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) throws ModHappyException { + if (!Objects.isNull(moduleCode)) { + editModuleDescription(moduleList); + } else { + Module targetModule = getTargetModule(moduleList); + editTaskFromModule(targetModule); + } + return new CommandResult(result); + } + + /** + * Changes module description of the target module. + * + * @param moduleList List from which the module's description is to be edited. + * @throws NoSuchModuleException If the module to be edited does not exist + */ + public void editModuleDescription(ModuleList moduleList) throws NoSuchModuleException { + Module targetModule = moduleList.getModule(moduleCode); + targetModule.setModuleDescription(changedParameter); + result = String.format(EDIT_MODULE_SUCCESS, targetModule.getModuleCode()); + } + + /** + * Changes task parameter (either task name, description or estimated working time) of the target task. + * + * @param targetModule The module (or General Tasks) the target task belongs to. + * @throws ModHappyException If the task to be edited does not exist, or if the working time entered cannot be + * parsed correctly + */ + private void editTaskFromModule(Module targetModule) throws ModHappyException { + TaskList taskList = targetModule.getTaskList(); + Task targetTask = taskList.getTask(taskIndex); + String targetTaskName = targetTask.getTaskName(); + switch (taskParameter) { + case TASK_DESCRIPTION: + targetTask.setTaskDescription(changedParameter); + break; + case TASK_ESTIMATED_WORKING_TIME: + targetTask.setWorkingTime(changedParameter); + break; + default: + targetTask.setTaskName(changedParameter); + } + if (isGeneralTask) { + result = String.format(EDIT_TASK_SUCCESS, taskParameter, targetTaskName); + } else { + result = String.format(EDIT_TASK_WITH_MODULE_SUCCESS, taskParameter, + targetTaskName, targetModule.getModuleCode()); + } + } + +} diff --git a/src/main/java/seedu/duke/commands/ExitCommand.java b/src/main/java/seedu/duke/commands/ExitCommand.java new file mode 100644 index 0000000000..7b7e6e8c36 --- /dev/null +++ b/src/main/java/seedu/duke/commands/ExitCommand.java @@ -0,0 +1,25 @@ +package seedu.duke.commands; + +import seedu.duke.data.ModuleList; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +public class ExitCommand extends Command { + + public static boolean isExit = false; + + /** + * Prepares the program for termination. + * @param moduleList The list of modules + * @param configuration The configuration settings of the application + * @return A new {@code CommandResult} with the result string + */ + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) { + // TODO: ask the user whether changes should be saved, if unsaved changes were detected. + CommandResult result = new CommandResult(StringConstants.READY_EXIT); + isExit = true; + return result; + } +} diff --git a/src/main/java/seedu/duke/commands/GpaCommand.java b/src/main/java/seedu/duke/commands/GpaCommand.java new file mode 100644 index 0000000000..80da1608c5 --- /dev/null +++ b/src/main/java/seedu/duke/commands/GpaCommand.java @@ -0,0 +1,67 @@ +package seedu.duke.commands; + +import seedu.duke.exceptions.GpaNotComputableException; +import seedu.duke.data.Module; +import seedu.duke.data.ModuleList; +import seedu.duke.util.Configuration; +import seedu.duke.util.Grades; +import seedu.duke.util.NumberConstants; +import seedu.duke.util.StringConstants; + +//@@author Yzkkk +public class GpaCommand extends Command { + + private static final String GPA_MESSAGE = StringConstants.GPA_MESSAGE; + private static final int MAXIMUM_TOTAL_CREDITS = NumberConstants.MAXIMUM_TOTAL_CREDITS; + + private String result; + + /** + * Calculates GPA based on currently stored module grades. + * @param moduleList List from which the grades are retrieved + * @throws GpaNotComputableException If the gpa is not computable + */ + public void calculateGpa(ModuleList moduleList) throws GpaNotComputableException { + int totalMc = 0; + double weightedSum = 0.0; + for (Module m : moduleList.getModuleList()) { + Grades modularGrade = m.getModuleGrade(); + switch (modularGrade) { + case CS: + case CU: + case S: + case U: + case NOT_ENTERED: + // Intentional fallthrough + break; + default: + int mc = m.getModularCredit(); + double modularGradePoint = m.getModuleGrade().getPoints(); + totalMc += mc; + weightedSum += modularGradePoint * mc; + } + if (totalMc > MAXIMUM_TOTAL_CREDITS) { + // Prevent integer overflow + throw new GpaNotComputableException(); + } + } + if (totalMc == 0) { + throw new GpaNotComputableException(); + } + double gpa = weightedSum / (double) totalMc; + result = String.format(GPA_MESSAGE, gpa); + } + + /** + * Calculates the gpa. + * @param moduleList The list of modules + * @param configuration The configuration settings of the application + * @return A new {@code CommandResult} with the result string + * @throws GpaNotComputableException If the gpa is not computable + */ + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) throws GpaNotComputableException { + calculateGpa(moduleList); + return new CommandResult(result); + } +} diff --git a/src/main/java/seedu/duke/commands/GradeCommand.java b/src/main/java/seedu/duke/commands/GradeCommand.java new file mode 100644 index 0000000000..b92bbce027 --- /dev/null +++ b/src/main/java/seedu/duke/commands/GradeCommand.java @@ -0,0 +1,67 @@ +package seedu.duke.commands; + +import java.util.Objects; + +import seedu.duke.data.Module; +import seedu.duke.data.ModuleList; +import seedu.duke.exceptions.NoSuchModuleException; +import seedu.duke.exceptions.InvalidGradeRemovalException; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; +import seedu.duke.util.Grades; + +//@@author heekit73098 +public class GradeCommand extends Command { + private static final String GRADE_ADDED_MESSAGE = StringConstants.GRADE_ADDED_MESSAGE; + private static final String GRADE_CHANGED_MESSAGE = StringConstants.GRADE_CHANGED_MESSAGE; + + private final String moduleCode; + private final String moduleGrade; + private String result; + + public String getModuleCode() { + return moduleCode; + } + + public String getModuleGrade() { + return moduleGrade; + } + + public GradeCommand(String moduleCode, String moduleGrade) { + this.moduleCode = moduleCode; + this.moduleGrade = moduleGrade; + } + + /** + * Sets a grade to the specified module. + * @param moduleList The list of modules + * @param configuration The configuration settings of the application + * @return A new {@code CommandResult} with the result string + * @throws NoSuchModuleException If the module does not exist + * @throws InvalidGradeRemovalException If the grade to be removed was not set in the first place + */ + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) + throws NoSuchModuleException, InvalidGradeRemovalException { + Module targetModule = moduleList.getModule(moduleCode); + addGradeToModule(targetModule); + return new CommandResult(result); + } + + /** + * Sets grade of the specified module. + * @param module The module specified for the grade to be set + * @throws InvalidGradeRemovalException If the grade to be removed was not set in the first place + */ + public void addGradeToModule(Module module) throws InvalidGradeRemovalException { + boolean hasGrade = !Objects.equals(module.getModuleGrade(), Grades.NOT_ENTERED); + if (hasGrade) { + result = String.format(GRADE_CHANGED_MESSAGE, moduleCode); + } else if (Grades.getGradeEnum(moduleGrade) != Grades.NOT_ENTERED) { + result = String.format(GRADE_ADDED_MESSAGE, moduleCode); + } else { + throw new InvalidGradeRemovalException(); + } + module.setModuleGrade(moduleGrade); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/commands/HelpCommand.java b/src/main/java/seedu/duke/commands/HelpCommand.java new file mode 100644 index 0000000000..bf6c82a850 --- /dev/null +++ b/src/main/java/seedu/duke/commands/HelpCommand.java @@ -0,0 +1,92 @@ +package seedu.duke.commands; + +import java.util.Objects; + +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.data.ModuleList; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; + +//@@author ngys117 +public class HelpCommand extends Command { + protected static final String EXIT_COMMAND_WORD = StringConstants.EXIT_COMMAND_WORD; + protected static final String ADD_COMMAND_WORD = StringConstants.ADD_COMMAND_WORD; + protected static final String DELETE_COMMAND_WORD = StringConstants.DELETE_COMMAND_WORD; + protected static final String EDIT_COMMAND_WORD = StringConstants.EDIT_COMMAND_WORD; + protected static final String GRADE_COMMAND_WORD = StringConstants.GRADE_COMMAND_WORD; + protected static final String GPA_COMMAND_WORD = StringConstants.GPA_COMMAND_WORD; + protected static final String LIST_COMMAND_WORD = StringConstants.LIST_COMMAND_WORD; + protected static final String MARK_COMMAND_WORD = StringConstants.MARK_COMMAND_WORD; + protected static final String OPTION_COMMAND_WORD = StringConstants.OPTION_COMMAND_WORD; + protected static final String RESET_COMMAND_WORD = StringConstants.RESET_COMMAND_WORD; + protected static final String SAVE_COMMAND_WORD = StringConstants.SAVE_COMMAND_WORD; + protected static final String TAG_COMMAND_WORD = StringConstants.TAG_COMMAND_WORD; + + protected static final String HELP_NOTE = StringConstants.HELP_NOTE; + protected static final String EXIT_HELP = StringConstants.EXIT_HELP; + protected static final String ADD_HELP = StringConstants.ADD_HELP; + protected static final String DELETE_HELP = StringConstants.DELETE_HELP; + protected static final String EDIT_HELP = StringConstants.EDIT_HELP; + protected static final String GRADE_HELP = StringConstants.GRADE_HELP; + protected static final String GPA_HELP = StringConstants.GPA_HELP; + protected static final String LIST_HELP = StringConstants.LIST_HELP; + protected static final String MARK_HELP = StringConstants.MARK_HELP; + protected static final String OPTION_HELP = StringConstants.OPTION_HELP; + protected static final String RESET_HELP = StringConstants.RESET_HELP; + protected static final String SAVE_HELP = StringConstants.SAVE_HELP; + protected static final String TAG_HELP = StringConstants.TAG_HELP; + protected static final String HELP = StringConstants.HELP; + protected static final String HELP_EXCEPTION = StringConstants.HELP_EXCEPTION; + + private final String command; + + public String getCommand() { + return command; + } + + public HelpCommand(String command) { + this.command = command; + } + + /** + * Displays help messages for different commands. + * @param moduleList The list of modules + * @param configuration The configuration settings of the application + * @return A new {@code CommandResult} with the result string + * @throws ModHappyException If the command given is unknown + */ + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) throws ModHappyException { + if (Objects.isNull(command)) { + return new CommandResult(HELP + "\n\n" + HELP_NOTE); + } + switch (command) { + case EXIT_COMMAND_WORD: + return new CommandResult(EXIT_HELP); + case ADD_COMMAND_WORD: + return new CommandResult(ADD_HELP); + case DELETE_COMMAND_WORD: + return new CommandResult(DELETE_HELP); + case EDIT_COMMAND_WORD: + return new CommandResult(EDIT_HELP); + case GRADE_COMMAND_WORD: + return new CommandResult(GRADE_HELP); + case GPA_COMMAND_WORD: + return new CommandResult(GPA_HELP); + case LIST_COMMAND_WORD: + return new CommandResult(LIST_HELP); + case MARK_COMMAND_WORD: + return new CommandResult(MARK_HELP); + case RESET_COMMAND_WORD: + return new CommandResult(RESET_HELP); + case SAVE_COMMAND_WORD: + return new CommandResult(SAVE_HELP); + case TAG_COMMAND_WORD: + return new CommandResult(TAG_HELP); + case OPTION_COMMAND_WORD: + return new CommandResult(OPTION_HELP + Configuration.getAllConfigurationExplanations()); + default: + throw new ModHappyException(HELP_EXCEPTION); + } + } +} diff --git a/src/main/java/seedu/duke/commands/ListCommand.java b/src/main/java/seedu/duke/commands/ListCommand.java new file mode 100644 index 0000000000..8dbec673c4 --- /dev/null +++ b/src/main/java/seedu/duke/commands/ListCommand.java @@ -0,0 +1,49 @@ +package seedu.duke.commands; + +import java.util.Objects; + +import seedu.duke.data.Module; +import seedu.duke.data.ModuleList; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; + +//@@author chooyikai +public class ListCommand extends Command { + private static final String LIST_MESSAGE = StringConstants.LIST_MESSAGE; + private final String argument; + + public String getArgument() { + return argument; + } + + public ListCommand(String argument) { + this.argument = argument; + } + + /** + * Lists all tasks when no argument is provided. Otherwise, list only tasks with matching tag. + * Depending on config settings, completed tasks may be hidden from the output. + * @param moduleList The list of modules + * @param configuration The configuration settings of the application + * @return A new {@code CommandResult} with the result string + */ + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) { + boolean showCompletedTasks = Boolean.parseBoolean(configuration.getConfigurationValue( + Configuration.ConfigurationGroup.SHOW_COMPLETED_TASKS)); + StringBuilder res = new StringBuilder(); + if (Objects.isNull(argument)) { + for (Module m : moduleList.getModuleList()) { + res.append(m.stringifyModuleTaskList(showCompletedTasks)).append(LS); + } + res.append(moduleList.getGeneralTasks().stringifyModuleTaskList(showCompletedTasks)); + } else { + for (Module m : moduleList.getModuleList()) { + res.append(m.stringifyModuleTaskListWithTag(argument, showCompletedTasks)).append(LS); + } + res.append(moduleList.getGeneralTasks().stringifyModuleTaskListWithTag(argument, showCompletedTasks)); + } + return new CommandResult(String.format(LIST_MESSAGE, res)); + } + +} diff --git a/src/main/java/seedu/duke/commands/MarkCommand.java b/src/main/java/seedu/duke/commands/MarkCommand.java new file mode 100644 index 0000000000..7a728b9193 --- /dev/null +++ b/src/main/java/seedu/duke/commands/MarkCommand.java @@ -0,0 +1,57 @@ +package seedu.duke.commands; + +import java.util.Objects; + +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.data.Module; +import seedu.duke.data.ModuleList; +import seedu.duke.data.Task; +import seedu.duke.data.TaskList; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; + +//@@author chooyikai +public class MarkCommand extends Command { + private static final String MARK_MESSAGE = StringConstants.MARK_MESSAGE_TOP + LS + "%s"; + private static final String UNMARK_MESSAGE = StringConstants.UNMARK_MESSAGE_TOP + LS + "%s"; + + private final int taskIndex; + private final String taskModuleString; + private final boolean status; + + public MarkCommand(int taskIndex, String taskModuleString, boolean status) { + this.taskIndex = taskIndex; + this.taskModuleString = taskModuleString; + this.status = status; + } + + public int getTaskIndex() { + return taskIndex; + } + + public String getTaskModuleString() { + return taskModuleString; + } + + /** + * Marks the specified task as completed or uncompleted. + * @param moduleList The list of modules + * @param configuration The configuration settings of the application + * @return A new {@code CommandResult} with the result string + * @throws ModHappyException If the task or the module it falls under does not exist + */ + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) throws ModHappyException { + Module targetModule = moduleList.getGeneralTasks(); + if (!Objects.isNull(taskModuleString)) { + targetModule = moduleList.getModule(taskModuleString); + } + TaskList taskList = targetModule.getTaskList(); + Task target = taskList.getTask(taskIndex); + target.setTaskDone(status); + if (status) { + return new CommandResult(String.format(MARK_MESSAGE, target)); + } + return new CommandResult(String.format(UNMARK_MESSAGE, target)); + } +} diff --git a/src/main/java/seedu/duke/commands/OptionCommand.java b/src/main/java/seedu/duke/commands/OptionCommand.java new file mode 100644 index 0000000000..8fba57e736 --- /dev/null +++ b/src/main/java/seedu/duke/commands/OptionCommand.java @@ -0,0 +1,75 @@ +package seedu.duke.commands; + +import java.util.Objects; + +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.UnknownConfigurationGroupWordException; +import seedu.duke.data.ModuleList; +import seedu.duke.exceptions.UnsupportedResultTypeException; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; + + +public class OptionCommand extends Command { + private static final String OPTION_CHECK_CONFIGURATIONS = StringConstants.OPTION_CHECK_CONFIGURATIONS; + private static final String OPTION_SET_SUCCESS = StringConstants.OPTION_SET_SUCCESS; + + private Configuration.ConfigurationGroup configurationGroup = null; + private String newValue = null; + + //@@author heekit73098 + + /** + * Constructs a new OptionCommand object. + * @param configurationGroupWord The configuration word inputted + * @param newValue The value of the configuration to be set + * @throws ModHappyException If the configuration word is unknown, + * or if the value is unsupported in the configuration word group + */ + public OptionCommand(String configurationGroupWord, String newValue) throws ModHappyException { + if (!Objects.isNull(configurationGroupWord)) { + try { + configurationGroup = Configuration.ConfigurationGroup.valueOf(configurationGroupWord); + } catch (IllegalArgumentException e) { + throw new UnknownConfigurationGroupWordException(configurationGroupWord); + } + if (!Configuration.LEGAL_VALUES.containsKey(configurationGroup)) { + throw new UnknownConfigurationGroupWordException(configurationGroupWord); + } + } + if (!Objects.isNull(newValue)) { + if (Configuration.LEGAL_VALUES.get(configurationGroup).contains(newValue)) { + this.newValue = newValue; + } else { + throw new UnsupportedResultTypeException(newValue, configurationGroupWord); + } + } + } + + //@@author Ch40gRv1-Mu + + /** + * Sets the new value or display help for the command depending on the command entered. + * @param moduleList The list of modules + * @param configuration The configuration settings of the application + * @return A new {@code CommandResult} with the result string + */ + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) { + // enter "option" to check the list of configuration setting + if (Objects.isNull(configurationGroup)) { + return new CommandResult(OPTION_CHECK_CONFIGURATIONS + StringConstants.LS + + configuration.getConfigurationsReport()); + } + + // enter "option " to check the legal values of a configuration group + if (Objects.isNull(newValue)) { + return new CommandResult(configurationGroup + StringConstants.LS + + configuration.getValueExplain(configurationGroup)); + } + + // enter "option =" to set legal value for a configuration group + configuration.configurationGroupHashMap.put(configurationGroup, newValue); + return new CommandResult(OPTION_SET_SUCCESS + configurationGroup + "=" + newValue); + } +} diff --git a/src/main/java/seedu/duke/commands/ResetCommand.java b/src/main/java/seedu/duke/commands/ResetCommand.java new file mode 100644 index 0000000000..1c4f2a610d --- /dev/null +++ b/src/main/java/seedu/duke/commands/ResetCommand.java @@ -0,0 +1,23 @@ +package seedu.duke.commands; + +import seedu.duke.data.ModuleList; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; + +//@@author ngys117 +public class ResetCommand extends Command { + + /** + * Resets all modules and tasks. + * @param moduleList The list of modules + * @param configuration The configuration settings of the application + * @return A new {@code CommandResult} with the result string + */ + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) { + moduleList.reset(); + assert (moduleList.getModuleList().size() == 0); + assert (moduleList.getGeneralTasks().getTaskList().getSize() == 0); + return new CommandResult(StringConstants.RESET_MESSAGE); + } +} diff --git a/src/main/java/seedu/duke/commands/SaveCommand.java b/src/main/java/seedu/duke/commands/SaveCommand.java new file mode 100644 index 0000000000..12a6d372fd --- /dev/null +++ b/src/main/java/seedu/duke/commands/SaveCommand.java @@ -0,0 +1,54 @@ +package seedu.duke.commands; + +import java.util.ArrayList; + +import seedu.duke.data.Module; +import seedu.duke.data.Task; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.storage.ModHappyStorageManager; +import seedu.duke.data.ModuleList; +import seedu.duke.data.TaskList; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +public class SaveCommand extends Command { + + /** + * Saves the existing list of general tasks and modules. + * @param moduleList The list of modules + * @param configuration The configuration settings of the application + * @return A new {@code CommandResult} with the result string + */ + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) { + // Even if there is an error writing to one file, we should still try to write to the others. + String writeStatus = ""; + try { + // Master Task List + TaskList taskList = moduleList.getGeneralTasks().getTaskList(); + ArrayList taskArrayList = taskList.getTaskList(); + ModHappyStorageManager.saveTaskList(taskArrayList); + writeStatus += StringConstants.TASK_DATA_SAVE_SUCCESS + StringConstants.LS; + } catch (ModHappyException e) { + writeStatus += e + StringConstants.LS; + writeStatus += StringConstants.TASK_DATA_SAVE_FAILED + StringConstants.LS; + } + try { + ArrayList moduleArrayList = moduleList.getModuleList(); + ModHappyStorageManager.saveModuleList(moduleArrayList); + writeStatus += StringConstants.MODULE_DATA_SAVE_SUCCESS + StringConstants.LS; + } catch (ModHappyException e) { + writeStatus += e + StringConstants.LS; + writeStatus += StringConstants.MODULE_DATA_SAVE_FAILED + StringConstants.LS; + } + try { + ModHappyStorageManager.saveConfiguration(configuration); + writeStatus += StringConstants.CONFIGURATION_DATA_SAVE_SUCCESS + StringConstants.LS; + } catch (ModHappyException e) { + writeStatus += e + StringConstants.LS; + writeStatus += StringConstants.CONFIGURATION_DATA_SAVE_FAILED + StringConstants.LS; + } + return new CommandResult(writeStatus); + } +} diff --git a/src/main/java/seedu/duke/commands/TagCommand.java b/src/main/java/seedu/duke/commands/TagCommand.java new file mode 100644 index 0000000000..86617466f1 --- /dev/null +++ b/src/main/java/seedu/duke/commands/TagCommand.java @@ -0,0 +1,101 @@ +package seedu.duke.commands; + +import java.util.Objects; + +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.GeneralParseException; +import seedu.duke.data.Module; +import seedu.duke.data.ModuleList; +import seedu.duke.data.Task; +import seedu.duke.data.TaskList; +import seedu.duke.exceptions.NoSuchTaskException; +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; + +//@@author ngys117 +public class TagCommand extends Command { + + private static final String ADD_TAG = StringConstants.ADD_COMMAND_WORD; + private static final String DEL_TAG = StringConstants.DELETE_COMMAND_WORD; + private static final String ADD_TAG_MESSAGE = StringConstants.ADD_TAG_MESSAGE; + private static final String DEL_TAG_MESSAGE = StringConstants.DEL_TAG_MESSAGE; + + private final String tagOperation; + private final int taskIndex; + private final String taskModule; + private final String tagDescription; + private String result; + + public int getTaskIndex() { + return taskIndex; + } + + public String getTagOperation() { + return tagOperation; + } + + public String getTaskModule() { + return taskModule; + } + + public String getTagDescription() { + return tagDescription; + } + + public TagCommand(String tagOperation, int taskIndex, String taskModule, String tagDescription) { + this.tagOperation = tagOperation; + this.taskIndex = taskIndex; + this.taskModule = taskModule; + this.tagDescription = tagDescription; + } + + /** + * Adds/deletes a tag to/from a task. + * @param moduleList The list of modules + * @param configuration The configuration settings of the application + * @return A new {@code CommandResult} with the result string + * @throws ModHappyException If the tag, task or module that the task falls under does not exist + */ + @Override + public CommandResult execute(ModuleList moduleList, Configuration configuration) throws ModHappyException { + Module targetModule; + if (Objects.isNull(taskModule)) { + targetModule = moduleList.getGeneralTasks(); + } else { + targetModule = moduleList.getModule(taskModule); + } + switch (tagOperation) { + case ADD_TAG: + addTag(targetModule); + return new CommandResult(result); + case DEL_TAG: + removeTag(targetModule); + return new CommandResult(result); + default: + throw new GeneralParseException(); + } + } + + /** + * Adds a tag to a task. + * + * @param targetModule Module that contains the task to be tagged + * @throws NoSuchTaskException If the task does not exist + */ + private void addTag(Module targetModule) throws NoSuchTaskException { + TaskList taskList = targetModule.getTaskList(); + result = String.format(ADD_TAG_MESSAGE, tagDescription, taskList.addTag(tagDescription, taskIndex)); + } + + /** + * Removes a tag from a task. + * + * @param targetModule Module that contains the task with the tag to be removed + * @throws ModHappyException If the task does not exist, or if the tag does not exist in the task + */ + private void removeTag(Module targetModule) throws ModHappyException { + TaskList taskList = targetModule.getTaskList(); + Task task = taskList.removeTag(tagDescription, taskIndex); + result = String.format(DEL_TAG_MESSAGE, tagDescription, task); + } +} diff --git a/src/main/java/seedu/duke/data/Module.java b/src/main/java/seedu/duke/data/Module.java new file mode 100644 index 0000000000..bfc99c66f8 --- /dev/null +++ b/src/main/java/seedu/duke/data/Module.java @@ -0,0 +1,113 @@ +package seedu.duke.data; + +import java.util.ArrayList; +import java.util.Objects; + +import seedu.duke.util.Grades; +import seedu.duke.util.StringConstants; + +//@@author chooyikai +public class Module { + private static final String LS = System.lineSeparator(); + private static final String MODULE_STRING_WITH_DESC = "%s (%s) (%sMC, Grade: %s)"; + private static final String MODULE_STRING_NO_DESC = "%s (%sMC, Grade: %s)"; + private static final String GENERAL_TASK_STRING = "%s"; + private static final String INDENT = StringConstants.INDENT; + private static final int NOT_APPLICABLE = 0; + + private final String moduleCode; + private String moduleDescription; + private Grades moduleGrade; + private boolean isGeneralTask; + private final int modularCredit; + private final TaskList taskList; + + public Module(String moduleCode) { + this(moduleCode, null, NOT_APPLICABLE); + isGeneralTask = true; + } + + public Module(String moduleCode, String moduleDescription, int modularCredit) { + this.moduleCode = moduleCode; + this.moduleDescription = moduleDescription; + this.taskList = new TaskList(); + this.modularCredit = modularCredit; + this.moduleGrade = Grades.NOT_ENTERED; + this.isGeneralTask = false; + } + + public String getModuleCode() { + return moduleCode; + } + + public void setModuleDescription(String description) { + if (!Objects.isNull(description) && !description.isBlank()) { + this.moduleDescription = description; + } else { + this.moduleDescription = null; + } + } + + public String getModuleDescription() { + return moduleDescription; + } + + public int getModularCredit() { + return modularCredit; + } + + public Grades getModuleGrade() { + return moduleGrade; + } + + public void setModuleGrade(String moduleGrade) { + this.moduleGrade = Grades.getGradeEnum(moduleGrade); + } + + /** + * Gets the task list associated with the module. + * @return Task list associated with the module + */ + public TaskList getTaskList() { + return taskList; + } + + public void setTaskArrayList(ArrayList list) { + taskList.setList(list); + } + + /** + * Adds one task in task list associated with the module. + * @param task The task to be added + */ + public void addTask(Task task) { + taskList.addTask(task); + } + + /** + * Formats the module and its associated tasks according to given parameters. + * @param showCompletedTasks Whether completed tasks should be shown or not + * @return The string of all tasks, depending on whether completed tasks should be shown + */ + public String stringifyModuleTaskList(boolean showCompletedTasks) { + return this + LS + taskList.getAllTasks(INDENT, showCompletedTasks); + } + + public String stringifyModuleTaskListWithTag(String tag, boolean showCompletedTasks) { + return this + LS + taskList.getTasksWithTag(INDENT, tag, showCompletedTasks); + } + + /** + * Formats the module as a string. + * @return The module as a string + */ + @Override + public String toString() { + if (isGeneralTask) { + return String.format(GENERAL_TASK_STRING, moduleCode); + } else if (moduleDescription != null) { + return String.format(MODULE_STRING_WITH_DESC, moduleCode, moduleDescription, modularCredit, moduleGrade); + } + return String.format(MODULE_STRING_NO_DESC, moduleCode, modularCredit, moduleGrade); + } +} diff --git a/src/main/java/seedu/duke/data/ModuleList.java b/src/main/java/seedu/duke/data/ModuleList.java new file mode 100644 index 0000000000..ad272a17bc --- /dev/null +++ b/src/main/java/seedu/duke/data/ModuleList.java @@ -0,0 +1,89 @@ +package seedu.duke.data; + +import seedu.duke.exceptions.NoSuchModuleException; + +import java.util.ArrayList; + +//@@author chooyikai +public class ModuleList { + private ArrayList list; + private final Module generalTasks; + + public ModuleList() { + list = new ArrayList<>(); + generalTasks = new Module("General tasks"); + } + + /** + * Adds the specified module to the module list, then returns it for convenience. + * @param module The module to be added + * @return The module added + */ + public Module addModule(Module module) { + list.add(module); + return module; + } + + /** + * Removes specified module from the module list. + * + * @param moduleCode The module code to be removed + * @return The module removed + */ + + public Module removeModule(String moduleCode) throws NoSuchModuleException { + Module module = getModule(moduleCode); + list.remove(module); + return module; + } + + /** + * Returns the module in the module list with the given module code. + * @param moduleCode The module code to search for + * @return The associated module if it exists + * @throws NoSuchModuleException If the module does not exist + */ + public Module getModule(String moduleCode) throws NoSuchModuleException { + for (Module m : list) { + if (m.getModuleCode().equals(moduleCode)) { + return m; + } + } + throw new NoSuchModuleException(); + } + + public ArrayList getModuleList() { + return list; + } + + public void setModuleList(ArrayList list) { + this.list = list; + } + + public Module getGeneralTasks() { + return generalTasks; + } + + public void initialiseGeneralTasksFromTaskList(ArrayList generalTaskList) { + generalTasks.setTaskArrayList(generalTaskList); + } + + public void reset() { + list.clear(); + generalTasks.getTaskList().reset(); + } + + /** + * Checks whether a module with the specified module code already exists in the module list. + * @param moduleCode The module code to check for + * @return True if a module in the module list already has that module code, or false otherwise + */ + public boolean isModuleExists(String moduleCode) { + try { + getModule(moduleCode); + return true; + } catch (NoSuchModuleException e) { + return false; + } + } +} diff --git a/src/main/java/seedu/duke/data/Task.java b/src/main/java/seedu/duke/data/Task.java new file mode 100644 index 0000000000..ca3a4e9d22 --- /dev/null +++ b/src/main/java/seedu/duke/data/Task.java @@ -0,0 +1,138 @@ +package seedu.duke.data; + +import java.util.ArrayList; +import java.util.Objects; + +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.util.StringConstants; + +//@@author chooyikai +public class Task { + public static final String ICON_UNCOMPLETED = StringConstants.ICON_UNCOMPLETED; + public static final String ICON_COMPLETED = StringConstants.ICON_COMPLETED; + public static final String TASK_STRING_NO_DESC_NO_TIME = "%s %s %s"; + public static final String TASK_STRING_WITH_DESC_NO_TIME = "%s %s (%s) %s"; + public static final String TASK_STRING_NO_DESC_WITH_TIME = "%s %s (" + + StringConstants.ESTIMATED_WORKING_TIME + "%s) %s"; + public static final String TASK_STRING_WITH_DESC_WITH_TIME = "%s %s (%s) (" + + StringConstants.ESTIMATED_WORKING_TIME + "%s) %s"; + + private boolean isTaskDone; + private String taskName; + private String taskDescription; + private TaskDuration workingTime; + private final ArrayList tags; + + public Task(String taskName, String taskDescription, String workingTime) throws ModHappyException { + this.taskName = taskName; + this.taskDescription = taskDescription; + if (!Objects.isNull(workingTime)) { + this.workingTime = new TaskDuration(workingTime); + } else { + this.workingTime = null; + } + isTaskDone = false; + tags = new ArrayList<>(); + + } + + public ArrayList getTagList() { + return tags; + } + + public String getTaskName() { + return taskName; + } + + public String getTaskDescription() { + return taskDescription; + } + + public String getWorkingTimeString() { + if (Objects.isNull(workingTime)) { + return null; + } else { + return workingTime.toString(); + } + } + + public TaskDuration getWorkingTime() { + return workingTime; + } + + public void setTaskDescription(String description) { + if (Objects.isNull(description) || description.isBlank()) { + taskDescription = null; + } else { + taskDescription = description; + } + } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + //@@author Ch40gRv1-Mu + public void setWorkingTime(String workingTime) throws ModHappyException { + if (!Objects.isNull(workingTime) && !workingTime.isBlank()) { + this.workingTime = new TaskDuration(workingTime); + } else { + this.workingTime = null; + } + } + + //@@author heekit73098 + /** + * Check what are the tasks parameters input by user. + * @return An enumeration of the task parameter status + */ + public TaskParameters getTaskParameterStatus() { + boolean hasTaskDescriptionAndWorkingTime = (taskDescription != null && workingTime != null); + boolean hasTaskDescriptionOnly = (taskDescription != null); + boolean hasWorkingTimeOnly = (workingTime != null); + if (hasTaskDescriptionAndWorkingTime) { + return TaskParameters.DESCRIPTION_AND_WORKING_TIME; + } else if (hasTaskDescriptionOnly) { + return TaskParameters.DESCRIPTION_ONLY; + } else if (hasWorkingTimeOnly) { + return TaskParameters.WORKING_TIME_ONLY; + } else { + return TaskParameters.NO_DESCRIPTION_OR_WORKING_TIME; + } + } + + //@@author chooyikai + public boolean getTaskDone() { + return isTaskDone; + } + + /** + * Sets the completion status of the task. + * @param status New task completion status + */ + public void setTaskDone(boolean status) { + isTaskDone = status; + } + + //@@author heekit73098 + /** + * Formats the task as a string. + * @return The string representation of the task + */ + @Override + public String toString() { + String taskStatusString = isTaskDone ? ICON_COMPLETED : ICON_UNCOMPLETED; + TaskParameters taskParameters = getTaskParameterStatus(); + switch (taskParameters) { + case DESCRIPTION_AND_WORKING_TIME: + return String.format(TASK_STRING_WITH_DESC_WITH_TIME, taskStatusString, taskName, + taskDescription, workingTime, tags); + case DESCRIPTION_ONLY: + return String.format(TASK_STRING_WITH_DESC_NO_TIME, taskStatusString, taskName, taskDescription, tags); + case WORKING_TIME_ONLY: + return String.format(TASK_STRING_NO_DESC_WITH_TIME, taskStatusString, taskName, workingTime, tags); + default: + return String.format(TASK_STRING_NO_DESC_NO_TIME, taskStatusString, taskName, tags); + } + } +} diff --git a/src/main/java/seedu/duke/data/TaskDuration.java b/src/main/java/seedu/duke/data/TaskDuration.java new file mode 100644 index 0000000000..4ece4488bd --- /dev/null +++ b/src/main/java/seedu/duke/data/TaskDuration.java @@ -0,0 +1,106 @@ +package seedu.duke.data; + +import java.time.Duration; +import java.util.Arrays; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.WrongDurationFormatException; +import seedu.duke.util.NumberConstants; +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +public class TaskDuration { + + private static final long MINUTE_PER_HOUR = NumberConstants.MINUTE_PER_HOUR; + private static final long MAXIMUM_ALLOWED_DURATION_NUMBER = NumberConstants.MAXIMUM_ALLOWED_DURATION_NUMBER; + private static final String DURATION_GROUP_WORD = StringConstants.DURATION_GROUP_WORD; + private static final String DURATION_UNIT_GROUP_WORD = StringConstants.DURATION_UNIT_GROUP_WORD; + private static final String TO_STRING_FORMAT_WITH_HOUR_AND_MINUTE = + StringConstants.TO_STRING_FORMAT_WITH_HOUR_AND_MINUTE; + private static final String TO_STRING_FORMAT_WITH_HOUR_ONLY = StringConstants.TO_STRING_FORMAT_WITH_HOUR_ONLY; + private static final String TO_STRING_FORMAT_WITH_MINUTE_ONLY = StringConstants.TO_STRING_FORMAT_WITH_MINUTE_ONLY; + private static final String DURATION_STRING_FORMAT = StringConstants.DURATION_STRING_FORMAT; + private static final String[] HOUR_UNIT_WORD + = {"h", "H", "hr", "Hr", "hrs", "Hrs", "hours", "Hours", "hour", "Hour"}; + private static final String[] MINUTE_UNIT_WORD + = {"m", "M", "min", "Min", "mins", "Mins", "minutes", "Minutes", "minute", "Minute"}; + + + protected Duration taskDuration; + + /** + * Constructs a new TaskDuration object to represent the duration of a task. + * @param durationString The duration, as a string + * @throws ModHappyException If the duration could not be properly parsed + */ + public TaskDuration(String durationString) throws ModHappyException { + HashMap parsedDurationString = parseDurationString(durationString); + + // the input unit is hours + if (Arrays.asList(HOUR_UNIT_WORD).contains(parsedDurationString.get(DURATION_UNIT_GROUP_WORD))) { + double numberOfHoursDouble = Double.parseDouble(parsedDurationString.get(DURATION_GROUP_WORD)); + if (numberOfHoursDouble > MAXIMUM_ALLOWED_DURATION_NUMBER) { + // Exceeds upperbound + throw new WrongDurationFormatException(); + } + long numberOfHoursInt = (long) numberOfHoursDouble; + taskDuration = Duration.ofHours(numberOfHoursInt); + long offSetMinute = Math.round(((numberOfHoursDouble - numberOfHoursInt) * MINUTE_PER_HOUR)); + taskDuration = taskDuration.plusMinutes(offSetMinute); + return; + } + + // the input unit is minutes + if (Arrays.asList(MINUTE_UNIT_WORD).contains(parsedDurationString.get(DURATION_UNIT_GROUP_WORD))) { + double numberOfMinutesDouble = Double.parseDouble(parsedDurationString.get(DURATION_GROUP_WORD)); + if (numberOfMinutesDouble > MAXIMUM_ALLOWED_DURATION_NUMBER) { + // Exceeds upperbound + throw new WrongDurationFormatException(); + } + taskDuration = Duration.ofMinutes( + Math.round(numberOfMinutesDouble)); + return; + } + + // no legal input unit match, throw exception + throw new WrongDurationFormatException(); + } + + private HashMap parseDurationString(String durationString) throws ModHappyException { + Pattern commandPattern = Pattern.compile(DURATION_STRING_FORMAT); + Matcher matcher = commandPattern.matcher(durationString.trim()); + HashMap parserDurationString = new HashMap<>(); + if (!matcher.matches()) { + throw new WrongDurationFormatException(); + } + parserDurationString.put(DURATION_UNIT_GROUP_WORD, matcher.group(DURATION_UNIT_GROUP_WORD).trim()); + parserDurationString.put(DURATION_GROUP_WORD, matcher.group(DURATION_GROUP_WORD).trim()); + return parserDurationString; + } + + public Duration getTaskDuration() { + return taskDuration; + } + + @Override + public String toString() { + long numberOfHours = taskDuration.toHours(); + long numberOfMinutes = taskDuration.toMinutes(); + + if (numberOfHours == 0) { + // The duration is less than 1 hour + return String.format(TO_STRING_FORMAT_WITH_MINUTE_ONLY, taskDuration.toMinutes()); + } else if (numberOfHours * MINUTE_PER_HOUR < numberOfMinutes) { + // The duration is more than 1 hour and has minute offset + long minuteOffset = numberOfMinutes - numberOfHours * MINUTE_PER_HOUR; + return String.format(TO_STRING_FORMAT_WITH_HOUR_AND_MINUTE, taskDuration.toHours(), minuteOffset); + } else { + // The duration is more than 1 hour but with no minute offset + return String.format(TO_STRING_FORMAT_WITH_HOUR_ONLY, taskDuration.toHours()); + } + + } +} diff --git a/src/main/java/seedu/duke/data/TaskList.java b/src/main/java/seedu/duke/data/TaskList.java new file mode 100644 index 0000000000..0613e9119c --- /dev/null +++ b/src/main/java/seedu/duke/data/TaskList.java @@ -0,0 +1,175 @@ +package seedu.duke.data; + +import java.util.ArrayList; + +import seedu.duke.exceptions.NoSuchTagException; +import seedu.duke.exceptions.NoSuchTaskException; +import seedu.duke.util.StringConstants; + +//@@author chooyikai +public class TaskList { + private static final String LS = StringConstants.LS; + private static final String ITEMIZE_FORMAT = "%d. %s" + LS; + private static final String EMPTY_LIST = StringConstants.EMPTY_LIST; + private static final String HIDDEN_TASKS_COUNT = StringConstants.HIDDEN_TASKS_COUNT; + + private ArrayList taskList; + + public TaskList() { + taskList = new ArrayList<>(); + } + + /** + * Gets the size of the task list. + * @return The size of the task list + */ + public int getSize() { + return taskList.size(); + } + + /** + * Adds the specified task to the task list, then returns the task for convenience. + * @param t The task to be added. + * @return The task added + */ + public Task addTask(Task t) { + taskList.add(t); + return t; + } + + //@@author ngys117 + /** + * Removes the specified task from the task list. + * @param index The index of task to be removed + * @return The task removed + * @throws NoSuchTaskException If the task does not exist + */ + public Task removeTask(int index) throws NoSuchTaskException { + if (index >= taskList.size()) { + throw new NoSuchTaskException(); + } + Task task = getTask(index); + taskList.remove(index); + return task; + } + + /** + * Adds tag to the task list. + * + * @param tagDescription The description of tag that is inputted by user. + * @param index The index of task to be added with tag + * @return The task which the tag was added + * @throws NoSuchTaskException If the user-supplied index is out of bounds + */ + public Task addTag(String tagDescription, int index) throws NoSuchTaskException { + if (index >= taskList.size()) { + throw new NoSuchTaskException(); + } + Task task = getTask(index); + ArrayList tags = task.getTagList(); + tags.add(tagDescription); + return task; + } + + /** + * Removes tag from the task list. + * + * @param tagDescription The description of tag that is inputted by user + * @param index The index of task to remove the tag + * @throws NoSuchTaskException If the user-supplied index is out of bounds + * @throws NoSuchTagException If the user-supplied tag to be removed does not exist + */ + public Task removeTag(String tagDescription, int index) throws NoSuchTaskException, NoSuchTagException { + if (index >= taskList.size()) { + throw new NoSuchTaskException(); + } + Task task = getTask(index); + ArrayList tags = task.getTagList(); + if (!tags.remove(tagDescription)) { + throw new NoSuchTagException(); + } + return task; + } + + //@@author chooyikai + public void setList(ArrayList list) { + taskList = list; + } + + /** + * Returns the task stored at the given index in the task list. + * @param index The index of the task + * @return The task at the given index + * @throws NoSuchTaskException If the task does not exist + */ + public Task getTask(int index) throws NoSuchTaskException { + if (index >= taskList.size() || index < 0) { + throw new NoSuchTaskException(); + } + return taskList.get(index); + } + + public ArrayList getTaskList() { + return taskList; + } + + /** + * Formats all tasks in the task list as a pretty printed string. + * + * @param indent String representing the indentation level for each task item + * @param showCompletedTasks Whether completed tasks should be listed + */ + public String getAllTasks(String indent, boolean showCompletedTasks) { + StringBuilder res = new StringBuilder(); + int numHiddenTasks = 0; + for (int i = 0; i < taskList.size(); i++) { + if (showCompletedTasks || !taskList.get(i).getTaskDone()) { + res.append(indent).append(String.format(ITEMIZE_FORMAT, i + 1, taskList.get(i))); + } else { + numHiddenTasks++; + } + } + if (res.length() == 0) { + res.append(indent).append(EMPTY_LIST).append(LS); + } + if (!showCompletedTasks && numHiddenTasks > 0) { + res.append(indent).append(String.format(HIDDEN_TASKS_COUNT, numHiddenTasks)).append(LS); + } + return res.toString(); + } + + /** + * Formats all tasks in the task list with a matching tag as a pretty printed string. + * + * @param indent String representing the indentation level for each task item + * @param tag The tag to be matched + * @param showCompletedTasks Whether completed tasks should be listed + */ + public String getTasksWithTag(String indent, String tag, boolean showCompletedTasks) { + StringBuilder res = new StringBuilder(); + int numHiddenTasks = 0; + for (int i = 0; i < taskList.size(); i++) { + if (!taskList.get(i).getTagList().contains(tag)) { + // Skip this task as it does not contain the tag we want + continue; + } + if (showCompletedTasks || !taskList.get(i).getTaskDone()) { + res.append(indent).append(String.format(ITEMIZE_FORMAT, i + 1, taskList.get(i))); + } else { + numHiddenTasks++; + } + + } + if (res.length() == 0) { + res.append(indent).append(EMPTY_LIST).append(LS); + } + if (!showCompletedTasks && numHiddenTasks > 0) { + res.append(indent).append(String.format(HIDDEN_TASKS_COUNT, numHiddenTasks)).append(LS); + } + return res.toString(); + } + + public void reset() { + taskList.clear(); + } +} diff --git a/src/main/java/seedu/duke/data/TaskParameters.java b/src/main/java/seedu/duke/data/TaskParameters.java new file mode 100644 index 0000000000..527733f286 --- /dev/null +++ b/src/main/java/seedu/duke/data/TaskParameters.java @@ -0,0 +1,9 @@ +package seedu.duke.data; + +//@@author heekit73098 +/** + * Enumeration for describing which attributes of the Task object are populated. + */ +public enum TaskParameters { + DESCRIPTION_AND_WORKING_TIME, DESCRIPTION_ONLY, WORKING_TIME_ONLY, NO_DESCRIPTION_OR_WORKING_TIME, TASK_NAME_ONLY +} diff --git a/src/main/java/seedu/duke/exceptions/AdditionalParameterException.java b/src/main/java/seedu/duke/exceptions/AdditionalParameterException.java new file mode 100644 index 0000000000..54bd46a1e3 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/AdditionalParameterException.java @@ -0,0 +1,17 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author heekit73098 +/** + * Exception to be thrown when the user inputted additional parameter for command + * that only takes in a single command word. + */ +public class AdditionalParameterException extends GeneralParseException { + private static final String ERROR_MESSAGE = StringConstants.ERROR_ADDITIONAL_PARAMETER; + + public AdditionalParameterException() { + super(ERROR_MESSAGE); + } + +} diff --git a/src/main/java/seedu/duke/exceptions/DuplicateModuleException.java b/src/main/java/seedu/duke/exceptions/DuplicateModuleException.java new file mode 100644 index 0000000000..4da65193f3 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/DuplicateModuleException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author chooyikai +/** + * Exception to be thrown when duplicated modules are detected. + */ +public class DuplicateModuleException extends ModHappyException { + public static final String ERROR_MESSAGE = StringConstants.ERROR_DUPLICATE_MODULE; + + public DuplicateModuleException(String moduleCode) { + super(String.format(ERROR_MESSAGE, moduleCode)); + } +} diff --git a/src/main/java/seedu/duke/exceptions/EmptyParamException.java b/src/main/java/seedu/duke/exceptions/EmptyParamException.java new file mode 100644 index 0000000000..4934b9abe0 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/EmptyParamException.java @@ -0,0 +1,16 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author Yzkkk +/** + * Exception to be thrown when the user-supplied parameter is empty. + */ +public class EmptyParamException extends GeneralParseException { + private static final String ERROR_STRING = StringConstants.ERROR_EMPTY_PARAM; + + public EmptyParamException(String error) { + super(ERROR_MESSAGE + String.format(ERROR_STRING, error)); + } + +} diff --git a/src/main/java/seedu/duke/exceptions/ExcessArgumentException.java b/src/main/java/seedu/duke/exceptions/ExcessArgumentException.java new file mode 100644 index 0000000000..fc229b40b0 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/ExcessArgumentException.java @@ -0,0 +1,16 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author Yzkkk +/** + * Exception to be thrown when the user supplied an excess argument. + */ +public class ExcessArgumentException extends GeneralParseException { + private static final String ERROR_STRING = StringConstants.ERROR_EXCESS_ARGUMENT; + + public ExcessArgumentException(String error) { + super(ERROR_MESSAGE + String.format(ERROR_STRING, error)); + } + +} diff --git a/src/main/java/seedu/duke/exceptions/FileCreateFailException.java b/src/main/java/seedu/duke/exceptions/FileCreateFailException.java new file mode 100644 index 0000000000..d5c7193cc3 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/FileCreateFailException.java @@ -0,0 +1,16 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +/** + * Exception to be thrown when the storage file does not exist and cannot be created. + */ +public class FileCreateFailException extends ModHappyException { + private static final String ERROR_MESSAGE = StringConstants.ERROR_FILE_CREATE_FAIL; + + public FileCreateFailException() { + super(ERROR_MESSAGE); + } + +} diff --git a/src/main/java/seedu/duke/exceptions/GeneralParseException.java b/src/main/java/seedu/duke/exceptions/GeneralParseException.java new file mode 100644 index 0000000000..054d7f50cc --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/GeneralParseException.java @@ -0,0 +1,19 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author heekit73098 +/** + * Exception to be thrown when parsing is failed. + */ +public class GeneralParseException extends ModHappyException { + protected static final String ERROR_MESSAGE = StringConstants.ERROR_PARSE_FAILED; + + public GeneralParseException() { + super(ERROR_MESSAGE); + } + + public GeneralParseException(String errorMessage) { + super(errorMessage); + } +} diff --git a/src/main/java/seedu/duke/exceptions/GpaNotComputableException.java b/src/main/java/seedu/duke/exceptions/GpaNotComputableException.java new file mode 100644 index 0000000000..a639d8fac1 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/GpaNotComputableException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author Yzkkk +/** + * Exception to be thrown when the module list is empty therefore GPA is not computable. + */ +public class GpaNotComputableException extends ModHappyException { + private static final String ERROR_MESSAGE = StringConstants.ERROR_MODULE_LIST_EMPTY; + + public GpaNotComputableException() { + super(ERROR_MESSAGE); + } +} diff --git a/src/main/java/seedu/duke/exceptions/InvalidCompulsoryParameterException.java b/src/main/java/seedu/duke/exceptions/InvalidCompulsoryParameterException.java new file mode 100644 index 0000000000..fc2bc696f3 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidCompulsoryParameterException.java @@ -0,0 +1,20 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author heekit73098 +/** + * Exception to be thrown when the user's input does not match the command format. + */ +public class InvalidCompulsoryParameterException extends GeneralParseException { + private static final String ERROR_STRING = StringConstants.ERROR_PARSE_INVALID_PARAM; + private static final String ERROR_STRING_GENERAL = StringConstants.ERROR_PARSE_INVALID_PARAM_GENERAL; + + public InvalidCompulsoryParameterException() { + super(ERROR_MESSAGE + ERROR_STRING_GENERAL); + } + + public InvalidCompulsoryParameterException(String parameter, String error) { + super(ERROR_MESSAGE + String.format(ERROR_STRING, parameter, error)); + } +} diff --git a/src/main/java/seedu/duke/exceptions/InvalidConfigurationException.java b/src/main/java/seedu/duke/exceptions/InvalidConfigurationException.java new file mode 100644 index 0000000000..3d6b3707c6 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidConfigurationException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author chooyikai +/** + * Exception to be thrown when invalid configuration is found in loaded configuration data. + */ +public class InvalidConfigurationException extends ModHappyException { + public static final String ERROR_MESSAGE = StringConstants.ERROR_INVALID_CONFIGURATION; + + public InvalidConfigurationException() { + super(ERROR_MESSAGE); + } +} diff --git a/src/main/java/seedu/duke/exceptions/InvalidConfigurationValueException.java b/src/main/java/seedu/duke/exceptions/InvalidConfigurationValueException.java new file mode 100644 index 0000000000..0230003a90 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidConfigurationValueException.java @@ -0,0 +1,16 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; + +//@@author chooyikai +/** + * Exception to be thrown when invalid configuration value is detected. + */ +public class InvalidConfigurationValueException extends ModHappyException { + public static final String ERROR_MESSAGE = StringConstants.ERROR_INVALID_CONFIG_VALUE; + + public InvalidConfigurationValueException(Configuration.ConfigurationGroup group, String value) { + super(String.format(ERROR_MESSAGE, group, value)); + } +} diff --git a/src/main/java/seedu/duke/exceptions/InvalidFlagException.java b/src/main/java/seedu/duke/exceptions/InvalidFlagException.java new file mode 100644 index 0000000000..b318d77fa4 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidFlagException.java @@ -0,0 +1,22 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author Yzkkk +/** + * Exception to be thrown when invalid flag or missing flag is detected. + */ +public class InvalidFlagException extends GeneralParseException { + private static final String ERROR_STRING_INVALID = StringConstants.ERROR_INVALID_FLAG; + private static final String ERROR_STRING_MISSING = StringConstants.ERROR_MISSING_FLAG; + + //@@author heekit73098 + public InvalidFlagException() { + super(ERROR_MESSAGE + ERROR_STRING_MISSING); + } + + public InvalidFlagException(String error) { + super(ERROR_MESSAGE + String.format(ERROR_STRING_INVALID, error)); + } + +} diff --git a/src/main/java/seedu/duke/exceptions/InvalidGradeRemovalException.java b/src/main/java/seedu/duke/exceptions/InvalidGradeRemovalException.java new file mode 100644 index 0000000000..064fb8ad8e --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidGradeRemovalException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author heekit73098 +/** + * Exception to be thrown when user wants to remove non-existing grade of a module. + */ +public class InvalidGradeRemovalException extends ModHappyException { + public static final String ERROR_MESSAGE = StringConstants.ERROR_GRADE_REMOVAL_FAILED; + + public InvalidGradeRemovalException() { + super(ERROR_MESSAGE); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/exceptions/InvalidModuleCreditsException.java b/src/main/java/seedu/duke/exceptions/InvalidModuleCreditsException.java new file mode 100644 index 0000000000..05c1e830fb --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidModuleCreditsException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author chooyikai +/** + * Exception to be thrown when invalid modular credit is detected. + */ +public class InvalidModuleCreditsException extends ModHappyException { + public static final String ERROR_MESSAGE = StringConstants.ERROR_INVALID_MODULE_CREDITS; + + public InvalidModuleCreditsException(String moduleCode, int modularCredits) { + super(String.format(ERROR_MESSAGE, moduleCode, modularCredits)); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/exceptions/InvalidModuleException.java b/src/main/java/seedu/duke/exceptions/InvalidModuleException.java new file mode 100644 index 0000000000..39fede26c8 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidModuleException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author chooyikai +/** + * Exception to be thrown when invalid module is detected. + */ +public class InvalidModuleException extends ModHappyException { + public static final String ERROR_MESSAGE = StringConstants.ERROR_INVALID_MODULE; + + public InvalidModuleException() { + super(ERROR_MESSAGE); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/exceptions/InvalidModuleGradeException.java b/src/main/java/seedu/duke/exceptions/InvalidModuleGradeException.java new file mode 100644 index 0000000000..50fca3e667 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidModuleGradeException.java @@ -0,0 +1,22 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author Yzkkk +/** + * Exception to be thrown when invalid module grade is detected. + */ +public class InvalidModuleGradeException extends GeneralParseException { + private static final String ERROR_STRING_INVALID = StringConstants.ERROR_INVALID_MODULE_GRADE; + private static final String ERROR_STRING_MISSING = StringConstants.ERROR_MISSING_MODULE_GRADE; + + //@@author heekit73098 + public InvalidModuleGradeException() { + super(ERROR_MESSAGE + ERROR_STRING_MISSING); + } + + public InvalidModuleGradeException(String error) { + super(ERROR_MESSAGE + String.format(ERROR_STRING_INVALID, error)); + } + +} diff --git a/src/main/java/seedu/duke/exceptions/InvalidNumberException.java b/src/main/java/seedu/duke/exceptions/InvalidNumberException.java new file mode 100644 index 0000000000..5f3af08516 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidNumberException.java @@ -0,0 +1,25 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author heekit73098 +/** + * Exception to be thrown when invalid number format is detected. + */ +public class InvalidNumberException extends GeneralParseException { + private static final String ERROR_STRING = StringConstants.ERROR_INVALID_NUMBER; + private static final String ERROR_STRING_MODULAR_CREDIT = StringConstants.ERROR_INVALID_MODULAR_CREDIT; + + public InvalidNumberException(String parameter, String error) { + super(ERROR_MESSAGE + String.format(ERROR_STRING, parameter, error)); + } + + /** + * Thrown when the modular credit is invalid. + * @param error the invalid modular credit string + */ + public InvalidNumberException(String error) { + super(ERROR_MESSAGE + String.format(ERROR_STRING_MODULAR_CREDIT, error)); + } + +} diff --git a/src/main/java/seedu/duke/exceptions/InvalidTagOperationException.java b/src/main/java/seedu/duke/exceptions/InvalidTagOperationException.java new file mode 100644 index 0000000000..387f7d64c3 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidTagOperationException.java @@ -0,0 +1,20 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author Yzkkk +/** + * Exception to be thrown when user inputted an invalid tag operation. + */ +public class InvalidTagOperationException extends GeneralParseException { + private static final String ERROR_STRING_INVALID = StringConstants.ERROR_INVALID_TAG_OPERATION; + private static final String ERROR_STRING_MISSING = StringConstants.ERROR_MISSING_TAG_OPERATION; + + public InvalidTagOperationException() { + super(ERROR_MESSAGE + ERROR_STRING_MISSING); + } + + public InvalidTagOperationException(String error) { + super(ERROR_MESSAGE + String.format(ERROR_STRING_INVALID, error)); + } +} diff --git a/src/main/java/seedu/duke/exceptions/InvalidTaskException.java b/src/main/java/seedu/duke/exceptions/InvalidTaskException.java new file mode 100644 index 0000000000..dacfe6d888 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/InvalidTaskException.java @@ -0,0 +1,12 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author chooyikai +public class InvalidTaskException extends ModHappyException { + public static final String ERROR_MESSAGE = StringConstants.ERROR_INVALID_TASK_DATA; + + public InvalidTaskException() { + super(ERROR_MESSAGE); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/exceptions/MissingCompulsoryParameterException.java b/src/main/java/seedu/duke/exceptions/MissingCompulsoryParameterException.java new file mode 100644 index 0000000000..e8003e858d --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/MissingCompulsoryParameterException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author heekit73098 +/** + * Exception to be thrown when the user-supplied command has missing parameters. + */ +public class MissingCompulsoryParameterException extends GeneralParseException { + private static final String ERROR_STRING = StringConstants.ERROR_PARSE_MISSING_PARAM; + + public MissingCompulsoryParameterException(String parameter) { + super(ERROR_MESSAGE + String.format(ERROR_STRING, parameter)); + } +} diff --git a/src/main/java/seedu/duke/exceptions/MissingNumberException.java b/src/main/java/seedu/duke/exceptions/MissingNumberException.java new file mode 100644 index 0000000000..a3c1a2222b --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/MissingNumberException.java @@ -0,0 +1,21 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author heekit73098 +/** + * Exception to be thrown when the user-supplied command has missing numbers. + */ +public class MissingNumberException extends GeneralParseException { + private static final String ERROR_STRING = StringConstants.ERROR_MISSING_NUMBER; + private static final String ERROR_STRING_MODULAR_CREDITS = StringConstants.ERROR_MISSING_MODULAR_CREDIT; + + public MissingNumberException() { + super(ERROR_MESSAGE + ERROR_STRING_MODULAR_CREDITS); + } + + public MissingNumberException(String error) { + super(ERROR_MESSAGE + String.format(ERROR_STRING, error)); + } + +} diff --git a/src/main/java/seedu/duke/exceptions/ModHappyException.java b/src/main/java/seedu/duke/exceptions/ModHappyException.java new file mode 100644 index 0000000000..323a409b34 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/ModHappyException.java @@ -0,0 +1,24 @@ +package seedu.duke.exceptions; + +//@@author Ch40gRv1-Mu +/** + * Base class for all program-specific exceptions. + */ +public class ModHappyException extends Exception { + protected static final String LS = System.lineSeparator(); + public String errorMessage; + + public ModHappyException(String message) { + super(message); + errorMessage = message; + } + + public ModHappyException() { + } + + @Override + public String toString() { + return errorMessage; + } + +} diff --git a/src/main/java/seedu/duke/exceptions/NoSuchModuleException.java b/src/main/java/seedu/duke/exceptions/NoSuchModuleException.java new file mode 100644 index 0000000000..96db79cb06 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/NoSuchModuleException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author chooyikai +/** + * Exception to be thrown when the user-supplied module for further actions does not exist. + */ +public class NoSuchModuleException extends ModHappyException { + private static final String ERROR_MESSAGE = StringConstants.ERROR_NO_SUCH_MODULE; + + public NoSuchModuleException() { + super(ERROR_MESSAGE); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/exceptions/NoSuchTagException.java b/src/main/java/seedu/duke/exceptions/NoSuchTagException.java new file mode 100644 index 0000000000..3c5a3a6c75 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/NoSuchTagException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author ngys117 +/** + * Exception to be thrown when the user-supplied tag for further actions does not exist. + */ +public class NoSuchTagException extends ModHappyException { + private static final String ERROR_MESSAGE = StringConstants.ERROR_NO_SUCH_TAG; + + public NoSuchTagException() { + super(ERROR_MESSAGE); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/exceptions/NoSuchTaskException.java b/src/main/java/seedu/duke/exceptions/NoSuchTaskException.java new file mode 100644 index 0000000000..c916ee836f --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/NoSuchTaskException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author chooyikai +/** + * Exception to be thrown when the user-supplied task for further actions does not exist. + */ +public class NoSuchTaskException extends ModHappyException { + private static final String ERROR_MESSAGE = StringConstants.ERROR_NO_SUCH_TASK; + + public NoSuchTaskException() { + super(ERROR_MESSAGE); + } +} diff --git a/src/main/java/seedu/duke/exceptions/ReadException.java b/src/main/java/seedu/duke/exceptions/ReadException.java new file mode 100644 index 0000000000..f1bb697e4a --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/ReadException.java @@ -0,0 +1,19 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author chooyikai +/** + * Exception to be thrown when an error was encountered during reading of storage file. + */ +public class ReadException extends ModHappyException { + private static final String ERROR_MESSAGE = StringConstants.ERROR_READ_FILE; + + public ReadException() { + super(ERROR_MESSAGE); + } + + public ReadException(String additionalMessage) { + super(ERROR_MESSAGE + additionalMessage); + } +} diff --git a/src/main/java/seedu/duke/exceptions/UnknownCommandException.java b/src/main/java/seedu/duke/exceptions/UnknownCommandException.java new file mode 100644 index 0000000000..529c6538a9 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/UnknownCommandException.java @@ -0,0 +1,19 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +/** + * Exception to be thrown when command word entered by the user is not recognised. + */ +public class UnknownCommandException extends ModHappyException { + private static final String ERROR_MESSAGE = StringConstants.ERROR_UNKNOWN_COMMAND + LS + "\"%s\""; + + public UnknownCommandException() { + super(ERROR_MESSAGE); + } + + public UnknownCommandException(String userInput) { + super(String.format(ERROR_MESSAGE, userInput)); + } +} diff --git a/src/main/java/seedu/duke/exceptions/UnknownConfigurationGroupWordException.java b/src/main/java/seedu/duke/exceptions/UnknownConfigurationGroupWordException.java new file mode 100644 index 0000000000..2ab0b2caff --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/UnknownConfigurationGroupWordException.java @@ -0,0 +1,16 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +/** + * Exception to be thrown when configuration group entered by the user is not recognised. + */ +public class UnknownConfigurationGroupWordException extends ModHappyException { + + private static final String ERROR_MESSAGE = StringConstants.ERROR_UNKNOWN_CONFIGURATION_GROUP; + + public UnknownConfigurationGroupWordException(String userInput) { + super(String.format(ERROR_MESSAGE, userInput)); + } +} diff --git a/src/main/java/seedu/duke/exceptions/UnknownException.java b/src/main/java/seedu/duke/exceptions/UnknownException.java new file mode 100644 index 0000000000..abb36bd75d --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/UnknownException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +/** + * Exception to be thrown when command entered by the user is not recognised. + */ +public class UnknownException extends ModHappyException { + private static final String ERROR_MESSAGE = StringConstants.ERROR_CATCH_UNKNOWN_EXCEPTION; + + public UnknownException(String userInput) { + super(String.format(ERROR_MESSAGE, userInput)); + } +} diff --git a/src/main/java/seedu/duke/exceptions/UnsupportedResultTypeException.java b/src/main/java/seedu/duke/exceptions/UnsupportedResultTypeException.java new file mode 100644 index 0000000000..ca8e76428c --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/UnsupportedResultTypeException.java @@ -0,0 +1,12 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author heekit73098 +public class UnsupportedResultTypeException extends ModHappyException { + private static final String ERROR_MESSAGE = StringConstants.ERROR_UNSUPPORTED_RESULT_TYPE; + + public UnsupportedResultTypeException(String newValue, String configurationGroupWord) { + super(String.format(ERROR_MESSAGE, newValue, configurationGroupWord)); + } +} diff --git a/src/main/java/seedu/duke/exceptions/WriteException.java b/src/main/java/seedu/duke/exceptions/WriteException.java new file mode 100644 index 0000000000..e11f0f9433 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/WriteException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +/** + * Exception to be thrown when an error was encountered during writing of the storage file. + */ +public class WriteException extends ModHappyException { + private static final String ERROR_MESSAGE = StringConstants.ERROR_WRITE_FILE; + + public WriteException() { + super(ERROR_MESSAGE); + } +} diff --git a/src/main/java/seedu/duke/exceptions/WrongDurationFormatException.java b/src/main/java/seedu/duke/exceptions/WrongDurationFormatException.java new file mode 100644 index 0000000000..72abb14e4a --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/WrongDurationFormatException.java @@ -0,0 +1,17 @@ +package seedu.duke.exceptions; + +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +/** + * Exception to be thrown when the user-supplied duration for estimated working time is in the wrong format. + */ +public class WrongDurationFormatException extends ModHappyException { + private static final String ERROR_MESSAGE = StringConstants.ERROR_WRONG_DURATION_FORMAT; + + public WrongDurationFormatException() { + super(ERROR_MESSAGE); + } + + +} diff --git a/src/main/java/seedu/duke/parsers/AddModuleParser.java b/src/main/java/seedu/duke/parsers/AddModuleParser.java new file mode 100644 index 0000000000..b0848785cc --- /dev/null +++ b/src/main/java/seedu/duke/parsers/AddModuleParser.java @@ -0,0 +1,159 @@ +package seedu.duke.parsers; + +import java.util.HashMap; +import java.util.Objects; + +import seedu.duke.commands.AddCommand; +import seedu.duke.commands.Command; +import seedu.duke.exceptions.EmptyParamException; +import seedu.duke.exceptions.GeneralParseException; +import seedu.duke.exceptions.InvalidNumberException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.InvalidCompulsoryParameterException; +import seedu.duke.exceptions.MissingNumberException; +import seedu.duke.exceptions.MissingCompulsoryParameterException; +import seedu.duke.util.NumberConstants; +import seedu.duke.util.StringConstants; + + +/** + * This Parser supports the "add mod" command. + */ +public class AddModuleParser extends AddParser { + private static final String MODULE_CODE = StringConstants.MODULE_CODE; + private static final String MODULE_DESCRIPTION = StringConstants.MODULE_DESCRIPTION; + private static final String MODULE_DESCRIPTION_STR = StringConstants.MODULE_DESCRIPTION_STR; + private static final String MODULAR_CREDIT = StringConstants.MODULAR_CREDIT; + private static final int MAXIMUM_MODULAR_CREDITS = NumberConstants.MAXIMUM_MODULAR_CREDITS; + private static final int MINIMUM_MODULAR_CREDITS = NumberConstants.MINIMUM_MODULAR_CREDITS; + private String userInput; + + // Unescaped regex for testing (split across a few lines): + // (mod\s+(?\w+)(\s+(?-?\d+)(?=(\s+-d\s+\"[^\"]+\")|.*$))(\s+(-d\s+\" + // (?[^\"]+)\"))?)(?.*) + + /* Explanation for regex: + * + * mod\s+(?\w+) -- matches [mod moduleCode] + * Same as above, note that moduleCode does not require "", + * but must also be a single word composed of [a-zA-Z0-9_]. + * + * (\s+(?-?\d+)(?=(\s+-d\s+\"[^\"]+\")|.*$)) -- matches [modularCredit] + * Must be a number + * + * (\s+(-d\s+\"(?[^\"]+)\"))?)(?.*) -- matches [-d "moduleDescription"] if present. + * Optional + * Does not accept " as a valid character. + * + * (?.*) -- matches [invalid] + * Any other excess inputs + */ + + private static final String ADD_FORMAT = "(mod\\s+(?\\w+)(\\s+(?-?\\d+)" + + "(?=(\\s+-d\\s+\\\"[^\\\"]+\\\")|.*$))(\\s+(-d\\s+\\\"" + + "(?[^\\\"]+)\\\"))?)(?.*)"; + private static final String WORD_CHAR_ONLY = StringConstants.WORD_CHAR_ONLY; + private static final String UNRESTRICTED_INT = StringConstants.UNRESTRICTED_INT; + + public AddModuleParser() { + super(); + // See also https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html + commandFormat = ADD_FORMAT; + groupNames.add(MODULE_CODE); + groupNames.add(MODULE_DESCRIPTION); + groupNames.add(MODULAR_CREDIT); + groupNames.add(INVALID); + } + + //@@author heekit73098 + /** + * Determines the error that the user made in its command based on the compulsory parameters. + * It first checks if the user input has a module code, and if the code is made up of only word characters. + * Then it checks if the user input has a modular credit, and if the modular credit is an unrestricted integer + * @throws MissingCompulsoryParameterException If module code is missing + * @throws MissingNumberException If modular credit is missing + * @throws InvalidNumberException If the modular credit is not in unrestricted integer format + * @throws InvalidCompulsoryParameterException If the module code is not made up of only word characters + */ + @Override + public void determineError() throws MissingCompulsoryParameterException, MissingNumberException, + InvalidNumberException, InvalidCompulsoryParameterException { + String moduleCode; + try { + moduleCode = userInput.split(WHITESPACES)[FIRST_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new MissingCompulsoryParameterException(MODULE_CODE_STR); + } + if (!moduleCode.matches(WORD_CHAR_ONLY)) { + throw new InvalidCompulsoryParameterException(MODULE_CODE_STR, moduleCode); + } + String modularCredit; + try { + modularCredit = userInput.split(WHITESPACES)[SECOND_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new MissingNumberException(); + } + if (!modularCredit.matches(UNRESTRICTED_INT)) { + throw new InvalidNumberException(modularCredit); + } + throw new InvalidCompulsoryParameterException(); + } + + //@@author Yzkkk + /** + * Checks if the description is empty. + * @param moduleDescription The description of the module to be added + * @throws EmptyParamException If the module description is empty + */ + private void checkForEmptyDescription(String moduleDescription) throws EmptyParamException { + if (!Objects.isNull(moduleDescription) && moduleDescription.isBlank()) { + throw new EmptyParamException(MODULE_DESCRIPTION_STR); + } + } + + //@@author Yzkkk + /** + * Parses the modular credit from a string to an integer, with checks on its validity. + * @param modularCreditStr The string representation of the modular credit + * @return The modular credits as an integer + * @throws InvalidNumberException If the string cannot be parsed into an integer, + * or if the credits is not in the range of 0 to 20 inclusive + */ + private int parseModularCredit(String modularCreditStr) throws InvalidNumberException { + int modularCredit; + try { + modularCredit = Integer.parseInt(modularCreditStr); + if (modularCredit > MAXIMUM_MODULAR_CREDITS || modularCredit < MINIMUM_MODULAR_CREDITS) { + throw new NumberFormatException(); + } + } catch (NumberFormatException e) { + throw new InvalidNumberException(modularCreditStr); + } + return modularCredit; + } + + //@@author chooyikai + + /** + * Parses the user input and extracts the parameters based on the command format. + * @param userInput User input of the module code, modular credits and module description + * @return A new {@code AddCommand} object to add a new module + * @throws ModHappyException If there is an error parsing the command + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + this.userInput = userInput; + HashMap parsedArguments = parseString(userInput); + final String moduleCode = parsedArguments.get(MODULE_CODE); + final String moduleDescription = parsedArguments.get(MODULE_DESCRIPTION); + final String modularCreditStr = parsedArguments.get(MODULAR_CREDIT); + + if (!Objects.isNull(moduleCode)) { + int modularCredit = parseModularCredit(modularCreditStr); + checkForEmptyDescription(moduleDescription); + checksForExcessArg(); + return new AddCommand(AddCommand.AddObjectType.MODULE, moduleCode, moduleDescription, modularCredit); + } + throw new GeneralParseException(); + } +} diff --git a/src/main/java/seedu/duke/parsers/AddParser.java b/src/main/java/seedu/duke/parsers/AddParser.java new file mode 100644 index 0000000000..23810baccc --- /dev/null +++ b/src/main/java/seedu/duke/parsers/AddParser.java @@ -0,0 +1,22 @@ +package seedu.duke.parsers; + +import seedu.duke.exceptions.UnknownCommandException; + +//@@author heekit73098 +public abstract class AddParser extends Parser { + + public AddParser() { + super(); + } + + public static AddParser getParser(String commandType) throws UnknownCommandException { + switch (commandType) { + case TASK: + return new AddTaskParser(); + case MODULE: + return new AddModuleParser(); + default: + throw new UnknownCommandException(); + } + } +} diff --git a/src/main/java/seedu/duke/parsers/AddTaskParser.java b/src/main/java/seedu/duke/parsers/AddTaskParser.java new file mode 100644 index 0000000000..a4f8ced45a --- /dev/null +++ b/src/main/java/seedu/duke/parsers/AddTaskParser.java @@ -0,0 +1,166 @@ +package seedu.duke.parsers; + +import java.util.HashMap; +import java.util.Objects; + +import seedu.duke.commands.AddCommand; +import seedu.duke.commands.Command; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.GeneralParseException; +import seedu.duke.exceptions.EmptyParamException; +import seedu.duke.exceptions.MissingCompulsoryParameterException; +import seedu.duke.exceptions.InvalidCompulsoryParameterException; +import seedu.duke.util.StringConstants; + + +/** + * This Parser supports the "add task" command. + */ +public class AddTaskParser extends AddParser { + private static final String TASK_STR = StringConstants.TASK_STR; + private static final String TASK_DESCRIPTION_STR = StringConstants.TASK_DESCRIPTION_STR; + private static final String TASK_ESTIMATED_WORKING_TIME_STR = StringConstants.TASK_ESTIMATED_WORKING_TIME_STR; + private static final String TASK_NAME = StringConstants.TASK_NAME; + private static final String TASK_DESCRIPTION = StringConstants.TASK_DESCRIPTION; + private static final String TASK_ESTIMATED_WORKING_TIME = StringConstants.TASK_ESTIMATED_WORKING_TIME; + private static final String TASK_MODULE = StringConstants.TASK_MODULE; + private static final String MODULE_FLAG = StringConstants.MODULE_FLAG; + private static final String MODULE_CODE_STR = StringConstants.MODULE_CODE_STR; + private String userInput; + + // Unescaped regex for testing (split across a few lines): + // (task\s+\"(?[^\"]*)\"(\s+((?-m)\s+(?\w*)))?(\s+-d\s+\" + // (?[^\"]*)\")?(\s+-t\s+\"(?[^\"]*)\")?)(?.*) + + /* Explanation for regex: + * (task\s+\"(?[^\"]*)\" -- matches [task "taskName"]. + * (?-m) -- matches [-m] if present. Optional + * (?\w*)? -- matches [taskModule] if present. Optional + * if this is present, it must be paired with -m + * Note that taskModule does not require "", but must be a + * single word composed of [a-zA-Z0-9_]. + * (\s+-d\s+\"(?[^\"]*)\")? -- matches [-d "taskDescription"] if present. Optional + * (\s+-t\s+\"(?[^\"]+)\")?) -- matches [-t "estimatedWorkingTime"] if present. Optional + * -- None of the above fields accept " as a valid character. + * + * (?.*) -- matches [invalid] + * Any other excess inputs + */ + + private static final String ADD_FORMAT = "(task\\s+\\\"(?[^\\\"]*)\\\"(\\s+((?-m)\\s+" + + "(?\\w*)))?(\\s+-d\\s+\\\"(?[^\\\"]*)\\\")?(\\s+-t\\s+\\\"" + + "(?[^\\\"]*)\\\")?)(?.*)"; + private static final String QUOTED_UNRESTRICTED_STR = StringConstants.QUOTED_UNRESTRICTED_STR; + private static final String TASK_PARAMETERS_FLAG_NO_NAME = StringConstants.TASK_PARAMETERS_FLAG_NO_NAME; + + public AddTaskParser() { + super(); + // See also https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html + commandFormat = ADD_FORMAT; + groupNames.add(TASK_NAME); + groupNames.add(TASK_DESCRIPTION); + groupNames.add(TASK_MODULE); + groupNames.add(TASK_ESTIMATED_WORKING_TIME); + groupNames.add(INVALID); + groupNames.add(MODULE_FLAG); + } + + //@@author heekit73098 + /** + * Throws an exception depending on the error of the task name based on the compulsory parameters. + * It will check if the user input has the task name and if it is wrapped with double quotes. + * @throws MissingCompulsoryParameterException If the task name is missing in the input. + * @throws InvalidCompulsoryParameterException If the task name is not wrapped with double quotes + */ + @Override + public void determineError() throws MissingCompulsoryParameterException, InvalidCompulsoryParameterException { + String taskName; + try { + taskName = userInput.split(WHITESPACES)[FIRST_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new MissingCompulsoryParameterException(TASK_NAME_STR); + } + if (!taskName.matches(QUOTED_UNRESTRICTED_STR)) { + throw new InvalidCompulsoryParameterException(TASK_NAME_STR, taskName); + } + throw new InvalidCompulsoryParameterException(); + } + + //@@author Yzkkk + /** + * Checks if the entered parameters are empty. + * @param taskName The name of the task to be added + * @param taskDescription The description of the task to be added + * @param estimatedWorkingTime The estimated working time of the task to be added + * @throws EmptyParamException If any of the parameters are empty + */ + private void checksForEmptyParams(String taskName, String taskDescription, String estimatedWorkingTime) + throws EmptyParamException { + if (taskName.isBlank()) { + throw new EmptyParamException(TASK_STR); + } + if (!Objects.isNull(taskDescription) && taskDescription.isBlank()) { + throw new EmptyParamException(TASK_DESCRIPTION_STR); + } + if (!Objects.isNull(estimatedWorkingTime) && estimatedWorkingTime.isBlank()) { + throw new EmptyParamException(TASK_ESTIMATED_WORKING_TIME_STR); + } + } + + //@@author Yzkkk + /** + * Checks if the module code contains errors and throws exceptions for any error. + * It will first attempt to get the string of module code. + * Then it checks if the string contains any potential flags. + * If there are potential flags, the flags are removed, leaving the module code inputted. + * The inputted module code will then be checked if it is in the correct format of word characters only. + * @param moduleFlag The string captured if the user wants to input a module code + * @throws EmptyParamException If no module code is inputted + * @throws InvalidCompulsoryParameterException If there is a module code, + * but it is not made up of only word characters + */ + private void checksForErrorInModuleCode(String moduleFlag, String taskModule, + String invalid, boolean hasTaskParameter) + throws EmptyParamException, InvalidCompulsoryParameterException { + if (!Objects.isNull(moduleFlag)) { + boolean hasEmptyTaskModule = Objects.isNull(taskModule) || taskModule.isBlank(); + if (!hasEmptyTaskModule) { + return; + } + boolean hasEmptyInvalid = Objects.isNull(invalid) || invalid.isBlank(); + if (hasEmptyInvalid || hasTaskParameter) { + throw new EmptyParamException(MODULE_CODE_STR); + } + String error = invalid.split(TASK_PARAMETERS_FLAG_NO_NAME + QUOTED_UNRESTRICTED_STR)[ZEROTH_INDEX]; + throw new InvalidCompulsoryParameterException(MODULE_CODE_STR, error); + } + } + + //@@author chooyikai + /** + * Parses the user input and extracts the parameters based on the command format. + * @param userInput User input of the task name, task module, task description, estimated working time + * @return A new {@code AddCommand} object to add a new task + * @throws ModHappyException If there is an error parsing the command + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + this.userInput = userInput; + HashMap parsedArguments = parseString(userInput); + final String taskName = parsedArguments.get(TASK_NAME); + final String taskDescription = parsedArguments.get(TASK_DESCRIPTION); + final String estimatedWorkingTime = parsedArguments.get(TASK_ESTIMATED_WORKING_TIME); + final String taskModule = parsedArguments.get(TASK_MODULE); + final String moduleFlag = parsedArguments.get(MODULE_FLAG); + final String invalid = parsedArguments.get(INVALID); + if (!Objects.isNull(taskName)) { + boolean hasTaskParameter = !Objects.isNull(taskDescription) || !Objects.isNull(estimatedWorkingTime); + checksForErrorInModuleCode(moduleFlag, taskModule, invalid, hasTaskParameter); + checksForEmptyParams(taskName, taskDescription, estimatedWorkingTime); + checksForExcessArg(); + return new AddCommand(AddCommand.AddObjectType.TASK, taskName, taskDescription, estimatedWorkingTime, + taskModule); + } + throw new GeneralParseException(); + } +} diff --git a/src/main/java/seedu/duke/parsers/DeleteParser.java b/src/main/java/seedu/duke/parsers/DeleteParser.java new file mode 100644 index 0000000000..3a3bd78fe4 --- /dev/null +++ b/src/main/java/seedu/duke/parsers/DeleteParser.java @@ -0,0 +1,130 @@ +package seedu.duke.parsers; + +import java.util.HashMap; +import java.util.Objects; + +import seedu.duke.commands.Command; +import seedu.duke.commands.DeleteCommand; +import seedu.duke.exceptions.InvalidNumberException; +import seedu.duke.exceptions.MissingNumberException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.InvalidCompulsoryParameterException; +import seedu.duke.exceptions.MissingCompulsoryParameterException; +import seedu.duke.exceptions.UnknownCommandException; +import seedu.duke.util.StringConstants; + +/** + * This Parser supports the "del" command. + */ +public class DeleteParser extends Parser { + private static final String TASK_NUMBER = StringConstants.TASK_NUMBER; + private static final String TASK_MODULE = StringConstants.TASK_MODULE; + private static final String MODULE_CODE = StringConstants.MODULE_CODE; + private String userInput; + + // Unescaped regex for testing: + // (task\\s+(?\\d+)(\\s+-m\\s+(?\\w+))?|mod\\s+(?\\w+))(?.*) + private static final String DELETE_FORMAT = "(task\\s+(?\\d+)(\\s+-m\\s+(?\\w+))?" + + "|mod\\s+(?\\w+))(?.*)"; + private static final String POSITIVE_INT = StringConstants.POSITIVE_INT; + private static final String WORD_CHAR_ONLY = StringConstants.WORD_CHAR_ONLY; + + public DeleteParser() { + super(); + this.commandFormat = DELETE_FORMAT; + groupNames.add(TASK_NUMBER); + groupNames.add(TASK_MODULE); + groupNames.add(MODULE_CODE); + groupNames.add(INVALID); + } + + //@@author heekit73098 + /** + * Determines the error that the user made in its command based on the compulsory parameters. + * It will first determine the object type the command is trying to delete, + * and then check for errors within each specific command. + * @throws MissingNumberException If the task number is missing for a del task command + * @throws MissingCompulsoryParameterException If the module code is missing for a del mod command + * @throws InvalidNumberException If the task number is not in a positive integer format for a del task command + * @throws InvalidCompulsoryParameterException If the module code is not made up of all word characters + * for a del mod command + * @throws UnknownCommandException If the object type specified is not mod or task + */ + @Override + public void determineError() throws MissingNumberException, MissingCompulsoryParameterException, + InvalidNumberException, InvalidCompulsoryParameterException, UnknownCommandException { + String type = userInput.split(WHITESPACES)[ZEROTH_INDEX]; + switch (type) { + case TASK: + determineErrorForTask(); + break; + case MODULE: + determineErrorForModule(); + break; + default: + throw new UnknownCommandException(); + } + } + + /** + * Determines the error of the del tag command based on the compulsory parameters. + * It will check if the task number is present and if it is in a positive integer format. + * @throws MissingNumberException If the task number is missing + * @throws InvalidNumberException If the task number is not in a positive integer format + */ + public void determineErrorForTask() throws MissingNumberException, InvalidNumberException { + String taskNumber; + try { + taskNumber = userInput.split(WHITESPACES)[FIRST_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new MissingNumberException(TASK_NUMBER_STR); + } + if (!taskNumber.matches(POSITIVE_INT)) { + throw new InvalidNumberException(TASK_NUMBER_STR, taskNumber); + } + } + + /** + * Determines the error for a del mod command, based on its compulsory parameters. + * It will check if the module code is present and if it is made up only of word characters. + * @throws MissingCompulsoryParameterException If the module code is missing + * @throws InvalidCompulsoryParameterException If the module code is not made up of word characters only + */ + public void determineErrorForModule() throws MissingCompulsoryParameterException, + InvalidCompulsoryParameterException { + String moduleCode; + try { + moduleCode = userInput.split(WHITESPACES)[FIRST_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new MissingCompulsoryParameterException(MODULE_CODE_STR); + } + if (!moduleCode.matches(WORD_CHAR_ONLY)) { + throw new InvalidCompulsoryParameterException(MODULE_CODE_STR, moduleCode); + } + } + + //author @@ngys117 + /** + * Parses the user input and extracts the parameters based on the command format. + * @param userInput User input of the task number and task module or the module code + * @return A new {@code DeleteCommand} object to delete either a task or module + * @throws ModHappyException If there is an error parsing the command + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + this.userInput = userInput; + HashMap parsedArguments = parseString(userInput); + String taskNumberString = parsedArguments.get(TASK_NUMBER); + String taskModuleString = parsedArguments.get(TASK_MODULE); + String moduleCode = parsedArguments.get(MODULE_CODE); + checksForExcessArg(); + if (!Objects.isNull(moduleCode)) { + return new DeleteCommand(moduleCode); + } + if (!Objects.isNull(taskNumberString)) { + int taskIndex = parseIndex(taskNumberString); + return new DeleteCommand(taskIndex, taskModuleString); + } + throw new ModHappyException(); + } +} diff --git a/src/main/java/seedu/duke/parsers/EditModuleParser.java b/src/main/java/seedu/duke/parsers/EditModuleParser.java new file mode 100644 index 0000000000..979536977e --- /dev/null +++ b/src/main/java/seedu/duke/parsers/EditModuleParser.java @@ -0,0 +1,152 @@ +package seedu.duke.parsers; + +import java.util.HashMap; +import java.util.Objects; + +import seedu.duke.commands.Command; +import seedu.duke.commands.EditCommand; +import seedu.duke.exceptions.MissingCompulsoryParameterException; +import seedu.duke.exceptions.InvalidCompulsoryParameterException; +import seedu.duke.exceptions.InvalidFlagException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.util.StringConstants; + + +//@@author heekit73098 +/** + * This Parser supports the "edit mod" command. + */ +public class EditModuleParser extends EditParser { + + private static final String MODULE_CODE = StringConstants.MODULE_CODE; + private static final String MODULE_DESCRIPTION = StringConstants.MODULE_DESCRIPTION; + private String userInput; + + // Unescaped regex for testing + // (mod\s+(?\w+?(?=(\s+-d\s+)))(\s+(-d\s+\"(?[^\"]*)\")))(?.*) + + /* Explanation for regex: + * + * (mod\s+(?\w+?(?=(\s+-d\s+))) -- matches [mod moduleCode], matches flag -d + * + * (\s+(-d\s+\"(?[^\"]*)\"))) -- matches [-d "taskDescription"] can be empty + * + * (?.*) -- matches [invalid] + * Any other excess inputs + */ + private static final String EDIT_FORMAT = "(mod\\s+(?\\w+?(?=(\\s+-d\\s+)))" + + "(\\s+(-d\\s+\\\"(?[^\\\"]*)\\\")))(?.*)"; + private static final String ANY_FLAG = StringConstants.ANY_FLAG; + private static final String ANY_TEXT = StringConstants.ANY_TEXT; + private static final String WORD_CHAR_ONLY = StringConstants.WORD_CHAR_ONLY; + private static final String DESCRIPTION_FLAG = StringConstants.DESCRIPTION_FLAG; + private static final String QUOTED_UNRESTRICTED_STR = StringConstants.QUOTED_UNRESTRICTED_STR; + + public EditModuleParser() { + super(); + this.commandFormat = EDIT_FORMAT; + groupNames.add(MODULE_CODE); + groupNames.add(MODULE_DESCRIPTION); + groupNames.add(INVALID); + } + + /** + * Determines the error that the user made in its command based on the compulsory parameters. + * It will first check if the module code is present and if the module code is made up of word characters only. + * Then it will check if the module description is present and if the flag is correct and the module description is + * wrapped with double quotes. + * @throws MissingCompulsoryParameterException If either module code is missing or module description is missing + * @throws InvalidCompulsoryParameterException If either module code is not made up of all word characters or + * if module description is wrapped with double quotes + * @throws InvalidFlagException If the flag used for the module description is incorrect + */ + @Override + public void determineError() throws MissingCompulsoryParameterException, + InvalidCompulsoryParameterException, InvalidFlagException { + checkForErrorInModuleCode(); + checkForErrorInModuleDescription(); + throw new InvalidCompulsoryParameterException(); + } + + /** + * Checks if the error is in the module code. + * It will check if the module code is present and if the module code is made up of word characters only. + * @throws MissingCompulsoryParameterException If the module code is missing + * @throws InvalidCompulsoryParameterException If the module code is not made up of word characters only + */ + private void checkForErrorInModuleCode() throws MissingCompulsoryParameterException, + InvalidCompulsoryParameterException { + String moduleCode; + try { + moduleCode = userInput.split(WHITESPACES)[FIRST_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new MissingCompulsoryParameterException(MODULE_CODE_STR); + } + if (!moduleCode.matches(WORD_CHAR_ONLY)) { + throw new InvalidCompulsoryParameterException(MODULE_CODE_STR, moduleCode); + } + } + + /** + * Checks if the error is in the module description. + * It will check if the module description is present and if the flag is correct and the module description is + * wrapped with double quotes. + * @throws MissingCompulsoryParameterException If the module description is missing + * @throws InvalidCompulsoryParameterException If the module description is not wrapped with double quotes + * @throws InvalidFlagException If the flag used for the module description is invalid + */ + private void checkForErrorInModuleDescription() throws MissingCompulsoryParameterException, + InvalidCompulsoryParameterException, InvalidFlagException { + String moduleDescription; + try { + moduleDescription = userInput.split(DESCRIPTION_FLAG)[FIRST_INDEX]; + } catch (IndexOutOfBoundsException e) { + determineErrorInDescription(); + throw new MissingCompulsoryParameterException(MODULE_DESCRIPTION_STR); + } + if (!moduleDescription.matches(QUOTED_UNRESTRICTED_STR)) { + throw new InvalidCompulsoryParameterException(MODULE_DESCRIPTION_STR, moduleDescription); + } + } + + /** + * Determines the error in the module description. + * It will first check if there is a description / flag. + * Then it will check if the user input has a flag with its parameter wrapped in double quotes. + * If there is, it means that the user has inputted the wrong flag. + * @throws MissingCompulsoryParameterException if there is no description or flag + * @throws InvalidFlagException if the user input the wrong flag + */ + private void determineErrorInDescription() throws MissingCompulsoryParameterException, InvalidFlagException { + String moduleFlag; + try { + moduleFlag = userInput.split(WHITESPACES)[SECOND_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new MissingCompulsoryParameterException(MODULE_DESCRIPTION_STR); + } + + if (userInput.matches(ANY_TEXT + ANY_FLAG + QUOTED_UNRESTRICTED_STR)) { + throw new InvalidFlagException(moduleFlag); + } + } + + /** + * Parses the user input and extracts the parameters based on the command format. + * @param userInput User input of the module code and the module description + * @return A new {@code EditCommand} object to edit the module description + * @throws ModHappyException If there is an error parsing the command + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + this.userInput = userInput; + HashMap parsedArguments = parseString(userInput); + String moduleCode = parsedArguments.get(MODULE_CODE); + String moduleDescription = parsedArguments.get(MODULE_DESCRIPTION); + if (!Objects.isNull(moduleCode)) { + checksForExcessArg(); + return new EditCommand(moduleCode, moduleDescription); + } + throw new ModHappyException(); + } + +} diff --git a/src/main/java/seedu/duke/parsers/EditParser.java b/src/main/java/seedu/duke/parsers/EditParser.java new file mode 100644 index 0000000000..fcc6bdbe70 --- /dev/null +++ b/src/main/java/seedu/duke/parsers/EditParser.java @@ -0,0 +1,22 @@ +package seedu.duke.parsers; + +import seedu.duke.exceptions.UnknownCommandException; + +//@@author heekit73098 +public abstract class EditParser extends Parser { + + public EditParser() { + super(); + } + + public static EditParser getParser(String commandType) throws UnknownCommandException { + switch (commandType) { + case TASK: + return new EditTaskParser(); + case MODULE: + return new EditModuleParser(); + default: + throw new UnknownCommandException(); + } + } +} diff --git a/src/main/java/seedu/duke/parsers/EditTaskParser.java b/src/main/java/seedu/duke/parsers/EditTaskParser.java new file mode 100644 index 0000000000..ef47d51f54 --- /dev/null +++ b/src/main/java/seedu/duke/parsers/EditTaskParser.java @@ -0,0 +1,271 @@ +package seedu.duke.parsers; + +import java.util.HashMap; +import java.util.Objects; + +import seedu.duke.commands.Command; +import seedu.duke.commands.EditCommand; +import seedu.duke.data.TaskParameters; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.InvalidNumberException; +import seedu.duke.exceptions.InvalidCompulsoryParameterException; +import seedu.duke.exceptions.MissingNumberException; +import seedu.duke.exceptions.MissingCompulsoryParameterException; +import seedu.duke.exceptions.InvalidFlagException; +import seedu.duke.exceptions.EmptyParamException; +import seedu.duke.util.StringConstants; + + +//@@author heekit73098 +/** + * This Parser supports the "edit task" command. + */ +public class EditTaskParser extends EditParser { + + private static final String TASK_NUMBER = StringConstants.TASK_NUMBER; + private static final String TASK_NAME_STR = StringConstants.TASK_NAME_STR; + private static final String TASK_PARAMETER_FLAG = StringConstants.TASK_PARAMETER_FLAG; + private static final String TASK_PARAMETER = StringConstants.TASK_PARAMETER; + private static final String TASK_MODULE = StringConstants.TASK_MODULE; + private static final String EMPTY_STRING = StringConstants.EMPTY_STRING; + private static final String MODULE_FIELD_STR = StringConstants.MODULE_FIELD_STR; + private static final String TASK_NAME_FLAG = StringConstants.TASK_NAME_FLAG; + private static final String TASK_DESCRIPTION_FLAG = StringConstants.TASK_DESCRIPTION_FLAG; + private static final String TASK_ESTIMATED_WORKING_TIME_FLAG = StringConstants.TASK_ESTIMATED_WORKING_TIME_FLAG; + private String userInput; + + // Unescaped regex for testing + // (task\s+(?\d+)(\s+-m\s+(?\w+))?(?=\s+(-n|-d|-t)\s+\"[^\"]*\")(\s+(? + // (-n|-d|-t))\s+\"(?[^\"]*)\"))(?.*) + + /* Explanation for regex: + * (task\s+(?\d+) -- matches [task taskNumber]. + * + * (\s+-m\s+(?\w+))? -- matches [-m taskModule] if present. Optional + * Note that taskModule does not require "", but must be a + * single word composed of [a-zA-Z0-9_]. + * + * (?=\s+(-n|-d|-t)\s+\"[^\"]*\")) -- asserts that there must be one task parameter flag. + * + * (?(-n|-d|-t)) -- matches [(-n|-d|-t)] the parameter flag. + * + * \"(?[^\"]*)\" -- matches ["taskParameter"]. + * + * (?.*) -- matches [invalid] + * Any other excess inputs + + */ + private static final String EDIT_FORMAT = "(task\\s+(?\\d+)(\\s+-m\\s+(?\\w+))?" + + "(?=\\s+(-n|-d|-t)\\s+\\\"[^\\\"]*\\\")(\\s+(?(-n|-d|-t))\\s+\\\"" + + "(?[^\\\"]*)\\\"))(?.*)"; + private static final String POSITIVE_INT = StringConstants.POSITIVE_INT; + private static final String QUOTED_UNRESTRICTED_STR = StringConstants.QUOTED_UNRESTRICTED_STR; + private static final String TASK_PARAMETERS_FLAGS = StringConstants.TASK_PARAMETERS_FLAG; + private static final String TASK_MODULE_FLAG = StringConstants.TASK_MODULE_FLAG; + private static final String ANY_TEXT = StringConstants.ANY_TEXT; + private static final String ANY_FLAG = StringConstants.ANY_FLAG; + private static final String ANY_FLAG_NO_WHITESPACE = StringConstants.ANY_FLAG_NO_WHITESPACE; + + public EditTaskParser() { + super(); + this.commandFormat = EDIT_FORMAT; + groupNames.add(TASK_NUMBER); + groupNames.add(TASK_PARAMETER_FLAG); + groupNames.add(TASK_PARAMETER); + groupNames.add(TASK_MODULE); + groupNames.add(INVALID); + } + + /** + * Determines the error that the user made in the edit task command based of the compulsory parameters. + * It will first check if the task number is present and if it is in a positive integer format. + * Then it will check if there is a correct task parameter present and if the task parameter has the correct flag, + * and it is wrapped in double quotes. + * Lastly, if the task number and task parameters have no errors, there should be errors in the module code field. + * The module code will be checked if it is empty or if it is invalid. + * @throws MissingNumberException If the task number is missing + * @throws InvalidNumberException If the task number is not in a positive integer format + * @throws MissingCompulsoryParameterException If the task parameter is missing + * @throws InvalidCompulsoryParameterException If the task parameter is not wrapped with double quotes + * @throws EmptyParamException If the module code inputted is empty + * @throws InvalidFlagException If the flag for the task parameter is incorrect + */ + @Override + public void determineError() throws MissingNumberException, InvalidNumberException, + MissingCompulsoryParameterException, InvalidCompulsoryParameterException, + EmptyParamException, InvalidFlagException { + String taskNumber = checkErrorInTaskNumber(); + checkErrorInTaskParameter(); + reduceErrorScope(taskNumber); + determineErrorInModuleCode(); + } + + /** + * Checks if the error is in task number. + * It checks if the task number is present, and if it is in a positive integer format. + * @return The string representation of the task number if it is present + * @throws MissingNumberException If the task number is missing + * @throws InvalidNumberException If the task number is not in positive integer format + */ + private String checkErrorInTaskNumber() throws MissingNumberException, InvalidNumberException { + String taskNumber; + try { + taskNumber = userInput.split(WHITESPACES)[FIRST_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new MissingNumberException(TASK_NUMBER_STR); + } + if (!taskNumber.matches(POSITIVE_INT)) { + throw new InvalidNumberException(TASK_NUMBER_STR, taskNumber); + } + return taskNumber; + } + + /** + * Checks if the error is in the task parameter. + * It checks if the task parameter is present, and if the flag and parameter is correctly formatted. + * @throws MissingCompulsoryParameterException If the task parameter is missing + * @throws InvalidCompulsoryParameterException If the task parameter is not wrapped in double quotes + * @throws InvalidFlagException If the flag used to represent the parameter is incorrect + */ + private void checkErrorInTaskParameter() throws MissingCompulsoryParameterException, + InvalidCompulsoryParameterException, InvalidFlagException { + String taskParameter; + try { + taskParameter = userInput.split(TASK_PARAMETERS_FLAGS)[FIRST_INDEX]; + } catch (IndexOutOfBoundsException e) { + determineErrorInParameter(); + throw new MissingCompulsoryParameterException(TASK_PARAMETER_STR); + } + if (!taskParameter.matches(QUOTED_UNRESTRICTED_STR)) { + throw new InvalidCompulsoryParameterException(TASK_PARAMETER_STR, taskParameter); + } + } + + /** + * Gets the incorrect flag. + * @return The first incorrect flag, not including the module flag if it is present + */ + private String getParameterFlag() { + String parameterFlag = null; + boolean hasModuleFlag = userInput.contains(TASK_MODULE_FLAG); + String [] arguments = userInput.split(WHITESPACES); + for (String argument : arguments) { + if (hasModuleFlag && argument.equals(TASK_MODULE_FLAG.trim())) { + hasModuleFlag = false; + continue; + } + if (argument.matches(ANY_FLAG_NO_WHITESPACE)) { + parameterFlag = argument; + break; + } + } + assert !Objects.isNull(parameterFlag); + return parameterFlag; + } + + /** + * Determine the error in the parameter if the parameter is wrapped in double quotes but with the wrong flag. + * @throws InvalidFlagException If the incorrect flag is used + */ + private void determineErrorInParameter() throws InvalidFlagException { + if (userInput.matches(ANY_TEXT + ANY_FLAG + QUOTED_UNRESTRICTED_STR)) { + String parameterFlag = getParameterFlag(); + throw new InvalidFlagException(parameterFlag); + } + } + + /** + * Narrows the scope of the error by eliminating the correct areas in the string. + * @param taskNumber The string representation of the task number + */ + private void reduceErrorScope(String taskNumber) { + userInput = userInput.replaceFirst(TASK, EMPTY_STRING); + userInput = userInput.replaceFirst(taskNumber, EMPTY_STRING); + userInput = userInput.split(TASK_PARAMETERS_FLAGS)[ZEROTH_INDEX]; + } + + /** + * Determines the error in the module code. + * It will check if the module code is empty, otherwise the module code is not made up of only word characters. + * @throws EmptyParamException If the module code supplied is empty + * @throws InvalidCompulsoryParameterException If module code is not made up of only word characters + * or if there is invalid input in where the field should be + */ + private void determineErrorInModuleCode() throws EmptyParamException, InvalidCompulsoryParameterException { + String moduleCode; + try { + moduleCode = userInput.split(TASK_MODULE_FLAG)[FIRST_INDEX]; + } catch (IndexOutOfBoundsException e) { + if (userInput.contains(TASK_MODULE_FLAG.trim())) { + throw new EmptyParamException(MODULE_CODE_STR); + } + throw new InvalidCompulsoryParameterException(MODULE_FIELD_STR, userInput.trim()); + } + String inputBeforeModuleCode = userInput.split(TASK_MODULE_FLAG.trim())[ZEROTH_INDEX]; + if (!inputBeforeModuleCode.isBlank()) { + throw new InvalidCompulsoryParameterException(MODULE_FIELD_STR, userInput.trim()); + } + + throw new InvalidCompulsoryParameterException(MODULE_CODE_STR, moduleCode); + } + + /** + * Gets the enumeration of the task parameter inputted. + * @param taskParameterFlag The flag inputted to represent the parameter + * @return The enumeration of the task parameter inputted + * @throws InvalidFlagException If the flag inputted is invalid + */ + private TaskParameters getTaskParameter(String taskParameterFlag) throws InvalidFlagException { + switch (taskParameterFlag) { + case TASK_NAME_FLAG: + return TaskParameters.TASK_NAME_ONLY; + case TASK_DESCRIPTION_FLAG: + return TaskParameters.DESCRIPTION_ONLY; + case TASK_ESTIMATED_WORKING_TIME_FLAG: + return TaskParameters.WORKING_TIME_ONLY; + default: + throw new InvalidFlagException(taskParameterFlag); + } + } + + + /** + * Parses the user input and extracts the parameters based on the command format. + * @param userInput User input of the task number, task module and a task parameter + * @return A new {@code EditCommand} object to edit a task parameter + * @throws ModHappyException If there is an error parsing the command + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + this.userInput = userInput; + HashMap parsedArguments = parseString(userInput); + String taskNumberString = parsedArguments.get(TASK_NUMBER); + String taskModule = parsedArguments.get(TASK_MODULE); + String taskParameterFlag = parsedArguments.get(TASK_PARAMETER_FLAG); + String taskParameter = parsedArguments.get(TASK_PARAMETER); + if (!Objects.isNull(taskNumberString)) { + TaskParameters taskParameterType = getTaskParameter(taskParameterFlag); + checkTaskName(taskParameter, taskParameterType); + final int taskIndex = parseIndex(taskNumberString); + checksForExcessArg(); + return new EditCommand(taskModule, taskIndex, taskParameter, taskParameterType); + } + throw new ModHappyException(); + } + + /** + * Checks if the task name inputted is empty. + * @param taskName The name of the task to be edited + * @param taskParametersType The enumeration of the task parameter that was inputted + * @throws EmptyParamException If the task name inputted is empty + */ + private void checkTaskName(String taskName, TaskParameters taskParametersType) throws EmptyParamException { + if (taskParametersType != TaskParameters.TASK_NAME_ONLY) { + return; + } + if (!Objects.isNull(taskName) && taskName.isBlank()) { + throw new EmptyParamException(TASK_NAME_STR); + } + } + +} diff --git a/src/main/java/seedu/duke/parsers/GradeParser.java b/src/main/java/seedu/duke/parsers/GradeParser.java new file mode 100644 index 0000000000..cfcb493b4f --- /dev/null +++ b/src/main/java/seedu/duke/parsers/GradeParser.java @@ -0,0 +1,90 @@ +package seedu.duke.parsers; + +import java.util.HashMap; +import java.util.Objects; + +import seedu.duke.commands.Command; +import seedu.duke.commands.GradeCommand; +import seedu.duke.exceptions.InvalidModuleGradeException; +import seedu.duke.exceptions.MissingCompulsoryParameterException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.InvalidCompulsoryParameterException; +import seedu.duke.util.StringConstants; + +//@@author heekit73098 +/** + * This Parser supports the "grade" command. + */ +public class GradeParser extends Parser { + public static final String MODULE_CODE = StringConstants.MODULE_CODE; + public static final String MODULE_GRADE = StringConstants.MODULE_GRADE; + private String userInput; + + // Unescaped regex for testing: + // ((?\\w+)(\\s+(?(?i)(CU|CS|[A-B][+-]?|[C-D][+]?|F|S|U|-)| + // (?.*))))(?.*) + private static final String GRADE_FORMAT = "((?\\w+)(\\s+" + + "(?(?i)(CU|CS|[A-B][+-]?|[C-D][+]?|F|S|U|-)|(?.*))))(?.*)"; + private static final String WORD_CHAR_ONLY = StringConstants.WORD_CHAR_ONLY; + private static final String MODULE_GRADES_MATCH = StringConstants.MODULE_GRADES_MATCH; + + public GradeParser() { + super(); + this.commandFormat = GRADE_FORMAT; + groupNames.add(MODULE_CODE); + groupNames.add(MODULE_GRADE); + groupNames.add(INVALID); + groupNames.add(INVALID_MODULE_GRADE); + } + + /** + * Determines the error that the user made in the grade command based on the compulsory parameters. + * It will first check if the module code is present and if it is made up of only word characters. + * Then it checks if the module grade entered is one of the grades specified. + * @throws MissingCompulsoryParameterException If the module code is missing + * @throws InvalidCompulsoryParameterException If the module code is not made up of only word characters + * @throws InvalidModuleGradeException If the module grade is not valid or missing + */ + @Override + public void determineError() throws MissingCompulsoryParameterException, + InvalidCompulsoryParameterException, InvalidModuleGradeException { + String moduleCode; + try { + moduleCode = userInput.split(WHITESPACES)[ZEROTH_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new MissingCompulsoryParameterException(MODULE_CODE_STR); + } + if (!moduleCode.matches(WORD_CHAR_ONLY)) { + throw new InvalidCompulsoryParameterException(MODULE_CODE_STR, moduleCode); + } + String moduleGrade; + try { + moduleGrade = userInput.split(WHITESPACES)[FIRST_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new InvalidModuleGradeException(); + } + if (!moduleGrade.matches(MODULE_GRADES_MATCH)) { + throw new InvalidModuleGradeException(moduleGrade); + } + throw new InvalidCompulsoryParameterException(); + } + + /** + * Parses the user input and extracts the parameters based on the command format. + * @param userInput User input of the module code and the module grade + * @return A new {@code GradeCommand} object to set a grade to the module + * @throws ModHappyException If there is an error parsing the command + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + this.userInput = userInput; + HashMap parsedArguments = parseString(userInput); + String moduleCode = parsedArguments.get(MODULE_CODE); + String moduleGrade = parsedArguments.get(MODULE_GRADE).toUpperCase(); + if (!Objects.isNull(moduleCode)) { + checksForExcessArg(); + return new GradeCommand(moduleCode, moduleGrade); + } + throw new ModHappyException(); + } +} diff --git a/src/main/java/seedu/duke/parsers/HelpParser.java b/src/main/java/seedu/duke/parsers/HelpParser.java new file mode 100644 index 0000000000..e3ba2ed766 --- /dev/null +++ b/src/main/java/seedu/duke/parsers/HelpParser.java @@ -0,0 +1,51 @@ +package seedu.duke.parsers; + +import java.util.HashMap; + +import seedu.duke.commands.Command; +import seedu.duke.commands.HelpCommand; +import seedu.duke.exceptions.GeneralParseException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.util.StringConstants; + +/** + * This Parser supports the "help" command. + */ +public class HelpParser extends Parser { + private static final String COMMAND_AS_HELP_ARGUMENT = StringConstants.HELP_COMMAND_ARGUMENT; + + // Unescaped regex for testing: + // (?\w+)?(?.*) + private static final String HELP_FORMAT = "(?\\w+)?(?.*)"; + + public HelpParser() { + super(); + this.commandFormat = HELP_FORMAT; + groupNames.add(COMMAND_AS_HELP_ARGUMENT); + groupNames.add(INVALID); + } + + /** + * Throws GeneralParseException as the user input does not match the regex. + * @throws GeneralParseException as it has no compulsory parameters. + */ + @Override + public void determineError() throws GeneralParseException { + throw new GeneralParseException(); + } + + //@@author ngys117 + /** + * Parses the user input and extracts the parameters based on the command format. + * @param userInput User input of the command to get a help message + * @return A new {@code HelpCommand} object to show a help message + * @throws ModHappyException If there is an error parsing the command + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + HashMap parsedArguments = parseString(userInput); + String command = parsedArguments.get(COMMAND_AS_HELP_ARGUMENT); + checksForExcessArg(); + return new HelpCommand(command); + } +} diff --git a/src/main/java/seedu/duke/parsers/ListParser.java b/src/main/java/seedu/duke/parsers/ListParser.java new file mode 100644 index 0000000000..5664ad15ce --- /dev/null +++ b/src/main/java/seedu/duke/parsers/ListParser.java @@ -0,0 +1,51 @@ +package seedu.duke.parsers; + +import java.util.HashMap; + +import seedu.duke.commands.Command; +import seedu.duke.commands.ListCommand; +import seedu.duke.exceptions.GeneralParseException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.util.StringConstants; + +/** + * This Parser supports the "list" command. + */ +//@@author Yzkkk +public class ListParser extends Parser { + private static final String TAG = StringConstants.TAG_COMMAND_WORD; + // Unescaped Regex for testing: + // ((?\w+))?(?.*) + private static final String LIST_FORMAT = "((?\\w+))?(?.*)"; + + public ListParser() { + super(); + this.commandFormat = LIST_FORMAT; + groupNames.add(TAG); + groupNames.add(INVALID); + } + + /** + * Throws GeneralParseException as the user input does not match the regex. + * @throws GeneralParseException as it has no compulsory parameters. + */ + @Override + public void determineError() throws GeneralParseException { + throw new GeneralParseException(); + } + + //@@author ngys117 + /** + * Parses the user input and extracts the parameters based on the command format. + * @param userInput User input of the tag name + * @return A new {@code ListCommand} object to display the list of modules and task + * @throws ModHappyException If there is an error parsing the command + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + HashMap parsedArguments = parseString(userInput); + String listArgument = parsedArguments.get(TAG); + checksForExcessArg(); + return new ListCommand(listArgument); + } +} diff --git a/src/main/java/seedu/duke/parsers/MarkParser.java b/src/main/java/seedu/duke/parsers/MarkParser.java new file mode 100644 index 0000000000..1bb3880990 --- /dev/null +++ b/src/main/java/seedu/duke/parsers/MarkParser.java @@ -0,0 +1,103 @@ +package seedu.duke.parsers; + +import java.util.HashMap; + +import seedu.duke.commands.Command; +import seedu.duke.commands.MarkCommand; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.InvalidFlagException; +import seedu.duke.exceptions.InvalidNumberException; +import seedu.duke.exceptions.MissingNumberException; +import seedu.duke.exceptions.InvalidCompulsoryParameterException; +import seedu.duke.exceptions.GeneralParseException; +import seedu.duke.util.StringConstants; + +/** + * This Parser supports the "mark" command. + */ +//@@author Yzkkk +public class MarkParser extends Parser { + private static final String FLAG = StringConstants.FLAG; + private static final String TASK_NUMBER = StringConstants.TASK_NUMBER; + private static final String TASK_MODULE = StringConstants.TASK_MODULE; + private static final String COMPLETED_FLAG = StringConstants.COMPLETED_FLAG; + private static final String UNCOMPLETED_FLAG = StringConstants.UNCOMPLETED_FLAG; + private static final String TASK_NUMBER_STR = StringConstants.TASK_NUMBER_STR; + private String userInput; + + // Unescaped regex for testing: + // (?(c|u))\s+(?\d+)(\s+-m\s+(?\w+))?(?.*) + private static final String MARK_FORMAT = "(?(c|u))\\s+" + + "(?\\d+)(\\s+-m\\s+(?\\w+))?(?.*)"; + private static final String MARK_COMMAND_FLAGS = StringConstants.MARK_COMMAND_FLAGS; + private static final String POSITIVE_INT = StringConstants.POSITIVE_INT; + + public MarkParser() { + super(); + // See also https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html + this.commandFormat = MARK_FORMAT; + groupNames.add(FLAG); + groupNames.add(TASK_NUMBER); + groupNames.add(TASK_MODULE); + groupNames.add(INVALID); + } + + //@@author heekit73098 + /** + * Determines the error made by the user in the mark command based on the compulsory parameters. + * It will first check if the flag is present and if it is either c or u. + * Then it will check if the task number is present and if it is in a positive integer format. + * @throws InvalidFlagException If the flag is missing, or not c nor u + * @throws MissingNumberException If the task number is missing + * @throws InvalidNumberException If the task number is not in a positive integer format + * @throws InvalidCompulsoryParameterException If the error is none of the above errors + */ + @Override + public void determineError() throws InvalidFlagException, MissingNumberException, + InvalidNumberException, InvalidCompulsoryParameterException { + String flag; + try { + flag = userInput.split(WHITESPACES)[ZEROTH_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new InvalidFlagException(); + } + if (!flag.matches(MARK_COMMAND_FLAGS)) { + throw new InvalidFlagException(flag); + } + String taskNumber; + try { + taskNumber = userInput.split(WHITESPACES)[FIRST_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new MissingNumberException(TASK_NUMBER_STR); + } + if (!taskNumber.matches(POSITIVE_INT)) { + throw new InvalidNumberException(TASK_NUMBER_STR, taskNumber); + } + throw new InvalidCompulsoryParameterException(); + } + + //@@author chooyikai + /** + * Parses user's input for "mark" command. + * + * @param userInput User input of completed flag or uncompleted flag, task index and task module + * @throws ModHappyException If completed flag or uncompleted flag is not detected + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + this.userInput = userInput; + HashMap parsedArguments = parseString(userInput); + final String commandFlag = parsedArguments.get(FLAG); + final String taskModule = parsedArguments.get(TASK_MODULE); + final int taskIndex = parseIndex(parsedArguments.get(TASK_NUMBER)); + checksForExcessArg(); + switch (commandFlag) { + case (COMPLETED_FLAG): + return new MarkCommand(taskIndex, taskModule, true); + case (UNCOMPLETED_FLAG): + return new MarkCommand(taskIndex, taskModule, false); + default: + throw new GeneralParseException(); + } + } +} diff --git a/src/main/java/seedu/duke/parsers/ModHappyParser.java b/src/main/java/seedu/duke/parsers/ModHappyParser.java new file mode 100644 index 0000000000..de47fa13b6 --- /dev/null +++ b/src/main/java/seedu/duke/parsers/ModHappyParser.java @@ -0,0 +1,104 @@ +package seedu.duke.parsers; + +import java.util.HashMap; +import java.util.HashSet; + +import seedu.duke.commands.Command; +import seedu.duke.exceptions.GeneralParseException; +import seedu.duke.exceptions.UnknownConfigurationGroupWordException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.UnknownCommandException; +import seedu.duke.exceptions.UnsupportedResultTypeException; +import seedu.duke.exceptions.WrongDurationFormatException; +import seedu.duke.util.StringConstants; + +/** + * This Parser distinguishes between various command words. + */ +public class ModHappyParser extends Parser { + private static final String ARGUMENT = StringConstants.ARGUMENT; + private static final String COMMAND_WORD = StringConstants.COMMAND_WORD; + private static final String MOD_HAPPY_COMMAND_FORMAT = "(?\\S+)" + + "\\s*(?.*)"; + + //@@author Ch40gRv1-Mu + public ModHappyParser() { + super(); + // See also https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html + this.commandFormat = MOD_HAPPY_COMMAND_FORMAT; + groupNames = new HashSet<>(); + parsedCommand = new HashMap<>(); + groupNames.add(COMMAND_WORD); + groupNames.add(ARGUMENT); + } + + /** + * Throws GeneralParseException as the user input does not match the regex. + * @throws GeneralParseException as it has no compulsory parameters. + */ + @Override + public void determineError() throws GeneralParseException { + throw new GeneralParseException(); + } + + /** + * Extract the command word from the user input and invoke the relevant command-specific parser. + * @return a Command instance associated with the user input + * @throws GeneralParseException if the user input does not match the string + * @throws ModHappyException if the command word was not recognised + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + try { + HashMap parsedCommand = parseString(userInput); + Parser commandParser = getCommandParser(parsedCommand.get(COMMAND_WORD)); + return commandParser.parseCommand(parsedCommand.get(ARGUMENT)); + } catch (GeneralParseException | UnknownConfigurationGroupWordException | UnsupportedResultTypeException e) { + throw e; + } catch (WrongDurationFormatException e) { + throw new WrongDurationFormatException(); + } catch (ModHappyException e) { + throw new UnknownCommandException(userInput); + } catch (Exception e) { + throw new UnknownCommandException(userInput); + } + } + + /** + * Returns the Parser object for parsing commands associated with the given command word. + * If the command takes no arguments, null is returned. + * @param commandWord the command word (e.g. "add") + * @return an instance of the relevant Parser object or null + * @throws UnknownCommandException if commandWord does not correspond with any command + */ + private Parser getCommandParser(String commandWord) throws UnknownCommandException { + switch (commandWord) { + case (EXIT_COMMAND_WORD): + case (GPA_COMMAND_WORD): + case (SAVE_COMMAND_WORD): + case (RESET_COMMAND_WORD): + // Intentional fallthrough + return new NoArgumentParser(commandWord); + case (LIST_COMMAND_WORD): + return new ListParser(); + case (ADD_COMMAND_WORD): + return AddParser.getParser(parsedCommand.get(ARGUMENT).split(WHITESPACES)[0]); + case (DELETE_COMMAND_WORD): + return new DeleteParser(); + case (MARK_COMMAND_WORD): + return new MarkParser(); + case (EDIT_COMMAND_WORD): + return EditParser.getParser(parsedCommand.get(ARGUMENT).split(WHITESPACES)[0]); + case (HELP_COMMAND_WORD): + return new HelpParser(); + case (TAG_COMMAND_WORD): + return new TagParser(); + case (GRADE_COMMAND_WORD): + return new GradeParser(); + case (OPTION_COMMAND_WORD): + return new OptionParser(); + default: + throw new UnknownCommandException(); + } + } +} diff --git a/src/main/java/seedu/duke/parsers/NoArgumentParser.java b/src/main/java/seedu/duke/parsers/NoArgumentParser.java new file mode 100644 index 0000000000..a19ae318cb --- /dev/null +++ b/src/main/java/seedu/duke/parsers/NoArgumentParser.java @@ -0,0 +1,53 @@ +package seedu.duke.parsers; + +import seedu.duke.commands.Command; +import seedu.duke.commands.ExitCommand; +import seedu.duke.commands.GpaCommand; +import seedu.duke.commands.ResetCommand; +import seedu.duke.commands.SaveCommand; +import seedu.duke.exceptions.AdditionalParameterException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.GeneralParseException; + +//@@author chooyikai +/** + * This Parser supports all commands which do not accept any additional arguments or parameters. + */ +public class NoArgumentParser extends Parser { + private final String myCommandWord; + + public NoArgumentParser(String commandWord) { + myCommandWord = commandWord; + } + + @Override + public void determineError() throws GeneralParseException { + throw new GeneralParseException(); + } + + /** + * Determines the command based on the command word. + * @param userInput An empty string + * @return A new {@code Command} object + * @throws ModHappyException If there is an error parsing the command + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + // NoArgumentParser commands strictly take no input. + if (userInput.length() != 0) { + throw new AdditionalParameterException(); + } + switch (myCommandWord) { + case (EXIT_COMMAND_WORD): + return new ExitCommand(); + case (GPA_COMMAND_WORD): + return new GpaCommand(); + case (RESET_COMMAND_WORD): + return new ResetCommand(); + case (SAVE_COMMAND_WORD): + return new SaveCommand(); + default: + throw new GeneralParseException(); + } + } +} diff --git a/src/main/java/seedu/duke/parsers/OptionParser.java b/src/main/java/seedu/duke/parsers/OptionParser.java new file mode 100644 index 0000000000..c1eb3e1bb5 --- /dev/null +++ b/src/main/java/seedu/duke/parsers/OptionParser.java @@ -0,0 +1,54 @@ +package seedu.duke.parsers; + +import java.util.HashMap; + +import seedu.duke.commands.Command; +import seedu.duke.commands.OptionCommand; +import seedu.duke.exceptions.GeneralParseException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +/** + * This Parser supports the "option" command. + */ +public class OptionParser extends Parser { + // Unescaped Regex for testing + // ((?[A-Z_]+)(=(?\w+))?)?(?.*) + private static final String OPTION_FORMAT = "((?[A-Z_]+)(=(?\\w+))?)?" + + "(?.*)"; + private static final String CONFIGURATION_GROUP_WORD = StringConstants.CONFIGURATION_GROUP_WORD; + private static final String NEW_VALUE = StringConstants.NEW_VALUE; + + public OptionParser() { + super(); + this.commandFormat = OPTION_FORMAT; + groupNames.add(CONFIGURATION_GROUP_WORD); + groupNames.add(NEW_VALUE); + groupNames.add(INVALID); + } + + /** + * Throws GeneralParseException as the user input does not match the regex. + * @throws GeneralParseException as it has no compulsory parameters. + */ + @Override + public void determineError() throws GeneralParseException { + throw new GeneralParseException(); + } + + /** + * Parses the user input and extracts the parameters based on the command format. + * @param userInput User input of the configuration word and its value to be set + * @return A new {@code OptionCommand} object to view or set an option + * @throws ModHappyException If there is an error parsing the command + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + HashMap parsedArguments = parseString(userInput); + String configurationGroupWord = parsedArguments.get(CONFIGURATION_GROUP_WORD); + String newValue = parsedArguments.get(NEW_VALUE); + checksForExcessArg(); + return new OptionCommand(configurationGroupWord, newValue); + } +} diff --git a/src/main/java/seedu/duke/parsers/Parser.java b/src/main/java/seedu/duke/parsers/Parser.java new file mode 100644 index 0000000000..4d1e94ebc6 --- /dev/null +++ b/src/main/java/seedu/duke/parsers/Parser.java @@ -0,0 +1,228 @@ +package seedu.duke.parsers; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.duke.commands.Command; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.InvalidFlagException; +import seedu.duke.exceptions.InvalidModuleGradeException; +import seedu.duke.exceptions.InvalidTagOperationException; +import seedu.duke.exceptions.ExcessArgumentException; +import seedu.duke.exceptions.InvalidNumberException; +import seedu.duke.util.StringConstants; +import seedu.duke.util.NumberConstants; + + +/** + * Represents a Parser that parse a {@code Command}. + */ +public abstract class Parser { + //@@author Yzkkk + protected static final String EXIT_COMMAND_WORD = StringConstants.EXIT_COMMAND_WORD; + protected static final String ADD_COMMAND_WORD = StringConstants.ADD_COMMAND_WORD; + protected static final String DELETE_COMMAND_WORD = StringConstants.DELETE_COMMAND_WORD; + protected static final String GPA_COMMAND_WORD = StringConstants.GPA_COMMAND_WORD; + protected static final String GRADE_COMMAND_WORD = StringConstants.GRADE_COMMAND_WORD; + protected static final String LIST_COMMAND_WORD = StringConstants.LIST_COMMAND_WORD; + protected static final String MARK_COMMAND_WORD = StringConstants.MARK_COMMAND_WORD; + protected static final String EDIT_COMMAND_WORD = StringConstants.EDIT_COMMAND_WORD; + protected static final String RESET_COMMAND_WORD = StringConstants.RESET_COMMAND_WORD; + protected static final String HELP_COMMAND_WORD = StringConstants.HELP_COMMAND_WORD; + protected static final String SAVE_COMMAND_WORD = StringConstants.SAVE_COMMAND_WORD; + protected static final String TAG_COMMAND_WORD = StringConstants.TAG_COMMAND_WORD; + protected static final String OPTION_COMMAND_WORD = StringConstants.OPTION_COMMAND_WORD; + + protected static final String INVALID = StringConstants.INVALID; + protected static final String INVALID_MOD_FLAG = StringConstants.INVALID_MOD_FLAG; + protected static final String INVALID_TASK_NAME_FLAG = StringConstants.INVALID_TASK_NAME_FLAG; + protected static final String INVALID_TASK_DES_FLAG = StringConstants.INVALID_TASK_DES_FLAG; + protected static final String INVALID_MOD_DES_FLAG = StringConstants.INVALID_MOD_DES_FLAG; + protected static final String INVALID_TIME_FLAG = StringConstants.INVALID_TIME_FLAG; + protected static final String INVALID_MODULE_GRADE = StringConstants.INVALID_MODULE_GRADE; + protected static final String INVALID_TAG_COMMAND = StringConstants.INVALID_TAG_COMMAND; + + protected static final String WHITESPACES = StringConstants.WHITESPACES; + protected static final String TASK = StringConstants.TASK_STR; + protected static final String MODULE = StringConstants.MODULE_STR; + protected static final String TASK_NAME_STR = StringConstants.TASK_NAME_STR; + protected static final String TASK_NUMBER_STR = StringConstants.TASK_NUMBER_STR; + protected static final String MODULE_CODE_STR = StringConstants.MODULE_CODE_STR; + protected static final String MODULE_DESCRIPTION_STR = StringConstants.MODULE_DESCRIPTION_STR; + protected static final String TASK_PARAMETER_STR = StringConstants.TASK_PARAMETER_STR; + protected static final String TAG_NAME_STR = StringConstants.TAG_NAME_STR; + protected static final int ZEROTH_INDEX = NumberConstants.ZEROTH_INDEX; + protected static final int FIRST_INDEX = NumberConstants.FIRST_INDEX; + protected static final int SECOND_INDEX = NumberConstants.SECOND_INDEX; + protected static final int FOURTH_INDEX = NumberConstants.FOURTH_INDEX; + protected static final int MINIMUM_INDEX = NumberConstants.MINIMUM_INDEX; + + + protected String commandFormat; + protected HashMap parsedCommand; + protected HashSet groupNames; + + //@@author Ch40gRv1-Mu + public Parser() { + groupNames = new HashSet(); + parsedCommand = new HashMap<>(); + } + + //@@author Ch40gRv1-Mu + /** + * Parses the provided user input and returns the relevant Command object. + * @throws ModHappyException If there is an error parsing the input + */ + public abstract Command parseCommand(String userInput) throws ModHappyException; + + + /** + * Determines the error that occurred in the command. + * @throws ModHappyException For the error that occurred in the command + */ + public abstract void determineError() throws ModHappyException; + + //@@author Ch40gRv1-Mu + /** + * Parses string into groups based on commandFormat. + * @throws ModHappyException if the provided string does not match the pattern + */ + public HashMap parseString(String userInput) throws ModHappyException { + final Pattern commandPattern = Pattern.compile(commandFormat); + final Matcher matcher = commandPattern.matcher(userInput.trim()); + if (!matcher.matches()) { + determineError(); + } + for (Object groupName : groupNames) { + try { + parsedCommand.put(groupName.toString(), matcher.group(groupName.toString()).trim()); + } catch (Exception e) { + parsedCommand.put(groupName.toString(), null); + } + } + checkForInvalidStrings(); + return parsedCommand; + } + + //@@author Yzkkk + /** + * Parses the task index from a string to an integer form. + * It will also check if the index is non-negative, throwing an exception if it is not. + * @param taskNumberString the string representation of the task number + * @return the zero-based index integer of the task number string + * @throws InvalidNumberException if the task index is less than 0 or if the string cannot be parsed into an integer + */ + protected int parseIndex(String taskNumberString) throws InvalidNumberException { + int taskIndex; + try { + taskIndex = Integer.parseInt(taskNumberString) - 1; + if (taskIndex < MINIMUM_INDEX) { + throw new NumberFormatException(); + } + } catch (NumberFormatException e) { + throw new InvalidNumberException(TASK_NUMBER_STR, taskNumberString); + } + return taskIndex; + } + + //@@author Yzkkk + private void checksForInvalidModuleGrade() throws InvalidModuleGradeException { + if (groupNames.contains(INVALID_MODULE_GRADE)) { + String invalidInput = parsedCommand.get(INVALID_MODULE_GRADE); + if (!Objects.isNull(invalidInput) && !invalidInput.isBlank()) { + throw new InvalidModuleGradeException(invalidInput); + } + } + } + + //@@author Yzkkk + private void checksForInvalidTagCommand() throws InvalidTagOperationException { + if (groupNames.contains(INVALID_TAG_COMMAND)) { + String invalidInput = parsedCommand.get(INVALID_TAG_COMMAND); + if (!Objects.isNull(invalidInput) && !invalidInput.isBlank()) { + throw new InvalidTagOperationException(invalidInput); + } + } + } + + //@@author Yzkkk + private void checksForInvalidTimeFlag() throws InvalidFlagException { + if (groupNames.contains(INVALID_TIME_FLAG)) { + String invalidInput = parsedCommand.get(INVALID_TIME_FLAG); + if (!Objects.isNull(invalidInput) && !invalidInput.isBlank()) { + throw new InvalidFlagException(invalidInput); + } + } + } + + //@@author Yzkkk + private void checksForInvalidModDescriptionFlag() throws InvalidFlagException { + if (groupNames.contains(INVALID_MOD_DES_FLAG)) { + String invalidInput = parsedCommand.get(INVALID_MOD_DES_FLAG); + if (!Objects.isNull(invalidInput) && !invalidInput.isBlank()) { + throw new InvalidFlagException(invalidInput); + } + } + } + + //@@author Yzkkk + private void checksForInvalidTaskDescriptionFlag() throws InvalidFlagException { + if (groupNames.contains(INVALID_TASK_DES_FLAG)) { + String invalidInput = parsedCommand.get(INVALID_TASK_DES_FLAG); + if (!Objects.isNull(invalidInput) && !invalidInput.isBlank()) { + throw new InvalidFlagException(invalidInput); + } + } + } + + //@@author Yzkkk + private void checksForInvalidTaskName() throws InvalidFlagException { + if (groupNames.contains(INVALID_TASK_NAME_FLAG)) { + String invalidInput = parsedCommand.get(INVALID_TASK_NAME_FLAG); + if (!Objects.isNull(invalidInput) && !invalidInput.isBlank()) { + throw new InvalidFlagException(invalidInput); + } + } + } + + //@@author Yzkkk + private void checksForInvalidModFlag() throws InvalidFlagException { + if (groupNames.contains(INVALID_MOD_FLAG)) { + String invalidInput = parsedCommand.get(INVALID_MOD_FLAG); + if (!Objects.isNull(invalidInput) && !invalidInput.isBlank()) { + throw new InvalidFlagException(invalidInput); + } + } + } + + //@@author Yzkkk + protected void checksForExcessArg() throws ExcessArgumentException { + if (groupNames.contains(INVALID)) { + String invalidInput = parsedCommand.get(INVALID); + if (!Objects.isNull(invalidInput) && !invalidInput.isBlank()) { + throw new ExcessArgumentException(invalidInput); + } + } + } + + //@@author Yzkkk + /** + * Checks for strings that are parsed into groups based on commandFormat, but are essentially invalid. + * @throws InvalidFlagException If the flag inputted is invalid + * @throws InvalidModuleGradeException If the module grade inputted is invalid + * @throws InvalidTagOperationException If the tag operation inputted is invalid + */ + private void checkForInvalidStrings() throws InvalidFlagException, + InvalidModuleGradeException, InvalidTagOperationException { + checksForInvalidModFlag(); + checksForInvalidTaskName(); + checksForInvalidTaskDescriptionFlag(); + checksForInvalidModDescriptionFlag(); + checksForInvalidTimeFlag(); + checksForInvalidTagCommand(); + checksForInvalidModuleGrade(); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/parsers/TagParser.java b/src/main/java/seedu/duke/parsers/TagParser.java new file mode 100644 index 0000000000..bb0bcaa91c --- /dev/null +++ b/src/main/java/seedu/duke/parsers/TagParser.java @@ -0,0 +1,159 @@ +package seedu.duke.parsers; + +import java.util.HashMap; + +import seedu.duke.commands.Command; +import seedu.duke.commands.TagCommand; +import seedu.duke.exceptions.InvalidCompulsoryParameterException; +import seedu.duke.exceptions.InvalidTagOperationException; +import seedu.duke.exceptions.InvalidNumberException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.MissingNumberException; +import seedu.duke.exceptions.MissingCompulsoryParameterException; +import seedu.duke.util.StringConstants; + + +/** + * This Parser supports the "tag" command. + */ +public class TagParser extends Parser { + private static final String TAG_OPERATION = StringConstants.TAG_OPERATION; + private static final String TASK_NUMBER = StringConstants.TASK_NUMBER; + private static final String TASK_MODULE = StringConstants.TASK_MODULE; + private static final String TAG_NAME = StringConstants.TAG_NAME; + private String userInput; + + // Unescaped Regex for testing: + // ((?\b(add|del)|(?.*)\b)?)(\s+(?\d+))((\s+(-m|(?.*)) + // \s+(?\w+))?)(\s+(?\w+))(?.*) + private static final String TAG_FORMAT = "((?\\b(add|del)|(?.*)\\b)?)" + + "(\\s+(?\\d+))((\\s+(-m|(?.*))\\s+(?\\w+))?)" + + "(\\s+(?\\w+))(?.*)"; + private static final String TAG_COMMAND_FLAGS = StringConstants.TAG_COMMAND_FLAGS; + private static final String POSITIVE_INT = StringConstants.POSITIVE_INT; + private static final String WORD_CHAR_ONLY = StringConstants.WORD_CHAR_ONLY; + private static final String TASK_MODULE_FLAG = StringConstants.TASK_MODULE_FLAG; + + public TagParser() { + super(); + this.commandFormat = TAG_FORMAT; + groupNames.add(TAG_OPERATION); + groupNames.add(TASK_NUMBER); + groupNames.add(TASK_MODULE); + groupNames.add(TAG_NAME); + groupNames.add(INVALID); + groupNames.add(INVALID_MOD_FLAG); + groupNames.add(INVALID_TAG_COMMAND); + } + + //@@author heekit73098 + /** + * Determines the error made by the user in the tag command based on its compulsory parameters. + * It first checks if the error is in the tag operation, then task number, then tag name. + * If there are no errors in the above, it means that there is an error due to the module code. + * @throws InvalidTagOperationException If the tag is missing or is not add nor del + * @throws MissingNumberException If the task number is missing + * @throws InvalidNumberException If the task number is not in a positive integer format + * @throws MissingCompulsoryParameterException If the tag name is missing + * @throws InvalidCompulsoryParameterException If the tag name is not made up of all word characters or + * if the module code is not made up of all word characters + */ + @Override + public void determineError() throws InvalidTagOperationException, MissingNumberException, + InvalidNumberException, MissingCompulsoryParameterException, InvalidCompulsoryParameterException { + determineErrorInTagOperation(); + determineErrorInTaskNumber(); + determineErrorInTagName(); + assertErrorInModuleCode(); + } + + /** + * Checks if the error is in the tag operation. + * Checks if the tag operation is present and if it is either add or del. + * @throws InvalidTagOperationException If the tag operation is missing or if it is neither add nor del + */ + private void determineErrorInTagOperation() throws InvalidTagOperationException { + String tagOperation; + try { + tagOperation = userInput.split(WHITESPACES)[ZEROTH_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new InvalidTagOperationException(); + } + if (!tagOperation.matches(TAG_COMMAND_FLAGS)) { + throw new InvalidTagOperationException(tagOperation); + } + } + + /** + * Checks if the error is in task number. + * Checks if the task number is present or if the task number is in a positive integer format. + * @throws MissingNumberException If the task number is missing + * @throws InvalidNumberException If the task number is not in a positive integer format + */ + private void determineErrorInTaskNumber() throws MissingNumberException, InvalidNumberException { + String taskNumber; + try { + taskNumber = userInput.split(WHITESPACES)[FIRST_INDEX]; + } catch (IndexOutOfBoundsException e) { + throw new MissingNumberException(TASK_NUMBER_STR); + } + if (!taskNumber.matches(POSITIVE_INT)) { + throw new InvalidNumberException(TASK_NUMBER_STR, taskNumber); + } + } + + /** + * Checks if the error is in the tag name. + * Check if the tag name is present or if it is made up of only word characters. + * @throws MissingCompulsoryParameterException If the tag name is missing + * @throws InvalidCompulsoryParameterException If the tag name is not made up of only word characters + */ + private void determineErrorInTagName() throws MissingCompulsoryParameterException, + InvalidCompulsoryParameterException { + String tagName; + try { + if (userInput.contains(TASK_MODULE_FLAG)) { + tagName = userInput.split(WHITESPACES)[FOURTH_INDEX]; + } else { + tagName = userInput.split(WHITESPACES)[SECOND_INDEX]; + } + } catch (IndexOutOfBoundsException e) { + throw new MissingCompulsoryParameterException(TAG_NAME_STR); + } + if (!tagName.matches(WORD_CHAR_ONLY)) { + throw new InvalidCompulsoryParameterException(TAG_NAME_STR, tagName); + } + } + + /** + * Throws exception that the error is in the module code field as the error is not present in the other compulsory + * parameters. + * @throws InvalidCompulsoryParameterException To show that the error is in the module code + */ + private void assertErrorInModuleCode() throws InvalidCompulsoryParameterException { + assert (userInput.contains(TASK_MODULE_FLAG)); + String moduleCode = userInput.split(TASK_MODULE_FLAG)[FIRST_INDEX].split(WHITESPACES)[ZEROTH_INDEX]; + throw new InvalidCompulsoryParameterException(MODULE_CODE_STR, moduleCode); + } + + //@@author ngys117 + /** + * Parses the user input and extracts the parameters based on the command format. + * @param userInput User input of the task number, task module and the tag name + * @return A new {@code TagCommand} object to add/delete a tag to/from a task + * @throws ModHappyException If there is an error parsing the command + */ + @Override + public Command parseCommand(String userInput) throws ModHappyException { + this.userInput = userInput; + HashMap parsedArguments = parseString(userInput); + String tagOperationString = parsedArguments.get(TAG_OPERATION); + String taskNumberString = parsedArguments.get(TASK_NUMBER); + String taskModuleString = parsedArguments.get(TASK_MODULE); + String tagDescription = parsedArguments.get(TAG_NAME); + int taskIndex = parseIndex(taskNumberString); + checksForExcessArg(); + return new TagCommand(tagOperationString, taskIndex, taskModuleString, tagDescription); + } + +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/storage/ConfigurationStorage.java b/src/main/java/seedu/duke/storage/ConfigurationStorage.java new file mode 100644 index 0000000000..fe4487e12b --- /dev/null +++ b/src/main/java/seedu/duke/storage/ConfigurationStorage.java @@ -0,0 +1,40 @@ +package seedu.duke.storage; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSyntaxException; + +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.ReadException; +import seedu.duke.exceptions.UnknownException; +import seedu.duke.util.Configuration; + +import static seedu.duke.util.StringConstants.MODIFIED_JSON_EXCEPTION; + +//@@author Ch40gRv1-Mu +public class ConfigurationStorage extends JsonStorage { + @Override + public Configuration loadData(String path) throws ModHappyException { + Gson gson = new GsonBuilder().create(); + Path file = new File(path).toPath(); + try { + Reader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8); + return gson.fromJson(reader, (Type) Configuration.class); + } catch (JsonSyntaxException e) { + throw new ReadException(); + } catch (JsonParseException | IOException e) { + throw new ReadException(MODIFIED_JSON_EXCEPTION); + } catch (Exception e) { + throw new UnknownException(e.toString()); + } + } +} diff --git a/src/main/java/seedu/duke/storage/JsonStorage.java b/src/main/java/seedu/duke/storage/JsonStorage.java new file mode 100644 index 0000000000..373ff102c1 --- /dev/null +++ b/src/main/java/seedu/duke/storage/JsonStorage.java @@ -0,0 +1,66 @@ +package seedu.duke.storage; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; + +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import seedu.duke.exceptions.FileCreateFailException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.UnknownException; +import seedu.duke.exceptions.WriteException; + +//@@author Ch40gRv1-Mu +public abstract class JsonStorage implements Storage { + /** + * Writes a ArrayList with elements of type ModHappyT to a json file. + * @param path The json file path + * @throws ModHappyException If an error was encountered during writing + */ + @Override + public void writeData(T object, String path) throws ModHappyException { + try { + createTargetFile(path); + FileOutputStream fos = new FileOutputStream(path); + OutputStreamWriter isr = new OutputStreamWriter(fos, StandardCharsets.UTF_8); + Gson gson = new Gson(); + gson.toJson(object, isr); + isr.close(); + fos.close(); + } catch (JsonParseException e) { + throw new WriteException(); + } catch (Exception e) { + throw new UnknownException(e.toString()); + } + + } + + + /** + * Checks for the existence of the storage file and create it if it does not already exist. + * @param path The json file path + * @throws ModHappyException If the file could not be created + */ + public void createTargetFile(String path) throws ModHappyException { + File targetFile = new File(path); + if (targetFile.exists()) { + return; + } + try { + // the result is ignored intentionally + targetFile.getParentFile().mkdirs(); + targetFile.createNewFile(); + } catch (IOException e) { + throw new FileCreateFailException(); + } catch (Exception e) { + throw new UnknownException(e.toString()); + } + } + + @Override + public abstract T loadData(String path) throws ModHappyException; + +} diff --git a/src/main/java/seedu/duke/storage/ListStorage.java b/src/main/java/seedu/duke/storage/ListStorage.java new file mode 100644 index 0000000000..fc33769baf --- /dev/null +++ b/src/main/java/seedu/duke/storage/ListStorage.java @@ -0,0 +1,19 @@ +package seedu.duke.storage; + + +import java.util.ArrayList; + +import seedu.duke.exceptions.ModHappyException; + +//@@author Ch40gRv1-Mu + +/** + * A data access object that can manage the storage of ArrayList instances. + * @param ModHappy class + */ +public abstract class ListStorage extends JsonStorage> { + + @Override + public abstract ArrayList loadData(String path) throws ModHappyException; + +} diff --git a/src/main/java/seedu/duke/storage/ModHappyStorageManager.java b/src/main/java/seedu/duke/storage/ModHappyStorageManager.java new file mode 100644 index 0000000000..b7e384b2b8 --- /dev/null +++ b/src/main/java/seedu/duke/storage/ModHappyStorageManager.java @@ -0,0 +1,174 @@ +package seedu.duke.storage; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Objects; + +import seedu.duke.data.Module; +import seedu.duke.data.ModuleList; +import seedu.duke.data.Task; +import seedu.duke.exceptions.DuplicateModuleException; +import seedu.duke.exceptions.InvalidConfigurationException; +import seedu.duke.exceptions.InvalidConfigurationValueException; +import seedu.duke.exceptions.InvalidModuleCreditsException; +import seedu.duke.exceptions.InvalidModuleException; +import seedu.duke.exceptions.InvalidTaskException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.ui.TextUi; +import seedu.duke.util.Configuration; +import seedu.duke.util.Grades; +import seedu.duke.util.StringConstants; + +import static seedu.duke.util.NumberConstants.MAXIMUM_MODULAR_CREDITS; +import static seedu.duke.util.NumberConstants.MINIMUM_MODULAR_CREDITS; + +public class ModHappyStorageManager { + + + private static Storage modHappyStorage; + + //@@author Ch40gRv1-Mu + /** + * Saves task list to storage. + * @param taskArrayList ArrayList of tasks to be saved + * @throws ModHappyException Failed to write the task to local storage + */ + @SuppressWarnings("unchecked") + public static void saveTaskList(ArrayList taskArrayList) throws ModHappyException { + modHappyStorage = new TaskListStorage(); + modHappyStorage.writeData(taskArrayList, StringConstants.TASK_PATH); + } + + //@@author Ch40gRv1-Mu + /** + * Saves module list to storage. + * @param moduleArrayList ArrayList of modules to be saved + * @throws ModHappyException Failed to write the modules to local storage + */ + @SuppressWarnings("unchecked") + public static void saveModuleList(ArrayList moduleArrayList) throws ModHappyException { + modHappyStorage = new ModuleListStorage(); + modHappyStorage.writeData(moduleArrayList, StringConstants.MODULE_PATH); + } + + //@@author Ch40gRv1-Mu + /** + * Saves Configuration to storage. + * @param configuration configuration to be saved + * @throws ModHappyException Failed to write the configuration to local storage + */ + @SuppressWarnings("unchecked") + public static void saveConfiguration(Configuration configuration) throws ModHappyException { + modHappyStorage = new ConfigurationStorage(); + modHappyStorage.writeData(configuration, StringConstants.CONFIGURATION_PATH); + } + + //@@author Ch40gRv1-Mu + /** + * Loads Configuration from storage. + * @param configurationPath The local path that a configuration is saved + * @return Loaded configuration or a default configuration objects + */ + public static Configuration loadConfiguration(String configurationPath) { + File configurationDataFile = new File(configurationPath); + if (configurationDataFile.exists()) { + modHappyStorage = new ConfigurationStorage(); + try { + Configuration configuration = (Configuration) modHappyStorage.loadData(configurationPath); + checkConfiguration(configuration); + TextUi.showUnformattedMessage(StringConstants.CONFIGURATION_DATA_LOAD_SUCCESS); + return configuration; + } catch (ModHappyException e) { + TextUi.showUnformattedMessage(e); + TextUi.showUnformattedMessage(StringConstants.CONFIGURATION_DATA_LOAD_FAILED); + return new Configuration(); + } + } else { + TextUi.showUnformattedMessage(StringConstants.NO_CONFIG_DATA_FILE); + return new Configuration(); + } + } + + //@@author chooyikai + public static void checkConfiguration(Configuration configuration) throws ModHappyException { + HashMap configMap = configuration.configurationGroupHashMap; + for (Configuration.ConfigurationGroup key : configMap.keySet()) { + if (key == null) { + throw new InvalidConfigurationException(); + } + if (!Configuration.LEGAL_VALUES.get(key).contains(configMap.get(key))) { + throw new InvalidConfigurationValueException(key, configMap.get(key)); + } + } + } + + public static void checkTaskList(ArrayList list) throws ModHappyException { + for (Task t : list) { + if (Objects.isNull(t.getTaskName())) { + throw new InvalidTaskException(); + } + if (Objects.isNull(t.getWorkingTime()) || t.getWorkingTime().getTaskDuration().isNegative() + || t.getWorkingTime().getTaskDuration().isZero()) { + t.setWorkingTime(null); + } + } + } + + public static void checkModuleList(ArrayList list) throws ModHappyException { + ArrayList moduleCodes = new ArrayList<>(); + for (Module m : list) { + if (Objects.isNull(m.getModuleCode())) { + throw new InvalidModuleException(); + } + if (Objects.isNull(m.getModuleGrade())) { + m.setModuleGrade(Grades.NOT_ENTERED.toString()); + } + if (moduleCodes.contains(m.getModuleCode())) { + throw new DuplicateModuleException(m.getModuleCode()); + } + if (m.getModularCredit() > MAXIMUM_MODULAR_CREDITS + || m.getModularCredit() < MINIMUM_MODULAR_CREDITS) { + throw new InvalidModuleCreditsException(m.getModuleCode(), m.getModularCredit()); + } + checkTaskList(m.getTaskList().getTaskList()); + moduleCodes.add(m.getModuleCode()); + } + } + + @SuppressWarnings("unchecked") + public static void loadTaskList(ModuleList moduleList, String taskPath) { + File taskDataFile = new File(taskPath); + if (!taskDataFile.exists()) { + return; + } + modHappyStorage = new TaskListStorage(); + try { + ArrayList list = (ArrayList) modHappyStorage.loadData(taskPath); + checkTaskList(list); + moduleList.initialiseGeneralTasksFromTaskList(list); + TextUi.showUnformattedMessage(StringConstants.TASK_DATA_LOAD_SUCCESS); + } catch (ModHappyException e) { + TextUi.showUnformattedMessage(e); + TextUi.showUnformattedMessage(StringConstants.TASK_DATA_LOAD_FAILED); + } + } + + @SuppressWarnings("unchecked") + public static void loadModuleList(ModuleList moduleList, String modulePath) { + File moduleDataFile = new File(modulePath); + if (!moduleDataFile.exists()) { + return; + } + modHappyStorage = new ModuleListStorage(); + try { + ArrayList arrayListModule = (ArrayList) modHappyStorage.loadData(modulePath); + checkModuleList(arrayListModule); + moduleList.setModuleList(arrayListModule); + TextUi.showUnformattedMessage(StringConstants.MODULE_DATA_LOAD_SUCCESS); + } catch (ModHappyException e) { + TextUi.showUnformattedMessage(e); + TextUi.showUnformattedMessage(StringConstants.MODULE_DATA_LOAD_FAILED); + } + } +} diff --git a/src/main/java/seedu/duke/storage/ModuleListStorage.java b/src/main/java/seedu/duke/storage/ModuleListStorage.java new file mode 100644 index 0000000000..f30f2b878f --- /dev/null +++ b/src/main/java/seedu/duke/storage/ModuleListStorage.java @@ -0,0 +1,61 @@ +package seedu.duke.storage; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; + +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.ReadException; + +import seedu.duke.data.Module; +import seedu.duke.exceptions.UnknownException; + +import static seedu.duke.util.StringConstants.MODIFIED_JSON_EXCEPTION; + +//@@author Ch40gRv1-Mu +/** + * A data access object managing the loading and saving of ModuleList instances. + * + */ +public class ModuleListStorage extends ListStorage { + + /** + * Deserialises the ModuleList stored in the json file. + * @param path The json file path + * @return Deserialised ModuleList object + * @throws ModHappyException If an error was encountered during reading + */ + @Override + public ArrayList loadData(String path) throws ModHappyException { + Gson gson = new GsonBuilder().create(); + Path file = new File(path).toPath(); + ArrayList arrayList; + try { + Reader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8); + Module[] list = gson.fromJson(reader, Module[].class); + if (!Objects.isNull(list)) { + arrayList = new ArrayList<>(Arrays.asList(list)); + } else { + arrayList = new ArrayList<>(); + } + } catch (JsonParseException e) { + throw new ReadException(MODIFIED_JSON_EXCEPTION); + } catch (IOException e) { + throw new ReadException(); + } catch (Exception e) { + throw new UnknownException(e.toString()); + } + return arrayList; + } + +} diff --git a/src/main/java/seedu/duke/storage/Storage.java b/src/main/java/seedu/duke/storage/Storage.java new file mode 100644 index 0000000000..1a5b403d83 --- /dev/null +++ b/src/main/java/seedu/duke/storage/Storage.java @@ -0,0 +1,28 @@ +package seedu.duke.storage; + +import seedu.duke.exceptions.ModHappyException; + +//@@author Ch40gRv1-Mu +/** + * Storage interfaces of ModHappy. + * @param any data type + */ +public interface Storage { + + /** + * Writes an object of type T to a json file. + * @param path The json file path + * @throws ModHappyException If an error was encountered during writing + */ + void writeData(T object, String path) throws ModHappyException; + + /** + * Load and deserialize a type T object from json file. + * @param path The json file path + * @return The unserialised object of type T + * @throws ModHappyException If an error was encountered during reading + */ + T loadData(String path) throws ModHappyException; + + +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/storage/TaskListStorage.java b/src/main/java/seedu/duke/storage/TaskListStorage.java new file mode 100644 index 0000000000..08345e4ede --- /dev/null +++ b/src/main/java/seedu/duke/storage/TaskListStorage.java @@ -0,0 +1,60 @@ +package seedu.duke.storage; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Objects; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import com.google.gson.JsonParseException; +import seedu.duke.exceptions.ModHappyException; +import seedu.duke.exceptions.ReadException; +import seedu.duke.data.Task; +import seedu.duke.exceptions.UnknownException; + +import static seedu.duke.util.StringConstants.MODIFIED_JSON_EXCEPTION; + +//@@author Ch40gRv1-Mu +/** + * A data access object managing the loading and saving of TaskList instances. + */ +public class TaskListStorage extends ListStorage { + + /** + * Deserialises the TaskList stored in the json file. + * @param path The json file path + * @return Deserialised TaskList object + * @throws ModHappyException If an error was encountered during reading + */ + @Override + public ArrayList loadData(String path) throws ModHappyException { + Gson gson = new GsonBuilder().create(); + Path file = new File(path).toPath(); + try { + Reader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8); + Task[] list = gson.fromJson(reader, Task[].class); + ArrayList arrayList; + if (!Objects.isNull(list)) { + arrayList = new ArrayList<>(Arrays.asList(list)); + } else { + arrayList = new ArrayList<>(); + } + return arrayList; + + } catch (JsonParseException e) { + throw new ReadException(MODIFIED_JSON_EXCEPTION); + } catch (IOException e) { + throw new ReadException(); + } catch (Exception e) { + throw new UnknownException(e.toString()); + } + } + +} diff --git a/src/main/java/seedu/duke/ui/TextUi.java b/src/main/java/seedu/duke/ui/TextUi.java new file mode 100644 index 0000000000..a1bf4999b8 --- /dev/null +++ b/src/main/java/seedu/duke/ui/TextUi.java @@ -0,0 +1,63 @@ +package seedu.duke.ui; + +import java.io.PrintStream; +import java.util.Scanner; + +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +public class TextUi { + protected static final Scanner in = new Scanner(System.in); + protected static final PrintStream out = System.out; + + + /** + * Formats the provided message. + * + * @param message The message to be printed + */ + public static String formatMessage(String message) { + return String.format("%s%s\n%s\n%s", StringConstants.LS, StringConstants.LINE, message, StringConstants.LINE); + } + + /** + * Receives command from user. + * + * @return User input + */ + public static String getUserCommand() { + out.print(StringConstants.COMMAND_PROMPT); + return in.nextLine(); + } + + + /** + * Displays a message enclosed by horizontal lines. + */ + public static void showMessage(Object message) { + out.println(formatMessage(message.toString())); + } + + /** + * Displays a message without any special formatting. + */ + public static void showUnformattedMessage(Object message) { + out.println(message.toString()); + } + + /** + * Displays the welcome message. + */ + public static void showHelloMessage() { + showMessage(StringConstants.MESSAGE_HELLO); + } + + /** + * Displays the goodbye message. + */ + public static void showGoodByeMessage() { + showMessage(StringConstants.MESSAGE_GOODBYE); + } + + +} diff --git a/src/main/java/seedu/duke/util/Configuration.java b/src/main/java/seedu/duke/util/Configuration.java new file mode 100644 index 0000000000..4fe0eb2c67 --- /dev/null +++ b/src/main/java/seedu/duke/util/Configuration.java @@ -0,0 +1,122 @@ +package seedu.duke.util; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; + +//@@author Ch40gRv1-Mu +public class Configuration { + + private static final String INDENT = StringConstants.INDENT; + private static final String LS = StringConstants.LS; + + private static final String TRUE = StringConstants.TRUE; + private static final String FALSE = StringConstants.FALSE; + private static final String DESCRIPTION_FORMAT = StringConstants.DESCRIPTION_FORMAT; + + private static final String SHOW_COMPLETED_TASKS_NAME = StringConstants.SHOW_COMPLETED_TASKS_NAME; + private static final String SHOW_COMPLETED_TASKS_EXPLAIN = StringConstants.SHOW_COMPLETED_TASKS_EXPLAIN; + private static final String SHOW_COMPLETED_TASKS_TRUE = StringConstants.SHOW_COMPLETED_TASKS_TRUE; + private static final String SHOW_COMPLETED_TASKS_FALSE = StringConstants.SHOW_COMPLETED_TASKS_FALSE; + + // Legal configuration groups. + public enum ConfigurationGroup { + SHOW_COMPLETED_TASKS + } + + // Each configuration group shall have a default value. + private static final String DEFAULT_VALUE_COMPLETED_TASK_SHOWN = FALSE; + + // Each configuration group shall have a well-defined legal values set. + public static final HashSet LEGAL_VALUE_OF_COMPLETED_TASK_SHOWN = new HashSet<>(Arrays.asList(TRUE, FALSE)); + + // Add the explanation of the configuration group here for help. + public static final HashSet EXPLAIN_CONFIGURE_GROUP = + new HashSet<>(Arrays.asList( + String.format(DESCRIPTION_FORMAT, SHOW_COMPLETED_TASKS_NAME, SHOW_COMPLETED_TASKS_EXPLAIN) + )); + + // Add the explanation of each legal values of a configuration group. + public static final HashSet EXPLAIN_LEGAL_VALUE_OF_COMPLETED_TASK_SHOWN = + new HashSet<>(Arrays.asList( + String.format(DESCRIPTION_FORMAT, TRUE, SHOW_COMPLETED_TASKS_TRUE), + String.format(DESCRIPTION_FORMAT, FALSE, SHOW_COMPLETED_TASKS_FALSE) + )); + + // A HashSet integrating legal values set for all configuration groups. + public static final HashMap> LEGAL_VALUES = new HashMap<>(); + // A HashSet integrating explanations sets of legal values for all configuration groups + public static final HashMap> EXPLAIN_LEGAL_VALUES = new HashMap<>(); + + // Initialise these values + static { + LEGAL_VALUES.put(ConfigurationGroup.SHOW_COMPLETED_TASKS, LEGAL_VALUE_OF_COMPLETED_TASK_SHOWN); + EXPLAIN_LEGAL_VALUES.put(ConfigurationGroup.SHOW_COMPLETED_TASKS, EXPLAIN_LEGAL_VALUE_OF_COMPLETED_TASK_SHOWN); + } + + // HashSet storing all current configuration settings. + public HashMap configurationGroupHashMap; + + + public Configuration() { + configurationGroupHashMap = new HashMap<>(); + LEGAL_VALUES.put(ConfigurationGroup.SHOW_COMPLETED_TASKS, LEGAL_VALUE_OF_COMPLETED_TASK_SHOWN); + EXPLAIN_LEGAL_VALUES.put(ConfigurationGroup.SHOW_COMPLETED_TASKS, EXPLAIN_LEGAL_VALUE_OF_COMPLETED_TASK_SHOWN); + + // Set the value of each configuration group to default + configurationGroupHashMap.put(ConfigurationGroup.SHOW_COMPLETED_TASKS, DEFAULT_VALUE_COMPLETED_TASK_SHOWN); + } + + public Configuration(HashMap configurationGroupStringHashMap) { + this(); + configurationGroupHashMap = configurationGroupStringHashMap; + } + + /** + * Gets the explanation of each legal value of given configuration group. + * @param configureGroup A configuration group to explain + * @return Explanation report of legal values of the given configuration group + */ + public String getValueExplain(ConfigurationGroup configureGroup) { + HashSet valueOfConfigureGroup = EXPLAIN_LEGAL_VALUES.get(configureGroup); + StringBuilder listResult = new StringBuilder(); + for (String explain : valueOfConfigureGroup) { + listResult.append(INDENT).append(explain).append(LS); + } + return listResult.toString(); + } + + /** + * Gets report of current configuration setting. + * @return Report of current configuration setting + */ + public String getConfigurationsReport() { + StringBuilder listResult = new StringBuilder(); + for (ConfigurationGroup group : ConfigurationGroup.values()) { + listResult.append(INDENT).append(String.format(DESCRIPTION_FORMAT, + group, configurationGroupHashMap.get(group))).append(LS); + } + return listResult.toString(); + } + + /** + * Gets current configuration value of a given configuration group. + * @param group Given configuration group + * @return Current configuration value of a given configuration group + */ + public String getConfigurationValue(ConfigurationGroup group) { + return configurationGroupHashMap.get(group); + } + + /** + * Returns a list of all config settings and their descriptions. + * @return String representation of the list of all config settings and descriptions + */ + public static String getAllConfigurationExplanations() { + StringBuilder result = new StringBuilder(); + for (String s : EXPLAIN_CONFIGURE_GROUP) { + result.append(s).append(LS); + } + return result.toString(); + } +} diff --git a/src/main/java/seedu/duke/util/Grades.java b/src/main/java/seedu/duke/util/Grades.java new file mode 100644 index 0000000000..240d53227d --- /dev/null +++ b/src/main/java/seedu/duke/util/Grades.java @@ -0,0 +1,73 @@ +package seedu.duke.util; + +import static seedu.duke.util.NumberConstants.GRADE_POINT_A_AND_A_PLUS; +import static seedu.duke.util.NumberConstants.GRADE_POINT_A_MINUS; +import static seedu.duke.util.NumberConstants.GRADE_POINT_B_PLUS; +import static seedu.duke.util.NumberConstants.GRADE_POINT_B; +import static seedu.duke.util.NumberConstants.GRADE_POINT_B_MINUS; +import static seedu.duke.util.NumberConstants.GRADE_POINT_C_PLUS; +import static seedu.duke.util.NumberConstants.GRADE_POINT_C; +import static seedu.duke.util.NumberConstants.GRADE_POINT_D_PLUS; +import static seedu.duke.util.NumberConstants.GRADE_POINT_D; +import static seedu.duke.util.NumberConstants.GRADE_POINT_ZERO; +import static seedu.duke.util.StringConstants.PLUS_STR; +import static seedu.duke.util.StringConstants.MINUS_STR; +import static seedu.duke.util.StringConstants.NOT_ENTERED_STR; +import static seedu.duke.util.StringConstants.PLUS; +import static seedu.duke.util.StringConstants.DASH; + +//@@author heekit73098 +/** + * Enumeration for grades and their associated grade points. + */ +public enum Grades { + A_PLUS(GRADE_POINT_A_AND_A_PLUS), + A(GRADE_POINT_A_AND_A_PLUS), + A_MINUS(GRADE_POINT_A_MINUS), + B_PLUS(GRADE_POINT_B_PLUS), + B(GRADE_POINT_B), + B_MINUS(GRADE_POINT_B_MINUS), + C_PLUS(GRADE_POINT_C_PLUS), + C(GRADE_POINT_C), + D_PLUS(GRADE_POINT_D_PLUS), + D(GRADE_POINT_D), + F(GRADE_POINT_ZERO), + S(GRADE_POINT_ZERO), + U(GRADE_POINT_ZERO), + CS(GRADE_POINT_ZERO), + CU(GRADE_POINT_ZERO), + NOT_ENTERED(GRADE_POINT_ZERO); + + private final double points; + Grades(double points) { + this.points = points; + } + + public double getPoints() { + return points; + } + + @Override + public String toString() { + final String name = name(); + if (name.contains(PLUS_STR)) { + return name.charAt(0) + PLUS; + } else if (name.contains(MINUS_STR)) { + return name.charAt(0) + DASH; + } else if (name.equals(NOT_ENTERED_STR)) { + return DASH; + } else { + return name; + } + } + + public static Grades getGradeEnum(String moduleGrade) { + for (Grades grade : Grades.values()) { + String gradeStr = grade.toString(); + if (moduleGrade.equals(gradeStr)) { + return grade; + } + } + return NOT_ENTERED; + } +} diff --git a/src/main/java/seedu/duke/util/NumberConstants.java b/src/main/java/seedu/duke/util/NumberConstants.java new file mode 100644 index 0000000000..87471b2897 --- /dev/null +++ b/src/main/java/seedu/duke/util/NumberConstants.java @@ -0,0 +1,48 @@ +package seedu.duke.util; + +public class NumberConstants { + /** + * For Grade Points. + */ + public static final double GRADE_POINT_A_AND_A_PLUS = 5.0; + public static final double GRADE_POINT_A_MINUS = 4.5; + public static final double GRADE_POINT_B_PLUS = 4.0; + public static final double GRADE_POINT_B = 3.5; + public static final double GRADE_POINT_B_MINUS = 3.0; + public static final double GRADE_POINT_C_PLUS = 2.5; + public static final double GRADE_POINT_C = 2.0; + public static final double GRADE_POINT_D_PLUS = 1.5; + public static final double GRADE_POINT_D = 1.0; + public static final double GRADE_POINT_ZERO = 0.0; + + /** + * For Task Indices. + */ + public static final int INVALID_TASK_INDEX = -1; + + /** + * For TaskDuration. + */ + public static final long MINUTE_PER_HOUR = 60; + public static final long MAXIMUM_ALLOWED_DURATION_NUMBER = 1000000000; + + /** + * For AddParsers. + */ + public static final int MAXIMUM_MODULAR_CREDITS = 20; + public static final int MINIMUM_MODULAR_CREDITS = 0; + + /** + * For GpaCommand. + */ + public static final int MAXIMUM_TOTAL_CREDITS = 2000000000; + + /** + * For Indices. + */ + public static final int ZEROTH_INDEX = 0; + public static final int FIRST_INDEX = 1; + public static final int SECOND_INDEX = 2; + public static final int FOURTH_INDEX = 4; + public static final int MINIMUM_INDEX = 0; +} diff --git a/src/main/java/seedu/duke/util/StringConstants.java b/src/main/java/seedu/duke/util/StringConstants.java new file mode 100644 index 0000000000..554f178ed0 --- /dev/null +++ b/src/main/java/seedu/duke/util/StringConstants.java @@ -0,0 +1,364 @@ +package seedu.duke.util; + +public class StringConstants { + /** + * File paths for data files. + */ + public static final String TASK_PATH = "data/task.json"; + public static final String MODULE_PATH = "data/module.json"; + public static final String CONFIGURATION_PATH = "data/configuration.json"; + public static final String TASK_TEST_PATH = "data/test/task.json"; + public static final String MODULE_TEST_PATH = "data/test/module.json"; + public static final String CONFIGURATION_TEST_PATH = "data/test/configuration.json"; + + /** + * For start and exit of program. + */ + public static final String MESSAGE_HELLO = "Hello, welcome to Mod Happy!"; + public static final String MESSAGE_GOODBYE = "See you later!"; + + /** + * For loading of data. + */ + public static final String MODULE_DATA_LOAD_FAILED = "Failed to load module data. " + + "Empty module list loaded instead."; + public static final String MODULE_DATA_LOAD_SUCCESS = "Successfully loaded module data!"; + public static final String TASK_DATA_LOAD_FAILED = "Failed to load general task data. " + + "Empty list of general tasks loaded instead."; + public static final String TASK_DATA_LOAD_SUCCESS = "Successfully loaded general task data!"; + public static final String CONFIGURATION_DATA_LOAD_FAILED = "Failed to load configuration data. " + + "Default config values loaded instead."; + public static final String CONFIGURATION_DATA_LOAD_SUCCESS = "Successfully loaded configuration data!"; + public static final String NO_CONFIG_DATA_FILE = "No saved config data found. Default config values loaded."; + + + /** + * For AddCommand. + */ + public static final String ADD_TASK_MESSAGE_TOP = "Hey! I have added this task under %s!"; + public static final String ADD_MODULE_MESSAGE_TOP = "Hey! I have added this module!"; + public static final String MODULE_ALREADY_EXISTS = "A module with that name already exists..."; + public static final String ESTIMATED_WORKING_TIME = "Estimated working time: "; + + + /** + * For DeleteCommand. + */ + public static final String DELETE_MESSAGE = "%s has been deleted."; + public static final String DELETE_ABORT = "Deletion has been cancelled."; + public static final String DELETE_CONFIRMATION = "%s contains task(s).\n" + + "Are you sure you want to delete this? (yes/no)"; + public static final String DELETE_CONFIRMATION_INPUT_ERROR = "Invalid Input. Please enter yes or no."; + + /** + * For EditCommand. + */ + public static final String EDIT_TASK_SUCCESS = "The %s of %s has been changed."; + public static final String EDIT_MODULE_SUCCESS = "The description of %s has been changed."; + public static final String EDIT_TASK_WITH_MODULE_SUCCESS = "The %s of %s from %s has been changed."; + + /** + * For ExitCommand. + */ + public static final String READY_EXIT = "I am ready to exit *_*"; + + + /** + * For GpaCommand. + */ + public static final String GPA_MESSAGE = "Your GPA is %.02f! :)"; + + /** + * For GradeCommand. + */ + public static final String GRADE_ADDED_MESSAGE = "Your grade for %s has been added."; + public static final String GRADE_CHANGED_MESSAGE = "Your grade for %s has been changed."; + + /** + * For ListCommand. + */ + public static final String LIST_MESSAGE = "Ok! Here are the task(s) in your list:\n%s"; + public static final String EMPTY_LIST = "(empty)"; + public static final String HIDDEN_TASKS_COUNT = "--- %d completed task(s) hidden ---"; + + /** + * For MarkCommand. + */ + public static final String MARK_MESSAGE_TOP = "Nice! I have marked this task as completed!"; + public static final String UNMARK_MESSAGE_TOP = "Ok! I have marked this task for you as uncompleted!"; + public static final String ICON_UNCOMPLETED = "( )"; + public static final String ICON_COMPLETED = "(X)"; + + /** + * For ResetCommand. + */ + public static final String RESET_MESSAGE = "All modules and tasks have been removed."; + + /** + * For HelpCommand. + */ + public static final String HELP_NOTE = "Compulsory parameters are fully capitalised: e.g. MODULE_CODE.\n" + + "Optional parameters are in square brackets: e.g. [-d MODULE_DESCRIPTION]"; + public static final String ADD_HELP = "Adds a module or task as indicated by the command input.\n" + + "Format to add module: add mod MODULE_CODE MODULAR_CREDITS [-d \"MODULE_DESCRIPTION\"]\n" + + "Format to add task: add task \"TASK_NAME\" [-m MODULE_CODE] [-d \"TASK_DESCRIPTION\"]" + + " [-t \"ESTIMATED_WORKING_TIME\"]"; + public static final String DELETE_HELP = "Deletes a module or task as indicated by command input.\n" + + "Format to delete a module: del mod MODULE_CODE\n" + + "Format to delete a task: del task TASK_NUMBER [-m MODULE_CODE]"; + public static final String EDIT_HELP = "Edits a module or task as indicated by command input.\n" + + "Format to edit a module: edit mod MODULE_CODE -d \"MODULE_DESCRIPTION\"\n" + + "Format to edit a task: edit task TASK_INDEX [-m MODULE_CODE]" + + " (-n \"TASK_NAME\" | -d \"TASK_DESCRIPTION\" | -t \"ESTIMATED_WORKING_TIME\")"; + public static final String EXIT_HELP = "Exits the program.\nFormat to exit program: exit"; + public static final String GRADE_HELP = "Sets the grade for the specified module.\n" + + "Accepted values: A+, A, B+, B, B-, C+, C, D+, D, F, S, U, CS, CU\n" + + "Format to set a module's grade: grade MODULE_CODE MODULE_GRADE"; + public static final String GPA_HELP = "Computes and displays the GPA based the inputted grades of all modules.\n" + + "Modules without any assigned grade are omitted from the calculation.\n" + + "Format to display GPA : gpa\n"; + public static final String LIST_HELP = "Displays a list of tasks, grouped by module code.\n" + + "Completed tasks may or may not be shown depending on current user preferences.\n" + + "If tag name is provided, list will only display tasks containing the tag name.\n" + + "Format to list all tasks: list\n" + + "Format to list task containing a tag: list TAG_NAME"; + public static final String MARK_HELP = "Mark a task with the given task number from the specified module." + + "If no module code is given, the task to be marked will be drawn from the \"general tasks\" list.\n" + + "Format to mark a task as completed: mark c TASK_NUMBER [-m MODULE_CODE]\n" + + "Format to mark a task as uncompleted: mark u TASK_NUMBER [-m MODULE_CODE]"; + public static final String RESET_HELP = "Removes all modules and tasks.\n" + + "Format to remove all modules and tasks: reset"; + public static final String SAVE_HELP = "Saves your modules and tasks.\n" + + "Format to save: save"; + public static final String HELP = "Displays help and format for selected command.\n" + + "Format to display help for specific command: help COMMAND\n" + + "Available commands: exit, add, del, edit, grade, gpa, help, list, mark, option, reset, save, tag"; + public static final String TAG_HELP = "Set a custom tag for your tasks. The tag cannot contain whitespace.\n" + + "Format to add a tag: tag add TASK_INDEX [-m MODULE_CODE] TAG_NAME\n" + + "Format to delete a tag: tag del TASK_INDEX [-m MODULE_CODE] TAG_NAME"; + public static final String HELP_EXCEPTION = "Sorry, but no help exists for that command."; + public static final String HELP_COMMAND_ARGUMENT = "command"; + public static final String OPTION_HELP = "View and edit program configuration options.\n" + + "Format to view all available configs: option\n" + + "Format to view details for a specific config option: option CONFIG_NAME\n" + + "Format to set a config option: option CONFIG_NAME=NEW_VALUE\n\n" + + "Available configs:\n"; + + + /** + * For SaveCommand. + */ + public static final String MODULE_DATA_SAVE_FAILED = "Failed to write module data to file. " + + "Your modules were NOT saved!"; + public static final String MODULE_DATA_SAVE_SUCCESS = "Module data written to file."; + public static final String TASK_DATA_SAVE_FAILED = "Failed to write general task data to file. " + + "Your general tasks were NOT saved!"; + public static final String TASK_DATA_SAVE_SUCCESS = "General tasks written to file."; + public static final String CONFIGURATION_DATA_SAVE_FAILED = "Failed to write config options to file. " + + "Your preferences were NOT saved!"; + public static final String CONFIGURATION_DATA_SAVE_SUCCESS = "Config options written to file."; + + + /** + * For OptionCommand. + */ + public static final String OPTION_SET_SUCCESS = "Preferences updated: "; + public static final String OPTION_CHECK_CONFIGURATIONS = "Available config settings: "; + + + /** + * For TagCommand. + */ + public static final String ADD_TAG_MESSAGE = "Tag \"%s\" added:\n%s."; + public static final String DEL_TAG_MESSAGE = "Tag \"%s\" removed:\n%s"; + + + /** + * For exceptions. + */ + public static final String ERROR_NO_SUCH_MODULE = "Sorry, no such module exists ._."; + public static final String ERROR_NO_SUCH_TASK = "Sorry, no such task exists ._."; + public static final String ERROR_PARSE_FAILED = "This parse failed 0_0"; + public static final String ERROR_PARSE_INVALID_PARAM_GENERAL = "\nInvalid compulsory parameters. " + + "Please check and try again."; + public static final String ERROR_PARSE_INVALID_PARAM = "\nInvalid %s '%s'. " + + "Please check and try again."; + public static final String ERROR_PARSE_MISSING_PARAM = "\nMissing %s. " + + "Please check and try again."; + public static final String ERROR_EMPTY_PARAM = "\nSorry, you have entered an empty %s ._. " + + "\nPlease try again."; + public static final String ERROR_ADDITIONAL_PARAMETER = "\nSorry, this command should have no parameters." + + "\nPlease enter only the command word."; + public static final String ERROR_EXCESS_ARGUMENT = "\nExcess argument '%s'.\nPlease delete it and try again."; + public static final String ERROR_INVALID_FLAG = "\nInvalid flag '%s'." + + "\nPlease check and try again. " + + "\nYou may input 'help' followed by your command word to view the expected input format."; + public static final String ERROR_MISSING_FLAG = "\nMissing flag." + + "\nPlease check and try again. " + + "\nYou may input 'help' followed by your command word to view the expected input format."; + public static final String ERROR_INVALID_TAG_OPERATION = "\nInvalid operation '%s'." + + "\nPlease try again. Accepted commands are: add, del."; + public static final String ERROR_MISSING_TAG_OPERATION = "\nMissing operation." + + "\nPlease try again. Accepted commands are: add, del."; + public static final String ERROR_INVALID_MODULE_GRADE = "\nInvalid module grade '%s'." + + "\nPlease try again. Accepted module grades are: A+, A, A-, B+, B, B-, C+, C, D+, D, F, CS, CU, S, U."; + public static final String ERROR_MISSING_MODULE_GRADE = "\nMissing module grade." + + "\nPlease try again. Accepted module grades are: A+, A, A-, B+, B, B-, C+, C, D+, D, F, CS, CU, S, U."; + public static final String ERROR_INVALID_NUMBER = "\nInvalid number format for %s '%s'." + + "\nPlease try again using a positive integer."; + public static final String ERROR_INVALID_MODULAR_CREDIT = "\nInvalid number format for modular credits '%s'." + + "\n(Accepted range: 0 to 20)"; + public static final String ERROR_MISSING_NUMBER = "\nMissing %s." + + "\nPlease try again using a positive integer."; + public static final String ERROR_MISSING_MODULAR_CREDIT = "\nMissing modular credits." + + "\nPlease try again using a integer from 0 to 20."; + public static final String ERROR_UNKNOWN_COMMAND = "Sorry, I don't understand the following command:"; + public static final String ERROR_UNSUPPORTED_RESULT_TYPE = "Sorry, the value \"%s\" is not supported for " + + "configuration \"%s\"."; + public static final String ERROR_WRITE_FILE = "Error writing to file..."; + public static final String ERROR_READ_FILE = "Error reading from file..."; + public static final String ERROR_FILE_CREATE_FAIL = "Sorry, file creation failed..."; + public static final String ERROR_NO_SUCH_TAG = "Sorry, no such tag exists ._."; + public static final String ERROR_UNKNOWN_CONFIGURATION_GROUP = "Sorry, no config named \"%s\" exists.\n" + + "View all available config settings with \"option\"."; + public static final String ERROR_MODULE_LIST_EMPTY = "\nSorry, you have 0 MCs counted towards your GPA ._." + + "\nPlease add some modules or grades!"; + public static final String ERROR_WRONG_DURATION_FORMAT = "Sorry, the estimated time is in wrong format ._."; + public static final String ERROR_DUPLICATE_MODULE = "Multiple modules with module code \"%s\" found. " + + "Aborting load..."; + public static final String ERROR_INVALID_MODULE_CREDITS = "Invalid module credits found (%s had %d MCs). " + + "Aborting load..."; + public static final String ERROR_INVALID_MODULE = "Invalid module data found. Aborting load..."; + public static final String ERROR_CATCH_UNKNOWN_EXCEPTION = "Oops, I caught an exception that I wasn't expecting " + + "0_0:\n%s"; + public static final String MODIFIED_JSON_EXCEPTION = "\nSomething went wrong trying to read the JSON save data.\n" + + "Has it been manually modified? >:("; + public static final String ERROR_INVALID_CONFIGURATION = "Invalid configuration found in loaded configuration data." + + " Aborting load..."; + public static final String ERROR_INVALID_CONFIG_VALUE = "Configuration \"%s\" has illegal value \"%s\"." + + " Aborting load..."; + public static final String ERROR_INVALID_TASK_DATA = "Invalid task data found in loaded data. Aborting load..."; + public static final String ERROR_GRADE_REMOVAL_FAILED = "Your grade has not been entered yet for this module..."; + + /** + * For parsers. + */ + public static final String TASK_NAME_FLAG = "-n"; + public static final String TASK_DESCRIPTION_FLAG = "-d"; + public static final String TASK_ESTIMATED_WORKING_TIME_FLAG = "-t"; + public static final String TASK_PARAMETER = "taskParameter"; + public static final String TASK_PARAMETER_FLAG = "taskParameterFlag"; + public static final String TASK_PARAMETER_STR = "task parameter"; + public static final String TASK_NAME = "taskName"; + public static final String TASK_DESCRIPTION = "taskDescription"; + public static final String TASK_ESTIMATED_WORKING_TIME = "estimatedWorkingTime"; + public static final String TASK_MODULE = "taskModule"; + public static final String TASK_NUMBER = "taskNumber"; + public static final String TASK_NUMBER_STR = "task number"; + public static final String TASK_STR = "task"; + public static final String TASK_NAME_STR = "task name"; + public static final String TASK_DESCRIPTION_STR = "task description"; + public static final String TASK_ESTIMATED_WORKING_TIME_STR = "estimated working time"; + public static final String MODULE_CODE = "moduleCode"; + public static final String MODULE_CODE_STR = "module code"; + public static final String MODULE_FIELD_STR = "module code field"; + public static final String MODULE_DESCRIPTION = "moduleDescription"; + public static final String MODULE_DESCRIPTION_STR = "module description"; + public static final String MODULAR_CREDIT = "modularCredit"; + public static final String MODULE_GRADE = "moduleGrade"; + public static final String MODULE_STR = "mod"; + public static final String FLAG = "flag"; + public static final String MODULE_FLAG = "moduleFlag"; + public static final String CONFIGURATION_GROUP_WORD = "configurationGroupWord"; + public static final String NEW_VALUE = "newValue"; + public static final String COMPLETED_FLAG = "c"; + public static final String UNCOMPLETED_FLAG = "u"; + public static final String ARGUMENT = "arguments"; + public static final String TAG_NAME = "tagName"; + public static final String TAG_NAME_STR = "tag name"; + public static final String TAG_OPERATION = "tagOperation"; + public static final String INVALID = "invalid"; + public static final String INVALID_MOD_FLAG = "invalidModFlag"; + public static final String INVALID_TASK_NAME_FLAG = "invalidTaskNameFlag"; + public static final String INVALID_TASK_DES_FLAG = "invalidTaskDesFlag"; + public static final String INVALID_MOD_DES_FLAG = "invalidModDesFlag"; + public static final String INVALID_TIME_FLAG = "invalidTimeFlag"; + public static final String INVALID_MODULE_GRADE = "invalidModuleGrade"; + public static final String INVALID_TAG_COMMAND = "invalidTagCommand"; + public static final String COMMAND_WORD = "commandWord"; + public static final String EXIT_COMMAND_WORD = "exit"; + public static final String ADD_COMMAND_WORD = "add"; + public static final String DELETE_COMMAND_WORD = "del"; + public static final String EDIT_COMMAND_WORD = "edit"; + public static final String GPA_COMMAND_WORD = "gpa"; + public static final String GRADE_COMMAND_WORD = "grade"; + public static final String LIST_COMMAND_WORD = "list"; + public static final String MARK_COMMAND_WORD = "mark"; + public static final String RESET_COMMAND_WORD = "reset"; + public static final String HELP_COMMAND_WORD = "help"; + public static final String SAVE_COMMAND_WORD = "save"; + public static final String TAG_COMMAND_WORD = "tag"; + public static final String OPTION_COMMAND_WORD = "option"; + + + /** + * For regex constants. + */ + public static final String ANY_TEXT = ".*"; + public static final String ANY_FLAG = "\\s+-\\w\\s+"; + public static final String ANY_FLAG_NO_WHITESPACE = "-\\w"; + public static final String WORD_CHAR_ONLY = "\\w+"; + public static final String UNRESTRICTED_INT = "-?\\d+"; + public static final String POSITIVE_INT = "\\d+"; + public static final String TASK_PARAMETERS_FLAG = "\\s+(-d|-n|-t)\\s+"; + public static final String TASK_PARAMETERS_FLAG_NO_NAME = "\\s+(-d|-t)\\s+"; + public static final String DESCRIPTION_FLAG = "\\s+-d\\s+"; + public static final String QUOTED_UNRESTRICTED_STR = "\"[^\"]*\""; + public static final String MODULE_GRADES_MATCH = "(?i)(CU|CS|[A-B][+-]?|[C-D][+]?|F|S|U|-)"; + public static final String MARK_COMMAND_FLAGS = "(c|u)"; + public static final String TAG_COMMAND_FLAGS = "(add|del)"; + public static final String TASK_MODULE_FLAG = " -m "; + public static final String WHITESPACES = "\\s+"; + + /** + * For grades. + */ + public static final String DASH = "-"; + public static final String PLUS = "+"; + public static final String PLUS_STR = "PLUS"; + public static final String MINUS_STR = "MINUS"; + public static final String NOT_ENTERED_STR = "NOT_ENTERED"; + + /** + * For option. + */ + public static final String DESCRIPTION_FORMAT = "%s: %s"; + public static final String TRUE = "true"; + public static final String FALSE = "false"; + + public static final String SHOW_COMPLETED_TASKS_NAME = "SHOW_COMPLETED_TASKS"; + public static final String SHOW_COMPLETED_TASKS_EXPLAIN = "Determines if completed tasks should be displayed" + + " by \"list\" or not."; + public static final String SHOW_COMPLETED_TASKS_TRUE = "Show completed tasks"; + public static final String SHOW_COMPLETED_TASKS_FALSE = "Hide completed tasks"; + + /** + * For TaskDuration. + */ + public static final String DURATION_GROUP_WORD = "duration"; + public static final String DURATION_UNIT_GROUP_WORD = "durationUnit"; + public static final String TO_STRING_FORMAT_WITH_HOUR_AND_MINUTE = "%d hour(s) %d minute(s)"; + public static final String TO_STRING_FORMAT_WITH_HOUR_ONLY = "%d hour(s)"; + public static final String TO_STRING_FORMAT_WITH_MINUTE_ONLY = "%d minute(s)"; + public static final String DURATION_STRING_FORMAT = "(?[1-9]\\d*\\.?\\d*|0\\.\\d*[1-9])" + + "\\s*(?.*)"; + + /** + * General strings. + */ + public static final String EMPTY_STRING = ""; + public static final String INDENT = " "; + public static final String LS = System.lineSeparator(); + public static final String LINE = "____________________________________________________________"; + public static final String COMMAND_PROMPT = "> "; +} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/duke/DukeTest.java deleted file mode 100644 index 2dda5fd651..0000000000 --- a/src/test/java/seedu/duke/DukeTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package seedu.duke; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -class DukeTest { - @Test - public void sampleTest() { - assertTrue(true); - } -} diff --git a/src/test/java/seedu/duke/data/TaskDurationTest.java b/src/test/java/seedu/duke/data/TaskDurationTest.java new file mode 100644 index 0000000000..0df189d680 --- /dev/null +++ b/src/test/java/seedu/duke/data/TaskDurationTest.java @@ -0,0 +1,252 @@ +package seedu.duke.data; + +import org.junit.jupiter.api.Test; +import seedu.duke.exceptions.ModHappyException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +//@@author Ch40gRv1-Mu +public class TaskDurationTest { + TaskDuration taskDuration; + + @Test + public void initializeWithHour_CheckAllDurationUnit() { + try { + // check all notation of hours + + // h + taskDuration = new TaskDuration("1.0h"); + assertEquals("1 hour(s)", taskDuration.toString()); + + // h + taskDuration = new TaskDuration("1.0 h"); + assertEquals("1 hour(s)", taskDuration.toString()); + + // H + taskDuration = new TaskDuration("1.0H"); + assertEquals("1 hour(s)", taskDuration.toString()); + + // H + taskDuration = new TaskDuration("1.0 H"); + assertEquals("1 hour(s)", taskDuration.toString()); + + // hours + taskDuration = new TaskDuration("1.0hours"); + assertEquals("1 hour(s)", taskDuration.toString()); + + // hours + taskDuration = new TaskDuration("1.0 hours"); + assertEquals("1 hour(s)", taskDuration.toString()); + + // Hours + taskDuration = new TaskDuration("1.0Hours"); + assertEquals("1 hour(s)", taskDuration.toString()); + + // Hours + taskDuration = new TaskDuration("1.0 Hours"); + assertEquals("1 hour(s)", taskDuration.toString()); + + // hour + taskDuration = new TaskDuration("1.0 hour"); + assertEquals("1 hour(s)", taskDuration.toString()); + + // hour + taskDuration = new TaskDuration("1.0hour"); + assertEquals("1 hour(s)", taskDuration.toString()); + + // hour + taskDuration = new TaskDuration("1.0 hour"); + assertEquals("1 hour(s)", taskDuration.toString()); + + // Hour + taskDuration = new TaskDuration("1.0Hour"); + assertEquals("1 hour(s)", taskDuration.toString()); + + // Hour + taskDuration = new TaskDuration("1.0 Hour"); + assertEquals("1 hour(s)", taskDuration.toString()); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void initializeWithHour_CheckAllPossibleDurations() { + try { + // check all notation of hours + taskDuration = new TaskDuration("1h"); + assertEquals("1 hour(s)", taskDuration.toString()); + + taskDuration = new TaskDuration("0.5h"); + assertEquals("30 minute(s)", taskDuration.toString()); + + taskDuration = new TaskDuration("1.1h"); + assertEquals("1 hour(s) 6 minute(s)", taskDuration.toString()); + + taskDuration = new TaskDuration("1.7h"); + assertEquals("1 hour(s) 42 minute(s)", taskDuration.toString()); + + + } catch (Exception e) { + e.printStackTrace(); + } + } + + + @Test + public void initializeWithMinute_CheckAllDurationUnit() { + try { + // check all notation of hours + + // m + taskDuration = new TaskDuration("1.0m"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // m + taskDuration = new TaskDuration("1.0 m"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // M + taskDuration = new TaskDuration("1.0M"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // M + taskDuration = new TaskDuration("1.0 M"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // min + taskDuration = new TaskDuration("1.0min"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // min + taskDuration = new TaskDuration("1.0 min"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // Min + taskDuration = new TaskDuration("1.0Min"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // Min + taskDuration = new TaskDuration("1.0 Min"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // mins + taskDuration = new TaskDuration("1.0mins"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // mins + taskDuration = new TaskDuration("1.0 mins"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // Mins + taskDuration = new TaskDuration("1.0Mins"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // Mins + taskDuration = new TaskDuration("1.0 Mins"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // minutes + taskDuration = new TaskDuration("1.0minutes"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // minutes + taskDuration = new TaskDuration("1.0 minutes"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // Minutes + taskDuration = new TaskDuration("1.0Minutes"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // Minutes + taskDuration = new TaskDuration("1.0 Minutes"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // minute + taskDuration = new TaskDuration("1.0minute"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // minute + taskDuration = new TaskDuration("1.0 minutes"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // Minute + taskDuration = new TaskDuration("1.0Minute"); + assertEquals("1 minute(s)", taskDuration.toString()); + + // Minute + taskDuration = new TaskDuration("1.0 minutes"); + assertEquals("1 minute(s)", taskDuration.toString()); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void initializeWithMinute_CheckAllPossibleValues() { + try { + + taskDuration = new TaskDuration("1 m"); + assertEquals("1 minute(s)", taskDuration.toString()); + + taskDuration = new TaskDuration("0.5 m"); + assertEquals("1 minute(s)", taskDuration.toString()); + + taskDuration = new TaskDuration("0.3 m"); + assertEquals("0 minute(s)", taskDuration.toString()); + + taskDuration = new TaskDuration("1.3 m"); + assertEquals("1 minute(s)", taskDuration.toString()); + + taskDuration = new TaskDuration("70 m"); + assertEquals("1 hour(s) 10 minute(s)", taskDuration.toString()); + + taskDuration = new TaskDuration("70.5 m"); + assertEquals("1 hour(s) 11 minute(s)", taskDuration.toString()); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void initializeWithMinute_withoutUnit() { + try { + taskDuration = new TaskDuration("1"); + } catch (ModHappyException e) { + return; + } catch (Exception e) { + fail(); + } + + } + + @Test + public void initializeWithMinute_withWrongUnitMinute() { + try { + taskDuration = new TaskDuration("1minu"); + } catch (ModHappyException e) { + return; + } catch (Exception e) { + fail(); + } + + } + + @Test + public void initializeWithMinute_withWrongUnitHour() { + try { + taskDuration = new TaskDuration("1hr"); + } catch (ModHappyException e) { + return; + } catch (Exception e) { + fail(); + } + + } + + +} diff --git a/src/test/java/seedu/duke/parsers/ModHappyParserTest.java b/src/test/java/seedu/duke/parsers/ModHappyParserTest.java new file mode 100644 index 0000000000..9703118675 --- /dev/null +++ b/src/test/java/seedu/duke/parsers/ModHappyParserTest.java @@ -0,0 +1,754 @@ +package seedu.duke.parsers; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import seedu.duke.commands.AddCommand; +import seedu.duke.commands.Command; +import seedu.duke.commands.DeleteCommand; +import seedu.duke.commands.EditCommand; +import seedu.duke.commands.ExitCommand; +import seedu.duke.commands.GradeCommand; +import seedu.duke.commands.GpaCommand; +import seedu.duke.commands.HelpCommand; +import seedu.duke.commands.ListCommand; +import seedu.duke.commands.MarkCommand; +import seedu.duke.commands.OptionCommand; +import seedu.duke.commands.ResetCommand; +import seedu.duke.commands.SaveCommand; +import seedu.duke.commands.TagCommand; +import seedu.duke.exceptions.AdditionalParameterException; +import seedu.duke.exceptions.InvalidNumberException; +import seedu.duke.exceptions.InvalidFlagException; +import seedu.duke.exceptions.ExcessArgumentException; +import seedu.duke.exceptions.InvalidCompulsoryParameterException; +import seedu.duke.exceptions.MissingCompulsoryParameterException; +import seedu.duke.exceptions.MissingNumberException; +import seedu.duke.exceptions.InvalidTagOperationException; +import seedu.duke.exceptions.UnknownCommandException; +import seedu.duke.data.Module; +import seedu.duke.data.Task; + +public class ModHappyParserTest { + private ModHappyParser parser; + + + private void testParseCommand_expectAdditionalParameterException(String testString) { + assertThrows(AdditionalParameterException.class, () -> parser.parseCommand(testString)); + } + + private void testParseCommand_expectInvalidNumberException(String testString) { + assertThrows(InvalidNumberException.class, () -> parser.parseCommand(testString)); + } + + private void testParseCommand_expectInvalidCompulsoryParameterException(String testString) { + assertThrows(InvalidCompulsoryParameterException.class, () -> parser.parseCommand(testString)); + } + + private void testParseCommand_expectMissingCompulsoryParameterException(String testString) { + assertThrows(MissingCompulsoryParameterException.class, () -> parser.parseCommand(testString)); + } + + private void testParseCommand_expectMissingNumberException(String testString) { + assertThrows(MissingNumberException.class, () -> parser.parseCommand(testString)); + } + + private void testParseCommand_expectUnknownCommandException(String testString) { + assertThrows(UnknownCommandException.class, () -> parser.parseCommand(testString)); + } + + + private void testParseCommand_expectInvalidExcessArgumentException(String testString) { + assertThrows(ExcessArgumentException.class, () -> parser.parseCommand(testString)); + + } + + private void testParseCommand_expectInvalidTagOperationException(String testString) { + assertThrows(InvalidTagOperationException.class, () -> parser.parseCommand(testString)); + } + + + private void testParseCommand_expectInvalidFlagException(String testString) { + assertThrows(InvalidFlagException.class, () -> parser.parseCommand(testString)); + } + + //@@author chooyikai + @BeforeEach + public void setUp() { + parser = new ModHappyParser(); + } + + @Test + public void parse_unrecognisedCommand_throwsException() { + final String testString = "invalid command"; + try { + parser.parseCommand(testString); + fail(); + } catch (UnknownCommandException e) { + return; + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_addCommand_task_noDescription_parsedCorrectly() { + final String testString = "add task \"/t/t/t/t-d-d-d-d-d -d/t/t-d-d-d-d -d-d-d \" "; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof AddCommand); + Task t = ((AddCommand) c).getNewTask(); + assertNotEquals(null, t); + assertNull(((AddCommand) c).getNewModule()); + assertEquals("/t/t/t/t-d-d-d-d-d -d/t/t-d-d-d-d -d-d-d", t.getTaskName()); + assertNull(t.getTaskDescription()); + assertNull(t.getWorkingTimeString()); + assertNull(((AddCommand) c).getTargetModuleName()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_addCommand_task_withDescription_parsedCorrectly() { + final String testString = "add task \"/t/t/t/t-d-d-d-d-d -d/t/t-d-d-d-d -d-d-d \" " + + "-d \"-d-d-d /t /m -d -d \""; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof AddCommand); + Task t = ((AddCommand) c).getNewTask(); + assertNotEquals(null, t); + assertNull(((AddCommand) c).getNewModule()); + assertEquals("/t/t/t/t-d-d-d-d-d -d/t/t-d-d-d-d -d-d-d", t.getTaskName()); + assertEquals("-d-d-d /t /m -d -d", t.getTaskDescription()); + assertNull(t.getWorkingTimeString()); + assertNull(((AddCommand) c).getTargetModuleName()); + } catch (Exception e) { + fail(); + } + } + + //@@author + @Test + public void parse_addCommand_mod_unknownCommand() { + final String testString = "add . mod 1 4"; + testParseCommand_expectUnknownCommandException(testString); + } + + @Test + public void parse_addCommand_mod_invalidModuleCode() { + final String testString = "add mod . 1 4"; + testParseCommand_expectInvalidCompulsoryParameterException(testString); + } + + @Test + public void parse_addCommand_mod_invalidModularCredits() { + final String testString = "add mod 1 .4"; + testParseCommand_expectInvalidNumberException(testString); + } + + @Test + public void parse_addCommand_mod_excessArguments() { + final String testString = "add mod 1 4 ."; + testParseCommand_expectInvalidExcessArgumentException(testString); + } + + @Test + public void parse_addCommand_task_invalidCommand() { + final String testString = "add . task \"test\" -m cs2113t -d \"desc\" -t \"2 hours\""; + testParseCommand_expectUnknownCommandException(testString); + } + + @Test + public void parse_addCommand_task_invalidTaskName() { + final String testString = "add task . \"test\" -m cs2113t -d \"desc\" -t \"2 hours\""; + testParseCommand_expectInvalidCompulsoryParameterException(testString); + } + + @Test + public void parse_addCommand_task_invalidModuleCode() { + final String testString = "add task \"test\" -m . cs2113t -d \"desc\" -t \"2 hours\""; + testParseCommand_expectInvalidCompulsoryParameterException(testString); + } + + @Test + public void parse_addCommand_task_invalidDescription() { + final String testString = "add task \"test\" -m cs2113t -d .\"desc\" -t \"2 hours\""; + testParseCommand_expectInvalidExcessArgumentException(testString); + } + + @Test + public void parse_addCommand_task_invalidTime() { + final String testString = "add task \"test\" -m cs2113t -d \"desc\" -t .\"2 hours\""; + testParseCommand_expectInvalidExcessArgumentException(testString); + } + + @Test + public void parse_addCommand_task_wrongOrder() { + final String testString = "add task \"test\" -m cs2113t -t .\"2 hours\" -d .\"desc\" "; + testParseCommand_expectInvalidExcessArgumentException(testString); + } + + @Test + public void parse_editCommand_mod_invalidCommand() { + final String testString = "edit . mod cs2113t -d \"changed\""; + testParseCommand_expectUnknownCommandException(testString); + } + + @Test + public void parse_editCommand_mod_invalidModule() { + final String testString = "edit mod . cs2113t -d \"changed\""; + testParseCommand_expectInvalidCompulsoryParameterException(testString); + } + + @Test + public void parse_editCommand_mod_invalidDescription() { + final String testString = "edit mod cs2113t -d .\"changed\""; + testParseCommand_expectInvalidCompulsoryParameterException(testString); + } + + @Test + public void parse_editCommand_task_invalidCommand() { + final String testString = "edit .task 1 -m CS2113T -d \"changed\""; + testParseCommand_expectUnknownCommandException(testString); + } + + @Test + public void parse_editCommand_task_invalidTaskNumber() { + final String testString = "edit task .1 -m CS2113T -d \"changed\""; + testParseCommand_expectInvalidNumberException(testString); + } + + @Test + public void parse_editCommand_task_invalidModuleCode() { + final String testString = "edit task 1 -m .CS2113T -d \"changed\""; + testParseCommand_expectInvalidCompulsoryParameterException(testString); + } + + @Test + public void parse_editCommand_task_invalidDescription() { + final String testString = "edit task 1 -m CS2113T -d .\"changed\""; + testParseCommand_expectInvalidCompulsoryParameterException(testString); + } + + //@@author chooyikai + @Test + public void parse_addCommand_task_withTargetModule_parsedCorrectly() { + final String testString = "add task \"/t/t/t/t-d-d-d-d-d -d/t/t-d-d-d-d -d-d-d \" " + + "-m cs2113t"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof AddCommand); + Task t = ((AddCommand) c).getNewTask(); + assertNotEquals(null, t); + assertNull(((AddCommand) c).getNewModule()); + assertEquals("/t/t/t/t-d-d-d-d-d -d/t/t-d-d-d-d -d-d-d", t.getTaskName()); + assertNull(t.getTaskDescription()); + assertNull(t.getWorkingTimeString()); + assertEquals("cs2113t", ((AddCommand) c).getTargetModuleName()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_addCommand_task_withDescription_withTargetModule_parsedCorrectly() { + final String testString = "add task \"/t/t/t/t-d\" -m cs2113t -d \"-d-d-d /t /m -d -d \" "; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof AddCommand); + Task t = ((AddCommand) c).getNewTask(); + assertNotEquals(null, t); + assertNull(((AddCommand) c).getNewModule()); + assertEquals("/t/t/t/t-d", t.getTaskName()); + assertEquals("-d-d-d /t /m -d -d", t.getTaskDescription()); + assertNull(t.getWorkingTimeString()); + assertEquals("cs2113t", ((AddCommand) c).getTargetModuleName()); + } catch (Exception e) { + fail(); + } + } + + + @Test + public void parse_addCommand_task_withDescription_withWorkingTime_withTargetModule_parsedCorrectly() { + final String testString = "add task \"/t/t/t/t-d\" -m cs2113t -d \"-d-d-t-m /m -m -d -t \" " + + "-t \"75 minutes\" "; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof AddCommand); + Task t = ((AddCommand) c).getNewTask(); + assertNotEquals(null, t); + assertNull(((AddCommand) c).getNewModule()); + assertEquals("/t/t/t/t-d", t.getTaskName()); + assertEquals("-d-d-t-m /m -m -d -t", t.getTaskDescription()); + assertEquals("1 hour(s) 15 minute(s)", t.getWorkingTimeString()); + assertEquals("cs2113t", ((AddCommand) c).getTargetModuleName()); + } catch (Exception e) { + fail(); + } + } + + //@@author + @Test + public void parse_addCommand_duplicateTaskDescription() { + final String testString = "add task \"000\" -d \"123\" -t \"456\" -d \"789\""; + testParseCommand_expectInvalidExcessArgumentException(testString); + } + + + + @Test + public void parse_addCommand_module_noDescription_parsedCorrectly() { + final String testString = "add \t mod modulecode 4 \t\t "; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof AddCommand); + Module m = ((AddCommand) c).getNewModule(); + assertNotEquals(null, m); + assertNull(((AddCommand) c).getNewTask()); + assertEquals("modulecode", m.getModuleCode()); + assertEquals(4, m.getModularCredit()); + assertNull(m.getModuleDescription()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_addCommand_module_invalidModularCredit() { + final String testString = "add \t mod modulecode four \t\t "; + testParseCommand_expectInvalidNumberException(testString); + } + + @Test + public void parse_addCommand_module_noDescription_invalidModuleCode() { + final String testString = "add \t mod module code /c 4 \t\t "; + testParseCommand_expectInvalidNumberException(testString); + } + + + @Test + public void parse_addCommand_module_withDescription_invalidModuleCode() { + final String testString = "add \t mod module code \t\t 4 -d \t\t \t \"i am a descrip\t -d-d tion\t \"\t "; + testParseCommand_expectInvalidNumberException(testString); + } + + @Test + public void parse_addCommand_module_withDescription_invalidInput() { + final String testString = "add mod cs2113t 4 -d \"11111\"123"; + testParseCommand_expectInvalidExcessArgumentException(testString); + } + + @Test + public void parse_addCommand_invalidFlag() { + final String testString = "add /a \"blahblah\" -d \"blahblahblah\""; + testParseCommand_expectUnknownCommandException(testString); + } + + @Test + public void parse_addCommand_noFlagProvided() { + final String testString = "add cs2113t"; + testParseCommand_expectUnknownCommandException(testString); + } + + @Test + public void parse_addCommand_withModuleOnly_noModuleProvided() { + final String testString = "add mod"; + testParseCommand_expectMissingCompulsoryParameterException(testString); + } + + @Test + public void parse_addCommand_withTaskOnly_noTaskProvided() { + final String testString = "add task"; + testParseCommand_expectMissingCompulsoryParameterException(testString); + } + + @Test + public void parse_deleteCommand_withTaskOnly_parsedCorrectly() { + final String testString = "del task 1"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof DeleteCommand); + assertEquals(0, ((DeleteCommand) c).getTaskIndex()); // zero-indexed + } catch (Exception e) { + fail(); + } + } + + + + @Test + public void parse_deleteCommand_withModuleOnly_parsedCorrectly() { + final String testString = "del mod CS2113T"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof DeleteCommand); + assertEquals("CS2113T", ((DeleteCommand) c).getModuleCode()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_deleteCommand_withTask_withTargetModule_parsedCorrectly() { + final String testString = "del task 1 -m cs2113t"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof DeleteCommand); + assertEquals(0, ((DeleteCommand) c).getTaskIndex()); // zero-indexed + assertEquals("cs2113t", ((DeleteCommand) c).getTaskModule()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_deleteCommand_withTask_withTargetModule_invalidModuleCode() { + final String testString = "del task 1 -m cs 2113 t"; + testParseCommand_expectInvalidExcessArgumentException(testString); + } + + @Test + public void parse_deleteCommand_invalidFlag() { + final String testString = "del a 1"; + testParseCommand_expectUnknownCommandException(testString); + } + + @Test + public void parse_deleteCommand_noFlagProvided() { + final String testString = "del 1"; + testParseCommand_expectUnknownCommandException(testString); + } + + @Test + public void parse_deleteCommand_withModuleOnly_noModuleProvided() { + final String testString = "del mod"; + testParseCommand_expectMissingCompulsoryParameterException(testString); + } + + @Test + public void parse_deleteCommand_withTaskOnly_noIndexProvided() { + final String testString = "del task"; + testParseCommand_expectMissingNumberException(testString); + } + + + @Test + public void parse_editCommand_task_parsedCorrectly() { + final String testString = "edit task 1 -m cs2113t -n \"changed\" "; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof EditCommand); + assertEquals(0, ((EditCommand) c).getTaskIndex()); // zero-indexed + assertNull(((EditCommand) c).getModuleCode()); + assertEquals("cs2113t", ((EditCommand) c).getTaskModule()); + } catch (Exception e) { + fail(); + } + } + + + + @Test + public void parse_editCommand_task_noOptionalFlags() { + final String testString = "edit task 1"; + testParseCommand_expectMissingCompulsoryParameterException(testString); + } + + @Test + public void parse_editCommand_module_wrongFlag() { + final String testString = "edit mod cs2113t -t \"111\""; + testParseCommand_expectInvalidFlagException(testString); + } + + + + + @Test + public void parse_exitCommand_parsedCorrectly() { + final String testString = "exit"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof ExitCommand); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_exitCommand_unnecessaryArgs() { + final String testString = "exit blahblah"; + testParseCommand_expectAdditionalParameterException(testString); + } + + @Test + public void parse_gradeCommand_parsedCorrectly() { + final String testString = "grade CS2113T a+"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof GradeCommand); + assertEquals("CS2113T", ((GradeCommand) c).getModuleCode()); // Remember, zero-indexed! + assertEquals("A+", ((GradeCommand) c).getModuleGrade()); + } catch (Exception e) { + fail(); + } + } + + + @Test + public void parse_gradeCommand_task_tooManyGrades() { + final String testString = "grade CS2113T A+ B+ B-"; + testParseCommand_expectInvalidExcessArgumentException(testString); + } + + @Test + public void parse_gradeCommand_wrongOrder() { + final String testString = "grade A- CS2113T"; + testParseCommand_expectInvalidCompulsoryParameterException(testString); + } + + @Test + public void parse_gpaCommand_parsedCorrectly() { + final String testString = "gpa"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof GpaCommand); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_gpaCommand_unnecessaryArgs() { + final String testString = "gpa blahblah"; + testParseCommand_expectAdditionalParameterException(testString); + } + + @Test + public void parse_helpCommand_parsedCorrectly() { + final String testString = "help"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof HelpCommand); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_helpCommand_withCommandWord_parsedCorrectly() { + final String testString = "help add"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof HelpCommand); + assertEquals("add", ((HelpCommand) c).getCommand()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_helpCommand_unnecessaryArgs() { + final String testString = "help add blahblah"; + testParseCommand_expectInvalidExcessArgumentException(testString); + } + + @Test + public void parse_listCommand_parsedCorrectly() { + final String testString = "list"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof ListCommand); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_listCommandwithArgument_noExeceptionThrown() { + final String testString = "list test"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof ListCommand); + assertEquals("test", ((ListCommand) c).getArgument()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_listCommand_unnecessaryArgs() { + final String testString = "list test blahblah"; + testParseCommand_expectInvalidExcessArgumentException(testString); + } + + @Test + public void parse_markCommand_noModule_parsedCorrectly() { + final String testString = "mark c 3"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof MarkCommand); + assertEquals(2, ((MarkCommand) c).getTaskIndex()); // Remember, zero-indexed! + assertNull(((MarkCommand) c).getTaskModuleString()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_markCommand_withModule_parsedCorrectly() { + final String testString = "mark c 3 -m cs2113t"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof MarkCommand); + assertEquals(2, ((MarkCommand) c).getTaskIndex()); // Remember, zero-indexed! + assertEquals("cs2113t", ((MarkCommand) c).getTaskModuleString()); + } catch (Exception e) { + fail(); + } + } + + + + @Test + public void parse_markCommand_noFlagProvided() { + final String testString = "mark 1"; + testParseCommand_expectInvalidFlagException(testString); + } + + @Test + public void parse_markCommand_noIndexProvided() { + final String testString = "mark c"; + testParseCommand_expectMissingNumberException(testString); + } + + + @Test + public void parse_markCommand_unnecessaryArgs() { + final String testString = "mark c 1 blahblah"; + testParseCommand_expectInvalidExcessArgumentException(testString); + } + + @Test + public void parse_optionCommand_parsedCorrectly() { + final String testString = "option"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof OptionCommand); + } catch (Exception e) { + fail(); + } + } + + + @Test + public void parse_resetCommand_parsedCorrectly() { + final String testString = "reset"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof ResetCommand); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_resetCommand_unnecessaryArgs() { + final String testString = "reset blahblah"; + testParseCommand_expectAdditionalParameterException(testString); + } + + @Test + public void parse_saveCommand_parsedCorrectly() { + final String testString = "save"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof SaveCommand); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_saveCommand_unnecessaryArgs() { + final String testString = "save blahblah"; + testParseCommand_expectAdditionalParameterException(testString); + } + + @Test + public void parse_tagCommand_addTag_withoutTargetModule_parsedCorrectly() { + final String testString = "tag add 1 tag"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof TagCommand); + assertEquals("add", ((TagCommand) c).getTagOperation()); + assertEquals(0, ((TagCommand) c).getTaskIndex()); + assertEquals("tag", ((TagCommand) c).getTagDescription()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_tagCommand_delTag_withoutTargetModule_parsedCorrectly() { + final String testString = "tag del 1 tag"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof TagCommand); + assertEquals("del", ((TagCommand) c).getTagOperation()); + assertEquals(0, ((TagCommand) c).getTaskIndex()); + assertEquals("tag", ((TagCommand) c).getTagDescription()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_tagCommand_addTag_withTargetModule_parsedCorrectly() { + final String testString = "tag add 1 -m cs2113t tag"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof TagCommand); + assertEquals("add", ((TagCommand) c).getTagOperation()); + assertEquals(0, ((TagCommand) c).getTaskIndex()); + assertEquals("cs2113t", ((TagCommand) c).getTaskModule()); + assertEquals("tag", ((TagCommand) c).getTagDescription()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_tagCommand_delTag_withTargetModule_parsedCorrectly() { + final String testString = "tag del 1 -m cs2113t tag"; + try { + Command c = parser.parseCommand(testString); + assertTrue(c instanceof TagCommand); + assertEquals("del", ((TagCommand) c).getTagOperation()); + assertEquals(0, ((TagCommand) c).getTaskIndex()); + assertEquals("cs2113t", ((TagCommand) c).getTaskModule()); + assertEquals("tag", ((TagCommand) c).getTagDescription()); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_tagCommand_invalidFlag_throwsException() { + final String testString = "tag add 1 -f cs2113t tag"; + testParseCommand_expectInvalidFlagException(testString); + } + + @Test + public void parse_tagCommand_invalidTagOperation_throwsException() { + final String testString = "tag invalidOp 1 tagDescription"; + testParseCommand_expectInvalidTagOperationException(testString); + } +} diff --git a/src/test/java/seedu/duke/parsers/OptionParserTest.java b/src/test/java/seedu/duke/parsers/OptionParserTest.java new file mode 100644 index 0000000000..f165ee4733 --- /dev/null +++ b/src/test/java/seedu/duke/parsers/OptionParserTest.java @@ -0,0 +1,98 @@ +package seedu.duke.parsers; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import seedu.duke.exceptions.UnknownConfigurationGroupWordException; +import seedu.duke.exceptions.UnsupportedResultTypeException; + +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +public class OptionParserTest { + private OptionParser optionParser; + + private void testParseCommand_expectUnknownConfigGroupWordException(String testString) { + assertThrows(UnknownConfigurationGroupWordException.class, () -> { + optionParser.parseCommand(testString); + }); + } + + private void testParseCommand_expectUnsupportedResultTypeException(String testString) { + assertThrows(UnsupportedResultTypeException.class, () -> { + optionParser.parseCommand(testString); + }); + } + + @BeforeEach + public void setUp() { + optionParser = new OptionParser(); + } + + @Test + public void parse_emptyArgument() { + final String testString = ""; + try { + optionParser.parseCommand(testString); + assertNull(optionParser.parsedCommand.get("configurationGroupWord")); + assertNull(optionParser.parsedCommand.get("newValue")); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_legalConfigName() { + final String testString = StringConstants.SHOW_COMPLETED_TASKS_NAME; + try { + optionParser.parseCommand(testString); + assertEquals(StringConstants.SHOW_COMPLETED_TASKS_NAME, + optionParser.parsedCommand.get("configurationGroupWord")); + assertNull(optionParser.parsedCommand.get("newValue")); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_legalConfigNameAndLegalValue() { + final String testString = StringConstants.SHOW_COMPLETED_TASKS_NAME + "=" + StringConstants.TRUE; + try { + optionParser.parseCommand(testString); + assertEquals(StringConstants.SHOW_COMPLETED_TASKS_NAME, + optionParser.parsedCommand.get("configurationGroupWord")); + assertEquals(StringConstants.TRUE, optionParser.parsedCommand.get("newValue")); + } catch (Exception e) { + fail(); + } + } + + @Test + public void parse_badConfigName() { + final String testString = "ILLEGAL_TASK_SHOWN"; + testParseCommand_expectUnknownConfigGroupWordException(testString); + } + + @Test + public void parse_legalConfigNameAndBadValue() { + final String testString = StringConstants.SHOW_COMPLETED_TASKS_NAME + "=true1"; + testParseCommand_expectUnsupportedResultTypeException(testString); + } + + @Test + public void parse_legalConfigNameAndLegalValue_withExtraWhitespace() { + final String testString = StringConstants.SHOW_COMPLETED_TASKS_NAME + "=" + StringConstants.TRUE + " "; + try { + optionParser.parseCommand(testString); + assertEquals(StringConstants.SHOW_COMPLETED_TASKS_NAME, + optionParser.parsedCommand.get("configurationGroupWord")); + assertEquals(StringConstants.TRUE, optionParser.parsedCommand.get("newValue")); + } catch (Exception e) { + fail(); + } + } +} diff --git a/src/test/java/seedu/duke/parsers/ParserTest.java b/src/test/java/seedu/duke/parsers/ParserTest.java new file mode 100644 index 0000000000..8dba0700a1 --- /dev/null +++ b/src/test/java/seedu/duke/parsers/ParserTest.java @@ -0,0 +1,42 @@ +package seedu.duke.parsers; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import seedu.duke.exceptions.GeneralParseException; +import seedu.duke.commands.Command; + +//@@author Ch40gRv1-Mu +public class ParserTest extends Parser { + public ParserTest() { + // This can be replaced to any regex you want to test + commandFormat = "((?[A-Z_]+)(=(?\\w+))?)?(?.*)"; + groupNames.add("configurationGroupWord"); + groupNames.add("newValue"); + } + + @Test + public void checkRegex() { + final String testString = "COMPLETED_TASK_SHOWN"; + try { + parsedCommand = parseString(testString); + assertEquals("COMPLETED_TASK_SHOWN", parsedCommand.get("configurationGroupWord")); + assertNull(parsedCommand.get("newValue")); + + } catch (Exception e) { + fail(); + } + } + + @Override + public void determineError() throws GeneralParseException { + throw new GeneralParseException(); + } + + @Override + public Command parseCommand(String userInput) { + return null; + } +} diff --git a/src/test/java/seedu/duke/storage/ConfigurationStorageTest.java b/src/test/java/seedu/duke/storage/ConfigurationStorageTest.java new file mode 100644 index 0000000000..af70d14233 --- /dev/null +++ b/src/test/java/seedu/duke/storage/ConfigurationStorageTest.java @@ -0,0 +1,42 @@ +package seedu.duke.storage; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import seedu.duke.util.Configuration; +import seedu.duke.util.StringConstants; +import static seedu.duke.util.Configuration.ConfigurationGroup.SHOW_COMPLETED_TASKS; + + + +//@@author Ch40gRv1-Mu +public class ConfigurationStorageTest { + + private ConfigurationStorage configurationStorage; + private Configuration configuration; + + @BeforeEach + public void setUp() { + configurationStorage = new ConfigurationStorage(); + configuration = new Configuration(); + } + + + @Test + public void modifyConfig_saveAndReload() { + try { + assertEquals("false", configuration.getConfigurationValue(SHOW_COMPLETED_TASKS)); + configuration.configurationGroupHashMap.put(SHOW_COMPLETED_TASKS, "true"); + String path = StringConstants.CONFIGURATION_TEST_PATH; + configurationStorage.writeData(configuration, path); + Configuration loadedConfiguration = configurationStorage.loadData(path); + assertEquals(configuration.getConfigurationsReport(), loadedConfiguration.getConfigurationsReport()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + +} diff --git a/src/test/java/seedu/duke/storage/ModuleListStorageTest.java b/src/test/java/seedu/duke/storage/ModuleListStorageTest.java new file mode 100644 index 0000000000..5294795a62 --- /dev/null +++ b/src/test/java/seedu/duke/storage/ModuleListStorageTest.java @@ -0,0 +1,103 @@ +package seedu.duke.storage; + +import java.util.ArrayList; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import seedu.duke.data.Module; +import seedu.duke.data.Task; +import seedu.duke.data.TaskList; +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +public class ModuleListStorageTest { + private final String path = StringConstants.MODULE_TEST_PATH; + private ModuleListStorage moduleListStorage; + private ArrayList moduleList; + + + @BeforeEach + public void setUp() { + moduleListStorage = new ModuleListStorage(); + moduleList = new ArrayList<>(); + } + + @Test + public void store_empty_module_list_and_read() { + try { + moduleListStorage.writeData(moduleList, path); + ArrayList list = moduleListStorage.loadData(path); + assertTrue(list.containsAll(moduleList) && moduleList.containsAll(list)); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Test + public void store_module_without_task_list_and_read() { + try { + Module module1 = new Module("CS2113T", "d1", 4); + Module module2 = new Module("CS2101", "d2", 4); + Module module3 = new Module("CS2040", "d3", 4); + moduleList.add(module1); + moduleList.add(module2); + moduleList.add(module3); + moduleListStorage.writeData(moduleList, path); + ArrayList list = moduleListStorage.loadData(path); + assertEquals(list.size(), moduleList.size()); + for (int i = 0; i < list.size(); i++) { + assertEquals(list.get(i).getModuleCode(), moduleList.get(i).getModuleCode()); + assertEquals(list.get(i).getModuleDescription(), moduleList.get(i).getModuleDescription()); + } + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Test + public void store_module_with_task_list_and_read() { + try { + Task task1 = new Task("t1", "dt1", "2h"); + Task task2 = new Task("t2", "dt2", "3h"); + Task task3 = new Task("t3", "dt3", "4h"); + Module module1 = new Module("CS2113T", "d1", 4); + Module module2 = new Module("CS2101", "d2", 4); + Module module3 = new Module("CS2040", "d3", 4); + module1.addTask(task1); + module2.addTask(task2); + module3.addTask(task3); + moduleList.add(module1); + moduleList.add(module2); + moduleList.add(module3); + moduleListStorage.writeData(moduleList, path); + ArrayList list = moduleListStorage.loadData(path); + assertEquals(list.size(), moduleList.size()); + for (int i = 0; i < list.size(); i++) { + assertEquals(list.get(i).getModuleCode(), moduleList.get(i).getModuleCode()); + assertEquals(list.get(i).getModuleDescription(), moduleList.get(i).getModuleDescription()); + assertEquals(list.get(i).getModularCredit(), moduleList.get(i).getModularCredit()); + TaskList taskList1 = list.get(i).getTaskList(); + TaskList taskList2 = moduleList.get(i).getTaskList(); + assertEquals(taskList1.getSize(), taskList2.getSize()); + for (int j = 0; j < taskList1.getSize(); j++) { + assertEquals(taskList1.getTask(j).getTaskName(), taskList2.getTask(j).getTaskName()); + assertEquals(taskList1.getTask(j).getTaskDescription(), taskList2.getTask(j).getTaskDescription()); + assertEquals(taskList1.getTask(j).getWorkingTimeString(), + taskList2.getTask(j).getWorkingTimeString()); + } + } + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + +} diff --git a/src/test/java/seedu/duke/storage/TaskListStorageTest.java b/src/test/java/seedu/duke/storage/TaskListStorageTest.java new file mode 100644 index 0000000000..094fb1040e --- /dev/null +++ b/src/test/java/seedu/duke/storage/TaskListStorageTest.java @@ -0,0 +1,62 @@ +package seedu.duke.storage; + +import java.util.ArrayList; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import seedu.duke.data.Task; +import seedu.duke.util.StringConstants; + +//@@author Ch40gRv1-Mu +public class TaskListStorageTest { + private TaskListStorage taskListStorage; + private ArrayList taskList; + private final String path = StringConstants.TASK_TEST_PATH; + + @BeforeEach + public void setUp() { + taskListStorage = new TaskListStorage(); + taskList = new ArrayList<>(); + } + + @Test + public void store_empty_module_list_and_read() { + try { + taskListStorage.writeData(taskList, path); + ArrayList list = taskListStorage.loadData(path); + assertTrue(list.containsAll(taskList) && taskList.containsAll(list)); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + @Test + public void store_task_list_and_read() { + try { + Task task1 = new Task("t1", "dt1", "2h"); + Task task2 = new Task("t2", "dt2", "3h"); + Task task3 = new Task("t3", "dt3", "4h"); + taskList.add(task1); + taskList.add(task2); + taskList.add(task3); + taskListStorage.writeData(taskList, path); + ArrayList list = taskListStorage.loadData(path); + assertEquals(list.size(), taskList.size()); + for (int i = 0; i < list.size(); i++) { + assertEquals(list.get(i).getTaskName(), taskList.get(i).getTaskName()); + assertEquals(list.get(i).getTaskDescription(), taskList.get(i).getTaskDescription()); + assertEquals(list.get(i).getWorkingTimeString(), taskList.get(i).getWorkingTimeString()); + } + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + +} \ No newline at end of file diff --git a/text-ui-test/ACTUAL.TXT b/text-ui-test/ACTUAL.TXT new file mode 100644 index 0000000000..892cb6cae7 --- /dev/null +++ b/text-ui-test/ACTUAL.TXT @@ -0,0 +1,9 @@ +Hello from + ____ _ +| _ \ _ _| | _____ +| | | | | | | |/ / _ \ +| |_| | |_| | < __/ +|____/ \__,_|_|\_\___| + +What is your name? +Hello James Gosling diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 25ac7a2989..766ee2bfe9 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -12,8 +12,8 @@ for /f "tokens=*" %%a in ( set jarloc=%%a ) -java -jar %jarloc% < ..\..\text-ui-test\input.txt > ..\..\text-ui-test\ACTUAL.TXT +java -jar %jarloc% < ..\..\text-seedu.duke.ui-test\input.txt > ..\..\text-seedu.duke.ui-test\ACTUAL.TXT -cd ..\..\text-ui-test +cd ..\..\text-seedu.duke.ui-test FC ACTUAL.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed!