diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000000..109efdf7bbb --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,25 @@ +name: MarkBind Action + +on: + push: + branches: + - master + +jobs: + build_and_deploy: + runs-on: ubuntu-latest + steps: + - name: Install Graphviz + run: sudo apt-get install graphviz + - name: Install Java + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'zulu' + - name: Build & Deploy MarkBind site + uses: MarkBind/markbind-action@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + rootDirectory: './docs' + baseUrl: '/tp' # assuming your repo name is tp + version: '^5.5.2' diff --git a/.gitignore b/.gitignore index 284c4ca7cd9..082bd7ba7b8 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,7 @@ src/test/data/sandbox/ # MacOS custom attributes files created by Finder .DS_Store docs/_site/ +docs/_markbind/logs/ + +# build files +bin/ diff --git a/README.md b/README.md index 16208adb9b6..67d2ae6baef 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,8 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/AY2425S2-CS2103-W11-1/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2425S2-CS2103-W11-1/tp/actions) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
- Example usages: - * as a starting point of a course project (as opposed to writing everything from scratch) - * as a case study -* The project simulates an ongoing software project for a desktop application (called _AddressBook_) used for managing contact details. - * It is **written in OOP fashion**. It provides a **reasonably well-written** code base **bigger** (around 6 KLoC) than what students usually write in beginner-level SE modules, without being overwhelmingly big. - * It comes with a **reasonable level of user and developer documentation**. -* It is named `AddressBook Level 3` (`AB3` for short) because it was initially created as a part of a series of `AddressBook` projects (`Level 1`, `Level 2`, `Level 3` ...). -* For the detailed documentation of this project, see the **[Address Book Product Website](https://se-education.org/addressbook-level3)**. -* This project is a **part of the se-education.org** initiative. If you would like to contribute code to this project, see [se-education.org](https://se-education.org/#contributing-to-se-edu) for more info. +* Mentorstack helps CS tutors efficiently manage and track student contacts, attendance, participation, progress, and streamlines communication. +* It simplifies student management across different levels and courses while catering to tech-savvy users who may prefer a command-line interface (CLI). +* For the detailed documentation of this project, see the **[Mentorstack Product Website](https://ay2425s2-cs2103-w11-1.github.io/tp/index.html)**. +* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). diff --git a/build.gradle b/build.gradle index 0db3743584e..96388364724 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { id 'jacoco' } -mainClassName = 'seedu.address.Main' +mainClassName = 'seedu.mentorstack.Main' sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 @@ -66,7 +66,11 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'mentorstack.jar' +} + +run { + enableAssertions = true } defaultTasks 'clean', 'test' diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000000..1748e487fbd --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,23 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +_markbind/logs/ + +# Dependency directories +node_modules/ + +# Production build files (change if you output the build to a different directory) +_site/ + +# Env +.env +.env.local + +# IDE configs +.vscode/ +.idea/* +*.iml diff --git a/docs/AboutUs.md b/docs/AboutUs.md index ff3f04abd02..179f0c54bf0 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,59 +1,59 @@ --- -layout: page -title: About Us + layout: default.md + title: "About Us" --- -We are a team based in the [School of Computing, National University of Singapore](https://www.comp.nus.edu.sg). +# About Us -You can reach us at the email `seer[at]comp.nus.edu.sg` +We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). ## Project team -### John Doe +### Chen Xinpeng - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/subiloble)] [[portfolio](team/subiloble.md)] -* Role: Project Advisor +* Role: Developer +* Responsibilities: Undo/View Commands -### Jane Doe +### Ng Jia Quan - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/quantin96)] +[[portfolio](team/quantin96.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: Edit/Mark/Finish Commands -### Johnny Doe +### She Yuting - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/sheyuting)] +[[portfolio](team/sheyuting.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: Add/Delete/Stats Commands -### Jean Doe +### Zhang Leran - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/zlllllr)] +[[portfolio](team/zlllllr.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Archive/Find Commands -### James Doe +### Willie Tay - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/ioubread)] +[[portfolio](team/ioubread.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: UI/Hinter diff --git a/docs/Configuration.md b/docs/Configuration.md index 13cf0faea16..32f6255f3b9 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1,6 +1,8 @@ --- -layout: page -title: Configuration guide + layout: default.md + title: "Configuration guide" --- +# Configuration guide + Certain properties of the application can be controlled (e.g user preferences file location, logging level) through the configuration file (default: `config.json`). diff --git a/docs/DevOps.md b/docs/DevOps.md index d2fd91a6001..8228c845e86 100644 --- a/docs/DevOps.md +++ b/docs/DevOps.md @@ -1,12 +1,15 @@ --- -layout: page -title: DevOps guide + layout: default.md + title: "DevOps guide" + pageNav: 3 --- -* Table of Contents -{:toc} +# DevOps guide --------------------------------------------------------------------------------------------------------------------- + + + + ## Build automation diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 743c65a49d2..9921a1ee378 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,15 +1,19 @@ --- -layout: page -title: Developer Guide + layout: default.md + title: "Developer Guide" + pageNav: 3 --- -* Table of Contents -{:toc} + +# Mentorstack Developer Guide + + + -------------------------------------------------------------------------------------------------------------------- ## **Acknowledgements** -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). -------------------------------------------------------------------------------------------------------------------- @@ -19,16 +23,13 @@ Refer to the guide [_Setting up and getting started_](SettingUp.md). -------------------------------------------------------------------------------------------------------------------- -## **Design** - -
+
-:bulb: **Tip:** The `.puml` files used to create diagrams in this document `docs/diagrams` folder. Refer to the [_PlantUML Tutorial_ at se-edu/guides](https://se-education.org/guides/tutorials/plantUml.html) to learn how to create and edit diagrams. -
+## **Design** ### Architecture - + The ***Architecture Diagram*** given above explains the high-level design of the App. @@ -36,7 +37,7 @@ Given below is a quick overview of main components and how they interact with ea **Main components of the architecture** -**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down. +**`Main`** (consisting of classes [`Main`](https://github.com/AY2425S2-CS2103-W11-1/tp/blob/master/src/main/java/seedu/mentorstack/Main.java) and [`MainApp`](https://github.com/AY2425S2-CS2103-W11-1/tp/blob/master/src/main/java/seedu/mentorstack/MainApp.java)) is in charge of the app launch and shut down. * At app launch, it initializes the other components in the correct sequence, and connects them up with each other. * At shut down, it shuts down the other components and invokes cleanup methods where necessary. @@ -49,11 +50,13 @@ The bulk of the app's work is done by the following four components: [**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. +
+ **How the architecture components interact with each other** The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. - + Each of the four main components (also shown in the diagram above), @@ -62,19 +65,21 @@ Each of the four main components (also shown in the diagram above), For example, the `Logic` component defines its API in the `Logic.java` interface and implements its functionality using the `LogicManager.java` class which follows the `Logic` interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component's being coupled to the implementation of a component), as illustrated in the (partial) class diagram below. - + The sections below give more details of each component. +
+ ### UI component -The **API** of this component is specified in [`Ui.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/Ui.java) +The **API** of this component is specified in [`Ui.java`](https://github.com/AY2425S2-CS2103-W11-1/tp/blob/master/src/main/java/seedu/mentorstack/ui/Ui.java) -![Structure of the UI Component](images/UiClassDiagram.png) + The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class which captures the commonalities between classes that represent parts of the visible GUI. -The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/resources/view/MainWindow.fxml) +The `UI` component uses the JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the [`MainWindow`](https://github.com/AY2425S2-CS2103-W11-1/tp/blob/master/src/main/java/seedu/mentorstack/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2425S2-CS2103-W11-1/tp/blob/master/src/main/resources/view/MainWindow.fxml) The `UI` component, @@ -83,24 +88,30 @@ The `UI` component, * keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. * depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. +
+ ### Logic component -**API** : [`Logic.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/logic/Logic.java) +**API** : [`Logic.java`](https://github.com/AY2425S2-CS2103-W11-1/tp/blob/master/src/main/java/seedu/mentorstack/logic/Logic.java) Here's a (partial) class diagram of the `Logic` component: - + The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete 1")` API call as an example. -![Interactions Inside the Logic Component for the `delete 1` Command](images/DeleteSequenceDiagram.png) + -
:information_source: **Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram. -
+ + +**Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline continues till the end of diagram. + + +
How the `Logic` component works: -1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. +1. When `Logic` is called upon to execute a command, it is passed to an `MentorstackParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. 1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. 1. The command can communicate with the `Model` when it is executed (e.g. to delete a person).
Note that although this is shown as a single step in the diagram above (for simplicity), in the code it can take several interactions (between the command object and the `Model`) to achieve. @@ -108,141 +119,165 @@ How the `Logic` component works: Here are the other classes in `Logic` (omitted from the class diagram above) that are used for parsing a user command: - + How the parsing works: -* When called upon to parse a user command, the `AddressBookParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `AddressBookParser` returns back as a `Command` object. -* All `XYZCommandParser` classes (e.g., `AddCommandParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. +* When called upon to parse a user command, the `MentorstackParser` class creates an `XYZCommandParser` (`XYZ` is a placeholder for the specific command name e.g., `AddCommandParser`) which uses the other classes shown above to parse the user command and create a `XYZCommand` object (e.g., `AddCommand`) which the `MentorstackParser` returns back as a `Command` object. +* All `XYZCommandParser` classes (e.g., `MentorstackParser`, `DeleteCommandParser`, ...) inherit from the `Parser` interface so that they can be treated similarly where possible e.g, during testing. + +
### Model component -**API** : [`Model.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/model/Model.java) +**API** : [`Model.java`](https://github.com/AY2425S2-CS2103-W11-1/tp/blob/master/src/main/java/seedu/mentorstack/model/Model.java) - + The `Model` component, -* stores the address book data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). +* stores the mentorstack data i.e., all `Person` objects (which are contained in a `UniquePersonList` object). * stores the currently 'selected' `Person` objects (e.g., results of a search query) as a separate _filtered_ list which is exposed to outsiders as an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * stores a `UserPref` object that represents the user’s preferences. This is exposed to the outside as a `ReadOnlyUserPref` objects. * does not depend on any of the other three components (as the `Model` represents data entities of the domain, they should make sense on their own without depending on other components) -
:information_source: **Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `AddressBook`, which `Person` references. This allows `AddressBook` to only require one `Tag` object per unique tag, instead of each `Person` needing their own `Tag` objects.
+ - + -
+ +
### Storage component -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) +**API** : [`Storage.java`](https://github.com/AY2425S2-CS2103-W11-1/tp/blob/master/src/main/java/seedu/mentorstack/storage/Storage.java) - + The `Storage` component, -* can save both address book data and user preference data in JSON format, and read them back into corresponding objects. -* inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). +* can save both mentorstack data and user preference data in JSON format, and read them back into corresponding objects. +* inherits from both `MentorstackStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). * depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) ### Common classes -Classes used by multiple components are in the `seedu.address.commons` package. +Classes used by multiple components are in the `seedu.mentorstack.commons` package. -------------------------------------------------------------------------------------------------------------------- +
+ ## **Implementation** This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### Undo feature -#### Proposed Implementation +#### Implementation -The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: +The key operation to store the states of the data is in the `Model` interface as `Model#remember()`. -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +Given below is an example usage scenario and how the undo mechanism behaves at each step. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +Step 1. The user launches the application for the first time. The `Mentorstack` will be initialized with the initial mentorstack state, and the empty list of state `history` is initialized. -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. + -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +Step 2. The user executes `delete 5` command to delete the 5th person in the mentorstack. The `delete` command calls `Model#remember()`, causing the original state before the `delete 5` command executes to be saved in the `history`. -![UndoRedoState0](images/UndoRedoState0.png) + -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#remember()`, causing another mentorstack state to be saved into the `history`. -![UndoRedoState1](images/UndoRedoState1.png) + -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. + -![UndoRedoState2](images/UndoRedoState2.png) +**Note:** If a command fails its execution, it will not call `Model#remember()`, so the mentorstack state will not be saved into the `history`. -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. + -
+Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undo()`, which, after verified by `Model#canUndo()`, will make `history` to pop once to give last state, and restores the mentorstack to that state. -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. + -![UndoRedoState3](images/UndoRedoState3.png) -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. - -
+ -The following sequence diagram shows how an undo operation goes through the `Logic` component: +**Note:** If the `history` has no previous mentorstack states to restore. The `undo` command uses `Model#canUndo()` to check if this is the case. If so, it will return an error to the user rather +than attempting to perform the undo. -![UndoSequenceDiagram](images/UndoSequenceDiagram-Logic.png) + -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. +
-
+The following sequence diagram shows how an undo operation goes through the `Logic` component: -Similarly, how an undo operation goes through the `Model` component is shown below: + -![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png) + -The `redo` command does the opposite — it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +**Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. -
:information_source: **Note:** If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone AddressBook states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. + -
+Similarly, how an undo operation goes through the `Model` component is shown below: -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. + -![UndoRedoState4](images/UndoRedoState4.png) +Step 5. The user then decides to execute the command `list`. Commands that do not modify the mentorstack, such as `list`, will usually not call `Model#remember()`. Thus, the `history` remains unchanged. -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. + -![UndoRedoState5](images/UndoRedoState5.png) +
The following activity diagram summarizes what happens when a user executes a new command: - + #### Design considerations: -**Aspect: How undo & redo executes:** +**Aspect: How undo executes:** -* **Alternative 1 (current choice):** Saves the entire address book. +* **Alternative 1 (current choice):** Saves the entire mentorstack. * Pros: Easy to implement. * Cons: May have performance issues in terms of memory usage. -* **Alternative 2:** Individual command knows how to undo/redo by +* **Alternative 2:** Individual command knows how to undo by itself. * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). * Cons: We must ensure that the implementation of each individual command are correct. _{more aspects and alternatives to be added}_ -### \[Proposed\] Data archiving +
+ +### Stats feature +#### Implementation +The `StatsCommand` provides statistical insights into the persons stored in MentorStack. It can either display overall statistics or filter by a specified subject. +#### Execution Flow +* If a subject is specified, filter the list of persons based on that subject. +* Count the number of persons, males, and females in the filtered list. +* Format the results into a string and return as a CommandResult. + + + +The following sequence diagram shows how a stats operation goes through the `Logic` component: + + + +
-_{Explain here how the data archiving feature will be implemented}_ +### Student archiving +The `ArchiveCommand` archives students and transfers their data to an archive list stored on Mentorstack. +It is similar to the active list but student entries cannot be edited. + +Given below is a sequence diagram for a sample `ArchiveCommand` + + + +A similar sequence diagram can be drawn for the `UnarchiveCommand`, which moves the student back to the active list. -------------------------------------------------------------------------------------------------------------------- @@ -262,71 +297,354 @@ _{Explain here how the data archiving feature will be implemented}_ **Target user profile**: -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps +Computer science tutors who need an efficient system to organize student information and track student progress + +**Value proposition**: -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +Mentorstack helps CS tutors efficiently manage and track student contacts, attendance, participation, progress, and streamlines communication. It simplifies student management across different levels and courses while catering to tech-savvy users who may prefer a command-line interface. +
### User stories Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` -| Priority | As a …​ | I want to …​ | So that I can…​ | -| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- | -| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App | -| `* * *` | user | add a new person | | -| `* * *` | user | delete a person | remove entries that I no longer need | -| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list | -| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident | -| `*` | user with many persons in the address book | sort persons by name | locate a person easily | - -*{More to be added}* +| Priority | As a …​ | I want to …​ | So that I can…​ | +|----------|---------|-----------------------------------------------------|-----------------------------------------------------------| +| `* * *` | tutor | add a student’s details (name, email, course, year) | keep track of their information | +| `* * *` | tutor | remove a student’s details | remove a student whose details no longer need to track | +| `* * *` | tutor | edit a student’s details | update their information | +| `* * *` | tutor | search for a student by name or ID | quickly find their details | +| `* * *` | tutor | view all students’ information | get in touch with the student whenever I want | +| `* *` | tutor | undo an unintended operation | quickly correct any mistakes | +| `* *` | tutor | View the gender distribution of my students | adjust my teaching style | +| `* *` | tutor | archive a student | focus on current student while not deleting past students | +| `* *` | tutor | unarchive a student | add mistakenly archived students back | +| `* *` | tutor | clear all student's details | reset the Mentorstack to an empty | +| `* *` | tutor | list all student's details | see all active students in Mentorstack | +| `* *` | tutor | mark students | quickly identify students | +| `* *` | tutor | unmark students | unmark mistakenly marked students | +| `* *` | tutor | mark a student's subject as completed | check if a student has completed a course | +| `* *` | tutor | mark a student's subject as uncompleted | unmark mistakenly marked subjects | +| `* *` | tutor | show all archived students | check which students are archived | +| `* *` | tutor | view help window or hinter command | quickly get help to using Mentorstack | + +
### Use cases -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +(For all use cases below, the **System** is `Mentorstack` and the **Actor** is the `tutor`, unless specified otherwise) + +**Use case: UC01 - Add student details** + +**MSS** +1. Tutor requests to add a new student. +2. System displays the required input format to add student information. +3. Tutor enters student's information. +4. System creates a new student profile. + + Use case ends. + +**Extensions** + +* 4a. Required information is missing. + * 4a1. System shows an error message. + * 4a2. System prompts the tutor to enter the missing information. + Use case resumes at step 3. + +* 4b. Information input format is invalid. + * 4b1. System shows an error message. + * 4b2. System prompts the tutor to enter information. + Use case resumes at step 3. + +* 4c. A student’s information already exists. + * 4c1. System shows an error message. + * 4c2. System prompts the tutor to add a new student or update the existing student. + Use case resumes at step 3. + +**Use case: UC02 - Delete a student** + +**MSS** + +1. Tutor requests to list students. +2. System shows a list of students. +3. Tutor requests to delete a specific student in the list. +4. System deletes the person. + + Use case ends. + +**Use case: UC03 - Edit student details** + +**MSS** + +1. User inputs the student ID and the updated information of the corresponding student. +2. Mentorstack shows a message of successful updating. + + Use case ends. + +**Extensions** + +* 2a. The given student ID is invalid. + + * 2a1. Mentorstack shows an error message. + + Use case ends. + +
+ +**Use case: UC04 - Find students by name** + +**MSS** + +1. User inputs a string. +2. Mentorstack shows a list of students that contains the string in the name. + + Use case ends. + +**Extensions** + +* 2a. There is no matching student. + + * 2a1. Mentorstack shows an error message. + + Use case ends. + +**Use case: UC05 - View all students’ information by filter** + +**MSS** + +1. Tutor enters the view_students command with an optional filter type and value. +2. Mentorstack retrieves a list of students matching the filter criteria. +3. Mentorstack displays a table of students retrieved. + + Use case ends. + +**Extensions** + +* 1a. The filter or value is invalid. + + * 1a1. Mentorstack shows an error message. + + Use case ends. -**Use case: Delete a person** +* 2a. The list is empty. + + * 2a1. Mentorstack shows a message indicating no student satisfies the input requirements. + + Use case ends. + +
+ +**Use case: UC06 - View student distribution by stats** **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. Tutor enters the stats command with an optional subject name. +2. Mentorstack calculates numbers by the filter criteria. +3. Mentorstack displays the number of distributions retrieved. Use case ends. **Extensions** +* 1a. The filter or value is invalid. + + * 1a1. Mentorstack shows an error message. + + Use case ends. + +* 1b. No subject input. + * 1b1. Take the filter criteria as the whole data set. + Back to step 2 + * 2a. The list is empty. + * 2a1. Mentorstack shows a message indicating no student satisfies the input requirements. + + Use case ends. + +**Use case: UC07 - Archive students** + +**MSS** +1. Tutor requests to archive a list of students. +2. Mentorstack archives the corresponding students. + + Use case ends. + +**Extensions** + +* 2a. Students are not in the list. + * 2a1. Mentorstack shows an error message. + + Use case ends. + +* 2b. A student is already archived. + * 2b1. Mentorstack shows an error message. + + Use case ends. + +
+ +**Use case: UC08 - Unarchive students** + +Preconditions: User is accessing the archive list and there are archived students. +**MSS** +1. Tutor requests to unarchive a list of students. +2. Mentorstack unarchives the corresponding students. + + Use case ends. + +**Extensions** + +* 2a. Students are not in the list. + * 2a1. Mentorstack shows an error message. + + Use case ends. + +* 2b. A student is already unarchived. + * 2b1. Mentorstack shows an error message. + + Use case ends. + +**Use case: UC09 - Show all archived students** + +**MSS** +1. Tutor requests to show all archived students. +2. Mentorstack shows a list of archived students. + Use case ends. -* 3a. The given index is invalid. +**Use case: UC10 - Mark student** - * 3a1. AddressBook shows an error message. +**MSS** +1. Tutor requests to indicate a student or list of students as marked. +2. Mentorstack marks the students if they have not been marked. + + Use case ends. - Use case resumes at step 2. +**Extensions** -*{More to be added}* +* 2a. Student is not in the list. + * 2a1. Mentorstack shows an error message. + + Use case ends. + +* 2b. Student is archived. + * 2b1. Mentorstack shows an error message. + + Use case ends. + +
+ +**Use case: UC11 - Unmark student** + +**MSS** +1. Tutor requests to indicate a student or list of students as unmarked. +2. Mentorstack unmarks the students if they have not been unmarked. + + Use case ends. + +**Extensions** + +* 2a. Student is not in the list. + * 2a1. Mentorstack shows an error message. + + Use case ends. + +* 2b. Student is archived. + * 2b1. Mentorstack shows an error message. + + Use case ends. + +**Use case: UC12 - Finish subject** + +**MSS** +1. Tutor requests to indicate a student's subject as completed. +2. Mentorstack marks the subject as completed. + + Use case ends. + +**Extensions** + +* 2a. Student is not in the list. + * 2a1. Mentorstack shows an error message. + + Use case ends. + +* 2b. Student is archived. + * 2b1. Mentorstack shows an error message. + + Use case ends. + +* 2c. Student is not enrolled in the subject + * 2c1. Mentorstack shows an error message. + + Use case ends. + +
+ +**Use case: UC13 - Unfinish subject** + +**MSS** +1. Tutor requests to indicate a student's subject as not completed. +2. Mentorstack marks the subject as not completed. + +Use case ends. + +**Extensions** + +* 2a. Student is not in the list. + * 2a1. Mentorstack shows an error message. + + Use case ends. + +* 2b. Student is archived. + * 2b1. Mentorstack shows an error message. + + Use case ends. + +* 2c. Student is not enrolled in the subject. + * 2c1. Mentorstack shows an error message. + + Use case ends. + +**Use case: UC14 - Undo an unintended operation** + +**Precondition** + +1. The user has performed at least one action that modifies the application state (e.g., adding, editing, or deleting student data). + +**MSS** + +1. Tutor enters the undo command. +2. Mentorstack reverts the last operation that modified the data storage. +3. Mentorstack displays a success message. +4. Mentorstack displays a description for the operation that is undone. + + Use case ends. + +**Extensions** + +* 1a. There is no operation to undo - undo is not valid. + + * 1a1. Mentorstack shows a message indicating no previous operation exists. + + Use case ends. ### Non-Functional Requirements 1. Should work on any _mainstream OS_ as long as it has Java `17` or above installed. 2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. 3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. - -*{More to be added}* +4. The system will respond to any command within 3 seconds. ### Glossary * **Mainstream OS**: Windows, Linux, Unix, MacOS -* **Private contact detail**: A contact detail that is not meant to be shared with others +* **Tutor**: CS educator using Mentorstack to manage students +* **Student Profile**: A record containing a student’s personal details (name, email, phone, subjects) + +
-------------------------------------------------------------------------------------------------------------------- @@ -334,10 +652,12 @@ Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unli Given below are instructions to test the app manually. -
:information_source: **Note:** These instructions only provide a starting point for testers to work on; + + +**Note:** These instructions only provide a starting point for testers to work on; testers are expected to do more *exploratory* testing. -
+
### Launch and shutdown @@ -347,6 +667,8 @@ testers are expected to do more *exploratory* testing. 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 1. If this still fails, refer to the instructions in the Quick Start section in the User Guide for launch instructions. + 1. Saving window preferences 1. Resize the window to an optimum size. Move the window to a different location. Close the window. @@ -354,7 +676,15 @@ testers are expected to do more *exploratory* testing. 1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ +### Adding a person + +1. Adding a person + + 1. Test case: `add n/John Doe g/M p/98765432 e/johnd@example.com s/CS2103`
+ Expected: Student is added to the end of the list with the above details. Details of the added student shown in the status message. + + 1. Test case: `add n/John Doe`
+ Expected: No person is added. Error details shown in the status message. Can try with other missing fields. ### Deleting a person @@ -363,7 +693,7 @@ testers are expected to do more *exploratory* testing. 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. 1. Test case: `delete 1`
- Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + Expected: First student is deleted from the list. Details of the deleted student shown in the status message. 1. Test case: `delete 0`
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. @@ -371,12 +701,153 @@ testers are expected to do more *exploratory* testing. 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
Expected: Similar to previous. -1. _{ more test cases …​ }_ +
+ +### Editing a person + +1. Editing a person while all persons are being shown + + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + + 1. Test case: `edit 1 s/CS2103`
+ Expected: First student is edited with the above details. Details of the edited student shown in the status message. + + 1. Test case: `edit 1`
+ Expected: No person is edited. Error details shown in the status message. At least one field must be edited. + + 1. Other incorrect edit commands to try: `edit`, `edit x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. + +### Finding a person + +1. Finds persons with matching name in Mentorstack. + + 1. Prerequisites: Student with name `alex` in Mentorstack. + + 1. Test case: `find alex`
+ Expected: Students with name containing `alex` is listed. + +### Filtering a person by view + +1. Finds persons with matching values in Mentorstack. + + 1. Test case: `view f/s v/CS`
+ Expected: Students taking `CS` subjects are listed. + + 1. Test case: `view`
+ Expected: All students listed. + + 1. Test case: `view f/s v/CS f/n v/alex`
+ Expected: Students taking `CS` subjects and name containing `alex` are listed. + + 1. Test case: `view f/n`
+ Expected: Filter/value not specified. No error. All students are listed. + +
+ +### Archiving a person + +1. Archives students in Mentorstack. + + 1. Prerequisites: Students in the active list in Mentorstack. + + 1. Test case: `archive 1`
+ Expected: First student is moved to the archive list. Verify with `showarchive`. Verify that student can no longer be edited. + +### Unarchiving a person + +1. Unarchives students in Mentorstack. + + 1. Prerequisites: Students in the archive list in Mentorstack. Access this list first using `showarchive` + + 1. Test case: `unarchive 1`
+ Expected: First student in the archive list is moved back to the active list. Verify with `list`. Verify that student can now be edited. + +### Marking a person + +1. Marks students in Mentorstack. + + 1. Prerequisites: List all students using the `list` command. Multiple persons in the list. + + 1. Test case: `mark 1`
+ Expected: First student is marked. Verify that `mark 1` again keeps first student as marked. + + 1. Test case: `mark 0`
+ Expected: No student is marked. Error details shown in the status message. + + 1. Other incorrect mark commands to try: `mark`, `mark x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. + +### Unmarking a person + +1. Unmarks students in Mentorstack. + + 1. Prerequisites: List all students using the `list` command. Multiple persons in the list. + + 1. Test case: `unmark 1`
+ Expected: First student is unmarked. Verify that `unmark 1` again keeps first student as unmarked. + + 1. Test case: `unmark 0`
+ Expected: No student is unmarked. Error details shown in the status message. + + 1. Other incorrect unmark commands to try: `unmark`, `unmark x`, `...` (where x is larger than the list size)
+ Expected: Similar to previous. + +
+ +### Mark a student's subject as finished + +1. Marks a student's subject as finished in Mentorstack. + + 1. Prerequisites: First student in the list must be enrolled in `CS2103` and not enrolled in `CS2104`. + + 1. Test case: `finish 1 s/CS2103`
+ Expected: Subject marked as finished. Verify that running the command again simply keeps the subject as finished. + + 1. Test case: `finish 1 s/CS2104`
+ Expected: Error details shown in the status message. + +### Mark a student's subject as unfinished + +1. Marks a student's subject as unfinished in Mentorstack. + + 1. Prerequisites: First student in the list must have finished `CS2103` and not enrolled in `CS2104`. + + 1. Test case: `unfinish 1 s/CS2103`
+ Expected: Subject marked as unfinished. Verify that running the command again simply keeps the subject as unfinished. + + 1. Test case: `unfinish 1 s/CS2104`
+ Expected: Error details shown in the status message. + +### Undo + +1. Undoes the previous state-changing command in Mentorstack successfully. + + 1. Prerequisites: Valid commands have been run that have changed the state of Mentorstack in the current session. + + 1. Test case: `undo`
+ Expected: Undoes the previous state-changing command. Mentorstack is restored to the previous state. + +1. Fails to undo the commands in Mentorstack. + + 1. Prerequisites: New Mentorstack session. + + 1. Test case: `undo`
+ Expected: No operation undone. Error details shown in the status message. -### Saving data +
-1. Dealing with missing/corrupted data files +## **Appendix: Planned Enhancements** - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ +Team size: 5 -1. _{ more test cases …​ }_ +1. **Change condition for duplicate entry**: Mentorstack currently considers entries with the same name as duplicates (case-sensitive). As it is possible for students to have the same name, this will be changed to checking for duplicate phone or email in future iterations. +2. **Allow other symbols for names**: Mentorstack currently only allows alphanumeric characters and spaces in names. This could be extended to include other symbols (e.g. `.`, `/`). +3. **Rework name parser**: Mentorstack currently includes all spaces between parts of a name. This could be reworked to remove redundant or accidental spacing. +4. **Rework `view` command**: The `view` command simply lists all students if invalid arguments are given. This could be reworked to always throw an error message whenever filter-value pairs are incorrectly specified to help users use the format. +5. **Make `stats` command work for archived students**: The `stats` command currently only displays statistics for students currently enrolled in subjects in the active list. This could be extended to work for the archive list and completed subjects. +6. **Make index error more specific**: Currently, Mentorstack does not flag index <= 0 as index error. This could be reworked to display an error message that the index is not within the range of the list of students. +7. **Improved error message for multiple indices**: For commands with multiple indices involved, Mentorstack can be reworked to be more specific in which index is invalid (if any). +8. **Allow other symbols for phones**: Mentorstack can allow other symbols (e.g. `+`, `-`) to account for country codes. +9. **Change subjects to be case-insensitive**: Subjects are currently case-sensitive in Mentorstack (i.e. `CS2103` is different from `cs2103`). This could be extended to consider these subjects as the same by making them case-insensitive. +10. **Create a search function that matches full words**: `find` and `view` currently matches partial words. It could be more effective if another search function was added that searches by full matching words. diff --git a/docs/Documentation.md b/docs/Documentation.md index 3e68ea364e7..082e652d947 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -1,29 +1,21 @@ --- -layout: page -title: Documentation guide + layout: default.md + title: "Documentation guide" + pageNav: 3 --- -**Setting up and maintaining the project website:** - -* We use [**Jekyll**](https://jekyllrb.com/) to manage documentation. -* The `docs/` folder is used for documentation. -* To learn how set it up and maintain the project website, follow the guide [_[se-edu/guides] **Using Jekyll for project documentation**_](https://se-education.org/guides/tutorials/jekyll.html). -* Note these points when adapting the documentation to a different project/product: - * The 'Site-wide settings' section of the page linked above has information on how to update site-wide elements such as the top navigation bar. - * :bulb: In addition to updating content files, you might have to update the config files `docs\_config.yml` and `docs\_sass\minima\_base.scss` (which contains a reference to `AB-3` that comes into play when converting documentation pages to PDF format). -* If you are using Intellij for editing documentation files, you can consider enabling 'soft wrapping' for `*.md` files, as explained in [_[se-edu/guides] **Intellij IDEA: Useful settings**_](https://se-education.org/guides/tutorials/intellijUsefulSettings.html#enabling-soft-wrapping) +# Documentation Guide +* We use [**MarkBind**](https://markbind.org/) to manage documentation. +* The `docs/` folder contains the source files for the documentation website. +* To learn how set it up and maintain the project website, follow the guide [[se-edu/guides] Working with Forked MarkBind sites](https://se-education.org/guides/tutorials/markbind-forked-sites.html) for project documentation. **Style guidance:** * Follow the [**_Google developer documentation style guide_**](https://developers.google.com/style). +* Also relevant is the [_se-edu/guides **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html). -* Also relevant is the [_[se-edu/guides] **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html) - -**Diagrams:** - -* See the [_[se-edu/guides] **Using PlantUML**_](https://se-education.org/guides/tutorials/plantUml.html) -**Converting a document to the PDF format:** +**Converting to PDF** -* See the guide [_[se-edu/guides] **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html) +* See the guide [_se-edu/guides **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html). diff --git a/docs/Gemfile b/docs/Gemfile deleted file mode 100644 index c8385d85874..00000000000 --- a/docs/Gemfile +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } - -gem 'jekyll' -gem 'github-pages', group: :jekyll_plugins -gem 'wdm', '~> 0.1.0' if Gem.win_platform? -gem 'webrick' diff --git a/docs/Logging.md b/docs/Logging.md index 5e4fb9bc217..589644ad5c6 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -1,8 +1,10 @@ --- -layout: page -title: Logging guide + layout: default.md + title: "Logging guide" --- +# Logging guide + * We are using `java.util.logging` package for logging. * The `LogsCenter` class is used to manage the logging levels and logging destinations. * The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level. diff --git a/docs/SettingUp.md b/docs/SettingUp.md index aef33ec72fd..682f95fe5f3 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -1,29 +1,35 @@ --- -layout: page -title: Setting up and getting started + layout: default.md + title: "Setting up and getting started" + pageNav: 3 --- -* Table of Contents -{:toc} +# Setting up and getting started + + -------------------------------------------------------------------------------------------------------------------- ## Setting up the project in your computer -
:exclamation: **Caution:** + +**Caution:** Follow the steps in the following guide precisely. Things will not work out if you deviate in some steps. -
+
First, **fork** this repo, and **clone** the fork into your computer. If you plan to use Intellij IDEA (highly recommended): + 1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to ensure Intellij is configured to use **JDK 17**. -1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
- :exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project. +1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA. + + Note: Importing a Gradle project is slightly different from importing a normal Java project. + 1. **Verify the setup**: - 1. Run the `seedu.address.Main` and try a few commands. + 1. Run the `seedu.mentorstack.Main` and try a few commands. 1. [Run the tests](Testing.md) to ensure they all pass. -------------------------------------------------------------------------------------------------------------------- @@ -34,10 +40,11 @@ If you plan to use Intellij IDEA (highly recommended): If using IDEA, follow the guide [_[se-edu/guides] IDEA: Configuring the code style_](https://se-education.org/guides/tutorials/intellijCodeStyle.html) to set up IDEA's coding style to match ours. -
:bulb: **Tip:** + + **Tip:** Optionally, you can follow the guide [_[se-edu/guides] Using Checkstyle_](https://se-education.org/guides/tutorials/checkstyle.html) to find how to use the CheckStyle within IDEA e.g., to report problems _as_ you write code. -
+ 1. **Set up CI** @@ -45,7 +52,7 @@ If you plan to use Intellij IDEA (highly recommended): 1. **Learn the design** - When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [AddressBook’s architecture](DeveloperGuide.md#architecture). + When you are ready to start coding, we recommend that you get some sense of the overall design by reading about [Mentorstack’s architecture](DeveloperGuide.md#architecture). 1. **Do the tutorials** These tutorials will help you get acquainted with the codebase. diff --git a/docs/Testing.md b/docs/Testing.md index 8a99e82438a..dc34ba695d4 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -1,12 +1,15 @@ --- -layout: page -title: Testing guide + layout: default.md + title: "Testing guide" + pageNav: 3 --- -* Table of Contents -{:toc} +# Testing guide --------------------------------------------------------------------------------------------------------------------- + + + + ## Running tests @@ -19,8 +22,10 @@ There are two ways to run tests. * **Method 2: Using Gradle** * Open a console and run the command `gradlew clean test` (Mac/Linux: `./gradlew clean test`) -
:link: **Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle. -
+ + +**Link**: Read [this Gradle Tutorial from the se-edu/guides](https://se-education.org/guides/tutorials/gradle.html) to learn more about using Gradle. + -------------------------------------------------------------------------------------------------------------------- @@ -29,8 +34,8 @@ There are two ways to run tests. This project has three types of tests: 1. *Unit tests* targeting the lowest level methods/classes.
- e.g. `seedu.address.commons.StringUtilTest` + e.g. `seedu.mentorstack.commons.StringUtilTest` 1. *Integration tests* that are checking the integration of multiple code units (those code units are assumed to be working).
- e.g. `seedu.address.storage.StorageManagerTest` + e.g. `seedu.mentorstack.storage.StorageManagerTest` 1. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
- e.g. `seedu.address.logic.LogicManagerTest` + e.g. `seedu.mentorstack.logic.LogicManagerTest` diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 27c2d1cf16c..834127fd2da 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,34 +1,40 @@ --- -layout: page -title: User Guide + layout: default.md + title: "User Guide" + pageNav: 3 --- -AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized for use via a Command Line Interface** (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB3 can get your contact management tasks done faster than traditional GUI apps. +# Mentorstack User Guide -* Table of Contents -{:toc} +* Mentorstack helps Computer Science (CS) tutors efficiently manage and track student contacts, attendance, participation, progress, and streamlines communication. +* It simplifies student management across different levels and courses while catering to tech-savvy users who may prefer a command-line interface (CLI). + + + -------------------------------------------------------------------------------------------------------------------- +
## Quick start 1. Ensure you have Java `17` or above installed in your Computer.
**Mac users:** Ensure you have the precise JDK version prescribed [here](https://se-education.org/guides/tutorials/javaInstallationMac.html). -1. Download the latest `.jar` file from [here](https://github.com/se-edu/addressbook-level3/releases). +1. Download the latest `.jar` file from [here](https://github.com/AY2425S2-CS2103-W11-1/tp/releases). -1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. +1. Copy the file to the folder you want to use as the _home folder_ for Mentorstack. -1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar addressbook.jar` command to run the application.
+1. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar mentorstack.jar` command to run the application.
A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
![Ui](images/Ui.png) + 1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will open the help window.
Some example commands you can try: * `list` : Lists all contacts. - * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book. + * `add n/John Doe g/M p/98765432 e/johnd@example.com s/CS2103` : Adds a contact named `John Doe` to the Mentorstack. * `delete 3` : Deletes the 3rd contact shown in the current list. @@ -39,30 +45,84 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo 1. Refer to the [Features](#features) below for details of each command. -------------------------------------------------------------------------------------------------------------------- +
## Features -
+ -**:information_source: Notes about the command format:**
+**Notes about the command format:**
* Words in `UPPER_CASE` are the parameters to be supplied by the user.
e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. * Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. + e.g `[n/NAME] [s/SUBJECT]` can be used as `n/John Doe s/CS2103` or as `n/John Doe` or as `s/CS2103`. -* Items with `…`​ after them can be used multiple times including zero times.
- e.g. `[t/TAG]…​` can be used as ` ` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. +* Items with `…`​ after them can be used multiple times.
+ e.g. `[s/SUBJECT]…​` can be used as `s/CS2103`, `s/CS2103 s/LAJ1201` etc. * Parameters can be in any order.
- e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. + e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
+except for `view` where one filter-value pair is treated as one parameter e.g. `view f/s v/CS f/n v/al` is equivalent to `view f/n v/al f/s v/CS` * Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`. -* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. -
+**Notes about fields in Mentorstack**: + +* Mentorstack supports operations over 5 different fields: (Name, Gender, Phone, Email, Subject). +* Listed below are the restrictions for each field. + +Name + +* Names should only contain alphanumeric characters and spaces, and it should not be blank. (Case-sensitive) + +Gender + +* Gender should be 'F' or 'M' only. ('f', 'm' will be converted to 'F', 'M' respectively) + +Phone + +* Phone numbers should only contain numbers, and it should be at least 3 digits long. + +Email + +* Emails should be of the format local-part@domain and adhere to the following constraints: +1. The local-part should only contain alphanumeric characters and these special characters, excluding the parentheses, (+_.-). The local-part may not start or end with any special characters. +2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels separated by periods. + The domain name must: + - end with a domain label at least 2 characters long + - have each domain label start and end with alphanumeric characters + - have each domain label consist of alphanumeric characters, separated only by hyphens, if any. + +Subject + +* Subject names should be alphanumeric. (Case-sensitive) + + +**Notes about student entries in Mentorstack**: + +* Each student entry in Mentorstack must have valid parameters for the following fields: + - Name + - Gender + - Phone + - Email + - Subject + +
+ +* Students must have 1 or more subjects, each subject can be marked as finished or unfinished. +* Students can be archived, in which case they will be moved to the archive list where they cannot be edited. +* Two student entries are considered the same entry if they have the same Name (case-sensitive). +* Mentorstack does not allow duplicate entries. + - `Alex Yeoh` is a duplicate of `Alex Yeoh`, but is a different entry from `alex yeoh`. + + +If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application. + + +
### Viewing help : `help` @@ -73,110 +133,297 @@ Shows a message explaning how to access the help page. Format: `help` -### Adding a person: `add` +### Adding a student: `add` -Adds a person to the address book. +Adds a student to the address book. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +Format: `add n/NAME g/GENDER p/PHONE_NUMBER e/EMAIL s/SUBJECT…​` -
:bulb: **Tip:** -A person can have any number of tags (including 0) -
+ + +**Tip:** A student can have any number of subjects (> 0). + + +A student can only have F or M as gender inputs. + Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `add n/John Doe g/M p/98765432 e/johnd@example.com s/CS2103` +* `add n/Betsy Crowe g/F s/CS2103 e/betsycrowe@example.com p/1234567 s/LAJ1201` -### Listing all persons : `list` +### Listing all students : `list` -Shows a list of all persons in the address book. +Shows a list of all students in the address book. Format: `list` -### Editing a person : `edit` +### Editing a student : `edit` -Edits an existing person in the address book. +Edits an existing student in the address book. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +Format: `edit INDEX [n/NAME] [g/GENDER] [p/PHONE] [e/EMAIL] [s/SUBJECT]…​` -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …​ +* Edits the student at the specified `INDEX`. The index refers to the index number shown in the displayed student list. The index **must be a positive integer** 1, 2, 3, …​ * At least one of the optional fields must be provided. * Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person’s tags by typing `t/` without - specifying any tags after it. +* When editing subjects, the existing unfinished subjects of the student will be removed (i.e. adding of subjects is not cumulative). +* Finished subjects cannot be edited (i.e. They do not need to be specified again when editing unfinished subjects). +* Cannot edit students who are already archived. Examples: -* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +* `edit 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st student to be `91234567` and `johndoe@example.com` respectively. +* `edit 2 n/Betsy Crower s/CS2103` Edits the name of the 2nd student to be `Betsy Crower` and changes their subjects to only `CS2103`. + +
-### Locating persons by name: `find` +### Locating students by name: `find` -Finds persons whose names contain any of the given keywords. +Finds students whose names contain any of the given keywords. Format: `find KEYWORD [MORE_KEYWORDS]` * The search is case-insensitive. e.g `hans` will match `Hans` * The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` * Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). +* Partial words will be matched e.g. `Han` will match `Hans` +* students matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` Examples: * `find John` returns `john` and `John Doe` * `find alex david` returns `Alex Yeoh`, `David Li`
+ ![result for 'find alex david'](images/findAlexDavidResult.png) -### Deleting a person : `delete` +
+ +### Filtering students by specific predicates: `view` + +Filters students based on values specified for different fields. + +Format: `view [[f/FIELD] [v/VALUE]]…​` + +* Listed below are the keys for `FIELD`. Use keys to refer to the corresponding field (i.e. `f/n` refers to NAME). +* There are 6 different keys: + +Key | Field +--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------- +**`n`** | NAME, filters entries containing VALUE +**`g`** | GENDER, filters entries containing VALUE +**`p`** | PHONE, filters entries containing VALUE +**`e`** | EMAIL, filters entries containing VALUE +**`s`** | SUBJECT, filters entries containing VALUE +**`a`** | ARCHIVED, filters entries by archived status (t for archived, any other value for non-archived) -Deletes the specified person from the address book. +* Values are case-insensitive. +* For NAME and SUBJECT, partial words will be matched e.g. `Han` will match `Hans`, `CS` will match `CS2103` +* For SUBJECT, only unfinished subjects will be considered e.g. finished subjects are not counted by the filter. +* For PHONE and EMAIL, partial words will be matched e.g. `123` will match `12345678`, `john` will match `john@doe.com` +* `view` with no arguments will just list all students. +* `view` will also list all students when given invalid format e.g.`view 123` or number of filters does not match number of values e.g. `f/n f/n f/n v/bob`. +* `view` returns error message `Invalid filter type or value.` if the format is correct but there are some invalid filters or values. +* `view` can have multiple filters applied for any field (can be the same field) e.g. `view f/n v/bob f/n v/jes`. +* Students matching all filters will be returned (i.e. `AND` search). +* Fields and values given will match the order in which they are specified (i.e. First instance of `f/` will match first instance of `v/`) -Format: `delete INDEX` +Examples: +* `view f/n v/john` returns `john` and `John Doe`. +* `view f/s v/CS` returns all entries taking CS courses. +* `view f/p v/12345678 f/e v/john@doe.com` returns only entries partially containing PHONE AND EMAIL.
+* `view f/a v/` returns all unarchived entries +* `view f/a f/n f/n f/n v/` returns all entries since number of values does not match number of filters +* `view f/abcde v/` returns `Invalid filter type or value.` as it is in the correct format but the filter is invalid + + ![result for 'view f/s v/cs'](images/viewResult.png) + +### Deleting a student : `delete` -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. +Deletes the specified student from the Mentorstack. + +Format: `delete INDEX…​` + +* Deletes the student at the specified `INDEX`. +* The index refers to the index number shown in the displayed student list. * The index **must be a positive integer** 1, 2, 3, …​ +* Input can contain multiple indices. Examples: -* `list` followed by `delete 2` deletes the 2nd person in the address book. -* `find Betsy` followed by `delete 1` deletes the 1st person in the results of the `find` command. +* `list` followed by `delete 2` deletes the 2nd student in Mentorstack. +* `find Bernice` followed by `delete 1` deletes the 1st student in the results of the `find` command. +* `list` followed by `delete 2 3` deletes the 2nd and 3rd student in Mentorstack. ### Clearing all entries : `clear` -Clears all entries from the address book. +Clears all entries from Mentorstack. Format: `clear` +
+ +### Undo an operation : `undo` + +* Undoes the previous operation in Mentorstack. +* Undo applies to all operations that modify the Mentorstack data storage e.g. the `archive` command can be undone, but the `view` command cannot. +* Undo can only undo operations in the current session. + +Format: `undo` + +![result for 'undo'](images/undoResult.png) + +### View statistics : `stats` + +* Views statistics of Mentorstack, supports subject-based statistics. +* Shows gender distribution and total students. + +Format: `stats [s/SUBJECT]` + +Examples: +* `stats` shows the gender-based statistics of Mentorstack. +* `stats s/CS2103` shows the statistics of students currently enrolled in CS2103. + +
+ +### Mark a student : `mark` + +* Marks a student in Mentorstack. +* Marked students will have a red circle next to their name. +* Input can contain multiple indices. +* Students remain marked if they have already been marked. +* Archived students cannot be marked. + +Format: `mark INDEX…​` + +Examples: +* `list` followed by `mark 1 3` marks the 1st and 3rd student in the current list. + +![result for 'mark'](images/markResultUG.png) + +### Unmark a student : `unmark` + +* Unmarks a student in Mentorstack. +* Input can contain multiple indices. +* Students remain unmarked if they have already been unmarked. +* Archived students cannot be unmarked. + +Format: `unmark INDEX…​` + +Examples: +* `list` followed by `unmark 1 3` unmarks the 1st and 3rd student in the current list. + +
+ +### Archive a student : `archive` + +* Archives a student in Mentorstack. +* Archives students are moved to the archive list. +* Input can contain multiple indices. +* Will display students who are not archived after the command. + +Format: `archive INDEX…​` + +Examples: +* `list` followed by `archive 1 3` archives the 1st and 3rd student in the current list. + +### Navigate to the archive list : `showarchive` + +* Lists the students in the archive list. +* Input arguments after showarchive will be ignored. (`showarchive 1` will be treated same as `showarchive`) + +Format: `showarchive` + +### Unarchive a student : `unarchive` + +* Unarchives a student in Mentorstack. +* Unarchived students are moved back to the main list. +* Input can contain multiple indices. +* Operation can only be performed on students in the archive list. +* Will display students who are archived after the command. + +Format: `unarchive INDEX…​` + +Examples: +* `showarchive` followed by `unarchive 1 3` unarchives the 1st and 3rd student in the archived list. + +
+ +### Indicate that a student has finished a subject : `finish` + +* Indicates that a student has finished a subject. +* Finished subjects are still shown but will be marked as a different colour. + +Format: `finish INDEX s/SUBJECT…​` + +* Input can only contain subjects that students have enrolled in. +* Input can contain multiple subjects. +* Finished subjects remain finished. +* Subjects of archived students cannot be finished. + +Examples: +* `finish 2 s/CS2103` marks CS2103 as completed by student 2. + +![result for 'finish'](images/finishResultUG.png) + +### Indicate that a student has not finished a subject : `unfinish` + +* Indicates that a student has not finished a subject. +* Primarily used as a reversal operation for `finish` + +Format: `unfinish INDEX s/SUBJECT…​` + +* Input can only contain subjects that students have enrolled in. +* Input can contain multiple subjects. +* Unfinished subjects remain unfinished. +* Subjects of archived students cannot be unfinished. + +Examples: +* `unfinish 2 s/CS2103` marks CS2103 as not completed by student 2. + +
+ ### Exiting the program : `exit` Exits the program. Format: `exit` +### Changing Mentorstack theme + +Mentorstack provides 3 visual themes to suit your preferences. You can change the theme by navigating to the View menu. + +Number| Theme +------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------- +1 | Dark +2 | Pink +3 | Light + +Mentorstack will always launch the Dark theme by default. + +### Viewing bar chart statistics + +Mentorstack provides users with the option to view the number of students enrolled in each subject in the current active list as a bar chart. +Users may view these statistics by navigating to the View menu. + ### Saving the data -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +Mentorstack data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. ### Editing the data file -AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. - -
:exclamation: **Caution:** -If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
-Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly. -
+Mentorstack data are saved automatically as a JSON file `[JAR file location]/data/mentorstack.json`. Advanced users are welcome to update data directly by editing that data file. -### Archiving data files `[coming in v2.0]` + -_Details coming soon ..._ +**Caution:** +If your changes to the data file makes its format invalid, Mentorstack will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
+Furthermore, certain edits can cause Mentorstack to behave in unexpected ways (e.g., if a value entered is outside the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly. +
-------------------------------------------------------------------------------------------------------------------- ## FAQ **Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Mentorstack home folder. -------------------------------------------------------------------------------------------------------------------- @@ -186,15 +433,27 @@ _Details coming soon ..._ 2. **If you minimize the Help Window** and then run the `help` command (or use the `Help` menu, or the keyboard shortcut `F1`) again, the original Help Window will remain minimized, and no new Help Window will appear. The remedy is to manually restore the minimized Help Window. -------------------------------------------------------------------------------------------------------------------- +
## Command summary -Action | Format, Examples ---------|------------------ -**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​`
e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -**Clear** | `clear` -**Delete** | `delete INDEX`
e.g., `delete 3` -**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` -**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` -**List** | `list` -**Help** | `help` +Action | Format, Examples +----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------- +**Add** | `add n/NAME g/GENDER p/PHONE_NUMBER e/EMAIL s/SUBJECT…​`
e.g., `add n/James Ho g/M p/22224444 e/jamesho@example.com s/CS2103 s/LAJ1201` +**Clear** | `clear` +**Delete** | `delete INDEX..`
e.g., `delete 1 3` +**Edit** | `edit INDEX [n/NAME] [g/GENDER] [p/PHONE_NUMBER] [e/EMAIL] [s/SUBJECT]…​`
e.g.,`edit 2 n/James Lee e/jameslee@example.com` +**Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` +**List** | `list` +**View** | `view [[f/FIELD] [v/VALUE]]…​`
e.g.,`view f/n v/john f/s v/CS` +**Undo** | `undo` +**Stats** | `stats [s/SUBJECT]`
e.g., `stats s/CS2103` +**Mark** | `mark INDEX…​`
e.g., `mark 2` +**Unmark** | `unmark INDEX…​`
e.g., `unmark 2` +**Archive** | `archive INDEX…​`
e.g., `archive 2` +**Showarchive** | `showarchive` +**Unarchive** | `unarchive INDEX…​`
e.g., `unarchive 2` +**Finish** | `finish INDEX s/SUBJECT…​`
e.g., `finish 2 s/CS2103` +**Unfinish** | `unfinish INDEX s/SUBJECT…​`
e.g., `unfinish 2 s/CS2103` +**Exit** | `exit` +**Help** | `help` diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 6bd245d8f4e..00000000000 --- a/docs/_config.yml +++ /dev/null @@ -1,15 +0,0 @@ -title: "AB-3" -theme: minima - -header_pages: - - UserGuide.md - - DeveloperGuide.md - - AboutUs.md - -markdown: kramdown - -repository: "se-edu/addressbook-level3" -github_icon: "images/github-icon.png" - -plugins: - - jemoji diff --git a/docs/_data/projects.yml b/docs/_data/projects.yml deleted file mode 100644 index 8f3e50cb601..00000000000 --- a/docs/_data/projects.yml +++ /dev/null @@ -1,23 +0,0 @@ -- name: "AB-1" - url: https://se-edu.github.io/addressbook-level1 - -- name: "AB-2" - url: https://se-edu.github.io/addressbook-level2 - -- name: "AB-3" - url: https://se-edu.github.io/addressbook-level3 - -- name: "AB-4" - url: https://se-edu.github.io/addressbook-level4 - -- name: "Duke" - url: https://se-edu.github.io/duke - -- name: "Collate" - url: https://se-edu.github.io/collate - -- name: "Book" - url: https://se-edu.github.io/se-book - -- name: "Resources" - url: https://se-edu.github.io/resources diff --git a/docs/_includes/custom-head.html b/docs/_includes/custom-head.html deleted file mode 100644 index 8559a67ffad..00000000000 --- a/docs/_includes/custom-head.html +++ /dev/null @@ -1,6 +0,0 @@ -{% comment %} - Placeholder to allow defining custom head, in principle, you can add anything here, e.g. favicons: - - 1. Head over to https://realfavicongenerator.net/ to add your own favicons. - 2. Customize default _includes/custom-head.html in your source directory and insert the given code snippet. -{% endcomment %} diff --git a/docs/_includes/head.html b/docs/_includes/head.html deleted file mode 100644 index 83ac5326933..00000000000 --- a/docs/_includes/head.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - {%- include custom-head.html -%} - - {{page.title}} - - diff --git a/docs/_includes/header.html b/docs/_includes/header.html deleted file mode 100644 index 33badcd4f99..00000000000 --- a/docs/_includes/header.html +++ /dev/null @@ -1,36 +0,0 @@ - diff --git a/docs/_layouts/alt-page.html b/docs/_layouts/alt-page.html deleted file mode 100644 index 5dbc6ef245f..00000000000 --- a/docs/_layouts/alt-page.html +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: default ---- -
- -
-

{{ page.alt_title | escape }}

-
- -
- {{ content }} -
- -
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html deleted file mode 100644 index e092cd572e0..00000000000 --- a/docs/_layouts/default.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - {%- include head.html -%} - - - - {%- include header.html -%} - -
-
- {{ content }} -
-
- - - - diff --git a/docs/_layouts/page.html b/docs/_layouts/page.html deleted file mode 100644 index 01e4b2a93b8..00000000000 --- a/docs/_layouts/page.html +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: default ---- -
- -
-

{{ page.title | escape }}

-
- -
- {{ content }} -
- -
diff --git a/docs/_markbind/layouts/default.md b/docs/_markbind/layouts/default.md new file mode 100644 index 00000000000..d9a5d731baf --- /dev/null +++ b/docs/_markbind/layouts/default.md @@ -0,0 +1,63 @@ + + + + +
+ + Mentorstack +
  • Home
  • +
  • User Guide
  • +
  • Developer Guide
  • +
  • About Us
  • +
  • :fab-github: +
  • +
  • + +
  • +
    +
    + +
    + +
    + {{ content }} +
    + + +
    + +
    + +
    + [**Powered by** {{MarkBind}}, generated on {{timestamp}}] +
    +
    diff --git a/docs/_markbind/variables.json b/docs/_markbind/variables.json new file mode 100644 index 00000000000..9d89eb0358b --- /dev/null +++ b/docs/_markbind/variables.json @@ -0,0 +1,3 @@ +{ + "jsonVariableExample": "Your variables can be defined here as well" +} diff --git a/docs/_markbind/variables.md b/docs/_markbind/variables.md new file mode 100644 index 00000000000..89ae5318fa4 --- /dev/null +++ b/docs/_markbind/variables.md @@ -0,0 +1,4 @@ + +To inject this HTML segment in your markbind files, use {{ example }} where you want to place it. +More generally, surround the segment's id with double curly braces. + diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss deleted file mode 100644 index 0d3f6e80ced..00000000000 --- a/docs/_sass/minima/_base.scss +++ /dev/null @@ -1,295 +0,0 @@ -html { - font-size: $base-font-size; -} - -/** - * Reset some basic elements - */ -body, h1, h2, h3, h4, h5, h6, -p, blockquote, pre, hr, -dl, dd, ol, ul, figure { - margin: 0; - padding: 0; - -} - - - -/** - * Basic styling - */ -body { - font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family; - color: $text-color; - background-color: $background-color; - -webkit-text-size-adjust: 100%; - -webkit-font-feature-settings: "kern" 1; - -moz-font-feature-settings: "kern" 1; - -o-font-feature-settings: "kern" 1; - font-feature-settings: "kern" 1; - font-kerning: normal; - display: flex; - min-height: 100vh; - flex-direction: column; - overflow-wrap: break-word; -} - - - -/** - * Set `margin-bottom` to maintain vertical rhythm - */ -h1, h2, h3, h4, h5, h6, -p, blockquote, pre, -ul, ol, dl, figure, -%vertical-rhythm { - margin-bottom: $spacing-unit / 2; -} - -hr { - margin-top: $spacing-unit; - margin-bottom: $spacing-unit; -} - -/** - * `main` element - */ -main { - display: block; /* Default value of `display` of `main` element is 'inline' in IE 11. */ -} - - - -/** - * Images - */ -img { - max-width: 100%; - vertical-align: middle; -} - - - -/** - * Figures - */ -figure > img { - display: block; -} - -figcaption { - font-size: $small-font-size; -} - - - -/** - * Lists - */ -ul, ol { - margin-left: $spacing-unit; -} - -li { - > ul, - > ol { - margin-bottom: 0; - } -} - - - -/** - * Headings - */ -h1, h2, h3, h4, h5, h6 { - font-weight: $base-font-weight; -} - - - -/** - * Links - */ -a { - color: $link-base-color; - text-decoration: none; - - &:visited { - color: $link-visited-color; - } - - &:hover { - color: $text-color; - text-decoration: underline; - } - - .social-media-list &:hover { - text-decoration: none; - - .username { - text-decoration: underline; - } - } -} - - -/** - * Blockquotes - */ -blockquote { - color: $brand-color; - border-left: 4px solid $brand-color-light; - padding-left: $spacing-unit / 2; - @include relative-font-size(1.125); - font-style: italic; - - > :last-child { - margin-bottom: 0; - } - - i, em { - font-style: normal; - } -} - - - -/** - * Code formatting - */ -pre, -code { - font-family: $code-font-family; - font-size: 0.9375em; - border: 1px solid $brand-color-light; - border-radius: 3px; - background-color: $code-background-color; -} - -code { - padding: 1px 5px; -} - -pre { - padding: 8px 12px; - overflow-x: auto; - - > code { - border: 0; - padding-right: 0; - padding-left: 0; - } -} - -.highlight { - border-radius: 3px; - background: $code-background-color; - @extend %vertical-rhythm; - - .highlighter-rouge & { - background: $code-background-color; - } -} - - - -/** - * Wrapper - */ -.wrapper { - max-width: calc(#{$content-width} - (#{$spacing-unit})); - margin-right: auto; - margin-left: auto; - padding-right: $spacing-unit / 2; - padding-left: $spacing-unit / 2; - @extend %clearfix; - - @media screen and (min-width: $on-large) { - max-width: calc(#{$content-width} - (#{$spacing-unit} * 2)); - padding-right: $spacing-unit; - padding-left: $spacing-unit; - } -} - - - -/** - * Clearfix - */ -%clearfix:after { - content: ""; - display: table; - clear: both; -} - - - -/** - * Icons - */ - -.orange { - color: #f66a0a; -} - -.grey { - color: #828282; -} - -/** - * Tables - */ -table { - margin-bottom: $spacing-unit; - width: 100%; - text-align: $table-text-align; - color: $table-text-color; - border-collapse: collapse; - border: 1px solid $table-border-color; - tr { - &:nth-child(even) { - background-color: $table-zebra-color; - } - } - th, td { - padding: ($spacing-unit / 3) ($spacing-unit / 2); - } - th { - background-color: $table-header-bg-color; - border: 1px solid $table-header-border; - } - td { - border: 1px solid $table-border-color; - } - - @include media-query($on-laptop) { - display: block; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: -ms-autohiding-scrollbar; - } -} - -@media print { - /** - * Prevents page break from cutting through content when printing - */ - body { - display: block; - } - /** - * Replaces the top navigation menu with the project name when printing - */ - .site-header .wrapper { - display: none; - } - .site-header { - text-align: center; - } - .site-header:before { - content: "AB-3"; - font-size: 32px; - } -} - diff --git a/docs/_sass/minima/_layout.scss b/docs/_sass/minima/_layout.scss deleted file mode 100644 index ca99f981701..00000000000 --- a/docs/_sass/minima/_layout.scss +++ /dev/null @@ -1,263 +0,0 @@ -/** - * Site header - */ -.site-header { - border-top: 5px solid $brand-color-dark; - border-bottom: 1px solid $brand-color-light; - min-height: $spacing-unit * 1.865; - line-height: $base-line-height * $base-font-size * 2.25; - - // Positioning context for the mobile navigation icon - position: relative; -} - -.site-title { - @include relative-font-size(1.625); - font-weight: 300; - letter-spacing: -1px; - margin-bottom: 0; - float: left; - - @include media-query($on-palm) { - padding-right: 45px; - } - - &, - &:visited { - color: $brand-color-dark; - } -} - -.site-nav { - position: absolute; - top: 9px; - right: $spacing-unit / 2; - background-color: $background-color; - border: 1px solid $brand-color-light; - border-radius: 5px; - text-align: right; - - .nav-trigger { - display: none; - } - - .menu-icon { - float: right; - width: 36px; - height: 26px; - line-height: 0; - padding-top: 10px; - text-align: center; - - > svg path { - fill: $brand-color-dark; - } - } - - label[for="nav-trigger"] { - display: block; - float: right; - width: 36px; - height: 36px; - z-index: 2; - cursor: pointer; - } - - input ~ .trigger { - clear: both; - display: none; - } - - input:checked ~ .trigger { - display: block; - padding-bottom: 5px; - } - - .page-link { - color: $text-color; - line-height: $base-line-height; - display: block; - padding: 5px 10px; - - // Gaps between nav items, but not on the last one - &:not(:last-child) { - margin-right: 0; - } - margin-left: 20px; - } - - @media screen and (min-width: $on-medium) { - position: static; - float: right; - border: none; - background-color: inherit; - - label[for="nav-trigger"] { - display: none; - } - - .menu-icon { - display: none; - } - - input ~ .trigger { - display: block; - } - - .page-link { - display: inline; - padding: 0; - - &:not(:last-child) { - margin-right: 20px; - } - margin-left: auto; - } - } -} - - - -/** - * Page content - */ -.page-content { - padding: $spacing-unit 0; - flex: 1 0 auto; -} - -.page-heading { - @include relative-font-size(2); -} - -.post-list-heading { - @include relative-font-size(1.75); -} - -.post-list { - margin-left: 0; - list-style: none; - - > li { - margin-bottom: $spacing-unit; - } -} - -.post-meta { - font-size: $small-font-size; - color: $brand-color; -} - -.post-link { - display: block; - @include relative-font-size(1.5); -} - - - -/** - * Posts - */ -.post-header { - margin-bottom: $spacing-unit; -} - -.post-title, -.post-content h1 { - @include relative-font-size(2.625); - letter-spacing: -1px; - line-height: 1.15; - - @media screen and (min-width: $on-large) { - @include relative-font-size(2.625); - } -} - -.post-content { - margin-bottom: $spacing-unit; - - h1, h2, h3 { margin-top: $spacing-unit * 2 } - h4, h5, h6 { margin-top: $spacing-unit } - - h2 { - @include relative-font-size(1.75); - - @media screen and (min-width: $on-large) { - @include relative-font-size(2); - } - } - - h3 { - @include relative-font-size(1.375); - - @media screen and (min-width: $on-large) { - @include relative-font-size(1.625); - } - } - - h4 { - @include relative-font-size(1.25); - } - - h5 { - @include relative-font-size(1.125); - } - h6 { - @include relative-font-size(1.0625); - } -} - - -.social-media-list { - display: table; - margin: 0 auto; - li { - float: left; - margin: 5px 10px 5px 0; - &:last-of-type { margin-right: 0 } - a { - display: block; - padding: $spacing-unit / 4; - border: 1px solid $brand-color-light; - &:hover { border-color: darken($brand-color-light, 10%) } - } - } -} - - - -/** - * Pagination navbar - */ -.pagination { - margin-bottom: $spacing-unit; - @extend .social-media-list; - li { - a, div { - min-width: 41px; - text-align: center; - box-sizing: border-box; - } - div { - display: block; - padding: $spacing-unit / 4; - border: 1px solid transparent; - - &.pager-edge { - color: darken($brand-color-light, 5%); - border: 1px dashed; - } - } - } -} - - - -/** - * Grid helpers - */ -@media screen and (min-width: $on-large) { - .one-half { - width: calc(50% - (#{$spacing-unit} / 2)); - } -} diff --git a/docs/_sass/minima/custom-mixins.scss b/docs/_sass/minima/custom-mixins.scss deleted file mode 100644 index 9d4bedc1c67..00000000000 --- a/docs/_sass/minima/custom-mixins.scss +++ /dev/null @@ -1,21 +0,0 @@ -@mixin alert-variant($background, $border, $color) { - color: $color; - @include gradient-bg($background); - border-color: $border; - - .alert-link { - color: darken($color, 10%); - } -} - -@mixin gradient-bg($color, $foreground: null) { - @if $enable-gradients { - @if $foreground { - background-image: $foreground, linear-gradient(180deg, mix($body-bg, $color, 15%), $color); - } @else { - background-image: linear-gradient(180deg, mix($body-bg, $color, 15%), $color); - } - } @else { - background-color: $color; - } -} diff --git a/docs/_sass/minima/custom-styles.scss b/docs/_sass/minima/custom-styles.scss deleted file mode 100644 index 56b5d56b430..00000000000 --- a/docs/_sass/minima/custom-styles.scss +++ /dev/null @@ -1,34 +0,0 @@ -// Placeholder to allow defining custom styles that override everything else. -// (Use `_sass/minima/custom-variables.scss` to override variable defaults) -h2, h3, h4, h5, h6 { - color: #e46c0a; -} - -// Bootstrap style alerts -.alert { - position: relative; - padding: $alert-padding-y $alert-padding-x; - margin-bottom: $alert-margin-bottom; - border: $alert-border-width solid transparent; - border-radius : $alert-border-radius; -} - -// Headings for larger alerts -.alert-heading { - // Specified to prevent conflicts of changing $headings-color - color: inherit; -} - -// Provide class for links that match alerts -.alert-link { - font-weight: $alert-link-font-weight; -} - -// Generate contextual modifier classes for colorizing the alert. - -@each $color, $value in $theme-colors { - .alert-#{$color} { - @include alert-variant(color-level($value, $alert-bg-level), color-level($value, $alert-border-level), color-level($value, $alert-color-level)); - } -} - diff --git a/docs/_sass/minima/custom-variables.scss b/docs/_sass/minima/custom-variables.scss deleted file mode 100644 index a128970cbe7..00000000000 --- a/docs/_sass/minima/custom-variables.scss +++ /dev/null @@ -1,76 +0,0 @@ -// Placeholder to allow overriding predefined variables smoothly. - -//Bootstrap's default -$white: #fff !default; -$gray-100: #f8f9fa !default; -$gray-200: #e9ecef !default; -$gray-300: #dee2e6 !default; -$gray-400: #ced4da !default; -$gray-500: #adb5bd !default; -$gray-600: #6c757d !default; -$gray-700: #495057 !default; -$gray-800: #343a40 !default; -$gray-900: #212529 !default; -$black: #000 !default; -$blue: #0d6efd !default; -$indigo: #6610f2 !default; -$purple: #6f42c1 !default; -$pink: #d63384 !default; -$red: #dc3545 !default; -$orange: #fd7e14 !default; -$yellow: #ffc107 !default; -$green: #28a745 !default; -$teal: #20c997 !default; -$cyan: #17a2b8 !default; - -$primary: $blue !default; -$secondary: $gray-600 !default; -$success: $green !default; -$info: $cyan !default; -$warning: $yellow !default; -$danger: $red !default; -$light: $gray-100 !default; -$dark: $gray-800 !default; - -$theme-colors: ( - "primary": $primary, - "secondary": $secondary, - "success": $success, - "info": $info, - "warning": $warning, - "danger": $danger, - "light": $light, - "dark": $dark -) !default; - -$theme-color-interval: 8% !default; - -$body-bg: $white !default; -$body-color: $gray-900 !default; -$body-text-align: null !default; - -$enable-gradients: true; - -// Define alert colors, border radius, and padding. -$border-radius: .25rem !default; -$border-width: 1px !default; -$font-weight-bold: 700 !default; - -$alert-padding-y: .75rem !default; -$alert-padding-x: 1.25rem !default; -$alert-margin-bottom: 1rem !default; -$alert-border-radius: $border-radius !default; -$alert-link-font-weight: $font-weight-bold !default; -$alert-border-width: $border-width !default; - -$alert-bg-level: -10 !default; -$alert-border-level: -9 !default; -$alert-color-level: 6 !default; - -// Request a color level -// scss-docs-start color-level -@function color-level($color: $primary, $level: 0) { - $color-base: if($level > 0, $black, $white); - $level: abs($level); - @return mix($color-base, $color, $level * $theme-color-interval); -} diff --git a/docs/_sass/minima/initialize.scss b/docs/_sass/minima/initialize.scss deleted file mode 100644 index 30288811151..00000000000 --- a/docs/_sass/minima/initialize.scss +++ /dev/null @@ -1,51 +0,0 @@ -@charset "utf-8"; - -// Define defaults for each variable. - -$base-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Segoe UI Symbol", "Segoe UI Emoji", "Apple Color Emoji", Roboto, Helvetica, Arial, sans-serif !default; -$code-font-family: "Menlo", "Inconsolata", "Consolas", "Roboto Mono", "Ubuntu Mono", "Liberation Mono", "Courier New", monospace; -$base-font-size: 16px !default; -$base-font-weight: 400 !default; -$small-font-size: $base-font-size * 0.875 !default; -$base-line-height: 1.5 !default; - -$spacing-unit: 30px !default; - -$table-text-align: left !default; - -// Width of the content area -$content-width: 800px !default; - -$on-palm: 600px !default; -$on-laptop: 800px !default; - -$on-medium: $on-palm !default; -$on-large: $on-laptop !default; - -// Use media queries like this: -// @include media-query($on-palm) { -// .wrapper { -// padding-right: $spacing-unit / 2; -// padding-left: $spacing-unit / 2; -// } -// } -// Notice the following mixin uses max-width, in a deprecated, desktop-first -// approach, whereas media queries used elsewhere now use min-width. -@mixin media-query($device) { - @media screen and (max-width: $device) { - @content; - } -} - -@mixin relative-font-size($ratio) { - font-size: #{$ratio}rem; -} - -// Import pre-styling-overrides hook and style-partials. -@import - "minima/custom-variables", // Hook to override predefined variables. - "minima/custom-mixins", // Hook to add custom mixins. - "minima/base", // Defines element resets. - "minima/layout", // Defines structure and style based on CSS selectors. - "minima/custom-styles" // Hook to override existing styles. -; diff --git a/docs/_sass/minima/skins/classic.scss b/docs/_sass/minima/skins/classic.scss deleted file mode 100644 index 37ea9c5244c..00000000000 --- a/docs/_sass/minima/skins/classic.scss +++ /dev/null @@ -1,84 +0,0 @@ -@charset "utf-8"; - -$brand-color: #828282 !default; -$brand-color-light: lighten($brand-color, 40%) !default; -$brand-color-dark: darken($brand-color, 25%) !default; - -$text-color: #111 !default; -$background-color: #fdfdfd !default; -$code-background-color: #eef !default; - -$link-base-color: #2a7ae2 !default; -$link-visited-color: darken($link-base-color, 15%) !default; - -$table-text-color: lighten($text-color, 18%) !default; -$table-zebra-color: lighten($brand-color, 46%) !default; -$table-header-bg-color: lighten($brand-color, 43%) !default; -$table-header-border: lighten($brand-color, 36%) !default; -$table-border-color: $brand-color-light !default; - - -// Syntax highlighting styles should be adjusted appropriately for every "skin" -// ---------------------------------------------------------------------------- - -.highlight { - .c { color: #998; font-style: italic } // Comment - .err { color: #a61717; background-color: #e3d2d2 } // Error - .k { font-weight: bold } // Keyword - .o { font-weight: bold } // Operator - .cm { color: #998; font-style: italic } // Comment.Multiline - .cp { color: #999; font-weight: bold } // Comment.Preproc - .c1 { color: #998; font-style: italic } // Comment.Single - .cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special - .gd { color: #000; background-color: #fdd } // Generic.Deleted - .gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific - .ge { font-style: italic } // Generic.Emph - .gr { color: #a00 } // Generic.Error - .gh { color: #999 } // Generic.Heading - .gi { color: #000; background-color: #dfd } // Generic.Inserted - .gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific - .go { color: #888 } // Generic.Output - .gp { color: #555 } // Generic.Prompt - .gs { font-weight: bold } // Generic.Strong - .gu { color: #aaa } // Generic.Subheading - .gt { color: #a00 } // Generic.Traceback - .kc { font-weight: bold } // Keyword.Constant - .kd { font-weight: bold } // Keyword.Declaration - .kp { font-weight: bold } // Keyword.Pseudo - .kr { font-weight: bold } // Keyword.Reserved - .kt { color: #458; font-weight: bold } // Keyword.Type - .m { color: #099 } // Literal.Number - .s { color: #d14 } // Literal.String - .na { color: #008080 } // Name.Attribute - .nb { color: #0086B3 } // Name.Builtin - .nc { color: #458; font-weight: bold } // Name.Class - .no { color: #008080 } // Name.Constant - .ni { color: #800080 } // Name.Entity - .ne { color: #900; font-weight: bold } // Name.Exception - .nf { color: #900; font-weight: bold } // Name.Function - .nn { color: #555 } // Name.Namespace - .nt { color: #000080 } // Name.Tag - .nv { color: #008080 } // Name.Variable - .ow { font-weight: bold } // Operator.Word - .w { color: #bbb } // Text.Whitespace - .mf { color: #099 } // Literal.Number.Float - .mh { color: #099 } // Literal.Number.Hex - .mi { color: #099 } // Literal.Number.Integer - .mo { color: #099 } // Literal.Number.Oct - .sb { color: #d14 } // Literal.String.Backtick - .sc { color: #d14 } // Literal.String.Char - .sd { color: #d14 } // Literal.String.Doc - .s2 { color: #d14 } // Literal.String.Double - .se { color: #d14 } // Literal.String.Escape - .sh { color: #d14 } // Literal.String.Heredoc - .si { color: #d14 } // Literal.String.Interpol - .sx { color: #d14 } // Literal.String.Other - .sr { color: #009926 } // Literal.String.Regex - .s1 { color: #d14 } // Literal.String.Single - .ss { color: #990073 } // Literal.String.Symbol - .bp { color: #999 } // Name.Builtin.Pseudo - .vc { color: #008080 } // Name.Variable.Class - .vg { color: #008080 } // Name.Variable.Global - .vi { color: #008080 } // Name.Variable.Instance - .il { color: #099 } // Literal.Number.Integer.Long -} diff --git a/docs/_sass/minima/skins/solarized-dark.scss b/docs/_sass/minima/skins/solarized-dark.scss deleted file mode 100644 index f3b1f387de0..00000000000 --- a/docs/_sass/minima/skins/solarized-dark.scss +++ /dev/null @@ -1,4 +0,0 @@ -@charset "utf-8"; - -$sol-is-dark: true; -@import "minima/skins/solarized"; diff --git a/docs/_sass/minima/skins/solarized.scss b/docs/_sass/minima/skins/solarized.scss deleted file mode 100644 index 982bd7f2990..00000000000 --- a/docs/_sass/minima/skins/solarized.scss +++ /dev/null @@ -1,133 +0,0 @@ -@charset "utf-8"; - -// Solarized skin -// ============== -// Created by Sander Voerman using the Solarized -// color scheme by Ethan Schoonover . - -// This style sheet implements two options for the minima.skin setting: -// "solarized" for light mode and "solarized-dark" for dark mode. -$sol-is-dark: false !default; - - -// Color scheme -// ------------ -// The inline comments show the canonical L*a*b values for each color. - -$sol-base03: #002b36; // 15 -12 -12 -$sol-base02: #073642; // 20 -12 -12 -$sol-base01: #586e75; // 45 -07 -07 -$sol-base00: #657b83; // 50 -07 -07 -$sol-base0: #839496; // 60 -06 -03 -$sol-base1: #93a1a1; // 65 -05 -02 -$sol-base2: #eee8d5; // 92 -00 10 -$sol-base3: #fdf6e3; // 97 00 10 -$sol-yellow: #b58900; // 60 10 65 -$sol-orange: #cb4b16; // 50 50 55 -$sol-red: #dc322f; // 50 65 45 -$sol-magenta: #d33682; // 50 65 -05 -$sol-violet: #6c71c4; // 50 15 -45 -$sol-blue: #268bd2; // 55 -10 -45 -$sol-cyan: #2aa198; // 60 -35 -05 -$sol-green: #859900; // 60 -20 65 - -$sol-mono3: $sol-base3; -$sol-mono2: $sol-base2; -$sol-mono1: $sol-base1; -$sol-mono00: $sol-base00; -$sol-mono01: $sol-base01; - -@if $sol-is-dark { - $sol-mono3: $sol-base03; - $sol-mono2: $sol-base02; - $sol-mono1: $sol-base01; - $sol-mono00: $sol-base0; - $sol-mono01: $sol-base1; -} - - -// Minima color variables -// ---------------------- - -$brand-color: $sol-mono1 !default; -$brand-color-light: mix($sol-mono1, $sol-mono3) !default; -$brand-color-dark: $sol-mono00 !default; - -$text-color: $sol-mono01 !default; -$background-color: $sol-mono3 !default; -$code-background-color: $sol-mono2 !default; - -$link-base-color: $sol-blue !default; -$link-visited-color: mix($sol-blue, $sol-mono00) !default; - -$table-text-color: $sol-mono00 !default; -$table-zebra-color: mix($sol-mono2, $sol-mono3) !default; -$table-header-bg-color: $sol-mono2 !default; -$table-header-border: $sol-mono1 !default; -$table-border-color: $sol-mono1 !default; - - -// Syntax highlighting styles -// -------------------------- - -.highlight { - .c { color: $sol-mono1; font-style: italic } // Comment - .err { color: $sol-red } // Error - .k { color: $sol-mono01; font-weight: bold } // Keyword - .o { color: $sol-mono01; font-weight: bold } // Operator - .cm { color: $sol-mono1; font-style: italic } // Comment.Multiline - .cp { color: $sol-mono1; font-weight: bold } // Comment.Preproc - .c1 { color: $sol-mono1; font-style: italic } // Comment.Single - .cs { color: $sol-mono1; font-weight: bold; font-style: italic } // Comment.Special - .gd { color: $sol-red } // Generic.Deleted - .gd .x { color: $sol-red } // Generic.Deleted.Specific - .ge { color: $sol-mono00; font-style: italic } // Generic.Emph - .gr { color: $sol-red } // Generic.Error - .gh { color: $sol-mono1 } // Generic.Heading - .gi { color: $sol-green } // Generic.Inserted - .gi .x { color: $sol-green } // Generic.Inserted.Specific - .go { color: $sol-mono00 } // Generic.Output - .gp { color: $sol-mono00 } // Generic.Prompt - .gs { color: $sol-mono01; font-weight: bold } // Generic.Strong - .gu { color: $sol-mono1 } // Generic.Subheading - .gt { color: $sol-red } // Generic.Traceback - .kc { color: $sol-mono01; font-weight: bold } // Keyword.Constant - .kd { color: $sol-mono01; font-weight: bold } // Keyword.Declaration - .kp { color: $sol-mono01; font-weight: bold } // Keyword.Pseudo - .kr { color: $sol-mono01; font-weight: bold } // Keyword.Reserved - .kt { color: $sol-violet; font-weight: bold } // Keyword.Type - .m { color: $sol-cyan } // Literal.Number - .s { color: $sol-magenta } // Literal.String - .na { color: $sol-cyan } // Name.Attribute - .nb { color: $sol-blue } // Name.Builtin - .nc { color: $sol-violet; font-weight: bold } // Name.Class - .no { color: $sol-cyan } // Name.Constant - .ni { color: $sol-violet } // Name.Entity - .ne { color: $sol-violet; font-weight: bold } // Name.Exception - .nf { color: $sol-blue; font-weight: bold } // Name.Function - .nn { color: $sol-mono00 } // Name.Namespace - .nt { color: $sol-blue } // Name.Tag - .nv { color: $sol-cyan } // Name.Variable - .ow { color: $sol-mono01; font-weight: bold } // Operator.Word - .w { color: $sol-mono1 } // Text.Whitespace - .mf { color: $sol-cyan } // Literal.Number.Float - .mh { color: $sol-cyan } // Literal.Number.Hex - .mi { color: $sol-cyan } // Literal.Number.Integer - .mo { color: $sol-cyan } // Literal.Number.Oct - .sb { color: $sol-magenta } // Literal.String.Backtick - .sc { color: $sol-magenta } // Literal.String.Char - .sd { color: $sol-magenta } // Literal.String.Doc - .s2 { color: $sol-magenta } // Literal.String.Double - .se { color: $sol-magenta } // Literal.String.Escape - .sh { color: $sol-magenta } // Literal.String.Heredoc - .si { color: $sol-magenta } // Literal.String.Interpol - .sx { color: $sol-magenta } // Literal.String.Other - .sr { color: $sol-green } // Literal.String.Regex - .s1 { color: $sol-magenta } // Literal.String.Single - .ss { color: $sol-magenta } // Literal.String.Symbol - .bp { color: $sol-mono1 } // Name.Builtin.Pseudo - .vc { color: $sol-cyan } // Name.Variable.Class - .vg { color: $sol-cyan } // Name.Variable.Global - .vi { color: $sol-cyan } // Name.Variable.Instance - .il { color: $sol-cyan } // Literal.Number.Integer.Long -} diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss deleted file mode 100644 index b5ec6976efa..00000000000 --- a/docs/assets/css/style.scss +++ /dev/null @@ -1,12 +0,0 @@ ---- -# Only the main Sass file needs front matter (the dashes are enough) ---- - -@import - "minima/skins/{{ site.minima.skin | default: 'classic' }}", - "minima/initialize"; - -.icon { - height: 21px; - width: 21px -} diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index 48b6cc4333c..c26f4acc116 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -20,7 +20,7 @@ activate model MODEL_COLOR model -[MODEL_COLOR]-> logic deactivate model -logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +logic -[LOGIC_COLOR]> storage : saveMentorstack(mentorstack) activate storage STORAGE_COLOR storage -[STORAGE_COLOR]> storage : Save to file diff --git a/docs/diagrams/ArchiveSequenceDiagram.puml b/docs/diagrams/ArchiveSequenceDiagram.puml new file mode 100644 index 00000000000..42e4d6771e1 --- /dev/null +++ b/docs/diagrams/ArchiveSequenceDiagram.puml @@ -0,0 +1,37 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +actor User +participant MentorstackParser MODEL_COLOR +participant ArchiveCommandParser MODEL_COLOR +participant ArchiveCommand MODEL_COLOR +participant ModelManager MODEL_COLOR + + +User -> MentorstackParser: parseCommand("archive 1") +activate MentorstackParser +MentorstackParser -> ArchiveCommandParser: new ArchiveCommandParser().parse("1") +activate ArchiveCommandParser +ArchiveCommandParser -> ArchiveCommand: new ArchiveCommand({1}) +activate ArchiveCommand +ArchiveCommand -> ModelManager: getFilteredPersonList().get(1) +activate ModelManager +ModelManager --> ArchiveCommand: personToArchive +deactivate ModelManager +ArchiveCommand -> ModelManager: archivePerson(personToArchive, archivedPerson) +activate ModelManager +ModelManager -> ModelManager: updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS) +activate ModelManager +ModelManager --> ModelManager +deactivate ModelManager +ModelManager --> ArchiveCommand +deactivate ModelManager +ArchiveCommand --> ArchiveCommandParser +deactivate ArchiveCommand +ArchiveCommandParser --> MentorstackParser +deactivate ArchiveCommandParser +MentorstackParser --> User +deactivate MentorstackParser + +@enduml diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 598474a5c82..27629d9947d 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -4,18 +4,13 @@ skinparam arrowThickness 1.1 skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR -AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList -UniqueTagList -[hidden]down- UniquePersonList -UniqueTagList -[hidden]down- UniquePersonList +Mentorstack *-right-> "1" UniquePersonList -UniqueTagList -right-> "*" Tag UniquePersonList -right-> Person -Person -up-> "*" Tag - Person *--> Name +Person *--> Gender Person *--> Phone Person *--> Email -Person *--> Address +Person *-right-> "1..n" Subject @enduml diff --git a/docs/diagrams/CommitActivityDiagram.puml b/docs/diagrams/CommitActivityDiagram.puml index 8c0892d6a70..33da44111a0 100644 --- a/docs/diagrams/CommitActivityDiagram.puml +++ b/docs/diagrams/CommitActivityDiagram.puml @@ -8,10 +8,9 @@ start 'Since the beta syntax does not support placing the condition outside the 'diamond we place it as the true branch instead. -if () then ([command commits AddressBook]) - :Purge redundant states; - :Save AddressBook to - addressBookStateList; +if () then ([command will make changes to Mentorstack]) + :Save Mentorstack to + history; else ([else]) endif stop diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 5241e79d7da..14c0fd7da49 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -4,7 +4,7 @@ skinparam ArrowFontStyle plain box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":MentorstackParser" as MentorstackParser LOGIC_COLOR participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR participant "r:CommandResult" as CommandResult LOGIC_COLOR @@ -17,17 +17,17 @@ end box [-> LogicManager : execute("delete 1") activate LogicManager -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser +LogicManager -> MentorstackParser : parseCommand("delete 1") +activate MentorstackParser create DeleteCommandParser -AddressBookParser -> DeleteCommandParser +MentorstackParser -> DeleteCommandParser activate DeleteCommandParser -DeleteCommandParser --> AddressBookParser +DeleteCommandParser --> MentorstackParser deactivate DeleteCommandParser -AddressBookParser -> DeleteCommandParser : parse("1") +MentorstackParser -> DeleteCommandParser : parse("1") activate DeleteCommandParser create DeleteCommand @@ -37,14 +37,14 @@ activate DeleteCommand DeleteCommand --> DeleteCommandParser : deactivate DeleteCommand -DeleteCommandParser --> AddressBookParser : d +DeleteCommandParser --> MentorstackParser : d deactivate DeleteCommandParser 'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser +DeleteCommandParser -[hidden]-> MentorstackParser destroy DeleteCommandParser -AddressBookParser --> LogicManager : d -deactivate AddressBookParser +MentorstackParser --> LogicManager : d +deactivate MentorstackParser LogicManager -> DeleteCommand : execute(m) activate DeleteCommand diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..3430067c5f0 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -5,20 +5,21 @@ skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR Package Model as ModelPackage <>{ -Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook +Class "<>\nReadOnlyMentorstack" as ReadOnlyMentorstack Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs Class "<>\nModel" as Model -Class AddressBook +Class Mentorstack Class ModelManager Class UserPrefs Class UniquePersonList Class Person -Class Address Class Email +Class Gender Class Name Class Phone -Class Tag +Class Subject +Class ArchiveStatus Class I #FFFFFF } @@ -26,29 +27,32 @@ Class I #FFFFFF Class HiddenOutside #FFFFFF HiddenOutside ..> Model -AddressBook .up.|> ReadOnlyAddressBook +Mentorstack .up.|> ReadOnlyMentorstack ModelManager .up.|> Model Model .right.> ReadOnlyUserPrefs -Model .left.> ReadOnlyAddressBook -ModelManager -left-> "1" AddressBook +Model .left.> ReadOnlyMentorstack +ModelManager -left-> "1" Mentorstack ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs -AddressBook *--> "1" UniquePersonList +Mentorstack *--> "1" UniquePersonList UniquePersonList --> "~* all" Person Person *--> Name Person *--> Phone Person *--> Email -Person *--> Address -Person *--> "*" Tag +Person *--> Gender +Person *--> Subject +Person *--> ArchiveStatus Person -[hidden]up--> I UniquePersonList -[hidden]right-> I Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +Phone -[hidden]right-> Gender +Gender -[hidden]right-> Email +Email -[hidden]right-> Subject +Subject -[hidden]right-> ArchiveStatus ModelManager --> "~* filtered" Person @enduml diff --git a/docs/diagrams/ParserClasses.puml b/docs/diagrams/ParserClasses.puml index ce4c5ce8c8d..dfc995cc1be 100644 --- a/docs/diagrams/ParserClasses.puml +++ b/docs/diagrams/ParserClasses.puml @@ -9,7 +9,7 @@ Class XYZCommand package "Parser classes"{ Class "<>\nParser" as Parser -Class AddressBookParser +Class MentorstackParser Class XYZCommandParser Class CliSyntax Class ParserUtil @@ -19,12 +19,12 @@ Class Prefix } Class HiddenOutside #FFFFFF -HiddenOutside ..> AddressBookParser +HiddenOutside ..> MentorstackParser -AddressBookParser .down.> XYZCommandParser: <> +MentorstackParser .down.> XYZCommandParser: <> XYZCommandParser ..> XYZCommand : <> -AddressBookParser ..> Command : <> +MentorstackParser ..> Command : <> XYZCommandParser .up.|> Parser XYZCommandParser ..> ArgumentMultimap XYZCommandParser ..> ArgumentTokenizer diff --git a/docs/diagrams/StatsCommandClassDiagram.puml b/docs/diagrams/StatsCommandClassDiagram.puml new file mode 100644 index 00000000000..62d291475df --- /dev/null +++ b/docs/diagrams/StatsCommandClassDiagram.puml @@ -0,0 +1,41 @@ +@startuml +class Command { + + execute(model: Model): CommandResult +} + +class StatsCommand { + - subject: Subject + + StatsCommand() + + StatsCommand(subject: Subject) + + execute(model: Model): CommandResult +} + +class StatsCommandParser { + + parse(args: String): StatsCommand +} + +class Model { + + getFilteredPersonList(): List +} + +class Subject { + + Subject(name: String) +} + +class Person { + + getGender(): String + + getSubjects(): List +} + +class CommandResult { + + CommandResult(message: String) +} + +Command <|-- StatsCommand +StatsCommand --> Model +StatsCommand --> Subject +Model --> Person +StatsCommand --> CommandResult +Person --> Subject +StatsCommandParser --> StatsCommand +@enduml diff --git a/docs/diagrams/StatsSequenceDiagram.puml b/docs/diagrams/StatsSequenceDiagram.puml new file mode 100644 index 00000000000..db1f0d47f14 --- /dev/null +++ b/docs/diagrams/StatsSequenceDiagram.puml @@ -0,0 +1,33 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +actor User +participant MentorstackParser MODEL_COLOR +participant StatsCommandParser MODEL_COLOR +participant StatsCommand MODEL_COLOR +participant Model MODEL_COLOR +participant CommandResult MODEL_COLOR + +User -> MentorstackParser: parseCommand("stats s/CS1010S") +activate MentorstackParser +MentorstackParser -> StatsCommandParser: parse("stats s/CS1010S") +activate StatsCommandParser +StatsCommandParser -> StatsCommand: new StatsCommand(Subject("CS1010S")) +activate StatsCommand +StatsCommand -> Model: getFilteredPersonList() +activate Model +Model --> StatsCommand +deactivate Model +StatsCommand -> CommandResult: new CommandResult(statisticsMessage) +activate CommandResult +CommandResult --> StatsCommand +deactivate CommandResult +StatsCommand --> StatsCommandParser +deactivate StatsCommand +StatsCommandParser --> MentorstackParser +deactivate StatsCommandParser +MentorstackParser --> User +deactivate MentorstackParser + +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..d5560701dc5 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -14,12 +14,12 @@ Class JsonUserPrefsStorage Class "<>\nStorage" as Storage Class StorageManager -package "AddressBook Storage" #F4F6F6{ -Class "<>\nAddressBookStorage" as AddressBookStorage -Class JsonAddressBookStorage -Class JsonSerializableAddressBook +package "Mentorstack Storage" #F4F6F6{ +Class "<>\nMentorstackStorage" as MentorstackStorage +Class JsonMentorstackStorage +Class JsonSerializableMentorstack Class JsonAdaptedPerson -Class JsonAdaptedTag +Class JsonAdaptedSubject } } @@ -29,15 +29,15 @@ HiddenOutside ..> Storage StorageManager .up.|> Storage StorageManager -up-> "1" UserPrefsStorage -StorageManager -up-> "1" AddressBookStorage +StorageManager -up-> "1" MentorstackStorage Storage -left-|> UserPrefsStorage Storage -right-|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage -JsonAddressBookStorage .up.|> AddressBookStorage -JsonAddressBookStorage ..> JsonSerializableAddressBook -JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonMentorstackStorage .up.|> MentorstackStorage +JsonMentorstackStorage ..> JsonSerializableMentorstack +JsonSerializableMentorstack --> "*" JsonAdaptedPerson +JsonAdaptedPerson --> "*" JsonAdaptedSubject @enduml diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml deleted file mode 100644 index 43a45903ac9..00000000000 --- a/docs/diagrams/UndoRedoState0.puml +++ /dev/null @@ -1,21 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 -skinparam ClassBackgroundColor #FFFFAA - -title Initial state - -package States { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" -} -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 -hide State2 -hide State3 - -class Pointer as "Current State" #FFFFFF -Pointer -up-> State1 -@end diff --git a/docs/diagrams/UndoSequenceDiagram-Logic.puml b/docs/diagrams/UndoSequenceDiagram-Logic.puml index e57368c5159..eb3c6ee7320 100644 --- a/docs/diagrams/UndoSequenceDiagram-Logic.puml +++ b/docs/diagrams/UndoSequenceDiagram-Logic.puml @@ -4,7 +4,7 @@ skinparam ArrowFontStyle plain box Logic LOGIC_COLOR_T1 participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":MentorstackParser" as MentorstackParser LOGIC_COLOR participant "u:UndoCommand" as UndoCommand LOGIC_COLOR end box @@ -14,23 +14,23 @@ end box [-> LogicManager : execute(undo) activate LogicManager -LogicManager -> AddressBookParser : parseCommand(undo) -activate AddressBookParser +LogicManager -> MentorstackParser : parseCommand(undo) +activate MentorstackParser create UndoCommand -AddressBookParser -> UndoCommand +MentorstackParser -> UndoCommand activate UndoCommand -UndoCommand --> AddressBookParser +UndoCommand --> MentorstackParser deactivate UndoCommand -AddressBookParser --> LogicManager : u -deactivate AddressBookParser +MentorstackParser --> LogicManager : u +deactivate MentorstackParser LogicManager -> UndoCommand : execute() activate UndoCommand -UndoCommand -> Model : undoAddressBook() +UndoCommand -> Model : undo() activate Model Model --> UndoCommand diff --git a/docs/diagrams/UndoSequenceDiagram-Model.puml b/docs/diagrams/UndoSequenceDiagram-Model.puml index 54d83208cb8..96af7e0de2f 100644 --- a/docs/diagrams/UndoSequenceDiagram-Model.puml +++ b/docs/diagrams/UndoSequenceDiagram-Model.puml @@ -4,18 +4,29 @@ skinparam ArrowFontStyle plain box Model MODEL_COLOR_T1 participant ":Model" as Model MODEL_COLOR -participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR +participant ":Mentorstack" as Mentorstack MODEL_COLOR +participant ":History" as History MODEL_COLOR end box -[-> Model : undoAddressBook() +[-> Model : undo() activate Model -Model -> VersionedAddressBook : undo() -activate VersionedAddressBook +Model -> Model : canUndo() -VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook) -VersionedAddressBook --> Model : -deactivate VersionedAddressBook +alt canUndo() == true + Model -> History : pop() + activate History + + History --> Model : ReadOnlyMentorstack + deactivate History + + Model -> Mentorstack : resetData(ReadOnlyMentorstack) + + activate Mentorstack + + Mentorstack --> Model : + deactivate Mentorstack +end [<-- Model deactivate Model diff --git a/docs/diagrams/UndoState0.puml b/docs/diagrams/UndoState0.puml new file mode 100644 index 00000000000..99593ab2655 --- /dev/null +++ b/docs/diagrams/UndoState0.puml @@ -0,0 +1,18 @@ +@startuml +!include style.puml +skinparam ClassFontColor #000000 +skinparam ClassBorderColor #000000 +skinparam ClassBackgroundColor #FFFFAA + +title Initial state + +package States { + class State1 as "ab0:Mentorstack" + class State2 as "ab1:Mentorstack" +} +State1 -[hidden]right-> State2 +hide State1 +hide State2 + + +@end diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoState1.puml similarity index 50% rename from docs/diagrams/UndoRedoState1.puml rename to docs/diagrams/UndoState1.puml index 5a41e9e1651..5f8fa2080a1 100644 --- a/docs/diagrams/UndoRedoState1.puml +++ b/docs/diagrams/UndoState1.puml @@ -7,17 +7,12 @@ skinparam ClassBackgroundColor #FFFFAA title After command "delete 5" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "ab0:Mentorstack" + class State2 as "ab1:Mentorstack" } State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 -hide State3 +hide State2 -class Pointer as "Current State" #FFFFFF - -Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoState2.puml similarity index 51% rename from docs/diagrams/UndoRedoState2.puml rename to docs/diagrams/UndoState2.puml index ad32fce1b0b..10d14cf38c3 100644 --- a/docs/diagrams/UndoRedoState2.puml +++ b/docs/diagrams/UndoState2.puml @@ -7,15 +7,10 @@ skinparam ClassBackgroundColor #FFFFAA title After command "add n/David" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "ab0:Mentorstack" + class State2 as "ab1:Mentorstack" } State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFFF - -Pointer -up-> State3 @end diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoState3.puml similarity index 51% rename from docs/diagrams/UndoRedoState3.puml rename to docs/diagrams/UndoState3.puml index 9187a690036..9304d428396 100644 --- a/docs/diagrams/UndoRedoState3.puml +++ b/docs/diagrams/UndoState3.puml @@ -7,15 +7,12 @@ skinparam ClassBackgroundColor #FFFFAA title After command "undo" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "ab0:Mentorstack" + class State2 as "ab1:Mentorstack" } State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFFF +hide State2 -Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoState4.puml similarity index 51% rename from docs/diagrams/UndoRedoState4.puml rename to docs/diagrams/UndoState4.puml index 2bc631ffcd0..c37e8435d77 100644 --- a/docs/diagrams/UndoRedoState4.puml +++ b/docs/diagrams/UndoState4.puml @@ -7,15 +7,12 @@ skinparam ClassBackgroundColor #FFFFAA title After command "list" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" + class State1 as "ab0:Mentorstack" + class State2 as "ab1:Mentorstack" } State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 -class Pointer as "Current State" #FFFFFF +hide State2 -Pointer -up-> State2 @end diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoState5.puml similarity index 73% rename from docs/diagrams/UndoRedoState5.puml rename to docs/diagrams/UndoState5.puml index e77b04104aa..27b4165b2eb 100644 --- a/docs/diagrams/UndoRedoState5.puml +++ b/docs/diagrams/UndoState5.puml @@ -7,9 +7,9 @@ skinparam ClassBackgroundColor #FFFFAA title After command "clear" package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab3:AddressBook" + class State1 as "ab0:Mentorstack" + class State2 as "ab1:Mentorstack" + class State3 as "ab3:Mentorstack" } State1 -[hidden]right-> State2 diff --git a/docs/images/ArchitectureDiagram.png b/docs/images/ArchitectureDiagram.png deleted file mode 100644 index cd540665053..00000000000 Binary files a/docs/images/ArchitectureDiagram.png and /dev/null differ diff --git a/docs/images/ArchitectureSequenceDiagram.png b/docs/images/ArchitectureSequenceDiagram.png deleted file mode 100644 index 37ad06a2803..00000000000 Binary files a/docs/images/ArchitectureSequenceDiagram.png and /dev/null differ diff --git a/docs/images/BetterModelClassDiagram.png b/docs/images/BetterModelClassDiagram.png deleted file mode 100644 index 02a42e35e76..00000000000 Binary files a/docs/images/BetterModelClassDiagram.png and /dev/null differ diff --git a/docs/images/CommitActivityDiagram.png b/docs/images/CommitActivityDiagram.png deleted file mode 100644 index 5b464126b35..00000000000 Binary files a/docs/images/CommitActivityDiagram.png and /dev/null differ diff --git a/docs/images/ComponentManagers.png b/docs/images/ComponentManagers.png deleted file mode 100644 index ae52a35718a..00000000000 Binary files a/docs/images/ComponentManagers.png and /dev/null differ diff --git a/docs/images/DeleteSequenceDiagram.png b/docs/images/DeleteSequenceDiagram.png deleted file mode 100644 index ac2ae217c51..00000000000 Binary files a/docs/images/DeleteSequenceDiagram.png and /dev/null differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png deleted file mode 100644 index fe91c69efe7..00000000000 Binary files a/docs/images/LogicClassDiagram.png and /dev/null differ diff --git a/docs/images/LogicStorageDIP.png b/docs/images/LogicStorageDIP.png deleted file mode 100644 index 871157f5a9c..00000000000 Binary files a/docs/images/LogicStorageDIP.png and /dev/null differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png deleted file mode 100644 index a19fb1b4ac8..00000000000 Binary files a/docs/images/ModelClassDiagram.png and /dev/null differ diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png deleted file mode 100644 index 2caeeb1a067..00000000000 Binary files a/docs/images/ParserClasses.png and /dev/null differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png deleted file mode 100644 index 18fa4d0d51f..00000000000 Binary files a/docs/images/StorageClassDiagram.png and /dev/null differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..d4881408738 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png deleted file mode 100644 index 11f06d68671..00000000000 Binary files a/docs/images/UiClassDiagram.png and /dev/null differ diff --git a/docs/images/UiLight.png b/docs/images/UiLight.png new file mode 100644 index 00000000000..fa92f771dda Binary files /dev/null and b/docs/images/UiLight.png differ diff --git a/docs/images/UiPink.png b/docs/images/UiPink.png new file mode 100644 index 00000000000..350478fc815 Binary files /dev/null and b/docs/images/UiPink.png differ diff --git a/docs/images/UndoRedoState0.png b/docs/images/UndoRedoState0.png deleted file mode 100644 index c5f91b58533..00000000000 Binary files a/docs/images/UndoRedoState0.png and /dev/null differ diff --git a/docs/images/UndoRedoState1.png b/docs/images/UndoRedoState1.png deleted file mode 100644 index 2d3ad09c047..00000000000 Binary files a/docs/images/UndoRedoState1.png and /dev/null differ diff --git a/docs/images/UndoRedoState2.png b/docs/images/UndoRedoState2.png deleted file mode 100644 index 20853694e03..00000000000 Binary files a/docs/images/UndoRedoState2.png and /dev/null differ diff --git a/docs/images/UndoRedoState3.png b/docs/images/UndoRedoState3.png deleted file mode 100644 index 1a9551b31be..00000000000 Binary files a/docs/images/UndoRedoState3.png and /dev/null differ diff --git a/docs/images/UndoRedoState4.png b/docs/images/UndoRedoState4.png deleted file mode 100644 index 46dfae78c94..00000000000 Binary files a/docs/images/UndoRedoState4.png and /dev/null differ diff --git a/docs/images/UndoRedoState5.png b/docs/images/UndoRedoState5.png deleted file mode 100644 index f45889b5fdf..00000000000 Binary files a/docs/images/UndoRedoState5.png and /dev/null differ diff --git a/docs/images/addResult.png b/docs/images/addResult.png new file mode 100644 index 00000000000..2dd794fdf23 Binary files /dev/null and b/docs/images/addResult.png differ diff --git a/docs/images/archiveResult.png b/docs/images/archiveResult.png new file mode 100644 index 00000000000..dfa30f0ada3 Binary files /dev/null and b/docs/images/archiveResult.png differ diff --git a/docs/images/deleteMultipleResult.png b/docs/images/deleteMultipleResult.png new file mode 100644 index 00000000000..2176f27ab7c Binary files /dev/null and b/docs/images/deleteMultipleResult.png differ diff --git a/docs/images/deleteResult.png b/docs/images/deleteResult.png new file mode 100644 index 00000000000..b3635d8bce9 Binary files /dev/null and b/docs/images/deleteResult.png differ diff --git a/docs/images/editResult.png b/docs/images/editResult.png new file mode 100644 index 00000000000..a2a369ae307 Binary files /dev/null and b/docs/images/editResult.png differ diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png index 235da1c273e..8cef059c219 100644 Binary files a/docs/images/findAlexDavidResult.png and b/docs/images/findAlexDavidResult.png differ diff --git a/docs/images/findResult.png b/docs/images/findResult.png new file mode 100644 index 00000000000..2b9978c7c94 Binary files /dev/null and b/docs/images/findResult.png differ diff --git a/docs/images/finishResult.png b/docs/images/finishResult.png new file mode 100644 index 00000000000..a044b469ddf Binary files /dev/null and b/docs/images/finishResult.png differ diff --git a/docs/images/finishResultUG.png b/docs/images/finishResultUG.png new file mode 100644 index 00000000000..b9bebda6925 Binary files /dev/null and b/docs/images/finishResultUG.png differ diff --git a/docs/images/gender.png b/docs/images/gender.png new file mode 100644 index 00000000000..c1edcc3a6f9 Binary files /dev/null and b/docs/images/gender.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..eeb09042230 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/ioubread.png b/docs/images/ioubread.png new file mode 100644 index 00000000000..c139ee2361c Binary files /dev/null and b/docs/images/ioubread.png differ diff --git a/docs/images/markResult.png b/docs/images/markResult.png new file mode 100644 index 00000000000..4fbac2eecf4 Binary files /dev/null and b/docs/images/markResult.png differ diff --git a/docs/images/markResultUG.png b/docs/images/markResultUG.png new file mode 100644 index 00000000000..3e114fff892 Binary files /dev/null and b/docs/images/markResultUG.png differ diff --git a/docs/images/quantin96.png b/docs/images/quantin96.png new file mode 100644 index 00000000000..0998ea46085 Binary files /dev/null and b/docs/images/quantin96.png differ diff --git a/docs/images/sheyuting.png b/docs/images/sheyuting.png new file mode 100644 index 00000000000..4e5cbcb394f Binary files /dev/null and b/docs/images/sheyuting.png differ diff --git a/docs/images/showArchiveResult.png b/docs/images/showArchiveResult.png new file mode 100644 index 00000000000..e93b27d0885 Binary files /dev/null and b/docs/images/showArchiveResult.png differ diff --git a/docs/images/statsResult.png b/docs/images/statsResult.png new file mode 100644 index 00000000000..adf4f43dda3 Binary files /dev/null and b/docs/images/statsResult.png differ diff --git a/docs/images/statsSubjectResult.png b/docs/images/statsSubjectResult.png new file mode 100644 index 00000000000..99af5217b50 Binary files /dev/null and b/docs/images/statsSubjectResult.png differ diff --git a/docs/images/subiloble.png b/docs/images/subiloble.png new file mode 100644 index 00000000000..1a74469650e Binary files /dev/null and b/docs/images/subiloble.png differ diff --git a/docs/images/unarchiveResult.png b/docs/images/unarchiveResult.png new file mode 100644 index 00000000000..174001c78c7 Binary files /dev/null and b/docs/images/unarchiveResult.png differ diff --git a/docs/images/undoResult.png b/docs/images/undoResult.png new file mode 100644 index 00000000000..be0e7f587ac Binary files /dev/null and b/docs/images/undoResult.png differ diff --git a/docs/images/unfinishResult.png b/docs/images/unfinishResult.png new file mode 100644 index 00000000000..6518dfeb4b0 Binary files /dev/null and b/docs/images/unfinishResult.png differ diff --git a/docs/images/unmarkResult.png b/docs/images/unmarkResult.png new file mode 100644 index 00000000000..50a47dbc167 Binary files /dev/null and b/docs/images/unmarkResult.png differ diff --git a/docs/images/viewResult.png b/docs/images/viewResult.png new file mode 100644 index 00000000000..d1a9a8f385d Binary files /dev/null and b/docs/images/viewResult.png differ diff --git a/docs/images/zlllllr.png b/docs/images/zlllllr.png new file mode 100644 index 00000000000..ab05fd9844e Binary files /dev/null and b/docs/images/zlllllr.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..d6b97f29fe3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,19 +1,23 @@ --- -layout: page -title: AddressBook Level-3 + layout: default.md + title: "" --- -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) -[![codecov](https://codecov.io/gh/se-edu/addressbook-level3/branch/master/graph/badge.svg)](https://codecov.io/gh/se-edu/addressbook-level3) +# Mentorstack + +[![CI Status](https://github.com/AY2425S2-CS2103-W11-1/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2425S2-CS2103-W11-1/tp/actions) +[![codecov](https://codecov.io/gh/AY2425S2-CS2103-W11-1/tp/graph/badge.svg?token=6WMHQ8PWP3)](https://codecov.io/gh/AY2425S2-CS2103-W11-1/tp) ![Ui](images/Ui.png) -**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface). +* Mentorstack helps CS tutors efficiently manage and track student contacts, attendance, participation, progress, and streamlines communication. +* It simplifies student management across different levels and courses while catering to tech-savvy users who may prefer a command-line interface (CLI). -* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). -* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. +* If you are interested in using Mentorstack, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing Mentorstack, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** +* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org). * Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5) diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 00000000000..63a232e05dc --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,8587 @@ +{ + "name": "docs", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "docs", + "version": "1.0.0", + "devDependencies": { + "markbind-cli": "^5.1.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz", + "integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==", + "dev": true, + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/file-exists/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@kwsites/file-exists/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true + }, + "node_modules/@markbind/core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@markbind/core/-/core-5.1.0.tgz", + "integrity": "sha512-YAXjH+qCXnrBzpKIAJkayVLmyIUaG/8Dms3Gpd2VIufeZyW8w0diXdgKSsymjzodTMgghZMdxG3Qpng833ARPg==", + "dev": true, + "dependencies": { + "@fortawesome/fontawesome-free": "^6.4.0", + "@markbind/core-web": "5.1.0", + "@primer/octicons": "^15.0.1", + "@sindresorhus/slugify": "^0.9.1", + "@tlylt/markdown-it-imsize": "^3.0.0", + "bluebird": "^3.7.2", + "bootswatch": "5.1.3", + "cheerio": "^0.22.0", + "crypto-js": "^4.0.0", + "csv-parse": "^4.14.2", + "ensure-posix-path": "^1.1.1", + "fastmatter": "^2.1.1", + "fs-extra": "^9.0.1", + "gh-pages": "^2.1.1", + "highlight.js": "^10.4.1", + "htmlparser2": "^3.10.1", + "ignore": "^5.1.4", + "js-beautify": "1.14.3", + "katex": "^0.15.6", + "lodash": "^4.17.15", + "markdown-it": "^12.3.2", + "markdown-it-attrs": "^4.1.3", + "markdown-it-emoji": "^1.4.0", + "markdown-it-linkify-images": "^3.0.0", + "markdown-it-mark": "^3.0.0", + "markdown-it-regexp": "^0.4.0", + "markdown-it-sub": "^1.0.0", + "markdown-it-sup": "^1.0.0", + "markdown-it-table-of-contents": "^0.4.4", + "markdown-it-task-lists": "^2.1.1", + "markdown-it-texmath": "^1.0.0", + "markdown-it-video": "^0.6.3", + "material-icons": "^1.9.1", + "moment": "^2.29.4", + "nunjucks": "3.2.2", + "path-is-inside": "^1.0.2", + "simple-git": "^2.17.0", + "url-parse": "^1.5.10", + "uuid": "^8.3.1", + "vue": "2.6.14", + "vue-server-renderer": "2.6.14", + "vue-template-compiler": "2.6.14", + "walk-sync": "^2.0.2", + "winston": "^2.4.4" + } + }, + "node_modules/@markbind/core-web": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@markbind/core-web/-/core-web-5.1.0.tgz", + "integrity": "sha512-TRzz8ZCr25pylKvFxF/WwXDi4Gbtsb2OLXV61WyTFqVy03tFoEJ2mqncpbliI9DrfDdKWcm1YZPgDCedVkYjKA==", + "dev": true + }, + "node_modules/@primer/octicons": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-15.2.0.tgz", + "integrity": "sha512-4cHZzcZ3F/HQNL4EKSaFyVsW7XtITiJkTeB1JDDmRuP/XobyWyF9gWxuV9c+byUa8dOB5KNQn37iRvNrIehPUQ==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.1" + } + }, + "node_modules/@sindresorhus/slugify": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-0.9.1.tgz", + "integrity": "sha512-b6heYM9dzZD13t2GOiEQTDE0qX+I1GyOotMwKh9VQqzuNiVdPVT8dM43fe9HNb/3ul+Qwd5oKSEDrDIfhq3bnQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5", + "lodash.deburr": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@tlylt/markdown-it-imsize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@tlylt/markdown-it-imsize/-/markdown-it-imsize-3.0.0.tgz", + "integrity": "sha512-6kTM+vRJTuN2UxNPyJ8yC+NHrzS+MxVHV+z+bDxSr/Fd7eTah2+otLKC2B17YI/1lQnSumA2qokPGuzsA98c6g==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/apache-crypt": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.5.tgz", + "integrity": "sha512-ICnYQH+DFVmw+S4Q0QY2XRXD8Ne8ewh8HgbuFH4K7022zCxgHM0Hz1xkRnUlEfAXNbwp1Cnhbedu60USIfDxvg==", + "dev": true, + "dependencies": { + "unix-crypt-td-js": "^1.1.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/apache-md5": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.7.tgz", + "integrity": "sha512-JtHjzZmJxtzfTSjsCyHgPR155HBe5WGyUyHTaEkfy46qhwCFKx1Epm6nAxgUG3WfUZP1dWhGqj9Z2NOBeZ+uBw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/bootswatch": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bootswatch/-/bootswatch-5.1.3.tgz", + "integrity": "sha512-NmZFN6rOCoXWQ/PkzmD8FFWDe24kocX9OXWHNVaLxVVnpqpAzEbMFsf8bAfKwVtpNXibasZCzv09B5fLieAh2g==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cheerio": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==", + "dev": true, + "dependencies": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==", + "dev": true + }, + "node_modules/css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==", + "dev": true, + "dependencies": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "node_modules/css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/csv-parse": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", + "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", + "dev": true + }, + "node_modules/cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "dependencies": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "dev": true, + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/editorconfig": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "dev": true, + "dependencies": { + "commander": "^2.19.0", + "lru-cache": "^4.1.5", + "semver": "^5.6.0", + "sigmund": "^1.0.1" + }, + "bin": { + "editorconfig": "bin/editorconfig" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ensure-posix-path": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz", + "integrity": "sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==", + "dev": true + }, + "node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "node_modules/event-stream/node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/event-stream/node_modules/stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "dev": true, + "engines": { + "node": "> 0.1.90" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "node_modules/fastmatter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fastmatter/-/fastmatter-2.1.1.tgz", + "integrity": "sha512-NFrjZEPJZTexoJEuyM5J7n4uFaLf0dOI7Ok4b2IZXOYBqCp1Bh5RskANmQ2TuDsz3M35B1yL2AP/Rn+kp85KeA==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.0", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through2": "^3.0.1" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==", + "dev": true + }, + "node_modules/figlet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz", + "integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/file-stream-rotator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.4.1.tgz", + "integrity": "sha512-W3aa3QJEc8BS2MmdVpQiYLKHj3ijpto1gMDlsgCRSKfIUe6MwkcpODGPQ3vZfb0XvCeCqlu9CBQTN7oQri2TZQ==", + "dev": true, + "dependencies": { + "moment": "^2.11.2" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "node_modules/filename-reserved-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", + "integrity": "sha512-UZArj7+U+2reBBVCvVmRlyq9D7EYQdUtuNN+1iz7pF1jGcJ2L0TjiRCxsTZfj2xFbM4c25uGCUDpKTHA7L2TKg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/filenamify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", + "integrity": "sha512-DKVP0WQcB7WaIMSwDETqImRej2fepPqvXQjaVib7LRZn9Rxn5UbvK2tYTqGf1A1DkIprQQkG4XSQXSOZp7Q3GQ==", + "dev": true, + "dependencies": { + "filename-reserved-regex": "^1.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/filenamify-url": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz", + "integrity": "sha512-O9K9JcZeF5VdZWM1qR92NSv1WY2EofwudQayPx5dbnnFl9k0IcZha4eV/FGkjnBK+1irOQInij0yiooCHu/0Fg==", + "dev": true, + "dependencies": { + "filenamify": "^1.0.0", + "humanize-url": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-pages": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-2.2.0.tgz", + "integrity": "sha512-c+yPkNOPMFGNisYg9r4qvsMIjVYikJv7ImFOhPIVPt0+AcRUamZ7zkGRLHz7FKB0xrlZ+ddSOJsZv9XAFVXLmA==", + "dev": true, + "dependencies": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify-url": "^1.0.0", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/gh-pages/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/gh-pages/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/gh-pages/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/gh-pages/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/http-auth": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-3.1.3.tgz", + "integrity": "sha512-Jbx0+ejo2IOx+cRUYAGS1z6RGc6JfYUNkysZM4u4Sfk1uLlGv814F7/PIjQQAuThLdAWxb74JMGd5J8zex1VQg==", + "dev": true, + "dependencies": { + "apache-crypt": "^1.1.2", + "apache-md5": "^1.0.6", + "bcryptjs": "^2.3.0", + "uuid": "^3.0.0" + }, + "engines": { + "node": ">=4.6.1" + } + }, + "node_modules/http-auth/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/humanize-url": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz", + "integrity": "sha512-RtgTzXCPVb/te+e82NDhAc5paj+DuKSratIGAr+v+HZK24eAQ8LMoBGYoL7N/O+9iEc33AKHg45dOMKw3DNldQ==", + "dev": true, + "dependencies": { + "normalize-url": "^1.0.0", + "strip-url-auth": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "node_modules/js-beautify": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.3.tgz", + "integrity": "sha512-f1ra8PHtOEu/70EBnmiUlV8nJePS58y9qKjl4JHfYWlFH6bo7ogZBz//FAZp7jDuXtYnGYKymZPlrg2I/9Zo4g==", + "dev": true, + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^0.15.3", + "glob": "^7.1.3", + "nopt": "^5.0.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/katex": { + "version": "0.15.6", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.15.6.tgz", + "integrity": "sha512-UpzJy4yrnqnhXvRPhjEuLA4lcPn6eRngixW7Q3TJErjg3Aw2PuLFBzTkdUb89UtumxjhHTqL3a5GDGETMSwgJA==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.0.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/live-server": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/live-server/-/live-server-1.2.1.tgz", + "integrity": "sha512-Yn2XCVjErTkqnM3FfTmM7/kWy3zP7+cEtC7x6u+wUzlQ+1UW3zEYbbyJrc0jNDwiMDZI0m4a0i3dxlGHVyXczw==", + "dev": true, + "dependencies": { + "chokidar": "^2.0.4", + "colors": "latest", + "connect": "^3.6.6", + "cors": "latest", + "event-stream": "3.3.4", + "faye-websocket": "0.11.x", + "http-auth": "3.1.x", + "morgan": "^1.9.1", + "object-assign": "latest", + "opn": "latest", + "proxy-middleware": "latest", + "send": "latest", + "serve-index": "^1.9.1" + }, + "bin": { + "live-server": "live-server.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/live-server/node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/live-server/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/live-server/node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/live-server/node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/live-server/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/live-server/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/live-server/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/live-server/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==", + "dev": true + }, + "node_modules/lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==", + "dev": true + }, + "node_modules/lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==", + "dev": true + }, + "node_modules/lodash.deburr": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz", + "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true + }, + "node_modules/lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "node_modules/lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==", + "dev": true + }, + "node_modules/lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", + "dev": true + }, + "node_modules/lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==", + "dev": true + }, + "node_modules/lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==", + "dev": true + }, + "node_modules/lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==", + "dev": true + }, + "node_modules/lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "node_modules/lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "dependencies": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "node_modules/logform": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-1.10.0.tgz", + "integrity": "sha512-em5ojIhU18fIMOw/333mD+ZLE2fis0EzXl1ZwHx4iQzmpQi6odNiY/t+ITNr33JZhT9/KEaH+UPIipr6a9EjWg==", + "dev": true, + "dependencies": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.2.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", + "dev": true + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markbind-cli": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/markbind-cli/-/markbind-cli-5.1.0.tgz", + "integrity": "sha512-6POI1Q++2aZa+Udk/oQ6LX1oNPbKUBDY0mN3Up7VOFeK+XYW51faxuCk2Q91JTBxYRKLNtshxf0y12kB4Cj9Qw==", + "dev": true, + "dependencies": { + "@markbind/core": "5.1.0", + "@markbind/core-web": "5.1.0", + "bluebird": "^3.7.2", + "chalk": "^3.0.0", + "cheerio": "^0.22.0", + "chokidar": "^3.3.0", + "colors": "1.4.0", + "commander": "^8.1.0", + "figlet": "^1.2.4", + "find-up": "^4.1.0", + "fs-extra": "^9.0.1", + "live-server": "1.2.1", + "lodash": "^4.17.15", + "url-parse": "^1.5.10", + "winston": "^2.4.4", + "winston-daily-rotate-file": "^3.10.0" + }, + "bin": { + "markbind": "index.js" + } + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-attrs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/markdown-it-attrs/-/markdown-it-attrs-4.1.6.tgz", + "integrity": "sha512-O7PDKZlN8RFMyDX13JnctQompwrrILuz2y43pW2GagcwpIIElkAdfeek+erHfxUOlXWPsjFeWmZ8ch1xtRLWpA==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "markdown-it": ">= 9.0.0" + } + }, + "node_modules/markdown-it-emoji": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz", + "integrity": "sha512-QCz3Hkd+r5gDYtS2xsFXmBYrgw6KuWcJZLCEkdfAuwzZbShCmCfta+hwAMq4NX/4xPzkSHduMKgMkkPUJxSXNg==", + "dev": true + }, + "node_modules/markdown-it-linkify-images": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-linkify-images/-/markdown-it-linkify-images-3.0.0.tgz", + "integrity": "sha512-Vs5yGJa5MWjFgytzgtn8c1U6RcStj3FZKhhx459U8dYbEE5FTWZ6mMRkYMiDlkFO0j4VCsQT1LT557bY0ETgtg==", + "dev": true, + "dependencies": { + "markdown-it": "^13.0.1" + } + }, + "node_modules/markdown-it-linkify-images/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/markdown-it-linkify-images/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/markdown-it-linkify-images/node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/markdown-it-linkify-images/node_modules/markdown-it": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-mark": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz", + "integrity": "sha512-HyxjAu6BRsdt6Xcv6TKVQnkz/E70TdGXEFHRYBGLncRE9lBFwDNLVtFojKxjJWgJ+5XxUwLaHXy+2sGBbDn+4A==", + "dev": true + }, + "node_modules/markdown-it-regexp": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/markdown-it-regexp/-/markdown-it-regexp-0.4.0.tgz", + "integrity": "sha512-0XQmr46K/rMKnI93Y3CLXsHj4jIioRETTAiVnJnjrZCEkGaDOmUxTbZj/aZ17G5NlRcVpWBYjqpwSlQ9lj+Kxw==", + "dev": true + }, + "node_modules/markdown-it-sub": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz", + "integrity": "sha512-z2Rm/LzEE1wzwTSDrI+FlPEveAAbgdAdPhdWarq/ZGJrGW/uCQbKAnhoCsE4hAbc3SEym26+W2z/VQB0cQiA9Q==", + "dev": true + }, + "node_modules/markdown-it-sup": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz", + "integrity": "sha512-E32m0nV9iyhRR7CrhnzL5msqic7rL1juWre6TQNxsnApg7Uf+F97JOKxUijg5YwXz86lZ0mqfOnutoryyNdntQ==", + "dev": true + }, + "node_modules/markdown-it-table-of-contents": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.4.tgz", + "integrity": "sha512-TAIHTHPwa9+ltKvKPWulm/beozQU41Ab+FIefRaQV1NRnpzwcV9QOe6wXQS5WLivm5Q/nlo0rl6laGkMDZE7Gw==", + "dev": true, + "engines": { + "node": ">6.4.0" + } + }, + "node_modules/markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", + "dev": true + }, + "node_modules/markdown-it-texmath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-texmath/-/markdown-it-texmath-1.0.0.tgz", + "integrity": "sha512-4hhkiX8/gus+6e53PLCUmUrsa6ZWGgJW2XCW6O0ASvZUiezIK900ZicinTDtG3kAO2kon7oUA/ReWmpW2FByxg==", + "dev": true + }, + "node_modules/markdown-it-video": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/markdown-it-video/-/markdown-it-video-0.6.3.tgz", + "integrity": "sha512-T4th1kwy0OcvyWSN4u3rqPGxvbDclpucnVSSaH3ZacbGsAts964dxokx9s/I3GYsrDCJs4ogtEeEeVP18DQj0Q==", + "dev": true + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/matcher-collection": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-2.0.1.tgz", + "integrity": "sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==", + "dev": true, + "dependencies": { + "@types/minimatch": "^3.0.3", + "minimatch": "^3.0.2" + }, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/material-icons": { + "version": "1.13.11", + "resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.11.tgz", + "integrity": "sha512-kp2oAdaqo/Zp6hpTZW01rOgDPWmxBUszSdDzkRm1idCjjNvdUMnqu8qu58cll6CObo+o0cydOiPLdoSugLm+mQ==", + "dev": true + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/nan": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", + "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", + "dev": true, + "optional": true + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha512-A48My/mtCklowHBlI8Fq2jFWK4tX4lJ5E6ytFsSOq1fzpvT0SQSgKhSg7lN5c2uYFOrUAOQp6zhhJnpp1eMloQ==", + "dev": true, + "dependencies": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/nunjucks": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.2.tgz", + "integrity": "sha512-KUi85OoF2NMygwODAy28Lh9qHmq5hO3rBlbkYoC8v377h4l8Pt5qFjILl0LWpMbOrZ18CzfVVUvIHUIrtED3sA==", + "dev": true, + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "optionalDependencies": { + "chokidar": "^3.3.0" + } + }, + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/opn": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-6.0.0.tgz", + "integrity": "sha512-I9PKfIZC+e4RXZ/qr1RhgyCnGgYX0UEIlXgWnCOVACIvFgaC9rz6Won7xbdhoHrd8IIhV7YEpHjreNUNkqCGkQ==", + "deprecated": "The package has been renamed to `open`", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "dev": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dev": true, + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/proxy-middleware": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", + "integrity": "sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", + "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", + "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==", + "dev": true + }, + "node_modules/simple-git": { + "version": "2.48.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.48.0.tgz", + "integrity": "sha512-z4qtrRuaAFJS4PUd0g+xy7aN4y+RvEt/QTJpR184lhJguBA1S/LsVlvE/CM95RsYMOFJG3NGGDjqFCzKU19S/A==", + "dev": true, + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/steveukx/" + } + }, + "node_modules/simple-git/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/simple-git/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-combiner": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "through": "~2.3.4" + } + }, + "node_modules/strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-url-auth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz", + "integrity": "sha512-++41PnXftlL3pvI6lpvhSEO+89g1kIJC4MYB5E6yH+WHa5InIqz51yGd1YOGd7VNSNdoEOfzTMqbAM/2PbgaHQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==", + "dev": true + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unix-crypt-td-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", + "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vue": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", + "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==", + "dev": true + }, + "node_modules/vue-server-renderer": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.14.tgz", + "integrity": "sha512-HifYRa/LW7cKywg9gd4ZtvtRuBlstQBao5ZCWlg40fyB4OPoGfEXAzxb0emSLv4pBDOHYx0UjpqvxpiQFEuoLA==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "hash-sum": "^1.0.2", + "he": "^1.1.0", + "lodash.template": "^4.5.0", + "lodash.uniq": "^4.5.0", + "resolve": "^1.2.0", + "serialize-javascript": "^3.1.0", + "source-map": "0.5.6" + } + }, + "node_modules/vue-server-renderer/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vue-server-renderer/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vue-server-renderer/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/vue-template-compiler": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", + "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.1.0" + } + }, + "node_modules/walk-sync": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-2.2.0.tgz", + "integrity": "sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg==", + "dev": true, + "dependencies": { + "@types/minimatch": "^3.0.3", + "ensure-posix-path": "^1.1.0", + "matcher-collection": "^2.0.0", + "minimatch": "^3.0.4" + }, + "engines": { + "node": "8.* || >= 10.*" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/winston": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.6.tgz", + "integrity": "sha512-J5Zu4p0tojLde8mIOyDSsmLmcP8I3Z6wtwpTDHx1+hGcdhxcJaAmG4CFtagkb+NiN1M9Ek4b42pzMWqfc9jm8w==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/winston-compat": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/winston-compat/-/winston-compat-0.1.5.tgz", + "integrity": "sha512-EPvPcHT604AV3Ji6d3+vX8ENKIml9VSxMRnPQ+cuK/FX6f3hvPP2hxyoeeCOCFvDrJEujalfcKWlWPvAnFyS9g==", + "dev": true, + "dependencies": { + "cycle": "~1.0.3", + "logform": "^1.6.0", + "triple-beam": "^1.2.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston-daily-rotate-file": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-3.10.0.tgz", + "integrity": "sha512-KO8CfbI2CvdR3PaFApEH02GPXiwJ+vbkF1mCkTlvRIoXFI8EFlf1ACcuaahXTEiDEKCii6cNe95gsL4ZkbnphA==", + "dev": true, + "dependencies": { + "file-stream-rotator": "^0.4.1", + "object-hash": "^1.3.0", + "semver": "^6.2.0", + "triple-beam": "^1.3.0", + "winston-compat": "^0.1.4", + "winston-transport": "^4.2.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "winston": "^2 || ^3" + } + }, + "node_modules/winston-daily-rotate-file/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "dev": true, + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston-transport/node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "dev": true + }, + "node_modules/winston-transport/node_modules/logform": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", + "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, + "node_modules/winston-transport/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/winston/node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "node_modules/winston/node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + }, + "dependencies": { + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true + }, + "@fortawesome/fontawesome-free": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz", + "integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==", + "dev": true + }, + "@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "requires": { + "debug": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true + }, + "@markbind/core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@markbind/core/-/core-5.1.0.tgz", + "integrity": "sha512-YAXjH+qCXnrBzpKIAJkayVLmyIUaG/8Dms3Gpd2VIufeZyW8w0diXdgKSsymjzodTMgghZMdxG3Qpng833ARPg==", + "dev": true, + "requires": { + "@fortawesome/fontawesome-free": "^6.4.0", + "@markbind/core-web": "5.1.0", + "@primer/octicons": "^15.0.1", + "@sindresorhus/slugify": "^0.9.1", + "@tlylt/markdown-it-imsize": "^3.0.0", + "bluebird": "^3.7.2", + "bootswatch": "5.1.3", + "cheerio": "^0.22.0", + "crypto-js": "^4.0.0", + "csv-parse": "^4.14.2", + "ensure-posix-path": "^1.1.1", + "fastmatter": "^2.1.1", + "fs-extra": "^9.0.1", + "gh-pages": "^2.1.1", + "highlight.js": "^10.4.1", + "htmlparser2": "^3.10.1", + "ignore": "^5.1.4", + "js-beautify": "1.14.3", + "katex": "^0.15.6", + "lodash": "^4.17.15", + "markdown-it": "^12.3.2", + "markdown-it-attrs": "^4.1.3", + "markdown-it-emoji": "^1.4.0", + "markdown-it-linkify-images": "^3.0.0", + "markdown-it-mark": "^3.0.0", + "markdown-it-regexp": "^0.4.0", + "markdown-it-sub": "^1.0.0", + "markdown-it-sup": "^1.0.0", + "markdown-it-table-of-contents": "^0.4.4", + "markdown-it-task-lists": "^2.1.1", + "markdown-it-texmath": "^1.0.0", + "markdown-it-video": "^0.6.3", + "material-icons": "^1.9.1", + "moment": "^2.29.4", + "nunjucks": "3.2.2", + "path-is-inside": "^1.0.2", + "simple-git": "^2.17.0", + "url-parse": "^1.5.10", + "uuid": "^8.3.1", + "vue": "2.6.14", + "vue-server-renderer": "2.6.14", + "vue-template-compiler": "2.6.14", + "walk-sync": "^2.0.2", + "winston": "^2.4.4" + } + }, + "@markbind/core-web": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@markbind/core-web/-/core-web-5.1.0.tgz", + "integrity": "sha512-TRzz8ZCr25pylKvFxF/WwXDi4Gbtsb2OLXV61WyTFqVy03tFoEJ2mqncpbliI9DrfDdKWcm1YZPgDCedVkYjKA==", + "dev": true + }, + "@primer/octicons": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-15.2.0.tgz", + "integrity": "sha512-4cHZzcZ3F/HQNL4EKSaFyVsW7XtITiJkTeB1JDDmRuP/XobyWyF9gWxuV9c+byUa8dOB5KNQn37iRvNrIehPUQ==", + "dev": true, + "requires": { + "object-assign": "^4.1.1" + } + }, + "@sindresorhus/slugify": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-0.9.1.tgz", + "integrity": "sha512-b6heYM9dzZD13t2GOiEQTDE0qX+I1GyOotMwKh9VQqzuNiVdPVT8dM43fe9HNb/3ul+Qwd5oKSEDrDIfhq3bnQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "lodash.deburr": "^4.1.0" + } + }, + "@tlylt/markdown-it-imsize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@tlylt/markdown-it-imsize/-/markdown-it-imsize-3.0.0.tgz", + "integrity": "sha512-6kTM+vRJTuN2UxNPyJ8yC+NHrzS+MxVHV+z+bDxSr/Fd7eTah2+otLKC2B17YI/1lQnSumA2qokPGuzsA98c6g==", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "apache-crypt": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/apache-crypt/-/apache-crypt-1.2.5.tgz", + "integrity": "sha512-ICnYQH+DFVmw+S4Q0QY2XRXD8Ne8ewh8HgbuFH4K7022zCxgHM0Hz1xkRnUlEfAXNbwp1Cnhbedu60USIfDxvg==", + "dev": true, + "requires": { + "unix-crypt-td-js": "^1.1.4" + } + }, + "apache-md5": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.7.tgz", + "integrity": "sha512-JtHjzZmJxtzfTSjsCyHgPR155HBe5WGyUyHTaEkfy46qhwCFKx1Epm6nAxgUG3WfUZP1dWhGqj9Z2NOBeZ+uBw==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "bootswatch": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bootswatch/-/bootswatch-5.1.3.tgz", + "integrity": "sha512-NmZFN6rOCoXWQ/PkzmD8FFWDe24kocX9OXWHNVaLxVVnpqpAzEbMFsf8bAfKwVtpNXibasZCzv09B5fLieAh2g==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cheerio": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha512-8/MzidM6G/TgRelkzDG13y3Y9LxBjCb+8yOEZ9+wwq5gVF2w2pV0wmHvjfT0RvuxGyR7UEuK36r+yYMbT4uKgA==", + "dev": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "dev": true + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==", + "dev": true + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, + "csv-parse": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", + "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", + "dev": true + }, + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "dev": true + }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", + "dev": true + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "editorconfig": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "dev": true, + "requires": { + "commander": "^2.19.0", + "lru-cache": "^4.1.5", + "semver": "^5.6.0", + "sigmund": "^1.0.1" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, + "ensure-posix-path": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz", + "integrity": "sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==", + "dev": true + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + }, + "dependencies": { + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dev": true, + "requires": { + "through": "2" + } + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "dev": true, + "requires": { + "duplexer": "~0.1.1" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "dev": true + }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, + "fastmatter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fastmatter/-/fastmatter-2.1.1.tgz", + "integrity": "sha512-NFrjZEPJZTexoJEuyM5J7n4uFaLf0dOI7Ok4b2IZXOYBqCp1Bh5RskANmQ2TuDsz3M35B1yL2AP/Rn+kp85KeA==", + "dev": true, + "requires": { + "js-yaml": "^3.13.0", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through2": "^3.0.1" + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==", + "dev": true + }, + "figlet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz", + "integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==", + "dev": true + }, + "file-stream-rotator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.4.1.tgz", + "integrity": "sha512-W3aa3QJEc8BS2MmdVpQiYLKHj3ijpto1gMDlsgCRSKfIUe6MwkcpODGPQ3vZfb0XvCeCqlu9CBQTN7oQri2TZQ==", + "dev": true, + "requires": { + "moment": "^2.11.2" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "filename-reserved-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", + "integrity": "sha512-UZArj7+U+2reBBVCvVmRlyq9D7EYQdUtuNN+1iz7pF1jGcJ2L0TjiRCxsTZfj2xFbM4c25uGCUDpKTHA7L2TKg==", + "dev": true + }, + "filenamify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", + "integrity": "sha512-DKVP0WQcB7WaIMSwDETqImRej2fepPqvXQjaVib7LRZn9Rxn5UbvK2tYTqGf1A1DkIprQQkG4XSQXSOZp7Q3GQ==", + "dev": true, + "requires": { + "filename-reserved-regex": "^1.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + } + }, + "filenamify-url": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz", + "integrity": "sha512-O9K9JcZeF5VdZWM1qR92NSv1WY2EofwudQayPx5dbnnFl9k0IcZha4eV/FGkjnBK+1irOQInij0yiooCHu/0Fg==", + "dev": true, + "requires": { + "filenamify": "^1.0.0", + "humanize-url": "^1.0.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "dev": true + }, + "gh-pages": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-2.2.0.tgz", + "integrity": "sha512-c+yPkNOPMFGNisYg9r4qvsMIjVYikJv7ImFOhPIVPt0+AcRUamZ7zkGRLHz7FKB0xrlZ+ddSOJsZv9XAFVXLmA==", + "dev": true, + "requires": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify-url": "^1.0.0", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "http-auth": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/http-auth/-/http-auth-3.1.3.tgz", + "integrity": "sha512-Jbx0+ejo2IOx+cRUYAGS1z6RGc6JfYUNkysZM4u4Sfk1uLlGv814F7/PIjQQAuThLdAWxb74JMGd5J8zex1VQg==", + "dev": true, + "requires": { + "apache-crypt": "^1.1.2", + "apache-md5": "^1.0.6", + "bcryptjs": "^2.3.0", + "uuid": "^3.0.0" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "dependencies": { + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "humanize-url": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz", + "integrity": "sha512-RtgTzXCPVb/te+e82NDhAc5paj+DuKSratIGAr+v+HZK24eAQ8LMoBGYoL7N/O+9iEc33AKHg45dOMKw3DNldQ==", + "dev": true, + "requires": { + "normalize-url": "^1.0.0", + "strip-url-auth": "^1.0.0" + } + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "js-beautify": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.3.tgz", + "integrity": "sha512-f1ra8PHtOEu/70EBnmiUlV8nJePS58y9qKjl4JHfYWlFH6bo7ogZBz//FAZp7jDuXtYnGYKymZPlrg2I/9Zo4g==", + "dev": true, + "requires": { + "config-chain": "^1.1.13", + "editorconfig": "^0.15.3", + "glob": "^7.1.3", + "nopt": "^5.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "katex": { + "version": "0.15.6", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.15.6.tgz", + "integrity": "sha512-UpzJy4yrnqnhXvRPhjEuLA4lcPn6eRngixW7Q3TJErjg3Aw2PuLFBzTkdUb89UtumxjhHTqL3a5GDGETMSwgJA==", + "dev": true, + "requires": { + "commander": "^8.0.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "live-server": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/live-server/-/live-server-1.2.1.tgz", + "integrity": "sha512-Yn2XCVjErTkqnM3FfTmM7/kWy3zP7+cEtC7x6u+wUzlQ+1UW3zEYbbyJrc0jNDwiMDZI0m4a0i3dxlGHVyXczw==", + "dev": true, + "requires": { + "chokidar": "^2.0.4", + "colors": "latest", + "connect": "^3.6.6", + "cors": "latest", + "event-stream": "3.3.4", + "faye-websocket": "0.11.x", + "http-auth": "3.1.x", + "morgan": "^1.9.1", + "object-assign": "latest", + "opn": "latest", + "proxy-middleware": "latest", + "send": "latest", + "serve-index": "^1.9.1" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==", + "dev": true + }, + "lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg==", + "dev": true + }, + "lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha512-lxdsn7xxlCymgLYo1gGvVrfHmkjDiyqVv62FAeF2i5ta72BipE1SLxw8hPEPLhD4/247Ijw07UQH7Hq/chT5LA==", + "dev": true + }, + "lodash.deburr": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz", + "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==", + "dev": true + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true + }, + "lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==", + "dev": true + }, + "lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==", + "dev": true + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", + "dev": true + }, + "lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==", + "dev": true + }, + "lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha512-qkTuvgEzYdyhiJBx42YPzPo71R1aEr0z79kAv7Ixg8wPFEjgRgJdUsGMG3Hf3OYSF/kHI79XhNlt+5Ar6OzwxQ==", + "dev": true + }, + "lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==", + "dev": true + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "logform": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-1.10.0.tgz", + "integrity": "sha512-em5ojIhU18fIMOw/333mD+ZLE2fis0EzXl1ZwHx4iQzmpQi6odNiY/t+ITNr33JZhT9/KEaH+UPIipr6a9EjWg==", + "dev": true, + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.2.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "markbind-cli": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/markbind-cli/-/markbind-cli-5.1.0.tgz", + "integrity": "sha512-6POI1Q++2aZa+Udk/oQ6LX1oNPbKUBDY0mN3Up7VOFeK+XYW51faxuCk2Q91JTBxYRKLNtshxf0y12kB4Cj9Qw==", + "dev": true, + "requires": { + "@markbind/core": "5.1.0", + "@markbind/core-web": "5.1.0", + "bluebird": "^3.7.2", + "chalk": "^3.0.0", + "cheerio": "^0.22.0", + "chokidar": "^3.3.0", + "colors": "1.4.0", + "commander": "^8.1.0", + "figlet": "^1.2.4", + "find-up": "^4.1.0", + "fs-extra": "^9.0.1", + "live-server": "1.2.1", + "lodash": "^4.17.15", + "url-parse": "^1.5.10", + "winston": "^2.4.4", + "winston-daily-rotate-file": "^3.10.0" + } + }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true + } + } + }, + "markdown-it-attrs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/markdown-it-attrs/-/markdown-it-attrs-4.1.6.tgz", + "integrity": "sha512-O7PDKZlN8RFMyDX13JnctQompwrrILuz2y43pW2GagcwpIIElkAdfeek+erHfxUOlXWPsjFeWmZ8ch1xtRLWpA==", + "dev": true, + "requires": {} + }, + "markdown-it-emoji": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz", + "integrity": "sha512-QCz3Hkd+r5gDYtS2xsFXmBYrgw6KuWcJZLCEkdfAuwzZbShCmCfta+hwAMq4NX/4xPzkSHduMKgMkkPUJxSXNg==", + "dev": true + }, + "markdown-it-linkify-images": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-linkify-images/-/markdown-it-linkify-images-3.0.0.tgz", + "integrity": "sha512-Vs5yGJa5MWjFgytzgtn8c1U6RcStj3FZKhhx459U8dYbEE5FTWZ6mMRkYMiDlkFO0j4VCsQT1LT557bY0ETgtg==", + "dev": true, + "requires": { + "markdown-it": "^13.0.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true + }, + "linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, + "markdown-it": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + } + } + }, + "markdown-it-mark": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz", + "integrity": "sha512-HyxjAu6BRsdt6Xcv6TKVQnkz/E70TdGXEFHRYBGLncRE9lBFwDNLVtFojKxjJWgJ+5XxUwLaHXy+2sGBbDn+4A==", + "dev": true + }, + "markdown-it-regexp": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/markdown-it-regexp/-/markdown-it-regexp-0.4.0.tgz", + "integrity": "sha512-0XQmr46K/rMKnI93Y3CLXsHj4jIioRETTAiVnJnjrZCEkGaDOmUxTbZj/aZ17G5NlRcVpWBYjqpwSlQ9lj+Kxw==", + "dev": true + }, + "markdown-it-sub": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz", + "integrity": "sha512-z2Rm/LzEE1wzwTSDrI+FlPEveAAbgdAdPhdWarq/ZGJrGW/uCQbKAnhoCsE4hAbc3SEym26+W2z/VQB0cQiA9Q==", + "dev": true + }, + "markdown-it-sup": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz", + "integrity": "sha512-E32m0nV9iyhRR7CrhnzL5msqic7rL1juWre6TQNxsnApg7Uf+F97JOKxUijg5YwXz86lZ0mqfOnutoryyNdntQ==", + "dev": true + }, + "markdown-it-table-of-contents": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/markdown-it-table-of-contents/-/markdown-it-table-of-contents-0.4.4.tgz", + "integrity": "sha512-TAIHTHPwa9+ltKvKPWulm/beozQU41Ab+FIefRaQV1NRnpzwcV9QOe6wXQS5WLivm5Q/nlo0rl6laGkMDZE7Gw==", + "dev": true + }, + "markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", + "dev": true + }, + "markdown-it-texmath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-texmath/-/markdown-it-texmath-1.0.0.tgz", + "integrity": "sha512-4hhkiX8/gus+6e53PLCUmUrsa6ZWGgJW2XCW6O0ASvZUiezIK900ZicinTDtG3kAO2kon7oUA/ReWmpW2FByxg==", + "dev": true + }, + "markdown-it-video": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/markdown-it-video/-/markdown-it-video-0.6.3.tgz", + "integrity": "sha512-T4th1kwy0OcvyWSN4u3rqPGxvbDclpucnVSSaH3ZacbGsAts964dxokx9s/I3GYsrDCJs4ogtEeEeVP18DQj0Q==", + "dev": true + }, + "matcher-collection": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-2.0.1.tgz", + "integrity": "sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ==", + "dev": true, + "requires": { + "@types/minimatch": "^3.0.3", + "minimatch": "^3.0.2" + } + }, + "material-icons": { + "version": "1.13.11", + "resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.11.tgz", + "integrity": "sha512-kp2oAdaqo/Zp6hpTZW01rOgDPWmxBUszSdDzkRm1idCjjNvdUMnqu8qu58cll6CObo+o0cydOiPLdoSugLm+mQ==", + "dev": true + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + } + }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "nan": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", + "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true + }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha512-A48My/mtCklowHBlI8Fq2jFWK4tX4lJ5E6ytFsSOq1fzpvT0SQSgKhSg7lN5c2uYFOrUAOQp6zhhJnpp1eMloQ==", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "nunjucks": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.2.tgz", + "integrity": "sha512-KUi85OoF2NMygwODAy28Lh9qHmq5hO3rBlbkYoC8v377h4l8Pt5qFjILl0LWpMbOrZ18CzfVVUvIHUIrtED3sA==", + "dev": true, + "requires": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "chokidar": "^3.3.0", + "commander": "^5.1.0" + }, + "dependencies": { + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true + } + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opn": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-6.0.0.tgz", + "integrity": "sha512-I9PKfIZC+e4RXZ/qr1RhgyCnGgYX0UEIlXgWnCOVACIvFgaC9rz6Won7xbdhoHrd8IIhV7YEpHjreNUNkqCGkQ==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dev": true, + "requires": { + "through": "~2.3" + } + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "proxy-middleware": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz", + "integrity": "sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha512-O2XLNDBIg1DnTOa+2XrIwSiXEV8h2KImXUnjhhn2+UsvZ+Es2uyd5CCRTNQlDGbzUQOW3aYCBx9rVA6dzsiY7Q==", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true + }, + "repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dev": true, + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safe-stable-stringify": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", + "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==", + "dev": true + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", + "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==", + "dev": true + }, + "simple-git": { + "version": "2.48.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.48.0.tgz", + "integrity": "sha512-z4qtrRuaAFJS4PUd0g+xy7aN4y+RvEt/QTJpR184lhJguBA1S/LsVlvE/CM95RsYMOFJG3NGGDjqFCzKU19S/A==", + "dev": true, + "requires": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.2" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "dev": true + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true + }, + "stream-combiner": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "through": "~2.3.4" + } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "strip-url-auth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz", + "integrity": "sha512-++41PnXftlL3pvI6lpvhSEO+89g1kIJC4MYB5E6yH+WHa5InIqz51yGd1YOGd7VNSNdoEOfzTMqbAM/2PbgaHQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==", + "dev": true + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + } + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "unix-crypt-td-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.1.4.tgz", + "integrity": "sha512-8rMeVYWSIyccIJscb9NdCfZKSRBKYTeVnwmiRYT2ulE3qd1RaDQ0xQDP+rI3ccIWbhu/zuo5cgN8z73belNZgw==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "dev": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "dev": true + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true + }, + "vue": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz", + "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==", + "dev": true + }, + "vue-server-renderer": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.14.tgz", + "integrity": "sha512-HifYRa/LW7cKywg9gd4ZtvtRuBlstQBao5ZCWlg40fyB4OPoGfEXAzxb0emSLv4pBDOHYx0UjpqvxpiQFEuoLA==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "hash-sum": "^1.0.2", + "he": "^1.1.0", + "lodash.template": "^4.5.0", + "lodash.uniq": "^4.5.0", + "resolve": "^1.2.0", + "serialize-javascript": "^3.1.0", + "source-map": "0.5.6" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + } + } + }, + "vue-template-compiler": { + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz", + "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==", + "dev": true, + "requires": { + "de-indent": "^1.0.2", + "he": "^1.1.0" + } + }, + "walk-sync": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-2.2.0.tgz", + "integrity": "sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg==", + "dev": true, + "requires": { + "@types/minimatch": "^3.0.3", + "ensure-posix-path": "^1.1.0", + "matcher-collection": "^2.0.0", + "minimatch": "^3.0.4" + } + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true + }, + "winston": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.6.tgz", + "integrity": "sha512-J5Zu4p0tojLde8mIOyDSsmLmcP8I3Z6wtwpTDHx1+hGcdhxcJaAmG4CFtagkb+NiN1M9Ek4b42pzMWqfc9jm8w==", + "dev": true, + "requires": { + "async": "^3.2.3", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + }, + "dependencies": { + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true + } + } + }, + "winston-compat": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/winston-compat/-/winston-compat-0.1.5.tgz", + "integrity": "sha512-EPvPcHT604AV3Ji6d3+vX8ENKIml9VSxMRnPQ+cuK/FX6f3hvPP2hxyoeeCOCFvDrJEujalfcKWlWPvAnFyS9g==", + "dev": true, + "requires": { + "cycle": "~1.0.3", + "logform": "^1.6.0", + "triple-beam": "^1.2.0" + } + }, + "winston-daily-rotate-file": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-3.10.0.tgz", + "integrity": "sha512-KO8CfbI2CvdR3PaFApEH02GPXiwJ+vbkF1mCkTlvRIoXFI8EFlf1ACcuaahXTEiDEKCii6cNe95gsL4ZkbnphA==", + "dev": true, + "requires": { + "file-stream-rotator": "^0.4.1", + "object-hash": "^1.3.0", + "semver": "^6.2.0", + "triple-beam": "^1.3.0", + "winston-compat": "^0.1.4", + "winston-transport": "^4.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "dev": true, + "requires": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "dev": true + }, + "logform": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", + "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 00000000000..aa7083fd8a7 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,14 @@ +{ + "name": "docs", + "version": "1.0.0", + "description": "AB-3 docs", + "scripts": { + "init": "markbind init", + "build": "markbind build", + "serve": "markbind serve", + "deploy": "markbind deploy" + }, + "devDependencies": { + "markbind-cli": "^5.1.0" + } +} diff --git a/docs/site.json b/docs/site.json new file mode 100644 index 00000000000..a2d69673d44 --- /dev/null +++ b/docs/site.json @@ -0,0 +1,29 @@ +{ + "baseUrl": "", + "titlePrefix": "Mentorstack", + "titleSuffix": "AddressBook Level-3", + "faviconPath": "images/SeEduLogo.png", + "style": { + "codeTheme": "light" + }, + "ignore": [ + "_markbind/layouts/*", + "_markbind/logs/*", + "_site/*", + "site.json", + "*.md", + "*.njk", + ".git/*", + "node_modules/*" + ], + "pagesExclude": ["node_modules/*"], + "pages": [ + { + "glob": ["**/index.md", "**/*.md"] + } + ], + "deploy": { + "message": "Site Update." + }, + "timeZone": "Asia/Singapore" +} diff --git a/docs/stylesheets/main.css b/docs/stylesheets/main.css new file mode 100644 index 00000000000..ba6f8385d2d --- /dev/null +++ b/docs/stylesheets/main.css @@ -0,0 +1,170 @@ +mark { + background-color: #ff0; + border-radius: 5px; + padding-top: 0; + padding-bottom: 0; +} + +.indented { + padding-left: 20px; +} + +.theme-card img { + width: 100%; +} + +/* Scrollbar */ + +.slim-scroll::-webkit-scrollbar { + width: 5px; +} + +.slim-scroll::-webkit-scrollbar-thumb { + background: #808080; + border-radius: 20px; +} + +.slim-scroll::-webkit-scrollbar-track { + background: transparent; + border-radius: 20px; +} + +.slim-scroll-blue::-webkit-scrollbar { + width: 5px; +} + +.slim-scroll-blue::-webkit-scrollbar-thumb { + background: #00b0ef; + border-radius: 20px; +} + +.slim-scroll-blue::-webkit-scrollbar-track { + background: transparent; + border-radius: 20px; +} + +/* Layout containers */ + +#flex-body { + display: flex; + flex: 1; + align-items: start; +} + +#content-wrapper { + flex: 1; + margin: 0 auto; + min-width: 0; + max-width: 1000px; + overflow-x: auto; + padding: 0.8rem 20px 0 20px; + transition: 0.4s; + -webkit-transition: 0.4s; +} + +#site-nav, +#page-nav { + display: flex; + flex-direction: column; + position: sticky; + top: var(--sticky-header-height); + flex: 0 0 auto; + max-width: 300px; + max-height: calc(100vh - var(--sticky-header-height)); + width: 300px; +} + +#site-nav { + border-right: 1px solid lightgrey; + padding-bottom: 20px; + z-index: 999; +} + +.site-nav-top { + margin: 0.8rem 0; + padding: 0 12px 12px 12px; +} + +.nav-component { + overflow-y: auto; +} + +#page-nav { + border-left: 1px solid lightgrey; +} + +@media screen and (max-width: 1299.98px) { + #page-nav { + display: none; + } +} + +/* Bootstrap medium(md) responsive breakpoint */ +@media screen and (max-width: 991.98px) { + #site-nav { + display: none; + } +} + +/* Bootstrap small(sm) responsive breakpoint */ +@media (max-width: 767.98px) { + .indented { + padding-left: 10px; + } + + #content-wrapper { + padding: 0 10px; + } +} + +/* Bootstrap extra small(xs) responsive breakpoint */ +@media screen and (max-width: 575.98px) { + #site-nav { + display: none; + } +} + +/* Hide site navigation when printing */ +@media print { + #site-nav { + display: none; + } + + #page-nav { + display: none; + } + + /* Reduce font size when printing */ + h1 { + font-size: 1.2rem !important; + } + h2 { + font-size: 1.0rem !important; + } + h3 { + font-size: 0.9rem !important; + } + h4 { + font-size: 0.8rem !important; + } + h5 { + font-size: 0.7rem !important; + } + body { + font-size: 0.65rem !important; + } + .btn { + font-size: 0.65rem !important; + } + img { + zoom: 0.8; /* might not work on some browsers */ + } +} + +h2, +h3, +h4, +h5, +h6 { + color: #e46c0a; +} diff --git a/docs/team/johndoe.md b/docs/team/ioubread.md similarity index 87% rename from docs/team/johndoe.md rename to docs/team/ioubread.md index 773a07794e2..be9bbed40a6 100644 --- a/docs/team/johndoe.md +++ b/docs/team/ioubread.md @@ -1,11 +1,11 @@ --- -layout: page -title: John Doe's Project Portfolio Page + layout: default.md + title: "Willie Tay's Project Portfolio Page" --- -### Project: AddressBook Level 3 +### Project: Mentorstack -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. +Mentorstack is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. Given below are my contributions to the project. diff --git a/docs/team/quantin96.md b/docs/team/quantin96.md new file mode 100644 index 00000000000..4a825697d5e --- /dev/null +++ b/docs/team/quantin96.md @@ -0,0 +1,47 @@ +--- + layout: default.md + title: "Ng Jia Quan's Project Portfolio Page" +--- + +### Project: Mentorstack + +Mentorstack is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to undo/redo previous commands. + * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. + * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* + +* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. + +* **Code contributed**: [RepoSense link]() + +* **Project management**: + * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + +* **Enhancements to existing features**: + * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) + * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) + +* **Documentation**: + * User Guide: + * Added documentation for the features `delete` and `find` [\#72]() + * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() + * Developer Guide: + * Added implementation details of the `delete` feature. + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() + * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) + * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) + * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) + +* **Tools**: + * Integrated a third party library (Natty) to the project ([\#42]()) + * Integrated a new Github plugin (CircleCI) to the team repo + +* _{you can add/remove categories in the list above}_ + diff --git a/docs/team/sheyuting.md b/docs/team/sheyuting.md new file mode 100644 index 00000000000..2ef8b955a3f --- /dev/null +++ b/docs/team/sheyuting.md @@ -0,0 +1,46 @@ +--- + layout: default.md + title: "She Yuting's Project Portfolio Page" +--- + +### Project: Mentorstack + +Mentorstack is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to undo/redo previous commands. + * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. + * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* + +* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. + +* **Code contributed**: [RepoSense link]() + +* **Project management**: + * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + +* **Enhancements to existing features**: + * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) + * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) + +* **Documentation**: + * User Guide: + * Added documentation for the features `delete` and `find` [\#72]() + * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() + * Developer Guide: + * Added implementation details of the `delete` feature. + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() + * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) + * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) + * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) + +* **Tools**: + * Integrated a third party library (Natty) to the project ([\#42]()) + * Integrated a new Github plugin (CircleCI) to the team repo + +* _{you can add/remove categories in the list above}_ diff --git a/docs/team/subiloble.md b/docs/team/subiloble.md new file mode 100644 index 00000000000..0e78e0db4f2 --- /dev/null +++ b/docs/team/subiloble.md @@ -0,0 +1,46 @@ +--- + layout: default.md + title: "Xinpeng's Project Portfolio Page" +--- + +### Project: Mentorstack + +Mentorstack is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to undo/redo previous commands. + * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. + * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* + +* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. + +* **Code contributed**: [RepoSense link]() + +* **Project management**: + * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + +* **Enhancements to existing features**: + * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) + * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) + +* **Documentation**: + * User Guide: + * Added documentation for the features `delete` and `find` [\#72]() + * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() + * Developer Guide: + * Added implementation details of the `delete` feature. + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() + * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) + * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) + * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) + +* **Tools**: + * Integrated a third party library (Natty) to the project ([\#42]()) + * Integrated a new Github plugin (CircleCI) to the team repo + +* _{you can add/remove categories in the list above}_ diff --git a/docs/team/zlllllr.md b/docs/team/zlllllr.md new file mode 100644 index 00000000000..6cf5480d474 --- /dev/null +++ b/docs/team/zlllllr.md @@ -0,0 +1,46 @@ +--- + layout: default.md + title: "Zhang Leran's Project Portfolio Page" +--- + +### Project: Mentorstack + +Mentorstack is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to undo/redo previous commands. + * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. + * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. + * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. + * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* + +* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. + +* **Code contributed**: [RepoSense link]() + +* **Project management**: + * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub + +* **Enhancements to existing features**: + * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) + * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) + +* **Documentation**: + * User Guide: + * Added documentation for the features `delete` and `find` [\#72]() + * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() + * Developer Guide: + * Added implementation details of the `delete` feature. + +* **Community**: + * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() + * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) + * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) + * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) + +* **Tools**: + * Integrated a third party library (Natty) to the project ([\#42]()) + * Integrated a new Github plugin (CircleCI) to the team repo + +* _{you can add/remove categories in the list above}_ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java deleted file mode 100644 index 5aa3b91c7d0..00000000000 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ /dev/null @@ -1,88 +0,0 @@ -package seedu.address.logic; - -import java.io.IOException; -import java.nio.file.AccessDeniedException; -import java.nio.file.Path; -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; -import seedu.address.storage.Storage; - -/** - * The main LogicManager of the app. - */ -public class LogicManager implements Logic { - public static final String FILE_OPS_ERROR_FORMAT = "Could not save data due to the following error: %s"; - - public static final String FILE_OPS_PERMISSION_ERROR_FORMAT = - "Could not save data to file %s due to insufficient permissions to write to the file or the folder."; - - private final Logger logger = LogsCenter.getLogger(LogicManager.class); - - private final Model model; - private final Storage storage; - private final AddressBookParser addressBookParser; - - /** - * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. - */ - public LogicManager(Model model, Storage storage) { - this.model = model; - this.storage = storage; - addressBookParser = new AddressBookParser(); - } - - @Override - public CommandResult execute(String commandText) throws CommandException, ParseException { - logger.info("----------------[USER COMMAND][" + commandText + "]"); - - CommandResult commandResult; - Command command = addressBookParser.parseCommand(commandText); - commandResult = command.execute(model); - - try { - storage.saveAddressBook(model.getAddressBook()); - } catch (AccessDeniedException e) { - throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e); - } catch (IOException ioe) { - throw new CommandException(String.format(FILE_OPS_ERROR_FORMAT, ioe.getMessage()), ioe); - } - - return commandResult; - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return model.getAddressBook(); - } - - @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); - } - - @Override - public Path getAddressBookFilePath() { - return model.getAddressBookFilePath(); - } - - @Override - public GuiSettings getGuiSettings() { - return model.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - model.setGuiSettings(guiSettings); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 9c86b1fa6e4..00000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.setAddressBook(new AddressBook()); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index 1135ac19b74..00000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof DeleteCommand)) { - return false; - } - - DeleteCommand otherDeleteCommand = (DeleteCommand) other; - return targetIndex.equals(otherDeleteCommand.targetIndex); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("targetIndex", targetIndex) - .toString(); - } -} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 4ff1a97ed77..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,61 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); - Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index 3149ee07e0b..00000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,86 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); - private static final Logger logger = LogsCenter.getLogger(AddressBookParser.class); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - - // Note to developers: Change the log level in config.json to enable lower level (i.e., FINE, FINER and lower) - // log messages such as the one below. - // Lower level log messages are used sparingly to minimize noise in the code. - logger.fine("Command word: " + commandWord + "; Arguments: " + arguments); - - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - default: - logger.finer("This user input caused a ParseException: " + userInput); - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java deleted file mode 100644 index 3527fe76a3e..00000000000 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses input arguments and creates a new DeleteCommand object - */ -public class DeleteCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 46b3309a78b..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,85 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index d54df471c1f..00000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,87 +0,0 @@ -package seedu.address.model; - -import java.nio.file.Path; -import java.util.function.Predicate; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.Person; - -/** - * The API of the Model component. - */ -public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; - - /** - * Replaces user prefs data with the data in {@code userPrefs}. - */ - void setUserPrefs(ReadOnlyUserPrefs userPrefs); - - /** - * Returns the user prefs. - */ - ReadOnlyUserPrefs getUserPrefs(); - - /** - * Returns the user prefs' GUI settings. - */ - GuiSettings getGuiSettings(); - - /** - * Sets the user prefs' GUI settings. - */ - void setGuiSettings(GuiSettings guiSettings); - - /** - * Returns the user prefs' address book file path. - */ - Path getAddressBookFilePath(); - - /** - * Sets the user prefs' address book file path. - */ - void setAddressBookFilePath(Path addressBookFilePath); - - /** - * Replaces address book data with the data in {@code addressBook}. - */ - void setAddressBook(ReadOnlyAddressBook addressBook); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** - * Returns true if a person with the same identity as {@code person} exists in the address book. - */ - boolean hasPerson(Person person); - - /** - * Deletes the given person. - * The person must exist in the address book. - */ - void deletePerson(Person target); - - /** - * Adds the given person. - * {@code person} must not already exist in the address book. - */ - void addPerson(Person person); - - /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. - */ - void setPerson(Person target, Person editedPerson); - - /** Returns an unmodifiable view of the filtered person list */ - ObservableList getFilteredPersonList(); - - /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. - * @throws NullPointerException if {@code predicate} is null. - */ - void updateFilteredPersonList(Predicate predicate); -} diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java deleted file mode 100644 index 57bc563fde6..00000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,148 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.nio.file.Path; -import java.util.function.Predicate; -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; - -/** - * Represents the in-memory model of the address book data. - */ -public class ModelManager implements Model { - private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - - private final AddressBook addressBook; - private final UserPrefs userPrefs; - private final FilteredList filteredPersons; - - /** - * Initializes a ModelManager with the given addressBook and userPrefs. - */ - public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) { - requireAllNonNull(addressBook, userPrefs); - - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - - this.addressBook = new AddressBook(addressBook); - this.userPrefs = new UserPrefs(userPrefs); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); - } - - public ModelManager() { - this(new AddressBook(), new UserPrefs()); - } - - //=========== UserPrefs ================================================================================== - - @Override - public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { - requireNonNull(userPrefs); - this.userPrefs.resetData(userPrefs); - } - - @Override - public ReadOnlyUserPrefs getUserPrefs() { - return userPrefs; - } - - @Override - public GuiSettings getGuiSettings() { - return userPrefs.getGuiSettings(); - } - - @Override - public void setGuiSettings(GuiSettings guiSettings) { - requireNonNull(guiSettings); - userPrefs.setGuiSettings(guiSettings); - } - - @Override - public Path getAddressBookFilePath() { - return userPrefs.getAddressBookFilePath(); - } - - @Override - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - userPrefs.setAddressBookFilePath(addressBookFilePath); - } - - //=========== AddressBook ================================================================================ - - @Override - public void setAddressBook(ReadOnlyAddressBook addressBook) { - this.addressBook.resetData(addressBook); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; - } - - @Override - public boolean hasPerson(Person person) { - requireNonNull(person); - return addressBook.hasPerson(person); - } - - @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); - } - - @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - } - - @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - addressBook.setPerson(target, editedPerson); - } - - //=========== Filtered Person List Accessors ============================================================= - - /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code versionedAddressBook} - */ - @Override - public ObservableList getFilteredPersonList() { - return filteredPersons; - } - - @Override - public void updateFilteredPersonList(Predicate predicate) { - requireNonNull(predicate); - filteredPersons.setPredicate(predicate); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof ModelManager)) { - return false; - } - - ModelManager otherModelManager = (ModelManager) other; - return addressBook.equals(otherModelManager.addressBook) - && userPrefs.equals(otherModelManager.userPrefs) - && filteredPersons.equals(otherModelManager.filteredPersons); - } - -} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 469a2cc9a1e..00000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,65 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_CONSTRAINTS); - value = address; - } - - /** - * Returns true if a given string is a valid email. - */ - public static boolean isValidAddress(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof Address)) { - return false; - } - - Address otherAddress = (Address) other; - return value.equals(otherAddress.value); - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index abe8c46b535..00000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,117 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.tag.Tag; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - // Identity fields - private final Name name; - private final Phone phone; - private final Email email; - - // Data fields - private final Address address; - private final Set tags = new HashSet<>(); - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - this.tags.addAll(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set getTags() { - return Collections.unmodifiableSet(tags); - } - - /** - * Returns true if both persons have the same name. - * This defines a weaker notion of equality between two persons. - */ - public boolean isSamePerson(Person otherPerson) { - if (otherPerson == this) { - return true; - } - - return otherPerson != null - && otherPerson.getName().equals(getName()); - } - - /** - * Returns true if both persons have the same identity and data fields. - * This defines a stronger notion of equality between two persons. - */ - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return name.equals(otherPerson.name) - && phone.equals(otherPerson.phone) - && email.equals(otherPerson.email) - && address.equals(otherPerson.address) - && tags.equals(otherPerson.tags); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("name", name) - .add("phone", phone) - .add("email", email) - .add("address", address) - .add("tags", tags) - .toString(); - } - -} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java deleted file mode 100644 index f1a0d4e233b..00000000000 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ /dev/null @@ -1,62 +0,0 @@ -package seedu.address.model.tag; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Tag in the address book. - * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} - */ -public class Tag { - - public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String VALIDATION_REGEX = "\\p{Alnum}+"; - - public final String tagName; - - /** - * Constructs a {@code Tag}. - * - * @param tagName A valid tag name. - */ - public Tag(String tagName) { - requireNonNull(tagName); - checkArgument(isValidTagName(tagName), MESSAGE_CONSTRAINTS); - this.tagName = tagName; - } - - /** - * Returns true if a given string is a valid tag name. - */ - public static boolean isValidTagName(String test) { - return test.matches(VALIDATION_REGEX); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof Tag)) { - return false; - } - - Tag otherTag = (Tag) other; - return tagName.equals(otherTag.tagName); - } - - @Override - public int hashCode() { - return tagName.hashCode(); - } - - /** - * Format state as text for viewing. - */ - public String toString() { - return '[' + tagName + ']'; - } - -} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java deleted file mode 100644 index 1806da4facf..00000000000 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -package seedu.address.model.util; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods for populating {@code AddressBook} with sample data. - */ -public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; - } - - public static ReadOnlyAddressBook getSampleAddressBook() { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); - } - return sampleAb; - } - - /** - * Returns a tag set containing the list of strings given. - */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); - } - -} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index f2e015105ae..00000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,45 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - Path getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * - * @throws DataLoadingException if loading the data from storage failed. - */ - Optional readAddressBook() throws DataLoadingException; - - /** - * @see #getAddressBookFilePath() - */ - Optional readAddressBook(Path filePath) throws DataLoadingException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java deleted file mode 100644 index bd1ca0f56c8..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ /dev/null @@ -1,109 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Person}. - */ -class JsonAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - private final String name; - private final String phone; - private final String email; - private final String address; - private final List tags = new ArrayList<>(); - - /** - * Constructs a {@code JsonAdaptedPerson} with the given person details. - */ - @JsonCreator - public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone") String phone, - @JsonProperty("email") String email, @JsonProperty("address") String address, - @JsonProperty("tags") List tags) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tags != null) { - this.tags.addAll(tags); - } - } - - /** - * Converts a given {@code Person} into this class for Jackson use. - */ - public JsonAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tags.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) - .collect(Collectors.toList())); - } - - /** - * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person. - */ - public Person toModelType() throws IllegalValueException { - final List personTags = new ArrayList<>(); - for (JsonAdaptedTag tag : tags) { - personTags.add(tag.toModelType()); - } - - if (name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(name)) { - throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); - } - final Name modelName = new Name(name); - - if (phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(phone)) { - throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); - } - final Phone modelPhone = new Phone(phone); - - if (email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(email)) { - throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); - } - final Email modelEmail = new Email(email); - - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); - } - final Address modelAddress = new Address(address); - - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTag.java b/src/main/java/seedu/address/storage/JsonAdaptedTag.java deleted file mode 100644 index 0df22bdb754..00000000000 --- a/src/main/java/seedu/address/storage/JsonAdaptedTag.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.storage; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; - -/** - * Jackson-friendly version of {@link Tag}. - */ -class JsonAdaptedTag { - - private final String tagName; - - /** - * Constructs a {@code JsonAdaptedTag} with the given {@code tagName}. - */ - @JsonCreator - public JsonAdaptedTag(String tagName) { - this.tagName = tagName; - } - - /** - * Converts a given {@code Tag} into this class for Jackson use. - */ - public JsonAdaptedTag(Tag source) { - tagName = source.tagName; - } - - @JsonValue - public String getTagName() { - return tagName; - } - - /** - * Converts this Jackson-friendly adapted tag object into the model's {@code Tag} object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted tag. - */ - public Tag toModelType() throws IllegalValueException { - if (!Tag.isValidTagName(tagName)) { - throw new IllegalValueException(Tag.MESSAGE_CONSTRAINTS); - } - return new Tag(tagName); - } - -} diff --git a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java b/src/main/java/seedu/address/storage/JsonAddressBookStorage.java deleted file mode 100644 index 41e06f264e1..00000000000 --- a/src/main/java/seedu/address/storage/JsonAddressBookStorage.java +++ /dev/null @@ -1,80 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as a json file on the hard disk. - */ -public class JsonAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(JsonAddressBookStorage.class); - - private Path filePath; - - public JsonAddressBookStorage(Path filePath) { - this.filePath = filePath; - } - - public Path getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional readAddressBook() throws DataLoadingException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()}. - * - * @param filePath location of the data. Cannot be null. - * @throws DataLoadingException if loading the data from storage failed. - */ - public Optional readAddressBook(Path filePath) throws DataLoadingException { - requireNonNull(filePath); - - Optional jsonAddressBook = JsonUtil.readJsonFile( - filePath, JsonSerializableAddressBook.class); - if (!jsonAddressBook.isPresent()) { - return Optional.empty(); - } - - try { - return Optional.of(jsonAddressBook.get().toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); - throw new DataLoadingException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)}. - * - * @param filePath location of the data. Cannot be null. - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - FileUtil.createIfMissing(filePath); - JsonUtil.saveJsonFile(new JsonSerializableAddressBook(addressBook), filePath); - } - -} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java deleted file mode 100644 index 9fba0c7a1d6..00000000000 --- a/src/main/java/seedu/address/storage/Storage.java +++ /dev/null @@ -1,32 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * API of the Storage component - */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { - - @Override - Optional readUserPrefs() throws DataLoadingException; - - @Override - void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; - - @Override - Path getAddressBookFilePath(); - - @Override - Optional readAddressBook() throws DataLoadingException; - - @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java deleted file mode 100644 index 8b84a9024d5..00000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,78 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; - -/** - * Manages storage of AddressBook data in local storage. - */ -public class StorageManager implements Storage { - - private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; - private UserPrefsStorage userPrefsStorage; - - /** - * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}. - */ - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { - this.addressBookStorage = addressBookStorage; - this.userPrefsStorage = userPrefsStorage; - } - - // ================ UserPrefs methods ============================== - - @Override - public Path getUserPrefsFilePath() { - return userPrefsStorage.getUserPrefsFilePath(); - } - - @Override - public Optional readUserPrefs() throws DataLoadingException { - return userPrefsStorage.readUserPrefs(); - } - - @Override - public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { - userPrefsStorage.saveUserPrefs(userPrefs); - } - - - // ================ AddressBook methods ============================== - - @Override - public Path getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); - } - - @Override - public Optional readAddressBook() throws DataLoadingException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); - } - - @Override - public Optional readAddressBook(Path filePath) throws DataLoadingException { - logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) throws IOException { - logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); - } - -} diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index 094c42cda82..00000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,59 +0,0 @@ -package seedu.address.ui; - -import java.util.Comparator; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart { - - private static final String FXML = "PersonListCard.fxml"; - - /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. - * As a consequence, UI elements' variable names cannot be set to such keywords - * or an exception will be thrown by JavaFX during runtime. - * - * @see The issue on AddressBook level 4 - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - /** - * Creates a {@code PersonCode} with the given {@code Person} and index to display. - */ - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } -} diff --git a/src/main/java/seedu/address/AppParameters.java b/src/main/java/seedu/mentorstack/AppParameters.java similarity index 91% rename from src/main/java/seedu/address/AppParameters.java rename to src/main/java/seedu/mentorstack/AppParameters.java index 3d603622d4e..d0d5269f438 100644 --- a/src/main/java/seedu/address/AppParameters.java +++ b/src/main/java/seedu/mentorstack/AppParameters.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.mentorstack; import java.nio.file.Path; import java.nio.file.Paths; @@ -7,9 +7,9 @@ import java.util.logging.Logger; import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.FileUtil; -import seedu.address.commons.util.ToStringBuilder; +import seedu.mentorstack.commons.core.LogsCenter; +import seedu.mentorstack.commons.util.FileUtil; +import seedu.mentorstack.commons.util.ToStringBuilder; /** * Represents the parsed command-line parameters given to the application. diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/seedu/mentorstack/Main.java similarity index 95% rename from src/main/java/seedu/address/Main.java rename to src/main/java/seedu/mentorstack/Main.java index 9461d6da769..097e90d20f1 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/seedu/mentorstack/Main.java @@ -1,9 +1,9 @@ -package seedu.address; +package seedu.mentorstack; import java.util.logging.Logger; import javafx.application.Application; -import seedu.address.commons.core.LogsCenter; +import seedu.mentorstack.commons.core.LogsCenter; /** * The main entry point to the application. @@ -25,7 +25,6 @@ public class Main { private static Logger logger = LogsCenter.getLogger(Main.class); public static void main(String[] args) { - // As per https://github.com/openjdk/jfx/blob/master/doc-files/release-notes-16.md // JavaFX 16 (or later) runtime logs a warning at startup if JavaFX classes are loaded from // the classpath instead of a module. diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/mentorstack/MainApp.java similarity index 65% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/seedu/mentorstack/MainApp.java index 678ddc8c218..9eb86f9ddae 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/mentorstack/MainApp.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.mentorstack; import java.io.IOException; import java.nio.file.Path; @@ -7,36 +7,36 @@ import javafx.application.Application; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.commons.util.ConfigUtil; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; -import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonAddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.storage.UserPrefsStorage; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; +import seedu.mentorstack.commons.core.Config; +import seedu.mentorstack.commons.core.LogsCenter; +import seedu.mentorstack.commons.core.Version; +import seedu.mentorstack.commons.exceptions.DataLoadingException; +import seedu.mentorstack.commons.util.ConfigUtil; +import seedu.mentorstack.commons.util.StringUtil; +import seedu.mentorstack.logic.Logic; +import seedu.mentorstack.logic.LogicManager; +import seedu.mentorstack.model.Mentorstack; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.ReadOnlyMentorstack; +import seedu.mentorstack.model.ReadOnlyUserPrefs; +import seedu.mentorstack.model.UserPrefs; +import seedu.mentorstack.model.util.SampleDataUtil; +import seedu.mentorstack.storage.JsonMentorstackStorage; +import seedu.mentorstack.storage.JsonUserPrefsStorage; +import seedu.mentorstack.storage.MentorstackStorage; +import seedu.mentorstack.storage.Storage; +import seedu.mentorstack.storage.StorageManager; +import seedu.mentorstack.storage.UserPrefsStorage; +import seedu.mentorstack.ui.Ui; +import seedu.mentorstack.ui.UiManager; /** * Runs the application. */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 2, 2, true); + public static final Version VERSION = new Version(1, 6, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -48,7 +48,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing Mentorstack ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -57,8 +57,8 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); UserPrefs userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + MentorstackStorage mentorstackStorage = new JsonMentorstackStorage(userPrefs.getMentorstackFilePath()); + storage = new StorageManager(mentorstackStorage, userPrefsStorage); model = initModelManager(storage, userPrefs); @@ -68,26 +68,26 @@ public void init() throws Exception { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
    - * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s mentorstack and {@code userPrefs}.
    + * The data from the sample mentorstack will be used instead if {@code storage}'s mentorstack is not found, + * or an empty mentorstack will be used instead if errors occur when reading {@code storage}'s mentorstack. */ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { - logger.info("Using data file : " + storage.getAddressBookFilePath()); + logger.info("Using data file : " + storage.getMentorstackFilePath()); - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + Optional mentorstackOptional; + ReadOnlyMentorstack initialData; try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Creating a new data file " + storage.getAddressBookFilePath() - + " populated with a sample AddressBook."); + mentorstackOptional = storage.readMentorstack(); + if (!mentorstackOptional.isPresent()) { + logger.info("Creating a new data file " + storage.getMentorstackFilePath() + + " populated with a sample Mentorstack."); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = mentorstackOptional.orElseGet(SampleDataUtil::getSampleMentorstack); } catch (DataLoadingException e) { - logger.warning("Data file at " + storage.getAddressBookFilePath() + " could not be loaded." - + " Will be starting with an empty AddressBook."); - initialData = new AddressBook(); + logger.warning("Data file at " + storage.getMentorstackFilePath() + " could not be loaded." + + " Will be starting with an empty Mentorstack."); + initialData = new Mentorstack(); } return new ModelManager(initialData, userPrefs); @@ -170,13 +170,13 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting Mentorstack " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping AddressBook ] ============================="); + logger.info("============================ [ Stopping Mentorstack ] ============================="); try { storage.saveUserPrefs(model.getUserPrefs()); } catch (IOException e) { diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/mentorstack/commons/core/Config.java similarity index 94% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/seedu/mentorstack/commons/core/Config.java index 485f85a5e05..323762837b6 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/mentorstack/commons/core/Config.java @@ -1,11 +1,11 @@ -package seedu.address.commons.core; +package seedu.mentorstack.commons.core; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Objects; import java.util.logging.Level; -import seedu.address.commons.util.ToStringBuilder; +import seedu.mentorstack.commons.util.ToStringBuilder; /** * Config values used by the app diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/mentorstack/commons/core/GuiSettings.java similarity index 96% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/seedu/mentorstack/commons/core/GuiSettings.java index a97a86ee8d7..011402aebd9 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/mentorstack/commons/core/GuiSettings.java @@ -1,10 +1,10 @@ -package seedu.address.commons.core; +package seedu.mentorstack.commons.core; import java.awt.Point; import java.io.Serializable; import java.util.Objects; -import seedu.address.commons.util.ToStringBuilder; +import seedu.mentorstack.commons.util.ToStringBuilder; /** * A Serializable class that contains the GUI settings. diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/mentorstack/commons/core/LogsCenter.java similarity index 97% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/seedu/mentorstack/commons/core/LogsCenter.java index 8cf8e15a0f0..79b8b7f173f 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/mentorstack/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.mentorstack.commons.core; import static java.util.Objects.requireNonNull; @@ -20,7 +20,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "mentorstack.log"; private static final Logger logger; // logger for this class private static Logger baseLogger; // to be used as the parent of all other loggers created by this class. private static Level currentLogLevel = Level.INFO; diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/mentorstack/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/seedu/mentorstack/commons/core/Version.java index 491d24559b4..429d82e75b7 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/mentorstack/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.mentorstack.commons.core; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/mentorstack/commons/core/index/Index.java similarity index 94% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/seedu/mentorstack/commons/core/index/Index.java index dd170d8b68d..63e3314254e 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/mentorstack/commons/core/index/Index.java @@ -1,6 +1,6 @@ -package seedu.address.commons.core.index; +package seedu.mentorstack.commons.core.index; -import seedu.address.commons.util.ToStringBuilder; +import seedu.mentorstack.commons.util.ToStringBuilder; /** * Represents a zero-based or one-based index. diff --git a/src/main/java/seedu/address/commons/exceptions/DataLoadingException.java b/src/main/java/seedu/mentorstack/commons/exceptions/DataLoadingException.java similarity index 81% rename from src/main/java/seedu/address/commons/exceptions/DataLoadingException.java rename to src/main/java/seedu/mentorstack/commons/exceptions/DataLoadingException.java index 9904ba47afe..6aa755bed40 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataLoadingException.java +++ b/src/main/java/seedu/mentorstack/commons/exceptions/DataLoadingException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.mentorstack.commons.exceptions; /** * Represents an error during loading of data from a file. diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/mentorstack/commons/exceptions/IllegalValueException.java similarity index 92% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/seedu/mentorstack/commons/exceptions/IllegalValueException.java index 19124db485c..13c9524477e 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/mentorstack/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.mentorstack.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/seedu/mentorstack/commons/util/AppUtil.java similarity index 93% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/seedu/mentorstack/commons/util/AppUtil.java index 87aa89c0326..76a7b9d0cb5 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/mentorstack/commons/util/AppUtil.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; import static java.util.Objects.requireNonNull; import javafx.scene.image.Image; -import seedu.address.MainApp; +import seedu.mentorstack.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/mentorstack/commons/util/CollectionUtil.java similarity index 95% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/seedu/mentorstack/commons/util/CollectionUtil.java index eafe4dfd681..0503ea867e4 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/mentorstack/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/seedu/mentorstack/commons/util/ConfigUtil.java similarity index 76% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/seedu/mentorstack/commons/util/ConfigUtil.java index 7b829c3c4cc..bd34cbba2f8 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/seedu/mentorstack/commons/util/ConfigUtil.java @@ -1,11 +1,11 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataLoadingException; +import seedu.mentorstack.commons.core.Config; +import seedu.mentorstack.commons.exceptions.DataLoadingException; /** * A class for accessing the Config File. diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/mentorstack/commons/util/FileUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/FileUtil.java rename to src/main/java/seedu/mentorstack/commons/util/FileUtil.java index b1e2767cdd9..573e3ac4548 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/mentorstack/commons/util/FileUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; import java.io.IOException; import java.nio.file.Files; diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/mentorstack/commons/util/JsonUtil.java similarity index 97% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/seedu/mentorstack/commons/util/JsonUtil.java index 100cb16c395..9622873f336 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/mentorstack/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; import static java.util.Objects.requireNonNull; @@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataLoadingException; +import seedu.mentorstack.commons.core.LogsCenter; +import seedu.mentorstack.commons.exceptions.DataLoadingException; /** * Converts a Java object instance to JSON and vice versa diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/mentorstack/commons/util/StringUtil.java similarity index 64% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/seedu/mentorstack/commons/util/StringUtil.java index 61cc8c9a1cb..68c629bf157 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/mentorstack/commons/util/StringUtil.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.mentorstack.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; @@ -38,6 +38,32 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { .anyMatch(preppedWord::equalsIgnoreCase); } + /** + * Returns true if some part of the {@code sentence} contains the {@code word}. + * Ignores case, but a full word match is not required. + *
    examples:
    +     *       containsPartialWordIgnoreCase("ABc def", "abc") == true
    +     *       containsPartialWordIgnoreCase("ABc def", "DEF") == true
    +     *       containsPartialWordIgnoreCase("ABc def", "AB") == true
    +     *       
    + * @param sentence cannot be null + * @param word cannot be null, cannot be empty, must be a single word + */ + public static boolean containsPartialWordIgnoreCase(String sentence, String word) { + requireNonNull(sentence); + requireNonNull(word); + + String preppedWord = word.trim(); + checkArgument(!preppedWord.isEmpty(), "Word parameter cannot be empty"); + checkArgument(preppedWord.split("\\s+").length == 1, "Word parameter should be a single word"); + + String preppedSentence = sentence; + String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); + + return Arrays.stream(wordsInPreppedSentence) + .anyMatch(words -> words.toLowerCase().contains(preppedWord.toLowerCase())); + } + /** * Returns a detailed message of the t, including the stack trace. */ diff --git a/src/main/java/seedu/address/commons/util/ToStringBuilder.java b/src/main/java/seedu/mentorstack/commons/util/ToStringBuilder.java similarity index 97% rename from src/main/java/seedu/address/commons/util/ToStringBuilder.java rename to src/main/java/seedu/mentorstack/commons/util/ToStringBuilder.java index d979b926734..35f39b36463 100644 --- a/src/main/java/seedu/address/commons/util/ToStringBuilder.java +++ b/src/main/java/seedu/mentorstack/commons/util/ToStringBuilder.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; /** * Builds a string representation of an object that is suitable as the return value of {@link Object#toString()}. diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/mentorstack/logic/Logic.java similarity index 64% rename from src/main/java/seedu/address/logic/Logic.java rename to src/main/java/seedu/mentorstack/logic/Logic.java index 92cd8fa605a..b5ad5eda265 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/mentorstack/logic/Logic.java @@ -1,14 +1,14 @@ -package seedu.address.logic; +package seedu.mentorstack.logic; import java.nio.file.Path; import javafx.collections.ObservableList; -import seedu.address.commons.core.GuiSettings; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.mentorstack.commons.core.GuiSettings; +import seedu.mentorstack.logic.commands.CommandResult; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.model.ReadOnlyMentorstack; +import seedu.mentorstack.model.person.Person; /** * API of the Logic component @@ -24,11 +24,11 @@ public interface Logic { CommandResult execute(String commandText) throws CommandException, ParseException; /** - * Returns the AddressBook. + * Returns the Mentorstack. * - * @see seedu.address.model.Model#getAddressBook() + * @see seedu.mentorstack.model.Model#getMentorstack() */ - ReadOnlyAddressBook getAddressBook(); + ReadOnlyMentorstack getMentorstack(); /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); @@ -36,7 +36,7 @@ public interface Logic { /** * Returns the user prefs' address book file path. */ - Path getAddressBookFilePath(); + Path getMentorstackFilePath(); /** * Returns the user prefs' GUI settings. diff --git a/src/main/java/seedu/mentorstack/logic/LogicManager.java b/src/main/java/seedu/mentorstack/logic/LogicManager.java new file mode 100644 index 00000000000..c30952a106c --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/LogicManager.java @@ -0,0 +1,128 @@ +package seedu.mentorstack.logic; + +import java.io.IOException; +import java.nio.file.AccessDeniedException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.scene.chart.XYChart; +import seedu.mentorstack.commons.core.GuiSettings; +import seedu.mentorstack.commons.core.LogsCenter; +import seedu.mentorstack.logic.commands.Command; +import seedu.mentorstack.logic.commands.CommandResult; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.logic.parser.MentorstackParser; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ReadOnlyMentorstack; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Subject; +import seedu.mentorstack.storage.Storage; + +/** + * The main LogicManager of the app. + */ +public class LogicManager implements Logic { + public static final String FILE_OPS_ERROR_FORMAT = "Could not save data due to the following error: %s"; + + public static final String FILE_OPS_PERMISSION_ERROR_FORMAT = + "Could not save data to file %s due to insufficient permissions to write to the file or the folder."; + + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + + private final Model model; + private final Storage storage; + private final MentorstackParser mentorstackParser; + + /** + * Constructs a {@code LogicManager} with the given {@code Model} and {@code Storage}. + */ + public LogicManager(Model model, Storage storage) { + this.model = model; + this.storage = storage; + mentorstackParser = new MentorstackParser(); + } + + @Override + public CommandResult execute(String commandText) throws CommandException, ParseException { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + + CommandResult commandResult; + Command command = mentorstackParser.parseCommand(commandText); + commandResult = command.execute(model); + + try { + storage.saveMentorstack(model.getMentorstack()); + } catch (AccessDeniedException e) { + throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e); + } catch (IOException ioe) { + throw new CommandException(String.format(FILE_OPS_ERROR_FORMAT, ioe.getMessage()), ioe); + } + + return commandResult; + } + + @Override + public ReadOnlyMentorstack getMentorstack() { + return model.getMentorstack(); + } + + @Override + public ObservableList getFilteredPersonList() { + return model.getFilteredPersonList(); + } + + public Map getStudentsBySubject() { + + Map studentsBySubjects = new HashMap<>(); + List personList = this.getFilteredPersonList(); + + for (Person person : personList) { + Set subjects = person.getSubjects(); + + for (Subject subject : subjects) { + String subjectName = subject.toString(); + studentsBySubjects.put(subjectName, studentsBySubjects.getOrDefault(subjectName, 0) + 1); + } + } + + return studentsBySubjects; + } + + /** + * Adds content from Map of subjects and student count to Series object + * to be used in charts. + */ + + public XYChart.Series populateSeries( + XYChart.Series series, + Map studentsBySubjects + ) { + + for (Map.Entry entry : studentsBySubjects.entrySet()) { + series.getData().add(new XYChart.Data<>(entry.getKey(), entry.getValue())); + } + + return series; + } + + @Override + public Path getMentorstackFilePath() { + return model.getMentorstackFilePath(); + } + + @Override + public GuiSettings getGuiSettings() { + return model.getGuiSettings(); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + model.setGuiSettings(guiSettings); + } +} diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/mentorstack/logic/Messages.java similarity index 80% rename from src/main/java/seedu/address/logic/Messages.java rename to src/main/java/seedu/mentorstack/logic/Messages.java index ecd32c31b53..addbd7a17f2 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/mentorstack/logic/Messages.java @@ -1,11 +1,11 @@ -package seedu.address.logic; +package seedu.mentorstack.logic; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import seedu.address.logic.parser.Prefix; -import seedu.address.model.person.Person; +import seedu.mentorstack.logic.parser.Prefix; +import seedu.mentorstack.model.person.Person; /** * Container for user visible messages. @@ -18,6 +18,7 @@ public class Messages { public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; public static final String MESSAGE_DUPLICATE_FIELDS = "Multiple values specified for the following single-valued field(s): "; + public static final String MESSAGE_IS_ARCHIVED = "Person is archived"; /** * Returns an error message indicating the duplicate prefixes. @@ -37,14 +38,14 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref public static String format(Person person) { final StringBuilder builder = new StringBuilder(); builder.append(person.getName()) + .append("; Gender: ") + .append(person.getGender()) .append("; Phone: ") .append(person.getPhone()) .append("; Email: ") .append(person.getEmail()) - .append("; Address: ") - .append(person.getAddress()) - .append("; Tags: "); - person.getTags().forEach(builder::append); + .append("; Subjects: "); + person.getSubjects().forEach(builder::append); return builder.toString(); } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/mentorstack/logic/commands/AddCommand.java similarity index 55% rename from src/main/java/seedu/address/logic/commands/AddCommand.java rename to src/main/java/seedu/mentorstack/logic/commands/AddCommand.java index 5d7185a9680..ac2b3e0ce28 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/mentorstack/logic/commands/AddCommand.java @@ -1,43 +1,48 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.mentorstack.commons.util.ToStringBuilder; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.Person; /** - * Adds a person to the address book. + * Adds a person to the Mentorstack. */ public class AddCommand extends Command { public static final String COMMAND_WORD = "add"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a student to Mentorstack. " + "Parameters: " + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " + + PREFIX_GENDER + "GENDER " + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" + + PREFIX_PHONE + "PHONE " + + PREFIX_SUBJECT + "SUBJECT...\n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " + + PREFIX_GENDER + "F " + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_PHONE + "98765432 " + + PREFIX_SUBJECT + "CS2103 " + + PREFIX_SUBJECT + "LAJ1201"; public static final String MESSAGE_SUCCESS = "New person added: %1$s"; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + private static final Logger logger = Logger.getLogger(AddCommand.class.getName()); + private final Person toAdd; /** @@ -46,17 +51,23 @@ public class AddCommand extends Command { public AddCommand(Person person) { requireNonNull(person); toAdd = person; + logger.log(Level.INFO, "AddCommand created with person: {0}", person); } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); + logger.log(Level.INFO, "Executing AddCommand for person: {0}", toAdd); if (model.hasPerson(toAdd)) { + logger.log(Level.WARNING, "Duplicate person detected: {0}", toAdd); throw new CommandException(MESSAGE_DUPLICATE_PERSON); } + model.rememberMentorstack(); // save the state for undo model.addPerson(toAdd); + logger.log(Level.INFO, "Person successfully added: {0}", toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); } diff --git a/src/main/java/seedu/mentorstack/logic/commands/ArchiveCommand.java b/src/main/java/seedu/mentorstack/logic/commands/ArchiveCommand.java new file mode 100644 index 00000000000..6b47edd2f8f --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/commands/ArchiveCommand.java @@ -0,0 +1,95 @@ +package seedu.mentorstack.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.commons.util.ToStringBuilder; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.Person; + +/** + * Archives a person identified using it's displayed index from Mentorstack. + */ +public class ArchiveCommand extends Command { + + public static final String COMMAND_WORD = "archive"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Archives the persons identified by the index numbers used in the displayed person list.\n" + + "Parameters: INDEX1 INDEX2 ... (each must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 2 3"; + + public static final String MESSAGE_ARCHIVE_PERSON_SUCCESS = "Archived Persons: %1$s\n" + + "Here is the list of all unarchived persons:"; + + public static final String MESSAGE_DUPLICATE_ARCHIVE = "This Person is already archived: "; + + private final Set targetIndices; + + public ArchiveCommand(Set targetIndices) { + this.targetIndices = targetIndices; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + // Ensure all indices are valid before archive + for (Index index : targetIndices) { + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + } + + for (Index index : targetIndices) { + if (lastShownList.get(index.getZeroBased()).getIsArchived().testStatus()) { + throw new CommandException(MESSAGE_DUPLICATE_ARCHIVE + + Messages.format(lastShownList.get(index.getZeroBased()))); + } + } + + ArrayList personsToArchive = new ArrayList(); + for (Index index : targetIndices) { + Person personToArchive = lastShownList.get(index.getZeroBased()); + personsToArchive.add(personToArchive); + } + + // Perform archiving + model.rememberMentorstack(); // save the state for undo + StringBuilder archivedPersons = new StringBuilder(); + for (Person personToArchive : personsToArchive) { + model.archivePerson(personToArchive, personToArchive.archived()); + archivedPersons.append(Messages.format(personToArchive)).append("\n"); + } + return new CommandResult(String.format(MESSAGE_ARCHIVE_PERSON_SUCCESS, archivedPersons.toString().trim())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ArchiveCommand)) { + return false; + } + + ArchiveCommand otherArchiveCommand = (ArchiveCommand) other; + return targetIndices.equals(otherArchiveCommand.targetIndices); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndices) + .toString(); + } +} diff --git a/src/main/java/seedu/mentorstack/logic/commands/ClearCommand.java b/src/main/java/seedu/mentorstack/logic/commands/ClearCommand.java new file mode 100644 index 00000000000..4c41fd4e243 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/commands/ClearCommand.java @@ -0,0 +1,24 @@ +package seedu.mentorstack.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.mentorstack.model.Mentorstack; +import seedu.mentorstack.model.Model; + +/** + * Clears the Mentorstack. + */ +public class ClearCommand extends Command { + + public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_SUCCESS = "Mentorstack has been cleared!"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.rememberMentorstack(); // save the state for undo + model.setMentorstack(new Mentorstack()); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/mentorstack/logic/commands/Command.java similarity index 77% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/seedu/mentorstack/logic/commands/Command.java index 64f18992160..7eb4b73b218 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/mentorstack/logic/commands/Command.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; /** * Represents a command with hidden internal logic and the ability to be executed. diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/mentorstack/logic/commands/CommandResult.java similarity index 95% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/seedu/mentorstack/logic/commands/CommandResult.java index 249b6072d0d..65e50070b94 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/mentorstack/logic/commands/CommandResult.java @@ -1,10 +1,10 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; import static java.util.Objects.requireNonNull; import java.util.Objects; -import seedu.address.commons.util.ToStringBuilder; +import seedu.mentorstack.commons.util.ToStringBuilder; /** * Represents the result of a command execution. diff --git a/src/main/java/seedu/mentorstack/logic/commands/DeleteCommand.java b/src/main/java/seedu/mentorstack/logic/commands/DeleteCommand.java new file mode 100644 index 00000000000..6e2bcd5ead0 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/commands/DeleteCommand.java @@ -0,0 +1,99 @@ +package seedu.mentorstack.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.commons.util.ToStringBuilder; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.Person; + +/** + * Deletes a person identified using its displayed index from Mentorstack. + */ +public class DeleteCommand extends Command { + + public static final String COMMAND_WORD = "delete"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the persons identified by the index numbers used in the displayed person list.\n" + + "Parameters: INDEX1 INDEX2 ... (each must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 2 3"; + + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Persons: %1$s"; + + private static final Logger logger = Logger.getLogger(DeleteCommand.class.getName()); + + private final Set targetIndices; + + /** + * Deletes a person identified using it's displayed index from Mentorstack. + */ + public DeleteCommand(Set targetIndices) { + this.targetIndices = targetIndices; + logger.log(Level.INFO, "DeleteCommand created with target indices: {0}", targetIndices); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + logger.log(Level.INFO, "Executing DeleteCommand with indices: {0}", targetIndices); + + List lastShownList = model.getFilteredPersonList(); + + // Ensure all indices are valid before deletion + for (Index index : targetIndices) { + if (index.getZeroBased() >= lastShownList.size()) { + logger.log(Level.WARNING, "Invalid index detected: {0}", index); + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + } + + ArrayList personsToDelete = new ArrayList<>(); + model.rememberMentorstack(); // save the state for undo + + for (Index index : targetIndices) { + Person personToDelete = lastShownList.get(index.getZeroBased()); + personsToDelete.add(personToDelete); + } + + StringBuilder deletedPersons = new StringBuilder(); + for (Person personToDelete : personsToDelete) { + model.deletePerson(personToDelete); + deletedPersons.append(Messages.format(personToDelete)).append("\n"); + logger.log(Level.INFO, "Deleted person: {0}", personToDelete); + } + + logger.log(Level.INFO, "DeleteCommand executed successfully, deleted persons: {0}", + deletedPersons.toString().trim()); + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, deletedPersons.toString().trim())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof DeleteCommand)) { + return false; + } + + DeleteCommand otherDeleteCommand = (DeleteCommand) other; + return targetIndices.equals(otherDeleteCommand.targetIndices); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndices", targetIndices) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/mentorstack/logic/commands/EditCommand.java similarity index 57% rename from src/main/java/seedu/address/logic/commands/EditCommand.java rename to src/main/java/seedu/mentorstack/logic/commands/EditCommand.java index 4b581c7331e..fc41c463f6e 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/mentorstack/logic/commands/EditCommand.java @@ -1,12 +1,12 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.mentorstack.model.Model.PREDICATE_SHOW_ALL_PERSONS; import java.util.Collections; import java.util.HashSet; @@ -15,21 +15,22 @@ import java.util.Optional; import java.util.Set; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.commons.util.CollectionUtil; +import seedu.mentorstack.commons.util.ToStringBuilder; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.ArchiveStatus; +import seedu.mentorstack.model.person.Email; +import seedu.mentorstack.model.person.Gender; +import seedu.mentorstack.model.person.Name; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Phone; +import seedu.mentorstack.model.person.Subject; /** - * Edits the details of an existing person in the address book. + * Edits the details of an existing person in Mentorstack. */ public class EditCommand extends Command { @@ -40,17 +41,22 @@ public class EditCommand extends Command { + "Existing values will be overwritten by the input values.\n" + "Parameters: INDEX (must be a positive integer) " + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_GENDER + "GENDER] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_SUBJECT + "SUBJECT]... " + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_GENDER + "M" + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; + + PREFIX_EMAIL + "johndoe@example.com " + + PREFIX_SUBJECT + "CS2103"; public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_EDIT_ARCHIVED_PERSON = "This person is archived."; + public static final String MESSAGE_SUBJECT_COMPLETED = "This person has already completed given subjects."; + public static final String MESSAGE_NO_SUBJECTS = "Person must have at least one subject."; private final Index index; private final EditPersonDescriptor editPersonDescriptor; @@ -83,6 +89,7 @@ public CommandResult execute(Model model) throws CommandException { throw new CommandException(MESSAGE_DUPLICATE_PERSON); } + model.rememberMentorstack(); // save the state for undo model.setPerson(personToEdit, editedPerson); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson))); @@ -92,16 +99,44 @@ public CommandResult execute(Model model) throws CommandException { * Creates and returns a {@code Person} with the details of {@code personToEdit} * edited with {@code editPersonDescriptor}. */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { + private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) + throws CommandException { assert personToEdit != null; Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); + Gender updatedGender = editPersonDescriptor.getGender().orElse(personToEdit.getGender()); Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); + Set updatedSubject = editPersonDescriptor.getSubjects().orElse(personToEdit.getSubjects()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + //fields that cannot be edited + Set updatedFinishSubject = personToEdit.getFinishedSubjects(); + ArchiveStatus updatedIsArchive = personToEdit.getIsArchived(); + boolean updatedIsMarked = personToEdit.getIsMarked(); + + if (updatedIsArchive.testStatus()) { + throw new CommandException(MESSAGE_EDIT_ARCHIVED_PERSON); + } + + if (checkFinishedSubjectsConflict(updatedSubject, updatedFinishSubject)) { + throw new CommandException(MESSAGE_SUBJECT_COMPLETED); + } + + if (updatedSubject.size() == 0 && updatedFinishSubject.size() == 0) { + throw new CommandException(MESSAGE_NO_SUBJECTS); + } + + return new Person(updatedName, updatedGender, updatedPhone, updatedEmail, + updatedSubject, updatedFinishSubject, updatedIsArchive, updatedIsMarked); + } + + private static boolean checkFinishedSubjectsConflict(Set subjects, Set finishedSubjects) { + for (Subject s : subjects) { + if (finishedSubjects.contains(s)) { + return true; + } + } + return false; } @Override @@ -134,30 +169,30 @@ public String toString() { */ public static class EditPersonDescriptor { private Name name; + private Gender gender; private Phone phone; private Email email; - private Address address; - private Set tags; + private Set subject; public EditPersonDescriptor() {} /** * Copy constructor. - * A defensive copy of {@code tags} is used internally. + * A defensive copy of {@code subject} is used internally. */ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); + setGender(toCopy.gender); setPhone(toCopy.phone); setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); + setSubjects(toCopy.subject); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, gender, phone, email, subject); } public void setName(Name name) { @@ -168,6 +203,14 @@ public Optional getName() { return Optional.ofNullable(name); } + public void setGender(Gender gender) { + this.gender = gender; + } + + public Optional getGender() { + return Optional.ofNullable(gender); + } + public void setPhone(Phone phone) { this.phone = phone; } @@ -184,29 +227,21 @@ public Optional getEmail() { return Optional.ofNullable(email); } - public void setAddress(Address address) { - this.address = address; - } - - public Optional
    getAddress() { - return Optional.ofNullable(address); - } - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. + * Sets {@code subject} to this object's {@code subject}. + * A defensive copy of {@code subject} is used internally. */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; + public void setSubjects(Set subject) { + this.subject = (subject != null) ? new HashSet<>(subject) : null; } /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * Returns an unmodifiable subject set, which throws {@code UnsupportedOperationException} * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. + * Returns {@code Optional#empty()} if {@code subject} is null. */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + public Optional> getSubjects() { + return (subject != null) ? Optional.of(Collections.unmodifiableSet(subject)) : Optional.empty(); } @Override @@ -222,20 +257,20 @@ public boolean equals(Object other) { EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) other; return Objects.equals(name, otherEditPersonDescriptor.name) + && Objects.equals(gender, otherEditPersonDescriptor.gender) && Objects.equals(phone, otherEditPersonDescriptor.phone) && Objects.equals(email, otherEditPersonDescriptor.email) - && Objects.equals(address, otherEditPersonDescriptor.address) - && Objects.equals(tags, otherEditPersonDescriptor.tags); + && Objects.equals(subject, otherEditPersonDescriptor.subject); } @Override public String toString() { return new ToStringBuilder(this) .add("name", name) + .add("gender", gender) .add("phone", phone) .add("email", email) - .add("address", address) - .add("tags", tags) + .add("subject", subject) .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/mentorstack/logic/commands/ExitCommand.java similarity index 74% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/seedu/mentorstack/logic/commands/ExitCommand.java index 3dd85a8ba90..f9d2e2803ef 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/mentorstack/logic/commands/ExitCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; -import seedu.address.model.Model; +import seedu.mentorstack.model.Model; /** * Terminates the program. @@ -9,7 +9,7 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Mentorstack as requested ..."; @Override public CommandResult execute(Model model) { diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/mentorstack/logic/commands/FindCommand.java similarity index 84% rename from src/main/java/seedu/address/logic/commands/FindCommand.java rename to src/main/java/seedu/mentorstack/logic/commands/FindCommand.java index 72b9eddd3a7..902d71e1c81 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/mentorstack/logic/commands/FindCommand.java @@ -1,11 +1,11 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; import static java.util.Objects.requireNonNull; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.mentorstack.commons.util.ToStringBuilder; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.NameContainsKeywordsPredicate; /** * Finds and lists all persons in address book whose name contains any of the argument keywords. @@ -15,7 +15,7 @@ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain parts of any of " + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + "Example: " + COMMAND_WORD + " alice bob charlie"; diff --git a/src/main/java/seedu/mentorstack/logic/commands/FinishCommand.java b/src/main/java/seedu/mentorstack/logic/commands/FinishCommand.java new file mode 100644 index 00000000000..465a02d5739 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/commands/FinishCommand.java @@ -0,0 +1,134 @@ +package seedu.mentorstack.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.mentorstack.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.commons.util.ToStringBuilder; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Subject; + +/** + * Marks a subject of an existing person as finished in Mentorstack. + */ +public class FinishCommand extends Command { + + public static final String COMMAND_WORD = "finish"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks a subject of an existing person " + + "by the index number used in the displayed person list as finished.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_SUBJECT + "SUBJECT]... " + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_SUBJECT + "CS2103"; + + public static final String MESSAGE_FINISH_SUBJECT_SUCCESS = "Marked subjects as finished."; + public static final String MESSAGE_NO_SUBJECTS = "At least one subject must be provided."; + public static final String MESSAGE_SUBJECT_DOES_NOT_EXIST = "Student is not enrolled in subjects provided."; + + private final Index index; + private final Set subjectsToFinish; + + /** + * @param index of the person in the filtered person list to edit + * @param subjectsToFinish subjects to mark as finished + */ + public FinishCommand(Index index, Set subjectsToFinish) { + requireNonNull(index); + requireNonNull(subjectsToFinish); + + this.index = index; + this.subjectsToFinish = subjectsToFinish; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToFinish = lastShownList.get(index.getZeroBased()); + Set subjects = personToFinish.getSubjects(); + Set finishedSubjects = personToFinish.getFinishedSubjects(); + + for (Subject subject : subjectsToFinish) { + if (!subjects.contains(subject) && !finishedSubjects.contains(subject)) { + throw new CommandException(MESSAGE_SUBJECT_DOES_NOT_EXIST); + } + } + + Person finishedPerson = createFinishedPerson(personToFinish, subjectsToFinish); + + model.rememberMentorstack(); // save the state for undo + model.setPerson(personToFinish, finishedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_FINISH_SUBJECT_SUCCESS, Messages.format(finishedPerson))); + } + + /** + * Creates and returns a {@code Person} with the details of {@code personToFinish} + * finished with {@code subjectsToFinish}. + */ + private static Person createFinishedPerson(Person personToFinish, Set subjectsToFinish) + throws CommandException { + assert personToFinish != null; + assert subjectsToFinish != null; + + if (personToFinish.getIsArchived().testStatus()) { + throw new CommandException(Messages.MESSAGE_IS_ARCHIVED); + } + + Set subjects = personToFinish.getSubjects(); + Set finishedSubjects = personToFinish.getFinishedSubjects(); + + Set newSubjects = new HashSet<>(); + Set newFinishedSubjects = new HashSet<>(finishedSubjects); + + for (Subject subject : subjects) { + if (subjectsToFinish.contains(subject)) { + newFinishedSubjects.add(subject); + } else { + newSubjects.add(subject); + } + } + + return new Person(personToFinish.getName(), personToFinish.getGender(), personToFinish.getPhone(), + personToFinish.getEmail(), newSubjects, newFinishedSubjects, + personToFinish.getIsArchived(), personToFinish.getIsMarked()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FinishCommand)) { + return false; + } + + FinishCommand otherFinishCommand = (FinishCommand) other; + return index.equals(otherFinishCommand.index) + && subjectsToFinish.equals(otherFinishCommand.subjectsToFinish); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("index", index) + .add("subjectsToFinish", subjectsToFinish) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/mentorstack/logic/commands/HelpCommand.java similarity index 86% rename from src/main/java/seedu/address/logic/commands/HelpCommand.java rename to src/main/java/seedu/mentorstack/logic/commands/HelpCommand.java index bf824f91bd0..50d33f541c7 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/mentorstack/logic/commands/HelpCommand.java @@ -1,6 +1,6 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; -import seedu.address.model.Model; +import seedu.mentorstack.model.Model; /** * Format full help instructions for every command for display. diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/mentorstack/logic/commands/ListCommand.java similarity index 69% rename from src/main/java/seedu/address/logic/commands/ListCommand.java rename to src/main/java/seedu/mentorstack/logic/commands/ListCommand.java index 84be6ad2596..aff28579b56 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/mentorstack/logic/commands/ListCommand.java @@ -1,12 +1,12 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.mentorstack.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import seedu.address.model.Model; +import seedu.mentorstack.model.Model; /** - * Lists all persons in the address book to the user. + * Lists all persons in Mentorstack to the user. */ public class ListCommand extends Command { diff --git a/src/main/java/seedu/mentorstack/logic/commands/MarkCommand.java b/src/main/java/seedu/mentorstack/logic/commands/MarkCommand.java new file mode 100644 index 00000000000..12c07412e80 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/commands/MarkCommand.java @@ -0,0 +1,97 @@ +package seedu.mentorstack.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.mentorstack.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.commons.util.ToStringBuilder; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.Person; + +/** + * Marks a person identified using it's displayed index from Mentorstack. + */ +public class MarkCommand extends Command { + + public static final String COMMAND_WORD = "mark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Marks the persons identified by the index numbers used in the displayed person list.\n" + + "Parameters: INDEX1 INDEX2 ... (each must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 2 3"; + + public static final String MESSAGE_MARK_PERSON_SUCCESS = "Marked Persons: %1$s"; + + private final Set targetIndices; + + public MarkCommand(Set targetIndices) { + this.targetIndices = targetIndices; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + for (Index index : targetIndices) { + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + } + + for (Index index : targetIndices) { + Person target = lastShownList.get(index.getZeroBased()); + createMarkedPerson(target); //throws error if person is archived + } + + model.rememberMentorstack(); // save the state for undo + StringBuilder markedPersons = new StringBuilder(); + + for (Index index : targetIndices) { + Person target = lastShownList.get(index.getZeroBased()); + Person marked = createMarkedPerson(target); + model.markPerson(target, marked); + markedPersons.append(Messages.format(target)).append("\n"); + } + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_MARK_PERSON_SUCCESS, markedPersons.toString().trim())); + } + + private static Person createMarkedPerson(Person target) throws CommandException { + assert target != null; + + if (target.getIsArchived().testStatus()) { + throw new CommandException(Messages.MESSAGE_IS_ARCHIVED); + } + + return new Person(target.getName(), target.getGender(), target.getPhone(), target.getEmail(), + target.getSubjects(), target.getFinishedSubjects(), target.getIsArchived(), true); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof MarkCommand)) { + return false; + } + + MarkCommand otherMarkCommand = (MarkCommand) other; + return targetIndices.equals(otherMarkCommand.targetIndices); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndices) + .toString(); + } +} diff --git a/src/main/java/seedu/mentorstack/logic/commands/ShowArchiveCommand.java b/src/main/java/seedu/mentorstack/logic/commands/ShowArchiveCommand.java new file mode 100644 index 00000000000..e562689c930 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/commands/ShowArchiveCommand.java @@ -0,0 +1,24 @@ +package seedu.mentorstack.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.mentorstack.model.Model.PREDICATE_SHOW_ALL_ARCHIVED_PERSONS; + +import seedu.mentorstack.model.Model; + +/** + * Lists all persons in Mentorstack to the user. + */ +public class ShowArchiveCommand extends Command { + + public static final String COMMAND_WORD = "showarchive"; + + public static final String MESSAGE_SUCCESS = "Showed all archived persons"; + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_ARCHIVED_PERSONS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/mentorstack/logic/commands/StatsCommand.java b/src/main/java/seedu/mentorstack/logic/commands/StatsCommand.java new file mode 100644 index 00000000000..95405ad9c35 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/commands/StatsCommand.java @@ -0,0 +1,111 @@ +package seedu.mentorstack.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.mentorstack.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.Gender; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Subject; + +/** + * Displays statistics about the persons in Mentorstack. + * If a subject is specified, it filters stats by that subject. + */ +public class StatsCommand extends Command { + + public static final String COMMAND_WORD = "stats"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows statistics of the Mentorstack.\n" + + "Usage: " + COMMAND_WORD + " s/[SUBJECT]\n" + + "Example: " + COMMAND_WORD + " s/CS1010S"; + + public static final String MESSAGE_SUCCESS = + "Total Persons: %d\nGender\nMale: %d\nFemale: %d"; + + private static final Logger logger = Logger.getLogger(StatsCommand.class.getName()); + + private final Subject subject; + + /** + * Initialise the statistics command with no parameter input + */ + public StatsCommand() { + this.subject = null; + logger.log(Level.INFO, "StatsCommand created without a subject filter."); + } + + /** + * Initialise the statistics command + * filters the students statistics based on the input subject + * @param subject + */ + public StatsCommand(Subject subject) { + this.subject = subject; + logger.log(Level.INFO, "StatsCommand created with subject filter: {0}", subject); + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + logger.log(Level.INFO, "Executing StatsCommand..."); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); // navigate to active list + List personList = model.getFilteredPersonList(); + logger.log(Level.INFO, "Total persons before filtering: {0}", personList.size()); + + Predicate subjectFilter; + Predicate personFilter; + + if (subject == null) { + subjectFilter = p -> true; // No filtering + logger.log(Level.INFO, "No subject filter applied."); + } else { + subjectFilter = p -> p.getSubjects().stream() + .anyMatch(sub -> sub.equals(subject)); + logger.log(Level.INFO, "Applying subject filter for: {0}", subject); + } + + personList = personList.stream() + .filter(subjectFilter) + .collect(Collectors.toList()); + personFilter = personList::contains; + + model.updateFilteredPersonList(personFilter); + + int totalPersons = personList.size(); + Gender maleGender = new Gender("M"); + int maleCount = (int) personList.stream().filter(p -> maleGender.equals(p.getGender())).count(); + int femaleCount = totalPersons - maleCount; + + logger.log(Level.INFO, "Filtered total persons: {0}", totalPersons); + logger.log(Level.INFO, "Male count: {0}, Female count: {1}", new Object[]{maleCount, femaleCount}); + + String resultMessage = subject == null + ? String.format(MESSAGE_SUCCESS, totalPersons, maleCount, femaleCount) + : String.format("Statistics for %s:\n" + MESSAGE_SUCCESS, subject, totalPersons, + maleCount, femaleCount); + + logger.log(Level.INFO, "StatsCommand executed successfully."); + return new CommandResult(resultMessage); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof StatsCommand)) { + return false; + } + + StatsCommand otherStatsCommand = (StatsCommand) other; + return subject.equals(otherStatsCommand.subject); + } +} diff --git a/src/main/java/seedu/mentorstack/logic/commands/UnarchiveCommand.java b/src/main/java/seedu/mentorstack/logic/commands/UnarchiveCommand.java new file mode 100644 index 00000000000..bf9ff7b6459 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/commands/UnarchiveCommand.java @@ -0,0 +1,95 @@ +package seedu.mentorstack.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.commons.util.ToStringBuilder; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.Person; + +/** + * Unarchives a person identified using it's displayed index from Mentorstack. + */ +public class UnarchiveCommand extends Command { + + public static final String COMMAND_WORD = "unarchive"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Unarchives the persons identified by the index numbers used in the displayed person list.\n" + + "Parameters: INDEX1 INDEX2 ... (each must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 2 3"; + + public static final String MESSAGE_UNARCHIVE_PERSON_SUCCESS = "Unarchived Persons: %1$s\n" + + "Here is the list of all archived persons:"; + + public static final String MESSAGE_DUPLICATE_UNARCHIVE = "This person is already unarchived: "; + + private final Set targetIndices; + + public UnarchiveCommand(Set targetIndices) { + this.targetIndices = targetIndices; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + // Ensure all indices are valid before unarchive + for (Index index : targetIndices) { + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + } + + for (Index index : targetIndices) { + if (!lastShownList.get(index.getZeroBased()).getIsArchived().testStatus()) { + throw new CommandException(MESSAGE_DUPLICATE_UNARCHIVE + + Messages.format(lastShownList.get(index.getZeroBased()))); + } + } + + ArrayList personsToUnarchive = new ArrayList(); + for (Index index : targetIndices) { + Person personToUnarchive = lastShownList.get(index.getZeroBased()); + personsToUnarchive.add(personToUnarchive); + } + + // Perform unarchiving + model.rememberMentorstack(); // save the state for undo + StringBuilder unarchivedPersons = new StringBuilder(); + for (Person personToUnarchive : personsToUnarchive) { + model.unarchivePerson(personToUnarchive, personToUnarchive.unarchived()); + unarchivedPersons.append(Messages.format(personToUnarchive)).append("\n"); + } + return new CommandResult(String.format(MESSAGE_UNARCHIVE_PERSON_SUCCESS, unarchivedPersons.toString().trim())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UnarchiveCommand)) { + return false; + } + + UnarchiveCommand otherUnarchiveCommand = (UnarchiveCommand) other; + return targetIndices.equals(otherUnarchiveCommand.targetIndices); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndices) + .toString(); + } +} diff --git a/src/main/java/seedu/mentorstack/logic/commands/UndoCommand.java b/src/main/java/seedu/mentorstack/logic/commands/UndoCommand.java new file mode 100644 index 00000000000..09326b73923 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/commands/UndoCommand.java @@ -0,0 +1,26 @@ +package seedu.mentorstack.logic.commands; + +import static seedu.mentorstack.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; + +/** + * Undo the last operation that changed the data. + */ +public class UndoCommand extends Command { + public static final String COMMAND_WORD = "undo"; + public static final String MESSAGE_SUCCESS = "Undo successful!"; + public static final String MESSAGE_FAILURE = "Nothing to undo."; + + @Override + public CommandResult execute(Model model) throws CommandException { + if (!model.canUndo()) { + throw new CommandException(MESSAGE_FAILURE); + } + assert model.canUndo() : "not undoable"; + model.undo(); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(MESSAGE_SUCCESS); + } +} diff --git a/src/main/java/seedu/mentorstack/logic/commands/UnfinishCommand.java b/src/main/java/seedu/mentorstack/logic/commands/UnfinishCommand.java new file mode 100644 index 00000000000..ab0af32c2c3 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/commands/UnfinishCommand.java @@ -0,0 +1,134 @@ +package seedu.mentorstack.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.mentorstack.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.commons.util.ToStringBuilder; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Subject; + +/** + * Marks a subject of an existing person as unfinished in Mentorstack. + */ +public class UnfinishCommand extends Command { + + public static final String COMMAND_WORD = "unfinish"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Marks a subject of an existing person " + + "by the index number used in the displayed person list as unfinished.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_SUBJECT + "SUBJECT]... " + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_SUBJECT + "CS2103"; + + public static final String MESSAGE_UNFINISH_SUBJECT_SUCCESS = "Marked subjects as unfinished."; + public static final String MESSAGE_NO_SUBJECTS = "At least one subject must be provided."; + public static final String MESSAGE_SUBJECT_DOES_NOT_EXIST = "Student is not enrolled in subjects provided."; + + private final Index index; + private final Set subjectsToUnfinish; + + /** + * @param index of the person in the filtered person list to edit + * @param subjectsToUnfinish subjects to mark as unfinished + */ + public UnfinishCommand(Index index, Set subjectsToUnfinish) { + requireNonNull(index); + requireNonNull(subjectsToUnfinish); + + this.index = index; + this.subjectsToUnfinish = subjectsToUnfinish; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + Person personToUnfinish = lastShownList.get(index.getZeroBased()); + Set subjects = personToUnfinish.getSubjects(); + Set finishedSubjects = personToUnfinish.getFinishedSubjects(); + + for (Subject subject : subjectsToUnfinish) { + if (!subjects.contains(subject) && !finishedSubjects.contains(subject)) { + throw new CommandException(MESSAGE_SUBJECT_DOES_NOT_EXIST); + } + } + + Person unfinishedPerson = createUnfinishedPerson(personToUnfinish, subjectsToUnfinish); + + model.rememberMentorstack(); // save the state for undo + model.setPerson(personToUnfinish, unfinishedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_UNFINISH_SUBJECT_SUCCESS, Messages.format(unfinishedPerson))); + } + + /** + * Creates and returns a {@code Person} with the details of {@code personToUnfinish} + * unfinished with {@code subjectsToUnfinish}. + */ + private static Person createUnfinishedPerson(Person personToUnfinish, Set subjectsToUnfinish) + throws CommandException { + assert personToUnfinish != null; + assert subjectsToUnfinish != null; + + if (personToUnfinish.getIsArchived().testStatus()) { + throw new CommandException(Messages.MESSAGE_IS_ARCHIVED); + } + + Set subjects = personToUnfinish.getSubjects(); + Set finishedSubjects = personToUnfinish.getFinishedSubjects(); + + Set newSubjects = new HashSet<>(subjects); + Set newFinishedSubjects = new HashSet<>(); + + for (Subject subject : finishedSubjects) { + if (subjectsToUnfinish.contains(subject)) { + newSubjects.add(subject); + } else { + newFinishedSubjects.add(subject); + } + } + + return new Person(personToUnfinish.getName(), personToUnfinish.getGender(), personToUnfinish.getPhone(), + personToUnfinish.getEmail(), newSubjects, newFinishedSubjects, + personToUnfinish.getIsArchived(), personToUnfinish.getIsMarked()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UnfinishCommand)) { + return false; + } + + UnfinishCommand otherUnfinishCommand = (UnfinishCommand) other; + return index.equals(otherUnfinishCommand.index) + && subjectsToUnfinish.equals(otherUnfinishCommand.subjectsToUnfinish); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("index", index) + .add("subjectsToUnfinish", subjectsToUnfinish) + .toString(); + } +} diff --git a/src/main/java/seedu/mentorstack/logic/commands/UnmarkCommand.java b/src/main/java/seedu/mentorstack/logic/commands/UnmarkCommand.java new file mode 100644 index 00000000000..f6df4d98aee --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/commands/UnmarkCommand.java @@ -0,0 +1,97 @@ +package seedu.mentorstack.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.mentorstack.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.commons.util.ToStringBuilder; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.Person; + +/** + * Unmarks a person identified using it's displayed index from Mentorstack. + */ +public class UnmarkCommand extends Command { + + public static final String COMMAND_WORD = "unmark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Unmarks the persons identified by the index numbers used in the displayed person list.\n" + + "Parameters: INDEX1 INDEX2 ... (each must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1 2 3"; + + public static final String MESSAGE_UNMARK_PERSON_SUCCESS = "Unmarked Persons: %1$s"; + + private final Set targetIndices; + + public UnmarkCommand(Set targetIndices) { + this.targetIndices = targetIndices; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredPersonList(); + + for (Index index : targetIndices) { + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + } + + for (Index index : targetIndices) { + Person target = lastShownList.get(index.getZeroBased()); + createUnmarkedPerson(target); //throws error if person is archived + } + + model.rememberMentorstack(); // save the state for undo + StringBuilder unmarkedPersons = new StringBuilder(); + + for (Index index : targetIndices) { + Person target = lastShownList.get(index.getZeroBased()); + Person unmarked = createUnmarkedPerson(target); + model.unmarkPerson(target, unmarked); + unmarkedPersons.append(Messages.format(target)).append("\n"); + } + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_UNMARK_PERSON_SUCCESS, unmarkedPersons.toString().trim())); + } + + private static Person createUnmarkedPerson(Person target) throws CommandException { + assert target != null; + + if (target.getIsArchived().testStatus()) { + throw new CommandException(Messages.MESSAGE_IS_ARCHIVED); + } + + return new Person(target.getName(), target.getGender(), target.getPhone(), target.getEmail(), + target.getSubjects(), target.getFinishedSubjects(), target.getIsArchived(), false); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UnmarkCommand)) { + return false; + } + + UnmarkCommand otherUnmarkCommand = (UnmarkCommand) other; + return targetIndices.equals(otherUnmarkCommand.targetIndices); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("targetIndex", targetIndices) + .toString(); + } +} diff --git a/src/main/java/seedu/mentorstack/logic/commands/ViewCommand.java b/src/main/java/seedu/mentorstack/logic/commands/ViewCommand.java new file mode 100644 index 00000000000..152bea5500d --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/commands/ViewCommand.java @@ -0,0 +1,71 @@ +package seedu.mentorstack.logic.commands; + +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_FILTER_TYPE; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_FILTER_VALUE; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.mentorstack.commons.util.ToStringBuilder; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.Person; + +/** + * Finds and lists all persons in Mentorstack according to the filter. + * Keyword matching is case insensitive. + */ +public class ViewCommand extends Command { + public static final String COMMAND_WORD = "view"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Displays a list of students matching the filter. " + + "Optional filters can be applied.\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_FILTER_TYPE + " s " + PREFIX_FILTER_VALUE + " A"; + + public static final String MESSAGE_SUCCESS = "Filtered Students:\n%s"; + public static final String MESSAGE_NO_MATCH = "No students match the given criteria."; + public static final String MESSAGE_INVALID_FILTER = "Invalid filter type or value."; + + public final Predicate predicate; + + public ViewCommand(Predicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + model.updateFilteredPersonList(predicate); + List filteredStudents = model.getFilteredPersonList() + .stream() + .filter(predicate) + .map(person -> person.getName().fullName) // Extract only the name + .toList(); + + if (filteredStudents.isEmpty()) { + return new CommandResult(MESSAGE_NO_MATCH); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, filteredStudents)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ViewCommand)) { + return false; + } + + ViewCommand otherViewCommand = (ViewCommand) other; + return predicate.equals(otherViewCommand.predicate); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/mentorstack/logic/commands/exceptions/CommandException.java similarity index 89% rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java rename to src/main/java/seedu/mentorstack/logic/commands/exceptions/CommandException.java index a16bd14f2cd..234f1ab2ad0 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/mentorstack/logic/commands/exceptions/CommandException.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands.exceptions; +package seedu.mentorstack.logic.commands.exceptions; /** * Represents an error which occurs during execution of a {@link Command}. diff --git a/src/main/java/seedu/mentorstack/logic/parser/AddCommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/AddCommandParser.java new file mode 100644 index 00000000000..84d53719e58 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/AddCommandParser.java @@ -0,0 +1,93 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import seedu.mentorstack.logic.commands.AddCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; +import seedu.mentorstack.model.person.Email; +import seedu.mentorstack.model.person.Gender; +import seedu.mentorstack.model.person.Name; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Phone; +import seedu.mentorstack.model.person.Subject; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class AddCommandParser extends CommandParser implements Parser { + + private static final Logger logger = Logger.getLogger(AddCommandParser.class.getName()); + + @Override + public AddCommand parse(String args) throws ParseException, ParseWithHintException { + logger.log(Level.INFO, "Parsing AddCommand with arguments: {0}", args); + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_GENDER, PREFIX_EMAIL, + PREFIX_PHONE, PREFIX_SUBJECT); + + Map ideal = new HashMap<>(); + ideal.put("n/", "John Doe"); + ideal.put("e/", "johnd@example.com"); + ideal.put("p/", "98765432"); + ideal.put("s/", "CS2103"); + ideal.put("g/", "F"); + + String missing = super.getMissingArgs(argMultimap, ideal); + + if (missing.length() > 0) { + throw new ParseWithHintException( + String.format( + MESSAGE_INVALID_COMMAND_FORMAT, + AddCommand.MESSAGE_USAGE + ), + missing + ); + } else if (!arePrefixesPresent( + argMultimap, + PREFIX_NAME, + PREFIX_GENDER, + PREFIX_EMAIL, + PREFIX_PHONE, + PREFIX_SUBJECT) || !argMultimap.getPreamble().isEmpty()) { + logger.log(Level.WARNING, "Missing required prefixes or unexpected preamble."); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_GENDER, PREFIX_PHONE, PREFIX_EMAIL); + + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + Gender gender = ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get()); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + Set subjectList = ParserUtil.parseSubjects(argMultimap.getAllValues(PREFIX_SUBJECT)); + + Person person = new Person(name, gender, phone, email, subjectList); + + logger.log(Level.INFO, "Successfully parsed AddCommand: {0}", person); + + return new AddCommand(person); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/mentorstack/logic/parser/ArchiveCommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/ArchiveCommandParser.java new file mode 100644 index 00000000000..0783d456a60 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/ArchiveCommandParser.java @@ -0,0 +1,37 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.ArchiveCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; + +/** + * Parses input arguments and creates a new ArchiveCommand object + */ +public class ArchiveCommandParser extends CommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ArchiveCommand + * and returns a ArchiveCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ArchiveCommand parse(String args) throws ParseException, ParseWithHintException { + try { + Set index = ParserUtil.parseIndexes(args); + return new ArchiveCommand(index); + } catch (ParseException pe) { + throw new ParseWithHintException( + String.format( + MESSAGE_INVALID_COMMAND_FORMAT, + ArchiveCommand.MESSAGE_USAGE), + pe, + "INDEX [INDEX] [INDEX]" + ); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/mentorstack/logic/parser/ArgumentMultimap.java similarity index 91% rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java rename to src/main/java/seedu/mentorstack/logic/parser/ArgumentMultimap.java index 21e26887a83..4a6b64d4021 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/mentorstack/logic/parser/ArgumentMultimap.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.mentorstack.logic.parser; import java.util.ArrayList; import java.util.HashMap; @@ -7,8 +7,8 @@ import java.util.Optional; import java.util.stream.Stream; -import seedu.address.logic.Messages; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.parser.exceptions.ParseException; /** * Stores mapping of prefixes to their respective arguments. @@ -55,6 +55,10 @@ public List getAllValues(Prefix prefix) { return new ArrayList<>(argMultimap.get(prefix)); } + public HashMap> getArgMap() { + return new HashMap>(this.argMultimap); + } + /** * Returns the preamble (text before the first valid prefix). Trims any leading/trailing spaces. */ diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/mentorstack/logic/parser/ArgumentTokenizer.java similarity index 99% rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java rename to src/main/java/seedu/mentorstack/logic/parser/ArgumentTokenizer.java index 5c9aebfa488..fb113485f7c 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/mentorstack/logic/parser/ArgumentTokenizer.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.mentorstack.logic.parser; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/mentorstack/logic/parser/CliSyntax.java similarity index 52% rename from src/main/java/seedu/address/logic/parser/CliSyntax.java rename to src/main/java/seedu/mentorstack/logic/parser/CliSyntax.java index 75b1a9bf119..1cf6a8bd0ff 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/mentorstack/logic/parser/CliSyntax.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.mentorstack.logic.parser; /** * Contains Command Line Interface (CLI) syntax definitions common to multiple commands @@ -7,9 +7,11 @@ public class CliSyntax { /* Prefix definitions */ public static final Prefix PREFIX_NAME = new Prefix("n/"); + public static final Prefix PREFIX_GENDER = new Prefix("g/"); public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_SUBJECT = new Prefix("s/"); + public static final Prefix PREFIX_FILTER_TYPE = new Prefix("f/"); + public static final Prefix PREFIX_FILTER_VALUE = new Prefix("v/"); } diff --git a/src/main/java/seedu/mentorstack/logic/parser/CommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/CommandParser.java new file mode 100644 index 00000000000..7647e5221cc --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/CommandParser.java @@ -0,0 +1,53 @@ +package seedu.mentorstack.logic.parser; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import seedu.mentorstack.logic.commands.Command; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; + +/** + * Abstract class responsible for parsing commands. This class defines a method for parsing arguments + * and checking for missing arguments based on an ideal set of arguments. + * Subclasses must implement the {@link #parse(String)} method to provide the logic for parsing the + * command arguments. + */ +public abstract class CommandParser { + + /** + * Parses the given arguments into a {@link Command} object. + * + * @param args The arguments to parse. + * @return A {@link Command} object representing the parsed command. + * @throws ParseException If there is an error during parsing. + * @throws ParseWithHintException If there is an error during parsing with a hint provided. + */ + public abstract Command parse(String args) throws ParseException, ParseWithHintException; + + /** + * Checks for missing arguments by comparing the provided arguments with an ideal set of arguments. + * The method generates a string containing the missing arguments from the ideal set, + * appending their associated values. + * + * @param argMultimap A map of argument prefixes to argument values. + * @param ideal A map of ideal argument prefixes to their corresponding values. + * @return A string containing the missing arguments in the format "prefix value". + */ + public String getMissingArgs(ArgumentMultimap argMultimap, Map ideal) { + Set hset = new HashSet(); + for (Map.Entry> entry : argMultimap.getArgMap().entrySet()) { + hset.add(entry.getKey().toString()); + } + + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : ideal.entrySet()) { + if (!hset.contains(entry.getKey())) { + sb.append(" " + entry.getKey() + entry.getValue()); + } + } + return sb.toString().trim(); + } +} diff --git a/src/main/java/seedu/mentorstack/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/DeleteCommandParser.java new file mode 100644 index 00000000000..48530bd8e92 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/DeleteCommandParser.java @@ -0,0 +1,43 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.DeleteCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; + +/** + * Parses input arguments and creates a new DeleteCommand object + */ +public class DeleteCommandParser extends CommandParser implements Parser { + + private static final Logger logger = Logger.getLogger(DeleteCommandParser.class.getName()); + + /** + * Parses the given {@code String} of arguments in the context of the DeleteCommand + * and returns a DeleteCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteCommand parse(String args) throws ParseException, ParseWithHintException { + logger.log(Level.INFO, "Parsing DeleteCommand with arguments: {0}", args); + try { + Set indices = ParserUtil.parseIndexes(args); + logger.log(Level.INFO, "Successfully parsed indices: {0}", indices); + return new DeleteCommand(indices); + } catch (ParseException pe) { + logger.log(Level.WARNING, "Failed to parse DeleteCommand arguments", pe); + throw new ParseWithHintException( + String.format( + MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE), + pe, + "INDEX [INDEX] [INDEX]" + ); + } + } +} diff --git a/src/main/java/seedu/mentorstack/logic/parser/EditCommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/EditCommandParser.java new file mode 100644 index 00000000000..3509eb4be7d --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/EditCommandParser.java @@ -0,0 +1,107 @@ +package seedu.mentorstack.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.EditCommand; +import seedu.mentorstack.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; +import seedu.mentorstack.model.person.Subject; + +/** + * Parses input arguments and creates a new EditCommand object + */ +public class EditCommandParser extends CommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditCommand + * and returns an EditCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public EditCommand parse(String args) throws ParseException, ParseWithHintException { + + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, + PREFIX_NAME, PREFIX_GENDER, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_SUBJECT); + + Index index; + + Map ideal = new HashMap<>(); + ideal.put("[n/", "John Doe]"); + ideal.put("[e/", "johnd@example.com]"); + ideal.put("[p/", "98765432]"); + ideal.put("[s/", "CS2103]"); + ideal.put("[g/", "F]"); + + String missing = super.getMissingArgs(argMultimap, ideal); + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseWithHintException(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, + EditCommand.MESSAGE_USAGE), + pe, + "INDEX " + missing + ); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_GENDER, PREFIX_PHONE, PREFIX_EMAIL); + + EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_GENDER).isPresent()) { + editPersonDescriptor.setGender(ParserUtil.parseGender(argMultimap.getValue(PREFIX_GENDER).get())); + } + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); + } + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); + } + parseSubjectsForEdit(argMultimap.getAllValues(PREFIX_SUBJECT)).ifPresent(editPersonDescriptor::setSubjects); + + if (!editPersonDescriptor.isAnyFieldEdited()) { + throw new ParseWithHintException(EditCommand.MESSAGE_NOT_EDITED, missing); + } + + return new EditCommand(index, editPersonDescriptor); + } + + /** + * Parses {@code Collection subjects} into a {@code Set} if {@code subjects} is non-empty. + * If {@code subjects} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero subjects. + */ + private Optional> parseSubjectsForEdit(Collection subjects) throws ParseException { + assert subjects != null; + + if (subjects.isEmpty()) { + return Optional.empty(); + } + + Collection subjectSet = subjects.size() == 1 && subjects.contains("") + ? Collections.emptySet() : subjects; + return Optional.of(ParserUtil.parseSubjects(subjectSet)); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/FindCommandParser.java similarity index 50% rename from src/main/java/seedu/address/logic/parser/FindCommandParser.java rename to src/main/java/seedu/mentorstack/logic/parser/FindCommandParser.java index 2867bde857b..d938e84a7c9 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/mentorstack/logic/parser/FindCommandParser.java @@ -1,28 +1,29 @@ -package seedu.address.logic.parser; +package seedu.mentorstack.logic.parser; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import java.util.Arrays; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.mentorstack.logic.commands.FindCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; +import seedu.mentorstack.model.person.NameContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object */ -public class FindCommandParser implements Parser { +public class FindCommandParser extends CommandParser implements Parser { /** * Parses the given {@code String} of arguments in the context of the FindCommand * and returns a FindCommand object for execution. * @throws ParseException if the user input does not conform the expected format */ - public FindCommand parse(String args) throws ParseException { + public FindCommand parse(String args) throws ParseException, ParseWithHintException { String trimmedArgs = args.trim(); if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + throw new ParseWithHintException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE), "NAME [NAME] [NAME]"); } String[] nameKeywords = trimmedArgs.split("\\s+"); diff --git a/src/main/java/seedu/mentorstack/logic/parser/FinishCommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/FinishCommandParser.java new file mode 100644 index 00000000000..07dd9e8aacd --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/FinishCommandParser.java @@ -0,0 +1,69 @@ +package seedu.mentorstack.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; + +import java.util.Collection; +import java.util.Optional; +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.FinishCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; +import seedu.mentorstack.model.person.Subject; + +/** + * Parses input arguments and creates a new FinishCommand object + */ +public class FinishCommandParser extends CommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FinishCommand + * and returns an FinishCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public FinishCommand parse(String args) throws ParseException, ParseWithHintException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_SUBJECT); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseWithHintException(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, + FinishCommand.MESSAGE_USAGE), + pe, + "INDEX [s/SUBJECT] [s/SUBJECT]" + ); + } + + Set subjectsToFinish = parseSubjectsForFinish(argMultimap.getAllValues(PREFIX_SUBJECT)) + .orElseThrow(() -> new ParseWithHintException( + FinishCommand.MESSAGE_NO_SUBJECTS, + "[s/SUBJECT] [s/SUBJECT]" + )); + + return new FinishCommand(index, subjectsToFinish); + } + + /** + * Parses {@code Collection subjects} into a {@code Set} if {@code subjects} is non-empty. + * If {@code subjects} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero subjects. + */ + private Optional> parseSubjectsForFinish(Collection subjects) throws ParseException { + assert subjects != null; + + if (subjects.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(ParserUtil.parseSubjects(subjects)); + } + +} diff --git a/src/main/java/seedu/mentorstack/logic/parser/MarkCommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/MarkCommandParser.java new file mode 100644 index 00000000000..df13fa80e6c --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/MarkCommandParser.java @@ -0,0 +1,37 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.MarkCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; + +/** + * Parses input arguments and creates a new MarkCommand object + */ +public class MarkCommandParser extends CommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the MarkCommand + * and returns a MarkCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public MarkCommand parse(String args) throws ParseException, ParseWithHintException { + try { + Set index = ParserUtil.parseIndexes(args); + return new MarkCommand(index); + } catch (ParseException pe) { + throw new ParseWithHintException( + String.format( + MESSAGE_INVALID_COMMAND_FORMAT, + MarkCommand.MESSAGE_USAGE), + pe, + "INDEX [INDEX] [INDEX]" + ); + } + } + +} diff --git a/src/main/java/seedu/mentorstack/logic/parser/MentorstackParser.java b/src/main/java/seedu/mentorstack/logic/parser/MentorstackParser.java new file mode 100644 index 00000000000..4dc3c82c396 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/MentorstackParser.java @@ -0,0 +1,126 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.Messages.MESSAGE_UNKNOWN_COMMAND; + +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.mentorstack.commons.core.LogsCenter; +import seedu.mentorstack.logic.commands.AddCommand; +import seedu.mentorstack.logic.commands.ArchiveCommand; +import seedu.mentorstack.logic.commands.ClearCommand; +import seedu.mentorstack.logic.commands.Command; +import seedu.mentorstack.logic.commands.DeleteCommand; +import seedu.mentorstack.logic.commands.EditCommand; +import seedu.mentorstack.logic.commands.ExitCommand; +import seedu.mentorstack.logic.commands.FindCommand; +import seedu.mentorstack.logic.commands.FinishCommand; +import seedu.mentorstack.logic.commands.HelpCommand; +import seedu.mentorstack.logic.commands.ListCommand; +import seedu.mentorstack.logic.commands.MarkCommand; +import seedu.mentorstack.logic.commands.ShowArchiveCommand; +import seedu.mentorstack.logic.commands.StatsCommand; +import seedu.mentorstack.logic.commands.UnarchiveCommand; +import seedu.mentorstack.logic.commands.UndoCommand; +import seedu.mentorstack.logic.commands.UnfinishCommand; +import seedu.mentorstack.logic.commands.UnmarkCommand; +import seedu.mentorstack.logic.commands.ViewCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; + +/** + * Parses user input. + */ +public class MentorstackParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + private static final Logger logger = LogsCenter.getLogger(MentorstackParser.class); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String arguments = matcher.group("arguments"); + + // Note to developers: Change the log level in config.json to enable lower level (i.e., FINE, FINER and lower) + // log messages such as the one below. + // Lower level log messages are used sparingly to minimize noise in the code. + logger.fine("Command word: " + commandWord + "; Arguments: " + arguments); + + switch (commandWord) { + + case AddCommand.COMMAND_WORD: + return new AddCommandParser().parse(arguments); + + case EditCommand.COMMAND_WORD: + return new EditCommandParser().parse(arguments); + + case DeleteCommand.COMMAND_WORD: + return new DeleteCommandParser().parse(arguments); + + case ClearCommand.COMMAND_WORD: + return new ClearCommand(); + + case FindCommand.COMMAND_WORD: + return new FindCommandParser().parse(arguments); + + case ListCommand.COMMAND_WORD: + return new ListCommand(); + + case ExitCommand.COMMAND_WORD: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + return new HelpCommand(); + + case ViewCommand.COMMAND_WORD: + return new ViewCommandParser().parse(arguments); + + case ArchiveCommand.COMMAND_WORD: + return new ArchiveCommandParser().parse(arguments); + + case UnarchiveCommand.COMMAND_WORD: + return new UnarchiveCommandParser().parse(arguments); + + case UndoCommand.COMMAND_WORD: + return new UndoCommand(); + + case StatsCommand.COMMAND_WORD: + return new StatsCommandParser().parse(arguments); + + case MarkCommand.COMMAND_WORD: + return new MarkCommandParser().parse(arguments); + + case UnmarkCommand.COMMAND_WORD: + return new UnmarkCommandParser().parse(arguments); + + case ShowArchiveCommand.COMMAND_WORD: + return new ShowArchiveCommand(); + + case FinishCommand.COMMAND_WORD: + return new FinishCommandParser().parse(arguments); + + case UnfinishCommand.COMMAND_WORD: + return new UnfinishCommandParser().parse(arguments); + + default: + logger.finer("This user input caused a ParseException: " + userInput); + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/mentorstack/logic/parser/Parser.java similarity index 70% rename from src/main/java/seedu/address/logic/parser/Parser.java rename to src/main/java/seedu/mentorstack/logic/parser/Parser.java index d6551ad8e3f..7226ac3691f 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/mentorstack/logic/parser/Parser.java @@ -1,7 +1,7 @@ -package seedu.address.logic.parser; +package seedu.mentorstack.logic.parser; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.commands.Command; +import seedu.mentorstack.logic.parser.exceptions.ParseException; /** * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/mentorstack/logic/parser/ParserUtil.java similarity index 52% rename from src/main/java/seedu/address/logic/parser/ParserUtil.java rename to src/main/java/seedu/mentorstack/logic/parser/ParserUtil.java index b117acb9c55..d16043300b1 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/mentorstack/logic/parser/ParserUtil.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.mentorstack.logic.parser; import static java.util.Objects.requireNonNull; @@ -6,14 +6,14 @@ import java.util.HashSet; import java.util.Set; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.commons.util.StringUtil; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.model.person.Email; +import seedu.mentorstack.model.person.Gender; +import seedu.mentorstack.model.person.Name; +import seedu.mentorstack.model.person.Phone; +import seedu.mentorstack.model.person.Subject; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -35,6 +35,25 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + /** + * Parses a sequence of indices separated by spaces into a {@code Set}. + * @throws ParseException if any specified index is invalid. + */ + public static Set parseIndexes(String indices) throws ParseException { + requireNonNull(indices); + Set indexSet = new HashSet<>(); + Set valueSet = new HashSet<>(); + for (String indexStr : indices.trim().split("\\s+")) { + Index index = parseIndex(indexStr); + int value = index.getZeroBased(); + if (!valueSet.contains(value)) { + indexSet.add(index); // Explicitly handle exceptions at this level + valueSet.add(value); + } + } + return indexSet; + } + /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -50,6 +69,21 @@ public static Name parseName(String name) throws ParseException { return new Name(trimmedName); } + /** + * Parses a {@code String gender} into a {@code Gender}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code gender} is invalid. + */ + public static Gender parseGender(String gender) throws ParseException { + requireNonNull(gender); + String trimmedGender = gender.trim(); + if (!Gender.isValidGender(trimmedGender)) { + throw new ParseException(Gender.MESSAGE_CONSTRAINTS); + } + return new Gender(trimmedGender); + } + /** * Parses a {@code String phone} into a {@code Phone}. * Leading and trailing whitespaces will be trimmed. @@ -65,21 +99,6 @@ public static Phone parsePhone(String phone) throws ParseException { return new Phone(trimmedPhone); } - /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws ParseException if the given {@code address} is invalid. - */ - public static Address parseAddress(String address) throws ParseException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new ParseException(Address.MESSAGE_CONSTRAINTS); - } - return new Address(trimmedAddress); - } - /** * Parses a {@code String email} into an {@code Email}. * Leading and trailing whitespaces will be trimmed. @@ -96,29 +115,29 @@ public static Email parseEmail(String email) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String subject} into a {@code Subject}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code subject} is invalid. */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + public static Subject parseSubjects(String subject) throws ParseException { + requireNonNull(subject); + String trimmedSubject = subject.trim(); + if (!Subject.isValidSubjectName(trimmedSubject)) { + throw new ParseException(Subject.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); + return new Subject(trimmedSubject); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses {@code Collection subject} into a {@code Set}. */ - public static Set parseTags(Collection tags) throws ParseException { - requireNonNull(tags); - final Set tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); + public static Set parseSubjects(Collection subject) throws ParseException { + requireNonNull(subject); + final Set subjectSet = new HashSet<>(); + for (String subjectName : subject) { + subjectSet.add(parseSubjects(subjectName)); } - return tagSet; + return subjectSet; } } diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/mentorstack/logic/parser/Prefix.java similarity index 95% rename from src/main/java/seedu/address/logic/parser/Prefix.java rename to src/main/java/seedu/mentorstack/logic/parser/Prefix.java index 348b7686c8a..238723a6ec0 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/seedu/mentorstack/logic/parser/Prefix.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.mentorstack.logic.parser; /** * A prefix that marks the beginning of an argument in an arguments string. diff --git a/src/main/java/seedu/mentorstack/logic/parser/StatsCommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/StatsCommandParser.java new file mode 100644 index 00000000000..499a27aa2f3 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/StatsCommandParser.java @@ -0,0 +1,71 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; + +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import seedu.mentorstack.logic.commands.StatsCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; +import seedu.mentorstack.model.person.Subject; + +/** + * Parses the given {@code String} of arguments in the context of the StatsCommand + * and returns a StatsCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ +public class StatsCommandParser extends CommandParser implements Parser { + + private static final Logger logger = Logger.getLogger(StatsCommandParser.class.getName()); + + @Override + public StatsCommand parse(String args) throws ParseException, ParseWithHintException { + logger.log(Level.INFO, "Parsing StatsCommand with arguments: {0}", args); + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + logger.log(Level.INFO, "No arguments provided, returning StatsCommand without filtering."); + return new StatsCommand(); + } + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_SUBJECT); + + if (!arePrefixesPresent(argMultimap, PREFIX_SUBJECT) + || !argMultimap.getPreamble().isEmpty()) { + logger.log(Level.WARNING, "Invalid command format for StatsCommand."); + throw new ParseWithHintException(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, + StatsCommand.MESSAGE_USAGE), + "s/[SUBJECT]" + ); + } + + Subject subject; + try { + subject = ParserUtil.parseSubjects(argMultimap.getValue(PREFIX_SUBJECT).get()); + logger.log(Level.INFO, "Successfully parsed subject: {0}", subject); + } catch (ParseException pe) { + logger.log(Level.WARNING, "Failed to parse subject in StatsCommand.", pe); + throw new ParseWithHintException(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, + StatsCommand.MESSAGE_USAGE), + pe, + "[SUBJECT]" + ); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_SUBJECT); + + return new StatsCommand(subject); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/mentorstack/logic/parser/UnarchiveCommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/UnarchiveCommandParser.java new file mode 100644 index 00000000000..baf284939a8 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/UnarchiveCommandParser.java @@ -0,0 +1,36 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.UnarchiveCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; + +/** + * Parses input arguments and creates a new UnarchiveCommand object + */ +public class UnarchiveCommandParser extends CommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the UnarchiveCommand + * and returns a UnarchiveCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UnarchiveCommand parse(String args) throws ParseException, ParseWithHintException { + try { + Set index = ParserUtil.parseIndexes(args); + return new UnarchiveCommand(index); + } catch (ParseException pe) { + throw new ParseWithHintException( + String.format( + MESSAGE_INVALID_COMMAND_FORMAT, + UnarchiveCommand.MESSAGE_USAGE), + pe, + "INDEX [INDEX] [INDEX]" + ); + } + } +} diff --git a/src/main/java/seedu/mentorstack/logic/parser/UnfinishCommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/UnfinishCommandParser.java new file mode 100644 index 00000000000..11d9297937f --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/UnfinishCommandParser.java @@ -0,0 +1,69 @@ +package seedu.mentorstack.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; + +import java.util.Collection; +import java.util.Optional; +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.UnfinishCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; +import seedu.mentorstack.model.person.Subject; + +/** + * Parses input arguments and creates a new UnfinishCommand object + */ +public class UnfinishCommandParser extends CommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the UnfinishCommand + * and returns an UnfinishCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UnfinishCommand parse(String args) throws ParseException, ParseWithHintException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_SUBJECT); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (ParseException pe) { + throw new ParseWithHintException(String.format( + MESSAGE_INVALID_COMMAND_FORMAT, + UnfinishCommand.MESSAGE_USAGE), + pe, + "INDEX [s/SUBJECT] [s/SUBJECT]" + ); + } + + Set subjectsToUnfinish = parseSubjectsForUnfinish(argMultimap.getAllValues(PREFIX_SUBJECT)) + .orElseThrow(() -> new ParseWithHintException( + UnfinishCommand.MESSAGE_NO_SUBJECTS, + "[s/SUBJECT] [s/SUBJECT]" + )); + + return new UnfinishCommand(index, subjectsToUnfinish); + } + + /** + * Parses {@code Collection subjects} into a {@code Set} if {@code subjects} is non-empty. + * If {@code subjects} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero subjects. + */ + private Optional> parseSubjectsForUnfinish(Collection subjects) throws ParseException { + assert subjects != null; + + if (subjects.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(ParserUtil.parseSubjects(subjects)); + } + +} diff --git a/src/main/java/seedu/mentorstack/logic/parser/UnmarkCommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/UnmarkCommandParser.java new file mode 100644 index 00000000000..f55b430ac4a --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/UnmarkCommandParser.java @@ -0,0 +1,37 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.UnmarkCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; + +/** + * Parses input arguments and creates a new UnmarkCommand object + */ +public class UnmarkCommandParser extends CommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the UnmarkCommand + * and returns a UnmarkCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UnmarkCommand parse(String args) throws ParseException, ParseWithHintException { + try { + Set index = ParserUtil.parseIndexes(args); + return new UnmarkCommand(index); + } catch (ParseException pe) { + throw new ParseWithHintException( + String.format( + MESSAGE_INVALID_COMMAND_FORMAT, + UnmarkCommand.MESSAGE_USAGE), + pe, + "INDEX [INDEX] [INDEX]" + ); + } + } + +} diff --git a/src/main/java/seedu/mentorstack/logic/parser/ViewCommandParser.java b/src/main/java/seedu/mentorstack/logic/parser/ViewCommandParser.java new file mode 100644 index 00000000000..918cb44b7de --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/ViewCommandParser.java @@ -0,0 +1,47 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_FILTER_TYPE; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_FILTER_VALUE; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import seedu.mentorstack.logic.commands.ViewCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.predicates.FilterPredicate; + +/** + * Parses input arguments and creates a new ViewStudentsCommand object. + */ +public class ViewCommandParser implements Parser { + + @Override + public ViewCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_FILTER_TYPE, PREFIX_FILTER_VALUE); + + List filterTypes = argMultimap.getAllValues(PREFIX_FILTER_TYPE); + List filterValues = argMultimap.getAllValues(PREFIX_FILTER_VALUE); + + if (filterTypes.isEmpty() || filterValues.isEmpty() || filterTypes.size() != filterValues.size()) { + return new ViewCommand(person -> true); // No valid filters, return all students + } + + // Create multiple predicates + List> predicates = new ArrayList<>(); + for (int i = 0; i < filterTypes.size(); i++) { + Predicate predicate = FilterPredicate.createPredicate(filterTypes.get(i) + .toLowerCase(), filterValues.get(i)); + if (predicate == null) { + throw new ParseException(ViewCommand.MESSAGE_INVALID_FILTER); + } + predicates.add(predicate); + } + + // Combine predicates (Logical AND) + Predicate finalPredicate = predicates.stream().reduce(p -> true, Predicate::and); + + return new ViewCommand(finalPredicate); + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/seedu/mentorstack/logic/parser/exceptions/ParseException.java similarity index 71% rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java rename to src/main/java/seedu/mentorstack/logic/parser/exceptions/ParseException.java index 158a1a54c1c..bab705be9fb 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/seedu/mentorstack/logic/parser/exceptions/ParseException.java @@ -1,6 +1,6 @@ -package seedu.address.logic.parser.exceptions; +package seedu.mentorstack.logic.parser.exceptions; -import seedu.address.commons.exceptions.IllegalValueException; +import seedu.mentorstack.commons.exceptions.IllegalValueException; /** * Represents a parse error encountered by a parser. diff --git a/src/main/java/seedu/mentorstack/logic/parser/exceptions/ParseWithHintException.java b/src/main/java/seedu/mentorstack/logic/parser/exceptions/ParseWithHintException.java new file mode 100644 index 00000000000..3a58dd21d69 --- /dev/null +++ b/src/main/java/seedu/mentorstack/logic/parser/exceptions/ParseWithHintException.java @@ -0,0 +1,42 @@ +package seedu.mentorstack.logic.parser.exceptions; + +/** + * Represents a parse error encountered by a parser, contains a hint + */ + +public class ParseWithHintException extends ParseException { + + private String hint; + + /** + * Constructs a new exception with a message and a hint. + * + * @param message The error message. + * @param hint The hint to resolve the error. + */ + public ParseWithHintException(String message, String hint) { + super(message); + this.hint = hint; + } + + /** + * Constructs a new exception with a message, cause, and a hint. + * + * @param message The error message. + * @param cause The cause of the exception. + * @param hint The hint to resolve the error. + */ + public ParseWithHintException(String message, Throwable cause, String hint) { + super(message, cause); + this.hint = hint; + } + + /** + * Returns the hint associated with this exception. + * + * @return The hint. + */ + public String getHint() { + return this.hint; + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/mentorstack/model/Mentorstack.java similarity index 59% rename from src/main/java/seedu/address/model/AddressBook.java rename to src/main/java/seedu/mentorstack/model/Mentorstack.java index 73397161e84..6897c068c1f 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/mentorstack/model/Mentorstack.java @@ -1,19 +1,19 @@ -package seedu.address.model; +package seedu.mentorstack.model; import static java.util.Objects.requireNonNull; import java.util.List; import javafx.collections.ObservableList; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; +import seedu.mentorstack.commons.util.ToStringBuilder; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.UniquePersonList; /** - * Wraps all data at the address-book level + * Wraps all data at the Mentorstack level * Duplicates are not allowed (by .isSamePerson comparison) */ -public class AddressBook implements ReadOnlyAddressBook { +public class Mentorstack implements ReadOnlyMentorstack { private final UniquePersonList persons; @@ -28,12 +28,12 @@ public class AddressBook implements ReadOnlyAddressBook { persons = new UniquePersonList(); } - public AddressBook() {} + public Mentorstack() {} /** - * Creates an AddressBook using the Persons in the {@code toBeCopied} + * Creates a Mentorstack using the Persons in the {@code toBeCopied} */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { + public Mentorstack(ReadOnlyMentorstack toBeCopied) { this(); resetData(toBeCopied); } @@ -49,9 +49,9 @@ public void setPersons(List persons) { } /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. + * Resets the existing data of this {@code Mentorstack} with {@code newData}. */ - public void resetData(ReadOnlyAddressBook newData) { + public void resetData(ReadOnlyMentorstack newData) { requireNonNull(newData); setPersons(newData.getPersonList()); @@ -60,7 +60,7 @@ public void resetData(ReadOnlyAddressBook newData) { //// person-level operations /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Returns true if a person with the same identity as {@code person} exists in Mentorstack. */ public boolean hasPerson(Person person) { requireNonNull(person); @@ -68,8 +68,8 @@ public boolean hasPerson(Person person) { } /** - * Adds a person to the address book. - * The person must not already exist in the address book. + * Adds a person to Mentorstack. + * The person must not already exist in Mentorstack. */ public void addPerson(Person p) { persons.add(p); @@ -77,8 +77,8 @@ public void addPerson(Person p) { /** * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * {@code target} must exist in Mentorstack. + * The person identity of {@code editedPerson} must not be the same as another existing person in Mentorstack. */ public void setPerson(Person target, Person editedPerson) { requireNonNull(editedPerson); @@ -87,8 +87,8 @@ public void setPerson(Person target, Person editedPerson) { } /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. + * Removes {@code key} from this {@code Mentorstack}. + * {@code key} must exist in Mentorstack. */ public void removePerson(Person key) { persons.remove(key); @@ -115,16 +115,36 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof AddressBook)) { + if (!(other instanceof Mentorstack)) { return false; } - AddressBook otherAddressBook = (AddressBook) other; - return persons.equals(otherAddressBook.persons); + Mentorstack otherMentorstack = (Mentorstack) other; + return persons.equals(otherMentorstack.persons); } @Override public int hashCode() { return persons.hashCode(); } + + /** + * Removes {@code personToArchive} from this {@code Mentorstack}. + * {@code personToArchive} must exist in Mentorstack. + */ + public void archive(Person personToArchive, Person archived) { + persons.archivePerson(personToArchive, archived); + } + + public void unarchive(Person personToUnarchive, Person unarchived) { + persons.unarchivePerson(personToUnarchive, unarchived); + } + + public void mark(Person target, Person marked) { + persons.mark(target, marked); + } + + public void unmark(Person target, Person unmarked) { + persons.unmark(target, unmarked); + } } diff --git a/src/main/java/seedu/mentorstack/model/Model.java b/src/main/java/seedu/mentorstack/model/Model.java new file mode 100644 index 00000000000..621de416d9e --- /dev/null +++ b/src/main/java/seedu/mentorstack/model/Model.java @@ -0,0 +1,123 @@ +package seedu.mentorstack.model; + +import java.nio.file.Path; +import java.util.function.Predicate; + +import javafx.collections.ObservableList; +import seedu.mentorstack.commons.core.GuiSettings; +import seedu.mentorstack.model.person.Person; + +/** + * The API of the Model component. + */ +public interface Model { + /** {@code Predicate} that always evaluate to true */ + Predicate PREDICATE_SHOW_ALL_PERSONS = person -> !person.getIsArchived().isArchived.equals("true"); + Predicate PREDICATE_SHOW_ALL_ARCHIVED_PERSONS = person -> person.getIsArchived().isArchived.equals("true"); + + /** + * Replaces user prefs data with the data in {@code userPrefs}. + */ + void setUserPrefs(ReadOnlyUserPrefs userPrefs); + + /** + * Returns the user prefs. + */ + ReadOnlyUserPrefs getUserPrefs(); + + /** + * Returns the user prefs' GUI settings. + */ + GuiSettings getGuiSettings(); + + /** + * Sets the user prefs' GUI settings. + */ + void setGuiSettings(GuiSettings guiSettings); + + /** + * Returns the user prefs' address book file path. + */ + Path getMentorstackFilePath(); + + /** + * Sets the user prefs' address book file path. + */ + void setMentorstackFilePath(Path mentorstackFilePath); + + /** + * Save the state of database. + */ + void rememberMentorstack(); + + /** + * Replaces address book data with the data in {@code mentorstack}. + */ + void setMentorstack(ReadOnlyMentorstack mentorstack); + + /** Returns the Mentorstack */ + ReadOnlyMentorstack getMentorstack(); + + /** + * Returns true if a person with the same identity as {@code person} exists in Mentorstack. + */ + boolean hasPerson(Person person); + + /** + * Deletes the given person. + * The person must exist in Mentorstack. + */ + void deletePerson(Person target); + + /** + * Adds the given person. + * {@code person} must not already exist in Mentorstack. + */ + void addPerson(Person person); + + /** + * Replaces the given person {@code target} with {@code editedPerson}. + * {@code target} must exist in Mentorstack. + * The person identity of {@code editedPerson} must not be the same as another existing person in Mentorstack. + */ + void setPerson(Person target, Person editedPerson); + + /** Returns an unmodifiable view of the filtered person list */ + ObservableList getFilteredPersonList(); + + /** + * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredPersonList(Predicate predicate); + + /** + * Archives the given person. + * {@code person} must not already exist in Mentorstack. + */ + void archivePerson(Person person, Person archived); + + /** + * Unarchives the given person. + * {@code person} must not already exist in Mentorstack. + */ + void unarchivePerson(Person personToUnarchive, Person unarchived); + + /** + * Marks the given person. + */ + void markPerson(Person target, Person marked); + + /** + * Unmarks the given person. + */ + void unmarkPerson(Person target, Person unmarked); + + /** Returns an indication of current undoable states */ + boolean canUndo(); + + /** + * Undo the last command by reverting to the last historical state. + */ + void undo(); +} diff --git a/src/main/java/seedu/mentorstack/model/ModelManager.java b/src/main/java/seedu/mentorstack/model/ModelManager.java new file mode 100644 index 00000000000..b30cf996158 --- /dev/null +++ b/src/main/java/seedu/mentorstack/model/ModelManager.java @@ -0,0 +1,199 @@ +package seedu.mentorstack.model; + +import static java.util.Objects.requireNonNull; +import static seedu.mentorstack.commons.util.CollectionUtil.requireAllNonNull; + +import java.nio.file.Path; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.NoSuchElementException; +import java.util.function.Predicate; +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import seedu.mentorstack.commons.core.GuiSettings; +import seedu.mentorstack.commons.core.LogsCenter; +import seedu.mentorstack.model.person.Person; + +/** + * Represents the in-memory model of the Mentorstack data. + */ +public class ModelManager implements Model { + private static final Logger logger = LogsCenter.getLogger(ModelManager.class); + + private final Mentorstack mentorstack; + private final UserPrefs userPrefs; + private final FilteredList filteredPersons; + private final Deque history = new ArrayDeque<>(); + + /** + * Initializes a ModelManager with the given mentorstack and userPrefs. + */ + public ModelManager(ReadOnlyMentorstack mentorstack, ReadOnlyUserPrefs userPrefs) { + requireAllNonNull(mentorstack, userPrefs); + + logger.fine("Initializing with mentorstack: " + mentorstack + " and user prefs " + userPrefs); + + this.mentorstack = new Mentorstack(mentorstack); + this.userPrefs = new UserPrefs(userPrefs); + filteredPersons = new FilteredList<>(this.mentorstack.getPersonList()); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + } + + public ModelManager() { + this(new Mentorstack(), new UserPrefs()); + } + + //=========== UserPrefs ================================================================================== + + @Override + public void setUserPrefs(ReadOnlyUserPrefs userPrefs) { + requireNonNull(userPrefs); + this.userPrefs.resetData(userPrefs); + } + + @Override + public ReadOnlyUserPrefs getUserPrefs() { + return userPrefs; + } + + @Override + public GuiSettings getGuiSettings() { + return userPrefs.getGuiSettings(); + } + + @Override + public void setGuiSettings(GuiSettings guiSettings) { + requireNonNull(guiSettings); + userPrefs.setGuiSettings(guiSettings); + } + + @Override + public Path getMentorstackFilePath() { + return userPrefs.getMentorstackFilePath(); + } + + @Override + public void setMentorstackFilePath(Path mentorstackFilePath) { + requireNonNull(mentorstackFilePath); + userPrefs.setMentorstackFilePath(mentorstackFilePath); + } + + //=========== Mentorstack ================================================================================ + + @Override + public void rememberMentorstack() { + history.push(new Mentorstack(this.mentorstack)); + } + + @Override + public void setMentorstack(ReadOnlyMentorstack mentorstack) { + this.mentorstack.resetData(mentorstack); + } + + @Override + public ReadOnlyMentorstack getMentorstack() { + return mentorstack; + } + + @Override + public boolean hasPerson(Person person) { + requireNonNull(person); + return mentorstack.hasPerson(person); + } + + @Override + public void deletePerson(Person target) { + mentorstack.removePerson(target); + } + + @Override + public void addPerson(Person person) { + mentorstack.addPerson(person); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + } + + @Override + public void setPerson(Person target, Person editedPerson) { + requireAllNonNull(target, editedPerson); + + mentorstack.setPerson(target, editedPerson); + } + + @Override + public boolean canUndo() { + return !history.isEmpty(); + } + + @Override + public void undo() { + if (!canUndo()) { + throw new NoSuchElementException("No previous state to undo."); + } + this.mentorstack.resetData(history.pop()); // Restore previous state + } + + @Override + public void archivePerson(Person personToArchive, Person archived) { + requireAllNonNull(personToArchive, archived); + mentorstack.archive(personToArchive, archived); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + } + + @Override + public void unarchivePerson(Person personToUnarchive, Person unarchived) { + requireAllNonNull(personToUnarchive, unarchived); + mentorstack.unarchive(personToUnarchive, unarchived); + updateFilteredPersonList(PREDICATE_SHOW_ALL_ARCHIVED_PERSONS); + } + + @Override + public void markPerson(Person target, Person marked) { + requireAllNonNull(target, marked); + mentorstack.mark(target, marked); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + } + + @Override + public void unmarkPerson(Person target, Person unmarked) { + requireAllNonNull(target, unmarked); + mentorstack.unmark(target, unmarked); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + } + + //=========== Filtered Person List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * {@code versionedMentorstack} + */ + @Override + public ObservableList getFilteredPersonList() { + return filteredPersons; + } + + @Override + public void updateFilteredPersonList(Predicate predicate) { + requireNonNull(predicate); + filteredPersons.setPredicate(predicate); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ModelManager)) { + return false; + } + + ModelManager otherModelManager = (ModelManager) other; + return mentorstack.equals(otherModelManager.mentorstack) + && userPrefs.equals(otherModelManager.userPrefs) + && filteredPersons.equals(otherModelManager.filteredPersons); + } + +} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/mentorstack/model/ReadOnlyMentorstack.java similarity index 59% rename from src/main/java/seedu/address/model/ReadOnlyAddressBook.java rename to src/main/java/seedu/mentorstack/model/ReadOnlyMentorstack.java index 6ddc2cd9a29..68e62ed1e3f 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/mentorstack/model/ReadOnlyMentorstack.java @@ -1,12 +1,12 @@ -package seedu.address.model; +package seedu.mentorstack.model; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.mentorstack.model.person.Person; /** - * Unmodifiable view of an address book + * Unmodifiable view of Mentorstack */ -public interface ReadOnlyAddressBook { +public interface ReadOnlyMentorstack { /** * Returns an unmodifiable view of the persons list. diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/mentorstack/model/ReadOnlyUserPrefs.java similarity index 55% rename from src/main/java/seedu/address/model/ReadOnlyUserPrefs.java rename to src/main/java/seedu/mentorstack/model/ReadOnlyUserPrefs.java index befd58a4c73..d0db1471b6b 100644 --- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java +++ b/src/main/java/seedu/mentorstack/model/ReadOnlyUserPrefs.java @@ -1,8 +1,8 @@ -package seedu.address.model; +package seedu.mentorstack.model; import java.nio.file.Path; -import seedu.address.commons.core.GuiSettings; +import seedu.mentorstack.commons.core.GuiSettings; /** * Unmodifiable view of user prefs. @@ -11,6 +11,6 @@ public interface ReadOnlyUserPrefs { GuiSettings getGuiSettings(); - Path getAddressBookFilePath(); + Path getMentorstackFilePath(); } diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/mentorstack/model/UserPrefs.java similarity index 71% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/seedu/mentorstack/model/UserPrefs.java index 6be655fb4c7..39077bfd4bb 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/mentorstack/model/UserPrefs.java @@ -1,4 +1,4 @@ -package seedu.address.model; +package seedu.mentorstack.model; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.nio.file.Paths; import java.util.Objects; -import seedu.address.commons.core.GuiSettings; +import seedu.mentorstack.commons.core.GuiSettings; /** * Represents User's preferences. @@ -14,7 +14,7 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path mentorstackFilePath = Paths.get("data" , "mentorstack.json"); /** * Creates a {@code UserPrefs} with default values. @@ -35,7 +35,7 @@ public UserPrefs(ReadOnlyUserPrefs userPrefs) { public void resetData(ReadOnlyUserPrefs newUserPrefs) { requireNonNull(newUserPrefs); setGuiSettings(newUserPrefs.getGuiSettings()); - setAddressBookFilePath(newUserPrefs.getAddressBookFilePath()); + setMentorstackFilePath(newUserPrefs.getMentorstackFilePath()); } public GuiSettings getGuiSettings() { @@ -47,13 +47,13 @@ public void setGuiSettings(GuiSettings guiSettings) { this.guiSettings = guiSettings; } - public Path getAddressBookFilePath() { - return addressBookFilePath; + public Path getMentorstackFilePath() { + return mentorstackFilePath; } - public void setAddressBookFilePath(Path addressBookFilePath) { - requireNonNull(addressBookFilePath); - this.addressBookFilePath = addressBookFilePath; + public void setMentorstackFilePath(Path mentorstackFilePath) { + requireNonNull(mentorstackFilePath); + this.mentorstackFilePath = mentorstackFilePath; } @Override @@ -69,19 +69,19 @@ public boolean equals(Object other) { UserPrefs otherUserPrefs = (UserPrefs) other; return guiSettings.equals(otherUserPrefs.guiSettings) - && addressBookFilePath.equals(otherUserPrefs.addressBookFilePath); + && mentorstackFilePath.equals(otherUserPrefs.mentorstackFilePath); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath); + return Objects.hash(guiSettings, mentorstackFilePath); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings); - sb.append("\nLocal data file location : " + addressBookFilePath); + sb.append("\nLocal data file location : " + mentorstackFilePath); return sb.toString(); } diff --git a/src/main/java/seedu/mentorstack/model/person/ArchiveStatus.java b/src/main/java/seedu/mentorstack/model/person/ArchiveStatus.java new file mode 100644 index 00000000000..19c58e8c9ae --- /dev/null +++ b/src/main/java/seedu/mentorstack/model/person/ArchiveStatus.java @@ -0,0 +1,62 @@ +package seedu.mentorstack.model.person; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Person's archive status in Mentorstack. + * Guarantees: immutable; + */ +public class ArchiveStatus { + + + public static final String MESSAGE_CONSTRAINTS = + "Archive Status should either be true or false"; + public final String isArchived; + + /** + * Constructs a {@code ArchiveStatus}. + * + * @param isArchived A boolean value. + */ + public ArchiveStatus(String isArchived) { + requireNonNull(isArchived); + this.isArchived = isArchived; + } + + public static boolean isValidArchiveStatus(String test) { + return test.equals("true") || test.equals("false"); + } + + @Override + public String toString() { + return isArchived; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ArchiveStatus)) { + return false; + } + + ArchiveStatus otherArchiveStatus = (ArchiveStatus) other; + return isArchived.equals(otherArchiveStatus.isArchived); + } + + @Override + public int hashCode() { + return isArchived.hashCode(); + } + + public String getStatus() { + return this.isArchived; + } + + public Boolean testStatus() { + return this.isArchived.equals("true"); + } +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/mentorstack/model/person/Email.java similarity index 94% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/seedu/mentorstack/model/person/Email.java index c62e512bc29..b5767254ed5 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/mentorstack/model/person/Email.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.mentorstack.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.mentorstack.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Person's email in Mentorstack. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { diff --git a/src/main/java/seedu/mentorstack/model/person/Gender.java b/src/main/java/seedu/mentorstack/model/person/Gender.java new file mode 100644 index 00000000000..8a80435a287 --- /dev/null +++ b/src/main/java/seedu/mentorstack/model/person/Gender.java @@ -0,0 +1,48 @@ +package seedu.mentorstack.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.mentorstack.commons.util.AppUtil.checkArgument; + +/** + * Represents a person's gender. + * Guarantees: immutable; is valid as declared in {@link #isValidGender(String)} + */ +public class Gender { + public static final String MESSAGE_CONSTRAINTS = "Gender should be 'F' or 'M' only."; + + public final String value; + + /** + * Constructs a {@code Gender}. + * + * @param gender A valid gender string. + */ + public Gender(String gender) { + requireNonNull(gender); + String upperGender = gender.substring(0).trim().toUpperCase(); + checkArgument(isValidGender(gender), MESSAGE_CONSTRAINTS); + this.value = upperGender; + } + + /** + * Returns true if a given string is a valid gender. + */ + public static boolean isValidGender(String test) { + return test.equalsIgnoreCase("F") || test.equalsIgnoreCase("M"); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return this == other || (other instanceof Gender && value.equals(((Gender) other).value)); + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/mentorstack/model/person/Name.java similarity index 90% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/mentorstack/model/person/Name.java index 173f15b9b00..9ee3afc7db9 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/mentorstack/model/person/Name.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.mentorstack.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.mentorstack.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Person's name in Mentorstack. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/mentorstack/model/person/NameContainsKeywordsPredicate.java similarity index 80% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/mentorstack/model/person/NameContainsKeywordsPredicate.java index 62d19be2977..2b72e3fa8b0 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/mentorstack/model/person/NameContainsKeywordsPredicate.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.mentorstack.model.person; import java.util.List; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; -import seedu.address.commons.util.ToStringBuilder; +import seedu.mentorstack.commons.util.StringUtil; +import seedu.mentorstack.commons.util.ToStringBuilder; /** * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. @@ -19,7 +19,7 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Person person) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsPartialWordIgnoreCase(person.getName().fullName, keyword)); } @Override diff --git a/src/main/java/seedu/mentorstack/model/person/Person.java b/src/main/java/seedu/mentorstack/model/person/Person.java new file mode 100644 index 00000000000..9c54b476411 --- /dev/null +++ b/src/main/java/seedu/mentorstack/model/person/Person.java @@ -0,0 +1,189 @@ +package seedu.mentorstack.model.person; + +import static seedu.mentorstack.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import seedu.mentorstack.commons.util.ToStringBuilder; + +/** + * Represents a Person in Mentorstack. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Person { + + // Identity fields + private final Name name; + private final Gender gender; + private final Phone phone; + private final Email email; + private final ArchiveStatus isArchived; + private final boolean isMarked; + + // Data fields + private final Set subject = new HashSet<>(); + private final Set finishedSubject = new HashSet<>(); + + /** + * Every field must be present and not null, new student is default to have archive status to be false. + */ + public Person(Name name, Gender gender, Phone phone, Email email, + Set subject) { + requireAllNonNull(name, phone, email, subject); + this.name = name; + this.gender = gender; + this.phone = phone; + this.email = email; + this.subject.addAll(subject); + this.isArchived = new ArchiveStatus("false"); + this.isMarked = false; + } + + /** + * Every field must be present and not null. + */ + public Person(Name name, Gender gender, Phone phone, Email email, + Set subject, Set finishedSubjects, + ArchiveStatus isArchived, boolean isMarked) { + requireAllNonNull(name, phone, email, subject, isArchived); + this.name = name; + this.gender = gender; + this.phone = phone; + this.email = email; + this.subject.addAll(subject); + this.finishedSubject.addAll(finishedSubjects); + this.isArchived = isArchived; + this.isMarked = isMarked; + } + + public Name getName() { + return name; + } + + public Gender getGender() { + return gender; + } + + public Phone getPhone() { + return phone; + } + + public Email getEmail() { + return email; + } + + /** + * Returns an immutable subject set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set getSubjects() { + return Collections.unmodifiableSet(subject); + } + + public Set getFinishedSubjects() { + return Collections.unmodifiableSet(finishedSubject); + } + + public ArchiveStatus getIsArchived() { + return isArchived; + } + + public boolean getIsMarked() { + return isMarked; + } + + /** + * Returns true if both persons have the same name. + * This defines a weaker notion of equality between two persons. + */ + public boolean isSamePerson(Person otherPerson) { + if (otherPerson == this) { + return true; + } + + return otherPerson != null + && otherPerson.getName().equals(getName()); + } + + /** + * Returns true if both persons have the same identity and data fields. + * This defines a stronger notion of equality between two persons. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Person)) { + return false; + } + + Person otherPerson = (Person) other; + return name.equals(otherPerson.name) + && gender.equals(otherPerson.gender) + && phone.equals(otherPerson.phone) + && email.equals(otherPerson.email) + && subject.equals(otherPerson.subject) + && finishedSubject.equals(otherPerson.finishedSubject) + && isArchived.equals(otherPerson.isArchived) + && isMarked == otherPerson.isMarked; + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(name, gender, phone, email, subject, finishedSubject, isArchived, isMarked); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("name", name) + .add("gender", gender) + .add("phone", phone) + .add("email", email) + .add("subject", subject) + .add("finished subject", finishedSubject) + .add("archive status", isArchived) + .add("marked", isMarked) + .toString(); + } + + /** + * Returns a person that is identical to the person except it is archived. + */ + public Person archived() { + return new Person(this.name, this.gender, this.phone, this.email, + this.subject, this.finishedSubject, new ArchiveStatus("true"), this.isMarked); + } + + /** + * Returns a person that is identical to the person except it is unarchived. + */ + public Person unarchived() { + return new Person(this.name, this.gender, this.phone, this.email, + this.subject, this.finishedSubject, new ArchiveStatus("false"), this.isMarked); + } + + /** + * Returns a person that is identical to the person except it is marked. + */ + public Person marked() { + return new Person(this.name, this.gender, this.phone, this.email, + this.subject, this.finishedSubject, this.isArchived, true); + } + + /** + * Returns a person that is identical to the person except it is unmarked. + */ + public Person unmarked() { + return new Person(this.name, this.gender, this.phone, this.email, + this.subject, this.finishedSubject, this.isArchived, false); + } + +} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/mentorstack/model/person/Phone.java similarity index 89% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/mentorstack/model/person/Phone.java index d733f63d739..e5eed52c3b3 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/mentorstack/model/person/Phone.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.mentorstack.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.mentorstack.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Person's phone number in Mentorstack. * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} */ public class Phone { diff --git a/src/main/java/seedu/mentorstack/model/person/Subject.java b/src/main/java/seedu/mentorstack/model/person/Subject.java new file mode 100644 index 00000000000..d0475ae012e --- /dev/null +++ b/src/main/java/seedu/mentorstack/model/person/Subject.java @@ -0,0 +1,61 @@ +package seedu.mentorstack.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.mentorstack.commons.util.AppUtil.checkArgument; + +/** + * Represents a Subject in Mentorstack. + * Guarantees: immutable; name is valid as declared in {@link #isValidSubjectName(String)} + */ +public class Subject { + public static final String MESSAGE_CONSTRAINTS = "Subject names should be alphanumeric"; + public static final String VALIDATION_REGEX = "\\p{Alnum}+"; + + public final String subjectName; + + /** + * Constructs a {@code Subject}. + * + * @param subjectName A valid subject name. + */ + public Subject(String subjectName) { + requireNonNull(subjectName); + checkArgument(isValidSubjectName(subjectName), MESSAGE_CONSTRAINTS); + this.subjectName = subjectName; + } + + /** + * Returns true if a given string is a valid subject name. + */ + public static boolean isValidSubjectName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Subject)) { + return false; + } + + Subject otherSubject = (Subject) other; + return subjectName.toLowerCase().equals(otherSubject.subjectName.toLowerCase()); + } + + @Override + public int hashCode() { + return subjectName.hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return '[' + subjectName + ']'; + } + +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/mentorstack/model/person/UniquePersonList.java similarity index 70% rename from src/main/java/seedu/address/model/person/UniquePersonList.java rename to src/main/java/seedu/mentorstack/model/person/UniquePersonList.java index cc0a68d79f9..32d8969573b 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/mentorstack/model/person/UniquePersonList.java @@ -1,15 +1,15 @@ -package seedu.address.model.person; +package seedu.mentorstack.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.mentorstack.commons.util.CollectionUtil.requireAllNonNull; import java.util.Iterator; import java.util.List; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.mentorstack.model.person.exceptions.DuplicatePersonException; +import seedu.mentorstack.model.person.exceptions.PersonNotFoundException; /** * A list of persons that enforces uniqueness between its elements and does not allow nulls. @@ -68,6 +68,36 @@ public void setPerson(Person target, Person editedPerson) { internalList.set(index, editedPerson); } + /** + * Archives the person {@code target} in the list. + * {@code target} must exist in the list. + */ + public void archivePerson(Person target, Person archivedPerson) { + requireAllNonNull(target, archivedPerson); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + internalList.set(index, archivedPerson); + } + + /** + * Unarchives the person {@code target} in the list. + * {@code target} must exist in the list. + */ + public void unarchivePerson(Person target, Person unarchivedPerson) { + requireAllNonNull(target, unarchivedPerson); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + internalList.set(index, unarchivedPerson); + } + /** * Removes the equivalent person from the list. * The person must exist in the list. @@ -79,6 +109,36 @@ public void remove(Person toRemove) { } } + /** + * Marks the target person. + * The person must exist in the list. + */ + public void mark(Person target, Person marked) { + requireAllNonNull(target, marked); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + internalList.set(index, marked); + } + + /** + * Unmarks the target person. + * The person must exist in the list. + */ + public void unmark(Person target, Person unmarked) { + requireAllNonNull(target, unmarked); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + internalList.set(index, unmarked); + } + public void setPersons(UniquePersonList replacement) { requireNonNull(replacement); internalList.setAll(replacement.internalList); diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/mentorstack/model/person/exceptions/DuplicatePersonException.java similarity index 86% rename from src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java rename to src/main/java/seedu/mentorstack/model/person/exceptions/DuplicatePersonException.java index d7290f59442..6c94500c438 100644 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ b/src/main/java/seedu/mentorstack/model/person/exceptions/DuplicatePersonException.java @@ -1,4 +1,4 @@ -package seedu.address.model.person.exceptions; +package seedu.mentorstack.model.person.exceptions; /** * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/mentorstack/model/person/exceptions/PersonNotFoundException.java similarity index 73% rename from src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java rename to src/main/java/seedu/mentorstack/model/person/exceptions/PersonNotFoundException.java index fa764426ca7..b10e6584ff7 100644 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ b/src/main/java/seedu/mentorstack/model/person/exceptions/PersonNotFoundException.java @@ -1,4 +1,4 @@ -package seedu.address.model.person.exceptions; +package seedu.mentorstack.model.person.exceptions; /** * Signals that the operation is unable to find the specified person. diff --git a/src/main/java/seedu/mentorstack/model/person/predicates/FilterPredicate.java b/src/main/java/seedu/mentorstack/model/person/predicates/FilterPredicate.java new file mode 100644 index 00000000000..c275b11eee4 --- /dev/null +++ b/src/main/java/seedu/mentorstack/model/person/predicates/FilterPredicate.java @@ -0,0 +1,37 @@ +package seedu.mentorstack.model.person.predicates; + +import java.util.function.Predicate; + +import seedu.mentorstack.model.person.Person; + +/** + * Generates filter predicates for student search. + */ +public class FilterPredicate { + + /** + * Creates a filter predicate for student search. + */ + public static Predicate createPredicate(String filterType, String filterValue) { + switch (filterType) { + case "n": + return person -> person.getName().fullName.toLowerCase().contains(filterValue.toLowerCase()); + case "p": + return person -> person.getPhone().value.toLowerCase().contains(filterValue.toLowerCase()); + case "e": + return person -> person.getEmail().value.toLowerCase().contains(filterValue.toLowerCase()); + case "s": + return person -> person.getSubjects().stream() + .map(subject -> subject.subjectName.toLowerCase()) // Extract subject name + .anyMatch(subjectName -> subjectName.contains(filterValue.toLowerCase())); // Match substring + case "g": + return person -> person.getGender().value.equalsIgnoreCase(filterValue); + case "a": + return person -> filterValue.equalsIgnoreCase("t") + ? person.getIsArchived().isArchived.equals("true") + : !person.getIsArchived().isArchived.equals("true"); + default: + return null; // Invalid filter type + } + } +} diff --git a/src/main/java/seedu/mentorstack/model/util/SampleDataUtil.java b/src/main/java/seedu/mentorstack/model/util/SampleDataUtil.java new file mode 100644 index 00000000000..1e36486fee2 --- /dev/null +++ b/src/main/java/seedu/mentorstack/model/util/SampleDataUtil.java @@ -0,0 +1,59 @@ +package seedu.mentorstack.model.util; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import seedu.mentorstack.model.Mentorstack; +import seedu.mentorstack.model.ReadOnlyMentorstack; +import seedu.mentorstack.model.person.Email; +import seedu.mentorstack.model.person.Gender; +import seedu.mentorstack.model.person.Name; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Phone; +import seedu.mentorstack.model.person.Subject; + +/** + * Contains utility methods for populating {@code Mentorstack} with sample data. + */ +public class SampleDataUtil { + public static Person[] getSamplePersons() { + return new Person[] { + new Person(new Name("Alex Yeoh"), new Gender("M"), new Phone("87438807"), + new Email("alexyeoh@example.com"), + getSubjectSet("CS2102")), + new Person(new Name("Bernice Yu"), new Gender("M"), new Phone("99272758"), + new Email("berniceyu@example.com"), + getSubjectSet("CS2103", "CS2101")), + new Person(new Name("Charlotte Oliveiro"), new Gender("F"), new Phone("93210283"), + new Email("charlotte@example.com"), + getSubjectSet("MA1100T", "CS2100", "CS2106")), + new Person(new Name("David Li"), new Gender("M"), new Phone("91031282"), + new Email("lidavid@example.com"), + getSubjectSet("CS2105")), + new Person(new Name("Irfan Ibrahim"), new Gender("M"), new Phone("92492021"), + new Email("irfan@example.com"), + getSubjectSet("GE2215")), + new Person(new Name("Roy Balakrishnan"), new Gender("M"), new Phone("92624417"), + new Email("royb@example.com"), + getSubjectSet("LAJ1101")) + }; + } + + public static ReadOnlyMentorstack getSampleMentorstack() { + Mentorstack sampleMs = new Mentorstack(); + for (Person samplePerson : getSamplePersons()) { + sampleMs.addPerson(samplePerson); + } + return sampleMs; + } + + /** + * Returns a subject set containing the list of strings given. + */ + public static Set getSubjectSet(String... strings) { + return Arrays.stream(strings) + .map(Subject::new) + .collect(Collectors.toSet()); + } +} diff --git a/src/main/java/seedu/mentorstack/storage/JsonAdaptedPerson.java b/src/main/java/seedu/mentorstack/storage/JsonAdaptedPerson.java new file mode 100644 index 00000000000..20a8d6bf7ba --- /dev/null +++ b/src/main/java/seedu/mentorstack/storage/JsonAdaptedPerson.java @@ -0,0 +1,135 @@ +package seedu.mentorstack.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.mentorstack.commons.exceptions.IllegalValueException; +import seedu.mentorstack.model.person.ArchiveStatus; +import seedu.mentorstack.model.person.Email; +import seedu.mentorstack.model.person.Gender; +import seedu.mentorstack.model.person.Name; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Phone; +import seedu.mentorstack.model.person.Subject; + +/** + * Jackson-friendly version of {@link Person}. + */ +class JsonAdaptedPerson { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; + + private final String name; + private final String gender; + private final String phone; + private final String email; + private final List subject = new ArrayList<>(); + private final List finishedSubject = new ArrayList<>(); + private final String isArchived; + private final boolean isMarked; + + /** + * Constructs a {@code JsonAdaptedPerson} with the given person details. + */ + @JsonCreator + public JsonAdaptedPerson(@JsonProperty("name") String name, + @JsonProperty("gender") String gender, + @JsonProperty("phone") String phone, + @JsonProperty("email") String email, + @JsonProperty("subject") List subject) { + this.name = name; + this.gender = gender; + this.phone = phone; + this.email = email; + if (subject != null) { + this.subject.addAll(subject); + } + this.isArchived = "false"; + this.isMarked = false; + } + + /** + * Converts a given {@code Person} into this class for Jackson use. + */ + public JsonAdaptedPerson(Person source) { + name = source.getName().fullName; + phone = source.getPhone().value; + gender = source.getGender().value; + email = source.getEmail().value; + subject.addAll(source.getSubjects().stream() + .map(JsonAdaptedSubject::new) + .collect(Collectors.toList())); + finishedSubject.addAll(source.getFinishedSubjects().stream() + .map(JsonAdaptedSubject::new) + .collect(Collectors.toList())); + isArchived = source.getIsArchived().getStatus(); + isMarked = source.getIsMarked(); + } + + /** + * Converts this Jackson-friendly adapted person object into the model's {@code Person} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person. + */ + public Person toModelType() throws IllegalValueException { + final List personSubject = new ArrayList<>(); + final List personFinishedSubject = new ArrayList<>(); + + for (JsonAdaptedSubject sub : subject) { + personSubject.add(sub.toModelType()); + } + + for (JsonAdaptedSubject sub : finishedSubject) { + personFinishedSubject.add(sub.toModelType()); + } + + if (name == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); + } + if (!Name.isValidName(name)) { + throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS); + } + final Name modelName = new Name(name); + + if (gender == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Gender.class.getSimpleName())); + } + if (!Gender.isValidGender(gender)) { + throw new IllegalValueException(Gender.MESSAGE_CONSTRAINTS); + } + final Gender modelGender = new Gender(gender); + + if (phone == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); + } + if (!Phone.isValidPhone(phone)) { + throw new IllegalValueException(Phone.MESSAGE_CONSTRAINTS); + } + final Phone modelPhone = new Phone(phone); + + if (email == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); + } + if (!Email.isValidEmail(email)) { + throw new IllegalValueException(Email.MESSAGE_CONSTRAINTS); + } + final Email modelEmail = new Email(email); + + if (!ArchiveStatus.isValidArchiveStatus(isArchived)) { + throw new IllegalValueException(ArchiveStatus.MESSAGE_CONSTRAINTS); + } + + final Set modelSubject = new HashSet<>(personSubject); + final Set modelFinishedSubject = new HashSet<>(personFinishedSubject); + final ArchiveStatus modelArchiveStatus = new ArchiveStatus(isArchived); + return new Person(modelName, modelGender, modelPhone, modelEmail, + modelSubject, modelFinishedSubject, modelArchiveStatus, isMarked); + } + +} diff --git a/src/main/java/seedu/mentorstack/storage/JsonAdaptedSubject.java b/src/main/java/seedu/mentorstack/storage/JsonAdaptedSubject.java new file mode 100644 index 00000000000..817cb915c2a --- /dev/null +++ b/src/main/java/seedu/mentorstack/storage/JsonAdaptedSubject.java @@ -0,0 +1,48 @@ +package seedu.mentorstack.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import seedu.mentorstack.commons.exceptions.IllegalValueException; +import seedu.mentorstack.model.person.Subject; + +/** + * Jackson-friendly version of {@link Subject}. + */ +class JsonAdaptedSubject { + + private final String subjectName; + + /** + * Constructs a {@code JsonAdaptedSubject} with the given {@code subjectName}. + */ + @JsonCreator + public JsonAdaptedSubject(String subjectName) { + this.subjectName = subjectName; + } + + /** + * Converts a given {@code Subject} into this class for Jackson use. + */ + public JsonAdaptedSubject(Subject source) { + subjectName = source.subjectName; + } + + @JsonValue + public String getSubjectName() { + return subjectName; + } + + /** + * Converts this Jackson-friendly adapted subject object into the model's {@code Subject} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted subject. + */ + public Subject toModelType() throws IllegalValueException { + if (!Subject.isValidSubjectName(subjectName)) { + throw new IllegalValueException(Subject.MESSAGE_CONSTRAINTS); + } + return new Subject(subjectName); + } + +} diff --git a/src/main/java/seedu/mentorstack/storage/JsonMentorstackStorage.java b/src/main/java/seedu/mentorstack/storage/JsonMentorstackStorage.java new file mode 100644 index 00000000000..50f86cb4ee9 --- /dev/null +++ b/src/main/java/seedu/mentorstack/storage/JsonMentorstackStorage.java @@ -0,0 +1,81 @@ +package seedu.mentorstack.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.mentorstack.commons.core.LogsCenter; +import seedu.mentorstack.commons.exceptions.DataLoadingException; +import seedu.mentorstack.commons.exceptions.IllegalValueException; +import seedu.mentorstack.commons.util.FileUtil; +import seedu.mentorstack.commons.util.JsonUtil; +import seedu.mentorstack.model.ReadOnlyMentorstack; + +/** + * A class to access Mentorstack data stored as a json file on the hard disk. + */ +public class JsonMentorstackStorage implements MentorstackStorage { + + private static final Logger logger = LogsCenter.getLogger(JsonMentorstackStorage.class); + + private Path filePath; + + public JsonMentorstackStorage(Path filePath) { + this.filePath = filePath; + } + + public Path getMentorstackFilePath() { + return filePath; + } + + @Override + public Optional readMentorstack() throws DataLoadingException { + return readMentorstack(filePath); + } + + /** + * Similar to {@link #readMentorstack()}. + * + * @param filePath location of the data. Cannot be null. + * @throws DataLoadingException if loading the data from storage failed. + */ + public Optional readMentorstack(Path filePath) throws DataLoadingException { + requireNonNull(filePath); + + Optional jsonMentorstack = JsonUtil.readJsonFile( + filePath, JsonSerializableMentorstack.class); + if (!jsonMentorstack.isPresent()) { + return Optional.empty(); + } + + try { + return Optional.of(jsonMentorstack.get().toModelType()); + + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + filePath + ": " + ive.getMessage()); + throw new DataLoadingException(ive); + } + } + + @Override + public void saveMentorstack(ReadOnlyMentorstack mentorstack) throws IOException { + saveMentorstack(mentorstack, filePath); + } + + /** + * Similar to {@link #saveMentorstack(ReadOnlyMentorstack)}. + * + * @param filePath location of the data. Cannot be null. + */ + public void saveMentorstack(ReadOnlyMentorstack mentorstack, Path filePath) throws IOException { + requireNonNull(mentorstack); + requireNonNull(filePath); + + FileUtil.createIfMissing(filePath); + JsonUtil.saveJsonFile(new JsonSerializableMentorstack(mentorstack), filePath); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/mentorstack/storage/JsonSerializableMentorstack.java similarity index 53% rename from src/main/java/seedu/address/storage/JsonSerializableAddressBook.java rename to src/main/java/seedu/mentorstack/storage/JsonSerializableMentorstack.java index 5efd834091d..c99b1927d32 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/mentorstack/storage/JsonSerializableMentorstack.java @@ -1,4 +1,4 @@ -package seedu.address.storage; +package seedu.mentorstack.storage; import java.util.ArrayList; import java.util.List; @@ -8,53 +8,53 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonRootName; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Person; +import seedu.mentorstack.commons.exceptions.IllegalValueException; +import seedu.mentorstack.model.Mentorstack; +import seedu.mentorstack.model.ReadOnlyMentorstack; +import seedu.mentorstack.model.person.Person; /** - * An Immutable AddressBook that is serializable to JSON format. + * An Immutable Mentorstack that is serializable to JSON format. */ -@JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { +@JsonRootName(value = "mentorstack") +class JsonSerializableMentorstack { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; private final List persons = new ArrayList<>(); /** - * Constructs a {@code JsonSerializableAddressBook} with the given persons. + * Constructs a {@code JsonSerializableMentorstack} with the given persons. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { + public JsonSerializableMentorstack(@JsonProperty("persons") List persons) { this.persons.addAll(persons); } /** - * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. + * Converts a given {@code ReadOnlyMentorstack} into this class for Jackson use. * - * @param source future changes to this will not affect the created {@code JsonSerializableAddressBook}. + * @param source future changes to this will not affect the created {@code JsonSerializableMentorstack}. */ - public JsonSerializableAddressBook(ReadOnlyAddressBook source) { + public JsonSerializableMentorstack(ReadOnlyMentorstack source) { persons.addAll(source.getPersonList().stream().map(JsonAdaptedPerson::new).collect(Collectors.toList())); } /** - * Converts this address book into the model's {@code AddressBook} object. + * Converts this address book into the model's {@code Mentorstack} object. * * @throws IllegalValueException if there were any data constraints violated. */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); + public Mentorstack toModelType() throws IllegalValueException { + Mentorstack mentorstack = new Mentorstack(); for (JsonAdaptedPerson jsonAdaptedPerson : persons) { Person person = jsonAdaptedPerson.toModelType(); - if (addressBook.hasPerson(person)) { + if (mentorstack.hasPerson(person)) { throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); } - addressBook.addPerson(person); + mentorstack.addPerson(person); } - return addressBook; + return mentorstack; } } diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/mentorstack/storage/JsonUserPrefsStorage.java similarity index 82% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/seedu/mentorstack/storage/JsonUserPrefsStorage.java index 48a9754807d..0302aea735d 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/seedu/mentorstack/storage/JsonUserPrefsStorage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; +package seedu.mentorstack.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import seedu.mentorstack.commons.exceptions.DataLoadingException; +import seedu.mentorstack.commons.util.JsonUtil; +import seedu.mentorstack.model.ReadOnlyUserPrefs; +import seedu.mentorstack.model.UserPrefs; /** * A class to access UserPrefs stored in the hard disk as a json file diff --git a/src/main/java/seedu/mentorstack/storage/MentorstackStorage.java b/src/main/java/seedu/mentorstack/storage/MentorstackStorage.java new file mode 100644 index 00000000000..80690781169 --- /dev/null +++ b/src/main/java/seedu/mentorstack/storage/MentorstackStorage.java @@ -0,0 +1,45 @@ +package seedu.mentorstack.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.mentorstack.commons.exceptions.DataLoadingException; +import seedu.mentorstack.model.ReadOnlyMentorstack; + +/** + * Represents a storage for {@link seedu.mentorstack.model.Mentorstack}. + */ +public interface MentorstackStorage { + + /** + * Returns the file path of the data file. + */ + Path getMentorstackFilePath(); + + /** + * Returns Mentorstack data as a {@link ReadOnlyMentorstack}. + * Returns {@code Optional.empty()} if storage file is not found. + * + * @throws DataLoadingException if loading the data from storage failed. + */ + Optional readMentorstack() throws DataLoadingException; + + /** + * @see #getMentorstackFilePath() + */ + Optional readMentorstack(Path filePath) throws DataLoadingException; + + /** + * Saves the given {@link ReadOnlyMentorstack} to the storage. + * @param mentorstack cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveMentorstack(ReadOnlyMentorstack mentorstack) throws IOException; + + /** + * @see #saveMentorstack(ReadOnlyMentorstack) + */ + void saveMentorstack(ReadOnlyMentorstack mentorstack, Path filePath) throws IOException; + +} diff --git a/src/main/java/seedu/mentorstack/storage/Storage.java b/src/main/java/seedu/mentorstack/storage/Storage.java new file mode 100644 index 00000000000..b8bb559c5ed --- /dev/null +++ b/src/main/java/seedu/mentorstack/storage/Storage.java @@ -0,0 +1,32 @@ +package seedu.mentorstack.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import seedu.mentorstack.commons.exceptions.DataLoadingException; +import seedu.mentorstack.model.ReadOnlyMentorstack; +import seedu.mentorstack.model.ReadOnlyUserPrefs; +import seedu.mentorstack.model.UserPrefs; + +/** + * API of the Storage component + */ +public interface Storage extends MentorstackStorage, UserPrefsStorage { + + @Override + Optional readUserPrefs() throws DataLoadingException; + + @Override + void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException; + + @Override + Path getMentorstackFilePath(); + + @Override + Optional readMentorstack() throws DataLoadingException; + + @Override + void saveMentorstack(ReadOnlyMentorstack mentorstack) throws IOException; + +} diff --git a/src/main/java/seedu/mentorstack/storage/StorageManager.java b/src/main/java/seedu/mentorstack/storage/StorageManager.java new file mode 100644 index 00000000000..1307d96d07f --- /dev/null +++ b/src/main/java/seedu/mentorstack/storage/StorageManager.java @@ -0,0 +1,78 @@ +package seedu.mentorstack.storage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.mentorstack.commons.core.LogsCenter; +import seedu.mentorstack.commons.exceptions.DataLoadingException; +import seedu.mentorstack.model.ReadOnlyMentorstack; +import seedu.mentorstack.model.ReadOnlyUserPrefs; +import seedu.mentorstack.model.UserPrefs; + +/** + * Manages storage of Mentorstack data in local storage. + */ +public class StorageManager implements Storage { + + private static final Logger logger = LogsCenter.getLogger(StorageManager.class); + private MentorstackStorage mentorstackStorage; + private UserPrefsStorage userPrefsStorage; + + /** + * Creates a {@code StorageManager} with the given {@code MentorstackStorage} and {@code UserPrefStorage}. + */ + public StorageManager(MentorstackStorage mentorstackStorage, UserPrefsStorage userPrefsStorage) { + this.mentorstackStorage = mentorstackStorage; + this.userPrefsStorage = userPrefsStorage; + } + + // ================ UserPrefs methods ============================== + + @Override + public Path getUserPrefsFilePath() { + return userPrefsStorage.getUserPrefsFilePath(); + } + + @Override + public Optional readUserPrefs() throws DataLoadingException { + return userPrefsStorage.readUserPrefs(); + } + + @Override + public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException { + userPrefsStorage.saveUserPrefs(userPrefs); + } + + + // ================ Mentorstack methods ============================== + + @Override + public Path getMentorstackFilePath() { + return mentorstackStorage.getMentorstackFilePath(); + } + + @Override + public Optional readMentorstack() throws DataLoadingException { + return readMentorstack(mentorstackStorage.getMentorstackFilePath()); + } + + @Override + public Optional readMentorstack(Path filePath) throws DataLoadingException { + logger.fine("Attempting to read data from file: " + filePath); + return mentorstackStorage.readMentorstack(filePath); + } + + @Override + public void saveMentorstack(ReadOnlyMentorstack mentorstack) throws IOException { + saveMentorstack(mentorstack, mentorstackStorage.getMentorstackFilePath()); + } + + @Override + public void saveMentorstack(ReadOnlyMentorstack mentorstack, Path filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + mentorstackStorage.saveMentorstack(mentorstack, filePath); + } + +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/mentorstack/storage/UserPrefsStorage.java similarity index 67% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/seedu/mentorstack/storage/UserPrefsStorage.java index e94ca422ea8..94f672529a1 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/mentorstack/storage/UserPrefsStorage.java @@ -1,15 +1,15 @@ -package seedu.address.storage; +package seedu.mentorstack.storage; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.ReadOnlyUserPrefs; -import seedu.address.model.UserPrefs; +import seedu.mentorstack.commons.exceptions.DataLoadingException; +import seedu.mentorstack.model.ReadOnlyUserPrefs; +import seedu.mentorstack.model.UserPrefs; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link seedu.mentorstack.model.UserPrefs}. */ public interface UserPrefsStorage { @@ -27,7 +27,7 @@ public interface UserPrefsStorage { Optional readUserPrefs() throws DataLoadingException; /** - * Saves the given {@link seedu.address.model.ReadOnlyUserPrefs} to the storage. + * Saves the given {@link seedu.mentorstack.model.ReadOnlyUserPrefs} to the storage. * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/mentorstack/ui/CommandBox.java similarity index 66% rename from src/main/java/seedu/address/ui/CommandBox.java rename to src/main/java/seedu/mentorstack/ui/CommandBox.java index 9e75478664b..1824d134f25 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/mentorstack/ui/CommandBox.java @@ -1,12 +1,14 @@ -package seedu.address.ui; +package seedu.mentorstack.ui; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextField; import javafx.scene.layout.Region; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; +import javafx.scene.layout.StackPane; +import seedu.mentorstack.logic.commands.CommandResult; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.parser.exceptions.ParseWithHintException; /** * The UI component that is responsible for receiving user command inputs. @@ -21,6 +23,9 @@ public class CommandBox extends UiPart { @FXML private TextField commandTextField; + @FXML + private TextField promptTextField; + /** * Creates a {@code CommandBox} with the given {@code CommandExecutor}. */ @@ -29,6 +34,7 @@ public CommandBox(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; // calls #setStyleToDefault() whenever there is a change to the text of the command box. commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); + promptTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); } /** @@ -44,8 +50,14 @@ private void handleCommandEntered() { try { commandExecutor.execute(commandText); commandTextField.setText(""); + } catch (ParseWithHintException eh) { + setStyleToIndicateCommandFailure(); + setPromptAsHint(eh); + System.out.println(eh.getMessage()); } catch (CommandException | ParseException e) { setStyleToIndicateCommandFailure(); + System.out.println(e.getMessage()); + } } @@ -54,6 +66,7 @@ private void handleCommandEntered() { */ private void setStyleToDefault() { commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS); + promptTextField.setVisible(false); } /** @@ -67,6 +80,14 @@ private void setStyleToIndicateCommandFailure() { } styleClass.add(ERROR_STYLE_CLASS); + + StackPane.setAlignment(promptTextField, javafx.geometry.Pos.CENTER); + } + + + private void setPromptAsHint(ParseWithHintException exceptionWithHint) { + promptTextField.setVisible(true); + promptTextField.setPromptText(commandTextField.getText() + ' ' + exceptionWithHint.getHint()); } /** @@ -77,7 +98,7 @@ public interface CommandExecutor { /** * Executes the command and returns the result. * - * @see seedu.address.logic.Logic#execute(String) + * @see seedu.mentorstack.logic.Logic#execute(String) */ CommandResult execute(String commandText) throws CommandException, ParseException; } diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/mentorstack/ui/HelpWindow.java similarity index 92% rename from src/main/java/seedu/address/ui/HelpWindow.java rename to src/main/java/seedu/mentorstack/ui/HelpWindow.java index 3f16b2fcf26..094a69b9ae8 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/mentorstack/ui/HelpWindow.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.mentorstack.ui; import java.util.logging.Logger; @@ -8,14 +8,14 @@ import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import javafx.stage.Stage; -import seedu.address.commons.core.LogsCenter; +import seedu.mentorstack.commons.core.LogsCenter; /** * Controller for a help page */ public class HelpWindow extends UiPart { - public static final String USERGUIDE_URL = "https://se-education.org/addressbook-level3/UserGuide.html"; + public static final String USERGUIDE_URL = "https://ay2425s2-cs2103-w11-1.github.io/tp/UserGuide.html"; public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL; private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/mentorstack/ui/MainWindow.java similarity index 58% rename from src/main/java/seedu/address/ui/MainWindow.java rename to src/main/java/seedu/mentorstack/ui/MainWindow.java index 79e74ef37c0..1e35d6c47c7 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/mentorstack/ui/MainWindow.java @@ -1,21 +1,31 @@ -package seedu.address.ui; +package seedu.mentorstack.ui; +import java.util.Map; import java.util.logging.Logger; +import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.chart.BarChart; +import javafx.scene.chart.CategoryAxis; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.control.CheckMenuItem; +import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; import javafx.stage.Stage; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.Logic; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.mentorstack.commons.core.GuiSettings; +import seedu.mentorstack.commons.core.LogsCenter; +import seedu.mentorstack.logic.Logic; +import seedu.mentorstack.logic.LogicManager; +import seedu.mentorstack.logic.commands.CommandResult; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.logic.parser.exceptions.ParseException; /** * The Main Window. Provides the basic application layout containing @@ -35,12 +45,18 @@ public class MainWindow extends UiPart { private ResultDisplay resultDisplay; private HelpWindow helpWindow; + @FXML + private Menu themeBar; + @FXML private StackPane commandBoxPlaceholder; @FXML private MenuItem helpMenuItem; + @FXML + private MenuItem statMenuItem; + @FXML private StackPane personListPanelPlaceholder; @@ -50,6 +66,9 @@ public class MainWindow extends UiPart { @FXML private StackPane statusbarPlaceholder; + @FXML + private BarChart barChart; + /** * Creates a {@code MainWindow} with the given {@code Stage} and {@code Logic}. */ @@ -116,7 +135,7 @@ void fillInnerParts() { resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getMentorstackFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(this::executeCommand); @@ -163,16 +182,109 @@ private void handleExit() { primaryStage.hide(); } + private ObservableList getCleanStyleSheetObject() { + Scene scene = primaryStage.getScene(); + + ObservableList stylesheets = scene.getStylesheets(); + + // remove all stylesheets + for (int i = 0; i < stylesheets.toArray().length; i++) { + stylesheets.remove(i); + } + + // add back required stylesheets + stylesheets.add(getClass().getResource("/view/BaseTheme.css").toExternalForm()); + stylesheets.add(getClass().getResource("/view/Extensions.css").toExternalForm()); + + return stylesheets; + } + + + private void uncheckAllBoxes() { + Map namespace = fxmlLoader.getNamespace(); + int k = 0; + while (namespace.get("helpMenuItem" + String.valueOf(k)) != null) { + CheckMenuItem helpMenuItem = (CheckMenuItem) namespace.get("helpMenuItem" + String.valueOf(k)); + helpMenuItem.setSelected(false); + k += 1; + } + } + + @FXML + private void handleThemeSwap0() { + getCleanStyleSheetObject().add( + getClass().getResource("/view/palette1.css").toExternalForm() + ); + uncheckAllBoxes(); + Map namespace = fxmlLoader.getNamespace(); + CheckMenuItem helpMenuItem = (CheckMenuItem) namespace.get("helpMenuItem" + String.valueOf(0)); + helpMenuItem.setSelected(true); + } + + @FXML + private void handleThemeSwap1() { + getCleanStyleSheetObject().add( + getClass().getResource("/view/palette2.css").toExternalForm() + ); + uncheckAllBoxes(); + Map namespace = fxmlLoader.getNamespace(); + CheckMenuItem helpMenuItem = (CheckMenuItem) namespace.get("helpMenuItem" + String.valueOf(1)); + helpMenuItem.setSelected(true); + } + + @FXML + private void handleThemeSwap2() { + getCleanStyleSheetObject().add( + getClass().getResource("/view/palette3.css").toExternalForm() + ); + uncheckAllBoxes(); + Map namespace = fxmlLoader.getNamespace(); + CheckMenuItem helpMenuItem = (CheckMenuItem) namespace.get("helpMenuItem" + String.valueOf(2)); + helpMenuItem.setSelected(true); + } + public PersonListPanel getPersonListPanel() { return personListPanel; } + /** + * Triggers from menuItem click. Generates statistics window. + */ + public void handleStatWindow() { + + CategoryAxis xAxis = new CategoryAxis(); + NumberAxis yAxis = new NumberAxis(); + + xAxis.setLabel("Subjects"); + yAxis.setLabel("Students"); + yAxis.setTickUnit(1); + + BarChart barChart = new BarChart<>(xAxis, yAxis); + barChart.setTitle("Student Distribution"); + + XYChart.Series series = new XYChart.Series<>(); + series.setName(""); + + Map studentsBySubjects = ((LogicManager) logic).getStudentsBySubject(); + + series = ((LogicManager) logic).populateSeries(series, studentsBySubjects); + barChart.getData().add(series); + + Stage popupStage = new Stage(); + popupStage.setTitle("Statistics"); + + Scene scene = new Scene(barChart, 600, 400); + popupStage.setScene(scene); + popupStage.show(); + } + /** * Executes the command and returns the result. * - * @see seedu.address.logic.Logic#execute(String) + * @see seedu.mentorstack.logic.Logic#execute(String) */ - private CommandResult executeCommand(String commandText) throws CommandException, ParseException { + private CommandResult executeCommand(String commandText) throws CommandException, + ParseException { try { CommandResult commandResult = logic.execute(commandText); logger.info("Result: " + commandResult.getFeedbackToUser()); diff --git a/src/main/java/seedu/mentorstack/ui/PersonCard.java b/src/main/java/seedu/mentorstack/ui/PersonCard.java new file mode 100644 index 00000000000..2d8a7be2bb0 --- /dev/null +++ b/src/main/java/seedu/mentorstack/ui/PersonCard.java @@ -0,0 +1,94 @@ +package seedu.mentorstack.ui; + +import java.util.Comparator; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.shape.Circle; +import seedu.mentorstack.model.person.Person; + +/** + * An UI component that displays information of a {@code Person}. + */ +public class PersonCard extends UiPart { + + private static final String FXML = "PersonListCard.fxml"; + private static final String MALE_IMAGE_PATH = "/images/male_profile.png"; + private static final String FEMALE_IMAGE_PATH = "/images/female_profile.png"; + private static final String DEFAULT_IMAGE_PATH = "/images/default_profile.png"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + public final Person person; + + @FXML + private HBox cardPane; + @FXML + private ImageView profilePicture; + @FXML + private Label name; + @FXML + private Label id; + @FXML + private Label phone; + @FXML + private Label email; + @FXML + private FlowPane subjects; + @FXML + private Circle markerCircle; + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to display. + */ + public PersonCard(Person person, int displayedIndex) { + super(FXML); + this.person = person; + id.setText(displayedIndex + ". "); + name.setText(person.getName().fullName); + phone.setText(person.getPhone().value); + email.setText(person.getEmail().value); + + // Load the profile picture based on gender + profilePicture.setImage(new Image(getGenderImagePath(person))); + + person.getSubjects().stream() + .sorted(Comparator.comparing(subject -> subject.subjectName)) + .forEach(subject -> subjects.getChildren().add(new Label(subject.subjectName))); + + person.getFinishedSubjects().stream() + .sorted(Comparator.comparing(subject -> subject.subjectName)) + .forEach(subject -> { + Label label = new Label(subject.subjectName); + label.getStyleClass().add("finished"); + subjects.getChildren().add(label); + }); + + updateMarkedStyle(); + } + + private void updateMarkedStyle() { + markerCircle.setVisible(person.getIsMarked()); + } + + private String getGenderImagePath(Person person) { + if (person.getGender().value.equals("M")) { + return getClass().getResource(MALE_IMAGE_PATH).toExternalForm(); + } else if (person.getGender().value.equals("F")) { + return getClass().getResource(FEMALE_IMAGE_PATH).toExternalForm(); + } else { + return getClass().getResource(DEFAULT_IMAGE_PATH).toExternalForm(); + } + } +} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/mentorstack/ui/PersonListPanel.java similarity index 91% rename from src/main/java/seedu/address/ui/PersonListPanel.java rename to src/main/java/seedu/mentorstack/ui/PersonListPanel.java index f4c501a897b..b5065aaf8a1 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/mentorstack/ui/PersonListPanel.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.mentorstack.ui; import java.util.logging.Logger; @@ -7,8 +7,8 @@ import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.model.person.Person; +import seedu.mentorstack.commons.core.LogsCenter; +import seedu.mentorstack.model.person.Person; /** * Panel containing the list of persons. diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/mentorstack/ui/ResultDisplay.java similarity index 95% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/seedu/mentorstack/ui/ResultDisplay.java index 7d98e84eedf..78aa597bd37 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/mentorstack/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.mentorstack.ui; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/mentorstack/ui/StatusBarFooter.java similarity index 95% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/seedu/mentorstack/ui/StatusBarFooter.java index b577f829423..3637547fef0 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/mentorstack/ui/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.mentorstack.ui; import java.nio.file.Path; import java.nio.file.Paths; diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/mentorstack/ui/Ui.java similarity index 84% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/seedu/mentorstack/ui/Ui.java index 17aa0b494fe..38ce896559d 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/seedu/mentorstack/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.mentorstack.ui; import javafx.stage.Stage; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/mentorstack/ui/UiManager.java similarity index 91% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/seedu/mentorstack/ui/UiManager.java index fdf024138bc..4bcc44ac83e 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/mentorstack/ui/UiManager.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.mentorstack.ui; import java.util.logging.Logger; @@ -7,10 +7,10 @@ import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; import javafx.stage.Stage; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; +import seedu.mentorstack.MainApp; +import seedu.mentorstack.commons.core.LogsCenter; +import seedu.mentorstack.commons.util.StringUtil; +import seedu.mentorstack.logic.Logic; /** * The manager of the UI component. @@ -65,7 +65,7 @@ void showAlertDialogAndWait(Alert.AlertType type, String title, String headerTex private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, String contentText) { final Alert alert = new Alert(type); - alert.getDialogPane().getStylesheets().add("view/DarkTheme.css"); + alert.getDialogPane().getStylesheets().add("view/BaseTheme.css"); alert.initOwner(owner); alert.setTitle(title); alert.setHeaderText(headerText); diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/mentorstack/ui/UiPart.java similarity index 95% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/seedu/mentorstack/ui/UiPart.java index fc820e01a9c..c56ba2b1f54 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/mentorstack/ui/UiPart.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.mentorstack.ui; import static java.util.Objects.requireNonNull; @@ -6,7 +6,7 @@ import java.net.URL; import javafx.fxml.FXMLLoader; -import seedu.address.MainApp; +import seedu.mentorstack.MainApp; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. @@ -17,7 +17,7 @@ public abstract class UiPart { /** Resource folder where FXML files are stored. */ public static final String FXML_FILE_FOLDER = "/view/"; - private final FXMLLoader fxmlLoader = new FXMLLoader(); + protected final FXMLLoader fxmlLoader = new FXMLLoader(); /** * Constructs a UiPart with the specified FXML file URL. diff --git a/src/main/main.iml b/src/main/main.iml new file mode 100644 index 00000000000..b89a3f5b0a6 --- /dev/null +++ b/src/main/main.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/main/resources/images/default_profile.png b/src/main/resources/images/default_profile.png new file mode 100644 index 00000000000..eac9c632302 Binary files /dev/null and b/src/main/resources/images/default_profile.png differ diff --git a/src/main/resources/images/female_profile.png b/src/main/resources/images/female_profile.png new file mode 100644 index 00000000000..c35bf9e67f9 Binary files /dev/null and b/src/main/resources/images/female_profile.png differ diff --git a/src/main/resources/images/male_profile.png b/src/main/resources/images/male_profile.png new file mode 100644 index 00000000000..bddd3bca0c1 Binary files /dev/null and b/src/main/resources/images/male_profile.png differ diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/BaseTheme.css similarity index 59% rename from src/main/resources/view/DarkTheme.css rename to src/main/resources/view/BaseTheme.css index 36e6b001cd8..d5d13d91bc8 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/BaseTheme.css @@ -1,26 +1,25 @@ .background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ + -fx-background-color: derive(-large-theme, -5%); } .label { -fx-font-size: 11pt; -fx-font-family: "Segoe UI Semibold"; - -fx-text-fill: #555555; + -fx-text-fill: -large-theme; -fx-opacity: 0.9; } .label-bright { -fx-font-size: 11pt; -fx-font-family: "Segoe UI Semibold"; - -fx-text-fill: white; + -fx-text-fill: -opp-font-color; -fx-opacity: 1; } .label-header { -fx-font-size: 32pt; -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-text-fill: -opp-font-color; -fx-opacity: 1; } @@ -40,9 +39,9 @@ } .table-view { - -fx-base: #1d1d1d; - -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; + -fx-base: -large-theme; + -fx-control-inner-background: -large-theme; + -fx-background-color: -large-theme; -fx-table-cell-border-color: transparent; -fx-table-header-border-color: transparent; -fx-padding: 5; @@ -67,7 +66,7 @@ .table-view .column-header .label { -fx-font-size: 20pt; -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-text-fill: -opp-font-color; -fx-alignment: center-left; -fx-opacity: 1; } @@ -77,20 +76,20 @@ } .split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-large-theme, 20%); -fx-border-color: transparent transparent transparent #4d4d4d; } .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-large-theme, 20%); } .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-large-theme, 20%); } .list-cell { @@ -100,111 +99,111 @@ } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: -color-card-even; } .list-cell:filled:odd { - -fx-background-color: #515658; + -fx-background-color: -color-card-odd; } .list-cell:filled:selected { - -fx-background-color: #424d5f; + -fx-background-color: -color-card-selected; } .list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; + -fx-border-color: -color-card-selected; -fx-border-width: 1; } .list-cell .label { - -fx-text-fill: white; + -fx-text-fill: -opp-font-color; } .cell_big_label { -fx-font-family: "Segoe UI Semibold"; -fx-font-size: 16px; - -fx-text-fill: #010504; + -fx-text-fill: -large-theme; } .cell_small_label { -fx-font-family: "Segoe UI"; -fx-font-size: 13px; - -fx-text-fill: #010504; + -fx-text-fill: -large-theme; } .stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-large-theme, 20%); } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); + -fx-background-color: derive(-large-theme, 20%); + -fx-border-color: derive(-large-theme, 10%); -fx-border-top-width: 1px; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(-large-theme, 30%); } .result-display { -fx-background-color: transparent; -fx-font-family: "Segoe UI Light"; -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-text-fill: -opp-font-color; } .result-display .label { - -fx-text-fill: black !important; + -fx-text-fill: derive(-large-theme, -5%) !important; } .status-bar .label { -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-text-fill: -opp-font-color; -fx-padding: 4px; -fx-pref-height: 30px; } .status-bar-with-border { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 25%); + -fx-background-color: derive(-large-theme, 30%); + -fx-border-color: derive(-large-theme, 25%); -fx-border-width: 1px; } .status-bar-with-border .label { - -fx-text-fill: white; + -fx-text-fill: -opp-font-color; } .grid-pane { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(-large-theme, 30%); + -fx-border-color: derive(-large-theme, 30%); -fx-border-width: 1px; } .grid-pane .stack-pane { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(-large-theme, 30%); } .context-menu { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(-large-theme, 50%); } .context-menu .label { - -fx-text-fill: white; + -fx-text-fill: -opp-font-color; } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-large-theme, 20%); } .menu-bar .label { -fx-font-size: 14pt; -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-text-fill: -opp-font-color; -fx-opacity: 0.9; } .menu .left-container { - -fx-background-color: black; + -fx-background-color: derive(-large-theme, -5%); } /* @@ -214,13 +213,13 @@ */ .button { -fx-padding: 5 22 5 22; - -fx-border-color: #e2e2e2; + -fx-border-color: -opp-font-color; -fx-border-width: 2; -fx-background-radius: 0; - -fx-background-color: #1d1d1d; + -fx-background-color: derive(-large-theme, -5%); -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; -fx-font-size: 11pt; - -fx-text-fill: #d8d8d8; + -fx-text-fill: -opp-font-color; -fx-background-insets: 0 0 0 0, 0, 1, 2; } @@ -229,12 +228,12 @@ } .button:pressed, .button:default:hover:pressed { - -fx-background-color: white; - -fx-text-fill: #1d1d1d; + -fx-background-color: -opp-font-color; + -fx-text-fill: -large-theme; } .button:focused { - -fx-border-color: white, white; + -fx-border-color: -opp-font-color, -opp-font-color; -fx-border-width: 1, 1; -fx-border-style: solid, segments(1, 1); -fx-border-radius: 0, 0; @@ -243,13 +242,13 @@ .button:disabled, .button:default:disabled { -fx-opacity: 0.4; - -fx-background-color: #1d1d1d; - -fx-text-fill: white; + -fx-background-color: -large-theme; + -fx-text-fill: -opp-font-color; } .button:default { -fx-background-color: -fx-focus-color; - -fx-text-fill: #ffffff; + -fx-text-fill: -opp-font-color; } .button:default:hover { @@ -257,36 +256,36 @@ } .dialog-pane { - -fx-background-color: #1d1d1d; + -fx-background-color: -large-theme; } .dialog-pane > *.button-bar > *.container { - -fx-background-color: #1d1d1d; + -fx-background-color: -large-theme; } .dialog-pane > *.label.content { -fx-font-size: 14px; -fx-font-weight: bold; - -fx-text-fill: white; + -fx-text-fill: -opp-font-color; } .dialog-pane:header *.header-panel { - -fx-background-color: derive(#1d1d1d, 25%); + -fx-background-color: derive(-large-theme, 25%); } .dialog-pane:header *.header-panel *.label { -fx-font-size: 18px; -fx-font-style: italic; - -fx-fill: white; - -fx-text-fill: white; + -fx-fill: -opp-font-color; + -fx-text-fill: -opp-font-color; } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(-large-theme, 20%); } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(-large-theme, 50%); -fx-background-insets: 3; } @@ -318,33 +317,55 @@ } #commandTextField { - -fx-background-color: transparent #383838 transparent #383838; + -fx-background-color: transparent -large-theme transparent -large-theme; -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; + -fx-border-color: -large-theme -large-theme -opp-font-color -large-theme; -fx-border-insets: 0; -fx-border-width: 1; -fx-font-family: "Segoe UI Light"; -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-text-fill: -opp-font-color; } +#promptTextField { + -fx-background-color: transparent -large-theme transparent -large-theme; + -fx-background-insets: 0; + -fx-border-color: -large-theme -large-theme -opp-font-color -large-theme; + -fx-border-insets: 0; + -fx-border-width: 1; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; + -fx-text-fill: -opp-font-color; +} + + + #filterField, #personListPanel, #personWebpage { - -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); + -fx-effect: innershadow(gaussian, derive(-large-theme, -5%), 10, 0, 0, 0); } #resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-color: transparent, -large-theme, transparent, -large-theme; -fx-background-radius: 0; } -#tags { +#subjects { -fx-hgap: 7; -fx-vgap: 3; } -#tags .label { - -fx-text-fill: white; - -fx-background-color: #3e7b91; +#subjects .label { + -fx-text-fill: -opp-font-color; + -fx-background-color: -color-tag; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + +#subjects .finished { + -fx-text-fill: -opp-font-color; + -fx-background-color: -color-tag-finish; -fx-padding: 1 3 1 3; -fx-border-radius: 2; -fx-background-radius: 2; diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 124283a392e..ace2ba41829 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -1,9 +1,9 @@ - + - + + - diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964..13071d81b76 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -1,11 +1,12 @@ - .error { - -fx-text-fill: #d06651 !important; /* The error class should always override the default text-fill style */ + -fx-text-fill: rgba(169, 169, 169, 0.6); /* Grey color with opacity (semi-transparent) */ + -fx-font-style: italic; /* Italicized text */ + -fx-opacity: 0.6; /* Lower opacity for a subtle hint */ } .list-cell:empty { /* Empty cells will not have alternating colours */ - -fx-background: #383838; + -fx-background: -large-theme; } .tag-selector { diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 7778f666a0a..5f96049ce11 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -3,23 +3,26 @@ + + + title="Mentorstack" minWidth="450" minHeight="600" onCloseRequest="#handleExit"> - + + @@ -31,20 +34,29 @@ + + + + + + + - - - - - + + + + + + - - - - - + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index 84e09833a87..acd78537409 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -2,6 +2,7 @@ + @@ -9,7 +10,11 @@ - + + + + + @@ -18,7 +23,7 @@ - + - + diff --git a/src/main/resources/view/palette1.css b/src/main/resources/view/palette1.css new file mode 100644 index 00000000000..cf08ede2e7e --- /dev/null +++ b/src/main/resources/view/palette1.css @@ -0,0 +1,9 @@ +* { + -large-theme:#1d1d1d; + -color-card-odd: #515658; + -color-card-even: #3c3e3f; + -color-card-selected: #424d5f; + -color-tag: #3e7b91; + -color-tag-finish: darkred; + -opp-font-color: white; +} diff --git a/src/main/resources/view/palette2.css b/src/main/resources/view/palette2.css new file mode 100644 index 00000000000..b22c78df7e3 --- /dev/null +++ b/src/main/resources/view/palette2.css @@ -0,0 +1,9 @@ +* { + -large-theme:#ffc8dd; + -color-card-odd: #ffd3e4; + -color-card-even: #ffe7f0; + -color-card-selected: rgb(163, 217, 247); + -color-tag: #e99ab8; + -color-tag-finish: #d099e2; + -opp-font-color: black; +} diff --git a/src/main/resources/view/palette3.css b/src/main/resources/view/palette3.css new file mode 100644 index 00000000000..89bbdf42ef4 --- /dev/null +++ b/src/main/resources/view/palette3.css @@ -0,0 +1,9 @@ +* { + -large-theme:white; + -color-card-odd: #eeeeee; + -color-card-even: #dddddd; + -color-card-selected: #cccccc; + -color-tag: #add8e6; + -color-tag-finish: #ff474c; + -opp-font-color: black; +} diff --git a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json b/src/test/data/JsonMentorstackStorageTest/invalidAndValidPersonMentorstack.json similarity index 70% rename from src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json rename to src/test/data/JsonMentorstackStorageTest/invalidAndValidPersonMentorstack.json index 6a4d2b7181c..e0bd2edbfde 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidAndValidPersonAddressBook.json +++ b/src/test/data/JsonMentorstackStorageTest/invalidAndValidPersonMentorstack.json @@ -1,13 +1,15 @@ { "persons": [ { "name": "Valid Person", + "gender": "M", "phone": "9482424", "email": "hans@example.com", - "address": "4th street" + "subject" : [ "CS2105" ] }, { "name": "Person With Invalid Phone Field", + "gender": "M", "phone": "948asdf2424", "email": "hans@example.com", - "address": "4th street" + "subject" : [ "CS2105" ] } ] } diff --git a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json b/src/test/data/JsonMentorstackStorageTest/invalidPersonMentorstack.json similarity index 75% rename from src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json rename to src/test/data/JsonMentorstackStorageTest/invalidPersonMentorstack.json index ccd21f7d1a9..1151b043323 100644 --- a/src/test/data/JsonAddressBookStorageTest/invalidPersonAddressBook.json +++ b/src/test/data/JsonMentorstackStorageTest/invalidPersonMentorstack.json @@ -1,8 +1,9 @@ { "persons": [ { "name": "Person with invalid name field: Ha!ns Mu@ster", + "gender": "M", "phone": "9482424", "email": "hans@example.com", - "address": "4th street" + "subject" : [ "CS2105" ] } ] } diff --git a/src/test/data/JsonAddressBookStorageTest/notJsonFormatAddressBook.json b/src/test/data/JsonMentorstackStorageTest/notJsonFormatMentorstack.json similarity index 100% rename from src/test/data/JsonAddressBookStorageTest/notJsonFormatAddressBook.json rename to src/test/data/JsonMentorstackStorageTest/notJsonFormatMentorstack.json diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableMentorstackTest/duplicatePersonMentorstack.json similarity index 67% rename from src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json rename to src/test/data/JsonSerializableMentorstackTest/duplicatePersonMentorstack.json index a7427fe7aa2..1b93b5da970 100644 --- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json +++ b/src/test/data/JsonSerializableMentorstackTest/duplicatePersonMentorstack.json @@ -1,14 +1,15 @@ { "persons": [ { "name": "Alice Pauline", + "gender" : "F", "phone": "94351253", "email": "alice@example.com", - "address": "123, Jurong West Ave 6, #08-111", - "tags": [ "friends" ] + "subject" : [ "CS2105" ] }, { "name": "Alice Pauline", + "gender" : "F", "phone": "94351253", "email": "pauline@example.com", - "address": "4th street" + "subject" : [ "CS2105" ] } ] } diff --git a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json b/src/test/data/JsonSerializableMentorstackTest/invalidPersonMentorstack.json similarity index 69% rename from src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json rename to src/test/data/JsonSerializableMentorstackTest/invalidPersonMentorstack.json index ad3f135ae42..5b06e413203 100644 --- a/src/test/data/JsonSerializableAddressBookTest/invalidPersonAddressBook.json +++ b/src/test/data/JsonSerializableMentorstackTest/invalidPersonMentorstack.json @@ -1,8 +1,9 @@ { "persons": [ { "name": "Hans Muster", + "gender" : "M", "phone": "9482424", "email": "invalid@email!3e", - "address": "4th street" + "subject" : [ "CS2105" ] } ] } diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableMentorstackTest/typicalPersonsMentorstack.json similarity index 53% rename from src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json rename to src/test/data/JsonSerializableMentorstackTest/typicalPersonsMentorstack.json index 72262099d35..6ecfcd9d167 100644 --- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json +++ b/src/test/data/JsonSerializableMentorstackTest/typicalPersonsMentorstack.json @@ -1,46 +1,46 @@ { - "_comment": "AddressBook save file which contains the same Person values as in TypicalPersons#getTypicalAddressBook()", + "_comment": "Mentorstack save file which contains the same Person values as in TypicalPersons#getTypicalMentorstack()", "persons" : [ { "name" : "Alice Pauline", + "gender" : "F", "phone" : "94351253", - "email" : "alice@example.com", - "address" : "123, Jurong West Ave 6, #08-111", - "tags" : [ "friends" ] + "email" : "amy@gmail.com", + "subject" : [ "CS1010S" ] }, { "name" : "Benson Meier", + "gender" : "M", "phone" : "98765432", "email" : "johnd@example.com", - "address" : "311, Clementi Ave 2, #02-25", - "tags" : [ "owesMoney", "friends" ] + "subject" : [ "CS1010C", "CS2100" ] }, { "name" : "Carl Kurz", + "gender" : "M", "phone" : "95352563", "email" : "heinz@example.com", - "address" : "wall street", - "tags" : [ ] + "subject" : [ "CS1010C", "CS2100" ] }, { "name" : "Daniel Meier", + "gender" : "M", "phone" : "87652533", "email" : "cornelia@example.com", - "address" : "10th street", - "tags" : [ "friends" ] + "subject" : [ "CS2106" ] }, { "name" : "Elle Meyer", + "gender" : "F", "phone" : "9482224", "email" : "werner@example.com", - "address" : "michegan ave", - "tags" : [ ] + "subject" : [ "CS1010C", "CS2100" ] }, { "name" : "Fiona Kunz", + "gender" : "F", "phone" : "9482427", "email" : "lydia@example.com", - "address" : "little tokyo", - "tags" : [ ] + "subject" : [ "CS1010C", "CS2100" ] }, { "name" : "George Best", + "gender" : "M", "phone" : "9482442", "email" : "anna@example.com", - "address" : "4th street", - "tags" : [ ] + "subject" : [ "CS1010C", "CS2100" ] } ] } diff --git a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json index 1037548a9cd..74972c78569 100644 --- a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json @@ -9,5 +9,5 @@ "z" : 99 } }, - "addressBookFilePath" : "addressbook.json" + "mentorstackFilePath" : "mentorstack.json" } diff --git a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json index b819bed900a..cbcea8dfe05 100644 --- a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json @@ -7,5 +7,5 @@ "y" : 100 } }, - "addressBookFilePath" : "addressbook.json" + "mentorstackFilePath" : "mentorstack.json" } diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java deleted file mode 100644 index 80d9110c03a..00000000000 --- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package seedu.address.logic.commands; - -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import org.junit.jupiter.api.Test; - -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; - -public class ClearCommandTest { - - @Test - public void execute_emptyAddressBook_success() { - Model model = new ModelManager(); - Model expectedModel = new ModelManager(); - - assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); - } - - @Test - public void execute_nonEmptyAddressBook_success() { - Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - expectedModel.setAddressBook(new AddressBook()); - - assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); - } - -} diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java deleted file mode 100644 index 5bc11d3cdaa..00000000000 --- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java +++ /dev/null @@ -1,196 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; -import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalPersons.AMY; -import static seedu.address.testutil.TypicalPersons.BOB; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.Messages; -import seedu.address.logic.commands.AddCommand; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; -import seedu.address.testutil.PersonBuilder; - -public class AddCommandParserTest { - private AddCommandParser parser = new AddCommandParser(); - - @Test - public void parse_allFieldsPresent_success() { - Person expectedPerson = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).build(); - - // whitespace only preamble - assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson)); - - - // multiple tags - all accepted - Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND) - .build(); - assertParseSuccess(parser, - NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, - new AddCommand(expectedPersonMultipleTags)); - } - - @Test - public void parse_repeatedNonTagValue_failure() { - String validExpectedPersonString = NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_FRIEND; - - // multiple names - assertParseFailure(parser, NAME_DESC_AMY + validExpectedPersonString, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME)); - - // multiple phones - assertParseFailure(parser, PHONE_DESC_AMY + validExpectedPersonString, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE)); - - // multiple emails - assertParseFailure(parser, EMAIL_DESC_AMY + validExpectedPersonString, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL)); - - // multiple addresses - assertParseFailure(parser, ADDRESS_DESC_AMY + validExpectedPersonString, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS)); - - // multiple fields repeated - assertParseFailure(parser, - validExpectedPersonString + PHONE_DESC_AMY + EMAIL_DESC_AMY + NAME_DESC_AMY + ADDRESS_DESC_AMY - + validExpectedPersonString, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_ADDRESS, PREFIX_EMAIL, PREFIX_PHONE)); - - // invalid value followed by valid value - - // invalid name - assertParseFailure(parser, INVALID_NAME_DESC + validExpectedPersonString, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME)); - - // invalid email - assertParseFailure(parser, INVALID_EMAIL_DESC + validExpectedPersonString, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL)); - - // invalid phone - assertParseFailure(parser, INVALID_PHONE_DESC + validExpectedPersonString, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE)); - - // invalid address - assertParseFailure(parser, INVALID_ADDRESS_DESC + validExpectedPersonString, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS)); - - // valid value followed by invalid value - - // invalid name - assertParseFailure(parser, validExpectedPersonString + INVALID_NAME_DESC, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME)); - - // invalid email - assertParseFailure(parser, validExpectedPersonString + INVALID_EMAIL_DESC, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL)); - - // invalid phone - assertParseFailure(parser, validExpectedPersonString + INVALID_PHONE_DESC, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE)); - - // invalid address - assertParseFailure(parser, validExpectedPersonString + INVALID_ADDRESS_DESC, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ADDRESS)); - } - - @Test - public void parse_optionalFieldsMissing_success() { - // zero tags - Person expectedPerson = new PersonBuilder(AMY).withTags().build(); - assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY, - new AddCommand(expectedPerson)); - } - - @Test - public void parse_compulsoryFieldMissing_failure() { - String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE); - - // missing name prefix - assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing phone prefix - assertParseFailure(parser, NAME_DESC_BOB + VALID_PHONE_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing email prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + VALID_EMAIL_BOB + ADDRESS_DESC_BOB, - expectedMessage); - - // missing address prefix - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_ADDRESS_BOB, - expectedMessage); - - // all prefixes missing - assertParseFailure(parser, VALID_NAME_BOB + VALID_PHONE_BOB + VALID_EMAIL_BOB + VALID_ADDRESS_BOB, - expectedMessage); - } - - @Test - public void parse_invalidValue_failure() { - // invalid name - assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS); - - // invalid phone - assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS); - - // invalid email - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS); - - // invalid address - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC - + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS); - - // invalid tag - assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB - + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS); - - // two invalid values, only first invalid value reported - assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC, - Name.MESSAGE_CONSTRAINTS); - - // non-empty preamble - assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB - + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, - String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } -} diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java deleted file mode 100644 index 5a1ab3dbc0c..00000000000 --- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package seedu.address.logic.parser; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; -import seedu.address.testutil.PersonBuilder; -import seedu.address.testutil.PersonUtil; - -public class AddressBookParserTest { - - private final AddressBookParser parser = new AddressBookParser(); - - @Test - public void parseCommand_add() throws Exception { - Person person = new PersonBuilder().build(); - AddCommand command = (AddCommand) parser.parseCommand(PersonUtil.getAddCommand(person)); - assertEquals(new AddCommand(person), command); - } - - @Test - public void parseCommand_clear() throws Exception { - assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD) instanceof ClearCommand); - assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD + " 3") instanceof ClearCommand); - } - - @Test - public void parseCommand_delete() throws Exception { - DeleteCommand command = (DeleteCommand) parser.parseCommand( - DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased()); - assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command); - } - - @Test - public void parseCommand_edit() throws Exception { - Person person = new PersonBuilder().build(); - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build(); - EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " " - + INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor)); - assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command); - } - - @Test - public void parseCommand_exit() throws Exception { - assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand); - assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD + " 3") instanceof ExitCommand); - } - - @Test - public void parseCommand_find() throws Exception { - List keywords = Arrays.asList("foo", "bar", "baz"); - FindCommand command = (FindCommand) parser.parseCommand( - FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); - assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command); - } - - @Test - public void parseCommand_help() throws Exception { - assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD) instanceof HelpCommand); - assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD + " 3") instanceof HelpCommand); - } - - @Test - public void parseCommand_list() throws Exception { - assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD) instanceof ListCommand); - assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand); - } - - @Test - public void parseCommand_unrecognisedInput_throwsParseException() { - assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), () - -> parser.parseCommand("")); - } - - @Test - public void parseCommand_unknownCommand_throwsParseException() { - assertThrows(ParseException.class, MESSAGE_UNKNOWN_COMMAND, () -> parser.parseCommand("unknownCommand")); - } -} diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java deleted file mode 100644 index cc7175172d4..00000000000 --- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java +++ /dev/null @@ -1,208 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_ADDRESS_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_NAME_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; -import static seedu.address.logic.commands.CommandTestUtil.INVALID_TAG_DESC; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_PERSON; - -import org.junit.jupiter.api.Test; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; -import seedu.address.testutil.EditPersonDescriptorBuilder; - -public class EditCommandParserTest { - - private static final String TAG_EMPTY = " " + PREFIX_TAG; - - private static final String MESSAGE_INVALID_FORMAT = - String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE); - - private EditCommandParser parser = new EditCommandParser(); - - @Test - public void parse_missingParts_failure() { - // no index specified - assertParseFailure(parser, VALID_NAME_AMY, MESSAGE_INVALID_FORMAT); - - // no field specified - assertParseFailure(parser, "1", EditCommand.MESSAGE_NOT_EDITED); - - // no index and no field specified - assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); - } - - @Test - public void parse_invalidPreamble_failure() { - // negative index - assertParseFailure(parser, "-5" + NAME_DESC_AMY, MESSAGE_INVALID_FORMAT); - - // zero index - assertParseFailure(parser, "0" + NAME_DESC_AMY, MESSAGE_INVALID_FORMAT); - - // invalid arguments being parsed as preamble - assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); - - // invalid prefix being parsed as preamble - assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); - } - - @Test - public void parse_invalidValue_failure() { - assertParseFailure(parser, "1" + INVALID_NAME_DESC, Name.MESSAGE_CONSTRAINTS); // invalid name - assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone - assertParseFailure(parser, "1" + INVALID_EMAIL_DESC, Email.MESSAGE_CONSTRAINTS); // invalid email - assertParseFailure(parser, "1" + INVALID_ADDRESS_DESC, Address.MESSAGE_CONSTRAINTS); // invalid address - assertParseFailure(parser, "1" + INVALID_TAG_DESC, Tag.MESSAGE_CONSTRAINTS); // invalid tag - - // invalid phone followed by valid email - assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS); - - // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Person} being edited, - // parsing it together with a valid tag results in error - assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS); - assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS); - assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS); - - // multiple invalid values, but only the first invalid value is captured - assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_ADDRESS_AMY + VALID_PHONE_AMY, - Name.MESSAGE_CONSTRAINTS); - } - - @Test - public void parse_allFieldsSpecified_success() { - Index targetIndex = INDEX_SECOND_PERSON; - String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND - + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND; - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) - .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) - .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - - assertParseSuccess(parser, userInput, expectedCommand); - } - - @Test - public void parse_someFieldsSpecified_success() { - Index targetIndex = INDEX_FIRST_PERSON; - String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + EMAIL_DESC_AMY; - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_AMY).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - - assertParseSuccess(parser, userInput, expectedCommand); - } - - @Test - public void parse_oneFieldSpecified_success() { - // name - Index targetIndex = INDEX_THIRD_PERSON; - String userInput = targetIndex.getOneBased() + NAME_DESC_AMY; - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // phone - userInput = targetIndex.getOneBased() + PHONE_DESC_AMY; - descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // email - userInput = targetIndex.getOneBased() + EMAIL_DESC_AMY; - descriptor = new EditPersonDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // address - userInput = targetIndex.getOneBased() + ADDRESS_DESC_AMY; - descriptor = new EditPersonDescriptorBuilder().withAddress(VALID_ADDRESS_AMY).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - - // tags - userInput = targetIndex.getOneBased() + TAG_DESC_FRIEND; - descriptor = new EditPersonDescriptorBuilder().withTags(VALID_TAG_FRIEND).build(); - expectedCommand = new EditCommand(targetIndex, descriptor); - assertParseSuccess(parser, userInput, expectedCommand); - } - - @Test - public void parse_multipleRepeatedFields_failure() { - // More extensive testing of duplicate parameter detections is done in - // AddCommandParserTest#parse_repeatedNonTagValue_failure() - - // valid followed by invalid - Index targetIndex = INDEX_FIRST_PERSON; - String userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + PHONE_DESC_BOB; - - assertParseFailure(parser, userInput, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE)); - - // invalid followed by valid - userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + INVALID_PHONE_DESC; - - assertParseFailure(parser, userInput, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE)); - - // mulltiple valid fields repeated - userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY - + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND - + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND; - - assertParseFailure(parser, userInput, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS)); - - // multiple invalid values - userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + INVALID_ADDRESS_DESC + INVALID_EMAIL_DESC - + INVALID_PHONE_DESC + INVALID_ADDRESS_DESC + INVALID_EMAIL_DESC; - - assertParseFailure(parser, userInput, - Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS)); - } - - @Test - public void parse_resetTags_success() { - Index targetIndex = INDEX_THIRD_PERSON; - String userInput = targetIndex.getOneBased() + TAG_EMPTY; - - EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build(); - EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); - - assertParseSuccess(parser, userInput, expectedCommand); - } -} diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java deleted file mode 100644 index 68c8c5ba4d5..00000000000 --- a/src/test/java/seedu/address/model/AddressBookTest.java +++ /dev/null @@ -1,108 +0,0 @@ -package seedu.address.model; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.testutil.PersonBuilder; - -public class AddressBookTest { - - private final AddressBook addressBook = new AddressBook(); - - @Test - public void constructor() { - assertEquals(Collections.emptyList(), addressBook.getPersonList()); - } - - @Test - public void resetData_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> addressBook.resetData(null)); - } - - @Test - public void resetData_withValidReadOnlyAddressBook_replacesData() { - AddressBook newData = getTypicalAddressBook(); - addressBook.resetData(newData); - assertEquals(newData, addressBook); - } - - @Test - public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { - // Two persons with the same identity fields - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - List newPersons = Arrays.asList(ALICE, editedAlice); - AddressBookStub newData = new AddressBookStub(newPersons); - - assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData)); - } - - @Test - public void hasPerson_nullPerson_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> addressBook.hasPerson(null)); - } - - @Test - public void hasPerson_personNotInAddressBook_returnsFalse() { - assertFalse(addressBook.hasPerson(ALICE)); - } - - @Test - public void hasPerson_personInAddressBook_returnsTrue() { - addressBook.addPerson(ALICE); - assertTrue(addressBook.hasPerson(ALICE)); - } - - @Test - public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() { - addressBook.addPerson(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) - .build(); - assertTrue(addressBook.hasPerson(editedAlice)); - } - - @Test - public void getPersonList_modifyList_throwsUnsupportedOperationException() { - assertThrows(UnsupportedOperationException.class, () -> addressBook.getPersonList().remove(0)); - } - - @Test - public void toStringMethod() { - String expected = AddressBook.class.getCanonicalName() + "{persons=" + addressBook.getPersonList() + "}"; - assertEquals(expected, addressBook.toString()); - } - - /** - * A stub ReadOnlyAddressBook whose persons list can violate interface constraints. - */ - private static class AddressBookStub implements ReadOnlyAddressBook { - private final ObservableList persons = FXCollections.observableArrayList(); - - AddressBookStub(Collection persons) { - this.persons.setAll(persons); - } - - @Override - public ObservableList getPersonList() { - return persons; - } - } - -} diff --git a/src/test/java/seedu/address/model/person/AddressTest.java b/src/test/java/seedu/address/model/person/AddressTest.java deleted file mode 100644 index 314885eca26..00000000000 --- a/src/test/java/seedu/address/model/person/AddressTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package seedu.address.model.person; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; - -import org.junit.jupiter.api.Test; - -public class AddressTest { - - @Test - public void constructor_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new Address(null)); - } - - @Test - public void constructor_invalidAddress_throwsIllegalArgumentException() { - String invalidAddress = ""; - assertThrows(IllegalArgumentException.class, () -> new Address(invalidAddress)); - } - - @Test - public void isValidAddress() { - // null address - assertThrows(NullPointerException.class, () -> Address.isValidAddress(null)); - - // invalid addresses - assertFalse(Address.isValidAddress("")); // empty string - assertFalse(Address.isValidAddress(" ")); // spaces only - - // valid addresses - assertTrue(Address.isValidAddress("Blk 456, Den Road, #01-355")); - assertTrue(Address.isValidAddress("-")); // one character - assertTrue(Address.isValidAddress("Leng Inc; 1234 Market St; San Francisco CA 2349879; USA")); // long address - } - - @Test - public void equals() { - Address address = new Address("Valid Address"); - - // same values -> returns true - assertTrue(address.equals(new Address("Valid Address"))); - - // same object -> returns true - assertTrue(address.equals(address)); - - // null -> returns false - assertFalse(address.equals(null)); - - // different types -> returns false - assertFalse(address.equals(5.0f)); - - // different values -> returns false - assertFalse(address.equals(new Address("Other Valid Address"))); - } -} diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java deleted file mode 100644 index 64d07d79ee2..00000000000 --- a/src/test/java/seedu/address/model/tag/TagTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.model.tag; - -import static seedu.address.testutil.Assert.assertThrows; - -import org.junit.jupiter.api.Test; - -public class TagTest { - - @Test - public void constructor_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> new Tag(null)); - } - - @Test - public void constructor_invalidTagName_throwsIllegalArgumentException() { - String invalidTagName = ""; - assertThrows(IllegalArgumentException.class, () -> new Tag(invalidTagName)); - } - - @Test - public void isValidTagName() { - // null tag name - assertThrows(NullPointerException.class, () -> Tag.isValidTagName(null)); - } - -} diff --git a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java deleted file mode 100644 index 4e5ce9200c8..00000000000 --- a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package seedu.address.storage; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.HOON; -import static seedu.address.testutil.TypicalPersons.IDA; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; - -public class JsonAddressBookStorageTest { - private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonAddressBookStorageTest"); - - @TempDir - public Path testFolder; - - @Test - public void readAddressBook_nullFilePath_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> readAddressBook(null)); - } - - private java.util.Optional readAddressBook(String filePath) throws Exception { - return new JsonAddressBookStorage(Paths.get(filePath)).readAddressBook(addToTestDataPathIfNotNull(filePath)); - } - - private Path addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) { - return prefsFileInTestDataFolder != null - ? TEST_DATA_FOLDER.resolve(prefsFileInTestDataFolder) - : null; - } - - @Test - public void read_missingFile_emptyResult() throws Exception { - assertFalse(readAddressBook("NonExistentFile.json").isPresent()); - } - - @Test - public void read_notJsonFormat_exceptionThrown() { - assertThrows(DataLoadingException.class, () -> readAddressBook("notJsonFormatAddressBook.json")); - } - - @Test - public void readAddressBook_invalidPersonAddressBook_throwDataLoadingException() { - assertThrows(DataLoadingException.class, () -> readAddressBook("invalidPersonAddressBook.json")); - } - - @Test - public void readAddressBook_invalidAndValidPersonAddressBook_throwDataLoadingException() { - assertThrows(DataLoadingException.class, () -> readAddressBook("invalidAndValidPersonAddressBook.json")); - } - - @Test - public void readAndSaveAddressBook_allInOrder_success() throws Exception { - Path filePath = testFolder.resolve("TempAddressBook.json"); - AddressBook original = getTypicalAddressBook(); - JsonAddressBookStorage jsonAddressBookStorage = new JsonAddressBookStorage(filePath); - - // Save in new file and read back - jsonAddressBookStorage.saveAddressBook(original, filePath); - ReadOnlyAddressBook readBack = jsonAddressBookStorage.readAddressBook(filePath).get(); - assertEquals(original, new AddressBook(readBack)); - - // Modify data, overwrite exiting file, and read back - original.addPerson(HOON); - original.removePerson(ALICE); - jsonAddressBookStorage.saveAddressBook(original, filePath); - readBack = jsonAddressBookStorage.readAddressBook(filePath).get(); - assertEquals(original, new AddressBook(readBack)); - - // Save and read without specifying file path - original.addPerson(IDA); - jsonAddressBookStorage.saveAddressBook(original); // file path not specified - readBack = jsonAddressBookStorage.readAddressBook().get(); // file path not specified - assertEquals(original, new AddressBook(readBack)); - - } - - @Test - public void saveAddressBook_nullAddressBook_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> saveAddressBook(null, "SomeFile.json")); - } - - /** - * Saves {@code addressBook} at the specified {@code filePath}. - */ - private void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) { - try { - new JsonAddressBookStorage(Paths.get(filePath)) - .saveAddressBook(addressBook, addToTestDataPathIfNotNull(filePath)); - } catch (IOException ioe) { - throw new AssertionError("There should not be an error writing to the file.", ioe); - } - } - - @Test - public void saveAddressBook_nullFilePath_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> saveAddressBook(new AddressBook(), null)); - } -} diff --git a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java deleted file mode 100644 index 188c9058d20..00000000000 --- a/src/test/java/seedu/address/storage/JsonSerializableAddressBookTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package seedu.address.storage; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static seedu.address.testutil.Assert.assertThrows; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.junit.jupiter.api.Test; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.AddressBook; -import seedu.address.testutil.TypicalPersons; - -public class JsonSerializableAddressBookTest { - - private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonSerializableAddressBookTest"); - private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("typicalPersonsAddressBook.json"); - private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonAddressBook.json"); - private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonAddressBook.json"); - - @Test - public void toModelType_typicalPersonsFile_success() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(TYPICAL_PERSONS_FILE, - JsonSerializableAddressBook.class).get(); - AddressBook addressBookFromFile = dataFromFile.toModelType(); - AddressBook typicalPersonsAddressBook = TypicalPersons.getTypicalAddressBook(); - assertEquals(addressBookFromFile, typicalPersonsAddressBook); - } - - @Test - public void toModelType_invalidPersonFile_throwsIllegalValueException() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(INVALID_PERSON_FILE, - JsonSerializableAddressBook.class).get(); - assertThrows(IllegalValueException.class, dataFromFile::toModelType); - } - - @Test - public void toModelType_duplicatePersons_throwsIllegalValueException() throws Exception { - JsonSerializableAddressBook dataFromFile = JsonUtil.readJsonFile(DUPLICATE_PERSON_FILE, - JsonSerializableAddressBook.class).get(); - assertThrows(IllegalValueException.class, JsonSerializableAddressBook.MESSAGE_DUPLICATE_PERSON, - dataFromFile::toModelType); - } - -} diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java deleted file mode 100644 index fec76fb7129..00000000000 --- a/src/test/java/seedu/address/testutil/TypicalPersons.java +++ /dev/null @@ -1,76 +0,0 @@ -package seedu.address.testutil; - -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import seedu.address.model.AddressBook; -import seedu.address.model.person.Person; - -/** - * A utility class containing a list of {@code Person} objects to be used in tests. - */ -public class TypicalPersons { - - public static final Person ALICE = new PersonBuilder().withName("Alice Pauline") - .withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com") - .withPhone("94351253") - .withTags("friends").build(); - public static final Person BENSON = new PersonBuilder().withName("Benson Meier") - .withAddress("311, Clementi Ave 2, #02-25") - .withEmail("johnd@example.com").withPhone("98765432") - .withTags("owesMoney", "friends").build(); - public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") - .withEmail("heinz@example.com").withAddress("wall street").build(); - public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") - .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build(); - public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224") - .withEmail("werner@example.com").withAddress("michegan ave").build(); - public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427") - .withEmail("lydia@example.com").withAddress("little tokyo").build(); - public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442") - .withEmail("anna@example.com").withAddress("4th street").build(); - - // Manually added - public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424") - .withEmail("stefan@example.com").withAddress("little india").build(); - public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131") - .withEmail("hans@example.com").withAddress("chicago ave").build(); - - // Manually added - Person's details found in {@code CommandTestUtil} - public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY) - .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build(); - public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) - .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND) - .build(); - - public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER - - private TypicalPersons() {} // prevents instantiation - - /** - * Returns an {@code AddressBook} with all the typical persons. - */ - public static AddressBook getTypicalAddressBook() { - AddressBook ab = new AddressBook(); - for (Person person : getTypicalPersons()) { - ab.addPerson(person); - } - return ab; - } - - public static List getTypicalPersons() { - return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); - } -} diff --git a/src/test/java/seedu/address/AppParametersTest.java b/src/test/java/seedu/mentorstack/AppParametersTest.java similarity index 99% rename from src/test/java/seedu/address/AppParametersTest.java rename to src/test/java/seedu/mentorstack/AppParametersTest.java index 133cc008bce..f68f7fc9a48 100644 --- a/src/test/java/seedu/address/AppParametersTest.java +++ b/src/test/java/seedu/mentorstack/AppParametersTest.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.mentorstack; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/java/seedu/address/commons/core/ConfigTest.java b/src/test/java/seedu/mentorstack/commons/core/ConfigTest.java similarity index 94% rename from src/test/java/seedu/address/commons/core/ConfigTest.java rename to src/test/java/seedu/mentorstack/commons/core/ConfigTest.java index d3ba2a52a89..46457e2864e 100644 --- a/src/test/java/seedu/address/commons/core/ConfigTest.java +++ b/src/test/java/seedu/mentorstack/commons/core/ConfigTest.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.mentorstack.commons.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/src/test/java/seedu/address/commons/core/GuiSettingsTest.java b/src/test/java/seedu/mentorstack/commons/core/GuiSettingsTest.java similarity index 93% rename from src/test/java/seedu/address/commons/core/GuiSettingsTest.java rename to src/test/java/seedu/mentorstack/commons/core/GuiSettingsTest.java index b7876c4349d..f97101c4d8a 100644 --- a/src/test/java/seedu/address/commons/core/GuiSettingsTest.java +++ b/src/test/java/seedu/mentorstack/commons/core/GuiSettingsTest.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.mentorstack.commons.core; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/seedu/address/commons/core/VersionTest.java b/src/test/java/seedu/mentorstack/commons/core/VersionTest.java similarity index 97% rename from src/test/java/seedu/address/commons/core/VersionTest.java rename to src/test/java/seedu/mentorstack/commons/core/VersionTest.java index 495cd231554..ec84c930641 100644 --- a/src/test/java/seedu/address/commons/core/VersionTest.java +++ b/src/test/java/seedu/mentorstack/commons/core/VersionTest.java @@ -1,8 +1,8 @@ -package seedu.address.commons.core; +package seedu.mentorstack.commons.core; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/seedu/address/commons/core/index/IndexTest.java b/src/test/java/seedu/mentorstack/commons/core/index/IndexTest.java similarity index 95% rename from src/test/java/seedu/address/commons/core/index/IndexTest.java rename to src/test/java/seedu/mentorstack/commons/core/index/IndexTest.java index fc395ab964b..baed6bbad31 100644 --- a/src/test/java/seedu/address/commons/core/index/IndexTest.java +++ b/src/test/java/seedu/mentorstack/commons/core/index/IndexTest.java @@ -1,9 +1,9 @@ -package seedu.address.commons.core.index; +package seedu.mentorstack.commons.core.index; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/seedu/address/commons/util/AppUtilTest.java b/src/test/java/seedu/mentorstack/commons/util/AppUtilTest.java similarity index 91% rename from src/test/java/seedu/address/commons/util/AppUtilTest.java rename to src/test/java/seedu/mentorstack/commons/util/AppUtilTest.java index 594de1e6365..6df554f21bc 100644 --- a/src/test/java/seedu/address/commons/util/AppUtilTest.java +++ b/src/test/java/seedu/mentorstack/commons/util/AppUtilTest.java @@ -1,7 +1,7 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/seedu/address/commons/util/CollectionUtilTest.java b/src/test/java/seedu/mentorstack/commons/util/CollectionUtilTest.java similarity index 95% rename from src/test/java/seedu/address/commons/util/CollectionUtilTest.java rename to src/test/java/seedu/mentorstack/commons/util/CollectionUtilTest.java index b467a3dc025..ca634277438 100644 --- a/src/test/java/seedu/address/commons/util/CollectionUtilTest.java +++ b/src/test/java/seedu/mentorstack/commons/util/CollectionUtilTest.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.mentorstack.testutil.Assert.assertThrows; import java.util.Arrays; import java.util.Collection; diff --git a/src/test/java/seedu/address/commons/util/ConfigUtilTest.java b/src/test/java/seedu/mentorstack/commons/util/ConfigUtilTest.java similarity index 94% rename from src/test/java/seedu/address/commons/util/ConfigUtilTest.java rename to src/test/java/seedu/mentorstack/commons/util/ConfigUtilTest.java index 69d7b89cfd8..3d0fdf21103 100644 --- a/src/test/java/seedu/address/commons/util/ConfigUtilTest.java +++ b/src/test/java/seedu/mentorstack/commons/util/ConfigUtilTest.java @@ -1,8 +1,8 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.Assert.assertThrows; import java.io.IOException; import java.nio.file.Path; @@ -13,8 +13,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataLoadingException; +import seedu.mentorstack.commons.core.Config; +import seedu.mentorstack.commons.exceptions.DataLoadingException; public class ConfigUtilTest { diff --git a/src/test/java/seedu/address/commons/util/FileUtilTest.java b/src/test/java/seedu/mentorstack/commons/util/FileUtilTest.java similarity index 83% rename from src/test/java/seedu/address/commons/util/FileUtilTest.java rename to src/test/java/seedu/mentorstack/commons/util/FileUtilTest.java index 1fe5478c756..85d60fc02ac 100644 --- a/src/test/java/seedu/address/commons/util/FileUtilTest.java +++ b/src/test/java/seedu/mentorstack/commons/util/FileUtilTest.java @@ -1,8 +1,8 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/seedu/address/commons/util/JsonUtilTest.java b/src/test/java/seedu/mentorstack/commons/util/JsonUtilTest.java similarity index 91% rename from src/test/java/seedu/address/commons/util/JsonUtilTest.java rename to src/test/java/seedu/mentorstack/commons/util/JsonUtilTest.java index d4907539dee..509d81e868a 100644 --- a/src/test/java/seedu/address/commons/util/JsonUtilTest.java +++ b/src/test/java/seedu/mentorstack/commons/util/JsonUtilTest.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -7,8 +7,8 @@ import org.junit.jupiter.api.Test; -import seedu.address.testutil.SerializableTestClass; -import seedu.address.testutil.TestUtil; +import seedu.mentorstack.testutil.SerializableTestClass; +import seedu.mentorstack.testutil.TestUtil; /** * Tests JSON Read and Write diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/seedu/mentorstack/commons/util/StringUtilTest.java similarity index 98% rename from src/test/java/seedu/address/commons/util/StringUtilTest.java rename to src/test/java/seedu/mentorstack/commons/util/StringUtilTest.java index c56d407bf3f..a716aca080c 100644 --- a/src/test/java/seedu/address/commons/util/StringUtilTest.java +++ b/src/test/java/seedu/mentorstack/commons/util/StringUtilTest.java @@ -1,8 +1,8 @@ -package seedu.address.commons.util; +package seedu.mentorstack.commons.util; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.Assert.assertThrows; import java.io.FileNotFoundException; diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/mentorstack/logic/LogicManagerTest.java similarity index 71% rename from src/test/java/seedu/address/logic/LogicManagerTest.java rename to src/test/java/seedu/mentorstack/logic/LogicManagerTest.java index baf8ce336a2..53c5c2091e1 100644 --- a/src/test/java/seedu/address/logic/LogicManagerTest.java +++ b/src/test/java/seedu/mentorstack/logic/LogicManagerTest.java @@ -1,14 +1,15 @@ -package seedu.address.logic; +package seedu.mentorstack.logic; import static org.junit.jupiter.api.Assertions.assertEquals; -import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; -import static seedu.address.logic.Messages.MESSAGE_UNKNOWN_COMMAND; -import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.AMY; +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX; +import static seedu.mentorstack.logic.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.mentorstack.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.GENDER_DESC_FEMALE; +import static seedu.mentorstack.logic.commands.CommandTestUtil.NAME_DESC_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.SUBJECT_DESC_CS2100; +import static seedu.mentorstack.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.TypicalPersons.AMY; import java.io.IOException; import java.nio.file.AccessDeniedException; @@ -18,20 +19,20 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; -import seedu.address.storage.JsonAddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; -import seedu.address.storage.StorageManager; -import seedu.address.testutil.PersonBuilder; +import seedu.mentorstack.logic.commands.AddCommand; +import seedu.mentorstack.logic.commands.CommandResult; +import seedu.mentorstack.logic.commands.ListCommand; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.ReadOnlyMentorstack; +import seedu.mentorstack.model.UserPrefs; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.storage.JsonMentorstackStorage; +import seedu.mentorstack.storage.JsonUserPrefsStorage; +import seedu.mentorstack.storage.StorageManager; +import seedu.mentorstack.testutil.PersonBuilder; public class LogicManagerTest { private static final IOException DUMMY_IO_EXCEPTION = new IOException("dummy IO exception"); @@ -45,10 +46,10 @@ public class LogicManagerTest { @BeforeEach public void setUp() { - JsonAddressBookStorage addressBookStorage = - new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json")); + JsonMentorstackStorage mentorstackStorage = + new JsonMentorstackStorage(temporaryFolder.resolve("mentorstack.json")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json")); - StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage); + StorageManager storage = new StorageManager(mentorstackStorage, userPrefsStorage); logic = new LogicManager(model, storage); } @@ -123,7 +124,7 @@ private void assertCommandException(String inputCommand, String expectedMessage) */ private void assertCommandFailure(String inputCommand, Class expectedException, String expectedMessage) { - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getMentorstack(), new UserPrefs()); assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel); } @@ -149,10 +150,10 @@ private void assertCommandFailure(String inputCommand, Class getFilteredPersonList() { public void updateFilteredPersonList(Predicate predicate) { throw new AssertionError("This method should not be called."); } + + @Override + public void archivePerson(Person person, Person archived) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void unarchivePerson(Person personToUnarchive, Person unarchived) { + throw new AssertionError("This method should not be called."); + } + + @Override + public boolean canUndo() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void undo() { + throw new AssertionError("This method should not be called."); + } + + @Override + public void markPerson(Person target, Person marked) { + throw new AssertionError("This method should not be called."); + } + + @Override + public void unmarkPerson(Person target, Person unmarked) { + throw new AssertionError("This method should not be called."); + } } /** @@ -196,8 +229,8 @@ public void addPerson(Person person) { } @Override - public ReadOnlyAddressBook getAddressBook() { - return new AddressBook(); + public ReadOnlyMentorstack getMentorstack() { + return new Mentorstack(); } } diff --git a/src/test/java/seedu/mentorstack/logic/commands/ArchiveCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/ArchiveCommandTest.java new file mode 100644 index 00000000000..ba838df9bf3 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/commands/ArchiveCommandTest.java @@ -0,0 +1,73 @@ +package seedu.mentorstack.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.testutil.TypicalIndexSets.INDEX_SET_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexSets.INDEX_SET_SECOND_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; +import seedu.mentorstack.model.person.Person; + +/** + * Contains integration tests (interaction with the Model) and unit tests for ArchiveCommand. + */ +class ArchiveCommandTest { + + private Model model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + + @Test + public void execute_success() { + Person personToArchive = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + ArchiveCommand archiveCommand = new ArchiveCommand(INDEX_SET_FIRST_PERSON); + + String expectedMessage = String.format(ArchiveCommand.MESSAGE_ARCHIVE_PERSON_SUCCESS, + Messages.format(personToArchive)); + + ModelManager expectedModel = new ModelManager(model.getMentorstack(), new UserPrefs()); + expectedModel.archivePerson(personToArchive, personToArchive.archived()); + + assertCommandSuccess(archiveCommand, model, expectedMessage, expectedModel); + } + + @Test + public void testEquals() { + ArchiveCommand archiveFirstCommand = new ArchiveCommand(INDEX_SET_FIRST_PERSON); + ArchiveCommand archiveSecondCommand = new ArchiveCommand(INDEX_SET_SECOND_PERSON); + + // same object -> returns true + assertTrue(archiveFirstCommand.equals(archiveFirstCommand)); + + // same values -> returns true + ArchiveCommand archiveFirstCommandCopy = new ArchiveCommand(INDEX_SET_FIRST_PERSON); + assertTrue(archiveFirstCommand.equals(archiveFirstCommandCopy)); + + // different types -> returns false + assertFalse(archiveFirstCommand.equals(1)); + + // null -> returns false + assertFalse(archiveFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(archiveFirstCommand.equals(archiveSecondCommand)); + } + + @Test + public void testToString() { + Index targetIndex = Index.fromOneBased(1); + ArchiveCommand archiveCommand = new ArchiveCommand(Set.of(targetIndex)); + String expected = ArchiveCommand.class.getCanonicalName() + "{targetIndex=" + Set.of(targetIndex) + "}"; + assertEquals(expected, archiveCommand.toString()); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/commands/ClearCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/ClearCommandTest.java new file mode 100644 index 00000000000..bd05d691899 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/commands/ClearCommandTest.java @@ -0,0 +1,32 @@ +package seedu.mentorstack.logic.commands; + +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.model.Mentorstack; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; + +public class ClearCommandTest { + + @Test + public void execute_emptyMentorstack_success() { + Model model = new ModelManager(); + Model expectedModel = new ModelManager(); + + assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void execute_nonEmptyMentorstack_success() { + Model model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + Model expectedModel = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + expectedModel.setMentorstack(new Mentorstack()); + + assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel); + } + +} diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/mentorstack/logic/commands/CommandResultTest.java similarity index 98% rename from src/test/java/seedu/address/logic/commands/CommandResultTest.java rename to src/test/java/seedu/mentorstack/logic/commands/CommandResultTest.java index 7b8c7cd4546..ebc4ccc6535 100644 --- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java +++ b/src/test/java/seedu/mentorstack/logic/commands/CommandResultTest.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/mentorstack/logic/commands/CommandTestUtil.java similarity index 68% rename from src/test/java/seedu/address/logic/commands/CommandTestUtil.java rename to src/test/java/seedu/mentorstack/logic/commands/CommandTestUtil.java index 643a1d08069..027bf52ba77 100644 --- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java +++ b/src/test/java/seedu/mentorstack/logic/commands/CommandTestUtil.java @@ -1,58 +1,61 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.mentorstack.testutil.Assert.assertThrows; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Mentorstack; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.NameContainsKeywordsPredicate; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.testutil.EditPersonDescriptorBuilder; /** * Contains helper methods for testing commands. */ public class CommandTestUtil { + public static final String VALID_ARCHIVE_STATUS_TRUE = "true"; + public static final String VALID_ARCHIVE_STATUS_FALSE = "false"; public static final String VALID_NAME_AMY = "Amy Bee"; public static final String VALID_NAME_BOB = "Bob Choo"; public static final String VALID_PHONE_AMY = "11111111"; public static final String VALID_PHONE_BOB = "22222222"; public static final String VALID_EMAIL_AMY = "amy@example.com"; public static final String VALID_EMAIL_BOB = "bob@example.com"; - public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1"; - public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3"; - public static final String VALID_TAG_HUSBAND = "husband"; - public static final String VALID_TAG_FRIEND = "friend"; + public static final String VALID_SUB_CS2102 = "CS2102"; + public static final String VALID_SUB_CS2100 = "CS2100"; + public static final String VALID_MALE = "M"; + public static final String VALID_FEMALE = "F"; + public static final String ARCHIVE_DESC_TRUE = " " + VALID_ARCHIVE_STATUS_FALSE; public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY; public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB; public static final String PHONE_DESC_AMY = " " + PREFIX_PHONE + VALID_PHONE_AMY; public static final String PHONE_DESC_BOB = " " + PREFIX_PHONE + VALID_PHONE_BOB; public static final String EMAIL_DESC_AMY = " " + PREFIX_EMAIL + VALID_EMAIL_AMY; public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB; - public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY; - public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB; - public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND; - public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND; + public static final String GENDER_DESC_MALE = " " + PREFIX_GENDER + VALID_MALE; + public static final String GENDER_DESC_FEMALE = " " + PREFIX_GENDER + VALID_FEMALE; + public static final String SUBJECT_DESC_CS2100 = " " + PREFIX_SUBJECT + VALID_SUB_CS2100; + public static final String SUBJECT_DESC_CS2102 = " " + PREFIX_SUBJECT + VALID_SUB_CS2102; public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones public static final String INVALID_EMAIL_DESC = " " + PREFIX_EMAIL + "bob!yahoo"; // missing '@' symbol - public static final String INVALID_ADDRESS_DESC = " " + PREFIX_ADDRESS; // empty string not allowed for addresses - public static final String INVALID_TAG_DESC = " " + PREFIX_TAG + "hubby*"; // '*' not allowed in tags + public static final String INVALID_GENDER_DESC = " " + PREFIX_GENDER + "male"; + public static final String INVALID_SUBJECT_DESC = " " + PREFIX_SUBJECT + "CS2100*"; // '*' not allowed in subjects public static final String PREAMBLE_WHITESPACE = "\t \r \n"; public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble"; @@ -61,12 +64,12 @@ public class CommandTestUtil { public static final EditCommand.EditPersonDescriptor DESC_BOB; static { - DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) - .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY) - .withTags(VALID_TAG_FRIEND).build(); - DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) - .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB) - .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build(); + DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).withGender(VALID_FEMALE) + .withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) + .withSubjects(VALID_SUB_CS2100).build(); + DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).withGender(VALID_MALE) + .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) + .withSubjects(VALID_SUB_CS2102, VALID_SUB_CS2100).build(); } /** @@ -104,11 +107,11 @@ public static void assertCommandSuccess(Command command, Model actualModel, Stri public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) { // we are unable to defensively copy the model for comparison later, so we can // only do so by copying its components. - AddressBook expectedAddressBook = new AddressBook(actualModel.getAddressBook()); + Mentorstack expectedAddressBook = new Mentorstack(actualModel.getMentorstack()); List expectedFilteredList = new ArrayList<>(actualModel.getFilteredPersonList()); assertThrows(CommandException.class, expectedMessage, () -> command.execute(actualModel)); - assertEquals(expectedAddressBook, actualModel.getAddressBook()); + assertEquals(expectedAddressBook, actualModel.getMentorstack()); assertEquals(expectedFilteredList, actualModel.getFilteredPersonList()); } /** diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/DeleteCommandTest.java similarity index 63% rename from src/test/java/seedu/address/logic/commands/DeleteCommandTest.java rename to src/test/java/seedu/mentorstack/logic/commands/DeleteCommandTest.java index b6f332eabca..25463ef49d1 100644 --- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java +++ b/src/test/java/seedu/mentorstack/logic/commands/DeleteCommandTest.java @@ -1,23 +1,27 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.mentorstack.testutil.TypicalIndexSets.INDEX_SET_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexSets.INDEX_SET_SECOND_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import java.util.Set; import org.junit.jupiter.api.Test; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.Messages; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; +import seedu.mentorstack.model.person.Person; /** * Contains integration tests (interaction with the Model) and unit tests for @@ -25,17 +29,17 @@ */ public class DeleteCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); @Test public void execute_validIndexUnfilteredList_success() { Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); + DeleteCommand deleteCommand = new DeleteCommand(INDEX_SET_FIRST_PERSON); String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete)); - ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + ModelManager expectedModel = new ModelManager(model.getMentorstack(), new UserPrefs()); expectedModel.deletePerson(personToDelete); assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel); @@ -44,7 +48,7 @@ public void execute_validIndexUnfilteredList_success() { @Test public void execute_invalidIndexUnfilteredList_throwsCommandException() { Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); - DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); + DeleteCommand deleteCommand = new DeleteCommand(Set.of(outOfBoundIndex)); assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } @@ -54,12 +58,12 @@ public void execute_validIndexFilteredList_success() { showPersonAtIndex(model, INDEX_FIRST_PERSON); Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); - DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON); + DeleteCommand deleteCommand = new DeleteCommand(INDEX_SET_FIRST_PERSON); String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete)); - Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + Model expectedModel = new ModelManager(model.getMentorstack(), new UserPrefs()); expectedModel.deletePerson(personToDelete); showNoPerson(expectedModel); @@ -72,23 +76,23 @@ public void execute_invalidIndexFilteredList_throwsCommandException() { Index outOfBoundIndex = INDEX_SECOND_PERSON; // ensures that outOfBoundIndex is still in bounds of address book list - assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); + assertTrue(outOfBoundIndex.getZeroBased() < model.getMentorstack().getPersonList().size()); - DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex); + DeleteCommand deleteCommand = new DeleteCommand(Set.of(outOfBoundIndex)); assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); } @Test public void equals() { - DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON); - DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON); + DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_SET_FIRST_PERSON); + DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SET_SECOND_PERSON); // same object -> returns true assertTrue(deleteFirstCommand.equals(deleteFirstCommand)); // same values -> returns true - DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_PERSON); + DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_SET_FIRST_PERSON); assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy)); // different types -> returns false @@ -104,8 +108,11 @@ public void equals() { @Test public void toStringMethod() { Index targetIndex = Index.fromOneBased(1); - DeleteCommand deleteCommand = new DeleteCommand(targetIndex); - String expected = DeleteCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex + "}"; + DeleteCommand deleteCommand = new DeleteCommand(Set.of(targetIndex)); + + // Update expected format to match the actual toString() implementation + String expected = DeleteCommand.class.getCanonicalName() + "{targetIndices=" + Set.of(targetIndex) + "}"; + assertEquals(expected, deleteCommand.toString()); } diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/EditCommandTest.java similarity index 73% rename from src/test/java/seedu/address/logic/commands/EditCommandTest.java rename to src/test/java/seedu/mentorstack/logic/commands/EditCommandTest.java index 469dd97daa7..9bcc68e2fe9 100644 --- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java +++ b/src/test/java/seedu/mentorstack/logic/commands/EditCommandTest.java @@ -1,39 +1,40 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.mentorstack.logic.commands.CommandTestUtil.DESC_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.DESC_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_MALE; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2102; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; import org.junit.jupiter.api.Test; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.Person; -import seedu.address.testutil.EditPersonDescriptorBuilder; -import seedu.address.testutil.PersonBuilder; +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.mentorstack.model.Mentorstack; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.testutil.EditPersonDescriptorBuilder; +import seedu.mentorstack.testutil.PersonBuilder; /** * Contains integration tests (interaction with the Model) and unit tests for EditCommand. */ public class EditCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); @Test public void execute_allFieldsSpecifiedUnfilteredList_success() { @@ -43,7 +44,7 @@ public void execute_allFieldsSpecifiedUnfilteredList_success() { String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)); - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Model expectedModel = new ModelManager(new Mentorstack(model.getMentorstack()), new UserPrefs()); expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); @@ -56,15 +57,18 @@ public void execute_someFieldsSpecifiedUnfilteredList_success() { PersonBuilder personInList = new PersonBuilder(lastPerson); Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB) - .withTags(VALID_TAG_HUSBAND).build(); + .withGender(VALID_MALE) + .withSubjects(VALID_SUB_CS2102).build(); EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB) - .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build(); + .withGender(VALID_MALE) + .withPhone(VALID_PHONE_BOB) + .withSubjects(VALID_SUB_CS2102).build(); EditCommand editCommand = new EditCommand(indexLastPerson, descriptor); String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)); - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Model expectedModel = new ModelManager(new Mentorstack(model.getMentorstack()), new UserPrefs()); expectedModel.setPerson(lastPerson, editedPerson); assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); @@ -77,7 +81,7 @@ public void execute_noFieldSpecifiedUnfilteredList_success() { String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)); - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Model expectedModel = new ModelManager(new Mentorstack(model.getMentorstack()), new UserPrefs()); assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); } @@ -93,7 +97,7 @@ public void execute_filteredList_success() { String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)); - Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs()); + Model expectedModel = new ModelManager(new Mentorstack(model.getMentorstack()), new UserPrefs()); expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson); assertCommandSuccess(editCommand, model, expectedMessage, expectedModel); @@ -113,7 +117,7 @@ public void execute_duplicatePersonFilteredList_failure() { showPersonAtIndex(model, INDEX_FIRST_PERSON); // edit person in filtered list into a duplicate in address book - Person personInList = model.getAddressBook().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); + Person personInList = model.getMentorstack().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased()); EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, new EditPersonDescriptorBuilder(personInList).build()); @@ -138,7 +142,7 @@ public void execute_invalidPersonIndexFilteredList_failure() { showPersonAtIndex(model, INDEX_FIRST_PERSON); Index outOfBoundIndex = INDEX_SECOND_PERSON; // ensures that outOfBoundIndex is still in bounds of address book list - assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size()); + assertTrue(outOfBoundIndex.getZeroBased() < model.getMentorstack().getPersonList().size()); EditCommand editCommand = new EditCommand(outOfBoundIndex, new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build()); diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/mentorstack/logic/commands/EditPersonDescriptorTest.java similarity index 67% rename from src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java rename to src/test/java/seedu/mentorstack/logic/commands/EditPersonDescriptorTest.java index b17c1f3d5c2..2615483720a 100644 --- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java +++ b/src/test/java/seedu/mentorstack/logic/commands/EditPersonDescriptorTest.java @@ -1,20 +1,20 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY; -import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; +import static seedu.mentorstack.logic.commands.CommandTestUtil.DESC_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.DESC_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_MALE; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2102; import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.testutil.EditPersonDescriptorBuilder; +import seedu.mentorstack.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.mentorstack.testutil.EditPersonDescriptorBuilder; public class EditPersonDescriptorTest { @@ -44,16 +44,16 @@ public void equals() { editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withPhone(VALID_PHONE_BOB).build(); assertFalse(DESC_AMY.equals(editedAmy)); - // different email -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withEmail(VALID_EMAIL_BOB).build(); + // different gender -> returns false + editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withGender(VALID_MALE).build(); assertFalse(DESC_AMY.equals(editedAmy)); - // different address -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withAddress(VALID_ADDRESS_BOB).build(); + // different email -> returns false + editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withEmail(VALID_EMAIL_BOB).build(); assertFalse(DESC_AMY.equals(editedAmy)); - // different tags -> returns false - editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTags(VALID_TAG_HUSBAND).build(); + // different subjects -> returns false + editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withSubjects(VALID_SUB_CS2102).build(); assertFalse(DESC_AMY.equals(editedAmy)); } @@ -61,11 +61,11 @@ public void equals() { public void toStringMethod() { EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); String expected = EditPersonDescriptor.class.getCanonicalName() + "{name=" - + editPersonDescriptor.getName().orElse(null) + ", phone=" + + editPersonDescriptor.getName().orElse(null) + ", gender=" + + editPersonDescriptor.getGender().orElse(null) + ", phone=" + editPersonDescriptor.getPhone().orElse(null) + ", email=" - + editPersonDescriptor.getEmail().orElse(null) + ", address=" - + editPersonDescriptor.getAddress().orElse(null) + ", tags=" - + editPersonDescriptor.getTags().orElse(null) + "}"; + + editPersonDescriptor.getEmail().orElse(null) + ", subject=" + + editPersonDescriptor.getSubjects().orElse(null) + "}"; assertEquals(expected, editPersonDescriptor.toString()); } } diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/ExitCommandTest.java similarity index 59% rename from src/test/java/seedu/address/logic/commands/ExitCommandTest.java rename to src/test/java/seedu/mentorstack/logic/commands/ExitCommandTest.java index 9533c473875..3b36f1a57b7 100644 --- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java +++ b/src/test/java/seedu/mentorstack/logic/commands/ExitCommandTest.java @@ -1,12 +1,12 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.logic.commands.ExitCommand.MESSAGE_EXIT_ACKNOWLEDGEMENT; import org.junit.jupiter.api.Test; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; public class ExitCommandTest { private Model model = new ModelManager(); diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/FindCommandTest.java similarity index 79% rename from src/test/java/seedu/address/logic/commands/FindCommandTest.java rename to src/test/java/seedu/mentorstack/logic/commands/FindCommandTest.java index b8b7dbba91a..651a9a5632d 100644 --- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java +++ b/src/test/java/seedu/mentorstack/logic/commands/FindCommandTest.java @@ -1,31 +1,31 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.testutil.TypicalPersons.CARL; -import static seedu.address.testutil.TypicalPersons.ELLE; -import static seedu.address.testutil.TypicalPersons.FIONA; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.mentorstack.logic.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.testutil.TypicalPersons.CARL; +import static seedu.mentorstack.testutil.TypicalPersons.ELLE; +import static seedu.mentorstack.testutil.TypicalPersons.FIONA; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; import java.util.Arrays; import java.util.Collections; import org.junit.jupiter.api.Test; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; +import seedu.mentorstack.model.person.NameContainsKeywordsPredicate; /** * Contains integration tests (interaction with the Model) for {@code FindCommand}. */ public class FindCommandTest { - private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs()); + private Model model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalMentorstack(), new UserPrefs()); @Test public void equals() { diff --git a/src/test/java/seedu/mentorstack/logic/commands/FinishCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/FinishCommandTest.java new file mode 100644 index 00000000000..1514e6950ae --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/commands/FinishCommandTest.java @@ -0,0 +1,115 @@ +package seedu.mentorstack.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Subject; + +class FinishCommandTest { + + private Model model; + + @BeforeEach + void setUp() { + model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + } + + @Test + void execute_validSubjects_success() throws Exception { + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Set subjectsToFinish = new HashSet<>(person.getSubjects()); + FinishCommand finishCommand = new FinishCommand(INDEX_FIRST_PERSON, subjectsToFinish); + + CommandResult result = finishCommand.execute(model); + + assertEquals(FinishCommand.MESSAGE_FINISH_SUBJECT_SUCCESS, result.getFeedbackToUser()); + } + + @Test + void execute_subjectNotEnrolled_throwsCommandException() { + Set nonExistentSubjects = new HashSet<>(); + nonExistentSubjects.add(new Subject("NONEXISTENTSUBJECT")); + FinishCommand finishCommand = new FinishCommand(INDEX_FIRST_PERSON, nonExistentSubjects); + + assertThrows(CommandException.class, () -> finishCommand.execute(model)); + } + + @Test + void execute_invalidIndex_throwsCommandException() { + Index outOfBoundsIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + Set subjects = new HashSet<>(); + subjects.add(new Subject("CS2103")); + FinishCommand finishCommand = new FinishCommand(outOfBoundsIndex, subjects); + + assertThrows(CommandException.class, () -> finishCommand.execute(model)); + } + + @Test + public void execute_finishSubjectsForArchivedPerson_throwsCommandException() { + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person archivedPerson = person.archived(); + Set subjectsToFinish = new HashSet<>(person.getSubjects()); + model.archivePerson(person, archivedPerson); + FinishCommand finishCommand = new FinishCommand(INDEX_FIRST_PERSON, subjectsToFinish); + + assertThrows(CommandException.class, () -> finishCommand.execute(model)); + } + + @Test + public void equals() { + Set subjectsA = new HashSet<>(); + subjectsA.add(new Subject("CS2103")); + FinishCommand commandA = new FinishCommand(INDEX_FIRST_PERSON, subjectsA); + + Set subjectsB = new HashSet<>(); + subjectsB.add(new Subject("CS2101")); + FinishCommand commandB = new FinishCommand(INDEX_SECOND_PERSON, subjectsB); + + // same values -> returns true + FinishCommand commandACopy = new FinishCommand(INDEX_FIRST_PERSON, subjectsA); + assertTrue(commandA.equals(commandACopy)); + + // same object -> returns true + assertTrue(commandA.equals(commandA)); + + // null -> returns false + assertFalse(commandA.equals(null)); + + // different types -> returns false + assertFalse(commandA.equals(new ClearCommand())); + + // different index -> returns false + assertFalse(commandA.equals(commandB)); + + // different subjects -> returns false + assertFalse(commandA.equals(new FinishCommand(INDEX_FIRST_PERSON, subjectsB))); + } + + @Test + public void toStringMethod() { + Index index = Index.fromOneBased(1); + Set subjects = new HashSet<>(); + subjects.add(new Subject("CS2103")); + FinishCommand finishCommand = new FinishCommand(index, subjects); + String expected = FinishCommand.class.getCanonicalName() + + "{index=" + index + ", subjectsToFinish=" + subjects + "}"; + assertEquals(expected, finishCommand.toString()); + } +} diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/HelpCommandTest.java similarity index 59% rename from src/test/java/seedu/address/logic/commands/HelpCommandTest.java rename to src/test/java/seedu/mentorstack/logic/commands/HelpCommandTest.java index 4904fc4352e..73717f46364 100644 --- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java +++ b/src/test/java/seedu/mentorstack/logic/commands/HelpCommandTest.java @@ -1,12 +1,12 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.HelpCommand.SHOWING_HELP_MESSAGE; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.logic.commands.HelpCommand.SHOWING_HELP_MESSAGE; import org.junit.jupiter.api.Test; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; public class HelpCommandTest { private Model model = new ModelManager(); diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/ListCommandTest.java similarity index 55% rename from src/test/java/seedu/address/logic/commands/ListCommandTest.java rename to src/test/java/seedu/mentorstack/logic/commands/ListCommandTest.java index 435ff1f7275..bb40105682b 100644 --- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java +++ b/src/test/java/seedu/mentorstack/logic/commands/ListCommandTest.java @@ -1,16 +1,16 @@ -package seedu.address.logic.commands; +package seedu.mentorstack.logic.commands; -import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; -import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.logic.commands.CommandTestUtil.showPersonAtIndex; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.UserPrefs; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; /** * Contains integration tests (interaction with the Model) and unit tests for ListCommand. @@ -22,8 +22,8 @@ public class ListCommandTest { @BeforeEach public void setUp() { - model = new ModelManager(getTypicalAddressBook(), new UserPrefs()); - expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs()); + model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + expectedModel = new ModelManager(model.getMentorstack(), new UserPrefs()); } @Test diff --git a/src/test/java/seedu/mentorstack/logic/commands/MarkCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/MarkCommandTest.java new file mode 100644 index 00000000000..c2ce30cb674 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/commands/MarkCommandTest.java @@ -0,0 +1,115 @@ +package seedu.mentorstack.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.testutil.PersonBuilder; + +public class MarkCommandTest { + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + } + + @Test + public void execute_validIndex_success() { + Set indices = new HashSet<>(); + indices.add(INDEX_FIRST_PERSON); + MarkCommand markCommand = new MarkCommand(indices); + + Person personToMark = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person markedPerson = new PersonBuilder(personToMark).build().marked(); + + String expectedMessage = String.format(MarkCommand.MESSAGE_MARK_PERSON_SUCCESS, Messages.format(personToMark)); + + Model expectedModel = new ModelManager(model.getMentorstack(), new UserPrefs()); + expectedModel.markPerson(personToMark, markedPerson); + + assertCommandSuccess(markCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndex_throwsCommandException() { + Set indices = new HashSet<>(); + indices.add(Index.fromOneBased(model.getFilteredPersonList().size() + 1)); + MarkCommand markCommand = new MarkCommand(indices); + + assertCommandFailure(markCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void execute_markAlreadyMarkedPerson_success() { + Set indices = new HashSet<>(); + indices.add(INDEX_FIRST_PERSON); + MarkCommand markCommand = new MarkCommand(indices); + + Person personToMark = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + model.markPerson(personToMark, new PersonBuilder(personToMark).build().marked()); + + Person alreadyMarkedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + String expectedMessage = String.format(MarkCommand.MESSAGE_MARK_PERSON_SUCCESS, + Messages.format(alreadyMarkedPerson)); + + Model expectedModel = new ModelManager(model.getMentorstack(), new UserPrefs()); + expectedModel.markPerson(alreadyMarkedPerson, alreadyMarkedPerson); + + assertCommandSuccess(markCommand, model, expectedMessage, expectedModel); + } + + @Test + public void equals() { + Set firstIndices = new HashSet<>(); + firstIndices.add(INDEX_FIRST_PERSON); + Set secondIndices = new HashSet<>(); + secondIndices.add(INDEX_SECOND_PERSON); + + MarkCommand markFirstCommand = new MarkCommand(firstIndices); + MarkCommand markSecondCommand = new MarkCommand(secondIndices); + + // same values -> returns true + Set indices = new HashSet<>(); + indices.add(INDEX_FIRST_PERSON); + MarkCommand commandWithSameValues = new MarkCommand(indices); + assertTrue(markFirstCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(markFirstCommand.equals(markFirstCommand)); + + // null -> returns false + assertFalse(markFirstCommand.equals(null)); + + // different types -> returns false + assertFalse(markFirstCommand.equals(new ClearCommand())); + + // different index -> returns false + assertFalse(markFirstCommand.equals(markSecondCommand)); + } + + @Test + public void toStringMethod() { + Index targetIndex = Index.fromOneBased(1); + MarkCommand markCommand = new MarkCommand(Set.of(targetIndex)); + String expected = MarkCommand.class.getCanonicalName() + "{targetIndex=" + Set.of(targetIndex) + "}"; + assertEquals(expected, markCommand.toString()); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/commands/ShowArchiveCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/ShowArchiveCommandTest.java new file mode 100644 index 00000000000..058f05f15af --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/commands/ShowArchiveCommandTest.java @@ -0,0 +1,34 @@ +package seedu.mentorstack.logic.commands; + +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.model.Model.PREDICATE_SHOW_ALL_ARCHIVED_PERSONS; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; + +/** + * Contains integration tests (interaction with the Model) and unit tests for ShowArchiveCommand. + */ +public class ShowArchiveCommandTest { + + private Model model; + private Model expectedModel; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + expectedModel = new ModelManager(model.getMentorstack(), new UserPrefs()); + } + + @Test + public void execute_listIsNotFiltered_showsSameList() { + expectedModel.updateFilteredPersonList(PREDICATE_SHOW_ALL_ARCHIVED_PERSONS); + assertCommandSuccess(new ShowArchiveCommand(), model, ShowArchiveCommand.MESSAGE_SUCCESS, expectedModel); + } + +} diff --git a/src/test/java/seedu/mentorstack/logic/commands/StatsCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/StatsCommandTest.java new file mode 100644 index 00000000000..1d1acf28700 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/commands/StatsCommandTest.java @@ -0,0 +1,53 @@ +package seedu.mentorstack.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.mentorstack.testutil.TypicalPersons.ALICE; +import static seedu.mentorstack.testutil.TypicalPersons.CARL; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.person.Subject; + +public class StatsCommandTest { + private Model model; + private StatsCommand statsCommand; + + @BeforeEach + public void setUp() { + model = new ModelManager(); + + // Add Alice and Carl to the model + model.addPerson(ALICE); + model.addPerson(CARL); + + // Initialize statsCommand before each test + statsCommand = new StatsCommand(); + } + + @Test + public void execute_noSubject_showsCorrectStats() { + // Execute the command + CommandResult result = statsCommand.execute(model); + + // Validate the result + String expectedMessage = String.format(StatsCommand.MESSAGE_SUCCESS, 2, 1, 1); // 2 persons, 1 male, 1 female + assertEquals(expectedMessage, result.getFeedbackToUser()); + } + + @Test + public void execute_withSubject_showsCorrectStats() { + // Create the command with a subject filter (CS1010S) + StatsCommand statsCommand = new StatsCommand(new Subject("CS1010S")); + + // Execute the command + CommandResult result = statsCommand.execute(model); + + // Validate the result + String expectedMessage = String.format("Statistics for [CS1010S]:\n" + StatsCommand.MESSAGE_SUCCESS, 1, 0, 1); + // 1 person in CS1010S, 0 male, 1 female (Alice) + assertEquals(expectedMessage, result.getFeedbackToUser()); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/commands/UnarchiveCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/UnarchiveCommandTest.java new file mode 100644 index 00000000000..e0297e3c42e --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/commands/UnarchiveCommandTest.java @@ -0,0 +1,96 @@ +package seedu.mentorstack.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.testutil.TypicalIndexSets.INDEX_SET_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexSets.INDEX_SET_SECOND_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; +import seedu.mentorstack.model.person.Person; + +/** + * Contains integration tests (interaction with the Model) and unit tests for UnarchiveCommand. + */ +class UnarchiveCommandTest { + + private Model model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + + @Test + public void execute_success() throws CommandException { + Person personToArchive = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + ArchiveCommand archiveCommand = new ArchiveCommand(INDEX_SET_FIRST_PERSON); + UnarchiveCommand unarchiveCommand = new UnarchiveCommand(INDEX_SET_FIRST_PERSON); + ShowArchiveCommand showArchiveCommand = new ShowArchiveCommand(); + archiveCommand.execute(model); + showArchiveCommand.execute(model); + Person personToUnarchive = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + + + String expectedMessage = String.format(UnarchiveCommand.MESSAGE_UNARCHIVE_PERSON_SUCCESS, + Messages.format(personToUnarchive)); + + ModelManager expectedModel = new ModelManager(model.getMentorstack(), new UserPrefs()); + expectedModel.unarchivePerson(personToUnarchive, personToUnarchive.unarchived()); + + assertCommandSuccess(unarchiveCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_fail() { + Person personToUnarchive = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + UnarchiveCommand unarchiveCommand = new UnarchiveCommand(INDEX_SET_FIRST_PERSON); + + String expectedMessage = String.format(UnarchiveCommand.MESSAGE_UNARCHIVE_PERSON_SUCCESS, + Messages.format(personToUnarchive)); + + ModelManager expectedModel = new ModelManager(model.getMentorstack(), new UserPrefs()); + expectedModel.unarchivePerson(personToUnarchive, personToUnarchive.unarchived()); + + assertCommandFailure(unarchiveCommand, model, + UnarchiveCommand.MESSAGE_DUPLICATE_UNARCHIVE + Messages.format(personToUnarchive)); + } + + @Test + public void testEquals() { + UnarchiveCommand unarchiveFirstCommand = new UnarchiveCommand(INDEX_SET_FIRST_PERSON); + UnarchiveCommand unarchiveSecondCommand = new UnarchiveCommand(INDEX_SET_SECOND_PERSON); + + // same object -> returns true + assertTrue(unarchiveFirstCommand.equals(unarchiveFirstCommand)); + + // same values -> returns true + UnarchiveCommand archiveFirstCommandCopy = new UnarchiveCommand(INDEX_SET_FIRST_PERSON); + assertTrue(unarchiveFirstCommand.equals(archiveFirstCommandCopy)); + + // different types -> returns false + assertFalse(unarchiveFirstCommand.equals(1)); + + // null -> returns false + assertFalse(unarchiveFirstCommand.equals(null)); + + // different person -> returns false + assertFalse(unarchiveFirstCommand.equals(unarchiveSecondCommand)); + } + + @Test + public void testToString() { + Index targetIndex = Index.fromOneBased(1); + UnarchiveCommand unarchiveCommand = new UnarchiveCommand(Set.of(targetIndex)); + String expected = UnarchiveCommand.class.getCanonicalName() + "{targetIndex=" + Set.of(targetIndex) + "}"; + assertEquals(expected, unarchiveCommand.toString()); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/commands/UndoCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/UndoCommandTest.java new file mode 100644 index 00000000000..b6ee2654cc8 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/commands/UndoCommandTest.java @@ -0,0 +1,44 @@ +package seedu.mentorstack.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; + +/** + * Contains tests for {@code UndoCommand}. + */ +public class UndoCommandTest { + + private Model model; + private UndoCommand undoCommand; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + undoCommand = new UndoCommand(); + } + + @Test + public void execute_undo_successful() throws CommandException { + model.rememberMentorstack(); + CommandResult result = undoCommand.execute(model); + + // Verify successful undo + assertEquals(UndoCommand.MESSAGE_SUCCESS, result.getFeedbackToUser()); + assertEquals(getTypicalMentorstack(), model.getMentorstack()); + } + + @Test + public void execute_noPreviousState_throwsCommandException() { + // Attempt to undo without any prior state change + assertThrows(CommandException.class, () -> undoCommand.execute(model), UndoCommand.MESSAGE_FAILURE); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/commands/UnfinishCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/UnfinishCommandTest.java new file mode 100644 index 00000000000..37b2079eaad --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/commands/UnfinishCommandTest.java @@ -0,0 +1,115 @@ +package seedu.mentorstack.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.exceptions.CommandException; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Subject; + +class UnfinishCommandTest { + + private Model model; + + @BeforeEach + void setUp() { + model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + } + + @Test + void execute_validSubjects_success() throws Exception { + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Set subjectsToUnfinish = new HashSet<>(person.getFinishedSubjects()); + UnfinishCommand unfinishCommand = new UnfinishCommand(INDEX_FIRST_PERSON, subjectsToUnfinish); + + CommandResult result = unfinishCommand.execute(model); + + assertEquals(UnfinishCommand.MESSAGE_UNFINISH_SUBJECT_SUCCESS, result.getFeedbackToUser()); + } + + @Test + void execute_subjectNotEnrolled_throwsCommandException() { + Set nonExistentSubjects = new HashSet<>(); + nonExistentSubjects.add(new Subject("NONEXISTENTSUBJECT")); + UnfinishCommand unfinishCommand = new UnfinishCommand(INDEX_FIRST_PERSON, nonExistentSubjects); + + assertThrows(CommandException.class, () -> unfinishCommand.execute(model)); + } + + @Test + void execute_invalidIndex_throwsCommandException() { + Index outOfBoundsIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1); + Set subjects = new HashSet<>(); + subjects.add(new Subject("CS2103")); + UnfinishCommand unfinishCommand = new UnfinishCommand(outOfBoundsIndex, subjects); + + assertThrows(CommandException.class, () -> unfinishCommand.execute(model)); + } + + @Test + public void execute_unfinishSubjectsForArchivedPerson_throwsCommandException() { + Person person = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person archivedPerson = person.archived(); + Set subjectsToUnfinish = new HashSet<>(person.getSubjects()); + model.archivePerson(person, archivedPerson); + UnfinishCommand unfinishCommand = new UnfinishCommand(INDEX_FIRST_PERSON, subjectsToUnfinish); + + assertThrows(CommandException.class, () -> unfinishCommand.execute(model)); + } + + @Test + public void equals() { + Set subjectsA = new HashSet<>(); + subjectsA.add(new Subject("CS2103")); + UnfinishCommand commandA = new UnfinishCommand(INDEX_FIRST_PERSON, subjectsA); + + Set subjectsB = new HashSet<>(); + subjectsB.add(new Subject("CS2101")); + UnfinishCommand commandB = new UnfinishCommand(INDEX_SECOND_PERSON, subjectsB); + + // same values -> returns true + UnfinishCommand commandACopy = new UnfinishCommand(INDEX_FIRST_PERSON, subjectsA); + assertTrue(commandA.equals(commandACopy)); + + // same object -> returns true + assertTrue(commandA.equals(commandA)); + + // null -> returns false + assertFalse(commandA.equals(null)); + + // different types -> returns false + assertFalse(commandA.equals(new ClearCommand())); + + // different index -> returns false + assertFalse(commandA.equals(commandB)); + + // different subjects -> returns false + assertFalse(commandA.equals(new UnfinishCommand(INDEX_FIRST_PERSON, subjectsB))); + } + + @Test + public void toStringMethod() { + Index index = Index.fromOneBased(1); + Set subjects = new HashSet<>(); + subjects.add(new Subject("CS2103")); + UnfinishCommand unfinishCommand = new UnfinishCommand(index, subjects); + String expected = UnfinishCommand.class.getCanonicalName() + + "{index=" + index + ", subjectsToUnfinish=" + subjects + "}"; + assertEquals(expected, unfinishCommand.toString()); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/commands/UnmarkCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/UnmarkCommandTest.java new file mode 100644 index 00000000000..a6efa2b0e0f --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/commands/UnmarkCommandTest.java @@ -0,0 +1,119 @@ +package seedu.mentorstack.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.testutil.PersonBuilder; + +public class UnmarkCommandTest { + private Model model; + + @BeforeEach + public void setUp() { + model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + Person personToMark = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person markedPerson = new PersonBuilder(personToMark).build().marked(); + model.markPerson(personToMark, markedPerson); + } + + @Test + public void execute_validIndex_success() { + Set indices = new HashSet<>(); + indices.add(INDEX_FIRST_PERSON); + UnmarkCommand unmarkCommand = new UnmarkCommand(indices); + + Person personToUnmark = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + Person unmarkedPerson = new PersonBuilder(personToUnmark).build().unmarked(); + + String expectedMessage = String.format(UnmarkCommand.MESSAGE_UNMARK_PERSON_SUCCESS, + Messages.format(personToUnmark)); + + Model expectedModel = new ModelManager(model.getMentorstack(), new UserPrefs()); + expectedModel.unmarkPerson(personToUnmark, unmarkedPerson); + + assertCommandSuccess(unmarkCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndex_throwsCommandException() { + Set indices = new HashSet<>(); + indices.add(Index.fromOneBased(model.getFilteredPersonList().size() + 1)); + UnmarkCommand unmarkCommand = new UnmarkCommand(indices); + + assertCommandFailure(unmarkCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + @Test + public void execute_unmarkAlreadyUnmarkedPerson_success() { + Set indices = new HashSet<>(); + indices.add(INDEX_FIRST_PERSON); + UnmarkCommand unmarkCommand = new UnmarkCommand(indices); + + Person personToUnmark = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + model.unmarkPerson(personToUnmark, new PersonBuilder(personToUnmark).build().unmarked()); + + Person alreadyUnmarkedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased()); + String expectedMessage = String.format(UnmarkCommand.MESSAGE_UNMARK_PERSON_SUCCESS, + Messages.format(alreadyUnmarkedPerson)); + + Model expectedModel = new ModelManager(model.getMentorstack(), new UserPrefs()); + expectedModel.unmarkPerson(alreadyUnmarkedPerson, alreadyUnmarkedPerson); + + assertCommandSuccess(unmarkCommand, model, expectedMessage, expectedModel); + } + + @Test + public void equals() { + Set firstIndices = new HashSet<>(); + firstIndices.add(INDEX_FIRST_PERSON); + Set secondIndices = new HashSet<>(); + secondIndices.add(INDEX_SECOND_PERSON); + + UnmarkCommand unmarkFirstCommand = new UnmarkCommand(firstIndices); + UnmarkCommand unmarkSecondCommand = new UnmarkCommand(secondIndices); + + // same values -> returns true + Set indices = new HashSet<>(); + indices.add(INDEX_FIRST_PERSON); + UnmarkCommand commandWithSameValues = new UnmarkCommand(indices); + assertTrue(unmarkFirstCommand.equals(commandWithSameValues)); + + // same object -> returns true + assertTrue(unmarkFirstCommand.equals(unmarkFirstCommand)); + + // null -> returns false + assertFalse(unmarkFirstCommand.equals(null)); + + // different types -> returns false + assertFalse(unmarkFirstCommand.equals(new ClearCommand())); + + // different index -> returns false + assertFalse(unmarkFirstCommand.equals(unmarkSecondCommand)); + } + + @Test + public void toStringMethod() { + Index targetIndex = Index.fromOneBased(1); + UnmarkCommand unmarkCommand = new UnmarkCommand(Set.of(targetIndex)); + String expected = UnmarkCommand.class.getCanonicalName() + "{targetIndex=" + Set.of(targetIndex) + "}"; + assertEquals(expected, unmarkCommand.toString()); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/commands/ViewCommandTest.java b/src/test/java/seedu/mentorstack/logic/commands/ViewCommandTest.java new file mode 100644 index 00000000000..703393c5910 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/commands/ViewCommandTest.java @@ -0,0 +1,79 @@ +package seedu.mentorstack.logic.commands; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.mentorstack.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import java.util.Collections; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.ModelManager; +import seedu.mentorstack.model.UserPrefs; +import seedu.mentorstack.model.person.Person; + +/** + * Contains integration tests (interaction with the Model) for {@code ViewCommand}. + */ +public class ViewCommandTest { + private Model model = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + private Model expectedModel = new ModelManager(getTypicalMentorstack(), new UserPrefs()); + + @Test + public void equals() { + Predicate firstPredicate = person -> person.getName().fullName.equalsIgnoreCase("Alice"); + Predicate secondPredicate = person -> person.getName().fullName.equalsIgnoreCase("Bob"); + + ViewCommand viewFirstCommand = new ViewCommand(firstPredicate); + ViewCommand viewSecondCommand = new ViewCommand(secondPredicate); + + // same object -> returns true + assertTrue(viewFirstCommand.equals(viewFirstCommand)); + + // same values -> returns true + ViewCommand viewFirstCommandCopy = new ViewCommand(firstPredicate); + assertTrue(viewFirstCommand.equals(viewFirstCommandCopy)); + + // different types -> returns false + assertFalse(viewFirstCommand.equals(1)); + + // null -> returns false + assertFalse(viewFirstCommand.equals(null)); + + // different predicates -> returns false + assertFalse(viewFirstCommand.equals(viewSecondCommand)); + } + + @Test + public void execute_noMatchingResults_noPersonFound() { + Predicate predicate = person -> person.getName().fullName.equalsIgnoreCase("NonExistent"); + ViewCommand command = new ViewCommand(predicate); + expectedModel.updateFilteredPersonList(predicate); + + assertCommandSuccess(command, model, ViewCommand.MESSAGE_NO_MATCH, expectedModel); + assertEquals(Collections.emptyList(), model.getFilteredPersonList()); + } + + @Test + public void execute_multipleMatchingResults_multiplePersonsFound() { + Predicate predicate = person -> person.getName().fullName.contains("Alice") + || person.getName().fullName.contains("Benson"); + ViewCommand command = new ViewCommand(predicate); + expectedModel.updateFilteredPersonList(predicate); + + String expectedMessage = String.format(ViewCommand.MESSAGE_SUCCESS, "[Alice Pauline, Benson Meier]"); + assertCommandSuccess(command, model, expectedMessage, expectedModel); + } + + @Test + public void toStringMethod() { + Predicate predicate = person -> person.getName().fullName.contains("Alice"); + ViewCommand command = new ViewCommand(predicate); + String expected = ViewCommand.class.getCanonicalName() + "{predicate=" + predicate + "}"; + assertEquals(expected, command.toString()); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/AddCommandParserTest.java new file mode 100644 index 00000000000..ffb35102517 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/parser/AddCommandParserTest.java @@ -0,0 +1,189 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.GENDER_DESC_FEMALE; +import static seedu.mentorstack.logic.commands.CommandTestUtil.GENDER_DESC_MALE; +import static seedu.mentorstack.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; +import static seedu.mentorstack.logic.commands.CommandTestUtil.INVALID_GENDER_DESC; +import static seedu.mentorstack.logic.commands.CommandTestUtil.INVALID_NAME_DESC; +import static seedu.mentorstack.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; +import static seedu.mentorstack.logic.commands.CommandTestUtil.INVALID_SUBJECT_DESC; +import static seedu.mentorstack.logic.commands.CommandTestUtil.NAME_DESC_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.NAME_DESC_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.PHONE_DESC_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE; +import static seedu.mentorstack.logic.commands.CommandTestUtil.SUBJECT_DESC_CS2100; +import static seedu.mentorstack.logic.commands.CommandTestUtil.SUBJECT_DESC_CS2102; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_MALE; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2100; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2102; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.mentorstack.testutil.TypicalPersons.AMY; +import static seedu.mentorstack.testutil.TypicalPersons.BOB; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.AddCommand; +import seedu.mentorstack.model.person.Email; +import seedu.mentorstack.model.person.Gender; +import seedu.mentorstack.model.person.Name; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Phone; +import seedu.mentorstack.model.person.Subject; +import seedu.mentorstack.testutil.PersonBuilder; + +public class AddCommandParserTest { + private AddCommandParser parser = new AddCommandParser(); + + @Test + public void parse_allFieldsPresent_success() { + Person expectedPerson = new PersonBuilder(BOB).withSubjects(VALID_SUB_CS2102).build(); + + // whitespace only preamble + assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + GENDER_DESC_MALE + PHONE_DESC_BOB + + EMAIL_DESC_BOB + SUBJECT_DESC_CS2102, new AddCommand(expectedPerson)); + + + // multiple subjects - all accepted + Person expectedPersonMultipleSubjects = new PersonBuilder(BOB).withSubjects(VALID_SUB_CS2100, VALID_SUB_CS2102) + .build(); + assertParseSuccess(parser, + NAME_DESC_BOB + GENDER_DESC_MALE + PHONE_DESC_BOB + EMAIL_DESC_BOB + + SUBJECT_DESC_CS2100 + SUBJECT_DESC_CS2102, + new AddCommand(expectedPersonMultipleSubjects)); + } + + @Test + public void parse_repeatedNonSubjectValue_failure() { + String validExpectedPersonString = NAME_DESC_BOB + GENDER_DESC_MALE + PHONE_DESC_BOB + EMAIL_DESC_BOB + + SUBJECT_DESC_CS2102; + + // multiple names + assertParseFailure(parser, NAME_DESC_AMY + validExpectedPersonString, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME)); + + // multiple phones + assertParseFailure(parser, PHONE_DESC_AMY + validExpectedPersonString, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE)); + + // multiple emails + assertParseFailure(parser, EMAIL_DESC_AMY + validExpectedPersonString, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL)); + + // multiple fields repeated + assertParseFailure(parser, + validExpectedPersonString + PHONE_DESC_AMY + GENDER_DESC_FEMALE + EMAIL_DESC_AMY + + NAME_DESC_AMY + + validExpectedPersonString, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_GENDER, PREFIX_EMAIL, PREFIX_PHONE)); + + // invalid value followed by valid value + + // invalid name + assertParseFailure(parser, INVALID_NAME_DESC + validExpectedPersonString, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME)); + + // invalid email + assertParseFailure(parser, INVALID_EMAIL_DESC + validExpectedPersonString, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL)); + + // invalid phone + assertParseFailure(parser, INVALID_PHONE_DESC + validExpectedPersonString, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE)); + + // valid value followed by invalid value + + // invalid name + assertParseFailure(parser, validExpectedPersonString + INVALID_NAME_DESC, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME)); + + // invalid email + assertParseFailure(parser, validExpectedPersonString + INVALID_EMAIL_DESC, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_EMAIL)); + + // invalid phone + assertParseFailure(parser, validExpectedPersonString + INVALID_PHONE_DESC, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE)); + } + + @Test + public void parse_optionalFieldsMissing_success() { + // zero subjects + Person expectedPerson = new PersonBuilder(AMY).build(); + assertParseSuccess(parser, NAME_DESC_AMY + GENDER_DESC_FEMALE + PHONE_DESC_AMY + EMAIL_DESC_AMY + + SUBJECT_DESC_CS2100, + new AddCommand(expectedPerson)); + } + + @Test + public void parse_compulsoryFieldMissing_failure() { + String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE); + + // missing name prefix + assertParseFailure(parser, VALID_NAME_BOB + GENDER_DESC_MALE + PHONE_DESC_BOB + EMAIL_DESC_BOB, + expectedMessage); + + // missing gender prefix + assertParseFailure(parser, NAME_DESC_BOB + VALID_MALE + PHONE_DESC_BOB + EMAIL_DESC_BOB, + expectedMessage); + + // missing phone prefix + assertParseFailure(parser, NAME_DESC_BOB + GENDER_DESC_MALE + VALID_PHONE_BOB + EMAIL_DESC_BOB, + expectedMessage); + + // missing email prefix + assertParseFailure(parser, NAME_DESC_BOB + GENDER_DESC_MALE + PHONE_DESC_BOB + VALID_EMAIL_BOB, + expectedMessage); + + // all prefixes missing + assertParseFailure(parser, VALID_NAME_BOB + VALID_MALE + VALID_PHONE_BOB + VALID_EMAIL_BOB, + expectedMessage); + } + + @Test + public void parse_invalidValue_failure() { + // invalid name + assertParseFailure(parser, INVALID_NAME_DESC + GENDER_DESC_MALE + PHONE_DESC_BOB + EMAIL_DESC_BOB + + SUBJECT_DESC_CS2100 + SUBJECT_DESC_CS2102, Name.MESSAGE_CONSTRAINTS); + + // invalid gender + assertParseFailure(parser, NAME_DESC_BOB + INVALID_GENDER_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + + SUBJECT_DESC_CS2100 + SUBJECT_DESC_CS2102, Gender.MESSAGE_CONSTRAINTS); + + // invalid phone + assertParseFailure(parser, NAME_DESC_BOB + GENDER_DESC_MALE + INVALID_PHONE_DESC + EMAIL_DESC_BOB + + SUBJECT_DESC_CS2100 + SUBJECT_DESC_CS2102, Phone.MESSAGE_CONSTRAINTS); + + // invalid email + assertParseFailure(parser, NAME_DESC_BOB + GENDER_DESC_MALE + PHONE_DESC_BOB + INVALID_EMAIL_DESC + + SUBJECT_DESC_CS2100 + SUBJECT_DESC_CS2102, Email.MESSAGE_CONSTRAINTS); + + // invalid subject + assertParseFailure(parser, NAME_DESC_BOB + GENDER_DESC_MALE + PHONE_DESC_BOB + EMAIL_DESC_BOB + + INVALID_SUBJECT_DESC + VALID_SUB_CS2100, Subject.MESSAGE_CONSTRAINTS); + + // two invalid values, only first invalid value reported + assertParseFailure(parser, INVALID_NAME_DESC + GENDER_DESC_MALE + PHONE_DESC_BOB + + EMAIL_DESC_BOB + SUBJECT_DESC_CS2102, + Name.MESSAGE_CONSTRAINTS); + + // non-empty preamble + assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + GENDER_DESC_MALE + + PHONE_DESC_BOB + EMAIL_DESC_BOB + + SUBJECT_DESC_CS2100 + SUBJECT_DESC_CS2102, + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/parser/ArchiveCommandParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/ArchiveCommandParserTest.java new file mode 100644 index 00000000000..c728212fec8 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/parser/ArchiveCommandParserTest.java @@ -0,0 +1,32 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.mentorstack.testutil.TypicalIndexSets.INDEX_SET_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.logic.commands.ArchiveCommand; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the ArchiveCommand code. For example, inputs "1" and "1 abc" take the + * same path through the ArchiveCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class ArchiveCommandParserTest { + + private ArchiveCommandParser parser = new ArchiveCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteCommand() { + assertParseSuccess(parser, "1", new ArchiveCommand(INDEX_SET_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ArchiveCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java b/src/test/java/seedu/mentorstack/logic/parser/ArgumentTokenizerTest.java similarity index 99% rename from src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java rename to src/test/java/seedu/mentorstack/logic/parser/ArgumentTokenizerTest.java index c97308935f5..f5b6f7306b1 100644 --- a/src/test/java/seedu/address/logic/parser/ArgumentTokenizerTest.java +++ b/src/test/java/seedu/mentorstack/logic/parser/ArgumentTokenizerTest.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.mentorstack.logic.parser; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; diff --git a/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java b/src/test/java/seedu/mentorstack/logic/parser/CommandParserTestUtil.java similarity index 89% rename from src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java rename to src/test/java/seedu/mentorstack/logic/parser/CommandParserTestUtil.java index 9bf1ccf1cef..c68112cb86c 100644 --- a/src/test/java/seedu/address/logic/parser/CommandParserTestUtil.java +++ b/src/test/java/seedu/mentorstack/logic/parser/CommandParserTestUtil.java @@ -1,9 +1,9 @@ -package seedu.address.logic.parser; +package seedu.mentorstack.logic.parser; import static org.junit.jupiter.api.Assertions.assertEquals; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.mentorstack.logic.commands.Command; +import seedu.mentorstack.logic.parser.exceptions.ParseException; /** * Contains helper methods for testing command parsers. diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/DeleteCommandParserTest.java similarity index 65% rename from src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java rename to src/test/java/seedu/mentorstack/logic/parser/DeleteCommandParserTest.java index 6a40e14a649..d5ee17f6470 100644 --- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java +++ b/src/test/java/seedu/mentorstack/logic/parser/DeleteCommandParserTest.java @@ -1,13 +1,14 @@ -package seedu.address.logic.parser; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.mentorstack.testutil.TypicalIndexSets.INDEX_SET_FIRST_PERSON; import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.DeleteCommand; +import seedu.mentorstack.logic.commands.DeleteCommand; /** * As we are only doing white-box testing, our test cases do not cover path variations @@ -22,7 +23,7 @@ public class DeleteCommandParserTest { @Test public void parse_validArgs_returnsDeleteCommand() { - assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON)); + assertParseSuccess(parser, "1", new DeleteCommand(INDEX_SET_FIRST_PERSON)); } @Test diff --git a/src/test/java/seedu/mentorstack/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/EditCommandParserTest.java new file mode 100644 index 00000000000..423c13a52ac --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/parser/EditCommandParserTest.java @@ -0,0 +1,187 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.commands.CommandTestUtil.EMAIL_DESC_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.EMAIL_DESC_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.INVALID_EMAIL_DESC; +import static seedu.mentorstack.logic.commands.CommandTestUtil.INVALID_NAME_DESC; +import static seedu.mentorstack.logic.commands.CommandTestUtil.INVALID_PHONE_DESC; +import static seedu.mentorstack.logic.commands.CommandTestUtil.INVALID_SUBJECT_DESC; +import static seedu.mentorstack.logic.commands.CommandTestUtil.NAME_DESC_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.PHONE_DESC_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.PHONE_DESC_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.SUBJECT_DESC_CS2100; +import static seedu.mentorstack.logic.commands.CommandTestUtil.SUBJECT_DESC_CS2102; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2100; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2102; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_THIRD_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.Messages; +import seedu.mentorstack.logic.commands.EditCommand; +import seedu.mentorstack.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.mentorstack.model.person.Email; +import seedu.mentorstack.model.person.Name; +import seedu.mentorstack.model.person.Phone; +import seedu.mentorstack.model.person.Subject; +import seedu.mentorstack.testutil.EditPersonDescriptorBuilder; + +public class EditCommandParserTest { + + private static final String SUBJECT_EMPTY = " " + PREFIX_SUBJECT; + + private static final String MESSAGE_INVALID_FORMAT = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE); + + private EditCommandParser parser = new EditCommandParser(); + + @Test + public void parse_missingParts_failure() { + // no index specified + assertParseFailure(parser, VALID_NAME_AMY, MESSAGE_INVALID_FORMAT); + + // no field specified + assertParseFailure(parser, "1", EditCommand.MESSAGE_NOT_EDITED); + + // no index and no field specified + assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidPreamble_failure() { + // negative index + assertParseFailure(parser, "-5" + NAME_DESC_AMY, MESSAGE_INVALID_FORMAT); + + // zero index + assertParseFailure(parser, "0" + NAME_DESC_AMY, MESSAGE_INVALID_FORMAT); + + // invalid arguments being parsed as preamble + assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); + + // invalid prefix being parsed as preamble + assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidValue_failure() { + assertParseFailure(parser, "1" + INVALID_NAME_DESC, Name.MESSAGE_CONSTRAINTS); // invalid name + assertParseFailure(parser, "1" + INVALID_PHONE_DESC, Phone.MESSAGE_CONSTRAINTS); // invalid phone + assertParseFailure(parser, "1" + INVALID_EMAIL_DESC, Email.MESSAGE_CONSTRAINTS); // invalid email + assertParseFailure(parser, "1" + INVALID_SUBJECT_DESC, Subject.MESSAGE_CONSTRAINTS); // invalid subject + + // invalid phone followed by valid email + assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS); + + // while parsing {@code PREFIX_SUBJECT} alone will reset the subjects of the {@code Person} being edited, + // parsing it together with a valid subject results in error + assertParseFailure(parser, "1" + SUBJECT_DESC_CS2102 + SUBJECT_DESC_CS2100 + SUBJECT_EMPTY, + Subject.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, "1" + SUBJECT_DESC_CS2102 + SUBJECT_EMPTY + SUBJECT_DESC_CS2100, + Subject.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, "1" + SUBJECT_EMPTY + SUBJECT_DESC_CS2102 + SUBJECT_DESC_CS2100, + Subject.MESSAGE_CONSTRAINTS); + + // multiple invalid values, but only the first invalid value is captured + assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_PHONE_AMY, + Name.MESSAGE_CONSTRAINTS); + } + + @Test + public void parse_allFieldsSpecified_success() { + Index targetIndex = INDEX_SECOND_PERSON; + String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + SUBJECT_DESC_CS2100 + + EMAIL_DESC_AMY + NAME_DESC_AMY + SUBJECT_DESC_CS2102; + + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY) + .withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY) + .withSubjects(VALID_SUB_CS2102, VALID_SUB_CS2100).build(); + EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_someFieldsSpecified_success() { + Index targetIndex = INDEX_FIRST_PERSON; + String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + EMAIL_DESC_AMY; + + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_AMY).build(); + EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_oneFieldSpecified_success() { + // name + Index targetIndex = INDEX_THIRD_PERSON; + String userInput = targetIndex.getOneBased() + NAME_DESC_AMY; + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build(); + EditCommand expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // phone + userInput = targetIndex.getOneBased() + PHONE_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // email + userInput = targetIndex.getOneBased() + EMAIL_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // subjects + userInput = targetIndex.getOneBased() + SUBJECT_DESC_CS2100; + descriptor = new EditPersonDescriptorBuilder().withSubjects(VALID_SUB_CS2100).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + } + + @Test + public void parse_multipleRepeatedFields_failure() { + // More extensive testing of duplicate parameter detections is done in + // AddCommandParserTest#parse_repeatedNonSubjectValue_failure() + + // valid followed by invalid + Index targetIndex = INDEX_FIRST_PERSON; + String userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + PHONE_DESC_BOB; + + assertParseFailure(parser, userInput, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE)); + + // invalid followed by valid + userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + INVALID_PHONE_DESC; + + assertParseFailure(parser, userInput, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE)); + + // multiple valid fields repeated + userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + EMAIL_DESC_AMY + + SUBJECT_DESC_CS2102 + PHONE_DESC_AMY + EMAIL_DESC_AMY + SUBJECT_DESC_CS2102 + + PHONE_DESC_BOB + EMAIL_DESC_BOB + SUBJECT_DESC_CS2100; + + assertParseFailure(parser, userInput, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL)); + + // multiple invalid values + userInput = targetIndex.getOneBased() + INVALID_PHONE_DESC + INVALID_EMAIL_DESC + + INVALID_PHONE_DESC + INVALID_EMAIL_DESC; + + assertParseFailure(parser, userInput, + Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL)); + } +} diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/FindCommandParserTest.java similarity index 66% rename from src/test/java/seedu/address/logic/parser/FindCommandParserTest.java rename to src/test/java/seedu/mentorstack/logic/parser/FindCommandParserTest.java index d92e64d12f9..37770a3f2a6 100644 --- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java +++ b/src/test/java/seedu/mentorstack/logic/parser/FindCommandParserTest.java @@ -1,15 +1,15 @@ -package seedu.address.logic.parser; +package seedu.mentorstack.logic.parser; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; -import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseSuccess; import java.util.Arrays; import org.junit.jupiter.api.Test; -import seedu.address.logic.commands.FindCommand; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.mentorstack.logic.commands.FindCommand; +import seedu.mentorstack.model.person.NameContainsKeywordsPredicate; public class FindCommandParserTest { diff --git a/src/test/java/seedu/mentorstack/logic/parser/FinishCommandParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/FinishCommandParserTest.java new file mode 100644 index 00000000000..63e5895accd --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/parser/FinishCommandParserTest.java @@ -0,0 +1,89 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.commands.CommandTestUtil.INVALID_SUBJECT_DESC; +import static seedu.mentorstack.logic.commands.CommandTestUtil.SUBJECT_DESC_CS2100; +import static seedu.mentorstack.logic.commands.CommandTestUtil.SUBJECT_DESC_CS2102; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2100; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.FinishCommand; +import seedu.mentorstack.model.person.Subject; + +public class FinishCommandParserTest { + + private static final String SUBJECT_EMPTY = " " + PREFIX_SUBJECT; + + private static final String MESSAGE_INVALID_FORMAT = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FinishCommand.MESSAGE_USAGE); + + private FinishCommandParser parser = new FinishCommandParser(); + + @Test + public void parse_missingParts_failure() { + // no index specified + assertParseFailure(parser, VALID_SUB_CS2100, MESSAGE_INVALID_FORMAT); + + // no field specified + assertParseFailure(parser, "1", FinishCommand.MESSAGE_NO_SUBJECTS); + + // no index and no field specified + assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidPreamble_failure() { + // negative index + assertParseFailure(parser, "-5" + SUBJECT_DESC_CS2100, MESSAGE_INVALID_FORMAT); + + // zero index + assertParseFailure(parser, "0" + SUBJECT_DESC_CS2100, MESSAGE_INVALID_FORMAT); + + // invalid arguments being parsed as preamble + assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); + + // invalid prefix being parsed as preamble + assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidValue_failure() { + assertParseFailure(parser, "1" + INVALID_SUBJECT_DESC, Subject.MESSAGE_CONSTRAINTS); // invalid subject + + // while parsing {@code PREFIX_SUBJECT} alone will reset the subjects of the {@code Person} being finished, + // parsing it together with a valid subject results in error + assertParseFailure(parser, "1" + SUBJECT_DESC_CS2102 + SUBJECT_DESC_CS2100 + SUBJECT_EMPTY, + Subject.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, "1" + SUBJECT_DESC_CS2102 + SUBJECT_EMPTY + SUBJECT_DESC_CS2100, + Subject.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, "1" + SUBJECT_EMPTY + SUBJECT_DESC_CS2102 + SUBJECT_DESC_CS2100, + Subject.MESSAGE_CONSTRAINTS); + } + + @Test + public void parse_validInput_success() { + Index targetIndex = INDEX_FIRST_PERSON; + String input = "1" + SUBJECT_DESC_CS2100; + FinishCommand expectedCommand = new FinishCommand(targetIndex, Set.of(new Subject("CS2100"))); + + assertParseSuccess(parser, input, expectedCommand); + } + + @Test + public void parse_multipleValidSubjects_success() { + Index targetIndex = INDEX_FIRST_PERSON; + String input = "1" + SUBJECT_DESC_CS2100 + SUBJECT_DESC_CS2102; + Set subjects = Set.of(new Subject("CS2100"), new Subject("CS2102")); + FinishCommand expectedCommand = new FinishCommand(targetIndex, subjects); + + assertParseSuccess(parser, input, expectedCommand); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/parser/MarkCommandParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/MarkCommandParserTest.java new file mode 100644 index 00000000000..726632c0fe6 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/parser/MarkCommandParserTest.java @@ -0,0 +1,33 @@ + +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.mentorstack.testutil.TypicalIndexSets.INDEX_SET_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.logic.commands.MarkCommand; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the MarkCommand code. For example, inputs "1" and "1 abc" take the + * same path through the MarkCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class MarkCommandParserTest { + + private MarkCommandParser parser = new MarkCommandParser(); + + @Test + public void parse_validArgs_returnsMarkCommand() { + assertParseSuccess(parser, "1", new MarkCommand(INDEX_SET_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, MarkCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/parser/MentorstackParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/MentorstackParserTest.java new file mode 100644 index 00000000000..b8ea83fefed --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/parser/MentorstackParserTest.java @@ -0,0 +1,185 @@ +package seedu.mentorstack.logic.parser; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.mentorstack.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.TypicalIndexSets.INDEX_SET_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.logic.commands.AddCommand; +import seedu.mentorstack.logic.commands.ArchiveCommand; +import seedu.mentorstack.logic.commands.ClearCommand; +import seedu.mentorstack.logic.commands.DeleteCommand; +import seedu.mentorstack.logic.commands.EditCommand; +import seedu.mentorstack.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.mentorstack.logic.commands.ExitCommand; +import seedu.mentorstack.logic.commands.FindCommand; +import seedu.mentorstack.logic.commands.FinishCommand; +import seedu.mentorstack.logic.commands.HelpCommand; +import seedu.mentorstack.logic.commands.ListCommand; +import seedu.mentorstack.logic.commands.MarkCommand; +import seedu.mentorstack.logic.commands.ShowArchiveCommand; +import seedu.mentorstack.logic.commands.StatsCommand; +import seedu.mentorstack.logic.commands.UnarchiveCommand; +import seedu.mentorstack.logic.commands.UndoCommand; +import seedu.mentorstack.logic.commands.UnfinishCommand; +import seedu.mentorstack.logic.commands.UnmarkCommand; +import seedu.mentorstack.logic.commands.ViewCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.model.person.NameContainsKeywordsPredicate; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Subject; +import seedu.mentorstack.testutil.EditPersonDescriptorBuilder; +import seedu.mentorstack.testutil.PersonBuilder; +import seedu.mentorstack.testutil.PersonUtil; + +public class MentorstackParserTest { + + private final MentorstackParser parser = new MentorstackParser(); + + @Test + public void parseCommand_add() throws Exception { + Person person = new PersonBuilder().build(); + AddCommand command = (AddCommand) parser.parseCommand(PersonUtil.getAddCommand(person)); + assertEquals(new AddCommand(person), command); + } + + @Test + public void parseCommand_clear() throws Exception { + assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD) instanceof ClearCommand); + assertTrue(parser.parseCommand(ClearCommand.COMMAND_WORD + " 3") instanceof ClearCommand); + } + + @Test + public void parseCommand_delete() throws Exception { + DeleteCommand command = (DeleteCommand) parser.parseCommand( + DeleteCommand.COMMAND_WORD + " 1 "); + assertEquals(new DeleteCommand(INDEX_SET_FIRST_PERSON), command); + } + + @Test + public void parseCommand_edit() throws Exception { + Person person = new PersonBuilder().build(); + EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build(); + EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " " + + INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor)); + assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command); + } + + @Test + public void parseCommand_exit() throws Exception { + assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD) instanceof ExitCommand); + assertTrue(parser.parseCommand(ExitCommand.COMMAND_WORD + " 3") instanceof ExitCommand); + } + + @Test + public void parseCommand_find() throws Exception { + List keywords = Arrays.asList("foo", "bar", "baz"); + FindCommand command = (FindCommand) parser.parseCommand( + FindCommand.COMMAND_WORD + " " + keywords.stream().collect(Collectors.joining(" "))); + assertEquals(new FindCommand(new NameContainsKeywordsPredicate(keywords)), command); + } + + @Test + public void parseCommand_help() throws Exception { + assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD) instanceof HelpCommand); + assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD + " 3") instanceof HelpCommand); + } + + @Test + public void parseCommand_list() throws Exception { + assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD) instanceof ListCommand); + assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand); + } + + @Test + public void parseCommand_view() throws Exception { + assertTrue(parser.parseCommand(ViewCommand.COMMAND_WORD) instanceof ViewCommand); + assertTrue(parser.parseCommand(ViewCommand.COMMAND_WORD + " 3") instanceof ViewCommand); + } + + @Test + public void parseCommand_undo() throws Exception { + assertTrue(parser.parseCommand(UndoCommand.COMMAND_WORD) instanceof UndoCommand); + assertTrue(parser.parseCommand(UndoCommand.COMMAND_WORD + " 3") instanceof UndoCommand); + } + + @Test + public void parseCommand_stats() throws Exception { + assertTrue(parser.parseCommand(StatsCommand.COMMAND_WORD) instanceof StatsCommand); + assertTrue(parser.parseCommand(StatsCommand.COMMAND_WORD + " s/CS2103") instanceof StatsCommand); + } + + @Test + public void parseCommand_finish() throws Exception { + Set subjects = new HashSet<>(); + subjects.add(new Subject("CS2103")); + FinishCommand command = (FinishCommand) parser.parseCommand( + FinishCommand.COMMAND_WORD + " 1 " + "s/CS2103"); + assertEquals(new FinishCommand(INDEX_FIRST_PERSON, subjects), command); + } + + @Test + public void parseCommand_unfinish() throws Exception { + Set subjects = new HashSet<>(); + subjects.add(new Subject("CS2103")); + UnfinishCommand command = (UnfinishCommand) parser.parseCommand( + UnfinishCommand.COMMAND_WORD + " 1 " + "s/CS2103"); + assertEquals(new UnfinishCommand(INDEX_FIRST_PERSON, subjects), command); + } + + @Test + public void parseCommand_mark() throws Exception { + MarkCommand command = (MarkCommand) parser.parseCommand( + MarkCommand.COMMAND_WORD + " 1 "); + assertEquals(new MarkCommand(INDEX_SET_FIRST_PERSON), command); + } + + @Test + public void parseCommand_unmark() throws Exception { + UnmarkCommand command = (UnmarkCommand) parser.parseCommand( + UnmarkCommand.COMMAND_WORD + " 1 "); + assertEquals(new UnmarkCommand(INDEX_SET_FIRST_PERSON), command); + } + + @Test + public void parseCommand_archive() throws Exception { + ArchiveCommand command = (ArchiveCommand) parser.parseCommand( + ArchiveCommand.COMMAND_WORD + " 1 "); + assertEquals(new ArchiveCommand(INDEX_SET_FIRST_PERSON), command); + } + + @Test + public void parseCommand_unarchive() throws Exception { + UnarchiveCommand command = (UnarchiveCommand) parser.parseCommand( + UnarchiveCommand.COMMAND_WORD + " 1 "); + assertEquals(new UnarchiveCommand(INDEX_SET_FIRST_PERSON), command); + } + + @Test + public void parseCommand_showArchive() throws Exception { + assertTrue(parser.parseCommand(ShowArchiveCommand.COMMAND_WORD) instanceof ShowArchiveCommand); + assertTrue(parser.parseCommand(ShowArchiveCommand.COMMAND_WORD + " 3") instanceof ShowArchiveCommand); + } + + @Test + public void parseCommand_unrecognisedInput_throwsParseException() { + assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), () + -> parser.parseCommand("")); + } + + @Test + public void parseCommand_unknownCommand_throwsParseException() { + assertThrows(ParseException.class, MESSAGE_UNKNOWN_COMMAND, () -> parser.parseCommand("unknownCommand")); + } +} diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/mentorstack/logic/parser/ParserUtilTest.java similarity index 59% rename from src/test/java/seedu/address/logic/parser/ParserUtilTest.java rename to src/test/java/seedu/mentorstack/logic/parser/ParserUtilTest.java index 4256788b1a7..988820f6a4a 100644 --- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java +++ b/src/test/java/seedu/mentorstack/logic/parser/ParserUtilTest.java @@ -1,10 +1,10 @@ -package seedu.address.logic.parser; +package seedu.mentorstack.logic.parser; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.mentorstack.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX; +import static seedu.mentorstack.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; import java.util.Arrays; import java.util.Collections; @@ -13,26 +13,23 @@ import org.junit.jupiter.api.Test; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.model.person.Email; +import seedu.mentorstack.model.person.Name; +import seedu.mentorstack.model.person.Phone; +import seedu.mentorstack.model.person.Subject; public class ParserUtilTest { private static final String INVALID_NAME = "R@chel"; private static final String INVALID_PHONE = "+651234"; - private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; - private static final String INVALID_TAG = "#friend"; + private static final String INVALID_SUBJECT = "#CS2103"; private static final String VALID_NAME = "Rachel Walker"; private static final String VALID_PHONE = "123456"; - private static final String VALID_ADDRESS = "123 Main Street #0505"; private static final String VALID_EMAIL = "rachel@example.com"; - private static final String VALID_TAG_1 = "friend"; - private static final String VALID_TAG_2 = "neighbour"; + private static final String VALID_SUBJECT_1 = "CS2103"; + private static final String VALID_SUBJECT_2 = "CS2100"; private static final String WHITESPACE = " \t\r\n"; @@ -102,29 +99,6 @@ public void parsePhone_validValueWithWhitespace_returnsTrimmedPhone() throws Exc assertEquals(expectedPhone, ParserUtil.parsePhone(phoneWithWhitespace)); } - @Test - public void parseAddress_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> ParserUtil.parseAddress((String) null)); - } - - @Test - public void parseAddress_invalidValue_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseAddress(INVALID_ADDRESS)); - } - - @Test - public void parseAddress_validValueWithoutWhitespace_returnsAddress() throws Exception { - Address expectedAddress = new Address(VALID_ADDRESS); - assertEquals(expectedAddress, ParserUtil.parseAddress(VALID_ADDRESS)); - } - - @Test - public void parseAddress_validValueWithWhitespace_returnsTrimmedAddress() throws Exception { - String addressWithWhitespace = WHITESPACE + VALID_ADDRESS + WHITESPACE; - Address expectedAddress = new Address(VALID_ADDRESS); - assertEquals(expectedAddress, ParserUtil.parseAddress(addressWithWhitespace)); - } - @Test public void parseEmail_null_throwsNullPointerException() { assertThrows(NullPointerException.class, () -> ParserUtil.parseEmail((String) null)); @@ -149,48 +123,50 @@ public void parseEmail_validValueWithWhitespace_returnsTrimmedEmail() throws Exc } @Test - public void parseTag_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> ParserUtil.parseTag(null)); + public void parseSubject_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseSubjects((String) null)); } @Test - public void parseTag_invalidValue_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseTag(INVALID_TAG)); + public void parseSubject_invalidValue_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseSubjects(INVALID_SUBJECT)); } @Test - public void parseTag_validValueWithoutWhitespace_returnsTag() throws Exception { - Tag expectedTag = new Tag(VALID_TAG_1); - assertEquals(expectedTag, ParserUtil.parseTag(VALID_TAG_1)); + public void parseSubject_validValueWithoutWhitespace_returnsSubject() throws Exception { + Subject expectedSubject = new Subject(VALID_SUBJECT_1); + assertEquals(expectedSubject, ParserUtil.parseSubjects(VALID_SUBJECT_1)); } @Test - public void parseTag_validValueWithWhitespace_returnsTrimmedTag() throws Exception { - String tagWithWhitespace = WHITESPACE + VALID_TAG_1 + WHITESPACE; - Tag expectedTag = new Tag(VALID_TAG_1); - assertEquals(expectedTag, ParserUtil.parseTag(tagWithWhitespace)); + public void parseSubject_validValueWithWhitespace_returnsTrimmedSubject() throws Exception { + String subjectWithWhitespace = WHITESPACE + VALID_SUBJECT_1 + WHITESPACE; + Subject expectedSubject = new Subject(VALID_SUBJECT_1); + assertEquals(expectedSubject, ParserUtil.parseSubjects(subjectWithWhitespace)); } @Test - public void parseTags_null_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> ParserUtil.parseTags(null)); + public void parseSubjects_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> ParserUtil.parseSubjects((String) null)); } @Test - public void parseTags_collectionWithInvalidTags_throwsParseException() { - assertThrows(ParseException.class, () -> ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, INVALID_TAG))); + public void parseSubjects_collectionWithInvalidSubjects_throwsParseException() { + assertThrows(ParseException.class, () -> ParserUtil.parseSubjects(Arrays.asList(VALID_SUBJECT_1, + INVALID_SUBJECT))); } @Test - public void parseTags_emptyCollection_returnsEmptySet() throws Exception { - assertTrue(ParserUtil.parseTags(Collections.emptyList()).isEmpty()); + public void parseSubjects_emptyCollection_returnsEmptySet() throws Exception { + assertTrue(ParserUtil.parseSubjects(Collections.emptyList()).isEmpty()); } @Test - public void parseTags_collectionWithValidTags_returnsTagSet() throws Exception { - Set actualTagSet = ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, VALID_TAG_2)); - Set expectedTagSet = new HashSet(Arrays.asList(new Tag(VALID_TAG_1), new Tag(VALID_TAG_2))); + public void parseSubjects_collectionWithValidSubjects_returnsSubjectSet() throws Exception { + Set actualSubjectSet = ParserUtil.parseSubjects(Arrays.asList(VALID_SUBJECT_1, VALID_SUBJECT_2)); + Set expectedSubjectSet = new HashSet(Arrays.asList(new Subject(VALID_SUBJECT_1), + new Subject(VALID_SUBJECT_2))); - assertEquals(expectedTagSet, actualTagSet); + assertEquals(expectedSubjectSet, actualSubjectSet); } } diff --git a/src/test/java/seedu/mentorstack/logic/parser/StatsCommandParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/StatsCommandParserTest.java new file mode 100644 index 00000000000..b750a7a92f0 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/parser/StatsCommandParserTest.java @@ -0,0 +1,59 @@ +package seedu.mentorstack.logic.parser; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.commands.CommandTestUtil.INVALID_SUBJECT_DESC; +import static seedu.mentorstack.logic.commands.CommandTestUtil.SUBJECT_DESC_CS2100; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.logic.commands.StatsCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.model.person.Subject; +import seedu.mentorstack.testutil.Assert; + +class StatsCommandParserTest { + + private StatsCommandParser parser = new StatsCommandParser(); + + @Test + public void parse_emptyArgs_returnsStatsCommandWithoutSubject() throws ParseException { + // Test for empty input + StatsCommand statsCommand = parser.parse(""); + assertNotNull(statsCommand); + } + + @Test + public void parse_validSubject_returnsStatsCommandWithSubject() throws ParseException { + // Test for valid subject input with the prefix + Subject subject = new Subject("CS2100"); + StatsCommand statsCommand = parser.parse(SUBJECT_DESC_CS2100); + assertEquals(new StatsCommand(subject), statsCommand); // Should match the subject + } + + @Test + public void parse_invalidSubject_throwsParseException() { + // Test for invalid subject input + assertThrows(ParseException.class, () -> { + parser.parse(" " + PREFIX_SUBJECT + "INVALID_SUBJECT "); + }); + } + + @Test + public void parse_mixedCaseSubject_returnsStatsCommandWithSubject() throws ParseException { + // Test for valid mixed-case subject input with the prefix + Subject subject = new Subject("Cs1010s"); // Assuming Subject is case-insensitive + StatsCommand statsCommand = parser.parse(" " + PREFIX_SUBJECT + "Cs1010s "); + assertEquals(new StatsCommand(subject), statsCommand); // Should match the subject + } + + @Test + public void parseCommand_unrecognisedInput_throwsParseException() { + Assert.assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, + StatsCommand.MESSAGE_USAGE), () + -> parser.parse(INVALID_SUBJECT_DESC)); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/parser/UnarchiveCommandParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/UnarchiveCommandParserTest.java new file mode 100644 index 00000000000..b018cb7be49 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/parser/UnarchiveCommandParserTest.java @@ -0,0 +1,32 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.mentorstack.testutil.TypicalIndexSets.INDEX_SET_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.logic.commands.UnarchiveCommand; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the UnarchiveCommand code. For example, inputs "1" and "1 abc" take the + * same path through the UnarchiveCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class UnarchiveCommandParserTest { + + private UnarchiveCommandParser parser = new UnarchiveCommandParser(); + + @Test + public void parse_validArgs_returnsDeleteCommand() { + assertParseSuccess(parser, "1", new UnarchiveCommand(INDEX_SET_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnarchiveCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/parser/UnfinishCommandParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/UnfinishCommandParserTest.java new file mode 100644 index 00000000000..81c2c1fd00a --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/parser/UnfinishCommandParserTest.java @@ -0,0 +1,89 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.commands.CommandTestUtil.INVALID_SUBJECT_DESC; +import static seedu.mentorstack.logic.commands.CommandTestUtil.SUBJECT_DESC_CS2100; +import static seedu.mentorstack.logic.commands.CommandTestUtil.SUBJECT_DESC_CS2102; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2100; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; + +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.logic.commands.UnfinishCommand; +import seedu.mentorstack.model.person.Subject; + +public class UnfinishCommandParserTest { + + private static final String SUBJECT_EMPTY = " " + PREFIX_SUBJECT; + + private static final String MESSAGE_INVALID_FORMAT = + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnfinishCommand.MESSAGE_USAGE); + + private UnfinishCommandParser parser = new UnfinishCommandParser(); + + @Test + public void parse_missingParts_failure() { + // no index specified + assertParseFailure(parser, VALID_SUB_CS2100, MESSAGE_INVALID_FORMAT); + + // no field specified + assertParseFailure(parser, "1", UnfinishCommand.MESSAGE_NO_SUBJECTS); + + // no index and no field specified + assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidPreamble_failure() { + // negative index + assertParseFailure(parser, "-5" + SUBJECT_DESC_CS2100, MESSAGE_INVALID_FORMAT); + + // zero index + assertParseFailure(parser, "0" + SUBJECT_DESC_CS2100, MESSAGE_INVALID_FORMAT); + + // invalid arguments being parsed as preamble + assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT); + + // invalid prefix being parsed as preamble + assertParseFailure(parser, "1 i/ string", MESSAGE_INVALID_FORMAT); + } + + @Test + public void parse_invalidValue_failure() { + assertParseFailure(parser, "1" + INVALID_SUBJECT_DESC, Subject.MESSAGE_CONSTRAINTS); // invalid subject + + // while parsing {@code PREFIX_SUBJECT} alone will reset the subjects of the {@code Person} being unfinished, + // parsing it together with a valid subject results in error + assertParseFailure(parser, "1" + SUBJECT_DESC_CS2102 + SUBJECT_DESC_CS2100 + SUBJECT_EMPTY, + Subject.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, "1" + SUBJECT_DESC_CS2102 + SUBJECT_EMPTY + SUBJECT_DESC_CS2100, + Subject.MESSAGE_CONSTRAINTS); + assertParseFailure(parser, "1" + SUBJECT_EMPTY + SUBJECT_DESC_CS2102 + SUBJECT_DESC_CS2100, + Subject.MESSAGE_CONSTRAINTS); + } + + @Test + public void parse_validInput_success() { + Index targetIndex = INDEX_FIRST_PERSON; + String input = "1" + SUBJECT_DESC_CS2100; + UnfinishCommand expectedCommand = new UnfinishCommand(targetIndex, Set.of(new Subject("CS2100"))); + + assertParseSuccess(parser, input, expectedCommand); + } + + @Test + public void parse_multipleValidSubjects_success() { + Index targetIndex = INDEX_FIRST_PERSON; + String input = "1" + SUBJECT_DESC_CS2100 + SUBJECT_DESC_CS2102; + Set subjects = Set.of(new Subject("CS2100"), new Subject("CS2102")); + UnfinishCommand expectedCommand = new UnfinishCommand(targetIndex, subjects); + + assertParseSuccess(parser, input, expectedCommand); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/parser/UnmarkCommandParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/UnmarkCommandParserTest.java new file mode 100644 index 00000000000..62f62b95155 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/parser/UnmarkCommandParserTest.java @@ -0,0 +1,33 @@ + +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.mentorstack.testutil.TypicalIndexSets.INDEX_SET_FIRST_PERSON; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.logic.commands.UnmarkCommand; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the UnmarkCommand code. For example, inputs "1" and "1 abc" take the + * same path through the UnmarkCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class UnmarkCommandParserTest { + + private UnmarkCommandParser parser = new UnmarkCommandParser(); + + @Test + public void parse_validArgs_returnsUnmarkCommand() { + assertParseSuccess(parser, "1", new UnmarkCommand(INDEX_SET_FIRST_PERSON)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, UnmarkCommand.MESSAGE_USAGE)); + } +} diff --git a/src/test/java/seedu/mentorstack/logic/parser/ViewCommandParserTest.java b/src/test/java/seedu/mentorstack/logic/parser/ViewCommandParserTest.java new file mode 100644 index 00000000000..5da04d45fc0 --- /dev/null +++ b/src/test/java/seedu/mentorstack/logic/parser/ViewCommandParserTest.java @@ -0,0 +1,42 @@ +package seedu.mentorstack.logic.parser; + +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_FILTER_TYPE; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_FILTER_VALUE; +import static seedu.mentorstack.logic.parser.CommandParserTestUtil.assertParseFailure; + +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.logic.commands.ViewCommand; +import seedu.mentorstack.logic.parser.exceptions.ParseException; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.testutil.PersonBuilder; + +/** + * Contains unit tests for {@code ViewCommandParser}. + */ +public class ViewCommandParserTest { + + private final ViewCommandParser parser = new ViewCommandParser(); + + @Test + public void parse_validFilterType_returnsCorrectPredicate() throws ParseException { + String userInput = " f/n v/Alice"; + ViewCommand command = parser.parse(userInput); + + Predicate predicate = command.predicate; + + Person alice = new PersonBuilder().withName("Alice").build(); + Person bob = new PersonBuilder().withName("Bob").build(); + + assert predicate.test(alice) : "Alice should match the filter"; + assert !predicate.test(bob) : "Bob should NOT match the filter"; + } + + @Test + public void parse_invalidFilter_throwsParseException() { + String userInput = " " + PREFIX_FILTER_TYPE + " invalid " + PREFIX_FILTER_VALUE + " XYZ"; + assertParseFailure(parser, userInput, "Invalid filter type or value."); + } +} diff --git a/src/test/java/seedu/mentorstack/model/MentorstackTest.java b/src/test/java/seedu/mentorstack/model/MentorstackTest.java new file mode 100644 index 00000000000..6bc66d08251 --- /dev/null +++ b/src/test/java/seedu/mentorstack/model/MentorstackTest.java @@ -0,0 +1,107 @@ +package seedu.mentorstack.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2102; +import static seedu.mentorstack.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.TypicalPersons.ALICE; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.exceptions.DuplicatePersonException; +import seedu.mentorstack.testutil.PersonBuilder; + +public class MentorstackTest { + + private final Mentorstack mentorstack = new Mentorstack(); + + @Test + public void constructor() { + assertEquals(Collections.emptyList(), mentorstack.getPersonList()); + } + + @Test + public void resetData_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> mentorstack.resetData(null)); + } + + @Test + public void resetData_withValidReadOnlyMentorstack_replacesData() { + Mentorstack newData = getTypicalMentorstack(); + mentorstack.resetData(newData); + assertEquals(newData, mentorstack); + } + + @Test + public void resetData_withDuplicatePersons_throwsDuplicatePersonException() { + // Two persons with the same identity fields + Person editedAlice = new PersonBuilder(ALICE).withSubjects(VALID_SUB_CS2102) + .build(); + List newPersons = Arrays.asList(ALICE, editedAlice); + MentorstackStub newData = new MentorstackStub(newPersons); + + assertThrows(DuplicatePersonException.class, () -> mentorstack.resetData(newData)); + } + + @Test + public void hasPerson_nullPerson_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> mentorstack.hasPerson(null)); + } + + @Test + public void hasPerson_personNotInMentorstack_returnsFalse() { + assertFalse(mentorstack.hasPerson(ALICE)); + } + + @Test + public void hasPerson_personInMentorstack_returnsTrue() { + mentorstack.addPerson(ALICE); + assertTrue(mentorstack.hasPerson(ALICE)); + } + + @Test + public void hasPerson_personWithSameIdentityFieldsInMentorstack_returnsTrue() { + mentorstack.addPerson(ALICE); + Person editedAlice = new PersonBuilder(ALICE).withSubjects(VALID_SUB_CS2102) + .build(); + assertTrue(mentorstack.hasPerson(editedAlice)); + } + + @Test + public void getPersonList_modifyList_throwsUnsupportedOperationException() { + assertThrows(UnsupportedOperationException.class, () -> mentorstack.getPersonList().remove(0)); + } + + @Test + public void toStringMethod() { + String expected = Mentorstack.class.getCanonicalName() + "{persons=" + mentorstack.getPersonList() + "}"; + assertEquals(expected, mentorstack.toString()); + } + + /** + * A stub ReadOnlyMentorstack whose persons list can violate interface constraints. + */ + private static class MentorstackStub implements ReadOnlyMentorstack { + private final ObservableList persons = FXCollections.observableArrayList(); + + MentorstackStub(Collection persons) { + this.persons.setAll(persons); + } + + @Override + public ObservableList getPersonList() { + return persons; + } + } + +} diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/mentorstack/model/ModelManagerTest.java similarity index 80% rename from src/test/java/seedu/address/model/ModelManagerTest.java rename to src/test/java/seedu/mentorstack/model/ModelManagerTest.java index 2cf1418d116..a8cc465adf3 100644 --- a/src/test/java/seedu/address/model/ModelManagerTest.java +++ b/src/test/java/seedu/mentorstack/model/ModelManagerTest.java @@ -1,12 +1,12 @@ -package seedu.address.model; +package seedu.mentorstack.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.mentorstack.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.mentorstack.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.TypicalPersons.ALICE; +import static seedu.mentorstack.testutil.TypicalPersons.BENSON; import java.nio.file.Path; import java.nio.file.Paths; @@ -14,9 +14,9 @@ import org.junit.jupiter.api.Test; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.person.NameContainsKeywordsPredicate; -import seedu.address.testutil.AddressBookBuilder; +import seedu.mentorstack.commons.core.GuiSettings; +import seedu.mentorstack.model.person.NameContainsKeywordsPredicate; +import seedu.mentorstack.testutil.AddressBookBuilder; public class ModelManagerTest { @@ -26,7 +26,7 @@ public class ModelManagerTest { public void constructor() { assertEquals(new UserPrefs(), modelManager.getUserPrefs()); assertEquals(new GuiSettings(), modelManager.getGuiSettings()); - assertEquals(new AddressBook(), new AddressBook(modelManager.getAddressBook())); + assertEquals(new Mentorstack(), new Mentorstack(modelManager.getMentorstack())); } @Test @@ -37,14 +37,14 @@ public void setUserPrefs_nullUserPrefs_throwsNullPointerException() { @Test public void setUserPrefs_validUserPrefs_copiesUserPrefs() { UserPrefs userPrefs = new UserPrefs(); - userPrefs.setAddressBookFilePath(Paths.get("address/book/file/path")); + userPrefs.setMentorstackFilePath(Paths.get("address/book/file/path")); userPrefs.setGuiSettings(new GuiSettings(1, 2, 3, 4)); modelManager.setUserPrefs(userPrefs); assertEquals(userPrefs, modelManager.getUserPrefs()); // Modifying userPrefs should not modify modelManager's userPrefs UserPrefs oldUserPrefs = new UserPrefs(userPrefs); - userPrefs.setAddressBookFilePath(Paths.get("new/address/book/file/path")); + userPrefs.setMentorstackFilePath(Paths.get("new/address/book/file/path")); assertEquals(oldUserPrefs, modelManager.getUserPrefs()); } @@ -62,14 +62,14 @@ public void setGuiSettings_validGuiSettings_setsGuiSettings() { @Test public void setAddressBookFilePath_nullPath_throwsNullPointerException() { - assertThrows(NullPointerException.class, () -> modelManager.setAddressBookFilePath(null)); + assertThrows(NullPointerException.class, () -> modelManager.setMentorstackFilePath(null)); } @Test public void setAddressBookFilePath_validPath_setsAddressBookFilePath() { Path path = Paths.get("address/book/file/path"); - modelManager.setAddressBookFilePath(path); - assertEquals(path, modelManager.getAddressBookFilePath()); + modelManager.setMentorstackFilePath(path); + assertEquals(path, modelManager.getMentorstackFilePath()); } @Test @@ -95,8 +95,8 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException @Test public void equals() { - AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); - AddressBook differentAddressBook = new AddressBook(); + Mentorstack addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build(); + Mentorstack differentAddressBook = new Mentorstack(); UserPrefs userPrefs = new UserPrefs(); // same values -> returns true @@ -126,7 +126,7 @@ public void equals() { // different userPrefs -> returns false UserPrefs differentUserPrefs = new UserPrefs(); - differentUserPrefs.setAddressBookFilePath(Paths.get("differentFilePath")); + differentUserPrefs.setMentorstackFilePath(Paths.get("differentFilePath")); assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs))); } } diff --git a/src/test/java/seedu/address/model/UserPrefsTest.java b/src/test/java/seedu/mentorstack/model/UserPrefsTest.java similarity index 79% rename from src/test/java/seedu/address/model/UserPrefsTest.java rename to src/test/java/seedu/mentorstack/model/UserPrefsTest.java index b1307a70d52..e03fc90a9fe 100644 --- a/src/test/java/seedu/address/model/UserPrefsTest.java +++ b/src/test/java/seedu/mentorstack/model/UserPrefsTest.java @@ -1,6 +1,6 @@ -package seedu.address.model; +package seedu.mentorstack.model; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; @@ -15,7 +15,7 @@ public void setGuiSettings_nullGuiSettings_throwsNullPointerException() { @Test public void setAddressBookFilePath_nullPath_throwsNullPointerException() { UserPrefs userPrefs = new UserPrefs(); - assertThrows(NullPointerException.class, () -> userPrefs.setAddressBookFilePath(null)); + assertThrows(NullPointerException.class, () -> userPrefs.setMentorstackFilePath(null)); } } diff --git a/src/test/java/seedu/address/model/person/EmailTest.java b/src/test/java/seedu/mentorstack/model/person/EmailTest.java similarity index 97% rename from src/test/java/seedu/address/model/person/EmailTest.java rename to src/test/java/seedu/mentorstack/model/person/EmailTest.java index f08cdff0a64..36d2243e236 100644 --- a/src/test/java/seedu/address/model/person/EmailTest.java +++ b/src/test/java/seedu/mentorstack/model/person/EmailTest.java @@ -1,8 +1,8 @@ -package seedu.address.model.person; +package seedu.mentorstack.model.person; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/seedu/mentorstack/model/person/GenderTest.java b/src/test/java/seedu/mentorstack/model/person/GenderTest.java new file mode 100644 index 00000000000..13506d7132b --- /dev/null +++ b/src/test/java/seedu/mentorstack/model/person/GenderTest.java @@ -0,0 +1,70 @@ +package seedu.mentorstack.model.person; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.mentorstack.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.logic.parser.exceptions.ParseException; + +class GenderTest { + + @Test + public void constructor_validGender_success() throws ParseException { + // Valid cases + assertEquals(new Gender("M").toString(), "M"); + assertEquals(new Gender("F").toString(), "F"); + assertEquals(new Gender("m").toString(), "M"); + assertEquals(new Gender("f").toString(), "F"); + } + + @Test + public void constructor_invalidGender_throwsParseException() { + // Invalid cases + assertThrows(IllegalArgumentException.class, () -> new Gender("X")); + assertThrows(IllegalArgumentException.class, () -> new Gender("")); + } + + @Test + public void isValidGender() { + // Valid genders + assertTrue(Gender.isValidGender("M")); + assertTrue(Gender.isValidGender("F")); + assertTrue(Gender.isValidGender("m")); + assertTrue(Gender.isValidGender("f")); + + // Invalid genders + assertFalse(Gender.isValidGender("X")); + assertFalse(Gender.isValidGender("Male")); + assertFalse(Gender.isValidGender("MF")); + assertFalse(Gender.isValidGender("")); + assertFalse(Gender.isValidGender(" ")); + } + + @Test + public void equals() throws ParseException { + Gender genderM1 = new Gender("M"); + Gender genderM2 = new Gender("m"); + Gender genderF = new Gender("F"); + + // Same object -> returns true + assertTrue(genderM1.equals(genderM1)); + + // Same value -> returns true + assertTrue(genderM1.equals(genderM2)); + + // Different values -> returns false + assertFalse(genderM1.equals(genderF)); + + // Different object type -> returns false + assertFalse(genderM1.equals("M")); + } + + @Test + public void toStringMethod() throws ParseException { + assertEquals(new Gender("M").toString(), "M"); + assertEquals(new Gender("f").toString(), "F"); + } +} diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/mentorstack/model/person/NameContainsKeywordsPredicateTest.java similarity index 95% rename from src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java rename to src/test/java/seedu/mentorstack/model/person/NameContainsKeywordsPredicateTest.java index 6b3fd90ade7..4bbcb6e41ae 100644 --- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java +++ b/src/test/java/seedu/mentorstack/model/person/NameContainsKeywordsPredicateTest.java @@ -1,4 +1,4 @@ -package seedu.address.model.person; +package seedu.mentorstack.model.person; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test; -import seedu.address.testutil.PersonBuilder; +import seedu.mentorstack.testutil.PersonBuilder; public class NameContainsKeywordsPredicateTest { @@ -71,7 +71,7 @@ public void test_nameDoesNotContainKeywords_returnsFalse() { // Keywords match phone, email and address, but does not match name predicate = new NameContainsKeywordsPredicate(Arrays.asList("12345", "alice@email.com", "Main", "Street")); assertFalse(predicate.test(new PersonBuilder().withName("Alice").withPhone("12345") - .withEmail("alice@email.com").withAddress("Main Street").build())); + .withEmail("alice@email.com").build())); } @Test diff --git a/src/test/java/seedu/address/model/person/NameTest.java b/src/test/java/seedu/mentorstack/model/person/NameTest.java similarity index 95% rename from src/test/java/seedu/address/model/person/NameTest.java rename to src/test/java/seedu/mentorstack/model/person/NameTest.java index 94e3dd726bd..75c62e77f37 100644 --- a/src/test/java/seedu/address/model/person/NameTest.java +++ b/src/test/java/seedu/mentorstack/model/person/NameTest.java @@ -1,8 +1,8 @@ -package seedu.address.model.person; +package seedu.mentorstack.model.person; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/mentorstack/model/person/PersonTest.java similarity index 69% rename from src/test/java/seedu/address/model/person/PersonTest.java rename to src/test/java/seedu/mentorstack/model/person/PersonTest.java index 31a10d156c9..f08972e2ee9 100644 --- a/src/test/java/seedu/address/model/person/PersonTest.java +++ b/src/test/java/seedu/mentorstack/model/person/PersonTest.java @@ -1,27 +1,26 @@ -package seedu.address.model.person; +package seedu.mentorstack.model.person; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2102; +import static seedu.mentorstack.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.TypicalPersons.ALICE; +import static seedu.mentorstack.testutil.TypicalPersons.BOB; import org.junit.jupiter.api.Test; -import seedu.address.testutil.PersonBuilder; +import seedu.mentorstack.testutil.PersonBuilder; public class PersonTest { @Test public void asObservableList_modifyList_throwsUnsupportedOperationException() { Person person = new PersonBuilder().build(); - assertThrows(UnsupportedOperationException.class, () -> person.getTags().remove(0)); + assertThrows(UnsupportedOperationException.class, () -> person.getSubjects().remove(0)); } @Test @@ -34,7 +33,7 @@ public void isSamePerson() { // same name, all other attributes different -> returns true Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB) - .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build(); + .withSubjects(VALID_SUB_CS2102).build(); assertTrue(ALICE.isSamePerson(editedAlice)); // different name, all other attributes same -> returns false @@ -81,19 +80,21 @@ public void equals() { editedAlice = new PersonBuilder(ALICE).withEmail(VALID_EMAIL_BOB).build(); assertFalse(ALICE.equals(editedAlice)); - // different address -> returns false - editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).build(); - assertFalse(ALICE.equals(editedAlice)); - - // different tags -> returns false - editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build(); + // different subjects -> returns false + editedAlice = new PersonBuilder(ALICE).withSubjects(VALID_SUB_CS2102).build(); assertFalse(ALICE.equals(editedAlice)); } @Test public void toStringMethod() { - String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone() - + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags() + "}"; + String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + + ", gender=" + ALICE.getGender() + + ", phone=" + ALICE.getPhone() + + ", email=" + ALICE.getEmail() + + ", subject=" + ALICE.getSubjects() + + ", finished subject=" + ALICE.getFinishedSubjects() + + ", archive status=" + ALICE.getIsArchived() + + ", marked=" + ALICE.getIsMarked() + "}"; assertEquals(expected, ALICE.toString()); } } diff --git a/src/test/java/seedu/address/model/person/PhoneTest.java b/src/test/java/seedu/mentorstack/model/person/PhoneTest.java similarity index 94% rename from src/test/java/seedu/address/model/person/PhoneTest.java rename to src/test/java/seedu/mentorstack/model/person/PhoneTest.java index deaaa5ba190..c8ced077755 100644 --- a/src/test/java/seedu/address/model/person/PhoneTest.java +++ b/src/test/java/seedu/mentorstack/model/person/PhoneTest.java @@ -1,8 +1,8 @@ -package seedu.address.model.person; +package seedu.mentorstack.model.person; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.Assert.assertThrows; import org.junit.jupiter.api.Test; diff --git a/src/test/java/seedu/mentorstack/model/person/SubjectTest.java b/src/test/java/seedu/mentorstack/model/person/SubjectTest.java new file mode 100644 index 00000000000..49afcbbe7b5 --- /dev/null +++ b/src/test/java/seedu/mentorstack/model/person/SubjectTest.java @@ -0,0 +1,26 @@ +package seedu.mentorstack.model.person; + +import static seedu.mentorstack.testutil.Assert.assertThrows; + +import org.junit.jupiter.api.Test; + +public class SubjectTest { + + @Test + public void constructor_null_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> new Subject(null)); + } + + @Test + public void constructor_invalidSubjectName_throwsIllegalArgumentException() { + String invalidSubjectName = ""; + assertThrows(IllegalArgumentException.class, () -> new Subject(invalidSubjectName)); + } + + @Test + public void isValidSubjectName() { + // null subject name + assertThrows(NullPointerException.class, () -> Subject.isValidSubjectName(null)); + } + +} diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/mentorstack/model/person/UniquePersonListTest.java similarity index 88% rename from src/test/java/seedu/address/model/person/UniquePersonListTest.java rename to src/test/java/seedu/mentorstack/model/person/UniquePersonListTest.java index 17ae501df08..910df5af1f3 100644 --- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java +++ b/src/test/java/seedu/mentorstack/model/person/UniquePersonListTest.java @@ -1,13 +1,12 @@ -package seedu.address.model.person; +package seedu.mentorstack.model.person; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB; -import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.ALICE; -import static seedu.address.testutil.TypicalPersons.BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2102; +import static seedu.mentorstack.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.TypicalPersons.ALICE; +import static seedu.mentorstack.testutil.TypicalPersons.BOB; import java.util.Arrays; import java.util.Collections; @@ -15,9 +14,9 @@ import org.junit.jupiter.api.Test; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; -import seedu.address.testutil.PersonBuilder; +import seedu.mentorstack.model.person.exceptions.DuplicatePersonException; +import seedu.mentorstack.model.person.exceptions.PersonNotFoundException; +import seedu.mentorstack.testutil.PersonBuilder; public class UniquePersonListTest { @@ -42,7 +41,7 @@ public void contains_personInList_returnsTrue() { @Test public void contains_personWithSameIdentityFieldsInList_returnsTrue() { uniquePersonList.add(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + Person editedAlice = new PersonBuilder(ALICE).withSubjects(VALID_SUB_CS2102) .build(); assertTrue(uniquePersonList.contains(editedAlice)); } @@ -85,7 +84,7 @@ public void setPerson_editedPersonIsSamePerson_success() { @Test public void setPerson_editedPersonHasSameIdentity_success() { uniquePersonList.add(ALICE); - Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND) + Person editedAlice = new PersonBuilder(ALICE).withSubjects(VALID_SUB_CS2102) .build(); uniquePersonList.setPerson(ALICE, editedAlice); UniquePersonList expectedUniquePersonList = new UniquePersonList(); diff --git a/src/test/java/seedu/mentorstack/model/person/predicates/FilterPredicateTest.java b/src/test/java/seedu/mentorstack/model/person/predicates/FilterPredicateTest.java new file mode 100644 index 00000000000..a67ffbbfbfb --- /dev/null +++ b/src/test/java/seedu/mentorstack/model/person/predicates/FilterPredicateTest.java @@ -0,0 +1,110 @@ +package seedu.mentorstack.model.person.predicates; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Set; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.model.person.ArchiveStatus; +import seedu.mentorstack.model.person.Email; +import seedu.mentorstack.model.person.Gender; +import seedu.mentorstack.model.person.Name; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Phone; +import seedu.mentorstack.model.person.Subject; + +class FilterPredicateTest { + + private Person createPerson(String name, String gender, String phone, String email, + Set subjects, Set finishedSubjects) { + return new Person(new Name(name), new Gender(gender), new Phone(phone), + new Email(email), subjects, finishedSubjects, new ArchiveStatus("false"), false); + } + + @Test + void createPredicate_nameFilter_returnsCorrectPredicate() { + Person person = createPerson("Alice Tan", "F", "12345678", + "alice@example.com", Set.of(new Subject("Math"), + new Subject("Physics")), Set.of()); + + Predicate predicate = FilterPredicate.createPredicate("n", "Alice"); + assertNotNull(predicate); + assertTrue(predicate.test(person)); + + Person otherPerson = createPerson("Bob", "M", "87654321", + "bob@example.com", Set.of(new Subject("CS")), Set.of()); + assertFalse(predicate.test(otherPerson)); + } + + @Test + void createPredicate_phoneFilter_returnsCorrectPredicate() { + Person person = createPerson("John Doe", "M", "98765432", + "john@example.com", Set.of(new Subject("Science")), Set.of()); + + Predicate predicate = FilterPredicate.createPredicate("p", "98765432"); + assertNotNull(predicate); + assertTrue(predicate.test(person)); + } + + @Test + void createPredicate_emailFilter_returnsCorrectPredicate() { + Person person = createPerson("Emily", "F", "55556666", + "emily@example.com", Set.of(new Subject("Biology")), Set.of()); + + Predicate predicate = FilterPredicate.createPredicate("e", + "emily@example.com"); + assertNotNull(predicate); + assertTrue(predicate.test(person)); + } + + @Test + void createPredicate_subjectFilter_returnsCorrectPredicate() { + Person person = createPerson("Anna", "F", "99998888", + "anna@example.com", Set.of(new Subject("Mathematics"), + new Subject("Physics")), Set.of()); + + Predicate predicate = FilterPredicate.createPredicate("s", "math"); + assertNotNull(predicate); + assertTrue(predicate.test(person)); + } + + @Test + void createPredicate_genderFilter_returnsCorrectPredicate() { + Person person = createPerson("Ethan", "M", "22223333", + "ethan@example.com", Set.of(new Subject("English")), Set.of()); + + Predicate predicate = FilterPredicate.createPredicate("g", "M"); + assertNotNull(predicate); + assertTrue(predicate.test(person)); + } + + @Test + void createPredicate_archivedFilter_returnsCorrectPredicate() { + Person person = createPerson("Ethan", "M", "22223333", + "ethan@example.com", Set.of(new Subject("English")), Set.of()).archived(); + Predicate predicate = FilterPredicate.createPredicate("a", "t"); + assertNotNull(predicate); + assertTrue(predicate.test(person)); + } + + @Test + void createPredicate_unarchivedFilter_returnsCorrectPredicate() { + Person person = createPerson("Ethan", "M", "22223333", + "ethan@example.com", Set.of(new Subject("English")), Set.of()); + Predicate predicate = FilterPredicate.createPredicate("a", "f"); + assertNotNull(predicate); + assertTrue(predicate.test(person)); + } + + @Test + void createPredicate_invalidFilterType_returnsNull() { + Predicate predicate = FilterPredicate.createPredicate("invalid", + "test"); + assertNull(predicate); + } +} diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/mentorstack/storage/JsonAdaptedPersonTest.java similarity index 56% rename from src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java rename to src/test/java/seedu/mentorstack/storage/JsonAdaptedPersonTest.java index 83b11331cdb..72e532f50f8 100644 --- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java +++ b/src/test/java/seedu/mentorstack/storage/JsonAdaptedPersonTest.java @@ -1,9 +1,9 @@ -package seedu.address.storage; +package seedu.mentorstack.storage; import static org.junit.jupiter.api.Assertions.assertEquals; -import static seedu.address.storage.JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT; -import static seedu.address.testutil.Assert.assertThrows; -import static seedu.address.testutil.TypicalPersons.BENSON; +import static seedu.mentorstack.storage.JsonAdaptedPerson.MISSING_FIELD_MESSAGE_FORMAT; +import static seedu.mentorstack.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.TypicalPersons.BENSON; import java.util.ArrayList; import java.util.List; @@ -11,25 +11,24 @@ import org.junit.jupiter.api.Test; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; +import seedu.mentorstack.commons.exceptions.IllegalValueException; +import seedu.mentorstack.model.person.Email; +import seedu.mentorstack.model.person.Name; +import seedu.mentorstack.model.person.Phone; public class JsonAdaptedPersonTest { private static final String INVALID_NAME = "R@chel"; + private static final String INVALID_GENDER = " Female"; private static final String INVALID_PHONE = "+651234"; - private static final String INVALID_ADDRESS = " "; private static final String INVALID_EMAIL = "example.com"; - private static final String INVALID_TAG = "#friend"; + private static final String INVALID_SUBJECT = "#friend"; private static final String VALID_NAME = BENSON.getName().toString(); + private static final String VALID_GENDER = BENSON.getGender().toString(); private static final String VALID_PHONE = BENSON.getPhone().toString(); private static final String VALID_EMAIL = BENSON.getEmail().toString(); - private static final String VALID_ADDRESS = BENSON.getAddress().toString(); - private static final List VALID_TAGS = BENSON.getTags().stream() - .map(JsonAdaptedTag::new) + private static final List VALID_SUBJECTS = BENSON.getSubjects().stream() + .map(JsonAdaptedSubject::new) .collect(Collectors.toList()); @Test @@ -41,29 +40,40 @@ public void toModelType_validPersonDetails_returnsPerson() throws Exception { @Test public void toModelType_invalidName_throwsIllegalValueException() { JsonAdaptedPerson person = - new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + new JsonAdaptedPerson(INVALID_NAME, VALID_GENDER, VALID_PHONE, VALID_EMAIL, VALID_SUBJECTS); String expectedMessage = Name.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullName_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_GENDER, VALID_PHONE, + VALID_EMAIL, VALID_SUBJECTS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } + @Test + public void toModelType_invalidGender_throwsIllegalValueException() { + List invalidSubjects = new ArrayList<>(VALID_SUBJECTS); + invalidSubjects.add(new JsonAdaptedSubject(INVALID_SUBJECT)); + JsonAdaptedPerson person = + new JsonAdaptedPerson(VALID_NAME, null, VALID_PHONE, VALID_EMAIL, VALID_SUBJECTS); + assertThrows(IllegalValueException.class, person::toModelType); + } + @Test public void toModelType_invalidPhone_throwsIllegalValueException() { JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + new JsonAdaptedPerson(VALID_NAME, VALID_GENDER, INVALID_PHONE, VALID_EMAIL, VALID_SUBJECTS); String expectedMessage = Phone.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullPhone_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_GENDER, null, + VALID_EMAIL, VALID_SUBJECTS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @@ -71,39 +81,25 @@ public void toModelType_nullPhone_throwsIllegalValueException() { @Test public void toModelType_invalidEmail_throwsIllegalValueException() { JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS); + new JsonAdaptedPerson(VALID_NAME, VALID_GENDER, VALID_PHONE, INVALID_EMAIL, VALID_SUBJECTS); String expectedMessage = Email.MESSAGE_CONSTRAINTS; assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test public void toModelType_nullEmail_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS); + JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_GENDER, VALID_PHONE, + null, VALID_SUBJECTS); String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName()); assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); } @Test - public void toModelType_invalidAddress_throwsIllegalValueException() { - JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS); - String expectedMessage = Address.MESSAGE_CONSTRAINTS; - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_nullAddress_throwsIllegalValueException() { - JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS); - String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName()); - assertThrows(IllegalValueException.class, expectedMessage, person::toModelType); - } - - @Test - public void toModelType_invalidTags_throwsIllegalValueException() { - List invalidTags = new ArrayList<>(VALID_TAGS); - invalidTags.add(new JsonAdaptedTag(INVALID_TAG)); + public void toModelType_invalidSubjects_throwsIllegalValueException() { + List invalidSubjects = new ArrayList<>(VALID_SUBJECTS); + invalidSubjects.add(new JsonAdaptedSubject(INVALID_SUBJECT)); JsonAdaptedPerson person = - new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags); + new JsonAdaptedPerson(VALID_NAME, VALID_GENDER, VALID_PHONE, VALID_EMAIL, invalidSubjects); assertThrows(IllegalValueException.class, person::toModelType); } diff --git a/src/test/java/seedu/mentorstack/storage/JsonMentorstackStorageTest.java b/src/test/java/seedu/mentorstack/storage/JsonMentorstackStorageTest.java new file mode 100644 index 00000000000..e7f3f7fb8c5 --- /dev/null +++ b/src/test/java/seedu/mentorstack/storage/JsonMentorstackStorageTest.java @@ -0,0 +1,110 @@ +package seedu.mentorstack.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static seedu.mentorstack.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.TypicalPersons.ALICE; +import static seedu.mentorstack.testutil.TypicalPersons.HOON; +import static seedu.mentorstack.testutil.TypicalPersons.IDA; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import seedu.mentorstack.commons.exceptions.DataLoadingException; +import seedu.mentorstack.model.Mentorstack; +import seedu.mentorstack.model.ReadOnlyMentorstack; + +public class JsonMentorstackStorageTest { + private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonMentorstackStorageTest"); + + @TempDir + public Path testFolder; + + @Test + public void readMentorstack_nullFilePath_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> readMentorstack(null)); + } + + private java.util.Optional readMentorstack(String filePath) throws Exception { + return new JsonMentorstackStorage(Paths.get(filePath)).readMentorstack(addToTestDataPathIfNotNull(filePath)); + } + + private Path addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) { + return prefsFileInTestDataFolder != null + ? TEST_DATA_FOLDER.resolve(prefsFileInTestDataFolder) + : null; + } + + @Test + public void read_missingFile_emptyResult() throws Exception { + assertFalse(readMentorstack("NonExistentFile.json").isPresent()); + } + + @Test + public void read_notJsonFormat_exceptionThrown() { + assertThrows(DataLoadingException.class, () -> readMentorstack("notJsonFormatMentorstack.json")); + } + + @Test + public void readMentorstack_invalidPersonMentorstack_throwDataLoadingException() { + assertThrows(DataLoadingException.class, () -> readMentorstack("invalidPersonMentorstack.json")); + } + + @Test + public void readMentorstack_invalidAndValidPersonMentorstack_throwDataLoadingException() { + assertThrows(DataLoadingException.class, () -> readMentorstack("invalidAndValidPersonMentorstack.json")); + } + + @Test + public void readAndSaveMentorstack_allInOrder_success() throws Exception { + Path filePath = testFolder.resolve("TempMentorstack.json"); + Mentorstack original = getTypicalMentorstack(); + JsonMentorstackStorage jsonMentorstackStorage = new JsonMentorstackStorage(filePath); + + // Save in new file and read back + jsonMentorstackStorage.saveMentorstack(original, filePath); + ReadOnlyMentorstack readBack = jsonMentorstackStorage.readMentorstack(filePath).get(); + assertEquals(original, new Mentorstack(readBack)); + + // Modify data, overwrite exiting file, and read back + original.addPerson(HOON); + original.removePerson(ALICE); + jsonMentorstackStorage.saveMentorstack(original, filePath); + readBack = jsonMentorstackStorage.readMentorstack(filePath).get(); + assertEquals(original, new Mentorstack(readBack)); + + // Save and read without specifying file path + original.addPerson(IDA); + jsonMentorstackStorage.saveMentorstack(original); // file path not specified + readBack = jsonMentorstackStorage.readMentorstack().get(); // file path not specified + assertEquals(original, new Mentorstack(readBack)); + + } + + @Test + public void saveMentorstack_nullMentorstack_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> saveMentorstack(null, "SomeFile.json")); + } + + /** + * Saves {@code mentorstack} at the specified {@code filePath}. + */ + private void saveMentorstack(ReadOnlyMentorstack mentorstack, String filePath) { + try { + new JsonMentorstackStorage(Paths.get(filePath)) + .saveMentorstack(mentorstack, addToTestDataPathIfNotNull(filePath)); + } catch (IOException ioe) { + throw new AssertionError("There should not be an error writing to the file.", ioe); + } + } + + @Test + public void saveMentorstack_nullFilePath_throwsNullPointerException() { + assertThrows(NullPointerException.class, () -> saveMentorstack(new Mentorstack(), null)); + } +} diff --git a/src/test/java/seedu/mentorstack/storage/JsonSerializableMentorstackTest.java b/src/test/java/seedu/mentorstack/storage/JsonSerializableMentorstackTest.java new file mode 100644 index 00000000000..3a3a40f6e6c --- /dev/null +++ b/src/test/java/seedu/mentorstack/storage/JsonSerializableMentorstackTest.java @@ -0,0 +1,47 @@ +package seedu.mentorstack.storage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.mentorstack.testutil.Assert.assertThrows; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; + +import seedu.mentorstack.commons.exceptions.IllegalValueException; +import seedu.mentorstack.commons.util.JsonUtil; +import seedu.mentorstack.model.Mentorstack; +import seedu.mentorstack.testutil.TypicalPersons; + +public class JsonSerializableMentorstackTest { + + private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "JsonSerializableMentorstackTest"); + private static final Path TYPICAL_PERSONS_FILE = TEST_DATA_FOLDER.resolve("typicalPersonsMentorstack.json"); + private static final Path INVALID_PERSON_FILE = TEST_DATA_FOLDER.resolve("invalidPersonMentorstack.json"); + private static final Path DUPLICATE_PERSON_FILE = TEST_DATA_FOLDER.resolve("duplicatePersonMentorstack.json"); + + @Test + public void toModelType_typicalPersonsFile_success() throws Exception { + JsonSerializableMentorstack dataFromFile = JsonUtil.readJsonFile(TYPICAL_PERSONS_FILE, + JsonSerializableMentorstack.class).get(); + Mentorstack mentorstackFromFile = dataFromFile.toModelType(); + Mentorstack typicalPersonsMentorstack = TypicalPersons.getTypicalMentorstack(); + assertEquals(mentorstackFromFile, typicalPersonsMentorstack); + } + + @Test + public void toModelType_invalidPersonFile_throwsIllegalValueException() throws Exception { + JsonSerializableMentorstack dataFromFile = JsonUtil.readJsonFile(INVALID_PERSON_FILE, + JsonSerializableMentorstack.class).get(); + assertThrows(IllegalValueException.class, dataFromFile::toModelType); + } + + @Test + public void toModelType_duplicatePersons_throwsIllegalValueException() throws Exception { + JsonSerializableMentorstack dataFromFile = JsonUtil.readJsonFile(DUPLICATE_PERSON_FILE, + JsonSerializableMentorstack.class).get(); + assertThrows(IllegalValueException.class, JsonSerializableMentorstack.MESSAGE_DUPLICATE_PERSON, + dataFromFile::toModelType); + } + +} diff --git a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java b/src/test/java/seedu/mentorstack/storage/JsonUserPrefsStorageTest.java similarity index 93% rename from src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java rename to src/test/java/seedu/mentorstack/storage/JsonUserPrefsStorageTest.java index ed0a413526a..ff8de8b1b98 100644 --- a/src/test/java/seedu/address/storage/JsonUserPrefsStorageTest.java +++ b/src/test/java/seedu/mentorstack/storage/JsonUserPrefsStorageTest.java @@ -1,8 +1,8 @@ -package seedu.address.storage; +package seedu.mentorstack.storage; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.Assert.assertThrows; import java.io.IOException; import java.nio.file.Path; @@ -12,9 +12,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.exceptions.DataLoadingException; -import seedu.address.model.UserPrefs; +import seedu.mentorstack.commons.core.GuiSettings; +import seedu.mentorstack.commons.exceptions.DataLoadingException; +import seedu.mentorstack.model.UserPrefs; public class JsonUserPrefsStorageTest { @@ -73,7 +73,7 @@ public void readUserPrefs_extraValuesInFile_extraValuesIgnored() throws DataLoad private UserPrefs getTypicalUserPrefs() { UserPrefs userPrefs = new UserPrefs(); userPrefs.setGuiSettings(new GuiSettings(1000, 500, 300, 100)); - userPrefs.setAddressBookFilePath(Paths.get("addressbook.json")); + userPrefs.setMentorstackFilePath(Paths.get("mentorstack.json")); return userPrefs; } diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/mentorstack/storage/StorageManagerTest.java similarity index 60% rename from src/test/java/seedu/address/storage/StorageManagerTest.java rename to src/test/java/seedu/mentorstack/storage/StorageManagerTest.java index 99a16548970..4ed47a5b134 100644 --- a/src/test/java/seedu/address/storage/StorageManagerTest.java +++ b/src/test/java/seedu/mentorstack/storage/StorageManagerTest.java @@ -1,8 +1,8 @@ -package seedu.address.storage; +package seedu.mentorstack.storage; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook; +import static seedu.mentorstack.testutil.TypicalPersons.getTypicalMentorstack; import java.nio.file.Path; @@ -10,10 +10,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import seedu.address.commons.core.GuiSettings; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; +import seedu.mentorstack.commons.core.GuiSettings; +import seedu.mentorstack.model.Mentorstack; +import seedu.mentorstack.model.ReadOnlyMentorstack; +import seedu.mentorstack.model.UserPrefs; public class StorageManagerTest { @@ -24,9 +24,9 @@ public class StorageManagerTest { @BeforeEach public void setUp() { - JsonAddressBookStorage addressBookStorage = new JsonAddressBookStorage(getTempFilePath("ab")); + JsonMentorstackStorage mentorstackStorage = new JsonMentorstackStorage(getTempFilePath("ab")); JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(getTempFilePath("prefs")); - storageManager = new StorageManager(addressBookStorage, userPrefsStorage); + storageManager = new StorageManager(mentorstackStorage, userPrefsStorage); } private Path getTempFilePath(String fileName) { @@ -48,21 +48,21 @@ public void prefsReadSave() throws Exception { } @Test - public void addressBookReadSave() throws Exception { + public void mentorstackReadSave() throws Exception { /* * Note: This is an integration test that verifies the StorageManager is properly wired to the - * {@link JsonAddressBookStorage} class. - * More extensive testing of UserPref saving/reading is done in {@link JsonAddressBookStorageTest} class. + * {@link JsonMentorstackStorage} class. + * More extensive testing of UserPref saving/reading is done in {@link JsonMentorstackStorageTest} class. */ - AddressBook original = getTypicalAddressBook(); - storageManager.saveAddressBook(original); - ReadOnlyAddressBook retrieved = storageManager.readAddressBook().get(); - assertEquals(original, new AddressBook(retrieved)); + Mentorstack original = getTypicalMentorstack(); + storageManager.saveMentorstack(original); + ReadOnlyMentorstack retrieved = storageManager.readMentorstack().get(); + assertEquals(original, new Mentorstack(retrieved)); } @Test - public void getAddressBookFilePath() { - assertNotNull(storageManager.getAddressBookFilePath()); + public void getMentorstackFilePath() { + assertNotNull(storageManager.getMentorstackFilePath()); } } diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/mentorstack/testutil/AddressBookBuilder.java similarity index 65% rename from src/test/java/seedu/address/testutil/AddressBookBuilder.java rename to src/test/java/seedu/mentorstack/testutil/AddressBookBuilder.java index d53799fd110..7b4424ef808 100644 --- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java +++ b/src/test/java/seedu/mentorstack/testutil/AddressBookBuilder.java @@ -1,7 +1,7 @@ -package seedu.address.testutil; +package seedu.mentorstack.testutil; -import seedu.address.model.AddressBook; -import seedu.address.model.person.Person; +import seedu.mentorstack.model.Mentorstack; +import seedu.mentorstack.model.person.Person; /** * A utility class to help with building Addressbook objects. @@ -10,13 +10,13 @@ */ public class AddressBookBuilder { - private AddressBook addressBook; + private Mentorstack addressBook; public AddressBookBuilder() { - addressBook = new AddressBook(); + addressBook = new Mentorstack(); } - public AddressBookBuilder(AddressBook addressBook) { + public AddressBookBuilder(Mentorstack addressBook) { this.addressBook = addressBook; } @@ -28,7 +28,7 @@ public AddressBookBuilder withPerson(Person person) { return this; } - public AddressBook build() { + public Mentorstack build() { return addressBook; } } diff --git a/src/test/java/seedu/address/testutil/Assert.java b/src/test/java/seedu/mentorstack/testutil/Assert.java similarity index 97% rename from src/test/java/seedu/address/testutil/Assert.java rename to src/test/java/seedu/mentorstack/testutil/Assert.java index 9863093bd6e..92bbf4ea0d8 100644 --- a/src/test/java/seedu/address/testutil/Assert.java +++ b/src/test/java/seedu/mentorstack/testutil/Assert.java @@ -1,4 +1,4 @@ -package seedu.address.testutil; +package seedu.mentorstack.testutil; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.function.Executable; diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/mentorstack/testutil/EditPersonDescriptorBuilder.java similarity index 64% rename from src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java rename to src/test/java/seedu/mentorstack/testutil/EditPersonDescriptorBuilder.java index 4584bd5044e..fc896687ded 100644 --- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java +++ b/src/test/java/seedu/mentorstack/testutil/EditPersonDescriptorBuilder.java @@ -1,16 +1,16 @@ -package seedu.address.testutil; +package seedu.mentorstack.testutil; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.mentorstack.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.mentorstack.model.person.Email; +import seedu.mentorstack.model.person.Gender; +import seedu.mentorstack.model.person.Name; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Phone; +import seedu.mentorstack.model.person.Subject; /** * A utility class to help with building EditPersonDescriptor objects. @@ -35,8 +35,7 @@ public EditPersonDescriptorBuilder(Person person) { descriptor.setName(person.getName()); descriptor.setPhone(person.getPhone()); descriptor.setEmail(person.getEmail()); - descriptor.setAddress(person.getAddress()); - descriptor.setTags(person.getTags()); + descriptor.setSubjects(person.getSubjects()); } /** @@ -64,20 +63,20 @@ public EditPersonDescriptorBuilder withEmail(String email) { } /** - * Sets the {@code Address} of the {@code EditPersonDescriptor} that we are building. + * Sets the {@code Gender} of the {@code EditPersonDescriptor} that we are building. */ - public EditPersonDescriptorBuilder withAddress(String address) { - descriptor.setAddress(new Address(address)); + public EditPersonDescriptorBuilder withGender(String gender) { + descriptor.setGender(new Gender(gender)); return this; } /** - * Parses the {@code tags} into a {@code Set} and set it to the {@code EditPersonDescriptor} + * Parses the {@code subjects} into a {@code Set} and set it to the {@code EditPersonDescriptor} * that we are building. */ - public EditPersonDescriptorBuilder withTags(String... tags) { - Set tagSet = Stream.of(tags).map(Tag::new).collect(Collectors.toSet()); - descriptor.setTags(tagSet); + public EditPersonDescriptorBuilder withSubjects(String... subjects) { + Set subjectSet = Stream.of(subjects).map(Subject::new).collect(Collectors.toSet()); + descriptor.setSubjects(subjectSet); return this; } diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/mentorstack/testutil/PersonBuilder.java similarity index 57% rename from src/test/java/seedu/address/testutil/PersonBuilder.java rename to src/test/java/seedu/mentorstack/testutil/PersonBuilder.java index 6be381d39ba..901dd297a74 100644 --- a/src/test/java/seedu/address/testutil/PersonBuilder.java +++ b/src/test/java/seedu/mentorstack/testutil/PersonBuilder.java @@ -1,15 +1,15 @@ -package seedu.address.testutil; +package seedu.mentorstack.testutil; import java.util.HashSet; import java.util.Set; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; -import seedu.address.model.util.SampleDataUtil; +import seedu.mentorstack.model.person.Email; +import seedu.mentorstack.model.person.Gender; +import seedu.mentorstack.model.person.Name; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Phone; +import seedu.mentorstack.model.person.Subject; +import seedu.mentorstack.model.util.SampleDataUtil; /** * A utility class to help with building Person objects. @@ -17,25 +17,27 @@ public class PersonBuilder { public static final String DEFAULT_NAME = "Amy Bee"; + public static final String DEFAULT_GENDER = "F"; public static final String DEFAULT_PHONE = "85355255"; public static final String DEFAULT_EMAIL = "amy@gmail.com"; - public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111"; + public static final String DEFAULT_SUB = "CS2103"; private Name name; + private Gender gender; private Phone phone; private Email email; - private Address address; - private Set tags; + private Set subjects; /** * Creates a {@code PersonBuilder} with the default details. */ public PersonBuilder() { name = new Name(DEFAULT_NAME); + gender = new Gender(DEFAULT_GENDER); phone = new Phone(DEFAULT_PHONE); email = new Email(DEFAULT_EMAIL); - address = new Address(DEFAULT_ADDRESS); - tags = new HashSet<>(); + subjects = new HashSet<>(); + subjects.add(new Subject(DEFAULT_SUB)); } /** @@ -43,10 +45,10 @@ public PersonBuilder() { */ public PersonBuilder(Person personToCopy) { name = personToCopy.getName(); + gender = personToCopy.getGender(); phone = personToCopy.getPhone(); email = personToCopy.getEmail(); - address = personToCopy.getAddress(); - tags = new HashSet<>(personToCopy.getTags()); + subjects = new HashSet<>(personToCopy.getSubjects()); } /** @@ -58,18 +60,18 @@ public PersonBuilder withName(String name) { } /** - * Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building. + * Sets the {@code Gender} of the {@code Person} that we are building. */ - public PersonBuilder withTags(String ... tags) { - this.tags = SampleDataUtil.getTagSet(tags); + public PersonBuilder withGender(String gender) { + this.gender = new Gender(gender); return this; } /** - * Sets the {@code Address} of the {@code Person} that we are building. + * Parses the {@code subjects} into a {@code Set} and set it to the {@code Person} that we are building. */ - public PersonBuilder withAddress(String address) { - this.address = new Address(address); + public PersonBuilder withSubjects(String ... subjects) { + this.subjects = SampleDataUtil.getSubjectSet(subjects); return this; } @@ -90,7 +92,7 @@ public PersonBuilder withEmail(String email) { } public Person build() { - return new Person(name, phone, email, address, tags); + return new Person(name, gender, phone, email, subjects); } } diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/mentorstack/testutil/PersonUtil.java similarity index 55% rename from src/test/java/seedu/address/testutil/PersonUtil.java rename to src/test/java/seedu/mentorstack/testutil/PersonUtil.java index 90849945183..cb8bb634d04 100644 --- a/src/test/java/seedu/address/testutil/PersonUtil.java +++ b/src/test/java/seedu/mentorstack/testutil/PersonUtil.java @@ -1,17 +1,17 @@ -package seedu.address.testutil; +package seedu.mentorstack.testutil; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_GENDER; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.mentorstack.logic.parser.CliSyntax.PREFIX_SUBJECT; import java.util.Set; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.model.person.Person; -import seedu.address.model.tag.Tag; +import seedu.mentorstack.logic.commands.AddCommand; +import seedu.mentorstack.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.mentorstack.model.person.Person; +import seedu.mentorstack.model.person.Subject; /** * A utility class for Person. @@ -31,11 +31,11 @@ public static String getAddCommand(Person person) { public static String getPersonDetails(Person person) { StringBuilder sb = new StringBuilder(); sb.append(PREFIX_NAME + person.getName().fullName + " "); + sb.append(PREFIX_GENDER + person.getGender().toString() + " "); sb.append(PREFIX_PHONE + person.getPhone().value + " "); sb.append(PREFIX_EMAIL + person.getEmail().value + " "); - sb.append(PREFIX_ADDRESS + person.getAddress().value + " "); - person.getTags().stream().forEach( - s -> sb.append(PREFIX_TAG + s.tagName + " ") + person.getSubjects().stream().forEach( + s -> sb.append(PREFIX_SUBJECT + s.subjectName + " ") ); return sb.toString(); } @@ -46,15 +46,15 @@ public static String getPersonDetails(Person person) { public static String getEditPersonDescriptorDetails(EditPersonDescriptor descriptor) { StringBuilder sb = new StringBuilder(); descriptor.getName().ifPresent(name -> sb.append(PREFIX_NAME).append(name.fullName).append(" ")); + descriptor.getGender().ifPresent(gender -> sb.append(PREFIX_GENDER).append(gender.value).append(" ")); descriptor.getPhone().ifPresent(phone -> sb.append(PREFIX_PHONE).append(phone.value).append(" ")); descriptor.getEmail().ifPresent(email -> sb.append(PREFIX_EMAIL).append(email.value).append(" ")); - descriptor.getAddress().ifPresent(address -> sb.append(PREFIX_ADDRESS).append(address.value).append(" ")); - if (descriptor.getTags().isPresent()) { - Set tags = descriptor.getTags().get(); + if (descriptor.getSubjects().isPresent()) { + Set tags = descriptor.getSubjects().get(); if (tags.isEmpty()) { - sb.append(PREFIX_TAG); + sb.append(PREFIX_SUBJECT); } else { - tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" ")); + tags.forEach(s -> sb.append(PREFIX_SUBJECT).append(s.subjectName).append(" ")); } } return sb.toString(); diff --git a/src/test/java/seedu/address/testutil/SerializableTestClass.java b/src/test/java/seedu/mentorstack/testutil/SerializableTestClass.java similarity index 98% rename from src/test/java/seedu/address/testutil/SerializableTestClass.java rename to src/test/java/seedu/mentorstack/testutil/SerializableTestClass.java index f5a66340489..87663221896 100644 --- a/src/test/java/seedu/address/testutil/SerializableTestClass.java +++ b/src/test/java/seedu/mentorstack/testutil/SerializableTestClass.java @@ -1,4 +1,4 @@ -package seedu.address.testutil; +package seedu.mentorstack.testutil; import java.time.LocalDateTime; import java.util.ArrayList; diff --git a/src/test/java/seedu/address/testutil/TestUtil.java b/src/test/java/seedu/mentorstack/testutil/TestUtil.java similarity index 89% rename from src/test/java/seedu/address/testutil/TestUtil.java rename to src/test/java/seedu/mentorstack/testutil/TestUtil.java index 896d103eb0b..a05e3c91a9c 100644 --- a/src/test/java/seedu/address/testutil/TestUtil.java +++ b/src/test/java/seedu/mentorstack/testutil/TestUtil.java @@ -1,13 +1,13 @@ -package seedu.address.testutil; +package seedu.mentorstack.testutil; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import seedu.address.commons.core.index.Index; -import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.mentorstack.commons.core.index.Index; +import seedu.mentorstack.model.Model; +import seedu.mentorstack.model.person.Person; /** * A utility class for test cases. diff --git a/src/test/java/seedu/mentorstack/testutil/TypicalEmails.java b/src/test/java/seedu/mentorstack/testutil/TypicalEmails.java new file mode 100644 index 00000000000..060667421ac --- /dev/null +++ b/src/test/java/seedu/mentorstack/testutil/TypicalEmails.java @@ -0,0 +1,12 @@ +package seedu.mentorstack.testutil; + +import seedu.mentorstack.model.person.Email; + +/** + * A utility class containing a list of {@code Index} objects to be used in tests. + */ +public class TypicalEmails { + public static final Email EMAIL_FIRST_PERSON = new Email("first@example.com"); + public static final Email EMAIL_SECOND_PERSON = new Email("second@example.com"); + public static final Email EMAIL_THIRD_PERSON = new Email("third@example.com"); +} diff --git a/src/test/java/seedu/mentorstack/testutil/TypicalIndexSets.java b/src/test/java/seedu/mentorstack/testutil/TypicalIndexSets.java new file mode 100644 index 00000000000..b9796830cd8 --- /dev/null +++ b/src/test/java/seedu/mentorstack/testutil/TypicalIndexSets.java @@ -0,0 +1,22 @@ +package seedu.mentorstack.testutil; + +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_FIRST_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_SECOND_PERSON; +import static seedu.mentorstack.testutil.TypicalIndexes.INDEX_THIRD_PERSON; + +import java.util.HashSet; +import java.util.Set; + +import seedu.mentorstack.commons.core.index.Index; + +/** + * A utility class containing sets of {@code Index} objects to be used in tests. + */ +public class TypicalIndexSets { + public static final Set INDEX_SET_FIRST_PERSON = Set.of(INDEX_FIRST_PERSON); + public static final Set INDEX_SET_SECOND_PERSON = Set.of(INDEX_SECOND_PERSON); + public static final Set INDEX_SET_THIRD_PERSON = Set.of(INDEX_THIRD_PERSON); + public static final Set INDEX_SET_ALL = new HashSet(Set.of( + INDEX_FIRST_PERSON, INDEX_SECOND_PERSON, INDEX_THIRD_PERSON + )); +} diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/mentorstack/testutil/TypicalIndexes.java similarity index 80% rename from src/test/java/seedu/address/testutil/TypicalIndexes.java rename to src/test/java/seedu/mentorstack/testutil/TypicalIndexes.java index 1e613937657..95690844cb4 100644 --- a/src/test/java/seedu/address/testutil/TypicalIndexes.java +++ b/src/test/java/seedu/mentorstack/testutil/TypicalIndexes.java @@ -1,6 +1,6 @@ -package seedu.address.testutil; +package seedu.mentorstack.testutil; -import seedu.address.commons.core.index.Index; +import seedu.mentorstack.commons.core.index.Index; /** * A utility class containing a list of {@code Index} objects to be used in tests. diff --git a/src/test/java/seedu/mentorstack/testutil/TypicalPersons.java b/src/test/java/seedu/mentorstack/testutil/TypicalPersons.java new file mode 100644 index 00000000000..525e1a538df --- /dev/null +++ b/src/test/java/seedu/mentorstack/testutil/TypicalPersons.java @@ -0,0 +1,77 @@ +package seedu.mentorstack.testutil; + +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_EMAIL_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_EMAIL_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_NAME_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_NAME_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_PHONE_AMY; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_PHONE_BOB; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2100; +import static seedu.mentorstack.logic.commands.CommandTestUtil.VALID_SUB_CS2102; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.mentorstack.model.Mentorstack; +import seedu.mentorstack.model.person.Person; + +/** + * A utility class containing a list of {@code Person} objects to be used in tests. + */ +public class TypicalPersons { + + public static final Person ALICE = new PersonBuilder().withName("Alice Pauline") + .withPhone("94351253").withGender("F") + .withSubjects("CS1010S").build(); + public static final Person BENSON = new PersonBuilder().withName("Benson Meier") + .withEmail("johnd@example.com").withPhone("98765432").withGender("M") + .withSubjects("CS1010C", "CS2100").build(); + public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563") + .withEmail("heinz@example.com").withGender("M") + .withSubjects("CS1010C", "CS2100").build(); + public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533") + .withEmail("cornelia@example.com").withGender("M").withSubjects("CS2106").build(); + public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withGender("F").withPhone("9482224") + .withEmail("werner@example.com").withSubjects("CS1010C", "CS2100").build(); + public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withGender("F").withPhone("9482427") + .withEmail("lydia@example.com").withSubjects("CS1010C", "CS2100").build(); + public static final Person GEORGE = new PersonBuilder().withName("George Best").withGender("M").withPhone("9482442") + .withEmail("anna@example.com").withSubjects("CS1010C", "CS2100").build(); + + // Manually added + public static final Person HOON = new PersonBuilder().withGender("M").withName("Hoon Meier").withPhone("8482424") + .withEmail("stefan@example.com").withSubjects("CS1101").build(); + public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withGender("F").withPhone("8482131") + .withEmail("hans@example.com").withSubjects("CS2100").build(); + + // Manually added - Person's details found in {@code CommandTestUtil} + public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withGender("F") + .withPhone(VALID_PHONE_AMY) + .withEmail(VALID_EMAIL_AMY) + .withSubjects(VALID_SUB_CS2100).build(); + public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withGender("M") + .withPhone(VALID_PHONE_BOB) + .withEmail(VALID_EMAIL_BOB) + .withSubjects(VALID_SUB_CS2102, VALID_SUB_CS2100) + .build(); + + public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER + + private TypicalPersons() {} // prevents instantiation + + /** + * Returns an {@code Mentorstack} with all the typical persons. + */ + public static Mentorstack getTypicalMentorstack() { + Mentorstack ab = new Mentorstack(); + for (Person person : getTypicalPersons()) { + ab.addPerson(person); + } + return ab; + } + + public static List getTypicalPersons() { + return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE)); + } +} diff --git a/src/test/java/seedu/address/ui/TestFxmlObject.java b/src/test/java/seedu/mentorstack/ui/TestFxmlObject.java similarity index 96% rename from src/test/java/seedu/address/ui/TestFxmlObject.java rename to src/test/java/seedu/mentorstack/ui/TestFxmlObject.java index 93f40f1276a..2b3f3b9bb83 100644 --- a/src/test/java/seedu/address/ui/TestFxmlObject.java +++ b/src/test/java/seedu/mentorstack/ui/TestFxmlObject.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.mentorstack.ui; import java.util.Objects; diff --git a/src/test/java/seedu/address/ui/UiPartTest.java b/src/test/java/seedu/mentorstack/ui/UiPartTest.java similarity index 97% rename from src/test/java/seedu/address/ui/UiPartTest.java rename to src/test/java/seedu/mentorstack/ui/UiPartTest.java index 33d82d911b8..3da736ce42b 100644 --- a/src/test/java/seedu/address/ui/UiPartTest.java +++ b/src/test/java/seedu/mentorstack/ui/UiPartTest.java @@ -1,8 +1,8 @@ -package seedu.address.ui; +package seedu.mentorstack.ui; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static seedu.address.testutil.Assert.assertThrows; +import static seedu.mentorstack.testutil.Assert.assertThrows; import java.net.URL; import java.nio.file.Path; @@ -11,7 +11,7 @@ import org.junit.jupiter.api.io.TempDir; import javafx.fxml.FXML; -import seedu.address.MainApp; +import seedu.mentorstack.MainApp; public class UiPartTest { diff --git a/src/test/resources/view/UiPartTest/validFile.fxml b/src/test/resources/view/UiPartTest/validFile.fxml index bab836af0db..7cfa214f8ef 100644 --- a/src/test/resources/view/UiPartTest/validFile.fxml +++ b/src/test/resources/view/UiPartTest/validFile.fxml @@ -1,4 +1,4 @@ - + Hello World! diff --git a/src/test/resources/view/UiPartTest/validFileWithFxRoot.fxml b/src/test/resources/view/UiPartTest/validFileWithFxRoot.fxml index 1a8b2c9e4d3..5ccec0cff17 100644 --- a/src/test/resources/view/UiPartTest/validFileWithFxRoot.fxml +++ b/src/test/resources/view/UiPartTest/validFileWithFxRoot.fxml @@ -1,6 +1,6 @@ - + Hello World! diff --git a/src/test/test.iml b/src/test/test.iml new file mode 100644 index 00000000000..6e30bb1c613 --- /dev/null +++ b/src/test/test.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + +