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..eab4c7db6a5 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ src/test/data/sandbox/ # MacOS custom attributes files created by Finder .DS_Store docs/_site/ +docs/_markbind/logs/ diff --git a/README.md b/README.md index 16208adb9b6..dbf7e2685c1 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,53 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![Java CI](https://github.com/AY2425S1-CS2103T-T11-3/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2425S1-CS2103T-T11-3/tp/actions/workflows/gradle.yml) ![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. +# Bridal Boss README + +## Overview + +**Bridal Boss** is an innovative app designed specifically for wedding organizers. It helps users manage multiple weddings simultaneously by streamlining the organization of vendor and client information. With Bridal Boss, wedding planners can efficiently categorize contacts, track event timelines, and access vital information quickly, ensuring smooth execution of all wedding events. + +--- + +## Table of Contents + +1. [Target Audience](#target-audience) +2. [Value Proposition](#value-proposition) +3. [Problem Scope](#problem-scope) +4. [Features](#features) +5. [Acknowledgements](#acknowledgements) + +--- + +## Target Audience + +Bridal Boss is tailored for **wedding organizers** who coordinate weddings, manage vendor relations, and handle client requests, often juggling multiple events at once. + +## Value Proposition + +Bridal Boss enables users to: + +- Manage multiple weddings concurrently +- Maintain detailed vendor and client records +- Access information quickly to accommodate last-minute changes +- Categorize and update contacts related to each wedding effortlessly + +## Problem Scope + +Bridal Boss addresses key challenges for wedding organizers: + +- Keeps up-to-date contact information for active vendors +- Organizes contacts by wedding and stakeholder type +- Facilitates last-minute adjustments to plans + +## Features + +- Create contacts +- Delete contacts +- View contacts +- Tag contacts +- Filter contacts + +## Acknowledgements +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..36de295d3e6 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,10 @@ plugins { id 'jacoco' } +run { + enableAssertions = true +} + mainClassName = 'seedu.address.Main' sourceCompatibility = JavaVersion.VERSION_17 @@ -66,7 +70,7 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'BridalBoss.jar' } 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..2d42cb56807 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,59 +1,60 @@ --- -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 + +We are a team based in the [School of Computing, National University of Singapore](http://www.comp.nus.edu.sg). You can reach us at the email `seer[at]comp.nus.edu.sg` ## Project team -### John Doe +### Yik Jun Xian - + [[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] +[[github](https://yikjunxian.com/johndoe)] [[portfolio](team/johndoe.md)] -* Role: Project Advisor +* Role: UI developer +* Responsibilities: Develop GUI features -### Jane Doe +### Sng Ying Xuan Shernice - + -[[github](http://github.com/johndoe)] +[[github](http://github.com/shernicesng)] [[portfolio](team/johndoe.md)] -* Role: Team Lead -* Responsibilities: UI +* Role: Developer +* Responsibilities: Code Quality -### Johnny Doe +### Joey Goh - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/jowhee3011)] +[[portfolio](team/johndoe.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: Scheduling and tracking -### Jean Doe +### Guan Yi Sim - - -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] + * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Testing -### James Doe +### Ved Joshi - + -[[github](http://github.com/johndoe)] +[[github](http://github.com/vedjoshi)] [[portfolio](team/johndoe.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Documentation 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..e5c721a3bc5 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..a3367a5989e 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,15 +1,13 @@ --- -layout: page -title: Developer Guide +layout: default.md +title: "Developer Guide" +pageNav: 3 --- -* Table of Contents -{:toc} --------------------------------------------------------------------------------------------------------------------- - -## **Acknowledgements** +# Bridal Boss Developer Guide -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} + + -------------------------------------------------------------------------------------------------------------------- @@ -21,14 +19,9 @@ 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. -
- ### Architecture - + The ***Architecture Diagram*** given above explains the high-level design of the App. @@ -53,7 +46,7 @@ The bulk of the app's work is done by the following four components: 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,17 +55,17 @@ 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/AY2425S1-CS2103T-T11-3/tp/blob/master/src/main/java/seedu/address/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 consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `WeddingListPanel`, `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) @@ -85,18 +78,20 @@ The `UI` component, ### 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/AY2425S1-CS2103T-T11-3/tp/blob/master/src/main/java/seedu/address/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: @@ -108,37 +103,37 @@ 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. ### 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/AY2425S1-CS2103T-T11-3/tp/blob/master/src/main/java/seedu/address/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 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 the address book data i.e., all `Person` and `Wedding` objects (which are contained in a `UniquePersonList` and `UniqueWeddingList` object). +* stores the currently 'selected' `Person` and/or `Wedding` objects (e.g., results of a search query) as separate _filtered_ lists 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/AY2425S1-CS2103T-T11-3/tp/blob/master/src/main/java/seedu/address/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. @@ -154,6 +149,28 @@ Classes used by multiple components are in the `seedu.address.commons` package. ## **Implementation** This section describes some noteworthy details on how certain features are implemented. +### Adding a new wedding + + + +1. **Identifying the Client**: + - The client for the new wedding is identified either by their index in the filtered person list or by their name. + +2. **Validating the Client**: + - Before creating the new wedding, the `AddwCommand` checks if the selected client is already associated with another wedding. + +3. **Creating the Wedding**: + - Once the client is validated, the `AddwCommand` creates a new `Wedding` object with the provided details (name, client, date, and venue). + +4. **Checking for Duplicates**: + - The `AddwCommand` checks if the new wedding already exists in the address book. + +5. **Updating the Model**: + - If the new wedding passes all the validation checks, the `AddwCommand` adds the wedding to the model. + - It also sets the client's own wedding to the new wedding using the `setOwnWedding` method of the `Person` class. + +6. **Returning the Result**: + - Finally, the success message is displayed to the user. ### \[Proposed\] Undo/redo feature @@ -171,58 +188,67 @@ Given below is an example usage scenario and how the undo/redo mechanism behaves 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. -![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. -![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) + + + -
: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`. +**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#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 + + +**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: -![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. +**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. -
+
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. -
: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. + + +**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. -
+ 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 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: @@ -237,14 +263,6 @@ The following activity diagram summarizes what happens when a user executes a ne * 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 - -_{Explain here how the data archiving feature will be implemented}_ - - --------------------------------------------------------------------------------------------------------------------- ## **Documentation, logging, testing, configuration, dev-ops** @@ -256,77 +274,368 @@ _{Explain here how the data archiving feature will be implemented}_ -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Requirements** +## Product Scope + +### Target User Profile + +A small to medium scale **wedding organizer** responsible for planning and managing weddings. They coordinate with vendors, clients (brides, grooms, and their families), and participants (guests, photographers, caterers, etc.). Their work involves juggling multiple tasks and deadlines to ensure that each wedding runs smoothly. They may need to manage multiple weddings simultaneously and prefer efficient tools that help streamline their workflow. They are comfortable using desktop applications and can type quickly, preferring typing over mouse interactions. + +### Value Proposition + +**Bridal Boss** enables wedding organizers to manage multiple weddings simultaneously while maintaining detailed vendor and client records. It offers fast and efficient access to information, helping organizers categorize and update contacts related to each wedding easily. By providing streamlined management of vendor contacts, client preferences, and event timelines, Bridal Boss helps wedding organizers accommodate last-minute changes effectively and keep everything up to date. + +### User Stories + +Priorities: + +- High (must have) - `* * *` +- Medium (nice to have) - `* *` +- Low (unlikely to have) - `*` + +| Priority | As a... | I want to... | So that I can... | +|----------|----------------------------|----------------------------------------------------|-------------------------------------------------------------------------------| +| `* * *` | Wedding organizer | create separate profiles for each wedding | manage multiple weddings without confusion | +| `* * *` | Wedding organizer | view an overview of all ongoing weddings | manage multiple events at once without losing track | +| `* * *` | Wedding organizer | add and categorize vendors | keep track of service providers for each wedding | +| `* * *` | Wedding organizer | view stakeholders related to each specific wedding | easily check their schedules | +| `* * *` | Wedding organizer | update client preferences easily | accommodate last-minute changes and keep everything up to date | +| `* * *` | Wedding organizer | delete profiles and roles | remove outdated or incorrect information | +| `* * *` | Wedding organizer | quickly search for specific contacts or vendors | access critical information without delays | +| `* * *` | Wedding organizer | filter contacts based on roles | retrieve contacts of stakeholders involved in a wedding or of a specific role | +| `* *` | Wedding organizer | archive completed weddings | focus on current and upcoming events without clutter | +| `* *` | Wedding organizer | track expenses for each wedding | manage the overall wedding budget efficiently | +| `* *` | Wedding organizer | set reminders for important tasks or deadlines | ensure critical milestones are not missed | +| `* *` | Wedding organizer | track RSVPs from clients | keep an accurate guest count for each wedding | +| `* *` | Wedding organizer | generate reports on completed tasks | review progress and share updates with clients | +| `* *` | Wedding organizer | assign tasks to team members | delegate responsibilities and track progress efficiently | +| `*` | Wedding organizer | upload important documents (e.g., contracts) | access them quickly during planning | +| `*` | Wedding organizer | send automated reminders to vendors and clients | ensure they stay informed of upcoming deadlines | +| `*` | Wedding organizer | set up recurring tasks for common preparations | avoid manually creating the same tasks for each event | + +### Use Cases + +For all use cases below, refer to the [User Guide](UserGuide.md) for more details. + +#### **Use Case: UC1 - Add a Contact** + +**MSS:** + +1. Wedding Organizer adds a contact using the add command. +2. Contact details are added to the address book. +Use case ends. + +**Extensions:** + +- **1a.** Name, phone, email, address prefixes not inputed + - **1a1.** Error message displayed in result display and proper command format is shown to user. + - Use case ends. +- **1b.** Name is blank, contains invalid symbol or beyond 70 characters + - **1b1.** Error message displayed in result display. + - Use case ends. +- **1c.** Phone is not 8 digits, or does not begin with '6', '8' or '9' + - **1c1.** Error message displayed in result display. + - Use case ends. +- **1d.** Email is blank, or does not match email regex. + - **1d1.** Error message displayed in result display. + - Use case ends. +- **1e.** Address is blank + - **1e1.** Error message displayed in result display. + - Use case ends. +- **1f.** Role is not one word + - **1f1.** Error message is displayed in result display. + - Use case ends. +- **1g.** Wedding is blank when prefix is inputted, or is not one word + - **1g1.** Error message is displayed in result display. + - Use case ends. +- **1h.** Wedding field is not an index. + - **1h1.** Error message is displayed in result display. + - Use case ends. +- **1i.** Wedding index inputted is not in the list. + - **1i1.** Error message is displayed in result display. + - Use case ends. + +#### **Use Case: UC2 - View a Contact** +1. Wedding Organizer views a contact using the view command. +2. Contact details are displayed. +Use case ends. + +**Extensions:** + +- **1a.** No prefixes inputted. + - **1a1.** Error message displayed in result display. + - Use case ends. +- **1b.** Person name does not exist in the address book + - **1b1.** Error message displayed in result display. + - Use case ends. +- **1c.** Person index is not in the displayed list + - **1c1.** Error message displayed in result display. + - Use case ends. +- **1d.** Duplicate contacts with name containing inputted name + - **1d1.** Bridal Boss filters the person list to show contacts containing inputted name. + - **1d2.** Person list filtered to contain contacts with names containing inputted name. + - **1d3.** Error message is displayed in result display. + - Use case ends. + +#### **Use Case: UC3 - Filter Contacts** +1. Wedding Organizer filters contacts using the filter command. +2. Contacts are displayed based on the filter criteria. +Use case ends. + +**Extensions:** + +- **1a.** No prefixes inputted. + - **1a1.** Error message displayed in result display. + - Use case ends + +#### **Use Case: UC4 - Delete a Contact** +1. Wedding Organizer deletes a contact using the delete command. +2. Contact is removed from the address book. +Use case ends. + +**Extensions:** + +- **1a.** Person name does not exist in the address book + - **1a1.** Error message displayed in result display. + - Use case ends. +- **1b.** Person index is not in the displayed list + - **1b1.** Error message displayed in result display. + - Use case ends. +- **1c.** Duplicate contacts with name containing inputted name + - **1c1.** Bridal Boss filters the person list to show contacts containing inputted name. + - **1c2.** Use case resumes from step 1. +- **1d.** Person is a client of a wedding + - **1d1.** Error message is displayed in result display. + - **1d2.** User do UC9 - Delete a wedding + - **1d3.** User redo this use case. + - Use case ends. + + +#### **Use Case: UC5 - Edit a Contact** +1. Wedding Organizer edits a contact using the edit command. +2. Contact details are updated. +Use case ends. + +**Extensions:** + +- **1a.** Contact details is not valid + - **1a1.** Error message displayed in result display. + - Use case ends. +- **1b.** Contact index is not in the displayed list. + - **1b1.** Same as 1a1. +- **1c.** Person name does not exist in the address book + - **1c1.** Same as 1a1. +- **1d.** Duplicate contacts. + - **1d1.** Same as 1a1. + + +#### **Use Case: UC6 - Add a Wedding** + +**MSS:** + +1. Wedding Organizer adds a wedding using the addw command. +2. Wedding details are added to the wedding list. + Use case ends. + +**Extensions:** + +- **1a.** Client name does not exist in the address book + - **1a1.** Error message displayed in result display. + - Use case ends. + - **1b.** Contact/Wedding index is not in the displayed list + - **1b1.** Error message displayed in result display. + - Use case ends. + - **1c.** Duplicate contacts with name containing inputted name + - **1c1.** Bridal Boss filters the person list to show contacts containing inputted name. + - **1c2.** Use case resumes from step 1. + - **1d.** Contact is already a client of another wedding + - **1d1.** Error message displayed in result display. + - Use case ends. + +#### **Use Case: UC7 - View a Wedding** + +1. Wedding Organizer views a wedding using the vieww command. +2. Wedding details are displayed. +Use case ends. + +**Extensions:** + +- **1a.** No prefixes inputted. + - **1a1.** Error message displayed in result display. + - Use case ends. +- **1b.** Wedding name does not exist in the address book + - **1b1.** Same as 1a1. +- **1c.** Wedding index is not in the displayed list + - **1c1.** Same as 1a1. +- **1d.** Duplicate wedding with name containing inputted name + - **1d1.** Bridal Boss filters the wedding list to show weddings containing inputted name. + - **1d2.** Same as 1a1. + +#### **Use Case: UC8 - Delete a wedding** +1. Wedding Organizer deletes a wedding using the deletew command. +2. Wedding is removed from the address book. +Use case ends. + +**Extensions:** + +- **1a.** Wedding name does not exist in the address book + - **1a1.** Error message displayed in result display. + - Use case ends. +- **1b.** Wedding index is not in the displayed list + - **1b1.** Error message displayed in result display. + - Use case ends. +- **1c.** Same as 1d in UC7 - View a wedding + +#### **Use Case: UC9 - Edit a Wedding** + +1. Wedding Organizer edits a contact using the edit command. +2. Contact details are updated. +Use case ends. + +**Extensions:** + +- **1a.** Contact details is not valid + - **1a1.** Error message displayed in result display. + - Use case ends. +- **1b.** Contact index is not in the displayed list. + - **1b1.** Same as 1a1. + +#### **Use Case: UC10 - Assign a contact to a Wedding** + +**MSS:** + +1. Wedding Organizer assigns the person (by name or index) to the wedding (by index) using the assign command. + Use case ends. + +**Extensions:** +- **1a.** Contact name does not exist in the address book + - **1a1.** Error message displayed in result display. + - Use case ends. +- **1b.** Contact/Wedding index is not in the displayed list + - **1b1.** Error message displayed in result display. + - Use case ends. +- **1c.** Duplicate contacts with name containing inputted name + - **1c1.** Bridal Boss filters the person list to show contacts containing inputted name. + - **1c2.** Use case resumes from step 1. +- **1d.** Contact is already assigned to the wedding + - **1d1.** Error message displayed in result display. + - Use case ends. +- **1e.** Contact is the client of the wedding + - **1e1.** Error message displayed in result display. + - Use case ends. + +#### **Use Case: UC11 - Delete a contact from a Wedding** + +**MSS:** -### Product scope +1. Wedding Organizer unassigns the person (by name or index) from the wedding (by index) using the delete command. + Use case ends. + +**Extensions:** +- **1a.** Contact is already not assigned to the wedding + - **1a1.** Error message displayed in result display. + - Use case ends. + +- Same as the Extensions from UC10 - Assign a contact to a Wedding excluding 1d. -**Target user profile**: +--- +## **Appendix: Planned Enhancements** -* 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 +Our team size is 5 which allows for **10** planned enhancements. -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +1. **Accept Special Characters in Names**: + - Extend current name field validations to support special characters, such as periods, slashes (s/o, d/o), and other symbols found in legal names. + - This enhancement will allow for more accurate and comprehensive name entries +2. **Restrict Wedding Date to Five Years into the Future**: + - Add a validation to ensure that the wedding date entered is within five years of the current date + - This will help maintain realistic and relevant wedding planning data -### User stories +3. **Allow Deletion of Wedding Date and Venue**: + - Expand functionality of edit wedding feature to delete individual wedding date and associated venue information + - This will allow users to remove outdated or irrelevant wedding details from the system -Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` +4. **Prevent Double-Booking of Vendors**: + - Enhance the assign wedding feature to check for and prevent vendors from being assigned to multiple weddings on the same date + - Display an appropriate error message if a user attempts to book a vendor who is already assigned to another wedding on the same date -| 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 | +5. **Provide Vendor Conflict Information in Error Messages**: + - When a vendor is already assigned to another wedding, and users attempt to assign them to that same wedding, + include the details of the conflicting wedding in the error message + - This will help users quickly identify the source of the conflict and take appropriate action -*{More to be added}* +6. **Ensure Full Display of Long Fields in the GUI**: + - Optimize the GUI to display the full contents of long fields (more than 50 characters) without truncation + - Implement responsive design or scrolling mechanisms to ensure all relevant information is visible to the user -### Use cases +7. **Improve Error Messages for Large Number Inputs**: + - Enhance the error messaging displayed when a user enters a number that is too large for the system to handle + - Provide clear and actionable guidance to the user on the appropriate range of values -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +8. **Allow Users to "Unclick" Fields in the GUI**: + - Add the ability for users to "unclick" or deselect fields in the GUI after they have been clicked + - This will improve the user experience and provide more flexibility when interacting with the application -**Use case: Delete a person** +9. **Ensure GUI Responsiveness to Window Size Adjustments**: + - Optimize the GUI to properly display wedding details and information regardless of the window size + - Implement responsive design techniques to ensure the application remains usable and visually appealing when the window size is adjusted -**MSS** +## **Appendix: Requirements** -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 +### Non-Functional Requirements - Use case ends. +1. **Performance Requirements:** + - **Response Time:** The system should respond to user commands within **1 second** under normal operating conditions (e.g., managing up to **50 weddings** and **500 contacts**). This ensures the application remains efficient for fast typists and small to medium-sized wedding organizers. + - **Startup Time:** The system should start up and be fully usable within **5 seconds** on modern machines (e.g., with at least 4GB RAM), ensuring quick access without reliance on any installer or external dependencies. -**Extensions** +2. **Portability:** + - The system must work cross-platform on **Windows 10/11** and **macOS 10.15 (Catalina)** or later, running **Java 17** and above. It should avoid the use of platform-specific libraries or features, ensuring it can be used by a wide range of users across different environments. + - The product must be packaged as a single **JAR file** to comply with the single-file packaging constraint, avoiding external dependencies that could complicate installation or distribution. -* 2a. The list is empty. +3. **Usability:** + - The system should offer a **CLI-first experience** optimized for fast typists, with a user interface that caters to wedding organizers who prefer text-based commands. + - The GUI should primarily provide feedback and display information, while command-based input remains the primary interaction method. - Use case ends. +4. **Data Storage:** + - All data should be stored locally in a human-readable format (e.g., JSON), allowing users to back up and edit data manually if necessary. + - The system should not require a database management system (DBMS), ensuring ease of setup and maintenance. -* 3a. The given index is invalid. +5. **Reliability and Fault Tolerance:** + - The system should have robust error handling that gives users clear feedback when errors occur. + - In case of errors, the system should allow users to retry operations or resolve issues without losing data. - * 3a1. AddressBook shows an error message. +6. **Maintainability:** + - The codebase should follow **Object-Oriented Programming (OOP) principles** and be modular to facilitate future maintenance and extension. + - Each component should be well-documented and follow best practices to ensure maintainability. + - Incremental updates are recommended to align with the **Incremental Delivery** approach, ensuring that the product evolves gradually. - Use case resumes at step 2. +7. **Testability:** + - The system should include unit and integration tests to ensure code quality. + - Manual testing should be straightforward, with clear feedback provided to testers. Detailed instructions for manual testing should be provided in the developer documentation. -*{More to be added}* +8. **Documentation:** + - Both **User Guide** and **Developer Guide** must be provided. + - The User Guide should explain how to interact with the CLI and manage wedding data. + - The Developer Guide should detail system architecture, design decisions, and instructions for extending the application. -### Non-Functional Requirements +9. **Compliance:** + - The system must operate entirely offline without dependence on a remote server, ensuring data privacy and usability without internet access. + - Legal compliance should be considered regarding data storage and user privacy, but given the local nature of data storage, extensive data security measures are not required. -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. +10. **Graphical Interface:** + - The GUI should be responsive and display correctly on common screen resolutions, including when the application window is maximized. + - The interface should be usable on screens with resolutions of **1280x720** and higher. -*{More to be added}* +--- ### Glossary -* **Mainstream OS**: Windows, Linux, Unix, MacOS -* **Private contact detail**: A contact detail that is not meant to be shared with others +- **Wedding Organizer:** A professional responsible for planning and managing wedding events, coordinating with clients and vendors. +- **Client:** The individuals who have hired the wedding organizer, typically the bride and groom. +- **Client Preferences:** Specific requirements and preferences of the clients for their wedding event eg: vendors in wedding, date and venue. +- **Vendor:** A service provider involved in the wedding event, such as caterers, photographers, florists, etc. +- **Stakeholders:** All parties involved in the wedding event, including clients, vendors, and participants. +- **Role:** A role assigned to contacts to categorize and filter them (e.g., "Florist", "Photographer"). +- **Contact:** An entry in the system containing information about a person or vendor. -------------------------------------------------------------------------------------------------------------------- @@ -334,10 +643,13 @@ 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 @@ -345,7 +657,8 @@ testers are expected to do more *exploratory* testing. 1. Download the jar file and copy into an empty folder - 1. Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + 1. Double-click the jar file
+ Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. 1. Saving window preferences @@ -354,29 +667,541 @@ 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 …​ }_ +### Editing a person + +Success action: Details of edited contact shown in the status message, person in person list is edited. + +#### Editing by INDEX + +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`
+ Test case: `edit 1`
+ Expected: No person is edited. `edit` command format is shown in the status message. + + 1. Test case: `edit 0 [n/NEW NAME] [p/NEW PHONE] [e/NEW EMAIL] [a/NEW ADDRESS]`
+ Test case: `edit x [n/NEW NAME] [p/NEW PHONE] [e/NEW EMAIL] [a/NEW ADDRESS]` (where x is a negative number)
+ Expected: No wedding added. `edit` command format is shown in the status message. + + 1. Test case: `edit x [n/NEW NAME] [p/NEW PHONE] [e/NEW EMAIL] [a/NEW ADDRESS]` (where x is larger than the size of the wedding list)
+ Expected: No wedding added. Error message prompting the user to choose an index within the range shown. + + 1. Test case: `edit 1 n/NEW NAME`
+ Expected (Valid Name): First person has name field edited to NEW NAME. Success action will be carried out.
+ Expected (Invalid Name): No person is edited. Error message with name restrictions shown in status message. + + 1. Test case: `edit 1 p/NEW PHONE`
+ Expected (Valid Phone): First contact has phone field edited to NEW PHONE. Success action will be carried out.
+ Expected (Invalid Phone): No person is edited. Error message with phone restrictions shown in status message. + + 1. Test case: `edit 1 e/NEW EMAIL`
+ Expected (Valid Email): First contact has email field edited to NEW EMAIL. Success action will be carried out.
+ Expected (Invalid Email): No person is edited. Error message with email restrictions shown in status message. + + 1. Test case: `edit 1 a/NEW ADDRESS`
+ Expected (Valid Address): First contact has address field edited to NEW ADDRESS. Success action will be carried out.
+ Expected (Invalid Address): No person is edited. Error message with address restrictions shown in status message. + + 1. Test case: `edit 1 n/EXISTING NAME p/EXISTING PHONE e/EXISTING EMAIL a/EXISTING ADDRESS`
+ Expected: No person is edited. Error message shows person already exist in status message. + + 1. Test case: `edit 1 p/EXISTING PHONE`
+ Expected: No person is edited. Error message shows phone already exist in status message. + + 1. Test case: `edit 1 e/EXISTING EMAIL`
+ Expected: No person is edited. Error message shows email already exist in status message. + +#### Editing by NAME + +1. Since `edit NAME ...` searches from the entire list of contacts, rather than only the partial list, it works either way. + + 1. Test case: `edit Alice n/Alice Teo`
+ Expected (No duplicated Alice): Person with name field containing Alice has name field edited to Alice Teo. Success action will be carried out.
+ Expected (Duplicated Alice): No person edited. Person list is filtered to show only contacts with name field containing Alice. Status message shows message to input person by indexing.
+ Expected (No Alice): No person edited. Error message shows this person do not exist in the address book. + + +### Viewing a person + +Success action: When a person is successfully viewed, the details of the viewed contact is shown in the person list. Status message shows that that contact is viewed. +The weddings involved of the person will be reflected in the wedding list on the right. + +#### Viewing with INDEX + +1. Viewing 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: `view 1`
+ Expected: First contact from the list is viewed. Success action will be carried out for that contact. + + 1. Test case: `view 0`
+ Expected: No person is viewed. `view` command format is shown in the status message. + + 1. Test case: `view x` (where x is larger than the size of person list)
+ Expected: Error message prompting the user to choose an index within the range shown. + + 1. Other incorrect view commands to try: `view`, `view x` (where x is a negative integer)
+ Expected: Similar to point #1(iii). + +1. Viewing a person while a partial list of contacts is shown. + + 1. Prerequisite: List some of the contacts using `view NAME` or `filter` command, where a partial list of contacts that matches NAME will be shown, assuming there are multiple of such contacts. + + 1. Test cases used are similar, except that the index will follow that of the shown list. + +#### Viewing with NAME + +1. Since `view NAME` searches from the entire list of contacts, rather than only the partial list, it works either way. + + 1. Test case: `view Alice Tan`
+ Expected (Unique Alice Tan): The contact of `Alice Tan` is viewed. Success action will be carried out for that contact.
+ Expected (Duplicated Alice Tan): Contacts with name field containing `Alice Tan` exactly will be shown. Status message shows number of contacts shown and prompts user to re-input using index according to the newly filtered list to specify which `Alice Tan` they want to view.
+ Expected (No Alice Tan): No person is viewed. Error details is shown in the status message, as the NAME does not belong to anyone in the address book. + ### Deleting a person +#### Deleting with INDEX + 1. Deleting 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: `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 contact is deleted from the list. Details of the deleted contact 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. + Expected: No person is deleted. `delete` command format is shown in the status message. + + 1. Test case: `delete x` (where x is larger than the size of person list)
+ Expected: Error message prompting the user to choose an index within the range shown. + + 1. Other incorrect delete commands to try: `delete`, `delete x` (where x is a negative integer)
+ Expected: Similar to point #1(iii). + +1. Deleting a person while a partial list of contacts is shown. + + 1. Prerequisite: List some of the contacts using `view NAME`, `delete NAME` or `filter` command, where a partial list of contacts that matches NAME will be shown, assuming there are multiple of such contacts. + + 1. Test cases used are similar, except that the index will follow that of the shown list. + +#### Deleting with NAME + +1. Since `delete NAME` searches from the entire list of contacts, rather than only the partial list, it works either way. + + 1. Test case: `delete Alice Tan`
+ Expected (Unique Alice Tan): The contact of `Alice Tan` will be deleted. Details of the deleted contact shown in the status message.
+ Expected (Duplicated Alice Tan): Contacts with name field containing `Alice Tan` exactly will be shown. Status message prompts user to re-input using index according to the newly filtered list to specify which `Alice Tan` they want to delete.
+ Expected (No Alice Tan): No person is deleted. Error details is shown in the status message, as the NAME does not belong to anyone in the address book. + + +### Filtering persons + +1. Filtering while all persons are being shown + + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + + 2. Test case: `filter n/John`
+ Expected: All the contacts with the exact name match "John" (case-insensitive matching) are shown. + + 3. Test case: `filter r/vendor e/gmail`
+ Expected: All the contacts who have the role "vendor" OR have "gmail" in their email are shown. + + 4. Test case: `filter n/Alex Tan`
+ Expected: Error message is shown as the name field must be a single word. Error details shown in the status message. + +2. Invalid filter commands to try: + + 1. Test case: `filter`
+ Expected: `filter` command format is shown in the status message, since at least one filter criteria must be provided. + + 2. Test case: `filter n/`
+ Expected: `filter` command format is shown in the status message, since parameter cannot be left empty. + + 3. Test case: `filter x/value`
+ Expected: `filter` command format is shown in the status message, since there is an unknown prefix. + +3. Edge cases to test: + + 1. Test case: `filter n/john`
+ Expected: The names of contacts will be matched and shown regardless of case (e.g., "John", "JOHN", "JoHn"). + + 2. Test case: `filter e/gmail a/street`
+ Expected: All the contacts with either "gmail" in email OR "street" in address will be shown. + + 3. Test case: `filter p/91234567`
+ Expected: Only the contacts with the exact phone number will be shown. + +4. Filtering a person while a filtered list of contacts is shown + + 1. Prerequisite: A partial list of contacts is shown. + + 1. Test cases used can be the same since `filter` searches from the entire list of contacts, rather than only the partial list. + +### Adding a Wedding +Success action: When wedding is successfully added, the details of the added wedding is shown in the status message and reflected in the wedding list. + +#### Inputting CLIENT using INDEX +1. Adding a wedding while all persons and weddings are shown. + + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + + 1. Test case: `addw n/Church Wedding c/1 d/2024-12-12 v/Church of the Holy Spirit`
+ Expected: Wedding added with first person in the persons list set as client, with given date and venue. Success action will be carried out. + + 1. Test case: `addw n/Church Wedding c/1`
+ Expected: Wedding added with first person in the persons list set as client, with no date and venue. Success action will be carried out. + + 1. Test case: `addw`
+ Test case: `addw n/Church Wedding`
+ Test case: `addw c/1`
+ Expected: No wedding added. `addw` command format is shown in the status message. + + 1. Test case: `addw n/Wedding 1 c/1`
+ Expected: No wedding added. Error message with restrictions on WEDDING_NAME shown in status message. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
- Expected: Similar to previous. + 1. Test case: `addw n/Wedding c/1.5`
+ Expected: No wedding added. Error message with CLIENT input options shown in status message. + + 1. Test case: `addw n/Wedding c/1 d/2024-13-50`
+ Expected: No wedding added. Error message with restrictions on DATE shown in status message. + + 1. Test case: `addw n/Wedding c/1 v/`
+ Expected: No wedding added. Error message with restrictions on VENUE shown in status message. + + 1. Test case: `addw n/Church Wedding c/0`
+ Test case: `addw n/Church Wedding c/x` (where x is a negative number)
+ Expected: No wedding added. Error message about invalid index shown, as x is not a non-zero unsigned integer. + + 1. Test case: `addw n/Church Wedding c/x` (where x is larger than the size of the wedding list)
+ Expected: No wedding added. Error message prompting the user to choose an index within the range shown. + +#### Inputting CLIENT using NAME +1. Since `addw` using `c/NAME` searches for the client from the entire list of contacts, rather than only the partial list, it is works either way. + + 1. Test case: `addw n/Church Wedding c/Alice`
+ Expected (No duplicated Alice): Wedding added with contact having name field containing Alice set to be client. Details of the added wedding is displayed on the status message.
+ Expected (Duplicated Alice): No wedding added. Person list is filtered to show contacts with names containing Alice. Status message prompts user to re-input CLIENT using index according to the newly filtered list.
+ Expected (No Alice): No wedding added. Error message shown in status message, as the NAME does not belong to anyone in the address book. + + +### Editing a Wedding +Success action: When wedding is successfully edited, the details of the updated wedding is shown in the status message and reflected in the wedding list. + +1. Editing a Wedding with all persons and weddings shown.
+ + 1. Prerequisites: List all contacts and weddings using the `list` command. Multiple persons in the list. + + 1. Test case: `editw w/1 [n/NAME] [d/DATE] [v/VENUE]`
+ Expected: First wedding in list is edited with the given inputs. Success action will be carried out. + + 1. Test case: `editw`
+ Test case: `editw w/1`
+ Test case: `editw w/1 c/1`
+ Expected: No wedding edited. `editw` command format is shown in the status message. + + 1. Test case: `editw w/0 [n/NEW WEDDING NAME] [d/NEW DATE] [v/NEW VENUE]`
+ Test case: `editw w/x [n/NEW WEDDING NAME] [d/NEW DATE] [v/NEW VENUE]` (where x is a negative number)
+ Expected: No wedding edited. `editw` command format is shown in the status message. + + 1. Test case: `addw w/x [n/NEW WEDDING NAME] [d/NEW DATE] [v/NEW VENUE]` (where x is outside of the range of wedding list or a non-numeric character)
+ Expected: No wedding edited. Error message prompting the user to choose an index within the range shown. + + +### Viewing weddings + +Success action: When wedding is successfully viewed, the details of the viewed wedding is shown in the status message and reflected in wedding list. +The persons involved in the viewed wedding will be shown in the person list. + +#### Viewing weddings using INDEX + +1. Viewing of a wedding while all weddings are being shown + + 1. Prerequisites: List all weddings using the `list` command. Multiple weddings in the list. + + 1. Test case: `vieww 1`
+ Expected: First wedding from the wedding list is viewed. Success action will be carried out for that wedding. + + 1. Test case: `vieww 0`
+ Expected: No wedding is viewed. `vieww` command format is shown in the status message. + + 1. Test case: `vieww x` (where x is larger than the size of wedding list)
+ Expected: Error message prompting the user to choose an index within the range shown. + + 1. Other incorrect vieww commands to try: `vieww`, `vieww x` (where x is a negative integer)
+ Expected: Similar to point #1(iii). + +1. Viewing of a wedding while a partial list of weddings is shown. + + 1. Prerequisite: List some of the weddings using `vieww NAME` command, where a partial list of weddings that matches NAME will be shown, assuming there are multiple of such weddings. + + 1. Test cases used are similar, except that the index will follow that of the shown list. + +#### Viewing weddings using WEDDING_NAME + +1. Since `vieww WEDDING_NAME` searches from the entire list of contacts, rather than only the partial list, it works either way. + + 1. Test case: `vieww Alice`
+ Expected (Unique Alice): The wedding of `Alice` will be shown. Success action will be carried out for that wedding.
+ Expected (Duplicated Alice): Weddings with name field matching `Alice` exactly will be shown. Status message prompts user to re-input using index according to the newly filtered list to specify which wedding of `Alice` they want to view.
+ Expected (No Alice): No wedding is viewed. Error details is shown in the status message, as the NAME does not belong to any wedding in the address book. + + +### Deleting weddings + +Success action: When a wedding is successfully deleted, the details of the deleted wedding is shown in the status message. The client of the wedding will have their wedding status reset. +Persons who are involved in the wedding will also be unassigned. + +- To verify this: view the contact itself using `view` command, which will show the weddings the person is involved in. The deleted wedding should not be included. + +#### Deleting wedding using INDEX + +1. Deleting a wedding while all weddings are being shown + + 1. Prerequisites: List all weddings using the `list` command. Multiple weddings in the list. + + 1. Test case: `deletew 1`
+ Expected: First wedding is deleted from the list. Success action will be carried out for that wedding. + + 1. Test case: `deletew 0`
+ Expected: No wedding is deleted. `deletew` command format is shown in the status message. + + 1. Test case: `deletew x` (where x is larger than the size of wedding list)
+ Expected: Error message prompting the user to choose an index within the range shown. + + 1. Other incorrect deletew commands to try: `deletew`, `deletew x` (where x is a negative integer)
+ Expected: Similar to point #1(iii). + +1. Deleting of a wedding while a partial list of weddings is shown. + + 1. Prerequisite: List some of the weddings using `vieww NAME` or `deletew NAME` command, where a partial list of weddings that matches NAME will be shown, assuming there are multiple of such weddings. + + 1. Test cases used are similar, except that the index will follow that of the shown list. + +#### Deleting wedding using WEDDING_NAME + +1. Since `deletew WEDDING_NAME` searches from the entire list of contacts, rather than only the partial list, it works either way. + + 1. Test case: `deletew Alice`
+ Expected (Unique Alice): The wedding of `Alice` will be deleted. Success action will be carried out for that wedding.
+ Expected (Duplicated Alice): Weddings with name field containing `Alice` exactly will be shown. Status message prompts user to re-input using index according to the newly filtered list to specify which wedding of `Alice` they want to delete.
+ Expected (No Alice): No wedding is deleted. Error details is shown in the status message, as the NAME does not belong to any wedding in the address book. + Expected: No wedding added. Error message states that the index is invalid, and prompts user to key indexes from within a specified range.
+ +### Assigning roles and weddings + +Success action: When a person is successfully assigned: +- If role assigned: Person's role is updated and shown in person list +- If wedding assigned: Person is added to wedding's participants list +- Status message shows successful assignment + +#### Assigning by INDEX + +1. Assigning a role + + 1. Prerequisites: List all persons using `list` command. Multiple persons in the list. + + 1. Test case: `assign 1 r/photographer`
+ Expected: First person in list is assigned photographer role. Success action carried out. + + 1. Test case: `assign 1 r/invalid role name`
+ Expected: Error message shown about invalid role format (must be one word). + + 1. Test case: `assign 0 r/photographer`
+ Expected: Error message shown about invalid person index. + + 1. Test case: `assign x r/photographer` (where x is an invalid index (< 0, 0 , larger than list size)))
+ Expected: Error message shown about invalid person index. + + 1. Test case: `assign 1 r/`
+ Expected: Role of first person in list is removed. Success action carried out + +2. Assigning to weddings + + 1. Prerequisites: Multiple weddings exist in wedding list. + + 1. Test case: `assign 1 w/1 w/2`
+ Expected: First person assigned to both wedding 1 and 2. Success action carried out. + + 1. Test case: `assign 1 w/0 w/2`
+ Expected: Error message shown about invalid wedding index. + + 1. Test case: `assign 1 w/-1 w/2`
+ Expected: Error message shown about invalid wedding index. + + 1. Test case: `assign 1 w/999 w/2` (where 999 > wedding list size)
+ Expected: Error message shown about invalid wedding index. + +3. Assigning both role and weddings + + 1. Test case: `assign 1 r/photographer w/1 w/2`
+ Expected: First person gets photographer role and is assigned to weddings 1 and 2. Success action carried out. + +4. Invalid commands + + 1. Test case: `assign`
+ Expected: Error message showing command format. + + 1. Test case: `assign 1`
+ Expected: Error message showing need for role and/or wedding assignment. + + 1. Test case: `assign -1 r/photographer`
+ Expected: Error message about invalid person index. + +#### Assigning by NAME + +1. Using contact name + + 1. Test case: `assign Alice r/photographer`
+ Expected (Unique Alice): Alice is assigned photographer role. Success action carried out.
+ Expected (Multiple matches): Person list filtered to show matches. Message prompts to use index.
+ Expected (No matches): Error message that no such person exists. + + 1. Test case: `assign Al!ce r/photographer` (name with special characters)
+ Expected: Error message that no such person exists. + + 1. Test case: `assign Alice r/`
+ Expected (Unique Alice): Removes role from Alice. Success action carried out.
+ Expected (Multiple matches): Person list filtered to show matches. Message prompts to use index.
+ Expected (No matches): Error message that no such person exists. + +2. Complex name assignments + + 1. Test case: `assign Alice r/photographer w/1 w/2`
+ Expected (Unique Alice): Alice gets role and both wedding assignments. Success action carried out.
+ Expected (Multiple matches): Person list filtered to show matches. Message prompts to use index. + +#### Special Cases + +1. Client assignments + + 1. Prerequisites: Person is client of wedding 1 + + 1. Test case: `assign 1 w/1`
+ Expected: Error message that client cannot be assigned to their own wedding. + +2. Multiple wedding assignments + + 1. Prerequisites: Person already assigned to wedding 1 + + 1. Test case: `assign 1 w/1 w/2`
+ Expected: Error message about already being assigned to wedding 1. + +### Removing wedding jobs assigned to a person + +#### Removing with INDEX of person list and wedding list + +1. Removing wedding jobs assigned to a person while all weddings are being shown + + 1. Prerequisites: List all persons and weddings using the `list` command. Multiple persons and weddings in the list.
+ + 1. Test case: `delete 1 w/1`
+ Expected: First wedding is unassigned from first person. Details of the unassigned contact shown in the status message.
+ + 1. Test case: `delete 0 w/1`
+ Expected: No person to unassign wedding job. `delete` command format is shown in the status message.
+ + 1. Test case: `delete 1 w/x` (where x is outside the range of wedding list or a non-numeric character)
+ Expected: Error message prompting the user to choose an index within the range shown.
+ +1. Removing wedding jobs assigned to a person while a partial list of weddings is shown. + + 1. Prerequisite: List some of the weddings using `vieww NAME`, where a partial list of weddings that matches NAME will be shown, assuming there are multiple of such weddings. + + 1. Test cases used are similar, except that the index will follow that of the shown list. + +#### Removing with NAME of person and INDEX of wedding list + +1. Since `delete NAME w/1` searches from the entire list of contacts, rather than only the partial list, it works either way. + + 1. Test case: `delete Alice Tan w/1`
+ Expected (Unique Alice Tan): The contact of `Alice Tan` will be unassigned from wedding at index 1. Details of the unassigned contact shown in the status message.
+ Expected (Duplicated Alice Tan): Contacts with name field containing `Alice Tan` exactly will be shown. Status message prompts user to re-input using index according to the newly filtered list to specify which `Alice Tan` they want to unassign.
+ Expected (No Alice Tan): No person to unassign. Error details is shown in the status message, as the NAME does not belong to anyone in the address book. -1. _{ more test cases …​ }_ ### Saving data 1. Dealing with missing/corrupted data files - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ - -1. _{ more test cases …​ }_ + 1. Prerequisites: Have the app running with some sample data saved + + 2. Test case: Delete addressbook.json + * Steps: + 1. Navigate to data folder (default: `[App Home]/data/`) + 2. Delete `addressbook.json` + 3. Restart the application + * Expected: + - App creates new `addressbook.json` with empty data + - Creata a new data file data/addressbook.json populated with a sample AddressBook. + + 3. Test case: Corrupt addressbook.json with invalid JSON + * Steps: + 1. Navigate to data folder + 2. Open `addressbook.json` + 3. Add random text or remove brackets to make it invalid JSON + 4. Save file and restart application + * Expected: + - Creates new `addressbook.json` with empty data + - Status bar shows "Corrupted data found. Starting with empty address book" + - List shows no persons or weddings + + 4. Test case: Corrupt addressbook.json with valid JSON but invalid data + * Steps: + 1. Navigate to data folder + 2. Open `addressbook.json` + 3. Modify data while keeping valid JSON structure (e.g., remove required fields, change data types) + 4. Save file and restart application + * Expected: + - Creates new `addressbook.json` with empty data + - Status bar shows "Corrupted data found. Starting with empty address book" + - List shows no persons or weddings + +2. Testing data persistence + + 1. Test case: Normal shutdown + * Steps: + 1. Modify data (add/edit/delete person or wedding) + 2. Exit application through UI + 3. Restart application + * Expected: All modifications are preserved + + 2. Test case: Forced shutdown + * Steps: + 1. Modify data + 2. Force close application (Task Manager/Force Quit) + 3. Restart application + * Expected: + - Last saved state (address book after last valid command executed) is recovered. + - Any unsaved modifications are lost + - No data corruption + +3. Testing data integrity + + 1. Test case: Wedding-Person relationship integrity + * Steps: + 1. Open `addressbook.json` + 2. Manually modify wedding references (change hashcodes) + 3. Save file and restart application + * Expected: + - Changing ownWedding field of a person to 0 will only result in wedding having no client and would not corrupt the file. + - App detects corrupted relationships + - Shows error message: "Address book data is corrupted: Found weddings without corresponding clients" + - Starts with empty address book + + 2. Test case: Client-Wedding integrity + * Steps: + 1. Open `addressbook.json` + 2. Manually remove client data while keeping wedding reference + 3. Save file and restart application + * Expected: + - App detects missing client data + - Shows error message about corrupted data + - Starts with empty address book + +## Acknowledgements +- GitHub copilot was used to generate some parts of the code. +- Generative AI was used for writing some unit tests, regex validation, generation of javadoc and code completion. diff --git a/docs/Documentation.md b/docs/Documentation.md index 3e68ea364e7..aee62ebd2b5 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..98709267a14 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 9f832a19674..00c1a8c4074 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -1,27 +1,33 @@ --- -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 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** diff --git a/docs/Testing.md b/docs/Testing.md index 8a99e82438a..9733f183fa8 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. + -------------------------------------------------------------------------------------------------------------------- diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 84b4ddc4e40..9439b008d12 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,12 +1,17 @@ --- -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. +# Bridal Boss User Guide -* Table of Contents -{:toc} +**Bridal Boss** is a desktop app designed for small to medium-scale **wedding planners to streamline contact management**. +It is 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, Bridal Boss will help you manage contacts faster than traditional GUI apps, combining speed with functionality to enhance your wedding planning experience. + + + -------------------------------------------------------------------------------------------------------------------- @@ -14,22 +19,23 @@ AddressBook Level 3 (AB3) is a **desktop app for managing contacts, optimized fo 1. Ensure you have Java `17` or above installed in your Computer. -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/AY2425S1-CS2103T-T11-3/tp/releases). 1. Copy the file to the folder you want to use as the _home folder_ for your AddressBook. -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 BridalBoss.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) + Ui +1. At the top is the command box for users to enter their commands. The column on the left contains the list of client and vendor contacts while the right column contains a list of weddings.
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 p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John` to Bridal Boss. - * `delete 3` : Deletes the 3rd contact shown in the current list. + * `view 3` : Views the 3rd contact shown in the current list. * `clear` : Deletes all contacts. @@ -37,145 +43,760 @@ 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. +[↥ Back to Top](#bridal-boss-user-guide) + -------------------------------------------------------------------------------------------------------------------- -## Features +## Common Scenarios + +### Understanding the Address Book Structure + +Bridal Boss manages two main types of entries: +1. **Contacts List**: Shows all people in your address book + - Clients (people who have their own wedding) + - Vendors (people who can be assigned to weddings) + +2. **Weddings List**: Shows all weddings with their: + - Wedding name + - Client + - Date (if specified) + - Venue (if specified) + - Assigned vendors + +Here are some common scenarios to help you understand how to use Bridal Boss: +#### 1. Setting Up a New Client's Wedding +1. First, add the client as a contact: + `add n/Sarah Chen p/91234567 e/sarah@example.com a/123 Orchard Road` + [↗ See add command details](#adding-a-person-add) + +2. Create their wedding: + `addw n/Sarah's Garden Wedding c/Sarah Chen d/2024-12-25 v/Botanical Gardens` + [↗ See addw command details](#adding-a-wedding-addw) + +3. View the created wedding: + `vieww Sarah` + [↗ See vieww command details](#viewing-wedding-details-vieww) + +#### 2. Managing Vendors for a Wedding +1. Add vendors as contacts:
+ `add n/John Doe p/91234567 e/john@photo.com a/456 River Valley r/photographer`
+ `add n/Mary Tan p/82345678 e/mary@flowers.com a/789 Garden Road r/florist` + +2. Assign them to a wedding (use `list` first to see indices):
+ `assign John Doe w/1`
+ `assign Mary Tan w/1`
+ [↗ See assign command details](#assigning-a-person-assign) + +3. Check wedding assignments: + `vieww 1` + +#### 3. Making Changes to Existing Entries +1. Update contact details:
+ `edit John Doe p/91234599 # Update phone number`
+ `edit Mary Tan a/100 New Road # Update address`
+ [↗ See edit command details](#editing-a-person-edit) + +2. Modify wedding details:
+ `editw w/1 d/2024-12-31 # Change date`
+ `editw w/1 v/New Venue # Change venue`
+ [↗ See editw command details](#editing-a-wedding-editw) + +#### 4. Finding and Filtering Contacts +1. Search by name (exact name match): + `find john # Finds "John" or "John Doe"` + [↗ See find command details](#finding-persons-by-name-find) + +2. Filter by multiple criteria:
+ `filter r/photographer # All photographers`
+ `filter n/John r/photographer # Johns or photographers`
+ [↗ See filter command details](#filtering-persons-filter) + +### Common Mistakes to Avoid -
+1. ❌ Creating a wedding without adding the client first + `addw n/Beach Wedding c/John Doe # Fails if John isn't a contact` -**:information_source: Notes about the command format:**
+ ✅ Correct approach:
+ `add n/John Doe p/91234567 e/john@example.com a/123 Main St`
+ `addw n/Beach Wedding c/John Doe` -* 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`. +2. ❌ Trying to delete a client who has a wedding + `delete John # Fails if John has an active wedding` -* 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`. + ✅ Correct approach:
+ `deletew 1 # Delete the wedding first`
+ `delete John # Then delete the contact` -* 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. +3. ❌ Assigning a client to their own wedding as vendor + `assign Sarah Chen w/1 # Fails if wedding 1 is Sarah's wedding` -* 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. + ✅ Correct approach: + - Clients cannot be vendors in their own wedding + - Create a new contact if the same person is both client and vendor -* 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`. +### Quick Tips +- Use `list` frequently to see updated indices +- For name-based commands, use index if you get multiple matches +- Check [validation rules](#validation-rules) when adding/editing entries +- Check [general command format](#general-command-format) for command structure +- Check [general command details](#general-command-details) for detailed command usage +- View the [command summary](#command-summary) for quick reference -* 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. -
+[↗ See more tips in FAQ section](#faq) -### Viewing help : `help` +[↥ Back to Top](#bridal-boss-user-guide) -Shows a message explaning how to access the help page. +--- -![help message](images/helpMessage.png) +## Features -Format: `help` +### General Command Details +Below are the detailed descriptions of each command, including examples, error messages, and important notes. -### Adding a person: `add` +#### Viewing Help: `help` -Adds a person to the address book. +Displays instructions on how to access the help page. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +- **Format**: `help` -
:bulb: **Tip:** -A person can have any number of tags (including 0) -
+--- -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` +#### Adding a Person: `add` -### Listing all persons : `list` +Adds a new person to the address book. -Shows a list of all persons in the address book. +- **Format**: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [r/ROLE] [w/WEDDING_INDEX]...` +- **Notes**: + - All fields except `[r/ROLE]` and `[w/WEDDING_INDEX]` are required. + - A person can have either 0 or 1 role. + - A person can be assigned to 0 or multiple weddings. +- **Validation**: + - Ensure all fields meet the criteria specified in the [Validation Rules](#validation-rules). +- **Examples**: + - `add n/John Doe p/98765432 e/johnd@example.com a/123 Street` + - `add n/Betsy Crowe p/91234567 e/betsycrowe@example.com a/Tanglin Mall r/florist` + - `add n/Betsy Crowe p/91234567 e/betsycrowe@example.com a/Tanglin Mall w/1 w/2` (assuming wedding list has at least 2 weddings) -Format: `list` +--- -### Editing a person : `edit` +#### Listing All Persons: `list` -Edits an existing person in the address book. +Displays a list of all persons and weddings in the address book. -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +- **Format**: `list` -* 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, …​ -* 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. +--- -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. +#### Editing a Person: `edit` + +Edits the details of an existing person. + +- **Formats**: + - **By Index**: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS]` + - **By Name**: `edit NAME [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS]` +- **Notes**: + - At least one of the optional fields must be provided. + - Existing values will be updated to the new values. + - If multiple matches are found when using name-based format, the system will prompt for index. +- **Examples**: + - `edit 1 p/91234567 e/johndoe@example.com` + - Edits the phone number and email of the person at index 1. + - `edit John Doe n/John Smith` + - Changes the name of `John Doe` to `John Smith`. + +--- -### Locating persons by name: `find` +#### Finding Persons by Name: `find` Finds persons whose names contain any of the given keywords. -Format: `find KEYWORD [MORE_KEYWORDS]` +- **Format**: `find KEYWORD [MORE_KEYWORDS]` +- **Notes**: + - Case-insensitive search. + - Order of keywords does not matter. + - Only the name is searched. + - Only full words will be matched. + - Returns persons matching at least one keyword (logical `OR` search). +- **Examples**: + - `find John` returns `John` and `John Doe`. + - `find alex david` returns `Alex Yeoh`, `David Li`. -* 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). - e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +--- + +#### Viewing a Contact: `view` + +Displays detailed information about a specified person. + +- **Formats**: + - By Index: `view INDEX` + - By Name: `view NAME` + - `NAME` will be divided into separate keywords, using spaces to distinguish each keyword +- **Notes**: + - Case-insensitive matching. + - Matches contacts containing ALL the keyword(s) (logical `AND` search). + - Wedding list is only updated when one unique person is found. + - When multiple matches are found, only the person list is updated. +- **Information Displayed**: + - Personal details (name, phone, email, address). + - Current role (if any). + - Own wedding (if the person is a client). + - Weddings where the person is assigned as a vendor (if any). + - Wedding person owns (if any) will have an ```Own Wedding``` label. +- **Examples**: + - `view Mike` displays details for `Mike`. + - `view Alex Yeo` displays details for `Alex Yeo`. +![View multiple weddings](images/view_mulitple_weddings_unfiltered.png)
+*Viewing a contact with multiple matches: contact details are shown and weddings remain unfiltered* + +--- -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` -### Deleting a person : `delete` +Deletes a specified person from the address book or removes wedding jobs assigned to specified person. -Deletes the specified person from the address book. +- **Formats**: + - **By Index**: `delete INDEX` + - **By Name**: `delete NAME` +- **Notes**: + - **Deleting a person**: + - If multiple matches are found when using name-based format, the system will prompt for index. + - Cannot delete a client who has an active wedding. + - Error: "Cannot delete this person as they are a client in a wedding. Please delete their wedding first." + - Deleting a person will remove them from their assigned weddings. -Format: `delete INDEX` +- **Examples**: + - `delete 2` deletes the person at index 2. + - `delete Betsy` deletes Betsy if there's only one match. -* Deletes 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, …​ +--- + +#### Removing Wedding Jobs Assigned to a Person: `delete` + +Removes wedding jobs assigned to specified person. + +- **Formats**: + - **By Index**: `delete INDEX [w/WEDDING_INDEX]...` + - **By Name**: `delete NAME [w/WEDDING_INDEX]...` +- **Notes**: + - **Removing wedding jobs assigned to a person**: + - If multiple matches are found when using name-based format, the system will prompt for index. + - Can remove multiple weddings jobs a person is assigned to. + - Wedding indices must be valid and must refer to weddings that the specified person is already assigned to. + - If no wedding indices are provided, the contact specified will be deleted. +- **Examples**: + - `delete 1 w/1` removes wedding job at index 1 that person at index 1 is assigned to. + - `delete Alice w/1 w/2` removes wedding jobs at index 1 and 2 assigned to Alice, if there's only one match to 'Alice'. -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. +--- -### Clearing all entries : `clear` +#### Clearing All Entries: `clear` Clears all entries from the address book. -Format: `clear` +- **Format**: `clear` +- **Warning**: + - This action cannot be undone. + +--- + +#### Filtering Persons: `filter` + +Filters and lists persons whose fields match the specified keywords. + +- **Format**: `filter [n/NAME] [r/ROLE] [e/EMAIL] [p/PHONE] [a/ADDRESS]` +- **Notes**: + - At least one field must be provided. + - Parameters can be in any order. + - Case-insensitive search. + - Returns persons matching any of the fields (logical `OR` search). +- **Field-Specific Matching**: + - **Name**: Allow partial matches. + - **Role**: Requires exact role match. + - **Email**: Requires substring email match. + - **Phone**: Requires exact number match. + - **Address**: Requires substring address match. + - **Validation**: + - Ensure all fields provided meet the criteria specified in the [Validation Rules](#validation-rules). +- **Examples**: + - `filter n/John` returns persons with names containing `John`. + - `filter n/Al Ye` returns persons with names containing both `Al` and `Ye`. + - `filter r/vendor` returns persons with role `vendor`. + - `filter e/john@gmail.com` returns persons with emails containing `john@gmail.com`. + - `filter p/91234567` returns the person with phone number `91234567`. + - `filter n/John r/vendor` returns persons with names containing `John` or with role `vendor`. + - `filter e/john@gmail.com a/Jurong` returns persons with emails containing "john@gmail.com" or address containing "Jurong".
+ +Visual Example: +![Multi-field filter results](images/filter_persons_by_multiple_fields.png)
+*`filter e/alex@gmail.com r/florist` Example of filtering results showing matched persons, weddings remain empty/unfiltered* + +[↥ Back to Top](#bridal-boss-user-guide) -### Exiting the program : `exit` +--- -Exits the program. +#### Wedding Management Commands -Format: `exit` +##### Adding a Wedding: `addw` -### Saving the data +Adds a new wedding to the address book. -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +- **Format**: `addw n/WEDDING_NAME c/CLIENT [d/DATE] [v/VENUE]` +- **Notes**: + - Wedding name (`n/`) and client (`c/`) are required. + - Client can be specified by index or name. + - If multiple matches are found when using name, the system will prompt for index. + - Names are considered matched if they contain the inputted words, either together or separate (for more than one word names) + - A client can have only one active wedding. +- **Validation**: + - Ensure all fields meet the criteria specified in the [Validation Rules](#validation-rules). +- **Examples**: + - `addw n/Beach Wedding c/1 d/2024-12-31 v/Sentosa Beach` + - Adds a wedding named "Beach Wedding" for the client at index 1. + - `addw n/Garden Wedding c/John Doe v/Botanical Gardens` + - Adds a wedding for "John Doe" if there's only one match.
-### Editing the data file +Visual Example:
+![Adding a wedding example](images/addw_example.png)
+*Example of successfully adding a new wedding with all fields specified* -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. -
+##### Editing a Wedding: `editw` -### Archiving data files `[coming in v2.0]` +Edits the details of an existing wedding. -_Details coming soon ..._ +- **Format**: `editw w/INDEX [n/NAME] [d/DATE] [v/VENUE]` + - At least one optional field (`NAME`, `DATE`, `VENUE`) must be provided. --------------------------------------------------------------------------------------------------------------------- +- **Notes**: + - Client cannot be changed after creation. + - Existing values will be updated to the new values. + - Date and venue must adhere to validation rules specified in the [Validation Rules for Wedding Fields](#wedding-fields). + - Make use of `list` command to refresh the list with the updated information. +- **Examples**: + - `editw w/1 n/Sunset Wedding` + - Changes the name of the wedding at index 1 to "Sunset Wedding". + - `editw w/2 d/2025-01-01 v/Grand Hotel` + - Updates the date and venue of the wedding at index 2. + +--- + +##### Viewing Wedding Details: `vieww` + +Views the details of a wedding. + +- **Formats**: + - **By Index**: `vieww INDEX` + - **By Keyword**: `vieww KEYWORD` +- **Notes**: + - Keyword can be the wedding name. + - Person list will only be updated when one unique wedding is found. + - If multiple matches are found, the system will prompt for index. + - Wedding list will be truncated to the matched wedding(s). + - Use `list` to view all weddings again. +- **Information Displayed**: + - Wedding name, client details, date, venue. + - Assigned vendors. + - Client of wedding will have a ```Client``` label. +- **Examples**: + - `vieww 1` displays details of the wedding at index 1. + - `vieww John` displays John's wedding if there's only one match. + +--- + +##### Deleting a Wedding: `deletew` + +Deletes a wedding from the address book. + +- **Formats**: + - **By Index**: `deletew INDEX` + - **By Keyword**: `deletew KEYWORD` +- **Notes**: + - Deleting a wedding removes the client-wedding relationship and all vendor assignments. + - If multiple matches are found when using keyword, the system will prompt for index. +- **Examples**: + - `deletew 2` deletes the wedding at index 2. + - `deletew Beach Wedding` deletes the wedding named "Beach Wedding" if there's only one match. + +--- + +#### Assigning a Person: `assign` + +Assigns a role and/or weddings to a person. + +- **Formats**: + - **By Index**: `assign INDEX [r/ROLE] [w/WEDDING_INDEX]...` + - **By Name**: `assign NAME [r/ROLE] [w/WEDDING_INDEX]...` +- **Notes**: + - At least one of `[r/ROLE]` or `[w/WEDDING_INDEX]` must be provided. + - **Role Assignment**: + - Each person can have either 0 or 1 role at a time. + - Assigning a new role replaces the existing role. + - Role can be blank using `r/` to remove the role of a person. + - **Wedding Assignment**: + - Can assign a person to multiple weddings. + - Wedding indices must be valid and refer to existing weddings. + - Cannot assign the same person to the same wedding multiple times. + - Cannot assign a client to their own wedding as a vendor. +- **Examples**: + - `assign 1 r/florist` + - Assigns the role "florist" to the person at index 1. + - `assign 1 w/1 w/2` + - Assigns the person at index 1 to weddings at indices 1 and 2. + - `assign 1 r/` + - Removes the role of person at index 1. + - `assign John Doe r/photographer w/2` + - If there's only one match for "John Doe", assigns them the role "photographer" and to wedding at index 2.
+ +Visual Examples:
+**Error Example**:
+![Assignment error example](images/error_example_assign.png)
+*Example of error when trying to assign a client to their own wedding* + +**Success Example**:
+![Successful multiple assignment](images/success_multi_match.png)
+*Example of successfully assigning a person to a role* + +--- + +#### Exiting the Program: `exit` + +Exits the application. + +- **Format**: `exit` + +--- + +#### Saving the Data + +- **Automatic Saving**: + - Data is automatically saved after any command that modifies the data. +- **Storage Location**: + - Data is saved as a JSON file at `[JAR file location]/data/addressbook.json`. + +[↥ Back to Top](#bridal-boss-user-guide) + +--- + +#### Editing the Data File + +Advanced users can edit the data file directly to modify the address book data. However, exercise caution when doing so. + +- **File Location**: + - `[JAR file location]/data/addressbook.json` +- **Caution**: + - **Backup First**: + - Make a copy of the data file before editing. + - **Validity**: + - Incorrect file formats or invalid data can cause the application to discard all data and start with an empty data file upon the next run. + - Changing ownWedding field of a person to 0 will only result in wedding having no client and would not corrupt the file. + - However, changing the hashcode of the `weddingJobs` to an incorrect value will result in a corrupted file and restart the application with an empty data file. +- **Recommendation**: + - Edit the data file only if you are confident in updating it correctly. + +--- + +### Additional Notes + +- **Using PDFs**: + - When copying commands from a PDF, ensure that spaces and line-breaks are correctly maintained to avoid errors. +- **Error Messages**: + - The application provides specific error messages to guide users in correcting their commands. +- **Case Sensitivity**: + - Commands must be **lowercase** (`add, addw, edit, editw etc.`) + - Parameters prefixes (`n/..., e/...` ) are **case-sensitive**. + +[↥ Back to Top](#bridal-boss-user-guide) + +### General Command Format + +- **Command Structure**: + - Commands are case-sensitive, i.e. must be in **lower-case only**.
e.g. `clear` will be accepted but `Clear` will not. + - Parameters are case-insensitive unless specified. + - For optimum viewing experience, restrict fields to 50 characters or less. +- **Parameters in `UPPER_CASE`** are to be supplied by the user. + - e.g., in `add n/NAME`, `NAME` is a parameter to be replaced: `add n/John Doe`. +- **Optional Parameters** are enclosed in square brackets `[ ]`. + - e.g., `n/NAME [r/ROLE]` can be `n/John Doe r/florist` or just `n/John Doe`. +- **Multiple Parameters**: + - Parameters with `...` after them can be used multiple times (including zero times). + - e.g., `[w/WEDDING_INDEX]...` can be used as ` ` (zero times), `w/1`, `w/1 w/2`, etc. +- **Flexible Order**: + - Parameters can be in any order. + - e.g., if the command specifies `n/NAME p/PHONE_NUMBER`, you can input `p/PHONE_NUMBER n/NAME`. +- **Extraneous Parameters**: + - Commands that do not take in parameters (e.g., `help`, `list`, `exit`, and `clear`) will ignore any extra parameters. + - e.g., `help 123` will be interpreted as `help`. +- **Client Parameter (`c/`)**: + - In wedding commands, accepts either an index number or a name. + - e.g., `c/1` or `c/John Doe` are both valid. +- **Date Format**: + - Dates must be specified in `YYYY-MM-DD` format. + - e.g., `d/2024-12-31` for December 31st, 2024. +- **Role Parameter (`r/`)**: + - Must be a single-word alphanumeric string (no spaces or special characters). + - e.g., `r/photographer` is valid, but `r/wedding planner` is not. +- **Email Addresses**: + - Must follow strict validation rules (see [Validation Rules](#validation-rules)). +- **Phone Numbers**: + - Must start with 6, 8 or 9 and be exactly 8 digits long, i.e. valid Singaporean number. +- **Copying Commands**: + - When copying commands that span multiple lines (e.g., from a PDF), ensure that spaces are correctly included. +--- -## FAQ +### Validation Rules + +#### Names + +- **Allowed Characters**: + - Alphabets, spaces, apostrophes (`'`), and hyphens (`-`). +- **Restrictions**: + - Cannot be blank. + - Maximum length of 70 characters. +- **Examples**: + - `John Doe`, `Mary-Jane`, `O'Connor`. + +#### Phone Numbers + +- **Format**: + - Must start with 6, 8 or 9. + - Exactly 8 digits long. + - Numbers only; no spaces or special characters. +- **Uniqueness**: + - Each phone number must be unique in the system. +- **Examples**: + - `91234567`, `82345678`, `67891234`. + +#### Email Addresses + +- **Format**: + - Must be in the form `local-part@domain` +- **Local-part Rules**: + - Can contain alphanumeric characters and `+`, `_`, `.`, `-` + - Cannot start or end with a special character + - Example: `user.name`, `john.doe-123`, `user+tag` +- **Domain Rules**: + - Domain labels (parts between dots) must: + - Start with an alphanumeric character + - End with at least 2 alphanumeric characters + - Can contain hyphens between alphanumeric characters + - Each label must contain at least one character + - Examples of valid domains: + - `example.com` + - `my-company.com` + - `school.edu.sg` + - `sub1.sub2.example.com` +- **Uniqueness**: + - Each email must be unique in the system +- **Valid Examples**: + - `john@example.com` + - `user.name+tag@my-company.com` + - `sales@company-name.com.sg` +- **Invalid Examples**: + - `user@e-a` (does not end with 2 alphanumeric characters) + - `user@-domain.com` (domain label starts with hyphen) + - `user@domain-.com` (domain label ends with hyphen) + - `user@.com` (domain label must contain at least one alphanumeric character) +#### Roles + +- **Format**: + - Single-word alphanumeric string. +- **Restrictions**: + - No spaces or special characters. + - Case-insensitive for matching. +- **Examples**: + - `photographer`, `florist`, `coordinator`. + +#### Wedding Fields + +- **Wedding Name**: + - Cannot be blank + - Must follow same restrictions as Person names: + - Maximum 70 characters + - Can only contain alphabets, spaces, apostrophes (') and hyphens (-) +- **Date**: + - Must be in `YYYY-MM-DD` format + - Must be a valid calendar date + - Optional field - only validated when v/ prefix is provided + +- **Venue**: + - Optional field - only validated when v/ prefix is provided + - When provided, cannot be blank or consist only of whitespace +- **Client**: + - A client can have only one wedding at a time. + +#### Addresses + +- **Restrictions**: + - Cannot be blank. + - Can contain any characters except leading/trailing spaces. + - No length restriction. -**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. +--- + +### Index vs. Name-Based Commands + +Certain commands (`edit`, `delete`, `deletew`, `view`, `vieww`, `assign`) support both index-based and name-based formats. + +#### Index Format + +- **Usage**: + - Uses the position number from the displayed list. + - Only positive non-zero integers are accepted. + - **Format**: `COMMAND INDEX [parameters]` + - **Example**: + - `edit 1 n/John Smith` + +#### Name-Based Format + +- **Usage**: + - Uses the person's or wedding's name. + - **Format**: `COMMAND NAME [parameters]` + - **Behavior**: + - **Case-insensitive matching**. + - **Full name matching**: Searches for names containing the entire keyword (not necessarily as substring). + - **Single Match**: + - Command executes immediately. + - **Multiple Matches**: + - System displays a list of matching entries with indices. + - User must re-enter the command using the index. + - **No Matches**: + - Displays relevant error message. +- **Examples**:
+ ![Multiple matches example](images/multiple_match.png)
+ *When multiple matches are found, the system displays a list with indices* + + ![Multiple matches resolution](images/multiple_match_solution.png)
+ *User selects a specific index to complete the command* + +[↥ Back to Top](#bridal-boss-user-guide) + +--- + +### Cross-Reference Validations + +#### Client-Wedding Relationship + +- **One Wedding per Client**: + - A client can have only one wedding at a time. +- **Deletion Restrictions**: + - Cannot delete a client who is associated with an active wedding. +- **Assignment Restrictions**: + - Cannot assign a client as a vendor to their own wedding. + +#### Person-Wedding Relationships + +- **Vendor Assignments**: + - A person can be assigned to multiple weddings as a vendor. + - Cannot assign the same person to the same wedding multiple times. + - A person without a role can still be assigned to a wedding. +- **Deletion Effects**: + - Deleting a wedding removes all vendor assignments related to that wedding. + - Can delete a vendor who is assigned weddings + +#### Role-Person Relationship + +- **Single Role per Person**: + - Each person can have at most one role. + - Assigning a new role replaces any existing role. + +--- + +## 📚 **FAQ** + +**Q**: **How do I transfer my data to another Computer?** +**A**: 🖥️ Install the app on the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. + +--- + +**Q**: **How do I input the name of a person with `d/o` or `s/o` in it?** +**A**: ✏️ You can input the name as `son of` or `daughter of` respectively. + +--- + +**Q**: **How do I add a wedding for an existing client?** +**A**: First, use `list` to see all contacts. Then, use either `addw n/WEDDING_NAME c/INDEX` using the client's index number, or `addw n/WEDDING_NAME c/CLIENT_NAME` using the client's name. + +--- + +**Q**: **What happens if I try to delete a client who has a wedding?** +**A**: ⚠️ The system will prevent you from deleting the client and show an error message. You must first delete the client's wedding before deleting the contact. + +--- + +**Q**: **Can I change a wedding's client after creation?** +**A**: ❌ No, a wedding's client cannot be changed after creation. You would need to create a new wedding for the different client. + +--- + +**Q**: **Can a client have multiple weddings?** +**A**: 📅 No, each client can only have one wedding at a time. + +--- + +**Q**: **Can I assign multiple roles to a person?** +**A**: 🚫 No, each person can only have at most one role at a time. Assigning a new role will replace the existing one. + +--- + +**Q**: **What happens when I delete a wedding?** +**A**: Deleting a wedding will remove all vendor assignments to that wedding and remove the client-wedding relationship. The contacts themselves are not deleted. + +--- + +**Q**: **Can I use the same phone number or email for different contacts?** +**A**: 📛 No, phone numbers and email addresses must be unique in the system. You'll receive an error message if you try to add or edit a contact with duplicate information. + +--- + +**Q**: **What happens if I find multiple contacts with the same name?** +**A**: When using name-based commands, if multiple matches are found, the system will show you a list of matching contacts with their indices. You'll need to use the index number to specify which contact you want to work with. + +--- + +**Q**: **How can I see all weddings a vendor is assigned to?** +**A**: Use the `view` command with the vendor's name or index. The system will show all weddings they are assigned to as part of their contact details. + +--- + +**Q**: **Can I search for contacts by partial name match?** +**A**: ✅ Yes, use the `find` command which matches partial names. However, note that it matches whole words only (e.g., "John" will match "John Doe" but not "Johnny"). + +--- + +**Q**: **What's the difference between `find` and `filter` commands?** +**A**: `find` and `filter` have different search capabilities: +- **`find`**: + - 🔍 Searches only names + - Supports partial word matches + - Allows multiple name searches (e.g., `find alex david` returns both `Alex Yeoh` and `David Li`) + - Uses OR logic (matches any keyword) + +- **`filter`**: + - Can search across multiple fields (name, role, email, phone, address) + - Requires exact word matches for names and roles + - Each field can only have one value - if you specify multiple values for the same field, only the last one is used + - e.g., `filter n/John n/Peter` will only search for "Peter" + - Uses OR logic between different fields + - e.g., `filter n/John r/vendor` returns contacts with either name "John" OR role "vendor" + - Cannot search for multiple names like `find` does - must use exact single name + +--- + +**Q**: **How do I remove a role from a contact?** +**A**: Roles can be removed using the assign command. +    Example: `assign 1 r/` to remove the role of person at index 1. + +--- + +**Q**: **What happens to wedding assignments if I edit a contact's details?** +**A**: ✏️ Editing a contact's basic details (name, phone, email, address) does not affect their wedding assignments or role. These relationships remain intact. + +--- + +**Q**: **Can I export my contact and wedding data?** +**A**: While there's no direct export command, you can copy the data file (addressbook.json) which contains all your data. This file is located in the same folder as the application. -------------------------------------------------------------------------------------------------------------------- @@ -183,17 +804,37 @@ _Details coming soon ..._ 1. **When using multiple screens**, if you move the application to a secondary screen, and later switch to using only the primary screen, the GUI will open off-screen. The remedy is to delete the `preferences.json` file created by the application before running the application again. 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. +3. Persons are able to have multiple weddings on the same day. +4. Client of a wedding can be assigned another wedding job (for a different wedding) on the same day. +5. Long fields (more than 50 characters) may not display fully in the GUI. +6. Incorrect error message shown when the number entered is too large. +7. User may not be able to "unclick" after clicking on a field on the GUI. +8. User may be able to add weddings that are in the past. +9. Adjusting the window size may cause the GUI to display to hide wedding details.
+ +[↥ Back to Top](#bridal-boss-user-guide) -------------------------------------------------------------------------------------------------------------------- ## 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 p/PHONE_NUMBER e/EMAIL a/ADDRESS [r/ROLE] [w/WEDDING_INDEX]... …​`
e.g., `add n/James Ho p/92224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 r/florist w/1 w/2` | +| **Clear** | `clear` | +| **Delete** | #1: `delete INDEX [w/WEDDING_INDEX]...` or
#2: `delete NAME [w/WEDDING_INDEX]...`
e.g., `delete 1`, `delete Alex`, `delete Alex Tan`, `delete 1 w/1`, `delete Alex w/1 w/2` | +| **Edit** | #1: `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS]` or
#2: `edit NAME [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS]`
e.g.,`edit 2 n/James Lee e/jameslee@example.com`, `edit James n/James Lee e/jameslee@example.com` | +| **View** | #1: `view NAME` or
#2: `view INDEX` e.g., `view Alex`, `view 1` | +| **Find** | `find KEYWORD [MORE_KEYWORDS]`
e.g., `find James Jake` | +| **Filter** | `filter [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [r/ROLE]`
e.g., `filter r/friends` | +| **View** | `view KEYWORD`
e.g., `view Alex`, `view Alex Tan` | +| **List** | `list` | +| **Addw** | `addw n/WEDDING_NAME c/CLIENT [d/DATE] [v/VENUE]`
e.g., `addw n/Beach Wedding c/1 d/2024-12-31 v/Sentosa Beach` | +| **Editw** | `editw w/INDEX [n/NAME] [d/DATE] [v/VENUE]`
e.g., `editw w/1 d/2024-12-31 v/Garden Venue` | +| **Vieww** | `vieww INDEX` or `vieww KEYWORD`
e.g., `vieww 1`, `vieww John` | +| **Deletew** | #1: `deletew INDEX` or
#2: `deletew KEYWORD`
e.g., `deletew 1`, `deletew Beach Wedding` | +| **Assign** | `assign INDEX [r/ROLE] [w/WEDDING_INDEX]...` or `assign NAME [r/ROLE] [w/WEDDING_INDEX]...`
e.g., `assign 1 r/vendor`, `assign John Doe r/photographer w/1 w/2`, `assign 1 r/` | +| **Help** | `help` | +| **Exit** | `exit` | + +[↥ Back to Top](#bridal-boss-user-guide) 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..245c144fd33 --- /dev/null +++ b/docs/_markbind/layouts/default.md @@ -0,0 +1,67 @@ + + + + +
+ + BridalBoss +
  • 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/AddWeddingSequenceDiagram.puml b/docs/diagrams/AddWeddingSequenceDiagram.puml new file mode 100644 index 00000000000..b82f04dfa1c --- /dev/null +++ b/docs/diagrams/AddWeddingSequenceDiagram.puml @@ -0,0 +1,46 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +Actor User as user USER_COLOR +Participant ":UI" as ui UI_COLOR +Participant ":Logic" as logic LOGIC_COLOR +Participant ":Model" as model MODEL_COLOR +Participant ":Storage" as storage STORAGE_COLOR + +user -[USER_COLOR]> ui : "addw n/WeddingName c/ClientName d/Date v/Venue" +activate ui UI_COLOR + +ui -[UI_COLOR]> logic : execute("addw n/WeddingName c/ClientName d/Date v/Venue") +activate logic LOGIC_COLOR + +logic -[LOGIC_COLOR]> model : hasWedding(toAdd) +activate model MODEL_COLOR + +alt Wedding Exists + model -[MODEL_COLOR]> logic : CommandException + deactivate model +else + model -[MODEL_COLOR]> model : addWedding(toAdd) + activate model MODEL_COLOR_T1 + model --[MODEL_COLOR]> logic + deactivate model +end + +logic -[LOGIC_COLOR]> storage : saveAddressBook(addressBook) +activate storage STORAGE_COLOR + +storage -[STORAGE_COLOR]> storage : Save to file +activate storage STORAGE_COLOR_T1 +storage --[STORAGE_COLOR]> storage +deactivate storage + +storage --[STORAGE_COLOR]> logic +deactivate storage + +logic --[LOGIC_COLOR]> ui : CommandResult("New wedding added") +deactivate logic + +ui--[UI_COLOR]> user +deactivate ui +@enduml diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml index 598474a5c82..53d2381429e 100644 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ b/docs/diagrams/BetterModelClassDiagram.puml @@ -5,17 +5,14 @@ skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList -UniqueTagList -[hidden]down- UniquePersonList -UniqueTagList -[hidden]down- UniquePersonList - -UniqueTagList -right-> "*" Tag UniquePersonList -right-> Person -Person -up-> "*" Tag +Person *--> Role Person *--> Name Person *--> Phone Person *--> Email Person *--> Address +Person --> "weddingJobs" Wedding +Person --> "ownWedding" Wedding @enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml index 5241e79d7da..19bac304166 100644 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ b/docs/diagrams/DeleteSequenceDiagram.puml @@ -49,12 +49,24 @@ deactivate AddressBookParser LogicManager -> DeleteCommand : execute(m) activate DeleteCommand -DeleteCommand -> Model : deletePerson(1) +DeleteCommand -> DeleteCommand : deleteWithIndex(m) +activate DeleteCommand + +DeleteCommand -> Model : deletePerson(p) +activate Model + +Model -> Model : updateFilteredPersonList(predicate) activate Model +Model --> Model +deactivate Model + Model --> DeleteCommand deactivate Model +DeleteCommand --> DeleteCommand : personToDelete +deactivate DeleteCommand + create CommandResult DeleteCommand -> CommandResult activate CommandResult diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..89f0f6b8438 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -4,23 +4,28 @@ skinparam arrowThickness 1.1 skinparam arrowColor MODEL_COLOR skinparam classBackgroundColor MODEL_COLOR -Package Model as ModelPackage <>{ -Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook -Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs -Class "<>\nModel" as Model -Class AddressBook -Class ModelManager -Class UserPrefs - -Class UniquePersonList -Class Person -Class Address -Class Email -Class Name -Class Phone -Class Tag - -Class I #FFFFFF +Package Model as ModelPackage <> { + Class "<>\nReadOnlyAddressBook" as ReadOnlyAddressBook + Class "<>\nModel" as Model + Class ModelManager + Class "<>\nReadOnlyUserPrefs" as ReadOnlyUserPrefs + Class AddressBook + Class UserPrefs + + Class UniquePersonList + Class UniqueWeddingList + Class Person + Class Wedding + Class Client + Class Address + Class Email + Class Name + Class Phone + Class Role + Class Date + Class Venue + + Class I #FFFFFF } Class HiddenOutside #FFFFFF @@ -29,26 +34,41 @@ HiddenOutside ..> Model AddressBook .up.|> ReadOnlyAddressBook ModelManager .up.|> Model -Model .right.> ReadOnlyUserPrefs -Model .left.> ReadOnlyAddressBook +Model -left.> ReadOnlyAddressBook + +Model -right.> ReadOnlyUserPrefs ModelManager -left-> "1" AddressBook ModelManager -right-> "1" UserPrefs UserPrefs .up.|> ReadOnlyUserPrefs + AddressBook *--> "1" UniquePersonList +AddressBook *--> "1" UniqueWeddingList UniquePersonList --> "~* all" Person +UniqueWeddingList --> "~* all" Wedding Person *--> Name Person *--> Phone Person *--> Email Person *--> Address -Person *--> "*" Tag +Person -left-> "weddingJobs" Wedding +Person --> "ownWedding" Wedding +Person *--> "0..1" Role +Client --> Person +Wedding *--> Name +Wedding *--> Client +Wedding *--> Date +Wedding *--> Venue Person -[hidden]up--> I UniquePersonList -[hidden]right-> I +Name -[hidden]up--> Client Name -[hidden]right-> Phone Phone -[hidden]right-> Address Address -[hidden]right-> Email +Email -[hidden]right-> Role +Role -[hidden]right-> Date +Date -[hidden]right-> Venue ModelManager --> "~* filtered" Person @enduml diff --git a/docs/diagrams/PersonClassDiagram.puml b/docs/diagrams/PersonClassDiagram.puml new file mode 100644 index 00000000000..a638dbf4568 --- /dev/null +++ b/docs/diagrams/PersonClassDiagram.puml @@ -0,0 +1,31 @@ +@startuml +!include style.puml +allow_mixing +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR_T4 +skinparam classBackgroundColor MODEL_COLOR + +package "Wedding" as WeddingPackage <> { +} + +package "Person" as PersonPackage <> { + class Person + class Name + class Phone + class Email + class Address + class Role + class UniquePersonList +} + +Person *--> "1" Name : "has a" +Person *--> "1" Phone : "has a" +Person *--> "1" Email : "has a" +Person *--> "1" Address : "has a" +Person *--> "0..1" Role : "has a" +Person --> "0..1" WeddingPackage : "ownWedding" +Person --> "*" WeddingPackage : "ownWedding" + +UniquePersonList "1" *--> "0..*" Person : "contains" + +@enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..7825f8022f2 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -6,22 +6,21 @@ skinparam classBackgroundColor STORAGE_COLOR package Storage as StoragePackage { -package "UserPrefs Storage" #F4F6F6{ -Class "<>\nUserPrefsStorage" as UserPrefsStorage -Class JsonUserPrefsStorage -} - -Class "<>\nStorage" as Storage -Class StorageManager - -package "AddressBook Storage" #F4F6F6{ -Class "<>\nAddressBookStorage" as AddressBookStorage -Class JsonAddressBookStorage -Class JsonSerializableAddressBook -Class JsonAdaptedPerson -Class JsonAdaptedTag -} - + package "UserPrefs Storage" #F4F6F6 { + Class "<>\nUserPrefsStorage" as UserPrefsStorage + Class JsonUserPrefsStorage + } + + Class "<>\nStorage" as Storage + Class StorageManager + + package "AddressBook Storage" #F4F6F6 { + Class "<>\nAddressBookStorage" as AddressBookStorage + Class JsonAddressBookStorage + Class JsonSerializableAddressBook + Class JsonAdaptedPerson + Class JsonAdaptedWedding + } } Class HiddenOutside #FFFFFF @@ -38,6 +37,6 @@ JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonSerializableAddressBook --> "*" JsonAdaptedWedding @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..bf1d4ef8cfc 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -13,6 +13,8 @@ Class HelpWindow Class ResultDisplay Class PersonListPanel Class PersonCard +Class WeddingListPanel +Class WeddingCard Class StatusBarFooter Class CommandBox } @@ -33,10 +35,12 @@ UiManager -down-> "1" MainWindow MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay MainWindow *-down-> "1" PersonListPanel +MainWindow *-down-> "1" WeddingListPanel MainWindow *-down-> "1" StatusBarFooter MainWindow --> "0..1" HelpWindow PersonListPanel -down-> "*" PersonCard +WeddingListPanel -down-> "*" WeddingCard MainWindow -left-|> UiPart @@ -44,10 +48,13 @@ ResultDisplay --|> UiPart CommandBox --|> UiPart PersonListPanel --|> UiPart PersonCard --|> UiPart +WeddingListPanel --|> UiPart +WeddingCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart PersonCard ..> Model +WeddingCard ..> Model UiManager -right-> Logic MainWindow -left-> Logic diff --git a/docs/diagrams/WeddingClassDiagram.puml b/docs/diagrams/WeddingClassDiagram.puml new file mode 100644 index 00000000000..bc7bac3dd69 --- /dev/null +++ b/docs/diagrams/WeddingClassDiagram.puml @@ -0,0 +1,29 @@ +@startuml +!include style.puml +allow_mixing +skinparam arrowThickness 1.1 +skinparam arrowColor MODEL_COLOR_T4 +skinparam classBackgroundColor MODEL_COLOR + +package "Person" as PersonPackage <> { +} + +package "Wedding" as WeddingPackage <> { + class Wedding + class Client + class Date + class Venue + class Name + class UniqueWeddingList +} + +Wedding "1" --> "1" Client : "has a" +Wedding "1" --> "1" Date : "has a" +Wedding "1" --> "1" Venue : "has a" +Wedding "1" --> "1" Name : "has a" + +Client "1" --> PersonPackage : "references" + +UniqueWeddingList "1" *--> "0..*" Wedding : "contains" + +@enduml 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..03dc64dd7e7 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/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/addw_example.png b/docs/images/addw_example.png new file mode 100644 index 00000000000..22caf7c976f Binary files /dev/null and b/docs/images/addw_example.png differ diff --git a/docs/images/droas590.png b/docs/images/droas590.png new file mode 100644 index 00000000000..a3d4256423f Binary files /dev/null and b/docs/images/droas590.png differ diff --git a/docs/images/error_example_assign.png b/docs/images/error_example_assign.png new file mode 100644 index 00000000000..c12233d76dd Binary files /dev/null and b/docs/images/error_example_assign.png differ diff --git a/docs/images/filter_persons_by_multiple_fields.png b/docs/images/filter_persons_by_multiple_fields.png new file mode 100644 index 00000000000..34e7c0b5238 Binary files /dev/null and b/docs/images/filter_persons_by_multiple_fields.png differ diff --git a/docs/images/jowhee3011.png b/docs/images/jowhee3011.png new file mode 100644 index 00000000000..fd554494b6f Binary files /dev/null and b/docs/images/jowhee3011.png differ diff --git a/docs/images/multi_filter_weddings_unfiltered.png b/docs/images/multi_filter_weddings_unfiltered.png new file mode 100644 index 00000000000..565d542d568 Binary files /dev/null and b/docs/images/multi_filter_weddings_unfiltered.png differ diff --git a/docs/images/multiple_match.png b/docs/images/multiple_match.png new file mode 100644 index 00000000000..c283a926a0f Binary files /dev/null and b/docs/images/multiple_match.png differ diff --git a/docs/images/multiple_match_solution.png b/docs/images/multiple_match_solution.png new file mode 100644 index 00000000000..5654b0adcf2 Binary files /dev/null and b/docs/images/multiple_match_solution.png differ diff --git a/docs/images/shernicesng.png b/docs/images/shernicesng.png new file mode 100644 index 00000000000..b30a7ed97a6 Binary files /dev/null and b/docs/images/shernicesng.png differ diff --git a/docs/images/success_multi_match.png b/docs/images/success_multi_match.png new file mode 100644 index 00000000000..3c4fe58b8da Binary files /dev/null and b/docs/images/success_multi_match.png differ diff --git a/docs/images/vedjoshi.png b/docs/images/vedjoshi.png new file mode 100644 index 00000000000..8d85fa165dd Binary files /dev/null and b/docs/images/vedjoshi.png differ diff --git a/docs/images/view_mulitple_weddings_unfiltered.png b/docs/images/view_mulitple_weddings_unfiltered.png new file mode 100644 index 00000000000..fc55363e4b7 Binary files /dev/null and b/docs/images/view_mulitple_weddings_unfiltered.png differ diff --git a/docs/images/yikjunxian.png b/docs/images/yikjunxian.png new file mode 100644 index 00000000000..ef754b2bb55 Binary files /dev/null and b/docs/images/yikjunxian.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..e068a9b2a7c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,19 @@ --- -layout: page -title: AddressBook Level-3 +layout: default.md +title: "Bridal Boss" --- -[![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) +# Bridal Boss + +[![Java CI](https://github.com/AY2425S1-CS2103T-T11-3/tp/actions/workflows/gradle.yml/badge.svg)](https://github.com/AY2425S1-CS2103T-T11-3/tp/actions/workflows/gradle.yml) +[![codecov](https://codecov.io/github/AY2425S1-CS2103T-T11-3/tp/branch/master/graph/badge.svg?token=L3GJNBUKTC)](https://codecov.io/github/AY2425S1-CS2103T-T11-3/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). +**Bridal Boss 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). -* 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 Bridal Boss, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start). +* If you are interested about developing Bridal Boss, the [**Developer Guide**](DeveloperGuide.html) is a good place to start. **Acknowledgements** 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..825a5db4931 --- /dev/null +++ b/docs/site.json @@ -0,0 +1,29 @@ +{ + "baseUrl": "", + "titlePrefix": "BridalBoss", + "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/johndoe.md index 773a07794e2..86aa7ebfc34 100644 --- a/docs/team/johndoe.md +++ b/docs/team/johndoe.md @@ -1,6 +1,6 @@ --- -layout: page -title: John Doe's Project Portfolio Page + layout: default.md + title: "John Doe's Project Portfolio Page" --- ### Project: AddressBook Level 3 diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java index dd170d8b68d..8141419e5cb 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -62,6 +62,11 @@ public boolean equals(Object other) { return zeroBasedIndex == otherIndex.zeroBasedIndex; } + @Override + public int hashCode() { + return Integer.hashCode(zeroBasedIndex); + } + @Override public String toString() { return new ToStringBuilder(this).add("zeroBasedIndex", zeroBasedIndex).toString(); diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb..bb69df8d2c6 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -38,6 +38,31 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { .anyMatch(preppedWord::equalsIgnoreCase); } + /** + * Returns true if the {@code sentence} contains the {@code phrase}. + * Ignores case, but a full phrase match is required. + *
    examples:
    +     *       containsPhraseIgnoreCase("ABc def", "abc") == true
    +     *       containsPhraseIgnoreCase("ABc def", "DEF") == true
    +     *       containsPhraseIgnoreCase("ABc def", "AB") == false //not a full phrase match
    +     *       
    + * @param sentence cannot be null + * @param phrase cannot be null, cannot be empty, must be a single phrase + */ + public static boolean containsPhraseIgnoreCase(String sentence, String phrase) { + requireNonNull(sentence); + requireNonNull(phrase); + + String preppedPhrase = phrase.trim(); + checkArgument(!preppedPhrase.isEmpty(), "Phrase parameter cannot be empty"); + + String preppedSentence = sentence.toLowerCase(); + String preppedPhraseLower = preppedPhrase.toLowerCase(); + + return preppedSentence.contains(preppedPhraseLower); + } + + /** * Returns a detailed message of the t, including the stack trace. */ diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..66d8cb2ccfd 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -9,6 +9,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.wedding.Wedding; /** * API of the Logic component @@ -33,6 +34,9 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of weddings */ + ObservableList getFilteredWeddingList(); + /** * Returns the user prefs' address book file path. */ diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 5aa3b91c7d0..5058ac3b640 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -16,6 +16,7 @@ import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.wedding.Wedding; import seedu.address.storage.Storage; /** @@ -71,6 +72,12 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getFilteredWeddingList() { + return model.getFilteredWeddingList(); + } + + @Override public Path getAddressBookFilePath() { return model.getAddressBookFilePath(); diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java index ecd32c31b53..bc76b1f05f9 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -6,18 +6,42 @@ import seedu.address.logic.parser.Prefix; import seedu.address.model.person.Person; +import seedu.address.model.role.Role; +import seedu.address.model.wedding.Wedding; /** * Container for user visible messages. */ public class Messages { + // General Incorrect Command Messages public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; + + // Parsing Messages + // Parse by index usages + public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = + "The person index %1$d provided is invalid, please enter an index that is between 1 and %2$d"; + public static final String MESSAGE_INVALID_WEDDING_DISPLAYED_INDEX = + "The wedding index %d provided is invalid, please " + + "enter an index that is between 1 and %1$d"; + + // Parse by name usages + public static final String MESSAGE_INVALID_PERSON = "This person does not exist in the address book"; + + + // List Command Messages public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_PERSONS_LISTED_NAME_OVERVIEW = "%1$d persons listed with name %2$s!"; + + + // Duplicate Messages public static final String MESSAGE_DUPLICATE_FIELDS = "Multiple values specified for the following single-valued field(s): "; + public static final String MESSAGE_DUPLICATE_CONTACT = "This contact already exists in the address book"; + public static final String MESSAGE_PHONE_EXIST = "This number already exists in the address book"; + public static final String MESSAGE_EMAIL_EXIST = "This email already exists in the address book"; + /** * Returns an error message indicating the duplicate prefixes. @@ -37,15 +61,38 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref public static String format(Person person) { final StringBuilder builder = new StringBuilder(); builder.append(person.getName()) - .append("; Phone: ") + .append(" | Phone: ") .append(person.getPhone()) - .append("; Email: ") + .append(" | Email: ") .append(person.getEmail()) - .append("; Address: ") + .append(" | Address: ") .append(person.getAddress()) - .append("; Tags: "); - person.getTags().forEach(builder::append); + .append(" | Role: ") + .append(person.getRole().map(Role::toString).orElse("NA")); + return builder.toString(); + } + + /** + * Formats the {@code wedding} for display to the user. + */ + public static String format(Wedding wedding) { + final StringBuilder builder = new StringBuilder(); + builder.append(wedding.getName()) + .append(" | Client: ") + .append(wedding.getClient().getPerson().getName().fullName) + .append(" | Date: ") + .append(wedding.getDate() == null ? "NA" : wedding.getDate()) + .append(" | Venue: ") + .append(wedding.getVenue() == null ? "NA" : wedding.getVenue()); return builder.toString(); } + /** + * Formats the list of {@code Wedding} for display to the user. + */ + public static String format(Set weddingJobs) { + return weddingJobs.stream() + .map(Messages::format) // Calls the static format method in Wedding for each wedding + .collect(Collectors.joining(", \n")); + } } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 5d7185a9680..e9e56dcc6bd 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -5,13 +5,19 @@ 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.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_WEDDING; -import seedu.address.commons.util.ToStringBuilder; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.index.Index; import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; import seedu.address.model.person.Person; +import seedu.address.model.wedding.Wedding; /** * Adds a person to the address book. @@ -20,32 +26,35 @@ 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 person to the address book. \n" + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" + + "[" + PREFIX_ROLE + "ROLE] " + + "[" + PREFIX_WEDDING + "WEDDING...]\n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + + PREFIX_ROLE + "florist " + + PREFIX_WEDDING + "1"; 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"; + public static final String MESSAGE_WEDDING_DOES_NOT_EXIST = "Wedding %d is not in the list."; private final Person toAdd; + private Set weddingIndices; /** - * Creates an AddCommand to add the specified {@code Person} + * Creates an {@code AddCommand} object to add the specified {@code Person} */ - public AddCommand(Person person) { + public AddCommand(Person person, Set weddingIndices) { requireNonNull(person); toAdd = person; + this.weddingIndices = weddingIndices != null ? weddingIndices : new HashSet<>(); } @Override @@ -53,20 +62,56 @@ public CommandResult execute(Model model) throws CommandException { requireNonNull(model); if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + throw new CommandException(Messages.MESSAGE_DUPLICATE_CONTACT); + } + if (model.hasPhone(toAdd)) { + throw new CommandException(Messages.MESSAGE_PHONE_EXIST); } + if (model.hasEmail(toAdd)) { + throw new CommandException(Messages.MESSAGE_EMAIL_EXIST); + } + + generateWeddingJobs(model); model.addPerson(toAdd); return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); } + /** + * Checks validity of all provided wedding indexes. + * + * @param model The model containing the list of weddings. + */ + public void checkWeddingJobs(Model model) throws CommandException { + List weddingList = model.getFilteredWeddingList(); + + for (Index index : weddingIndices) { + if (!(index.getZeroBased() >= 0 && index.getZeroBased() < weddingList.size())) { + throw new CommandException(String.format(MESSAGE_WEDDING_DOES_NOT_EXIST, index.getOneBased())); + } + } + } + + /** + * Associates the person with wedding jobs based on the provided indices. + * + * @param model The model containing the list of weddings. + */ + public void generateWeddingJobs(Model model) throws CommandException { + List weddingList = model.getFilteredWeddingList(); + + checkWeddingJobs(model); + for (Index index : weddingIndices) { + toAdd.addWeddingJob(weddingList.get(index.getZeroBased())); + } + } + @Override public boolean equals(Object other) { if (other == this) { return true; } - // instanceof handles nulls if (!(other instanceof AddCommand)) { return false; } @@ -77,8 +122,6 @@ public boolean equals(Object other) { @Override public String toString() { - return new ToStringBuilder(this) - .add("toAdd", toAdd) - .toString(); + return String.format("%s{toAdd=%s}", this.getClass().getCanonicalName(), toAdd); } } diff --git a/src/main/java/seedu/address/logic/commands/AddwCommand.java b/src/main/java/seedu/address/logic/commands/AddwCommand.java new file mode 100644 index 00000000000..df832433904 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddwCommand.java @@ -0,0 +1,212 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CLIENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_VENUE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +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.NameMatchesKeywordPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.wedding.Client; +import seedu.address.model.wedding.Wedding; + +/** + * Adds a wedding to the Address Book. + */ +public class AddwCommand extends Command { + public static final String COMMAND_WORD = "addw"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a wedding to the address book. \n" + + "Parameters: " + + PREFIX_NAME + "WEDDING'S NAME " + + PREFIX_CLIENT + "CLIENT " + + "[" + PREFIX_DATE + "DATE" + "] " + + "[" + PREFIX_VENUE + "VENUE" + "], " + + "where client can be INDEX (must be positive integer) or KEYWORD (the name of contact, " + + "case-insensitive)\n" + + "Example: \n" + + COMMAND_WORD + " " + PREFIX_NAME + "Alice Wedding " + PREFIX_CLIENT + "1 " + PREFIX_VENUE + "Hotel\n" + + COMMAND_WORD + " " + PREFIX_NAME + "Alice Wedding " + PREFIX_CLIENT + "Alice " + PREFIX_DATE + + "2024-12-12"; + + public static final String MESSAGE_SUCCESS = "New wedding added: %1$s"; + public static final String MESSAGE_DUPLICATE_WEDDING = "This wedding already exists in the address book"; + public static final String MESSAGE_ALREADY_A_CLIENT = "This person is already a client for another wedding."; + + public static final String MESSAGE_DUPLICATE_HANDLING = + "Please specify the index of the contact you want to set as client.\n" + + "Find the index from the list below and type c/INDEX ...\n" + + "Example: " + COMMAND_WORD + "n/WEDDING NAME c/1 ..."; + + private final Index index; + private final NameMatchesKeywordPredicate predicate; + private Wedding toAdd; + + /** + * Creates a new {@code AddwCommand} to add the specified wedding. + * Either index or predicate must be provided to identify the client, but not both. + * + * @param index The index of the person to set as client. Can be null if using name matching. + * @param predicate The predicate to match person by name. Can be null if using index. + * @param wedding The wedding to be added to the address book. + * @throws NullPointerException if the wedding parameter is null + */ + public AddwCommand(Index index, NameMatchesKeywordPredicate predicate, Wedding wedding) { + requireNonNull(wedding); + this.index = index; + this.predicate = predicate; + this.toAdd = wedding; + } + + /** + * Executes the command to add a new wedding to the address book. + * This method will:
    + * 1. Identify the client using either index or name matching
    + * 2. Verify the client is not already associated with another wedding
    + * 3. Create a new wedding with the identified client
    + * 4. Add the wedding to the model if it's not a duplicate
    + * + * @param model The model which the command should operate on. + * @return A CommandResult indicating the outcome of the command execution. + * @throws CommandException if:
    + * - The specified person does not exist
    + * - The person is already a client for another wedding
    + * - The wedding is a duplicate
    + * - Multiple persons match the provided name + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + Person client = selectClient(model); + + if (client.hasOwnWedding()) { + throw new CommandException(MESSAGE_ALREADY_A_CLIENT); + } + + // re-initialise wedding with client + toAdd = new Wedding(toAdd.getName(), new Client(client), toAdd.getDate(), toAdd.getVenue()); + + if (model.hasWedding(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_WEDDING); + } + + client.setOwnWedding(toAdd); + model.addWedding(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); + } + + /** + * Selects the client. + * + * @param model The model containing the list of persons. + * @return The selected client. + * @throws CommandException If the list is empty, index is invalid, no name matches or duplicate name matches. + */ + public Person selectClient(Model model) throws CommandException { + if (this.index != null) { + return selectClientWithIndex(model); + } else { + return selectClientWithKeyword(model); + } + } + + /** + * Selects the client based on the provided index. + * + * @param model The model containing the list of persons. + * @return The selected client at the given index. + * @throws CommandException If the list is empty or the index is invalid. + */ + public Person selectClientWithIndex(Model model) throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (lastShownList.isEmpty()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON); + } + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(String.format( + Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, index.getOneBased(), lastShownList.size())); + } + + return lastShownList.get(index.getZeroBased()); + } + + /** + * Selects the client by matching the name keyword. + * + * @param model The model containing the filtered list of persons. + * @return The selected client. + * @throws CommandException If no person matches the keyword or there are multiple matches. + */ + public Person selectClientWithKeyword(Model model) throws CommandException { + model.updateFilteredPersonList(predicate); + List filteredPersonList = model.getFilteredPersonList(); + + if (filteredPersonList.isEmpty()) { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(Messages.MESSAGE_INVALID_PERSON); + } else if (filteredPersonList.size() == 1) { + return filteredPersonList.get(0); + } else { + throw new CommandException(MESSAGE_DUPLICATE_HANDLING); + } + } + + /** + * Compares this {@code AddwCommand} object with another object for equality. + * Two {@code AddwCommand} objects are considered equal if they have:
    + * - The same index (or both null)
    + * - The same predicate (or both null)
    + * - Equal wedding objects + * + * @param other The object to compare with. + * @return true if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof AddwCommand)) { + return false; + } + + AddwCommand otherAddwCommand = (AddwCommand) other; + + boolean indexEqual = (index == null && otherAddwCommand.index == null) + || (index != null && index.equals(otherAddwCommand.index)); + + boolean predicateEqual = (predicate == null && otherAddwCommand.predicate == null) + || (predicate != null && predicate.equals(otherAddwCommand.predicate)); + + return indexEqual && predicateEqual && toAdd.equals(otherAddwCommand.toAdd); + + } + + /** + * Returns a string representation of the {@code AddwCommand} object. + * Includes the index, predicate, and wedding to be added. + * + * @return A string representation of this command. + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("index", index) + .add("predicate", predicate) + .add("toAddw", toAdd) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AssignCommand.java b/src/main/java/seedu/address/logic/commands/AssignCommand.java new file mode 100644 index 00000000000..1d2a9f2a830 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AssignCommand.java @@ -0,0 +1,426 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_WEDDING; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_WEDDINGS; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +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.Address; +import seedu.address.model.person.Email; +import seedu.address.model.person.Name; +import seedu.address.model.person.NameMatchesKeywordPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.role.Role; +import seedu.address.model.wedding.Wedding; + +/** + * Assigns, reassigns or removes the role of an existing person in the address book. + * Assigns wedding(s) to an existing person in the address book. + */ +public class AssignCommand extends Command { + + public static final String COMMAND_WORD = "assign"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": assigns a role and/or wedding(s) to person identified " + + "by index or name. Weddings are identified by index.\n" + + "Parameters: " + + "INDEX (must be a positive integer) or KEYWORD (the name of contact) " + + "[" + PREFIX_ROLE + "ROLE] " + + "[" + PREFIX_WEDDING + "WEDDING_INDEX]...\n" + + "Example: \n" + COMMAND_WORD + + " Alex Yeoh " + PREFIX_ROLE + "food vendor\n" + + COMMAND_WORD + " Alex Yeoh " + PREFIX_WEDDING + "1\n" + + COMMAND_WORD + " 1 " + PREFIX_ROLE + "florist " + PREFIX_WEDDING + "1"; + + public static final String MESSAGE_ASSIGN_PERSON_ROLE_SUCCESS = "Assigned role to: %1$s"; + + public static final String MESSAGE_ASSIGN_PERSON_TO_WEDDING_SUCCESS = "Assigned %1$s to wedding(s):\n%2$s"; + + public static final String MESSAGE_DUPLICATE_ROLE = "This role has already been assigned to %1$s."; + + public static final String MESSAGE_DUPLICATE_WEDDING = "Person has already been assigned to wedding(s)."; + + public static final String MESSAGE_ASSIGN_EMPTY_PERSON_LIST_ERROR = "There is no person to assign."; + + public static final String MESSAGE_CLIENT_ASSIGN_ERROR = "Person is already a client of the wedding"; + + public static final String MESSAGE_ASSIGN_EMPTY_WEDDING_LIST_ERROR = + "There is no wedding to assign as the wedding list is empty.\n" + + "Please refresh the list with a command (e.g. list, vieww)."; + + public static final String MESSAGE_MISSING_FIELDS = "There is nothing to assign. \n" + + "Please specify the role or wedding to assign with r/ROLE or w/WEDDING"; + + public static final String MESSAGE_DUPLICATE_HANDLING = + "Please specify the index of the contact you want to assign.\n" + + "Find the index from the list below and type: assign INDEX " + "[" + PREFIX_ROLE + "ROLE] " + + "[" + PREFIX_WEDDING + "WEDDING_INDEX]...\n" + + "Example: " + COMMAND_WORD + " 1 " + PREFIX_ROLE + "florist " + PREFIX_WEDDING + "1"; + private final Index index; + private final NameMatchesKeywordPredicate predicate; + private final PersonWithRoleDescriptor personWithRoleDescriptor; + private final Set weddingIndices; + + /** + * Creates a new {@code AssignCommand} to assign a role or wedding(s) to a specified person. + * Either index or predicate must be provided to identify the person to be assigned, but not both. + * + * @param index {@code Index} of the person in the filtered person list to assign role or wedding(s). + * @param predicate {@code NameMatchesKeywordPredicate} used to filter the person list to find the target person. + * @param personWithRoleDescriptor details of the person, including the new role to be assigned. + * @param weddingIndices set of indices representing the weddings to assign to the person. + */ + public AssignCommand(Index index, NameMatchesKeywordPredicate predicate, + PersonWithRoleDescriptor personWithRoleDescriptor, Set weddingIndices) { + requireNonNull(personWithRoleDescriptor); + + this.index = index; + this.predicate = predicate; + this.personWithRoleDescriptor = new PersonWithRoleDescriptor(personWithRoleDescriptor); + this.weddingIndices = weddingIndices; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Person personToAssign; + + if (this.index != null) { + personToAssign = assignWithIndex(model); + } else { + personToAssign = assignWithKeyword(model); + } + + Person assignedPerson; + if (weddingIndices != null) { + checkValidWeddingIndices(model); + checkIsClientOfWedding(model, personToAssign); + checkIsAssignedWeddings(model, personToAssign); + assignWeddingJobs(model); + assignedPerson = createPersonWithRole(personToAssign, personWithRoleDescriptor); + model.setPerson(personToAssign, assignedPerson); + model.updateFilteredWeddingList(PREDICATE_SHOW_ALL_WEDDINGS); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format( + MESSAGE_ASSIGN_PERSON_TO_WEDDING_SUCCESS, + Messages.format(assignedPerson), + Messages.format(assignedPerson.getWeddingJobs())) + ); + } else { + assignedPerson = createPersonWithRole(personToAssign, personWithRoleDescriptor); + if (personToAssign.getRole().equals(assignedPerson.getRole())) { + throw new CommandException(String.format(MESSAGE_DUPLICATE_ROLE, Messages.format(assignedPerson))); + } + model.setPerson(personToAssign, assignedPerson); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_ASSIGN_PERSON_ROLE_SUCCESS, + Messages.format(assignedPerson))); + } + } + + /** + * Performs {@code AssignCommand} logic when the input is an index. + * + * @param model {@code Model} which the command should operate on. + * @return the person to be assigned. + * @throws CommandException if the list is empty or if the index is invalid. + */ + public Person assignWithIndex(Model model) throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (lastShownList.isEmpty()) { + throw new CommandException(MESSAGE_ASSIGN_EMPTY_PERSON_LIST_ERROR); + } + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, + index.getOneBased(), lastShownList.size())); + } + + return lastShownList.get(index.getZeroBased()); + } + + /** + * Performs {@code AssignCommand} logic when the input is a {@code String}. + * + * @param model {@code Model} which the command should operate on. + * @return the person to be assigned. + * @throws CommandException if the filtered list using {@code predicate} is empty or contains more than 1 element. + */ + public Person assignWithKeyword(Model model) throws CommandException { + model.updateFilteredPersonList(predicate); + List filteredList = model.getFilteredPersonList(); + + if (filteredList.isEmpty()) { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(MESSAGE_ASSIGN_EMPTY_PERSON_LIST_ERROR); + } else if (filteredList.size() == 1) { + return filteredList.get(0); + } else { + throw new CommandException(MESSAGE_DUPLICATE_HANDLING); + } + } + + /** + * Returns a {@code Person} with updated role and wedding job list. + * + * @param personToAddRole the original person from the contact list to be assigned. + * @param personWithRoleDescriptor details of the person, including the new role to be assigned. + * @return the updated {@code Person} with updated role and wedding(s). + * @throws CommandException if the wedding to be assigned is already assigned. + */ + private static Person createPersonWithRole(Person personToAddRole, + PersonWithRoleDescriptor personWithRoleDescriptor) + throws CommandException { + assert personToAddRole != null; + + Name updatedName = personWithRoleDescriptor.getName().orElse(personToAddRole.getName()); + Phone updatedPhone = personWithRoleDescriptor.getPhone().orElse(personToAddRole.getPhone()); + Email updatedEmail = personWithRoleDescriptor.getEmail().orElse(personToAddRole.getEmail()); + Address updatedAddress = personWithRoleDescriptor.getAddress().orElse(personToAddRole.getAddress()); + + // if the role is null -> r/ prefix was not specified, retain original role + // if role is Optional -> update role + Optional updatedRole = personWithRoleDescriptor.getRole() == null + ? personToAddRole.getRole() + : personWithRoleDescriptor.getRole(); + Wedding ownWedding = personToAddRole.getOwnWedding(); + + Person person = new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedRole, + ownWedding); + person.setWeddingJobs(personToAddRole.getWeddingJobs()); + + for (Wedding wedding : personWithRoleDescriptor.getWeddingJobs()) { + if (person.isAssignedToWedding(wedding)) { + throw new CommandException(MESSAGE_DUPLICATE_WEDDING); + } + } + + person.setWeddingJobs(personWithRoleDescriptor.getWeddingJobs()); + return person; + } + + /** + * Checks if the wedding indices inputs are valid. + * + * @param model {@code Model} which the command should operate on. + * @throws CommandException if the list is empty or if the index is invalid. + */ + public void checkValidWeddingIndices(Model model) throws CommandException { + List lastShownList = model.getFilteredWeddingList(); + + if (lastShownList.isEmpty()) { + throw new CommandException(MESSAGE_ASSIGN_EMPTY_WEDDING_LIST_ERROR); + } + + for (Index index : weddingIndices) { + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(String.format( + Messages.MESSAGE_INVALID_WEDDING_DISPLAYED_INDEX, index.getOneBased(), lastShownList.size())); + } + } + } + + /** + * Checks if person is client of the wedding to be assigned. + * + * @param model {@code Model} which the command should operate on. + * @param personToAssign the target person to check. + * @throws CommandException if the {@code personToAssign} is the client of the wedding to be assigned. + */ + public void checkIsClientOfWedding(Model model, Person personToAssign) throws CommandException { + List weddings = model.getFilteredWeddingList(); + for (Index index : weddingIndices) { + Wedding wedding = weddings.get(index.getZeroBased()); + if (personToAssign.getOwnWedding() != null && personToAssign.getOwnWedding().isSameWedding(wedding)) { + throw new CommandException(MESSAGE_CLIENT_ASSIGN_ERROR); + } + } + } + + /** + * Checks if any of the weddings to be assigned are already assigned to the person. + * + * @param model {@code Model} which the command should operate on. + * @param personToAssign the target person to check. + * @throws CommandException if any of the weddings to be assigned are already assigned to {@code personToAssign}. + */ + public void checkIsAssignedWeddings(Model model, Person personToAssign) throws CommandException { + List weddings = model.getFilteredWeddingList(); + for (Index index : weddingIndices) { + Wedding wedding = weddings.get(index.getZeroBased()); + if (personToAssign.isAssignedToWeddingNonClient(wedding)) { + throw new CommandException(MESSAGE_DUPLICATE_WEDDING); + } + } + } + + /** + * Assigns the person with wedding jobs based on the provided indices. + * + * @param model {@code Model} which the command should operate on, which contains the list of weddings. + */ + public void assignWeddingJobs(Model model) { + List weddingList = model.getFilteredWeddingList(); + + for (Index index : weddingIndices) { + personWithRoleDescriptor.addWeddingJob(weddingList.get(index.getZeroBased())); + } + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof AssignCommand)) { + return false; + } + + AssignCommand otherCommand = (AssignCommand) other; + + boolean indexEqual = Objects.equals(this.index, otherCommand.index); + boolean predicateEqual = Objects.equals(this.predicate, otherCommand.predicate); + boolean descriptorEqual = Objects.equals(this.personWithRoleDescriptor, otherCommand.personWithRoleDescriptor); + boolean weddingIndicesEqual = Objects.equals(this.weddingIndices, otherCommand.weddingIndices); + + return indexEqual && predicateEqual && descriptorEqual && weddingIndicesEqual; + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("index", index) + .add("personWithRoleDescriptor", personWithRoleDescriptor).toString(); + } + + /** + * Stores details about a person for the purpose of assigning or removing a role. + * Each non-empty field value will replace the corresponding field value of the person. + */ + public static class PersonWithRoleDescriptor { + private Name name; + private Phone phone; + private Email email; + private Address address; + private Optional role = Optional.empty(); + private Wedding ownWedding; + private Set weddingJobs = new HashSet<>(); + + /** + * Default constructor for {@code PersonWithRoleDescriptor}. + * Initializes an empty descriptor with no values set. + */ + public PersonWithRoleDescriptor() { + } + + /** + * Copy constructor. + * A defensive copy of {@code role} is used internally. + */ + public PersonWithRoleDescriptor(PersonWithRoleDescriptor toCopy) { + setName(toCopy.name); + setPhone(toCopy.phone); + setEmail(toCopy.email); + setAddress(toCopy.address); + setRole(toCopy.role); + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setPhone(Phone phone) { + this.phone = phone; + } + + public Optional getPhone() { + return Optional.ofNullable(phone); + } + + public void setEmail(Email email) { + this.email = email; + } + + public Optional getEmail() { + return Optional.ofNullable(email); + } + + public void setAddress(Address address) { + this.address = address; + } + + public Optional
    getAddress() { + return Optional.ofNullable(address); + } + + public void setRole(Optional role) { + this.role = role; + } + + public Optional getRole() { + return this.role; + } + + public Set getWeddingJobs() { + return weddingJobs; + } + + /** + * Adds a wedding to the list of wedding jobs. + * + * @param wedding {@code Wedding} to be added to the list of wedding jobs + */ + public void addWeddingJob(Wedding wedding) { + if (ownWedding == null || !ownWedding.isSameWedding(wedding)) { + weddingJobs.add(wedding); + } else { + throw new IllegalArgumentException("Cannot add own wedding as a job."); + } + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; // same object + } + if (!(other instanceof PersonWithRoleDescriptor)) { + return false; // different type + } + PersonWithRoleDescriptor otherDescriptor = (PersonWithRoleDescriptor) other; + + // Ensure all fields (including role) are compared correctly + return Objects.equals(name, otherDescriptor.name) + && Objects.equals(role, otherDescriptor.role); // add other fields as necessary + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("name", name) + .add("phone", phone) + .add("email", email) + .add("address", address) + .add("role", role) + .toString(); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index 1135ac19b74..34b9a380655 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -1,48 +1,209 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; import java.util.List; +import java.util.Set; 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.NameMatchesKeywordPredicate; import seedu.address.model.person.Person; +import seedu.address.model.wedding.Wedding; /** - * Deletes a person identified using it's displayed index from the address book. + * Deletes a person identified from the address book, using index or keyword. */ 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"; + + ": Deletes the person identified by the index number used in the displayed person list or keyword.\n" + + "OR deletes wedding jobs identified by index number used in displayed wedding list " + + "from specified person.\n" + + "Parameters: INDEX (must be a positive integer) or KEYWORD (the name of contact)\n" + + "Examples:\n" + COMMAND_WORD + " 1\n" + COMMAND_WORD + " alex\n" + + COMMAND_WORD + " 1 w/1 w/2"; + public static final String MESSAGE_DELETE_EMPTY_PERSON_LIST_ERROR = "There is no person to delete."; + public static final String MESSAGE_DELETE_EMPTY_WEDDING_LIST_ERROR = + "There is no wedding to assign as the wedding list is empty.\n" + + "Please refresh the list with a command (e.g. list, vieww)."; public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_REMOVE_WEDDING_JOBS_SUCCESS = "Removed wedding jobs from person: %1$s"; + + public static final String MESSAGE_DUPLICATE_HANDLING = + "Please specify the index of the contact you want to delete.\n" + + "Find the index from the list below and type delete INDEX\n" + + "Example: " + COMMAND_WORD + " 1"; + public static final String MESSAGE_PERSON_IS_CLIENT = + "Cannot delete this person as they are a client in a wedding.\n" + + "Please delete their wedding first."; + + public static final String MESSAGE_PERSON_NOT_ASSIGNED_WEDDING = + "Cannot unassign wedding(s) from this person because they are not assigned to the specified wedding(s)"; + private final Index targetIndex; + private final NameMatchesKeywordPredicate predicate; + private final Set weddingIndices; - public DeleteCommand(Index targetIndex) { + + /** + * Creates a {@code DeleteCommand} object to delete the person at the specified {@code Index} or keyword. + * + * @param targetIndex {@code Index} of the person in the filtered person list to delete. + * @param predicate {@code NameMatchesKeywordPredicate} used to filter the person list to find the target person. + * @param weddingIndices set of indices representing the weddings jobs to delete from the person. + */ + public DeleteCommand(Index targetIndex, NameMatchesKeywordPredicate predicate, Set weddingIndices) { this.targetIndex = targetIndex; + this.predicate = predicate; + this.weddingIndices = weddingIndices; } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); + Person personToDelete; + + if (this.targetIndex != null) { + personToDelete = getPersonByIndex(model); + } else { + personToDelete = getPersonByKeyword(model); + } + + boolean isDeleteWedding = weddingIndices != null; + + if (personToDelete == null) { + return new CommandResult(String.format(MESSAGE_DUPLICATE_HANDLING)); + } + + if (isDeleteWedding) { + checkValidWeddingIndices(model); + // check if person was assigned to those weddings + checkIsAssignedWeddings(model, personToDelete); + // delete those weddings + removeWeddingJobs(personToDelete, model); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); // Reset filter + return new CommandResult(String.format(MESSAGE_REMOVE_WEDDING_JOBS_SUCCESS, + Messages.format(personToDelete))); + } else { + validatePersonIsNotClient(personToDelete); + model.deletePerson(personToDelete); + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); // Reset filter + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); + } + } + + /** + * Returns the target person by index without deleting them. + * + * @param model {@code Model} which the command should operate on. + * @return the target person. + * @throws CommandException if the list is empty or if the index is invalid. + */ + private Person getPersonByIndex(Model model) throws CommandException { List lastShownList = model.getFilteredPersonList(); + if (lastShownList.isEmpty()) { + throw new CommandException(MESSAGE_DELETE_EMPTY_PERSON_LIST_ERROR); + } if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, + targetIndex.getOneBased(), lastShownList.size())); + } + + return lastShownList.get(targetIndex.getZeroBased()); + } + + /** + * Returns the target person by keyword without deleting them. + * + * @param model {@code Model} which the command should operate on. + * @return the target person (if only one person matched) or null (if multiple people matched). + * @throws CommandException if the list resulting from {@code predicate} is empty. + */ + private Person getPersonByKeyword(Model model) throws CommandException { + model.updateFilteredPersonList(predicate); + List filteredList = model.getFilteredPersonList(); + + if (filteredList.isEmpty()) { + throw new CommandException(MESSAGE_DELETE_EMPTY_PERSON_LIST_ERROR); + } else if (filteredList.size() == 1) { + return filteredList.get(0); + } else { + return null; + } + } + + /** + * Validates that the person to be deleted is not a client in any wedding. + * + * @param person the person to be checked. + * @throws CommandException if the person is a client in a wedding. + */ + private void validatePersonIsNotClient(Person person) throws CommandException { + if (person.getOwnWedding() != null) { + throw new CommandException(MESSAGE_PERSON_IS_CLIENT); + } + } + + /** + * Checks if wedding indices inputs are valid. + * + * @param model {@code Model} which the command should operate on. + * @throws CommandException if the list is empty or if the index is invalid. + */ + public void checkValidWeddingIndices(Model model) throws CommandException { + List lastShownList = model.getFilteredWeddingList(); + + if (lastShownList.isEmpty()) { + throw new CommandException(MESSAGE_DELETE_EMPTY_WEDDING_LIST_ERROR); } - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); + for (Index index : weddingIndices) { + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(String.format( + Messages.MESSAGE_INVALID_WEDDING_DISPLAYED_INDEX, index.getOneBased(), lastShownList.size())); + } + } + } + + /** + * Checks if the person is assigned to weddings before attempting to remove them. + * + * @param personToDelete the person to be checked. + * @param model {@code Model} which the command should operate on, which contains the list of weddings. + * @throws CommandException if person is not assigned weddings. + */ + public void checkIsAssignedWeddings(Model model, Person personToDelete) throws CommandException { + List weddingList = model.getFilteredWeddingList(); + for (Index index : weddingIndices) { + Wedding wedding = weddingList.get(index.getZeroBased()); + if (!personToDelete.isAssignedToWeddingNonClient(wedding)) { + throw new CommandException(MESSAGE_PERSON_NOT_ASSIGNED_WEDDING); + } + } + } + + /** + * Removes specified wedding jobs from the person. + * + * @param personToDelete the target person getting wedding jobs removed. + * @param model {@code Model} which the command should operate on, which contains the list of weddings. + */ + public void removeWeddingJobs(Person personToDelete, Model model) { + List weddingList = model.getFilteredWeddingList(); + for (Index index : weddingIndices) { + personToDelete.removeWeddingJob(weddingList.get(index.getZeroBased())); + } } @Override @@ -57,13 +218,44 @@ public boolean equals(Object other) { } DeleteCommand otherDeleteCommand = (DeleteCommand) other; - return targetIndex.equals(otherDeleteCommand.targetIndex); + + // Both commands have null fields + boolean bothHaveNullIndex = targetIndex == null && otherDeleteCommand.targetIndex == null; + boolean bothHaveNullPredicates = predicate == null && otherDeleteCommand.predicate == null; + + // Both commands have non-null fields + boolean bothHaveIndex = targetIndex != null && otherDeleteCommand.targetIndex != null; + boolean bothHavePredicates = predicate != null && otherDeleteCommand.predicate != null; + + // Case 1: Both have null targetIndex and null predicate + if (bothHaveNullIndex && bothHaveNullPredicates) { + return true; + } + + // Case 2: Both have targetIndex but null predicate + if (bothHaveIndex && bothHaveNullPredicates) { + return targetIndex.equals(otherDeleteCommand.targetIndex); + } + + // Case 3: Both have null targetIndex but have predicate + if (bothHaveNullIndex && bothHavePredicates) { + return predicate.equals(otherDeleteCommand.predicate); + } + + // All other cases are false + return false; } @Override public String toString() { - return new ToStringBuilder(this) - .add("targetIndex", targetIndex) - .toString(); + if (this.targetIndex != null) { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } else { + return new ToStringBuilder(this) + .add("targetKeywords", predicate.toString()) + .toString(); + } } } diff --git a/src/main/java/seedu/address/logic/commands/DeletewCommand.java b/src/main/java/seedu/address/logic/commands/DeletewCommand.java new file mode 100644 index 00000000000..0590b0d7d19 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeletewCommand.java @@ -0,0 +1,166 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_WEDDINGS; + +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.wedding.NameMatchesWeddingPredicate; +import seedu.address.model.wedding.Wedding; + +/** + * Deletes a wedding identified from the address book, using index or keyword. + */ +public class DeletewCommand extends Command { + public static final String COMMAND_WORD = "deletew"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the wedding identified by the index number used in the displayed wedding list or keyword.\n" + + "Parameters: INDEX (must be a positive integer) or KEYWORD (the name of wedding)\n" + + "Example: " + COMMAND_WORD + " 1 or " + COMMAND_WORD + " alex"; + + public static final String MESSAGE_DELETE_EMPTY_LIST_ERROR = "There is nothing to delete."; + public static final String MESSAGE_DELETE_WEDDING_SUCCESS = "Deleted Wedding: %1$s"; + public static final String MESSAGE_DUPLICATE_HANDLING = + "Please specify the index of the wedding you want to delete.\n" + + "Find the index from the list below and type deletew INDEX\n" + + "Example: " + COMMAND_WORD + " 1"; + private final Index targetIndex; + private final NameMatchesWeddingPredicate predicate; + + /** + * Creates a {@code DeletewCommand} object to delete the wedding at the specified {@code Index} or keyword. + * + * @param targetIndex {@code Index} of the wedding in the filtered wedding list to delete. + * @param predicate {@code NameMatchesWeddingPredicate} used to filter the wedding list to find the target wedding. + */ + public DeletewCommand(Index targetIndex, NameMatchesWeddingPredicate predicate) { + this.targetIndex = targetIndex; + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (this.targetIndex != null) { + Wedding weddingToDelete = getWeddingByIndex(model); + model.deleteWedding(weddingToDelete); + model.updateFilteredWeddingList(PREDICATE_SHOW_ALL_WEDDINGS); // Reset filter + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_DELETE_WEDDING_SUCCESS, Messages.format(weddingToDelete))); + } else { + Wedding weddingToDelete = getWeddingByKeyword(model); + if (weddingToDelete != null) { + model.deleteWedding(weddingToDelete); + model.updateFilteredWeddingList(PREDICATE_SHOW_ALL_WEDDINGS); // Reset filter + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_DELETE_WEDDING_SUCCESS, + Messages.format(weddingToDelete))); + } else { + return new CommandResult(String.format(MESSAGE_DUPLICATE_HANDLING)); + } + } + } + + /** + * Returns the wedding by index without deleting them. + * + * @param model {@code Model} which the command should operate on. + * @return the target wedding. + * @throws CommandException if the list is empty or if the index is invalid. + */ + private Wedding getWeddingByIndex(Model model) throws CommandException { + List lastShownList = model.getFilteredWeddingList(); + if (lastShownList.isEmpty()) { + throw new CommandException(MESSAGE_DELETE_EMPTY_LIST_ERROR); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(String.format( + Messages.MESSAGE_INVALID_WEDDING_DISPLAYED_INDEX, targetIndex.getOneBased(), lastShownList.size())); + } + + return lastShownList.get(targetIndex.getZeroBased()); + } + + /** + * Returns the wedding by keyword without deleting them. + * + * @param model {@code Model} which the command should operate on. + * @return the target wedding (if only one wedding matched) or null (if multiple weddings matched). + * @throws CommandException if the list resulting from {@code predicate} is empty. + */ + private Wedding getWeddingByKeyword(Model model) throws CommandException { + model.updateFilteredWeddingList(predicate); + List filteredList = model.getFilteredWeddingList(); + + if (filteredList.isEmpty()) { + throw new CommandException(MESSAGE_DELETE_EMPTY_LIST_ERROR); + } else if (filteredList.size() == 1) { + return filteredList.get(0); + } else { + return null; + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeletewCommand)) { + return false; + } + + DeletewCommand otherDeletewCommand = (DeletewCommand) other; + + // Both commands have null fields + boolean bothHaveNullIndex = targetIndex == null && otherDeletewCommand.targetIndex == null; + boolean bothHaveNullPredicates = predicate == null && otherDeletewCommand.predicate == null; + + // Both commands have non-null fields + boolean bothHaveIndex = targetIndex != null && otherDeletewCommand.targetIndex != null; + boolean bothHavePredicates = predicate != null && otherDeletewCommand.predicate != null; + + // Case 1: Both have null targetIndex and null predicate + if (bothHaveNullIndex && bothHaveNullPredicates) { + return true; + } + + // Case 2: Both have targetIndex but null predicate + if (bothHaveIndex && bothHaveNullPredicates) { + return targetIndex.equals(otherDeletewCommand.targetIndex); + } + + // Case 3: Both have null targetIndex but have predicate + if (bothHaveNullIndex && bothHavePredicates) { + return predicate.equals(otherDeletewCommand.predicate); + } + + // All other cases are false + return false; + } + + @Override + public String toString() { + if (this.targetIndex != null) { + return new ToStringBuilder(this) + .add("targetIndex", targetIndex) + .toString(); + } else { + return new ToStringBuilder(this) + .add("targetKeywords", predicate.toString()) + .toString(); + } + + } +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index 4b581c7331e..5b6637ff487 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -5,11 +5,9 @@ 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 java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -24,12 +22,16 @@ import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; +import seedu.address.model.person.NameMatchesKeywordPredicate; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.address.model.role.Role; +import seedu.address.model.wedding.Client; +import seedu.address.model.wedding.Wedding; /** * Edits the details of an existing person in the address book. + * The details that can be edited are name, phone, email and address. */ public class EditCommand extends Command { @@ -38,59 +40,142 @@ public class EditCommand extends Command { public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " + "by the index number used in the displayed person list. " + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " + + "Parameters: INDEX (must be a positive integer) or KEYWORD (the name of contact) " + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; + + "[" + PREFIX_ADDRESS + "ADDRESS] \n" + + "Example: \n" + + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " + PREFIX_EMAIL + "johndoe@example.com\n" + + COMMAND_WORD + " John " + PREFIX_NAME + "John Tan " + PREFIX_PHONE + "91238923"; 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_NO_CHANGE = "Fields provided are the same as before, no edit is made."; public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_DUPLICATE_PHONE = "This number already exists in the address book."; + public static final String MESSAGE_DUPLICATE_EMAIL = "This email already exists in the address book."; + public static final String MESSAGE_EDIT_EMPTY_LIST_ERROR = "There is no contact to edit."; + public static final String MESSAGE_DUPLICATE_HANDLING = + "Please specify the index of the contact you want to edit.\n" + + "Find the index from the list below and type edit INDEX ...\n" + + "Example: " + COMMAND_WORD + " 1 ..."; private final Index index; + private final NameMatchesKeywordPredicate predicate; private final EditPersonDescriptor editPersonDescriptor; /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with + * Creates a {@code EditCommand} object to edit the person at the specified {@code Index} or keyword. + * + * @param index {@code Index} of the person in the filtered person list to delete. + * @param predicate {@code NameMatchesKeywordPredicate} used to filter the person list to find the target person. + * @param editPersonDescriptor details of what is to be edited in the person. */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); + public EditCommand(Index index, NameMatchesKeywordPredicate predicate, EditPersonDescriptor editPersonDescriptor) { requireNonNull(editPersonDescriptor); this.index = index; + this.predicate = predicate; this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); } @Override public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); + Person personToEdit; - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + if (this.index != null) { + personToEdit = editWithIndex(model); + } else { + personToEdit = editWithKeyword(model); } - Person personToEdit = lastShownList.get(index.getZeroBased()); Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); + if (personToEdit.isSamePerson(editedPerson)) { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(MESSAGE_NO_CHANGE); + } + if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); throw new CommandException(MESSAGE_DUPLICATE_PERSON); } + if (!personToEdit.getPhone().equals(editedPerson.getPhone()) && model.hasPhone(editedPerson)) { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(MESSAGE_DUPLICATE_PHONE); + } + + if (!personToEdit.getEmail().equals(editedPerson.getEmail()) && model.hasEmail(editedPerson)) { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(MESSAGE_DUPLICATE_EMAIL); + } + model.setPerson(personToEdit, editedPerson); + Wedding ownWedding = editedPerson.getOwnWedding(); + if (ownWedding != null) { + Wedding editedWedding = new Wedding(ownWedding.getName(), new Client(editedPerson), ownWedding.getDate(), + ownWedding.getVenue()); + model.setWedding(ownWedding, editedWedding); + model.updatePersonEditedWedding(ownWedding, editedWedding); + } + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson))); } /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. + * Returns the target person to edit, by index. + * + * @param model {@code Model} which the command should operate on. + * @return the person to be edited. + * @throws CommandException if the list is empty or if the index is invalid. + */ + public Person editWithIndex(Model model) throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (lastShownList.isEmpty()) { + throw new CommandException(MESSAGE_EDIT_EMPTY_LIST_ERROR); + } + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, + index.getOneBased(), lastShownList.size())); + } + + return lastShownList.get(index.getZeroBased()); + } + + /** + * Returns the target person to edit, by keyword. + * + * @param model {@code Model} which the command should operate on. + * @return the person to be edited. + * @throws CommandException if the filtered list using {@code predicate} is empty or contains more than 1 element. + */ + public Person editWithKeyword(Model model) throws CommandException { + model.updateFilteredPersonList(predicate); + List filteredList = model.getFilteredPersonList(); + + if (filteredList.isEmpty()) { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(MESSAGE_EDIT_EMPTY_LIST_ERROR); + } else if (filteredList.size() == 1) { + return filteredList.get(0); + } else { + throw new CommandException(MESSAGE_DUPLICATE_HANDLING); + } + } + + /** + * Returns a {@code Person} with updated name, phone, email or address. + * + * @param personToEdit the target person. + * @param editPersonDescriptor details of what is to be edited in the person. + * @return updated {@code Person} with the edited information. */ private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { assert personToEdit != null; @@ -99,9 +184,14 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript 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()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + Optional updatedRole = personToEdit.getRole(); + Wedding ownWedding = personToEdit.getOwnWedding(); + Set weddingJobs = personToEdit.getWeddingJobs(); + + Person editedPerson = new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, + updatedRole, ownWedding); + editedPerson.setWeddingJobs(weddingJobs); + return editedPerson; } @Override @@ -137,27 +227,29 @@ public static class EditPersonDescriptor { private Phone phone; private Email email; private Address address; - private Set tags; + private Role role; + private Set weddingJobs; public EditPersonDescriptor() {} /** * Copy constructor. - * A defensive copy of {@code tags} is used internally. + * A defensive copy of {@code role} is used internally. */ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); setAddress(toCopy.address); - setTags(toCopy.tags); + setRole(toCopy.role); + setWeddings(toCopy.weddingJobs); } /** * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); + return CollectionUtil.isAnyNonNull(name, phone, email, address); } public void setName(Name name) { @@ -193,20 +285,30 @@ public Optional
    getAddress() { } /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. + * Sets {@code role} to this object's {@code role}. + * A defensive copy of {@code role} is used internally. + * + * @param role the role to be set. */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; + public void setRole(Role role) { + this.role = role; } /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} + * Returns an unmodifiable role set, which throws {@code UnsupportedOperationException} * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. + * Returns {@code Optional#empty()} if {@code role} is null. */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); + public Optional getRole() { + return (role != null) ? Optional.of(role) : Optional.empty(); + } + + public void setWeddings(Set weddingJobs) { + this.weddingJobs = weddingJobs; + } + + public Optional> getWeddingJobs() { + return (weddingJobs != null) ? Optional.of(Collections.unmodifiableSet(weddingJobs)) : Optional.empty(); } @Override @@ -224,8 +326,7 @@ public boolean equals(Object other) { return Objects.equals(name, otherEditPersonDescriptor.name) && Objects.equals(phone, otherEditPersonDescriptor.phone) && Objects.equals(email, otherEditPersonDescriptor.email) - && Objects.equals(address, otherEditPersonDescriptor.address) - && Objects.equals(tags, otherEditPersonDescriptor.tags); + && Objects.equals(address, otherEditPersonDescriptor.address); } @Override @@ -235,7 +336,7 @@ public String toString() { .add("phone", phone) .add("email", email) .add("address", address) - .add("tags", tags) + .add("role", role != null ? role.toString() : "") .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/EditwCommand.java b/src/main/java/seedu/address/logic/commands/EditwCommand.java new file mode 100644 index 00000000000..344451de31e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditwCommand.java @@ -0,0 +1,167 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_VENUE; + +import java.util.List; +import java.util.Optional; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Name; +import seedu.address.model.wedding.Date; +import seedu.address.model.wedding.Venue; +import seedu.address.model.wedding.Wedding; + +/** + * Edits the details of an existing wedding in wedding list. Details that can be edited are name, date and venue. + */ +public class EditwCommand extends Command { + + public static final String COMMAND_WORD = "editw"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the wedding identified " + + "by the index number used in the displayed wedding list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: w/INDEX " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_DATE + "DATE] " + + "[" + PREFIX_VENUE + "VENUE]\n" + + "Example: " + COMMAND_WORD + " w/1 " + PREFIX_NAME + "Wedding2 " + PREFIX_DATE + "2024-11-01"; + + public static final String MESSAGE_EDIT_WEDDING_SUCCESS = "Edited Wedding: %1$s"; + public static final String MESSAGE_INVALID_WEDDING_INDEX = "The wedding index provided is invalid, please " + + "enter an index that is between 1 and %1$d"; + + public static final String MESSAGE_EDIT_EMPTY_LIST_ERROR = "There is nothing to edit."; + + private final Index index; + private final EditWeddingDescriptor editWeddingDescriptor; + + /** + * Creates a {@code EditwCommand} object to edit the wedding at the specified {@code Index}. + * + * @param index {@code Index} of the wedding in the filtered wedding list to edit. + * @param editWeddingDescriptor details of what is to be edited in the wedding. + */ + public EditwCommand(Index index, EditWeddingDescriptor editWeddingDescriptor) { + requireNonNull(index); + requireNonNull(editWeddingDescriptor); + + this.index = index; + this.editWeddingDescriptor = editWeddingDescriptor; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (!editWeddingDescriptor.isAnyFieldEdited()) { + throw new CommandException("No fields specified to edit."); + } + + List lastShownList = model.getFilteredWeddingList(); + + if (lastShownList.isEmpty()) { + throw new CommandException(MESSAGE_EDIT_EMPTY_LIST_ERROR); + } + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(String.format(MESSAGE_INVALID_WEDDING_INDEX, + lastShownList.size())); + } + + Wedding weddingToEdit = lastShownList.get(index.getZeroBased()); + Wedding editedWedding = createEditedWedding(weddingToEdit, editWeddingDescriptor); + model.updatePersonEditedWedding(weddingToEdit, editedWedding); + + + model.setWedding(weddingToEdit, editedWedding); + return new CommandResult(String.format(MESSAGE_EDIT_WEDDING_SUCCESS, Messages.format(editedWedding))); + } + + /** + * Returns a {@code Wedding} with updated name, date or venue. + * + * @param weddingToEdit the target wedding. + * @param descriptor details of what is to be edited in the wedding. + * @return updated {@code Wedding} with the edited information. + */ + static Wedding createEditedWedding(Wedding weddingToEdit, EditWeddingDescriptor descriptor) { + assert weddingToEdit != null; + + Name updatedName = descriptor.getName().orElse(weddingToEdit.getName()); + Date updatedDate = descriptor.getDate().orElse(weddingToEdit.getDate()); + Venue updatedVenue = descriptor.getVenue().orElse(weddingToEdit.getVenue()); + + return new Wedding(updatedName, weddingToEdit.getClient(), updatedDate, updatedVenue); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof EditwCommand + && index.equals(((EditwCommand) other).index) + && editWeddingDescriptor.equals(((EditwCommand) other).editWeddingDescriptor)); + } + + /** + * Stores the details to edit the wedding with. Each non-empty field value will replace the + * corresponding field value of the wedding. + */ + public static class EditWeddingDescriptor { + private Name name; + private Date date; + private Venue venue; + + public EditWeddingDescriptor() {} + + public boolean isAnyFieldEdited() { + return name != null || date != null || venue != null; + } + + public void setName(Name name) { + this.name = name; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public void setDate(Date date) { + this.date = date; + } + + public Optional getDate() { + return Optional.ofNullable(date); + } + + public void setVenue(Venue venue) { + this.venue = venue; + } + + public Optional getVenue() { + return Optional.ofNullable(venue); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof EditWeddingDescriptor)) { + return false; + } + + EditWeddingDescriptor e = (EditWeddingDescriptor) other; + return getName().equals(e.getName()) + && getDate().equals(e.getDate()) + && getVenue().equals(e.getVenue()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/FilterCommand.java b/src/main/java/seedu/address/logic/commands/FilterCommand.java new file mode 100644 index 00000000000..d6213e39775 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FilterCommand.java @@ -0,0 +1,145 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; + +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.AddressContainsKeywordsPredicate; +import seedu.address.model.person.Email; +import seedu.address.model.person.EmailContainsKeywordsPredicate; +import seedu.address.model.person.Name; +import seedu.address.model.person.NameMatchesKeywordPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.PhoneContainsKeywordsPredicate; +import seedu.address.model.person.RoleContainsKeywordsPredicate; +import seedu.address.model.role.Role; + +/** + * Filters and lists all persons in address book whose fields (name, role, email, phone, address) + * match any of the specified keywords (case-insensitive) and displays them as a list with index numbers. + * Keyword matching is case-insensitive. + */ +public class FilterCommand extends Command { + + public static final String COMMAND_WORD = "filter"; + + public static final String MESSAGE_NO_CRITERIA = "At least one filter criteria must be provided"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Filters persons by multiple criteria. " + + "At least one field must be specified.\n" + + "Parameters: [n/NAME] [r/ROLE] [e/EMAIL] [p/PHONE] [a/ADDRESS]...\n" + + "Example: " + COMMAND_WORD + " n/John r/vendor"; + + private final Name name; + private final Optional role; + private final Email email; + private final Phone phone; + private final Address address; + + /** + * Creates a {@code FilterCommand} object to filter persons by the specified {@code name}, + * {@code role}, {@code email}, {@code phone} and {@code address}. + * + * @param name the {@code Name} field to check against. + * @param role the {@code Role} field to check against. + * @param email the {@code Email} field to check against. + * @param phone the {@code Phone} field to check against. + * @param address the {@code Address} field to check against. + */ + public FilterCommand(Name name, Optional role, Email email, Phone phone, Address address) { + this.name = name; + this.role = role; + this.email = email; + this.phone = phone; + this.address = address; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + List> predicates = new ArrayList<>(); + + if (name != null) { + String[] wordNames = name.toString().toLowerCase().split("\\s+"); + predicates.add(new NameMatchesKeywordPredicate(Arrays.asList(wordNames))); + } + + if (role != null) { + predicates.add(new RoleContainsKeywordsPredicate(Arrays.asList(role.get().toString().toLowerCase()))); + } + + if (email != null) { + predicates.add(new EmailContainsKeywordsPredicate(Arrays.asList(email.toString().toLowerCase()))); + } + + if (phone != null) { + predicates.add(new PhoneContainsKeywordsPredicate(Arrays.asList(phone.toString()))); + } + + if (address != null) { + predicates.add(new AddressContainsKeywordsPredicate(Arrays.asList(address.toString().toLowerCase()))); + } + + if (predicates.isEmpty()) { + throw new CommandException(MESSAGE_NO_CRITERIA); + } + + Predicate combinedPredicate = predicates.stream() + .reduce(person -> false, Predicate::or); + + model.updateFilteredPersonList(combinedPredicate); + + return new CommandResult( + String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof FilterCommand)) { + return false; + } + + FilterCommand otherFilterCommand = (FilterCommand) other; + + boolean equalName = (name == null && otherFilterCommand.name == null) + || name != null && name.equals(otherFilterCommand.name); + + boolean equalRole = (role == null && otherFilterCommand.role == null) + || role != null && role.equals(otherFilterCommand.role); + + boolean equalEmail = (email == null && otherFilterCommand.email == null) + || email != null && email.equals(otherFilterCommand.email); + + boolean equalPhone = (phone == null && otherFilterCommand.phone == null) + || phone != null && phone.equals(otherFilterCommand.phone); + + boolean equalAddress = (address == null && otherFilterCommand.address == null) + || address != null && address.equals(otherFilterCommand.address); + + return equalName && equalRole && equalEmail && equalPhone && equalAddress; + } + + @Override + public String toString() { + return "FilterCommand{" + + "name='" + name + '\'' + + ", role='" + role + '\'' + + ", email='" + email + '\'' + + ", phone='" + phone + '\'' + + ", address='" + address + '\'' + + '}'; + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 72b9eddd3a7..2d2b62d8aa1 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -9,7 +9,7 @@ /** * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. + * Keyword matching is case-insensitive. */ public class FindCommand extends Command { diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..e2a3eba04e4 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -2,6 +2,7 @@ import static java.util.Objects.requireNonNull; import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_WEDDINGS; import seedu.address.model.Model; @@ -12,13 +13,14 @@ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_SUCCESS = "Listed all persons and weddings"; @Override public CommandResult execute(Model model) { requireNonNull(model); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredWeddingList(PREDICATE_SHOW_ALL_WEDDINGS); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/address/logic/commands/ViewCommand.java b/src/main/java/seedu/address/logic/commands/ViewCommand.java new file mode 100644 index 00000000000..7673d5b7b1f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewCommand.java @@ -0,0 +1,191 @@ +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.NameMatchesKeywordPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.wedding.PersonHasWeddingPredicate; +import seedu.address.model.wedding.Wedding; +import seedu.address.model.wedding.WeddingMatchesClientPredicate; + + +/** + * Views a person identified from the address book, using index or keyword. + * 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 + ": View the contact you want to see.\n" + + "Parameters: INDEX (must be a positive integer) or KEYWORD (the name of contact, " + + "case-insensitive)\n" + + "Example: " + COMMAND_WORD + " alice or " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_VIEW_EMPTY_LIST_ERROR = "There is no contact to view."; + + public static final String MESSAGE_VIEW_PERSON_SUCCESS = "Viewing contact: %s"; + + public static final String MESSAGE_DUPLICATE_HANDLING = + "To view a specific contact, please specify the index of the contact you want to view.\n" + + "Find the index from the list below and type view INDEX\n" + + "Example: " + COMMAND_WORD + " 1"; + private static final String MESSAGE_MULTIPLE_WEDDING = "The client %s has multiple weddings."; + + private final Index targetIndex; + private final NameMatchesKeywordPredicate predicate; + + /** + * Creates a {@code ViewCommand} object to view the person at the specified {@code Index} or keyword. + * + * @param targetIndex {@code Index} of the person in the filtered person list to view. + * @param predicate {@code NameMatchesKeywordPredicate} used to filter the person list to find the target person. + */ + public ViewCommand(Index targetIndex, NameMatchesKeywordPredicate predicate) { + this.targetIndex = targetIndex; + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (this.targetIndex != null) { + Person personToView = getPersonByIndex(model); + model.updateFilteredPersonList(p -> p.equals(personToView)); + updateWedding(personToView, model); + model.updateFilteredWeddingListWithOwnWedding(new PersonHasWeddingPredicate(personToView)); + return new CommandResult(String.format(MESSAGE_VIEW_PERSON_SUCCESS, personToView.getName())); + } else { + Person personToView = getPersonByKeyword(model); + if (personToView != null) { + // unique person found + updateWedding(personToView, model); + model.updateFilteredWeddingListWithOwnWedding(new PersonHasWeddingPredicate(personToView)); + return new CommandResult(String.format(MESSAGE_VIEW_PERSON_SUCCESS, personToView.getName())); + } else { + return new CommandResult(String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, + model.getFilteredPersonList().size()) + "\n" + MESSAGE_DUPLICATE_HANDLING); + } + } + } + + /** + * Returns the target person by index. + * + * @param model {@code Model} which the command should operate on. + * @return the target person to be viewed. + * @throws CommandException if the list is empty or if the index is invalid. + */ + private Person getPersonByIndex(Model model) throws CommandException { + List lastShownList = model.getFilteredPersonList(); + if (lastShownList.isEmpty()) { + throw new CommandException(MESSAGE_VIEW_EMPTY_LIST_ERROR); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException( + String.format(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX, + targetIndex.getOneBased(), lastShownList.size())); + } + + return lastShownList.get(targetIndex.getZeroBased()); + } + + /** + * Returns the target person by keyword. + * + * @param model {@code Model} which the command should operate on. + * @return the target person (if only one person matched) or null (if multiple people matched). + * @throws CommandException if the list resulting from {@code predicate} is empty. + */ + private Person getPersonByKeyword(Model model) throws CommandException { + model.updateFilteredPersonList(predicate); + List filteredList = model.getFilteredPersonList(); + + if (filteredList.isEmpty()) { + throw new CommandException(MESSAGE_VIEW_EMPTY_LIST_ERROR); + } else if (filteredList.size() == 1) { + return filteredList.get(0); + } else { + return null; + } + } + + /** + * Updates the filtered wedding list with the client's own wedding, if any. + * + * @param client the {@code Person} to be checked. + * @param model {@code Model} which the command should operate on. + * @throws CommandException if the {@code client} owns multiple weddings. + */ + private void updateWedding(Person client, Model model) throws CommandException { + WeddingMatchesClientPredicate clientPredicate = new WeddingMatchesClientPredicate(client); + model.updateFilteredWeddingList(clientPredicate); + List filteredList = model.getFilteredWeddingList(); + + if (filteredList.size() == 1) { + Wedding ownWedding = filteredList.get(0); + Wedding updatedWedding = new Wedding(ownWedding.getName(), ownWedding.getClient(), + ownWedding.getDate(), ownWedding.getVenue()); + updatedWedding.setIsOwnWedding(true); + model.setWedding(ownWedding, updatedWedding); + } else if (filteredList.size() > 1) { + throw new CommandException(String.format(MESSAGE_MULTIPLE_WEDDING, client.getName())); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ViewCommand)) { + return false; + } + + ViewCommand otherViewCommand = (ViewCommand) other; + + // Both commands have null fields + boolean bothHaveNullIndex = targetIndex == null && otherViewCommand.targetIndex == null; + boolean bothHaveNullPredicates = predicate == null && otherViewCommand.predicate == null; + + // Both commands have non-null fields + boolean bothHaveIndex = targetIndex != null && otherViewCommand.targetIndex != null; + boolean bothHavePredicates = predicate != null && otherViewCommand.predicate != null; + + // Case 1: Both have null targetIndex and null predicate + if (bothHaveNullIndex && bothHaveNullPredicates) { + return true; + } + + // Case 2: Both have targetIndex but null predicate + if (bothHaveIndex && bothHaveNullPredicates) { + return targetIndex.equals(otherViewCommand.targetIndex); + } + + // Case 3: Both have null targetIndex but have predicate + if (bothHaveNullIndex && bothHavePredicates) { + return predicate.equals(otherViewCommand.predicate); + } + + // All other cases are false + return false; + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ViewwCommand.java b/src/main/java/seedu/address/logic/commands/ViewwCommand.java new file mode 100644 index 00000000000..687acdb1408 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ViewwCommand.java @@ -0,0 +1,198 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; + +import java.util.List; +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.ClientMatchesWeddingPredicate; +import seedu.address.model.person.Person; +import seedu.address.model.person.PersonMatchesWeddingPredicate; +import seedu.address.model.wedding.NameMatchesWeddingPredicate; +import seedu.address.model.wedding.Wedding; + +/** + * Views wedding details of a person identified from the address book, using index or keyword. + * Keyword matching is case-insensitive. + */ +public class ViewwCommand extends Command { + + public static final String COMMAND_WORD = "vieww"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": View the wedding details of the person identified by the index number used in " + + "the displayed person list or the wedding name.\n" + + "Parameters: INDEX (must be a positive integer) or WEDDINGNAME (the name of wedding, case insensitive)\n" + + "Example: " + COMMAND_WORD + " 1" + " or " + COMMAND_WORD + " alex wedding"; + + public static final String MESSAGE_VIEW_EMPTY_LIST_ERROR = "There are no wedding records to view."; + public static final String MESSAGE_VIEW_WEDDING_SUCCESS = "Viewing Wedding Details of: %1$s"; + + public static final String MESSAGE_DUPLICATE_HANDLING = + "Please specify the index of the wedding which wedding details you want to view.\n" + + "Find the index from the list below and type vieww INDEX\n" + + "Example: " + COMMAND_WORD + " 1"; + public static final String MESSAGE_NO_CLIENT = "No client found for: %1$s"; + public static final String MESSAGE_MULTIPLE_CLIENT = "Multiple client found for: %1$s"; + + private final Index targetIndex; + private final NameMatchesWeddingPredicate predicate; + + /** + * Creates a {@code ViewwCommand} to view the wedding details of the specified wedding. + * + * @param targetIndex {@code Index} of the wedding in the filtered wedding list to view. + * @param predicate {@code NameMatchesWeddingPredicate} used to filter the wedding list to find the target wedding. + */ + public ViewwCommand(Index targetIndex, NameMatchesWeddingPredicate predicate) { + this.targetIndex = targetIndex; + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (this.targetIndex != null) { + Wedding weddingToView = viewWithIndex(model); + PersonMatchesWeddingPredicate weddingPredicateToView = new PersonMatchesWeddingPredicate(weddingToView); + updateClient(weddingToView, model); + model.updateFilteredPersonListWithClient(weddingPredicateToView); + + return new CommandResult(String.format(MESSAGE_VIEW_WEDDING_SUCCESS, + Messages.format(weddingPredicateToView.getWedding()))); + + } else { + Wedding weddingToView = viewWithKeyword(model); + PersonMatchesWeddingPredicate weddingPredicateToView = new PersonMatchesWeddingPredicate(weddingToView); + + if (weddingToView != null) { + model.updateFilteredPersonList(weddingPredicateToView); + updateClient(weddingToView, model); + return new CommandResult(String.format(MESSAGE_VIEW_WEDDING_SUCCESS, + Messages.format(weddingPredicateToView.getWedding()))); + } else { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + return new CommandResult(String.format(MESSAGE_DUPLICATE_HANDLING)); + } + } + } + + /** + * Returns the target wedding by index. + * + * @param model {@code Model} which the command should operate on. + * @return the target wedding to be viewed. + * @throws CommandException if the list is empty or if the index is invalid. + */ + private Wedding viewWithIndex(Model model) throws CommandException { + List lastShownList = model.getFilteredWeddingList(); + if (lastShownList.isEmpty()) { + throw new CommandException(MESSAGE_VIEW_EMPTY_LIST_ERROR); + } + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(String.format( + Messages.MESSAGE_INVALID_WEDDING_DISPLAYED_INDEX, targetIndex.getOneBased(), lastShownList.size())); + } + Wedding weddingToView = lastShownList.get(targetIndex.getZeroBased()); + model.updateFilteredWeddingList(p -> p.equals(weddingToView)); + + return weddingToView; + } + + /** + * Returns the target wedding by keyword. + * + * @param model {@code Model} which the command should operate on. + * @return the target wedding (if only one wedding matched) or null (if multiple wedding matched). + * @throws CommandException if the list resulting from {@code predicate} is empty. + */ + private Wedding viewWithKeyword(Model model) throws CommandException { + model.updateFilteredWeddingList(predicate); + List filteredList = model.getFilteredWeddingList(); + + if (filteredList.isEmpty()) { + model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + throw new CommandException(MESSAGE_VIEW_EMPTY_LIST_ERROR); + } else if (filteredList.size() == 1) { + Wedding weddingToView = filteredList.get(0); + return weddingToView; + } else { + return null; + } + } + + /** + * Updates the filtered person list with the client and vendors (if any) of the {@code weddingToView}. + * + * @param weddingToView the {@code Wedding} to be checked. + * @param model {@code Model} which the command should operate on. + * @throws CommandException if the {@code weddingToView} does not have a client or has multiple clients. + */ + private void updateClient(Wedding weddingToView, Model model) throws CommandException { + ClientMatchesWeddingPredicate clientPredicate = new ClientMatchesWeddingPredicate(weddingToView); + model.updateFilteredPersonList(clientPredicate); + List filteredList = model.getFilteredPersonList(); + + if (filteredList.isEmpty()) { + throw new CommandException(String.format(MESSAGE_NO_CLIENT, weddingToView.getName())); + } else if (filteredList.size() == 1) { + Person clientOfWedding = filteredList.get(0); + Set weddingJobs = clientOfWedding.getWeddingJobs(); + Person updatedClient = new Person(clientOfWedding.getName(), clientOfWedding.getPhone(), + clientOfWedding.getEmail(), clientOfWedding.getAddress(), clientOfWedding.getRole(), + weddingToView); + updatedClient.setWeddingJobs(weddingJobs); + updatedClient.setIsClient(true); + model.setPerson(clientOfWedding, updatedClient); + } else { + throw new CommandException(String.format(MESSAGE_MULTIPLE_CLIENT, weddingToView.getName())); + } + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ViewwCommand)) { + return false; + } + + ViewwCommand otherViewwCommand = (ViewwCommand) other; + + // Both commands have null fields + boolean bothHaveNullIndex = targetIndex == null && otherViewwCommand.targetIndex == null; + boolean bothHaveNullPredicates = predicate == null && otherViewwCommand.predicate == null; + + // Both commands have non-null fields + boolean bothHaveIndex = targetIndex != null && otherViewwCommand.targetIndex != null; + boolean bothHavePredicates = predicate != null && otherViewwCommand.predicate != null; + + // Case 1: Both have null targetIndex and null predicate + if (bothHaveNullIndex && bothHaveNullPredicates) { + return true; + } + + // Case 2: Both have targetIndex but null predicate + if (bothHaveIndex && bothHaveNullPredicates) { + return targetIndex.equals(otherViewwCommand.targetIndex); + } + + // Case 3: Both have null targetIndex but have predicate + if (bothHaveNullIndex && bothHavePredicates) { + return predicate.equals(otherViewwCommand.predicate); + } + + // All other cases are false + return false; + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 4ff1a97ed77..310a71a2b12 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -5,11 +5,14 @@ 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.logic.parser.CliSyntax.PREFIX_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_WEDDING; +import java.util.Optional; import java.util.Set; import java.util.stream.Stream; +import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.Address; @@ -17,42 +20,62 @@ 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.role.Role; /** - * Parses input arguments and creates a new AddCommand object + * Parses input arguments and creates a new {@code 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. + * Parses the given {@code String} of arguments in the context of the {@code AddCommand} + * and returns an {@code 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); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, + PREFIX_ROLE, PREFIX_WEDDING); 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); + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_ROLE); + + // Parse required fields 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); + // Parse optional role + Optional role = Optional.empty(); + if (argMultimap.getValue(PREFIX_ROLE).isPresent()) { + try { + role = ParserUtil.parseRole(argMultimap.getValue(PREFIX_ROLE).get()); + } catch (ParseException e) { + throw new ParseException(Role.MESSAGE_CONSTRAINTS); + } + } + + // Parse wedding indices + Set weddingIndices = ParserUtil.parseWeddingJobs(argMultimap.getAllValues(PREFIX_WEDDING)); + + // Create the person with the parsed values + Person person = new Person(name, phone, email, address, role, null); - return new AddCommand(person); + return new AddCommand(person, weddingIndices); } /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. + * Checks if all specified prefixes are present in the provided {@code ArgumentMultimap}. + * + * @param argumentMultimap {@code ArgumentMultimap} containing the prefixes and their values. + * @param prefixes Array of {@code Prefix} objects to check for presence in {@code argumentMultimap}. + * @return true if all specified prefixes are present, false otherwise. */ 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 index 3149ee07e0b..f3de796f2cb 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -9,14 +9,21 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddwCommand; +import seedu.address.logic.commands.AssignCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeletewCommand; import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.EditwCommand; import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.commands.FilterCommand; import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ViewCommand; +import seedu.address.logic.commands.ViewwCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -65,9 +72,15 @@ public Command parseCommand(String userInput) throws ParseException { case ClearCommand.COMMAND_WORD: return new ClearCommand(); + case FilterCommand.COMMAND_WORD: + return new FilterCommandParser().parse(arguments); + case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case ViewCommand.COMMAND_WORD: + return new ViewCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: return new ListCommand(); @@ -77,6 +90,21 @@ public Command parseCommand(String userInput) throws ParseException { case HelpCommand.COMMAND_WORD: return new HelpCommand(); + case AssignCommand.COMMAND_WORD: + return new AssignCommandParser().parse(arguments); + + case EditwCommand.COMMAND_WORD: + return new EditwCommandParser().parse(arguments); + + case AddwCommand.COMMAND_WORD: + return new AddwCommandParser().parse(arguments); + + case ViewwCommand.COMMAND_WORD: + return new ViewwCommandParser().parse(arguments); + + case DeletewCommand.COMMAND_WORD: + return new DeletewCommandParser().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/AddwCommandParser.java b/src/main/java/seedu/address/logic/parser/AddwCommandParser.java new file mode 100644 index 00000000000..e7c238de23f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddwCommandParser.java @@ -0,0 +1,77 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_CLIENT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_VENUE; +import static seedu.address.logic.parser.ParserUtil.isNumeric; + +import java.util.Arrays; +import java.util.stream.Stream; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.AddwCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Name; +import seedu.address.model.person.NameMatchesKeywordPredicate; +import seedu.address.model.wedding.Date; +import seedu.address.model.wedding.Venue; +import seedu.address.model.wedding.Wedding; + +/** + * Parses input arguments and creates a new {@code AddwCommand} object + */ +public class AddwCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the {@code AddwCommand} + * and returns an {@code AddwCommand} object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public AddwCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, + PREFIX_NAME, PREFIX_CLIENT, PREFIX_DATE, PREFIX_VENUE); + + if (!arePrefixesPresent(argMultimap, + PREFIX_NAME, PREFIX_CLIENT) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddwCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor( + PREFIX_NAME, PREFIX_CLIENT, PREFIX_DATE, PREFIX_VENUE); + + Name weddingName = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + String stringClient = ParserUtil.parseClient(argMultimap.getValue(PREFIX_CLIENT).get()); + + Index index = null; + NameMatchesKeywordPredicate predicate = null; + + if (isNumeric(stringClient)) { + index = ParserUtil.parseIndex(stringClient); + } else { + String[] nameKeywords = stringClient.split(ParserUtil.WHITESPACE_REGEX); + predicate = new NameMatchesKeywordPredicate(Arrays.asList(nameKeywords)); + } + + Date date = ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).orElse(null)); + Venue venue = ParserUtil.parseVenue(argMultimap.getValue(PREFIX_VENUE).orElse(null)); + + Wedding wedding = new Wedding(weddingName, date, venue); + + return new AddwCommand(index, predicate, wedding); + } + + /** + * Checks if all specified prefixes are present in the provided {@code ArgumentMultimap}. + * + * @param argumentMultimap {@code ArgumentMultimap} containing the prefixes and their values. + * @param prefixes Array of {@code Prefix} objects to check for presence in {@code argumentMultimap}. + * @return true if all specified prefixes are present, false otherwise. + */ + 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/AssignCommandParser.java b/src/main/java/seedu/address/logic/parser/AssignCommandParser.java new file mode 100644 index 00000000000..cf52697e431 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AssignCommandParser.java @@ -0,0 +1,93 @@ +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_ROLE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_WEDDING; +import static seedu.address.logic.parser.ParserUtil.isNumeric; + +import java.util.Arrays; +import java.util.Set; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.AssignCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.NameMatchesKeywordPredicate; + +/** + * Parses input arguments and creates a new {@code AssignCommand} object + */ +public class AssignCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the {@code AssignCommand} + * and returns a {@code AssignCommand} object for execution. + * + * @throws ParseException if the user input does not conform to the expected format + */ + public AssignCommand parse(String args) throws ParseException { + requireNonNull(args); + + // Check for invalid format where prefix immediately follows number + String trimmedArgs = args.trim(); + if (trimmedArgs.matches("\\d+" + PREFIX_ROLE.getPrefix() + ".*") + || trimmedArgs.matches("\\d+" + PREFIX_WEDDING.getPrefix() + ".*")) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, AssignCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_ROLE, PREFIX_WEDDING); + + Index personIndex = null; + NameMatchesKeywordPredicate predicate = null; + + try { + String target = argMultimap.getPreamble(); + + if (target.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AssignCommand.MESSAGE_USAGE)); + } + + // Determine if the target is an personIndex or a name + if (isNumeric(target)) { + personIndex = ParserUtil.parseIndex(target); + } else { + String[] nameKeywords = target.split("\\s+"); + predicate = new NameMatchesKeywordPredicate(Arrays.asList(nameKeywords)); + } + + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AssignCommand.MESSAGE_USAGE), pe); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_ROLE); + + boolean isAssignRole = argMultimap.getValue(PREFIX_ROLE).isPresent(); + boolean isAssignWedding = !argMultimap.getAllValues(PREFIX_WEDDING).isEmpty(); + + AssignCommand.PersonWithRoleDescriptor personWithRoleDescriptor = new AssignCommand.PersonWithRoleDescriptor(); + + if (!isAssignRole && !isAssignWedding) { + // no role and wedding to assign + throw new ParseException(AssignCommand.MESSAGE_MISSING_FIELDS); + + } else if (isAssignRole & !isAssignWedding) { + // assign role only + String roleValue = argMultimap.getValue(PREFIX_ROLE).get(); + personWithRoleDescriptor.setRole(ParserUtil.parseRole(roleValue)); + + return new AssignCommand(personIndex, predicate, personWithRoleDescriptor, null); + } else if (isAssignWedding & !isAssignRole) { + // assign to wedding only + Set weddingIndices = ParserUtil.parseWeddingJobs(argMultimap.getAllValues(PREFIX_WEDDING)); + personWithRoleDescriptor.setRole(null); + return new AssignCommand(personIndex, predicate, personWithRoleDescriptor, weddingIndices); + } else { + // assign role and wedding + String roleValue = argMultimap.getValue(PREFIX_ROLE).get(); + personWithRoleDescriptor.setRole(ParserUtil.parseRole(roleValue)); + Set weddingIndices = ParserUtil.parseWeddingJobs(argMultimap.getAllValues(PREFIX_WEDDING)); + return new AssignCommand(personIndex, predicate, personWithRoleDescriptor, weddingIndices); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..2b6b082ae33 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -10,6 +10,10 @@ public class CliSyntax { 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_ROLE = new Prefix("r/"); + public static final Prefix PREFIX_WEDDING = new Prefix("w/"); + public static final Prefix PREFIX_CLIENT = new Prefix("c/"); + public static final Prefix PREFIX_DATE = new Prefix("d/"); + public static final Prefix PREFIX_VENUE = new Prefix("v/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 3527fe76a3e..b97013390e0 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -1,29 +1,69 @@ 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_WEDDING; +import static seedu.address.logic.parser.ParserUtil.isNumeric; + +import java.util.Arrays; +import java.util.Set; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.NameMatchesKeywordPredicate; /** - * Parses input arguments and creates a new DeleteCommand object + * Parses input arguments and creates a new {@code 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. + * Parses the given {@code String} of arguments in the context of the {@code DeleteCommand} + * and returns a {@code DeleteCommand} object for execution. * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { + requireNonNull(args); + // Check for invalid format where prefix immediately follows number + String trimmedArgs = args.trim(); + if (trimmedArgs.matches("\\d+" + PREFIX_WEDDING.getPrefix() + ".*")) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_WEDDING); + + Index personIndex = null; + NameMatchesKeywordPredicate predicate = null; + try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); + String target = argMultimap.getPreamble(); + + if (target.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + } + + // Determine if the target is an personIndex or a name + if (isNumeric(target)) { + personIndex = ParserUtil.parseIndex(target); + } else { + String[] nameKeywords = target.split("\\s+"); + predicate = new NameMatchesKeywordPredicate(Arrays.asList(nameKeywords)); + } + } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); } - } + boolean isDeleteAssignWeddings = !argMultimap.getAllValues(PREFIX_WEDDING).isEmpty(); + + if (isDeleteAssignWeddings) { + Set weddingIndices = ParserUtil.parseWeddingJobs(argMultimap.getAllValues(PREFIX_WEDDING)); + return new DeleteCommand(personIndex, predicate, weddingIndices); + } else { + // delete persons only + return new DeleteCommand(personIndex, predicate, null); + } + } } diff --git a/src/main/java/seedu/address/logic/parser/DeletewCommandParser.java b/src/main/java/seedu/address/logic/parser/DeletewCommandParser.java new file mode 100644 index 00000000000..2033445076a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeletewCommandParser.java @@ -0,0 +1,46 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.ParserUtil.isNumeric; + +import java.util.Arrays; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.DeletewCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.wedding.NameMatchesWeddingPredicate; + +/** + * Parses input arguments and creates a new {@code DeletewCommand} object + */ +public class DeletewCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the {@code DeletewCommand} + * and returns a {@code DeletewCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public DeletewCommand parse(String args) throws ParseException { + try { + String trimmedArgs = args.trim(); + + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeletewCommand.MESSAGE_USAGE)); + } + + if (isNumeric(trimmedArgs)) { + Index index = ParserUtil.parseIndex(trimmedArgs); + return new DeletewCommand(index, null); + } else { + String[] nameKeywords = trimmedArgs.split("\\s+"); + NameMatchesWeddingPredicate predicate = new NameMatchesWeddingPredicate( + Arrays.asList(nameKeywords)); + + return new DeletewCommand(null, predicate); + } + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeletewCommand.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 index 46b3309a78b..433c548113e 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -6,38 +6,48 @@ 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.logic.parser.ParserUtil.isNumeric; -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; +import java.util.Arrays; 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; +import seedu.address.model.person.NameMatchesKeywordPredicate; /** - * Parses input arguments and creates a new EditCommand object + * Parses input arguments and creates a new {@code 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. + * Parses the given {@code String} of arguments in the context of the {@code EditCommand} + * and returns an {@code 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); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); - Index index; + Index index = null; + NameMatchesKeywordPredicate predicate = null; try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); + String target = argMultimap.getPreamble(); + + if (target.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + } + + if (isNumeric(target)) { + index = ParserUtil.parseIndex(target); + } else { + String[] nameKeywords = target.split("\\s+"); + predicate = new NameMatchesKeywordPredicate(Arrays.asList(nameKeywords)); + } + } catch (ParseException pe) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); } @@ -58,28 +68,11 @@ public EditCommand parse(String args) throws ParseException { 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; + return new EditCommand(index, predicate, editPersonDescriptor); - 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/logic/parser/EditwCommandParser.java b/src/main/java/seedu/address/logic/parser/EditwCommandParser.java new file mode 100644 index 00000000000..6f1461a1404 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/EditwCommandParser.java @@ -0,0 +1,58 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_VENUE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_WEDDING; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.EditwCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new {@code EditwCommand} object. + */ +public class EditwCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the {@code EditwCommand} + * and returns an {@code EditwCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditwCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_WEDDING, PREFIX_NAME, PREFIX_DATE, PREFIX_VENUE); + + if (!argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditwCommand.MESSAGE_USAGE)); + } + + Index index; + try { + index = ParserUtil.parseIndex(argMultimap.getValue(PREFIX_WEDDING).orElseThrow(() -> + new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditwCommand.MESSAGE_USAGE)) + )); + } catch (ParseException pe) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditwCommand.MESSAGE_USAGE), pe); + } + + EditwCommand.EditWeddingDescriptor editWeddingDescriptor = new EditwCommand.EditWeddingDescriptor(); + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + editWeddingDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); + } + if (argMultimap.getValue(PREFIX_DATE).isPresent()) { + editWeddingDescriptor.setDate(ParserUtil.parseDate(argMultimap.getValue(PREFIX_DATE).get())); + } + if (argMultimap.getValue(PREFIX_VENUE).isPresent()) { + editWeddingDescriptor.setVenue(ParserUtil.parseVenue(argMultimap.getValue(PREFIX_VENUE).get())); + } + + if (!editWeddingDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditwCommand.MESSAGE_USAGE); + } + + return new EditwCommand(index, editWeddingDescriptor); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FilterCommandParser.java b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java new file mode 100644 index 00000000000..16b56484118 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FilterCommandParser.java @@ -0,0 +1,109 @@ +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_ROLE; + +import java.util.Optional; +import java.util.stream.Stream; + +import seedu.address.logic.commands.FilterCommand; +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.role.Role; + +/** + * Parses input arguments and creates a new {@code FilterCommand} object. + */ +public class FilterCommandParser implements Parser { + + public static final String MESSAGE_ROLE_CANNOT_BE_EMPTY = "Inputted role to be filtered cannot be blank."; + + /** + * Parses the given {@code String} of arguments in the context of the {@code FilterCommand} + * and returns an {@code FilterCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public FilterCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize( + args, + PREFIX_NAME, PREFIX_ROLE, PREFIX_EMAIL, PREFIX_PHONE, PREFIX_ADDRESS + ); + + // Check if any prefix values are empty + if (areAnyPrefixesEmpty(argMultimap, PREFIX_NAME, PREFIX_EMAIL, PREFIX_PHONE, PREFIX_ADDRESS)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE)); + } + + if (!areAnyPrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ROLE, PREFIX_EMAIL, PREFIX_PHONE, PREFIX_ADDRESS)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FilterCommand.MESSAGE_USAGE)); + } + + Name name = null; + Phone phone = null; + Email email = null; + Address address = null; + Optional role = null; + + if (argMultimap.getValue(PREFIX_NAME).isPresent()) { + name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + } + + if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { + phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get()); + } + + if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { + email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get()); + } + + if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { + address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()); + } + + if (argMultimap.getValue(PREFIX_ROLE).isPresent()) { + if (argMultimap.getValue(PREFIX_ROLE).get().isEmpty()) { + throw new ParseException(MESSAGE_ROLE_CANNOT_BE_EMPTY); + } + role = ParserUtil.parseRole(argMultimap.getValue(PREFIX_ROLE).get()); + } + + return new FilterCommand(name, role, email, phone, address); + } + + /** + * Checks if all specified prefixes are present in the provided {@code ArgumentMultimap}. + * + * @param argumentMultimap {@code ArgumentMultimap} containing the prefixes and their values. + * @param prefixes Array of {@code Prefix} objects to check for presence in {@code argumentMultimap}. + * @return true if all specified prefixes are present, false otherwise. + */ + private static boolean areAnyPrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).anyMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + /** + * Checks if any of the prefixes has an empty value. + * + * @param argumentMultimap {@code ArgumentMultimap} containing the prefixes and their values. + * @param prefixes Array of {@code Prefix} objects to check for presence in {@code argumentMultimap}. + * @return true if any of the present prefixes has an empty value, false otherwise. + */ + private static boolean areAnyPrefixesEmpty(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes) + .filter(prefix -> argumentMultimap.getValue(prefix).isPresent()) + .anyMatch(prefix -> argumentMultimap.getValue(prefix).get().trim().isEmpty()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 2867bde857b..8fffcdcf5e4 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -9,13 +9,13 @@ import seedu.address.model.person.NameContainsKeywordsPredicate; /** - * Parses input arguments and creates a new FindCommand object + * Parses input arguments and creates a new {@code FindCommand} object */ public class FindCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the FindCommand - * and returns a FindCommand object for execution. + * Parses the given {@code String} of arguments in the context of the {@code FindCommand} + * and returns a {@code FindCommand} object for execution. * @throws ParseException if the user input does not conform the expected format */ public FindCommand parse(String args) throws ParseException { diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java index d6551ad8e3f..162a5b3ab73 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/address/logic/parser/Parser.java @@ -7,7 +7,6 @@ * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. */ public interface Parser { - /** * Parses {@code userInput} into a command and returns it. * @throws ParseException if {@code userInput} does not conform the expected format diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..8f9fe24d35a 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.HashSet; +import java.util.Optional; import java.util.Set; import seedu.address.commons.core.index.Index; @@ -13,7 +14,10 @@ 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.model.role.Role; +import seedu.address.model.wedding.Client; +import seedu.address.model.wedding.Date; +import seedu.address.model.wedding.Venue; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -21,6 +25,7 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String WHITESPACE_REGEX = "\\s+"; /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be @@ -96,29 +101,103 @@ public static Email parseEmail(String email) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String client} into an {@code Client}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code name} and {@code index} are 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 String parseClient(String client) throws ParseException { + requireNonNull(client); + String trimmedClient = client.trim(); + + if (!Client.isValidClientIndex(trimmedClient)) { + if (!Client.isValidClientName(trimmedClient)) { + throw new ParseException(Client.MESSAGE_CONSTRAINTS); + } + } + + return trimmedClient; + } + + /** + * Parses a {@code String date} into an {@code Date}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code date} is invalid. + */ + public static Date parseDate(String date) throws ParseException { + if (date == null) { + return null; } - return new Tag(trimmedTag); + String trimmedDate = date.trim(); + if (!Date.isValidDate(trimmedDate)) { + throw new ParseException(Date.MESSAGE_CONSTRAINTS); + } + return new Date(trimmedDate); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a {@code String venue} into an {@code Venue}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code venue} is invalid. */ - 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 Venue parseVenue(String venue) throws ParseException { + if (venue == null) { + return null; } - return tagSet; + String trimmedVenue = venue.trim(); + if (!Venue.isValidVenue(trimmedVenue)) { + throw new ParseException(Venue.MESSAGE_CONSTRAINTS); + } + return new Venue(trimmedVenue); } + + + /** + * Parses a {@code String role} into a {@code Optional}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code role} is invalid. + */ + public static Optional parseRole(String role) throws ParseException { + requireNonNull(role); + String trimmedRole = role.trim(); + if (trimmedRole.isEmpty()) { + return Optional.empty(); + } + if (!Role.isValidRoleName(trimmedRole)) { + throw new ParseException(Role.MESSAGE_CONSTRAINTS); + } + return Optional.of(new Role(trimmedRole)); + } + + /** + * Parses {@code Collection weddings} into a {@code Set}. + * + * @throws ParseException if any of the given {@code index} is invalid. + */ + public static Set parseWeddingJobs(Collection weddings) throws ParseException { + requireNonNull(weddings); + + final Set weddingSet = new HashSet<>(); + for (String weddingIndex : weddings) { + Index indexToAdd = parseIndex(weddingIndex); + if (!weddingSet.contains(indexToAdd)) { + weddingSet.add(indexToAdd); + } + } + return weddingSet; + } + + /** + * Checks if the given {@code String} is a numeric value. + * + * @param str The {@code String} to be checked. + * @return true if the {@code String} is numeric, false otherwise. + */ + public static boolean isNumeric(String str) { + return str != null && str.matches("-?\\d+"); + } + } diff --git a/src/main/java/seedu/address/logic/parser/ViewCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java new file mode 100644 index 00000000000..a59ff97d5af --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ViewCommandParser.java @@ -0,0 +1,59 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; + +import seedu.address.logic.commands.ViewCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.NameMatchesKeywordPredicate; + +/** + * Parses input arguments and creates a new {@code ViewCommand} object + */ +public class ViewCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the {@code ViewCommand} + * and returns a {@code ViewCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewCommand parse(String args) throws ParseException { + if (args == null || args.trim().isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } + + String trimmedArgs = args.trim(); + + try { + if (isIndex(trimmedArgs)) { + return new ViewCommand(ParserUtil.parseIndex(trimmedArgs), null); + } else if (isValidName(trimmedArgs)) { + String[] nameKeywords = trimmedArgs.split("\\s+"); + return new ViewCommand(null, new NameMatchesKeywordPredicate(Arrays.asList(nameKeywords))); + } else { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } + } catch (ParseException pe) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE), pe); + } + } + + /** + * Returns true if the argument is a valid index. + */ + private boolean isIndex(String test) { + return test.matches("^[1-9]\\d*$"); + } + + /** + * Returns true if the argument contains only valid name characters. + */ + private boolean isValidName(String test) { + return test.matches("^[a-zA-Z'\\-\\s]+$") + && !test.matches(".*\\d+.*"); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ViewwCommandParser.java b/src/main/java/seedu/address/logic/parser/ViewwCommandParser.java new file mode 100644 index 00000000000..f0232e98a70 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ViewwCommandParser.java @@ -0,0 +1,49 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.ParserUtil.isNumeric; + +import java.util.Arrays; + +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.ViewwCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.person.Name; +import seedu.address.model.wedding.NameMatchesWeddingPredicate; + +/** + * Parses input arguments and creates a new {@code ViewwCommand} object + */ +public class ViewwCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the {@code ViewwCommand} + * and returns a {@code ViewwCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewwCommand parse(String args) throws ParseException { + try { + String trimmedArgs = args.trim(); + + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewwCommand.MESSAGE_USAGE)); + } + + if (isNumeric(trimmedArgs)) { + Index index = ParserUtil.parseIndex(trimmedArgs); + return new ViewwCommand(index, null); + } else { + Name weddingName = new Name(trimmedArgs); + String[] nameKeywords = weddingName.fullName.split("\\s+"); + NameMatchesWeddingPredicate predicate = new NameMatchesWeddingPredicate( + Arrays.asList(nameKeywords)); + + return new ViewwCommand(null, predicate); + } + } catch (ParseException | IllegalArgumentException e) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewwCommand.MESSAGE_USAGE), e); + } + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 73397161e84..fb9b5e2bc5e 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -8,6 +8,8 @@ import seedu.address.commons.util.ToStringBuilder; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; +import seedu.address.model.wedding.UniqueWeddingList; +import seedu.address.model.wedding.Wedding; /** * Wraps all data at the address-book level @@ -16,6 +18,8 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private final UniqueWeddingList weddings; + /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication @@ -26,6 +30,7 @@ public class AddressBook implements ReadOnlyAddressBook { */ { persons = new UniquePersonList(); + weddings = new UniqueWeddingList(); } public AddressBook() {} @@ -48,15 +53,27 @@ public void setPersons(List persons) { this.persons.setPersons(persons); } + /** + * Replaces the contents of the weddings list with {@code weddings}. + * {@code weddings} must not contain duplicate weddings. + */ + public void setWeddings(List weddings) { + this.weddings.setWeddings(weddings); + } + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); - + setWeddings(newData.getWeddingList()); setPersons(newData.getPersonList()); } + public void setAllPersonNotClient() { + persons.setAllPersonNotClient(); + } + //// person-level operations /** @@ -67,6 +84,22 @@ public boolean hasPerson(Person person) { return persons.contains(person); } + /** + * Returns true if a person has the same phone number as {@code person} in the address book. + */ + public boolean hasPhone(Person person) { + requireNonNull(person); + return persons.containsPhone(person); + } + + /** + * Returns true if a person has the same email address as {@code person} in the address book. + */ + public boolean hasEmail(Person person) { + requireNonNull(person); + return persons.containsEmail(person); + } + /** * Adds a person to the address book. * The person must not already exist in the address book. @@ -94,6 +127,65 @@ public void removePerson(Person key) { persons.remove(key); } + /** + * Returns true if a wedding with the same identity as {@code wedding} exists in the address book. + */ + public boolean hasWedding(Wedding wedding) { + requireNonNull(wedding); + return weddings.contains(wedding); + } + + /** + * Updates the wedding of all persons in the address book that are involved in the edited wedding. + */ + public void updatePersonEditedWedding(Wedding target, Wedding editedWedding) { + requireNonNull(editedWedding); + persons.updatePersonInvolveInEditedWedding(target, editedWedding); + } + + /** + * Adds a wedding to the address book. + * The wedding must not already exist in the address book. + */ + public void addWedding(Wedding w) { + weddings.add(w); + } + + /** + * Replaces the given person {@code target} in the list with {@code editedWedding}. + * {@code target} must exist in the address book. + * The wedding identity of {@code editedWedding} must not be the same as + * another existing wedding in the address book. + */ + public void setWedding(Wedding target, Wedding editedWedding) { + requireNonNull(editedWedding); + + weddings.setWedding(target, editedWedding); + } + + /** + * Sets all wedding to not be own wedding. + */ + public void setAllWeddingIsOwnFalse() { + weddings.setAllWeddingIsOwnFalse(); + } + + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removeWedding(Wedding key) { + weddings.remove(key); + for (Person person : persons) { + person.resetOwnWedding(key); + + if (person.containsWeddingJob(key)) { + person.removeWeddingJob(key); + } + } + } + + //// util methods @Override @@ -108,6 +200,12 @@ public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + @Override + public ObservableList getWeddingList() { + return weddings.asUnmodifiableObservableList(); + } + + @Override public boolean equals(Object other) { if (other == this) { diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..3140d8476aa 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -6,6 +6,7 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.GuiSettings; import seedu.address.model.person.Person; +import seedu.address.model.wedding.Wedding; /** * The API of the Model component. @@ -13,6 +14,7 @@ public interface Model { /** {@code Predicate} that always evaluate to true */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; + Predicate PREDICATE_SHOW_ALL_WEDDINGS = unused -> true; /** * Replaces user prefs data with the data in {@code userPrefs}. @@ -57,6 +59,16 @@ public interface Model { */ boolean hasPerson(Person person); + /** + * Returns true if a person has the same phone number as {@code person} in the address book. + */ + boolean hasPhone(Person person); + + /** + * Returns true if a person has the same email address as {@code person} in the address book. + */ + boolean hasEmail(Person person); + /** * Deletes the given person. * The person must exist in the address book. @@ -84,4 +96,66 @@ public interface Model { * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + /** + * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Ensures that the correct client is set to be client. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredPersonListWithClient(Predicate predicate); + + /** + * Sets all person to be not client. + */ + void setAllPersonNotClient(); + + // =========== Wedding =================================================================================== + + /** + * Returns true if a wedding with the same identity as {@code wedding} exists in the address book. + */ + boolean hasWedding(Wedding wedding); + + void updatePersonEditedWedding(Wedding target, Wedding editedWedding); + + /** + * Deletes the given wedding. + * The wedding must exist in the address book. + */ + void deleteWedding(Wedding wedding); + + /** + * Adds the given wedding. + * {@code wedding} must not already exist in the address book. + */ + void addWedding(Wedding wedding); + + /** + * Replaces the given wedding {@code target} with {@code editedWedding}. + * {@code target} must exist in the address book. + * The wedding identity of {@code editedWedding} must not be the same as + * another existing wedding in the address book. + */ + void setWedding(Wedding target, Wedding editedWedding); + + /** Returns an unmodifiable view of the filtered wedding list */ + ObservableList getFilteredWeddingList(); + + /** + * Updates the filter of the filtered wedding list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredWeddingList(Predicate predicate); + + /** + * Updates the filter of the filtered wedding list to filter by the given {@code predicate}. + * Ensures that all correct weddings are set to be own wedding. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredWeddingListWithOwnWedding(Predicate predicate); + + /** + * Sets all wedding to not be own-wedding. + */ + void setAllWeddingNotOwnWedding(); } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..3ee514aba4f 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -12,6 +12,7 @@ import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; import seedu.address.model.person.Person; +import seedu.address.model.wedding.Wedding; /** * Represents the in-memory model of the address book data. @@ -22,6 +23,7 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final FilteredList filteredWeddings; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -34,6 +36,7 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredWeddings = new FilteredList<>(this.addressBook.getWeddingList()); } public ModelManager() { @@ -93,6 +96,18 @@ public boolean hasPerson(Person person) { return addressBook.hasPerson(person); } + @Override + public boolean hasPhone(Person person) { + requireNonNull(person); + return addressBook.hasPhone(person); + } + + @Override + public boolean hasEmail(Person person) { + requireNonNull(person); + return addressBook.hasEmail(person); + } + @Override public void deletePerson(Person target) { addressBook.removePerson(target); @@ -111,6 +126,37 @@ public void setPerson(Person target, Person editedPerson) { addressBook.setPerson(target, editedPerson); } + @Override + public boolean hasWedding(Wedding wedding) { + requireNonNull(wedding); + return addressBook.hasWedding(wedding); + } + + @Override + public void updatePersonEditedWedding(Wedding target, Wedding editedWedding) { + requireAllNonNull(target, editedWedding); + addressBook.updatePersonEditedWedding(target, editedWedding); + } + + @Override + public void deleteWedding(Wedding target) { + addressBook.removeWedding(target); + } + + @Override + public void addWedding(Wedding wedding) { + addressBook.addWedding(wedding); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + updateFilteredWeddingList(PREDICATE_SHOW_ALL_WEDDINGS); + } + + @Override + public void setWedding(Wedding target, Wedding editedWedding) { + requireAllNonNull(target, editedWedding); + + addressBook.setWedding(target, editedWedding); + } + //=========== Filtered Person List Accessors ============================================================= /** @@ -125,7 +171,47 @@ public ObservableList getFilteredPersonList() { @Override public void updateFilteredPersonList(Predicate predicate) { requireNonNull(predicate); + setAllPersonNotClient(); filteredPersons.setPredicate(predicate); + + } + + @Override + public void updateFilteredPersonListWithClient(Predicate predicate) { + requireNonNull(predicate); + filteredPersons.setPredicate(predicate); + } + + @Override + public void setAllPersonNotClient() { + addressBook.setAllPersonNotClient(); + } + + /** + * Returns an unmodifiable view of the list of {@code Wedding} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredWeddingList() { + return filteredWeddings; + } + + @Override + public void updateFilteredWeddingList(Predicate predicate) { + requireNonNull(predicate); + setAllWeddingNotOwnWedding(); + filteredWeddings.setPredicate(predicate); + } + + @Override + public void updateFilteredWeddingListWithOwnWedding(Predicate predicate) { + requireNonNull(predicate); + filteredWeddings.setPredicate(predicate); + } + + @Override + public void setAllWeddingNotOwnWedding() { + addressBook.setAllWeddingIsOwnFalse(); } @Override diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..5f277e616b0 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -2,6 +2,7 @@ import javafx.collections.ObservableList; import seedu.address.model.person.Person; +import seedu.address.model.wedding.Wedding; /** * Unmodifiable view of an address book @@ -14,4 +15,10 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + /** + * Returns an unmodifiable view of the weddings list. + * This list will not contain any duplicate weddings. + */ + ObservableList getWeddingList(); + } diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java index 469a2cc9a1e..451a98d89ad 100644 --- a/src/main/java/seedu/address/model/person/Address.java +++ b/src/main/java/seedu/address/model/person/Address.java @@ -9,7 +9,7 @@ */ public class Address { - public static final String MESSAGE_CONSTRAINTS = "Addresses can take any values, and it should not be blank"; + 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, diff --git a/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicate.java new file mode 100644 index 00000000000..418ff1e9542 --- /dev/null +++ b/src/main/java/seedu/address/model/person/AddressContainsKeywordsPredicate.java @@ -0,0 +1,42 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Person}'s {@code Address} matches any of the keywords given. + */ +public class AddressContainsKeywordsPredicate implements Predicate { + private final List keywords; + + /** + * Constructs a AddressContainsKeywordsPredicate with the given wedding + * + * @param keywords The keywords to check against + */ + public AddressContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + /** + * Tests if a given person has address containing some keywords. + * Returns true only if some keywords are found in the person's address + * + * @param person The person to test + * @return true if some keywords are found in the person's address, false otherwise + */ + @Override + public boolean test(Person person) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPhraseIgnoreCase(person.getAddress().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof AddressContainsKeywordsPredicate + && keywords.equals(((AddressContainsKeywordsPredicate) other).keywords)); + } +} diff --git a/src/main/java/seedu/address/model/person/ClientMatchesWeddingPredicate.java b/src/main/java/seedu/address/model/person/ClientMatchesWeddingPredicate.java new file mode 100644 index 00000000000..8546b8afe91 --- /dev/null +++ b/src/main/java/seedu/address/model/person/ClientMatchesWeddingPredicate.java @@ -0,0 +1,53 @@ +package seedu.address.model.person; + +import java.util.function.Predicate; + +import seedu.address.model.wedding.Wedding; + +/** + * Tests that a {@code Person}'s {@code Wedding} matches the given wedding. + */ +public class ClientMatchesWeddingPredicate implements Predicate { + private final Wedding wedding; + + /** + * Constructs a ClientMatchesWeddingPredicate with the given wedding + * + * @param wedding The wedding to check against + */ + public ClientMatchesWeddingPredicate(Wedding wedding) { + this.wedding = wedding; + } + + public Wedding getWedding() { + return wedding; + } + + /** + * Tests if a given person is the client of the wedding. + * Returns true only if the person is the client tof the wedding. + * + * @param person The wedding to test + * @return true if the person is the client tof the wedding, false otherwise + */ + @Override + public boolean test(Person person) { + return person.getOwnWedding() != null && person.getOwnWedding().equals(wedding); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ClientMatchesWeddingPredicate)) { + return false; + } + + ClientMatchesWeddingPredicate otherWeddingPredicate = (ClientMatchesWeddingPredicate) other; + return wedding.equals(otherWeddingPredicate.wedding); + } +} + diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java index c62e512bc29..1e5315d6e67 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -21,6 +21,7 @@ public class Email { + " - end with a domain label at least 2 characters long\n" + " - have each domain label start and end with alphanumeric characters\n" + " - have each domain label consist of alphanumeric characters, separated only by hyphens, if any."; + // alphanumeric and special characters private static final String ALPHANUMERIC_NO_UNDERSCORE = "[^\\W_]+"; // alphanumeric characters except underscore private static final String LOCAL_PART_REGEX = "^" + ALPHANUMERIC_NO_UNDERSCORE + "([" + SPECIAL_CHARACTERS + "]" @@ -41,7 +42,7 @@ public class Email { public Email(String email) { requireNonNull(email); checkArgument(isValidEmail(email), MESSAGE_CONSTRAINTS); - value = email; + value = email.toLowerCase(); } /** diff --git a/src/main/java/seedu/address/model/person/EmailContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/EmailContainsKeywordsPredicate.java new file mode 100644 index 00000000000..c37e5d265fa --- /dev/null +++ b/src/main/java/seedu/address/model/person/EmailContainsKeywordsPredicate.java @@ -0,0 +1,42 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Person}'s {@code Email} matches any of the keywords given. + */ +public class EmailContainsKeywordsPredicate implements Predicate { + private final List keywords; + + /** + * Constructs a EmailContainsKeywordsPredicate with the given wedding + * + * @param keywords The keywords to check against + */ + public EmailContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + /** + * Tests if a given person has name containing some keywords. + * Returns true only if some keywords are found in the person's email + * + * @param person The person to test + * @return true if some keywords are found in the person's email, false otherwise + */ + @Override + public boolean test(Person person) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsPhraseIgnoreCase(person.getEmail().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof EmailContainsKeywordsPredicate // instanceof handles nulls + && keywords.equals(((EmailContainsKeywordsPredicate) other).keywords)); // state check + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 173f15b9b00..178a0b7c89e 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -10,13 +10,14 @@ public class Name { public static final String MESSAGE_CONSTRAINTS = - "Names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Names should only contain alphabetical characters, " + + "spaces, hyphens or apostrophes, at most 70 characters, and 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 = "[\\p{Alnum}][\\p{Alnum} ]*"; + public static final String VALIDATION_REGEX = "^(?!\\s*$)[A-Za-z\\s\\'-]{1,70}$"; public final String fullName; @@ -38,7 +39,6 @@ public static boolean isValidName(String test) { return test.matches(VALIDATION_REGEX); } - @Override public String toString() { return fullName; diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java index 62d19be2977..e57577cb479 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java @@ -12,10 +12,22 @@ public class NameContainsKeywordsPredicate implements Predicate { private final List keywords; + /** + * Constructs a NameContainsKeywordsPredicate with the given wedding + * + * @param keywords The keywords to check against + */ public NameContainsKeywordsPredicate(List keywords) { this.keywords = keywords; } + /** + * Tests if a given person has name containing some keywords. + * Returns true only if some keywords are found in the person's name + * + * @param person The person to test + * @return true if some keywords are found in the person's name, false otherwise + */ @Override public boolean test(Person person) { return keywords.stream() diff --git a/src/main/java/seedu/address/model/person/NameMatchesKeywordPredicate.java b/src/main/java/seedu/address/model/person/NameMatchesKeywordPredicate.java new file mode 100644 index 00000000000..f8eba80ab25 --- /dev/null +++ b/src/main/java/seedu/address/model/person/NameMatchesKeywordPredicate.java @@ -0,0 +1,67 @@ +package seedu.address.model.person; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. + * Different from NameContainsKeywordsPredicate as it requires all keywords to be present in the name. + */ +public class NameMatchesKeywordPredicate implements Predicate { + private final List keywords; + + /** + * Constructs a NameMatchesKeywordPredicate with the given wedding + * + * @param names The keywords to check against + */ + public NameMatchesKeywordPredicate(List names) { + this.keywords = names; + } + + /** + * Tests if a given person has name containing all keywords. + * Returns true only if all keywords are found in the person's name + * + * @param person The person to test + * @return true if the keywords are all found in the person's name, false otherwise + */ + @Override + public boolean test(Person person) { + requireNonNull(person); + // Return false if keywords list is empty + if (keywords.isEmpty()) { + return false; + } + return keywords.stream() + .allMatch(keyword -> person.getName().fullName.toLowerCase().contains(keyword.toLowerCase())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof NameMatchesKeywordPredicate)) { + return false; + } + + NameMatchesKeywordPredicate otherNameMatchesKeywordPredicate = (NameMatchesKeywordPredicate) other; + return keywords.equals(otherNameMatchesKeywordPredicate.keywords); + } + + public List getKeywords() { + return keywords; + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index abe8c46b535..237ef7b6756 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -2,39 +2,41 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; -import java.util.Collections; import java.util.HashSet; import java.util.Objects; +import java.util.Optional; import java.util.Set; import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.tag.Tag; +import seedu.address.model.role.Role; +import seedu.address.model.wedding.Wedding; /** * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. + * Guarantees: Field values are validated and 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<>(); + private final Optional role; + private Wedding ownWedding; + private final Set weddingJobs = new HashSet<>(); + private boolean isClient = false; /** - * Every field must be present and not null. + * Every field, except tag and wedding, must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Phone phone, Email email, Address address, Optional role, Wedding ownWedding) { + requireAllNonNull(name, phone, email, address, role); this.name = name; this.phone = phone; this.email = email; this.address = address; - this.tags.addAll(tags); + this.role = role; + this.ownWedding = ownWedding; } public Name getName() { @@ -54,24 +56,161 @@ public Address getAddress() { } /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * Returns an immutable role set, which throws {@code UnsupportedOperationException} * if modification is attempted. */ - public Set getTags() { - return Collections.unmodifiableSet(tags); + public Optional getRole() { + return role; + } + + public Wedding getOwnWedding() { + return ownWedding; + } + + public Set getWeddingJobs() { + return weddingJobs; + } + + public boolean isClient() { + return isClient; + } + + public void setOwnWedding(Wedding wedding) { + if (wedding == null) { + throw new NullPointerException("Wedding cannot be null."); + } + ownWedding = wedding; + wedding.setClient(this); } /** - * Returns true if both persons have the same name. + * Reset the {@code ownWedding} status of person. + * + * @param wedding {@code Wedding} object to check against {@code ownWedding} + */ + public void resetOwnWedding(Wedding wedding) { + if (this.ownWedding == null) { + return; + } + + if (this.ownWedding.equals(wedding)) { + this.ownWedding = null; + } + } + + public void setIsClient(boolean isClient) { + this.isClient = isClient; + } + + /** + * Adds a wedding to the list of wedding jobs. + * Throws IllegalArgumentException if attempting to add own wedding as a job. + * + * @param wedding {@code Wedding} to be added to the list of wedding jobs + * @throws IllegalArgumentException if the wedding is the person's own wedding + */ + public void addWeddingJob(Wedding wedding) { + if (ownWedding != null && ownWedding.equals(wedding)) { + throw new IllegalArgumentException("Cannot add own wedding as a job."); + } + weddingJobs.add(wedding); + } + + /** + * Checks if this person is already assigned to the given wedding or person is client of wedding. + * + * @param wedding The wedding to check + * @return true if the person is already assigned to the wedding + */ + public boolean isAssignedToWedding(Wedding wedding) { + return weddingJobs.contains(wedding) + || (ownWedding != null && ownWedding.equals(wedding)); + } + + /** + * Checks if this person is already assigned to the given wedding and not client of the wedding. + * + * @param wedding The wedding to check + * @return true if the person is already assigned to the wedding + */ + public boolean isAssignedToWeddingNonClient(Wedding wedding) { + return weddingJobs.contains(wedding); + } + + /** + * Adds a list of wedding jobs to the pre-existing list. + * + * @param weddingJobs {@code Set} to be added to the list of wedding jobs + */ + public void setWeddingJobs(Set weddingJobs) { + for (Wedding wedding : weddingJobs) { + this.addWeddingJob(wedding); + } + } + + /** + * Checks if the {@code weddingJobs} of the person contains the Wedding object. + * + * @param target {@code Wedding} object to be found + * @return true if {@code target} is found in {@code weddingJobs} + */ + public boolean containsWeddingJob(Wedding target) { + for (Wedding weddingJob : weddingJobs) { + if (weddingJob.equals(target)) { + return true; + } + } + return false; + } + + public void removeWeddingJob(Wedding weddingJob) { + this.weddingJobs.remove(weddingJob); + } + + /* + * Returns true if person has own wedding. + * + */ + public boolean hasOwnWedding() { + return ownWedding != null; + } + + /** + * Returns true if both persons have the same name, phone, email, address. * This defines a weaker notion of equality between two persons. */ public boolean isSamePerson(Person otherPerson) { + if (otherPerson == null) { + return false; + } + return this.name.equals(otherPerson.name) + && this.phone.equals(otherPerson.phone) + && this.email.equals(otherPerson.email) + && this.address.equals(otherPerson.address); + } + + /** + * Returns true if both persons have the same phone number. + */ + public boolean hasSamePhone(Person otherPerson) { + if (otherPerson == this) { + return true; + } + + return otherPerson != null + && otherPerson.getPhone().equals(getPhone()); + } + + /** + * Returns true if both persons have the same email address. + */ + public boolean hasSameEmail(Person otherPerson) { if (otherPerson == this) { return true; } return otherPerson != null - && otherPerson.getName().equals(getName()); + && otherPerson.getEmail().equals(getEmail()); } /** @@ -90,28 +229,33 @@ public boolean equals(Object other) { } 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); + && role.equals(otherPerson.role) + && weddingJobs.equals(otherPerson.weddingJobs); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, phone, email, address, role); } @Override public String toString() { + String nullString = ""; + return new ToStringBuilder(this) .add("name", name) .add("phone", phone) .add("email", email) .add("address", address) - .add("tags", tags) + .add("roles", role.isPresent() ? role : "NA") + .add("wedding", ownWedding == null ? "NA" : ownWedding) + .add("wedding jobs", weddingJobs) .toString(); } - } diff --git a/src/main/java/seedu/address/model/person/PersonMatchesWeddingPredicate.java b/src/main/java/seedu/address/model/person/PersonMatchesWeddingPredicate.java new file mode 100644 index 00000000000..29e24a1cc0b --- /dev/null +++ b/src/main/java/seedu/address/model/person/PersonMatchesWeddingPredicate.java @@ -0,0 +1,61 @@ +package seedu.address.model.person; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.wedding.Wedding; + +/** + * Tests that a {@code Person}'s {@code Wedding} matches the given wedding. + */ +public class PersonMatchesWeddingPredicate implements Predicate { + private final Wedding wedding; + + /** + * Constructs a PersonMatchesWeddingPredicate with the given wedding + * + * @param wedding The wedding to check against + */ + public PersonMatchesWeddingPredicate(Wedding wedding) { + this.wedding = wedding; + } + + public Wedding getWedding() { + return wedding; + } + + /** + * Tests if a given person is associated with the wedding + * Returns true only if person is either the client of the wedding, or has weddingJobs with the wedding. + * + * @param person The person to test + * @return true if the person is associated with the wedding, false otherwise + */ + @Override + public boolean test(Person person) { + return (person.getOwnWedding() != null && person.getOwnWedding().equals(wedding)) + || person.getWeddingJobs().contains(wedding); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PersonMatchesWeddingPredicate)) { + return false; + } + + PersonMatchesWeddingPredicate otherWeddingPredicate = (PersonMatchesWeddingPredicate) other; + return wedding.equals(otherWeddingPredicate.wedding); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("wedding", wedding) + .toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index d733f63d739..aae174f405f 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -9,10 +9,13 @@ */ public class Phone { - public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; - public static final String VALIDATION_REGEX = "\\d{3,}"; + "Phone numbers should only contain numbers, start with either 6, 8 or 9, \n" + + "and be exactly 8 digits long."; + + // 8 digits numbers starting with 6, 8 or 9 + public static final String VALIDATION_REGEX = "[689]\\d{7}"; + public final String value; /** diff --git a/src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicate.java new file mode 100644 index 00000000000..fd3c9e5c6a3 --- /dev/null +++ b/src/main/java/seedu/address/model/person/PhoneContainsKeywordsPredicate.java @@ -0,0 +1,42 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Person}'s {@code Phone} matches any of the keywords given. + */ +public class PhoneContainsKeywordsPredicate implements Predicate { + private final List keywords; + + /** + * Constructs a PhoneContainsKeywordsPredicate with the given wedding + * + * @param keywords The keywords to check against + */ + public PhoneContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + /** + * Tests if a given person has phone containing some keywords. + * Returns true only if some keywords are found in the person's phone + * + * @param person The person to test + * @return true if some keywords are found in the person's phone, false otherwise + */ + @Override + public boolean test(Person person) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getPhone().value, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof PhoneContainsKeywordsPredicate + && keywords.equals(((PhoneContainsKeywordsPredicate) other).keywords)); + } +} diff --git a/src/main/java/seedu/address/model/person/RoleContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/RoleContainsKeywordsPredicate.java new file mode 100644 index 00000000000..039578c4144 --- /dev/null +++ b/src/main/java/seedu/address/model/person/RoleContainsKeywordsPredicate.java @@ -0,0 +1,59 @@ +package seedu.address.model.person; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; + + +/** + * Tests that a {@code Person}'s {@code Role} matches any of the keywords given. + */ +public class RoleContainsKeywordsPredicate implements Predicate { + private final List keywords; + + /** + * Constructs a RoleContainsKeywordsPredicate with the given wedding + * + * @param keywords The keywords to check against + */ + public RoleContainsKeywordsPredicate(List keywords) { + this.keywords = keywords; + } + + /** + * Tests if a given person has role containing some keywords. + * Returns true only if some keywords are found in the person's role. + * + * @param person The person to test + * @return true if some keywords are found in the person's role, false otherwise + */ + @Override + public boolean test(Person person) { + return keywords.stream() + .anyMatch(keyword -> person.getRole() + .map(role -> role.roleName.equalsIgnoreCase(keyword)) + .orElse(false)); + } + + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof RoleContainsKeywordsPredicate)) { + return false; + } + + RoleContainsKeywordsPredicate otherRoleContainsKeywordsPredicate = (RoleContainsKeywordsPredicate) other; + return keywords.equals(otherRoleContainsKeywordsPredicate.keywords); + } + + @Override + public String toString() { + return new ToStringBuilder(this).add("keywords", keywords).toString(); + } +} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index cc0a68d79f9..bce031b1d40 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -10,6 +10,7 @@ import javafx.collections.ObservableList; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.wedding.Wedding; /** * A list of persons that enforces uniqueness between its elements and does not allow nulls. @@ -36,6 +37,22 @@ public boolean contains(Person toCheck) { return internalList.stream().anyMatch(toCheck::isSamePerson); } + /** + * Returns true if the list contains the same phone number as the given argument. + */ + public boolean containsPhone(Person toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::hasSamePhone); + } + + /** + * Returns true if the list contains the same email as the given argument. + */ + public boolean containsEmail(Person toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::hasSameEmail); + } + /** * Adds a person to the list. * The person must not already exist in the list. @@ -68,6 +85,22 @@ public void setPerson(Person target, Person editedPerson) { internalList.set(index, editedPerson); } + /** + * Updates the person involve in the edited wedding. + */ + public void updatePersonInvolveInEditedWedding(Wedding target, Wedding editedWedding) { + requireAllNonNull(target, editedWedding); + for (Person person : internalList) { + if (person.getOwnWedding() != null && person.getOwnWedding().equals(target)) { + person.setOwnWedding(editedWedding); + } + if (person.getWeddingJobs().contains(target)) { + person.getWeddingJobs().remove(target); + person.getWeddingJobs().add(editedWedding); + } + } + } + /** * Removes the equivalent person from the list. * The person must exist in the list. @@ -97,6 +130,15 @@ public void setPersons(List persons) { internalList.setAll(persons); } + /** + * Sets all persons in the list to be non-client. + */ + public void setAllPersonNotClient() { + for (Person person : internalList) { + person.setIsClient(false); + } + } + /** * Returns the backing list as an unmodifiable {@code ObservableList}. */ diff --git a/src/main/java/seedu/address/model/role/Role.java b/src/main/java/seedu/address/model/role/Role.java new file mode 100644 index 00000000000..2ec3f9ac8e0 --- /dev/null +++ b/src/main/java/seedu/address/model/role/Role.java @@ -0,0 +1,62 @@ +package seedu.address.model.role; + +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Role in the address book. + * Guarantees: immutable; name is valid as declared in {@link #isValidRoleName(String)} + */ +public class Role { + + public static final String MESSAGE_CONSTRAINTS = "Role should be one word and alphanumeric"; + + public static final String VALIDATION_REGEX = "^[A-Za-z0-9]*$"; + + public final String roleName; + + /** + * Constructs a {@code Role}. + * + * @param roleName A valid role name. + */ + public Role(String roleName) { + checkArgument(isValidRoleName(roleName), MESSAGE_CONSTRAINTS); + this.roleName = roleName; + } + + /** + * Returns true if a given string is a valid role name. + */ + public static boolean isValidRoleName(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Role)) { + return false; + } + + Role otherRole = (Role) other; + return roleName.toLowerCase() + .equals(otherRole.roleName.toLowerCase()); + } + + @Override + public int hashCode() { + return roleName.toLowerCase().hashCode(); + } + + /** + * Format state as text for viewing. + */ + public String toString() { + return roleName; + } + +} 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 index 1806da4facf..31fbd9d02f0 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,8 +1,6 @@ package seedu.address.model.util; -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.Optional; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; @@ -11,7 +9,11 @@ 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.role.Role; +import seedu.address.model.wedding.Date; +import seedu.address.model.wedding.Venue; +import seedu.address.model.wedding.Wedding; + /** * Contains utility methods for populating {@code AddressBook} with sample data. @@ -21,40 +23,82 @@ 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")), + Optional.empty(), null), 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")), + Optional.empty(), null), 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")), + Optional.empty(), null), 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")), + Optional.empty(), null), 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")), + Optional.empty(), null), 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")) + Optional.of(new Role("DJ")), null), + new Person(new Name("Gourmet Bites Catering"), new Phone("84362714"), new Email("gourmetbites@example.com"), + new Address("123 Orchard Road, #05-67"), Optional.of(new Role("FoodCaterer")), null), + new Person(new Name("Timeless Moments Photograph"), new Phone("82336242"), + new Email("timelessmoments@example.com"), + new Address("456 Marine Parade, #08-34"), Optional.of(new Role("Photographer")), null), + new Person(new Name("Petal Pusher Florals"), new Phone("91234852"), new Email("petalpusher@example.com"), + new Address("321 Holland Road, #01-12"), Optional.of(new Role("Florist")), null), + new Person(new Name("Blossom and Vine"), new Phone("80472642"), new Email("blossomvine@example.com"), + new Address("135 Tanjong Pagar Road, #02-58"), Optional.of(new Role("Florist")), null), + new Person(new Name("Glam Squad"), new Phone("89373673"), new Email("glamsquad@example.com"), + new Address("678 Serangoon Road, #10-23"), Optional.of(new Role("MakeUp")), null) + }; + } + + public static Wedding[] getSampleWeddings() { + return new Wedding[] { + new Wedding(new Name("Alex's Wedding"), null, null), + new Wedding(new Name("Bernice's Wedding"), new Date("2024-12-12"), null), + new Wedding(new Name("Charlotte's Wedding"), null, new Venue("Grand Hyatt")), + new Wedding(new Name("David's Wedding"), new Date("2025-06-01"), new Venue("Marina Bay Sands")) }; } + /** + * Set weddings with clients. + * + * @param persons personlist with persons. + * @param weddings weddinglist with weddings. + */ + public static void setUpWeddings(Person[] persons, Wedding[] weddings) { + // Set up clients + weddings[0].setClient(persons[0]); + weddings[1].setClient(persons[1]); + weddings[2].setClient(persons[2]); + weddings[3].setClient(persons[3]); + // Set up jobs + persons[7].addWeddingJob(weddings[0]); + persons[0].addWeddingJob(weddings[1]); + persons[0].addWeddingJob(weddings[2]); + persons[6].addWeddingJob(weddings[0]); + persons[6].addWeddingJob(weddings[1]); + persons[8].addWeddingJob(weddings[1]); + persons[8].addWeddingJob(weddings[2]); + persons[9].addWeddingJob(weddings[3]); + persons[10].addWeddingJob(weddings[3]); + } + public static ReadOnlyAddressBook getSampleAddressBook() { AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { + Person[] persons = getSamplePersons(); + Wedding[] weddings = getSampleWeddings(); + setUpWeddings(persons, weddings); + for (Person samplePerson : persons) { sampleAb.addPerson(samplePerson); } + for (Wedding wedding : weddings) { + sampleAb.addWedding(wedding); + } 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/model/wedding/Client.java b/src/main/java/seedu/address/model/wedding/Client.java new file mode 100644 index 00000000000..85f63ababe8 --- /dev/null +++ b/src/main/java/seedu/address/model/wedding/Client.java @@ -0,0 +1,87 @@ +package seedu.address.model.wedding; + +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; + +/** + * Represents a Client in a Wedding. + * Guarantees: Field values are validated and immutable. + */ +public class Client { + + public static final String MESSAGE_CONSTRAINTS = + "Client can take any names or index in the address book, and it should not be blank."; + + // Only contains digits 0-9 + public static final String INDEX_VALIDATION_REGEX = "^\\d+$"; + public static final String NAME_VALIDATION_REGEX = Name.VALIDATION_REGEX; + + private final Person person; + + /** + * Constructs a {@code Client}. + * + * @param person A valid {@code Person} to be classified as the Client + */ + public Client(Person person) { + this.person = new Person(person.getName(), person.getPhone(), + person.getEmail(), person.getAddress(), person.getRole(), null); + } + + public Person getPerson() { + return this.person; + } + + public Name getName() { + return this.person.getName(); + } + + /** + * Returns true if a given string is a valid client name. + * + * @param test string to be tested + * @return whether the string is a valid client name. + */ + public static boolean isValidClientName(String test) { + return test.matches(NAME_VALIDATION_REGEX); + } + + + /** + * Returns true if a given string is a valid index. + * + * @param test string to be tested + * @return whether the string is a valid index. + */ + public static boolean isValidClientIndex(String test) { + return test.matches(INDEX_VALIDATION_REGEX); + } + + @Override + public String toString() { + return person.toString(); + } + + public void setOwnWedding(Wedding wedding) { + this.person.setOwnWedding(wedding); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Client)) { + return false; + } + + Client otherClient = (Client) other; + return this.person.equals(otherClient.person); + } + + @Override + public int hashCode() { + return this.person.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/wedding/Date.java b/src/main/java/seedu/address/model/wedding/Date.java new file mode 100644 index 00000000000..6f153462edd --- /dev/null +++ b/src/main/java/seedu/address/model/wedding/Date.java @@ -0,0 +1,73 @@ +package seedu.address.model.wedding; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * Represents the Date of a Wedding. + */ +public class Date { + + public static final String MESSAGE_CONSTRAINTS = + "Date should be valid and in the following format, " + + "YYYY-MM-DD."; + + // Use built-in formatter to parse and validate the date + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + private LocalDate fullDate; + + /** + * Constructs a {@code Date}. + * + * @param date A valid date in the format of "yyyy-MM-dd". + */ + public Date(String date) { + requireNonNull(date); + checkArgument(isValidDate(date), MESSAGE_CONSTRAINTS); + this.fullDate = LocalDate.parse(date, FORMATTER); + } + + /** + * Returns true if a given string is a valid date. + * + * @param test string to be tested + * @return whether the string is a valid date. + */ + public static boolean isValidDate(String test) { + try { + LocalDate.parse(test); + return true; + } catch (DateTimeParseException e) { + return false; + } + } + + @Override + public String toString() { + return fullDate.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Date)) { + return false; + } + + Date otherDate = (Date) other; + return fullDate.equals(otherDate.fullDate); + } + + @Override + public int hashCode() { + return fullDate.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/wedding/NameMatchesWeddingPredicate.java b/src/main/java/seedu/address/model/wedding/NameMatchesWeddingPredicate.java new file mode 100644 index 00000000000..6ee183d0147 --- /dev/null +++ b/src/main/java/seedu/address/model/wedding/NameMatchesWeddingPredicate.java @@ -0,0 +1,90 @@ +package seedu.address.model.wedding; + +import static java.util.Objects.requireNonNull; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import seedu.address.commons.util.ToStringBuilder; + +/** + * Tests that a {@code Wedding}'s couple names match all of the keywords given. + * Different from NameContainsKeywordsPredicate as it requires all keywords to be present in the names. + * Example: Given keywords ["John", "Mary"], a wedding with name "John Mary Smith" would match + * because both "John" and "Mary" are present in the name. + */ +public class NameMatchesWeddingPredicate implements Predicate { + private final List keywords; + + /** + * Constructs a NameMatchesWeddingPredicate with the given list of names. + * Any multi-word names in the list will be split into individual words. + * For example, "John Mary" would be split into ["John", "Mary"]. + * + * @param names A list of strings containing names or name keywords to match against + */ + public NameMatchesWeddingPredicate(List names) { + // Split any multi-word strings into individual words + this.keywords = names.stream() + .flatMap(name -> Arrays.stream(name.split("\\s+"))) + .filter(word -> !word.isEmpty()) + .collect(Collectors.toList()); + } + + /** + * Tests if a given wedding's name contains all the keywords. + * The test is case-insensitive. + * Returns true only if all keywords are found in the wedding name. + * If no keywords were provided (empty list), returns false. + * + * @param wedding The wedding to test + * @return true if the wedding's name contains all keywords, false otherwise + */ + @Override + public boolean test(Wedding wedding) { + requireNonNull(wedding); + // Return false if keywords list is empty + if (keywords.isEmpty()) { + return false; + } + return keywords.stream() + .allMatch(keyword -> wedding.getName().fullName.toLowerCase().contains(keyword.toLowerCase())); + } + + /** + * Compares this predicate with another object for equality. + * Two NameMatchesWeddingPredicates are equal if they have the same keywords. + * + * @param other The object to compare with + * @return true if the objects are equal, false otherwise + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof NameMatchesWeddingPredicate)) { + return false; + } + + NameMatchesWeddingPredicate otherNameMatchesWeddingPredicate = (NameMatchesWeddingPredicate) other; + return keywords.equals(otherNameMatchesWeddingPredicate.keywords); + } + + /** + * Returns a string representation of this NameMatchesWeddingPredicate. + * The string includes the list of keywords being matched against. + * + * @return A string representation of this predicate + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("keywords", keywords) + .toString(); + } +} diff --git a/src/main/java/seedu/address/model/wedding/PersonHasWeddingPredicate.java b/src/main/java/seedu/address/model/wedding/PersonHasWeddingPredicate.java new file mode 100644 index 00000000000..d884cf76ac2 --- /dev/null +++ b/src/main/java/seedu/address/model/wedding/PersonHasWeddingPredicate.java @@ -0,0 +1,71 @@ +package seedu.address.model.wedding; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Wedding} is contained in a person under their wedding jobs or own wedding. + */ +public class PersonHasWeddingPredicate implements Predicate { + private final Person person; + + /** + * Constructs a PersonHasWeddingPredicate with the given wedding + * + * @param person The person object to check against + */ + public PersonHasWeddingPredicate(Person person) { + this.person = person; + } + + /** + * Tests if a given wedding is contained by the person + * Returns true only if wedding is found in the person as its {@code ownWedding} or {@code weddingJobs}. + * + * @param wedding The wedding to test + * @return true if the wedding is found in the person, false otherwise + */ + @Override + public boolean test(Wedding wedding) { + return (person.getOwnWedding() != null && person.getOwnWedding().equals(wedding)) + || person.containsWeddingJob(wedding); + } + + /** + * Compares this predicate with another object for equality. + * Two PersonHasWeddingPredicate are equal if they have the same person. + * + * @param other The object to compare with + * @return true if the objects are equal, false otherwise + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PersonHasWeddingPredicate)) { + return false; + } + + PersonHasWeddingPredicate otherPersonHasWeddingPredicate = (PersonHasWeddingPredicate) other; + return person.equals(otherPersonHasWeddingPredicate.person); + } + + /** + * Returns a string representation of this PersonHasWeddingPredicate. + * The string includes the person being matched against. + * + * @return A string representation of this predicate + */ + @Override + public String toString() { + return new ToStringBuilder(this) + .add("person", person) + .toString(); + } + +} diff --git a/src/main/java/seedu/address/model/wedding/UniqueWeddingList.java b/src/main/java/seedu/address/model/wedding/UniqueWeddingList.java new file mode 100644 index 00000000000..3dda96d7774 --- /dev/null +++ b/src/main/java/seedu/address/model/wedding/UniqueWeddingList.java @@ -0,0 +1,165 @@ +package seedu.address.model.wedding; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.model.wedding.exceptions.DuplicateWeddingException; +import seedu.address.model.wedding.exceptions.WeddingNotFoundException; + +/** + * A list of wedding that enforces uniqueness between its elements and does not allow nulls. + * A wedding is considered unique by comparing using {@code Wedding#isSameWedding(Wedding)}. + * As such, adding and updating of persons uses Wedding#isSameWedding(Wedding) for equality + * so as to ensure that the wedding being added or updated is unique in terms of identity in the UniqueWeddingList. + * However, the removal of a wedding uses Wedding#isSameWedding(Object) + * so as to ensure that the person with exactly the same fields will be removed. + * + * Supports a minimal set of list operations. + * + * @see Wedding#isSameWedding(Wedding) + */ + +public class UniqueWeddingList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent wedding as the given argument. + */ + public boolean contains(Wedding toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameWedding); + } + + /** + * Adds a wedding to the list. + * The wedding must not already exist in the list. + */ + public void add(Wedding toAdd) { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateWeddingException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the wedding {@code target} in the list with {@code editedWedding}. + * {@code target} must exist in the list. + * The wedding identity of {@code editedWedding} must not be the same as another existing wedding in the list. + */ + public void setWedding(Wedding target, Wedding editedWedding) { + requireAllNonNull(target, editedWedding); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new WeddingNotFoundException(); + } + + if (!target.isSameWedding(editedWedding) && contains(editedWedding)) { + throw new DuplicateWeddingException(); + } + + internalList.set(index, editedWedding); + } + + /** + * Removes the equivalent wedding from the list. + * The wedding must exist in the list. + */ + public void remove(Wedding toRemove) { + requireNonNull(toRemove); + if (!internalList.remove(toRemove)) { + throw new WeddingNotFoundException(); + } + } + + /** + * Replaces the contents of this list with {@code weddings}. + * {@code weddings} must not contain duplicate weddings. + */ + public void setWeddings(List weddings) { + requireAllNonNull(weddings); + if (!weddingsAreUnique(weddings)) { + throw new DuplicateWeddingException(); + } + + internalList.setAll(weddings); + } + + /** + * Replaces the contents of this list with {@code weddings}. + * {@code weddings} must not contain duplicate weddings. + */ + public void setWeddings(UniqueWeddingList replacement) { + requireAllNonNull(replacement.internalList); + internalList.setAll(replacement.internalList); + } + + /** + * Sets all wedding in this list to not be ownWedding. + */ + public void setAllWeddingIsOwnFalse() { + for (Wedding wedding : internalList) { + wedding.setIsOwnWedding(false); + } + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof UniqueWeddingList)) { + return false; + } + + UniqueWeddingList otherUniqueWeddingList = (UniqueWeddingList) other; + return internalList.equals(otherUniqueWeddingList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + + /** + * Returns true if {@code weddings} contains only unique weddings. + */ + private boolean weddingsAreUnique(List weddings) { + for (int i = 0; i < weddings.size() - 1; i++) { + for (int j = i + 1; j < weddings.size(); j++) { + if (weddings.get(i).isSameWedding(weddings.get(j))) { + return false; + } + } + } + return true; + } +} diff --git a/src/main/java/seedu/address/model/wedding/Venue.java b/src/main/java/seedu/address/model/wedding/Venue.java new file mode 100644 index 00000000000..fecbd6166e8 --- /dev/null +++ b/src/main/java/seedu/address/model/wedding/Venue.java @@ -0,0 +1,63 @@ +package seedu.address.model.wedding; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents the Venue of a Wedding. + */ +public class Venue { + + public static final String MESSAGE_CONSTRAINTS = "Venues can take any values, and it should not be blank."; + + // Any non empty string + public static final String VALIDATION_REGEX = "[^\\s].*"; + + private String fullVenue; + + /** + * Constructs a {@code Venue}. + * + * @param venue A valid venue. + */ + public Venue(String venue) { + requireNonNull(venue); + checkArgument(isValidVenue(venue), MESSAGE_CONSTRAINTS); + this.fullVenue = venue; + } + + /** + * Returns true if a given string is a valid venue. + * @param test string to be tested + * @return whether the string is a valid venue. + */ + public static boolean isValidVenue(String test) { + return test.matches(VALIDATION_REGEX); + } + + @Override + public String toString() { + return fullVenue; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof Venue)) { + return false; + } + + Venue otherVenue = (Venue) other; + return fullVenue.equals(otherVenue.fullVenue); + } + + @Override + public int hashCode() { + return fullVenue.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/wedding/Wedding.java b/src/main/java/seedu/address/model/wedding/Wedding.java new file mode 100644 index 00000000000..366b8587fd4 --- /dev/null +++ b/src/main/java/seedu/address/model/wedding/Wedding.java @@ -0,0 +1,134 @@ +package seedu.address.model.wedding; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; + +/** + * Represents a wedding plan. + */ +public class Wedding { + private Name name; + private Client client; + private Date date; + private Venue venue; + private boolean isOwnWedding; + + /** + * Constructs a temporary {@code Wedding}. + */ + public Wedding(Name name, Date date, Venue venue) { + requireAllNonNull(name); + this.name = name; + this.date = date; + this.venue = venue; + } + + /** + * Constructs a {@code Wedding}. + */ + public Wedding(Name name, Client client, Date date, Venue venue) { + requireAllNonNull(name); + this.name = name; + this.client = client; + if (client != null + && (client.getPerson().getOwnWedding() == null || client.getPerson().getOwnWedding() != this)) { + client.getPerson().setOwnWedding(this); + } + this.date = date; + this.venue = venue; + } + + public Name getName() { + return name; + } + + public Client getClient() { + return client; + } + + public Date getDate() { + return date; + } + + public Venue getVenue() { + return venue; + } + + public boolean isOwnWedding() { + return isOwnWedding; + } + + public void setClient(Person person) { + this.client = new Client(person); + if (person.getOwnWedding() == null || person.getOwnWedding() != this) { + person.setOwnWedding(this); + } + } + + public void setDate(Date date) { + this.date = date; + } + + public void setVenue(Venue venue) { + this.venue = venue; + } + + public void setIsOwnWedding(boolean isOwnWedding) { + this.isOwnWedding = isOwnWedding; + } + + /** + * Returns true if both weddings have the same identity. + */ + public boolean isSameWedding(Wedding otherWedding) { + return this.equals(otherWedding); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("name", name) + .add("client", client == null ? "NA" : client) + .add("date", date == null ? "NA" : date) + .add("venue", venue == null ? "NA" : venue) + .toString(); + } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Wedding)) { + return false; + } + + Wedding otherWedding = (Wedding) other; + + boolean nameEqual = name.equals(otherWedding.name); + + boolean clientEqual = (client == null && otherWedding.client == null) + || (client != null && client.equals(otherWedding.client)); + + boolean dateEqual = (date == null && otherWedding.date == null) + || (date != null && date.equals(otherWedding.date)); + + boolean venueEqual = (venue == null && otherWedding.venue == null) + || (venue != null && venue.equals(otherWedding.venue)); + + return nameEqual + && clientEqual + && dateEqual + && venueEqual; + } + + @Override + public int hashCode() { + return Objects.hash(name, client, date, venue); + } +} diff --git a/src/main/java/seedu/address/model/wedding/WeddingMatchesClientPredicate.java b/src/main/java/seedu/address/model/wedding/WeddingMatchesClientPredicate.java new file mode 100644 index 00000000000..ea4f1783e9c --- /dev/null +++ b/src/main/java/seedu/address/model/wedding/WeddingMatchesClientPredicate.java @@ -0,0 +1,52 @@ +package seedu.address.model.wedding; + +import java.util.function.Predicate; + +import seedu.address.model.person.Person; + +/** + * Tests that a {@code Person}'s {@code Wedding} matches the given wedding. + */ +public class WeddingMatchesClientPredicate implements Predicate { + private final Person person; + + /** + * Constructs a WeddingMatchesClientPredicate with the given person + * + * @param person The person object to check against + */ + public WeddingMatchesClientPredicate(Person person) { + this.person = person; + } + + public Person getPerson() { + return person; + } + + /** + * Tests if a person is the client of a given wedding + * Returns true only if person is the client of the given wedding. + * + * @param wedding The wedding to test + * @return true if the person is the client of the wedding, false otherwise + */ + @Override + public boolean test(Wedding wedding) { + return person.getOwnWedding() != null && person.getOwnWedding().equals(wedding); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof WeddingMatchesClientPredicate)) { + return false; + } + + WeddingMatchesClientPredicate other1 = (WeddingMatchesClientPredicate) other; + return person.equals(other1.person); + } +} diff --git a/src/main/java/seedu/address/model/wedding/exceptions/DuplicateWeddingException.java b/src/main/java/seedu/address/model/wedding/exceptions/DuplicateWeddingException.java new file mode 100644 index 00000000000..7c1904637c8 --- /dev/null +++ b/src/main/java/seedu/address/model/wedding/exceptions/DuplicateWeddingException.java @@ -0,0 +1,11 @@ +package seedu.address.model.wedding.exceptions; + +/** + * Signals that the operation will result in duplicate Wedding (Wedding are considered duplicates if they have the same + * name, venue, date and client). + */ +public class DuplicateWeddingException extends RuntimeException { + public DuplicateWeddingException() { + super("Operation would result in duplicate weddings"); + } +} diff --git a/src/main/java/seedu/address/model/wedding/exceptions/WeddingNotFoundException.java b/src/main/java/seedu/address/model/wedding/exceptions/WeddingNotFoundException.java new file mode 100644 index 00000000000..7562f909194 --- /dev/null +++ b/src/main/java/seedu/address/model/wedding/exceptions/WeddingNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.address.model.wedding.exceptions; + +/** + * Signals that the operation is unable to find the specified wedding. + */ +public class WeddingNotFoundException extends RuntimeException { +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index bd1ca0f56c8..cc8d96c81b9 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -3,6 +3,8 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -15,7 +17,8 @@ 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.role.Role; +import seedu.address.model.wedding.Wedding; /** * Jackson-friendly version of {@link Person}. @@ -28,21 +31,33 @@ class JsonAdaptedPerson { private final String phone; private final String email; private final String address; - private final List tags = new ArrayList<>(); + private final String role; + + //Own wedding is stored as hashcode + private final int ownWedding; + // Wedding jobs are stored as hashcodes + private final List weddingJobs = 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) { + public JsonAdaptedPerson( + @JsonProperty("name") String name, + @JsonProperty("phone") String phone, + @JsonProperty("email") String email, + @JsonProperty("address") String address, + @JsonProperty("role") String role, + @JsonProperty("ownWedding") int ownWedding, + @JsonProperty("weddingJobs") List weddingJobs) { this.name = name; this.phone = phone; this.email = email; this.address = address; - if (tags != null) { - this.tags.addAll(tags); + this.role = role; + this.ownWedding = ownWedding; + if (weddingJobs != null) { + this.weddingJobs.addAll(weddingJobs); } } @@ -54,8 +69,12 @@ public JsonAdaptedPerson(Person source) { phone = source.getPhone().value; email = source.getEmail().value; address = source.getAddress().value; - tags.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) + role = source.getRole().map(r -> r.roleName).orElse(null); + // If person is not a client, hashcode to represent own wedding is replaced by 0 + ownWedding = source.getOwnWedding() != null ? source.getOwnWedding().hashCode() : 0; + // Wedding jobs are stored as hashcodes + weddingJobs.addAll(source.getWeddingJobs().stream() + .map(Wedding::hashCode) .collect(Collectors.toList())); } @@ -64,11 +83,7 @@ public JsonAdaptedPerson(Person source) { * * @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()); - } + public Person toModelType(List weddingList) throws IllegalValueException { if (name == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); @@ -102,8 +117,88 @@ public Person toModelType() throws IllegalValueException { } final Address modelAddress = new Address(address); - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + Optional modelRole = Optional.empty(); + + if (role != null) { + if (!Role.isValidRoleName(role)) { + throw new IllegalValueException(Role.MESSAGE_CONSTRAINTS); + } + modelRole = Optional.of(new Role(role)); + } + boolean modelHasOwnWedding = ownWedding != 0; + Wedding modelOwnWedding = null; + if (modelHasOwnWedding) { + modelOwnWedding = lookupWeddingByHashCode(ownWedding, weddingList); + } + Set modelWeddingJobs = new HashSet<>(); + for (Integer weddingHashCode : weddingJobs) { + Wedding wedding = lookupWeddingByHashCode(weddingHashCode, weddingList); + if (wedding != null) { + modelWeddingJobs.add(wedding); + } + } + Person person = new Person(modelName, modelPhone, modelEmail, modelAddress, modelRole, modelOwnWedding); + person.getWeddingJobs().addAll(modelWeddingJobs); + return person; + } + + /** + * Returns the {@code Wedding} corresponding to the hashcode + * @param hashCode hashcode to match Weddings to + * @param weddings List of {@code Wedding} to search search from + */ + private Wedding lookupWeddingByHashCode(int hashCode, List weddings) { + for (Wedding wedding : weddings) { + if (wedding.hashCode() == hashCode) { + return wedding; + } + } + return null; + } + + /** + * Gets the wedding jobs hashcodes list. + */ + public List getWeddingJobs() { + return new ArrayList<>(weddingJobs); + } + + /** + * Gets the own wedding hashcode. + */ + public int getOwnWedding() { + return ownWedding; + } + + /** + * Gets the name of the person. + */ + public String getName() { + return name; } + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof JsonAdaptedPerson)) { + return false; + } + + JsonAdaptedPerson otherPerson = (JsonAdaptedPerson) other; + return name.equals(otherPerson.name) + && phone.equals(otherPerson.phone) + && email.equals(otherPerson.email) + && address.equals(otherPerson.address) + && ((role == null && otherPerson.role == null) || (role != null && role.equals(otherPerson.role))) + && ownWedding == otherPerson.ownWedding + && weddingJobs.equals(otherPerson.weddingJobs); + } + + @Override + public int hashCode() { + return Objects.hash(name, phone, email, address, role, ownWedding, weddingJobs); + } } 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/JsonAdaptedWedding.java b/src/main/java/seedu/address/storage/JsonAdaptedWedding.java new file mode 100644 index 00000000000..6e2ab561703 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedWedding.java @@ -0,0 +1,117 @@ +package seedu.address.storage; + +import java.util.List; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.person.Name; +import seedu.address.model.wedding.Client; +import seedu.address.model.wedding.Date; +import seedu.address.model.wedding.Venue; +import seedu.address.model.wedding.Wedding; + +/** + * Jackson-friendly version of {@link Wedding}. + */ +class JsonAdaptedWedding { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Wedding's %s field is missing!"; + + private final String name; + private final JsonAdaptedPerson client; + private final String date; + private final String venue; + + /** + * Constructs a {@code JsonAdaptedWedding} with the given wedding details. + */ + @JsonCreator + public JsonAdaptedWedding( + @JsonProperty("name") String name, + @JsonProperty("client") JsonAdaptedPerson client, + @JsonProperty("date") String date, + @JsonProperty("venue") String venue) { + this.name = name; + this.client = client; + this.date = date; + this.venue = venue; + } + + /** + * Converts a given {@code Wedding} into this class for Jackson use. + */ + public JsonAdaptedWedding(Wedding source) { + name = source.getName().fullName; + if (source.getClient() == null) { + client = null; + } else { + client = new JsonAdaptedPerson(source.getClient().getPerson()); + } + date = source.getDate() == null ? null : source.getDate().toString(); + venue = source.getVenue() == null ? null : source.getVenue().toString(); + } + + /** + * Converts this Jackson-friendly adapted wedding object into the model's {@code Wedding} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted wedding. + */ + public Wedding toModelType(List weddingList) throws IllegalValueException { + 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 (client == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Client.class.getSimpleName())); + } + + Client finalClient = new Client(client.toModelType(weddingList)); + + Date modelDate = null; + if (date != null) { + if (!Date.isValidDate(date)) { + throw new IllegalValueException(Date.MESSAGE_CONSTRAINTS); + } + modelDate = new Date(date); + } + + Venue modelVenue = null; + if (venue != null) { + if (!Venue.isValidVenue(venue)) { + throw new IllegalValueException(Venue.MESSAGE_CONSTRAINTS); + } + modelVenue = new Venue(venue); + } + + return new Wedding(modelName, finalClient, modelDate, modelVenue); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof JsonAdaptedWedding)) { + return false; + } + + JsonAdaptedWedding otherWedding = (JsonAdaptedWedding) other; + return Objects.equals(name, otherWedding.name) + && Objects.equals(client, otherWedding.client) + && Objects.equals(date, otherWedding.date) + && Objects.equals(venue, otherWedding.venue); + } + + @Override + public int hashCode() { + return Objects.hash(name, client, date, venue); + } +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..f93c56c9463 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonCreator; @@ -12,23 +13,30 @@ import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; import seedu.address.model.person.Person; +import seedu.address.model.wedding.Wedding; /** * An Immutable AddressBook that is serializable to JSON format. */ @JsonRootName(value = "addressbook") -class JsonSerializableAddressBook { +public class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_WEDDING = "Weddings list contains duplicate wedding(s)."; + public static final String MESSAGE_CORRUPTED_WEDDING_DATA = + "Address book data is corrupted: Found weddings without corresponding clients."; private final List persons = new ArrayList<>(); + private final List weddings = new ArrayList<>(); /** * Constructs a {@code JsonSerializableAddressBook} with the given persons. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { + public JsonSerializableAddressBook(@JsonProperty("persons") List persons, + @JsonProperty("weddings") List weddings) { this.persons.addAll(persons); + this.weddings.addAll(weddings); } /** @@ -38,6 +46,42 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List weddings) throws IllegalValueException { + // Collect all valid wedding hash codes + Set validWeddingHashCodes = weddings.stream() + .map(Wedding::hashCode) + .collect(Collectors.toSet()); + + for (JsonAdaptedPerson person : persons) { + int ownWeddingHash = person.getOwnWedding(); + + // Check if own wedding hash is valid, ignore if 0 (unassigned) + boolean personIsClient = ownWeddingHash != 0; + boolean weddingHashCodeExists = validWeddingHashCodes.contains(ownWeddingHash); + if (personIsClient && !weddingHashCodeExists) { + throw new IllegalValueException(MESSAGE_CORRUPTED_WEDDING_DATA); + } + + // Validate each wedding job hash + List weddingJobs = person.getWeddingJobs(); + if (weddingJobs != null) { + for (Integer weddingJobHash : weddingJobs) { + boolean weddingJobHashExists = validWeddingHashCodes.contains(weddingJobHash); + if (!weddingJobHashExists) { + throw new IllegalValueException(MESSAGE_CORRUPTED_WEDDING_DATA); + } + } + } + } } /** @@ -46,15 +90,37 @@ public JsonSerializableAddressBook(ReadOnlyAddressBook source) { * @throws IllegalValueException if there were any data constraints violated. */ public AddressBook toModelType() throws IllegalValueException { + + AddressBook addressBook = new AddressBook(); + + // Add weddings first + for (JsonAdaptedWedding jsonAdaptedWedding : weddings) { + Wedding wedding = jsonAdaptedWedding.toModelType(addressBook.getWeddingList()); + if (addressBook.hasWedding(wedding)) { + throw new IllegalValueException(MESSAGE_DUPLICATE_WEDDING); + } + addressBook.addWedding(wedding); + } + validateWeddingIntegrity(addressBook.getWeddingList()); + + // Add persons for (JsonAdaptedPerson jsonAdaptedPerson : persons) { - Person person = jsonAdaptedPerson.toModelType(); + Person person = jsonAdaptedPerson.toModelType(addressBook.getWeddingList()); if (addressBook.hasPerson(person)) { throw new IllegalValueException(MESSAGE_DUPLICATE_PERSON); } addressBook.addPerson(person); } + + // Link clients to weddings + for (Wedding wedding : addressBook.getWeddingList()) { + for (Person person : addressBook.getPersonList()) { + if (person.getOwnWedding() != null && person.getOwnWedding().equals(wedding)) { + person.setOwnWedding(wedding); + } + } + } return addressBook; } - } diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9e75478664b..82574a03ddd 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -4,9 +4,11 @@ import javafx.fxml.FXML; import javafx.scene.control.TextField; import javafx.scene.layout.Region; +import javafx.scene.text.Text; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.ui.suggestion.Suggestions; /** * The UI component that is responsible for receiving user command inputs. @@ -17,9 +19,12 @@ public class CommandBox extends UiPart { private static final String FXML = "CommandBox.fxml"; private final CommandExecutor commandExecutor; + private final Suggestions suggestions; @FXML private TextField commandTextField; + @FXML + private TextField suggestionTextField; /** * Creates a {@code CommandBox} with the given {@code CommandExecutor}. @@ -27,8 +32,58 @@ public class CommandBox extends UiPart { public CommandBox(CommandExecutor commandExecutor) { super(FXML); this.commandExecutor = commandExecutor; - // calls #setStyleToDefault() whenever there is a change to the text of the command box. - commandTextField.textProperty().addListener((unused1, unused2, unused3) -> setStyleToDefault()); + this.suggestions = new Suggestions(); + setupTextFields(); + } + + private void setupTextFields() { + suggestionTextField.setMouseTransparent(true); + suggestionTextField.setFocusTraversable(false); + + // Make suggestion text field transparent and match command text field + suggestionTextField.setStyle("-fx-background-color: transparent;"); + suggestionTextField.fontProperty().bind(commandTextField.fontProperty()); + + // Bind suggestion width to command width + suggestionTextField.prefWidthProperty().bind(commandTextField.widthProperty()); + + // Add listeners for real-time command detection and positioning + commandTextField.textProperty().addListener((observable, oldValue, newValue) -> { + updateSuggestion(newValue); + updateSuggestionPosition(); + setStyleToDefault(); + }); + + // This ensures suggestion updates when the text layout changes + commandTextField.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> { + updateSuggestionPosition(); + }); + } + + private void updateSuggestion(String currentText) { + String fullSuggestion = suggestions.checkAllCommands(currentText); + + if (fullSuggestion.isEmpty() || !fullSuggestion.startsWith(currentText)) { + suggestionTextField.setText(""); + return; + } + + // Only show the remaining part of the suggestion + String remainingSuggestion = fullSuggestion.substring(currentText.length()); + suggestionTextField.setText(remainingSuggestion); + } + + + private void updateSuggestionPosition() { + String currentText = commandTextField.getText(); + + // Get the text width using a Text node for accurate measurement + Text text = new Text(currentText); + text.setFont(commandTextField.getFont()); + double textWidth = text.getLayoutBounds().getWidth(); + + // Position the suggestion text field + suggestionTextField.setTranslateX(textWidth); } /** @@ -37,13 +92,14 @@ public CommandBox(CommandExecutor commandExecutor) { @FXML private void handleCommandEntered() { String commandText = commandTextField.getText(); - if (commandText.equals("")) { + if (commandText.isEmpty()) { return; } try { commandExecutor.execute(commandText); - commandTextField.setText(""); + commandTextField.clear(); + suggestionTextField.clear(); } catch (CommandException | ParseException e) { setStyleToIndicateCommandFailure(); } @@ -54,6 +110,7 @@ private void handleCommandEntered() { */ private void setStyleToDefault() { commandTextField.getStyleClass().remove(ERROR_STYLE_CLASS); + suggestionTextField.getStyleClass().remove(ERROR_STYLE_CLASS); } /** @@ -61,12 +118,10 @@ private void setStyleToDefault() { */ private void setStyleToIndicateCommandFailure() { ObservableList styleClass = commandTextField.getStyleClass(); - - if (styleClass.contains(ERROR_STYLE_CLASS)) { - return; + if (!styleClass.contains(ERROR_STYLE_CLASS)) { + styleClass.add(ERROR_STYLE_CLASS); + suggestionTextField.clear(); } - - styleClass.add(ERROR_STYLE_CLASS); } /** @@ -81,5 +136,4 @@ public interface CommandExecutor { */ CommandResult execute(String commandText) throws CommandException, ParseException; } - } diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..f9324b9fda3 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -15,7 +15,7 @@ */ 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://ay2425s1-cs2103t-t11-3.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/address/ui/MainWindow.java index 79e74ef37c0..62e7c06e4ed 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -32,6 +32,7 @@ public class MainWindow extends UiPart { // Independent Ui parts residing in this Ui container private PersonListPanel personListPanel; + private WeddingListPanel weddingListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; @@ -44,6 +45,9 @@ public class MainWindow extends UiPart { @FXML private StackPane personListPanelPlaceholder; + @FXML + private StackPane weddingListPanelPlaceholder; + @FXML private StackPane resultDisplayPlaceholder; @@ -113,6 +117,9 @@ void fillInnerParts() { personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + weddingListPanel = new WeddingListPanel(logic.getFilteredWeddingList()); + weddingListPanelPlaceholder.getChildren().add(weddingListPanel.getRoot()); + resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); @@ -167,6 +174,10 @@ public PersonListPanel getPersonListPanel() { return personListPanel; } + public WeddingListPanel getWeddingListPanel() { + return weddingListPanel; + } + /** * Executes the command and returns the result. * diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 094c42cda82..ea6fc637b86 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -1,7 +1,5 @@ package seedu.address.ui; -import java.util.Comparator; - import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.FlowPane; @@ -39,7 +37,9 @@ public class PersonCard extends UiPart { @FXML private Label email; @FXML - private FlowPane tags; + private FlowPane client; + @FXML + private FlowPane role; /** * Creates a {@code PersonCode} with the given {@code Person} and index to display. @@ -52,8 +52,14 @@ public PersonCard(Person person, int displayedIndex) { 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))); + if (person.isClient()) { + client.getChildren().add(new Label("Client")); + } else { + client.setVisible(false); + client.setManaged(false); + } + person.getRole().ifPresent(personRole -> { + role.getChildren().add(new Label(personRole.roleName)); + }); } } diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/address/ui/ResultDisplay.java index 7d98e84eedf..0b2d70769c2 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/address/ui/ResultDisplay.java @@ -16,13 +16,20 @@ public class ResultDisplay extends UiPart { @FXML private TextArea resultDisplay; + /** + * Creates a {@code ResultDisplay} with the given {@code String}. + */ public ResultDisplay() { super(FXML); + resultDisplay.setWrapText(true); + // Make text area auto-resize + resultDisplay.textProperty().addListener((observable, oldValue, newValue) -> { + resultDisplay.setPrefRowCount(Math.max(1, newValue.split("\n").length)); + }); } public void setFeedbackToUser(String feedbackToUser) { requireNonNull(feedbackToUser); resultDisplay.setText(feedbackToUser); } - } diff --git a/src/main/java/seedu/address/ui/WeddingCard.java b/src/main/java/seedu/address/ui/WeddingCard.java new file mode 100644 index 00000000000..53ac9faaaa4 --- /dev/null +++ b/src/main/java/seedu/address/ui/WeddingCard.java @@ -0,0 +1,63 @@ +package seedu.address.ui; + +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.wedding.Wedding; + +/** + * An UI component that displays information of a {@code Person}. + */ +public class WeddingCard extends UiPart { + + private static final String FXML = "WeddingListCard.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 Wedding wedding; + + @FXML + private HBox cardPane; + @FXML + private Label weddingName; + @FXML + private Label id; + @FXML + private Label date; + @FXML + private Label venue; + @FXML + private FlowPane ownWedding; + + /** + * Creates a {@code PersonCode} with the given {@code Person} and index to display. + */ + public WeddingCard(Wedding wedding, int displayedIndex) { + super(FXML); + this.wedding = wedding; + id.setText(displayedIndex + ". "); + weddingName.setText(wedding.getName().fullName); + venue.setText(wedding.getVenue() == null ? null : wedding.getVenue().toString()); + + if (wedding.getDate() == null) { + date.visibleProperty().setValue(false); + } else { + date.setText(wedding.getDate().toString()); + } + + if (wedding.isOwnWedding()) { + ownWedding.getChildren().add(new Label("Own Wedding")); + } else { + ownWedding.visibleProperty().setValue(false); + ownWedding.setManaged(false); + } + } +} diff --git a/src/main/java/seedu/address/ui/WeddingListPanel.java b/src/main/java/seedu/address/ui/WeddingListPanel.java new file mode 100644 index 00000000000..9eb9baf03b1 --- /dev/null +++ b/src/main/java/seedu/address/ui/WeddingListPanel.java @@ -0,0 +1,49 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +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.wedding.Wedding; + +/** + * Panel containing the list of persons. + */ +public class WeddingListPanel extends UiPart { + private static final String FXML = "WeddingListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(WeddingListPanel.class); + + @FXML + private ListView weddingListView; + + /** + * Creates a {@code PersonListPanel} with the given {@code ObservableList}. + */ + public WeddingListPanel(ObservableList weddingList) { + super(FXML); + weddingListView.setItems(weddingList); + weddingListView.setCellFactory(listView -> new WeddingListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}. + */ + class WeddingListViewCell extends ListCell { + @Override + protected void updateItem(Wedding wedding, boolean empty) { + super.updateItem(wedding, empty); + + if (empty || wedding == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new WeddingCard(wedding, getIndex() + 1).getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/suggestion/Commands.java b/src/main/java/seedu/address/ui/suggestion/Commands.java new file mode 100644 index 00000000000..540c9f40105 --- /dev/null +++ b/src/main/java/seedu/address/ui/suggestion/Commands.java @@ -0,0 +1,73 @@ +package seedu.address.ui.suggestion; + +/** + * Enum representing the various commands available in the application. + * Each command has a name, an example format, and associated prefixes for parameters. + */ +public enum Commands { + ADD("add", "add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [r/ROLE] [w/WEDDING...]", + new String[]{"n/", "p/", "e/", "a/", "r/", "w/"}), + DELETE("delete", "delete INDEX/NAME [w/WEDDING...]", new String[]{}), + CLEAR("clear", "clear", new String[]{}), + VIEW("view", "view INDEX/NAME", new String[]{}), + FILTER("filter", "filter [n/NAME] [r/ROLE] [e/EMAIL] [p/PHONE] [a/ADDRESS]...", + new String[]{"n/", "r/", "e/", "p/", "a/"}), + FIND("find", "find KEYWORD [MORE_KEYWORDS...]", new String[]{}), + LIST("list", "list", new String[]{}), + EXIT("exit", "exit", new String[]{}), + HELP("help", "help", new String[]{}), + EDIT("edit", "edit INDEX/NAME [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS]", + new String[]{"n/", "p/", "e/", "a/"}), + ADDWEDDING("addw", "addw n/WEDDING_NAME c/CLIENT [d/DATE] [v/VENUE]", + new String[]{"n/", "c/", "d/", "v/"}), + EDITWEDDING("editw", "editw w/INDEX [n/NAME] [d/DATE] [v/VENUE]", + new String[]{"n/", "d/", "v/", "w/"}), + VIEWWEDDING("vieww", "vieww INDEX/WEDDING_NAME", new String[]{}), + DELETEWEDDING("deletew", "deletew INDEX/WEDDING_NAME", new String[]{}), + ASSIGN("assign", "assign INDEX/NAME [r/ROLE] [w/WEDDING_INDEX...]", + new String[]{"r/", "w/"}); + + private final String commandName; + private final String formatExample; + private final String[] formatPrefixes; + + /** + * Constructor for Commands enum. + * + * @param commandName The name of the command. + * @param formatExample The example format of the command. + * @param formatPrefixes The prefixes for parameters associated with the command. + */ + Commands(String commandName, String formatExample, String[] formatPrefixes) { + this.commandName = commandName; + this.formatExample = formatExample; + this.formatPrefixes = formatPrefixes; + } + + /** + * Gets the command name. + * + * @return The name of the command. + */ + public String getCommand() { + return commandName; + } + + /** + * Gets the example format of the command. + * + * @return The format example of the command. + */ + public String getExample() { + return formatExample; + } + + /** + * Gets the prefixes associated with the command. + * + * @return An array of prefixes for the command's parameters. + */ + public String[] getPrefix() { + return formatPrefixes; + } +} diff --git a/src/main/java/seedu/address/ui/suggestion/FormatSuggestion.java b/src/main/java/seedu/address/ui/suggestion/FormatSuggestion.java new file mode 100644 index 00000000000..ef290a08f3a --- /dev/null +++ b/src/main/java/seedu/address/ui/suggestion/FormatSuggestion.java @@ -0,0 +1,116 @@ +package seedu.address.ui.suggestion; + +/** + * Represents a format suggestion for a command in the application. + * This class provides functionality to generate remaining command formats + * based on user input and predefined command formats. + */ +public class FormatSuggestion { + private final String commandName; + private final String formatExample; + private final String[] formatPrefixes; + private final String[] formatParts; + + /** + * Constructs a FormatSuggestion with the specified command name, + * example format, and parameter prefixes. + * + * @param commandName The name of the command. + * @param formatExample The example format for the command. + * @param formatPrefixes The prefixes associated with the command's parameters. + */ + public FormatSuggestion(String commandName, String formatExample, String[] formatPrefixes) { + this.commandName = commandName; + this.formatExample = formatExample; + this.formatPrefixes = formatPrefixes; + this.formatParts = removeFirstWord(formatExample).split("\\s+"); + } + + /** + * Returns the remaining format parts that the user needs to enter based + * on the input provided. + * + * @param enteredText The text entered by the user. + * @return A string representing the remaining format parts. + */ + public String getRemainingFormat(String enteredText) { + if (enteredText.trim().equals(commandName)) { + return " " + removeFirstWord(formatExample); + } else if (enteredText.length() > commandName.length()) { + String typedAfterCommandName = enteredText.substring(commandName.length()).trim(); + String[] typedParts = typedAfterCommandName.split("\\s+"); + + StringBuilder remainingFormat = new StringBuilder(); + + // If user did not type a number for INDEX, there is no remaining format parts + if (formatParts[0].equals("INDEX") && !typedParts[0].matches("\\d+")) { + return ""; + } + + // If user did not type an alphabet for INDEX, there is no remaining format parts + if (formatParts[0].equals("NAME") && !typedParts[0].matches("[a-zA-Z]+")) { + return ""; + } + + // If user did not type number or alphabet for INDEX/NAME, there is no remaining format parts + if (formatParts[0].equals("INDEX/NAME") && !typedParts[0].matches("^[a-zA-Z0-9_.-]*$")) { + return ""; + } + + + for (String formatPart : formatParts) { + boolean matched = false; + if (formatPart.equals("INDEX") || formatPart.equals("NAME") || formatPart.equals("INDEX/NAME")) { + // Skip the INDEX or NAME part if a number has already been entered + continue; + } + + for (String typedPart : typedParts) { + if (typedPart.startsWith(getPrefix(formatPart.trim().replace("[", "").replace("]", "")))) { + matched = true; + break; + } + } + if (!matched) { + // Add a single space before each remaining part + remainingFormat.append(" ").append(formatPart.trim()); + } + } + + return remainingFormat.toString(); + } + return ""; + } + + /** + * Returns the prefix for a given format part. If no prefix matches, + * the original part is returned. + * + * @param part The format part to check for prefixes. + * @return The matched prefix or the original part if no prefix matches. + */ + private String getPrefix(String part) { + for (String prefix : formatPrefixes) { + if (part.startsWith(prefix)) { + return prefix; + } + } + return part; + } + + /** + * Removes the first word from the given input string. + * + * @param input The input string from which the first word will be removed. + * @return The input string without the first word, or an empty string + * if the input has one word or is empty. + */ + public static String removeFirstWord(String input) { + String[] words = input.trim().split("\\s+", 2); // Split into two parts + if (words.length > 1) { + return words[1]; // Return the string without the first word + } else { + return ""; // If there's only one word or the input is empty, return an empty string + } + } +} diff --git a/src/main/java/seedu/address/ui/suggestion/Suggestions.java b/src/main/java/seedu/address/ui/suggestion/Suggestions.java new file mode 100644 index 00000000000..1ec03e08935 --- /dev/null +++ b/src/main/java/seedu/address/ui/suggestion/Suggestions.java @@ -0,0 +1,33 @@ +package seedu.address.ui.suggestion; + +/** + * Provides functionality for generating command suggestions based on user input. + * This class checks the entered text against predefined commands and + * generates suggestions for completing the command format. + */ +public class Suggestions { + + /** + * Checks all available commands and generates an appropriate suggestion + * based on the user's input. + * + * @param enteredText The text entered by the user in the command input field. + * @return A string containing the entered text followed by the suggestion, + * or just the entered text if no matching command is found. + */ + public String checkAllCommands(String enteredText) { + // Iterate through all defined commands to find matches + for (Commands command : Commands.values()) { + String firstWord = enteredText.split("\\s+")[0]; + if (firstWord.equals(command.getCommand())) { + // Generate suggestion based on the matching command + FormatSuggestion commandSuggestion = new FormatSuggestion(command.getCommand(), + command.getExample(), command.getPrefix()); + String suggestionText = commandSuggestion.getRemainingFormat(enteredText); + return enteredText + suggestionText; + } + } + // If no command matches, return the entered text as is + return enteredText; + } +} diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 124283a392e..8be40532979 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -4,6 +4,12 @@ - + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..af434d79c37 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,15 +1,27 @@ +.suggestion-text-field { + -fx-background-color: transparent; + -fx-text-fill: #808080; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; +} + +#suggestionTextField { + -fx-background-color: transparent; + -fx-text-fill: #808080; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; +} + +#commandTextField, #suggestionTextField { + -fx-font-size: 13pt; + -fx-font-family: "Segoe UI Light"; +} + .background { -fx-background-color: derive(#1d1d1d, 20%); background-color: #383838; /* Used in the default.html file */ } -.label { - -fx-font-size: 11pt; - -fx-font-family: "Segoe UI Semibold"; - -fx-text-fill: #555555; - -fx-opacity: 0.9; -} - .label-bright { -fx-font-size: 11pt; -fx-font-family: "Segoe UI Semibold"; @@ -100,11 +112,11 @@ } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: #c4c4c4; } .list-cell:filled:odd { - -fx-background-color: #515658; + -fx-background-color: white; } .list-cell:filled:selected { @@ -117,7 +129,7 @@ } .list-cell .label { - -fx-text-fill: white; + -fx-text-fill: black; } .cell_big_label { @@ -151,6 +163,33 @@ -fx-font-family: "Segoe UI Light"; -fx-font-size: 13pt; -fx-text-fill: white; + -fx-wrap-text: true; + -fx-pref-width: 800px; + -fx-min-height: 60px; +} + +.result-display .content { + -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-radius: 0; + -fx-padding: 10px; +} + +.result-display .viewport { + -fx-background-color: transparent; +} + +.result-display:focused { + -fx-background-color: transparent; +} + +.text-area { + -fx-background-color: transparent; +} + +.text-area .content { + -fx-padding: 10px; + -fx-wrap-text: true; + -fx-alignment: top-left; } .result-display .label { @@ -337,16 +376,27 @@ -fx-background-radius: 0; } -#tags { +#role { -fx-hgap: 7; -fx-vgap: 3; } -#tags .label { +#role .label { -fx-text-fill: white; - -fx-background-color: #3e7b91; + -fx-background-color: #bd942d; -fx-padding: 1 3 1 3; -fx-border-radius: 2; -fx-background-radius: 2; -fx-font-size: 11; } + + +#client, #ownWedding .label { + -fx-background-color: gold; + -fx-text-fill: black; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 15; + -fx-font-weight: bold; +} diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 7778f666a0a..57e777c751e 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -11,8 +11,9 @@ + + title="Bridal Boss" minWidth="450" minHeight="600" onCloseRequest="#handleExit"> @@ -46,12 +47,23 @@ - - - - - - + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index 84e09833a87..13090cfc2ba 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -26,8 +26,12 @@