diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000000..1e1862b0e82 --- /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: '11' + distribution: 'temurin' + - name: Build & Deploy MarkBind site + uses: MarkBind/markbind-action@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + rootDirectory: './docs' + baseUrl: '/tp' # replace with your repo name + version: '^5.1.0' 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 13f5c77403f..cd799659f56 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ -[![CI Status](https://github.com/se-edu/addressbook-level3/workflows/Java%20CI/badge.svg)](https://github.com/se-edu/addressbook-level3/actions) +[![CI Status](https://github.com/AY2324S1-CS2103T-T10-3/tp/workflows/Java%20CI/badge.svg)](https://github.com/AY2324S1-CS2103T-T10-3/tp/actions) ![Ui](docs/images/Ui.png) -* This is **a sample project for Software Engineering (SE) students**.
+* This is ProjectPRO for university students with a lot of projects.
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. + * Group contact lists + * Track group mates availability +* The project simulates an ongoing school software project for a desktop application (called _ProjectPRO_) used for managing group mates * 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#https://se-education.org/#contributing) for more info. diff --git a/build.gradle b/build.gradle index a2951cc709e..b756e6dc502 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,10 @@ mainClassName = 'seedu.address.Main' sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 +run { + enableAssertions = true +} + repositories { mavenCentral() maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } @@ -66,7 +70,7 @@ dependencies { } shadowJar { - archiveFileName = 'addressbook.jar' + archiveFileName = 'ProjectPRO.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 1c9514e966a..a26821fc779 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,59 +1,62 @@ --- -layout: page -title: About Us + layout: default.md + title: "About Us" --- +# 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 +### Kailashwaran - + -[[homepage](http://www.comp.nus.edu.sg/~damithch)] -[[github](https://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/Kailash201)] +[[portfolio](team/kailash201.md)] -* Role: Project Advisor +* Role: Developer +* Role: Team Lead -### Jane Doe +### Huang Yixin - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/coderhuang559)] +[[portfolio](team/coderhuang559.md)] -* Role: Team Lead +* Role: Developer * Responsibilities: UI -### Johnny Doe +### Nicholas Tng - + -[[github](http://github.com/johndoe)] [[portfolio](team/johndoe.md)] +[[github](http://github.com/nicholastng010601)] +[[portfolio](team/nicholastng010601.md)] * Role: Developer -* Responsibilities: Data +* Responsibilities: Code quality -### Jean Doe +### Goh Ler Xuan - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](http://github.com/lerxuann)] +[[portfolio](team/lerxuann.md)] * Role: Developer -* Responsibilities: Dev Ops + Threading +* Responsibilities: Documentation -### James Doe +### Ong Zhen Dong - + -[[github](http://github.com/johndoe)] -[[portfolio](team/johndoe.md)] +[[github](https://github.com/zd292)] +[[portfolio](team/zd292.md)] * Role: Developer -* Responsibilities: UI +* Responsibilities: Team lead diff --git a/docs/Configuration.md b/docs/Configuration.md index 13cf0faea16..32f6255f3b9 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1,6 +1,8 @@ --- -layout: page -title: Configuration guide + layout: default.md + title: "Configuration guide" --- +# Configuration guide + Certain properties of the application can be controlled (e.g user preferences file location, logging level) through the configuration file (default: `config.json`). diff --git a/docs/DevOps.md b/docs/DevOps.md index d2fd91a6001..8228c845e86 100644 --- a/docs/DevOps.md +++ b/docs/DevOps.md @@ -1,12 +1,15 @@ --- -layout: page -title: DevOps guide + layout: default.md + title: "DevOps guide" + pageNav: 3 --- -* Table of Contents -{:toc} +# DevOps guide --------------------------------------------------------------------------------------------------------------------- + + + + ## Build automation diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 8a861859bfd..1eec94304d6 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,59 +1,75 @@ --- -layout: page -title: Developer Guide +layout: default.md +title: "Developer Guide" +pageNav: 3 --- -* Table of Contents -{:toc} + +# ProjectPRO Developer Guide + + + -------------------------------------------------------------------------------------------------------------------- -## **Acknowledgements** +## 1. Introduction + +### **1.1. Product Overview** +ProjectPro is a contacts organisation application designed for university students. + +University students often spend a lot of time coordinating project meetup sessions and finding project information, resulting in large amounts of time wasted. -* {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +This app can help to save time by: +* listing available time slots of individuals in a team. +* Finding common time slots for meetings. +* Saving extra information regarding a project. -------------------------------------------------------------------------------------------------------------------- -## **Setting up, getting started** +### 1.2 Setting up getting started Refer to the guide [_Setting up and getting started_](SettingUp.md). -------------------------------------------------------------------------------------------------------------------- -## **Design** +### 1.3 Acknowledgements -
+ProjectPro is a brownfield Java Project based on the AB3 project template created by the [SE-EDU initiative](https://se-education.org). -: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 +-------------------------------------------------------------------------------------------------------------------- + +## 2 Design - +### 2.1 Architecture + + The ***Architecture Diagram*** given above explains the high-level design of the App. Given below is a quick overview of main components and how they interact with each other. -**Main components of the architecture** +**2.1.1. Main components of the architecture** -**`Main`** (consisting of classes [`Main`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/MainApp.java)) is in charge of the app launch and shut down. +**`Main`** has two main classes called [`Main`](https://github.com/AY2324S1-CS2103T-T10-3/tp/blob/master/src/main/java/seedu/address/Main.java) and [`MainApp`](https://github.com/AY2324S1-CS2103T-T10-3/tp/blob/master/src/main/java/seedu/address/MainApp.java). It is responsible for app launch and shut down. * At app launch, it initializes the other components in the correct sequence, and connects them up with each other. * At shut down, it shuts down the other components and invokes cleanup methods where necessary. -The bulk of the app's work is done by the following four components: + + +The rest of the App consists of these main four components: * [**`UI`**](#ui-component): The UI of the App. * [**`Logic`**](#logic-component): The command executor. * [**`Model`**](#model-component): Holds the data of the App in memory. * [**`Storage`**](#storage-component): Reads data from, and writes data to, the hard disk. -[**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. +On the other hand, [**`Commons`**](#common-classes) represents a collection of classes used by multiple other components. -**How the architecture components interact with each other** +**2.1.2. How the architecture components interact with each other** -The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete 1`. +The *Sequence Diagram* below shows how the components interact with each other for the scenario where the user issues the command `delete n/Alex`. - + Each of the four main components (also shown in the diagram above), @@ -62,186 +78,714 @@ 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 +### 2.2 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/AY2324S1-CS2103T-T10-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` 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) +`GroupTimeContainer` is a helper class that process the data from the `model` in `Calendar` and passes the processed data to `DayCard` which will be then displayed in the `UI`. + +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/AY2324S1-CS2103T-T10-3/tp/blob/master/src/main/java/seedu/address/ui/MainWindow.java) is specified in [`MainWindow.fxml`](https://github.com/AY2324S1-CS2103T-T10-3/tp/blob/master/src/main/resources/view/MainWindow.fxml). -The `UI` component, +The `UI` component * executes user commands using the `Logic` component. * listens for changes to `Model` data so that the UI can be updated with the modified data. * keeps a reference to the `Logic` component, because the `UI` relies on the `Logic` to execute commands. * depends on some classes in the `Model` component, as it displays `Person` object residing in the `Model`. -### Logic component +### 2.3 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/AY2324S1-CS2103T-T10-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. +The sequence diagram below illustrates the interactions within the `Logic` component, taking `execute("delete n/Alex")` 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 reaches 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 reaches the end of diagram. + How the `Logic` component works: -1. When `Logic` is called upon to execute a command, it is passed to an `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. -1. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`) which is executed by the `LogicManager`. -1. The command can communicate with the `Model` when it is executed (e.g. to delete a person). -1. The result of the command execution is encapsulated as a `CommandResult` object which is returned back from `Logic`. +1. When `Logic` is called upon to execute a command, it calls the `AddressBookParser` object which in turn creates a parser that matches the command (e.g., `DeleteCommandParser`) and uses it to parse the command. +2. This results in a `Command` object (more precisely, an object of one of its subclasses e.g., `DeleteCommand`), being executed by the `LogicManager`. +3. The command calls upon `Model` to execute the action (e.g. to delete a person). +4. The result of the command execution is encapsulated as a `CommandResult` object which is returned back to `Logic`. 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) +### 2.4 Model component +**API** : [`Model.java`](https://github.com/AY2324S1-CS2103T-T10-3/tp/blob/master/src/main/java/seedu/address/model/Model.java) - + -The `Model` component, +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 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.
- - -
+### 2.5 Storage component +**API** : [`Storage.java`](https://github.com/AY2324S1-CS2103T-T10-3/tp/blob/master/src/main/java/seedu/address/storage/Storage.java) -### Storage component + -**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java) - - - -The `Storage` component, +The `Storage` component * can save both address book data and user preference data in JSON format, and read them back into corresponding objects. * inherits from both `AddressBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed). -* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`) +* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`). + +With the inclusion of `Group` and `TimeIntervals` in our application, more components had to be saved by our storage. -### Common classes +To address this requirement, we updated our storage to save both classes in JSON format. + +### 2.6 Common classes Classes used by multiple components are in the `seedu.addressbook.commons` package. -------------------------------------------------------------------------------------------------------------------- -## **Implementation** +## 3 Implementation This section describes some noteworthy details on how certain features are implemented. -### \[Proposed\] Undo/redo feature +### 3.1 Adding a Person + +#### Implementation + +The `add` feature is facilitated by a number of classes such as `Person` and `Model` + +Step 1. The user launches the application for the first time. + +Step 2. The user executes `“add n/John Doe p/98765432 e/johnd@example.com g/CS2103T”` command to add a new person. + +Step 3, The `AddCommandParser` is called to read the user input. `AddCommandParser` parses the input and calls `AddCommand`. + +Step.4 `AddCommand` then calls `Model#addPerson()` which then calls `AddressBook#addPerson()`. The latter method will add person inside the `uniquePersonList` in `addressBook`. `AddCommand` also calls `Model#addGroup` which then calls `AddressBook#addGroup` to add the group inside `grouplist` if the group does not exist. Lastly, `AddCommand` adds the person inside the group + +**Note** No duplication is allowed in addressbook for name, email and phone number field. + +The following sequence diagram describes the process of `add` command: + + + + +**Note:** The lifeline for `AddCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + + + +#### Design considerations: + +**Aspect: Handling group attribute in user input** + +* **Alternative 1 (Current Choice):** Only allow user to add one group for each `add` Command + * Pros: User input length is reduced, lowering the chance of invalid input on the user's side. + * Cons: User will have to type more inputs to add more groups. + +* **Alternative 2:** Allow user to add as many groups as required for each `add` Command + * Pros: Conveniently adds a person into multiple group while creating a new contact at the same time. + * Cons: User input can get potentially very long, increasing the chance of invalid input, relatively harder to implement parser. The implementation of it will get more complex. + +-------------------------------------------------------------------------------------------------------------------- + +### 3.2 Adding a Group #### Proposed Implementation -The proposed undo/redo mechanism is facilitated by `VersionedAddressBook`. It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. Additionally, it implements the following operations: +The Add Group mechanism is facilitated by `Group` class. This operation is exposed in the `Model` interface as `Model#addGroup()`. + +Given below is an example usage scenario and how the group creation mechanism behaves at each step. + +**Step 1:** User launches the application. + +**Step 2:** The user executes `new g/GROUPNAME` to create a new group with the name GROUPNAME. `CreateGroupCommandParser` parses the GROUPNAME, ensuring the input is valid, and creates a `CreateGroupCommand`, which calls `Model#addGroup()`. The model retrieves the existing groupList from the addressBook and adds this new group to the groupList. + +**Note:** ProjectPRO does not allow 2 groups of the same name to be added. The group name must also be alphanumerical and non empty. + +The following activity diagram summarizes what happens when a user executes a new command: + + +Below is a sequence diagram that summarizes how a user creates a new group: + + + + +**Note:** The lifeline for `CreateGroupCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + + + +#### Design Considerations: + +**Aspect: Groups with the same name** + +* **Alternative 1 (current choice):** Group names are Unique + * Pros: Allow users to create groups with the same name + * Cons: User might have to be creative with naming groups + +* **Alternative 2:** Group names are not unique but tagged with an id + * Pros: Users can reuse commonly used group names + * Cons: Users may get confused as to what each group is meant for + +-------------------------------------------------------------------------------------------------------------------- + +### 3.3 Delete Person and Group + +#### Implementation + +The Delete person/group mechanisms are facilitated by `Model` class, which accesses the `AddressBook` class. It implements the following operations: + +* `AddressBook#removePerson(Person p)` — Removes Person p from the address book. +* `AddressBook#removeGroup(Group g)` — Removes Group g from the address book. + +These operations are exposed in the `Model` interface as `Model#deletePerson()`, `Model#deleteGroup()` respectively. + +Both `DeletePersonCommand` and `DeleteGroupCommand` implement an abstract class `DeleteCommand`, which helps to encapsulate the similarities between these two commands. + +Since both Delete Person and Delete Group commands utilise the same command word, the `DeleteCommandParser` will create either a `DeletePersonCommand` or `DeleteGroupCommand` and return it as a `DeleteCommand` after parsing the user input. + +The following activity diagram summarizes what happens when a user executes a delete command: + + + +### Delete Person + +Given below is an example usage scenario and how the Delete Person mechanism behaves at each step. + +Step 1. The user executes `delete n/Alex Yeoh` command to delete a person named 'Alex Yeoh' in the contact list. After parsing, a new `DeletePersonCommand` object will be returned. + +Step 2. `DeletePersonCommand` is executed, in which `Model#deletePerson("Alex Yeoh")` is called. + + + +**Note:** If no such person named 'Alex Yeoh' exists, a `CommandException` will be thrown. + + + +Step 3. `Model#deletePerson()` will also call `AddressBook#removePerson(Alex Yeoh)` which will remove the target contact from the contact list while removing it from all the groups it was part of by calling `Group#removePerson(Alex Yeoh)`. + +The following sequence diagram shows how the Delete Person operation works: + + + + + +**Note:** The lifeline for `DeleteCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + + + +### Delete Group + +The Delete Group command mechanism behaves the same as the Delete Person command above, except it deletes the target `Group` object instead of the `Person` object. + +Additionally, `AddressBook#removeGroup(Group g)` will remove the target group 'g' from the group lists of all the members that were a part of it by calling `Person#removeGroup(Group g)`. + +#### Design Considerations: + +**Aspect: How to handle two similar but different commands (delete group and delete person)** + +* **Alternative 1 (current choice):** Use the same command word + * Pros: Reduces the amount of command words that users have to remember + * Cons: Users may get confused because one command word can be used for different things + +* **Alternative 2:** Use different command words for both commands + * Pros: Less confusing as it is intuitive that different command words are used for different commands + * Cons: Users have to remember more command words which may take more time to get used to + +-------------------------------------------------------------------------------------------------------------------- +### 3.4 List Person and list group + +### List-Person + +The List mechanism is facilitated by the `Model` class. + +The operation it utilises is exposed in the `Model` interface as `Model#updateFilteredPersonList(Predicate predicate)`. + +Given below is an example usage scenario and how the list mechanism behaves at each step. + +**Step 1:** User executes `list` command to view all contacts. After parsing, a new `ListCommand` object will be returned. + +**Step 2:** `ListCommand` is executed, in which `Model#updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS)` is called with the Predicate which will be true for all contacts. This updates the address book view, showing all the contacts in the address book to the user. + +The following activity diagram summarizes what happens when a user executes a list command: + + + + + +**Note:** The lifeline for `ListCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + + + + +#### List Group + +The List Group mechanism is facilitated by the `Model` class. + +The operation it utilises is exposed in the `Model` interface as `Model#getFilteredGroupList()`. + +Given below is an example usage scenario and how the listgroup mechanism behaves at each step. + +**Step 1:** User executes `listgroup` command to view all groups in their contact list. After parsing, a new `ListGroupCommand` object will be returned. + +**Step 2:** `ListGroupCommand` is executed, in which `Model#getFilteredGroupList()` is called, returning all the groups the user has in the contact list. + +**Step 3:** The group names of all the groups in the contact list are appended to a `String` which will be displayed to the user as a message in the output box. + +The following activity diagram summarizes what happens when a user executes a listgroup command: + + + + + +**Note:** The lifeline for `ListGroupCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + + + +-------------------------------------------------------------------------------------------------------------------- + +### 3.5 Find Person and Find Group features + +#### Implementation + +The Find person/group mechanisms are facilitated by the `Model` class. + +The operation they utilise is exposed in the `Model` interface as `Model#updateFilteredPersonList(Predicate predicate)`. + +Both `FindPersonCommand` and `FindGroupCommand` implement an abstract class `FindCommand`, which helps to encapsulate the similarities between these two commands. + +Since both Find Person and Find Group commands utilise the same command word, the `FindCommandParser` will create either a `FindPersonCommand` or `FindGroupCommand` and return it as a `FindCommand` after parsing the user input. + +The following activity diagram summarizes what happens when a user executes a find command: + + + +### Find Person + +Given below is an example usage scenario and how the Find Person mechanism behaves at each step. + +Step 1. The user executes `find n/Alex John` command to find all contacts whose names contain either 'Alex' or 'John' in the address book. After parsing, a new `FindPersonCommand` object will be returned along with the corresponding `Predicate`. -* `VersionedAddressBook#commit()` — Saves the current address book state in its history. -* `VersionedAddressBook#undo()` — Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` — Restores a previously undone address book state from its history. +Step 2. `FindPersonCommand` is executed, in which `Model#updateFilteredPersonList()` is called with the Predicate. This through all the contacts in the address book, showing the updated list of contacts whose names start with 'Alex' or 'John' to the user. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. + -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +**Note:** If there are no contacts whose name contains 'Alex, or 'John', no contacts will be shown. -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. +The following sequence diagram shows how the Find Person operation works: -![UndoRedoState1](images/UndoRedoState1.png) + -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. + -![UndoRedoState2](images/UndoRedoState2.png) +**Note:** The lifeline for `FindCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. + -
+### Find Group -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. +The Find Group mechanism works like the Find Person mechanism, expect it filters contacts by a different `Predicate` which will filter for contacts who are a part of the target group. -![UndoRedoState3](images/UndoRedoState3.png) +Additionally, it calls on an extra method `Group#getGroupRemark()`. `Group#getGroupRemarks()` will be called to display the previously saved group remarks to the user. -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial AddressBook state, then there are no previous AddressBook states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather -than attempting to perform the undo. +#### Design Considerations: -
+**Aspect: How to handle two similar but different commands (find by group and find by name)** -The following sequence diagram shows how the undo operation works: +* **Alternative 1 (current choice):** Use the same command word + * Pros: Reduces the amount of command words that users have to remember + * Cons: Users may get confused because one command word can be used for different things -![UndoSequenceDiagram](images/UndoSequenceDiagram.png) +* **Alternative 2:** Use different command words for both commands + * Pros: Less confusing as it is intuitive that different command words are used for different commands + * Cons: Users have to remember more command words which may take more time to get used to -
: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. +**Aspect: Case-sensitivity for find by name** -
+* **Alternative 1 (current choice):** Not case-sensitive + * Pros: Fulfils its functionality better as users can search freely for their contacts even if they do not remember the exact name their contacts were saved under + * Cons: Users may get confused because aside from find by name, other user inputs are all case-sensitive -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. +* **Alternative 2:** Case sensitive + * Pros: Less confusing as it is more intuitive that all user inputs are case-sensitive, and users will be able to find specific contacts easily if they remember how exactly they were saved by + * Cons: Might hinder some more forgetful users from finding their contacts in mind if they do not remember how they capitalised their contacts when adding them -
: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. +-------------------------------------------------------------------------------------------------------------------- + +### 3.6 Group and Ungrouping Person + +#### Implementation + +The group mechanism is facilitated by `Group`. It is stored internally as a `Group`. This operation is exposed in the `Model` interface as `Model#groupPerson(personName, groupName)`. -
+Given below is an example usage scenario and how the group mechanism behaves at each step. -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. +**Step 1:** User launches the application. -![UndoRedoState4](images/UndoRedoState4.png) +**Step 2:** The user executes `group n/personName g/groupName` to group a person `personName` into group `groupName`. ` -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. +**Step 3.** GroupPersonCommandParser` parses the personName and groupName ensuring the input is valid and creates a `GroupPersonCommand` -![UndoRedoState5](images/UndoRedoState5.png) +**Step 4.** GroupPersonCommand calls `Model#groupPerson(personName, groupName)`. The model retrieves the existing person and group from the addressBook. + +**Step 5.** Model calls `Model#assignGroup(Person person, Group group)` which adds a group to a person's groupList and person to the personList in group. + +**Note:** Should a person or group not exist, an error is thrown, displaying the missing entity to the User. + +Ungroup works in the same way as group except the use of Command word ungroup The following activity diagram summarizes what happens when a user executes a new command: + - +#### Design Considerations: -#### Design considerations: +**Aspect: Whether to store references in both person and group** + +* **Alternative 1 (current choice):** Store references in both person and group + * Pros: Easy to implement. More efficient when searching. + * Cons: More bug-prone. May have object referencing and loading from storage issues as both the person reference to group, and group reference to person has to be loaded properly. + +* **Alternative 2:** Store reference to the other entity, e.g. store a list of groups in person. + * Pros: Easier to load from storage. One centralized place to store data. Less coupling. + * Cons: Searching might become more costly. + +-------------------------------------------------------------------------------------------------------------------- + +### 3.7 Adding a Group Remark + +#### Implementation + +The Group Remark mechanism is facilitated by the `Group Remark` class, involving other classes like `Group`. It implements the following operation: + +* `Group#setGroupRemark()` — Sets the group's remark. + +This operation is exposed in the `Model` interface as `Model#addGroupRemark()`. + +Here's an example usage scenario and how the group remark mechanism behaves at each step: + +**Step 1.** The user creates a group called "CS2103T". The group is initialized with an empty `groupRemark`. + +**Step 2.** The user executes the `remark g/CS2103T r/Quiz tomorrow` command to add the remark "Quiz tomorrow" to the "CS2103T" group. The `GroupRemarkCommandParser` extracts the group and remark from the input and creates a `GroupRemarkCommand`, which calls `Model#addGroupRemark(groupName, groupRemark)`. The model retrieves the existing "CS2103T" group from the database and calls the group's `Group#setGroupRemark(groupRemark)`, setting the "Quiz tomorrow" as the `groupRemark` of the group. + +**Note:** If the user wants to modify the group remark, they can execute the same command with the new remark. The existing remark will be overwritten, and the new remark is stored in the group. + +The following activity diagram summarizes what happens when a user executes a new command: + + + + + +**Note:** The lifeline for `GroupRemarkCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + + + +#### Design Considerations + +**Aspects: How to change the group remark** + +- **Alternative 1 (current choice):** Override the original remark + - Pros: Easy to implement. + - Cons: May be troublesome if the user wants to keep contents from the original remark. -**Aspect: How undo & redo executes:** +- **Alternative 2:** Edit the original remark + - Pros: Easy to add more information. + - Cons: May be confusing to edit if there are many changes or remark is too long. -* **Alternative 1 (current choice):** Saves the entire address book. - * Pros: Easy to implement. - * Cons: May have performance issues in terms of memory usage. +-------------------------------------------------------------------------------------------------------------------- + +### 3.8 Adding Time to a Person or Group + +#### Implementation + +The Add Time to person/group mechanisms are facilitated by the `TimeInterval` class, involving other classes like `Person` and `Group`. It implements the following operations: + +* `Person#addFreeTime()` — Adds the inputted time intervals to the person. +* `Group#addTime()` — Adds the inputted time intervals to the group. + +These operations are exposed in the `Model` interface as `Model#addTimeToPerson()`, `Model#addTimeToGroup()` respectively. + +The following activity diagram summarizes what happens when a user executes an add time command: + + + +### Adding Time to a Person + +Given below is an example usage scenario and how the Add Time to Person mechanism behaves at each step. + +Step 1. The user executes `addtime n/Alex Yeoh t/mon 1300 - mon 1400 t/tue 1300 - tue 1400` command to add the free time intervals of Monday 1 pm to 2 pm and Tuesday 1 pm to 2 pm to a person named "Alex Yeoh" in the contact list. The `AddTimeCommandParser` will be called to parse the inputs and check if any of the inputted times clash with each other. It will then call the `AddTimeCommand`. + +**Note:** Since multiple inputs are allowed, an array list of time intervals is passed around, each of which is to be added. + +Step 2. `AddTimeCommand` is executed, in which `Model#addTimeToPerson()` is called. + + + +**Note:** If no such person named "Alex Yeoh" exists, a `CommandException` will be thrown. -* **Alternative 2:** Individual command knows how to undo/redo by - itself. - * Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). - * Cons: We must ensure that the implementation of each individual command are correct. + -_{more aspects and alternatives to be added}_ +Step 3. `Model#addTimeToPerson()` will also call `Person#addFreeTime()` which will add all times stored in the array list to the person's list of free times, given that none of the times clash with the person's existing free time intervals. If clashes do occur, the user will be notified of the problematic time intervals while the problem-free intervals will be added. -### \[Proposed\] Data archiving +The following sequence diagram shows how the Add Time to Person operation works: -_{Explain here how the data archiving feature will be implemented}_ + + + +**Note:** The lifeline for `AddTimeCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + + + +### Adding Time to a Group + +The Add Time to Group command mechanism behaves the same as the Add Time to Person command above, except it uses the `Group` class instead of the `Person` class. + +#### Design Considerations + +**Aspect: How to handle two similar but different commands (add meeting time to group and add free time to person)** + +* **Alternative 1 (current choice):** Use different command words for both commands + * Pros: Less confusing as the 2 commands add different types of times. + * Cons: Users have to remember more command words which may take more time to get used to. + +* **Alternative 2:** Use the same command word + * Pros: Reduces the amount of command words that users have to remember. + * Cons: Users may get confused because the 2 commands do not add the same types of times. + +**Aspect: How to handle time clashes** + +* **Alternative 1 (current choice):** Add all non-clashing time intervals + * Pros: More convenient as users will not need to retype the entire command if there is a clash. + * Cons: May no longer want to add certain non-clashing inputs after encountering this clash. + +* **Alternative 2:** Reject all time intervals + * Pros: Allows users to (heavily) edit their inputted time intervals accordingly to resolve clashes. + * Cons: May be troublesome to retype the entire command, especially if it is very long. -------------------------------------------------------------------------------------------------------------------- -## **Documentation, logging, testing, configuration, dev-ops** +### 3.9 Delete Time Feature + +#### Implementation + +The Delete Time from person/group mechanisms are facilitated by the `TimeInterval` class, involving other classes like `Person` and `Group`. It implements the following operations: + +* `Person#deleteFreeTime()` — Deletes the inputted time intervals from the person. +* `Group#deleteTime()` — Deletes the inputted time intervals from the group. + +These operations are exposed in the `Model` interface as `Model#deleteTimeFromPerson()`, `Model#deleteTimeFromGroup()` respectively. + +Below is an activity diagram that illustrates the control flow for Delete Person Time feature. + + + +### Deleting Time from a Person + +The proposed delete time feature is facilitated by the `timeIntervalList` and `Person` class. It accesses the `timeIntervalList` from the `Person` class and deletes a time interval with `Person#deleteFreeTime()`. The operation is exposed in the `Model` interface as `Model#deleteTimeFromPerson`. + +Step 1. The user launches the application. The `AddressBook` will be initialized with the free time of its contacts. + +Step 2. The user executes the command `deleteTime n/Alex Yeoh t/mon 1200 - mon 1400 t/tue 1000 - wed 1600`. The `deleteTimeCommandParser` will be called to parse the inputs and call the `deletePersonTimeCommand`. The `deletePersonTime` command calls `Model#deleteTimeFromPerson()`, which will call `Person#deleteFreeTime()`. + +**Note:** Since multiple inputs are allowed, an array list of time intervals are passed around, each of which is to be deleted. + +Step 3. The function will be called in the person's `timeInterval` list. The application will loop through each time interval that is to be deleted and in the person's `timeInterval` list. Each time interval will be compared to see whether the `timeIntervalList` contains the time interval to be deleted. Afterwards, the new `timeInterval` list will be saved. + +**Note:** If a time interval is not in the person's list, that interval will be collated and printed to specifically notify the user which time intervals are not in the list. The other time intervals that are in the list will still be deleted. + +The following sequence diagram summarizes what happens when a user executes a new command: + + + + + +**Note:** The lifeline for `DeleteTimeCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + + + +### Deleting Time from a Group + +The Delete Time from Group command mechanism behaves the same as the Delete Time from Person command above, except it uses the `Group` class instead of the `Person` class. + +#### Design Considerations + +**Aspect: Error Messages** + +* Alternative 1 (current choice): Print specific error messages. + * Pros: Allow users to understand which inputs went wrong. + * Cons: May have performance issues in terms of runtime as more functions will be used to craft the error message. Currently, we utilized a `StringBuilder` to craft the message and did extra checks to see whether there had been any errors appended to the error message. + +* Alternative 2: Generalized error message. + * Pros: Will be faster to implement. + * Cons: User might be unsure why the function went wrong. + +**Aspect: How to Handle Multiple Time Inputs** + +* Alternative 1 (current choice): Parse each time input one by one and execute the commands. + * Pros: More user-friendly and efficient as users can delete more time intervals at once. + * Cons: More expensive as more functions will be called to parse the inputs. + +* Alternative 2: Allow only single input. + * Pros: Faster as fewer functions are called. + * Cons: Not as user-friendly since users will have to delete time intervals one by one. + +**Aspect: How to Handle Errors in Time Intervals** + +* Alternative 1 (current choice): Delete the time intervals that are correct and return the intervals that are wrong. + Pros: Better user experience as users need not rewrite intervals that were right. + Cons: Increased memory usage to store the errors. + +* Alternative 2: Do not carry out the delete at all. + Pros: More time and memory efficient. + Cons: Not as user-friendly since users will have to re-input the time intervals that were originally correct. + +-------------------------------------------------------------------------------------------------------------------- + +### 3.10 Listing Time for a Person or Group + +#### Implementation + +The List Time for person/group mechanisms are facilitated by the `TimeInterval` class, involving other classes like `Person` and `Group`. It implements the following operations: + +* `Person#getTime()` — Shows the person's time intervals. +* `Group#getTime()` — Shows the group's time intervals. + +These operations are exposed in the `Model` interface as `Model#getTimeFromPerson()`, `Model#getTimeFromGroup()` respectively. + +Both `ListTimePersonCommand` and `ListTimeGroupCommand` implement an abstract class `ListTimeCommand`, which helps to encapsulate the similarities between these two commands. + +Since both List Time Person and List Time Group commands utilise the same command word `listtime`, the `ListTimeCommandParser` will create either a `ListTimePersonCommand` or `ListTimeGroupCommand` and return it as a `ListTimeCommand` after parsing the user input. + +The following activity diagram summarizes what happens when a user executes an add time command: + + + +### Listing Time from a Person + +Given below is an example usage scenario and how the List Time from Person mechanism behaves at each step. + +Step 1. The user executes `listtime n/Alex Yeoh` command to list the free time intervals of a person named "Alex Yeoh" in the contact list. The `ListTimeCommandParser` will be called to parse the inputs. It will then call the `ListTimePersonCommand`. + +Step 2. `ListTimePersonCommand` is executed, in which `Model#getTimeFromPerson()` is called. + + + +**Note:** If no such person named "Alex Yeoh" exists, a `CommandException` will be thrown. + + + +Step 3. `Model#getTimeFromPerson()` will also call `Person#getTime()` which will parse all times stored in the array list into an easy-to-read chunk using a StringBuilder, and show it in the output box. + +The following sequence diagram shows how the List Time from Person operation works: + + + + + +**Note:** The lifeline for `ListTimeCommandParser` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. + + + +### Listing Time from a Group + +The List Time from Group command mechanism behaves the same as the List Time from Person command above, except it uses the `Group` class instead of the `Person` class. + +#### Design Considerations + +**Aspect: How to handle two similar but different commands (add meeting time to group and add free time to person)** + +* **Alternative 1 (current choice):** Use different command words for both commands + * Pros: Less confusing as the 2 commands add different types of times. + * Cons: Users have to remember more command words which may take more time to get used to. + +* **Alternative 2:** Use the same command word + * Pros: Reduces the amount of command words that users have to remember. + * Cons: Users may get confused because the 2 commands do not add the same types of times. + +**Aspect: Display format** + +* **Alternative 1 (current choice):** Print raw list of times + * Pros: Allows users to copy and paste the intervals shown as they are in the correct format. + * Cons: May be too convoluted due to repeated information caused by reiterating the day of the time interval. + +* **Alternative 2:** Organise time intervals by day. + * Pros: Allows users to better see the intervals in each day. + * Cons: May cause inconvenience to users when they need to copy and paste time chunks should they need it again. + +-------------------------------------------------------------------------------------------------------------------- + +### 3.11 Find Free Time +#### Implementation + +The FindFreeTime mechanism is facilitated by the `Model`, `Group` and `Person` class. It retrieves `Group` from `Model` to find a free time between group members in `listOfGroupMates` in `Group` with a duration specified, `Duration`. The operation is exposed to `Model` interface as `Model#findGroup`. + + +Given below is an example usage scenario and how the list mechanism behaves at each step. + +**Step 1:** User launches the application. +**Step 2:** User executes `findfreetime g/CS2103 d/60` command to find a common meeting time with duration 60 minutes for group CS2103. +**Step 3:** FindFreeTimeCommandParser parses the group name CS2103 and duration 60, ensuring that duration is a valid integer in terms of minutes, and returns a FindFreeTimeCommand. +**Step 4:** FindFreeTimeCommand calls `Model#findGroup(groupName)` to retrieve the group with matching name. If group does not exist, then an error is thrown. +**Step 4:** FindFreeTimeCommand calls `Group#findFreeTime(duration)`, to retrieve the all common timeslots between `listOfGroupMates` in `Group` and return them in a list should they accommodate the duration stated. +**Note:** +If group is empty, having no group mates in `listOfGroupMates` an error is thrown. If any group mate has not key in their free time slots using `addtime`, an error is thrown. + +The following activity diagram summarizes what happens when a user executes a FindFreeTime command: + +-------------------------------------------------------------------------------------------------------------------- + +## 4 Planned Enhancements + +### 4.1 Undo/redo feature + +Currently, we do not have undo and redo feature which open rooms for users to accidentally have typos in their command messages, causing certain unwanted commands to be executed. This can cause an array of problems, +from deleting important data along with the contact, to inconvenience for the users themselves. As such, we plan to implement undo and redo feature in the future to provide a better user experience for our users. + +### 4.2. `addtime`/`deletetime` feature + +Currently, our add and delete time features allow users to input multiple time slots in the command box. Should a certain input encounter an error (clashing time intervals), ProjectPRO adds / deletes the other time intervals that are valid and clears the command box. We recognise that users might encounter accidental typos while typing time intervals. Hence, we plan to disallow the clearing command box when the user typed an invalid input. + +### 4.3. Improving our user interface. + +Currently, ProjectPRO's output box height is relative short which could lead to user frustration and hindered efficiency. Users may find it challenging to quickly interpret and comprehend the output, especially when certain commands generate outputs with numerous lines. This limitation might result in a suboptimal user experience, making it difficult for users to extract the desired information promptly.Addressing this problem is crucial to ensuring a more user-friendly interface and improving the overall effectiveness of ProjectPRO for its users. Our future implementation involves optimizing the output display by increasing the height of the output box. This enhancement aims to provide users with a more comprehensive and visually accessible view, allowing them to easily grasp the content at a glance. + +### 4.4. Allowing extensive modification to command words. + +As ProjectPro is a CLI text-based application, it requires extensive typing which might prove troublesome at times. As a result, our users would naturally have their personal preferences regarding the command word inputs. +Enabling users to customize input requirements for functions within ProjectPRO would significantly enhance the flexibility and adaptability of the system. This future implementation empowers users by allowing them to tailor input parameters based on their specific needs and preferences. By accommodating a range of input variations, users can streamline their workflows and optimize the tool to align with diverse use cases. + +### 4.5 Improving `addmeeting` feature. + +In the `addmeeting` feature, user can add free time intervals to a group. Currently, user can add same free time intervals for 2 separate groups or overlapping time intervals between 2 or more groups, causing a clash in their schedule. (Eg. `Group A: Mon 1200 - Mon 1400`; `Group B: Mon 1300 - Mon 1500`). These clashes are not detected and will allow this state to exist. We plan to not allow this, so user cannot insert time intervals that are overlapping with other groups. Eg. `Group A: Mon 1200 - Mon 1400`; `Group B: Mon 1300 - Mon 1500` will not be allowed. + +-------------------------------------------------------------------------------------------------------------------- + +## 5 Documentation logging testing configuration dev-ops * [Documentation guide](Documentation.md) * [Testing guide](Testing.md) @@ -251,127 +795,673 @@ _{Explain here how the data archiving feature will be implemented}_ -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Requirements** +## 6 Appendix Requirements -### Product scope +### 6.1. Product scope **Target user profile**: -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing to mouse interactions -* is reasonably comfortable using CLI apps +* University students often spend a lot of time coordinating project meetup +* sessions and waiting for replies and they are not aware of one another’s schedules. +* This app can help to save time by listing available time slots of individuals in a team. + + +**Value proposition**: Text-friendly project management tool that helps students schedule meetings with different groups while also keeping track of tasks and +responsibilities of each member. Our app will track the schedule of each contact and tasks individuals have to do for their project. + + +### 6.2. User stories + +| Priority | As a ... | I want to ... | So that I can ... | +|----------|--------------------------------------------|------------------------------|------------------------------------------------------------------------| +| `***` | student | add a new contact | keep track of any new contacts | +| `***` | student with many contacts | organize contacts into groups | easily keep track and manage my contacts | +| `***` | student with many team members | record team members' info | keep track of my team members' contact information | +| `***` | student with many projects | delete a group | avoid clutter and unnecessary attention to completed projects | +| `***` | student with many contacts | search for group members | quickly access contact details using name, contact number | +| `***` | user | save entered information | avoid repetitive data entry | +| `**` | team leader | add tasks to contacts | remember who is responsible for which task | +| `**` | student | filter contacts by project | easily view tasks for a specific project group | +| `**` | user | prioritize tasks | work on important tasks first | +| `**` | student | add time slots of group mates| find a suitable meeting time when everyone is available | +| `**` | new user | access a help command | quickly learn about application functions without reading a long guide | +| `*` | impatient user | access the user guide | quickly learn how to use the application | +| `*` | technology-challenged student | read the user guide | gain a better understanding of how to use the application | +| `*` | fast but inaccurate typer | undo a previous command | correct typing mistakes | +| `*` | forgetful student | add a reminder | ensure attendance at upcoming project meetings | +| `*` | student with many projects | color code projects | differentiate between various project groups | +| `*` | lazy user | minimize typing/clicking | achieve tasks with minimal effort | +| `*` | student with an irregular schedule | edit contact information | easily manage changes in contact details | +| `*` | user | filter contacts by courses | view contacts based on shared courses or projects | +| `*` | user | upload attachments/files | improve collaboration and reference for tasks and projects | +| `*` | user | view contact profiles | access course schedules, contact details, and profile pictures | + +### 6.3. Use cases + +(For all use cases below, the **System** is `ProjectPRO` and the **Actor** is the `user`, unless specified otherwise) -**Value proposition**: manage contacts faster than a typical mouse/GUI driven app +**6.3.1. Use case 1: Creating contact** +**MSS** + +1. User requests to add contact. +2. ProjectPRO adds new contact. +3. ProjectPRO informs user contact has been successfully added. + + Use Case ends. + +**Extensions:** +* 1a. ProjectPRO detects error in input + * 1a1. ProjectPRO displays an error message. + * Use case ends. + +* 1b. ProjectPRO detects duplicate contact requested by user. + * 1b1. ProjectPRO displays an error message. + * Use case ends. + +**6.3.2. Use case 2: Delete contact** + +**MSS** + +1. User requests to delete contact +2. ProjectPRO deletes contact. +3. ProjectPRO informs user contact has been successfully deleted. + + Use Case ends + +**Extensions** +* 1a. Contact details is incorrect. + * 1a1. ProjectPRO displays an error message. + * Use case ends. + + +**6.3.3. Use case 3: Finding a contact** + +**MSS** + +1. User requests to find a contact. +2. ProjectPRO searches for the contact. +3. ProjectPRO displays the contact details. -### User stories +Use Case ends. + +**Extensions** -Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*` +* 2a. If the contact is not found. + * 2a1. ProjectPRO displays an empty list. + * Use case ends. -| 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 | +**6.3.4. Use case 4: Listing all contacts** -*{More to be added}* +**MSS** -### Use cases +1. User requests to list all contacts. +2. ProjectPRO displays the list of contacts. -(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise) +Use Case ends. -**Use case: Delete a person** +**6.3.5. Use case 5: Creating a group** **MSS** -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests to create a group. +2. ProjectPRO creates the group. +3. ProjectPRO displays a success message. - Use case ends. +Use Case ends. **Extensions** -* 2a. The list is empty. +* 1a. If the user provides incomplete or incorrect information. + * 1a1. ProjectPRO displays an error message. + * Use case repeats from step 1. - Use case ends. +**6.3.6. Use case 6: Deleting a group** -* 3a. The given index is invalid. +**MSS** - * 3a1. AddressBook shows an error message. +1. User requests to delete a group. +2. ProjectPRO deletes the group. +3. ProjectPRO displays a success message. - Use case resumes at step 2. +Use Case ends. -*{More to be added}* +**Extensions** -### Non-Functional Requirements +* 1a. If the group does not exist. + * 1a1. ProjectPRO displays an error message. + * Use case ends. -1. Should work on any _mainstream OS_ as long as it has Java `11` or above installed. -2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. -*{More to be added}* +**6.3.7. Use case 7: Adding a group remark** -### Glossary +**MSS** -* **Mainstream OS**: Windows, Linux, Unix, OS-X -* **Private contact detail**: A contact detail that is not meant to be shared with others +1. User requests to add a remark to a group. +2. ProjectPRO adds the remark to the group. +3. ProjectPRO displays a success message. + +**Extensions** + +* 1a. If the group does not exist. + * 1a1. ProjectPRO displays an error message. + * Use case ends. + +Use Case ends. + +**6.3.8. Use case 8: Finding a group** + +**MSS** +1. User requests to find a group. +2. ProjectPRO searches for the contact. +3. ProjectPRO shows all the contacts from the group and the group remark. + +Use Case ends. + +**Extensions** + +* 2a. If the group does not exist. + * 2a1. ProjectPRO displays a message indicating that the group does not exist. + * Use case ends. + +**6.3.9. Use case 9: Listing all groups** + +**MSS** + +1. User requests to list all groups. +2. ProjectPRO displays the list of groups. + +Use Case ends. + +**6.3.10. Use case 10: Add contact to group** + +**MSS** +1. User requests to add a contact to a group. +2. ProjectPRO adds the contact to the group. +3. ProjectPRO displays a success message. + +Use Case ends. + +**Extensions** + +* 1a. If the group does not exist. + * 1a1. ProjectPRO displays an error message. + * Use case repeats from step 1. + +* 1b. If the contact does not exist. + * 1b1. ProjectPRO displays an error message. + * Use case repeats from step 1. + +**6.3.11. Use case 11: Removing contact from group** + +**MSS** + +1. User requests to remove a contact from a group. +2. ProjectPRO removes the contact from the group. +3. ProjectPRO displays a success message. +Use Case ends. + +**Extensions** + +* 1a. If the group does not exist. + * 1a1. ProjectPRO displays an error message. + * Use case repeats from step 1. + +* 1b. If the contact does not exist. + * 1bProjectPRO displays an error message. + * Use case repeats from step 1. + +**6.3.12. Use case 12: Adding free time to contact** + +**MSS** + +1. User requests to add free time to a contact. +2. ProjectPRO adds the free time to the contact. +3. ProjectPRO displays a success message. + +Use Case ends. + +**Extensions** + +* 1a. If the contact does not exist. + * 1a1. ProjectPRO displays an error message. + * Use case repeats from step 1. + +* 1b. If the time input does not follow the format. + * 1b1. ProjectPRO displays an error message. + * Use case repeats from step 1. + +**6.3.13. Use case 13: Removing free time from contact** + +**MSS** + +1. User requests to remove free time from a contact. +2. ProjectPRO removes the selected free time from the contact. +3. ProjectPRO displays a success message. +Use Case ends. + +**Extensions** + +* 1a. If the contact does not exist. + * 1a1. ProjectPRO displays an error message. + * Use case repeats from step 1. + +* 1b. If the time input does not follow the format. + * 1b1. ProjectPRO displays an error message. + * Use case repeats from step 1. + +**6.3.14. Use case 14: Listing free time of contact** + +**MSS** + +1. User requests to list the free time of a contact. +2. ProjectPRO displays the list of free time. +Use Case ends. + +**Extensions** + +* 2a. If the contact has no free time. + * 2a1. ProjectPRO displays nothing. + * Use case repeats from step 1. + +**6.3.15. Use case 15: Adding meeting time to group** + +**MSS** + +1. User requests to add meeting time to a group. +2. ProjectPRO adds the meeting time to the group. +3. ProjectPRO displays a success message. +Use Case ends. + +**Extensions** + +* 1a. If the user provides incorrect information or the group does not exist. + * 1a1. ProjectPRO displays an error message. + * Use case repeats from step 2. + +**6.3.16. Use case 16: Remove meeting time from group** + +**MSS** + +1. User requests to remove meeting time from a group. +2. ProjectPRO removes the selected meeting time from the group. +3. ProjectPRO displays a success message. +Use Case ends. + +**Extensions** + +* 1a. If the group does not exist. + * 2a1. ProjectPRO displays an error message. + * Use case repeats from step 1. +* 1b. If the selected meeting time does not exist for the group. + * 1b. ProjectPRO displays an error message. + * Use case repeats from step 1. +* 1c. If the meeting time does not follow the required format. + * 1b. ProjectPRO displays an error message. + * Use case repeats from step 1. + + +**6.3.17. Use case 17: Listing meeting time of group** + +**MSS** + +1. User requests to list the meeting time of a group. +2. ProjectPRO retrieves the meeting time details for the group. +3. ProjectPRO displays the list of meeting time. +Use Case ends. + +**6.3.18. Use case 18: Find free time of group** + +**MSS** + +1. User requests to find common free time for a group. +2. ProjectPRO identifies common free time slots between all members of the group. +3. ProjectPRO displays the list of common free time slots. +Use Case ends. + +**Extensions** + +* 2a. If there is no common free time the length of the duration. + * 2a1. ProjectPRO displays an empty list. + * Use case repeats from step 1. + +### 6.4. Non-Functional Requirements + +1. Compatibility: Should work on any mainstream OS as long as it has Java 11 or above installed. +2. Performance: Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +3. Usability: 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. +4. Usability: Should be able to save the state of the user’s actions. +5. Performance: Should be able to handle an increasing number of contacts and events without a significant degradation in performance. +6. Usability: Data loss or corruption should not occur, even in the event of unexpected crashes or system failures. +7. Accessibility: The system shall be operable even without internet connection. + +### 6.5. Glossary + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|-----------|----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------|-------------------------------------------------| +| `n/` | Contact name of the student | Alphanumeric characters (a to z, A to Z, 0 to 9) | John Doe, David Li 2 | Kishen s/o Kasinathan, ナルト, அசிங்கமான | +| `p/` | Phone number of the student | Positive integer with 3 or more digits | 999, 98765432, 18003569377 | 1-800-356-9377, 0, -1, 98431234.5 | +| `e/` | Email of the student | Email prefix: Alphanumeric characters (a to z, A to Z, 0 to 9), @, Email Domain | example@gmail.com, example@moe.edu.sg | example@!.com, example@moed.edu.s | +| `g/` | Name of the group | Alphanumeric characters (a to z, A to Z, 0 to 9) | CS2103T, Group 3 | Group 3!, 1 | +| `r/` | Group remark | N/A | Zoom link: CS2101.zoom, 123!@#$#@ | N/A | +| `t/` | Time interval of student / group | timings are written with the first 3 letters of the day and time in 24 hour format, with a `-` between the timings. Start time cannot be after end time | mon 1300 - mon 1400, sat 1000 - sun 1300 | monday 1300 - tuesday 1200, wed 1300 - wed 1000 | -------------------------------------------------------------------------------------------------------------------- -## **Appendix: Instructions for manual testing** +## 7 Appendix Instructions for manual testing 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 +### 7.1. Launch and shutdown 1. Initial launch 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. + 2. 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 +2. Saving window preferences - 1. Resize the window to an optimum size. Move the window to a different location. Close the window. + 1. Resize the window to an optimum size. Move the window to a different location. Close the window. - 1. Re-launch the app by double-clicking the jar file.
+ 2. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained. -1. _{ more test cases …​ }_ - -### Deleting a person +### 7.2. Deleting a person 1. Deleting a person while all persons are being shown + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + + 2. Test case: `delete n/Alex Yeoh`
+ Expected: Alex is deleted from the list. Details of the deleted person shown in the status message. Timestamp in the status bar is updated. + + 3. Test case: `delete n/Alex Yeoh`, after Alex has been deleted
+ Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + + 4. Other incorrect delete commands to try: `delete`, `delete n/x`, `...` (where x is not in the list)
+ Expected: Similar to previous. + +### 7.3. Finding a contact + +1. Find a particular contact in your address book + + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + + 2. Test case: `find n/Alex Yeoh`
+ Expected: Only contacts with Alex is shown in the list. Number of perosns with that name is shown. + + 3. Test case: `find n/Alex Yeoh`, after Alex has been searched
+ Expected: No change to the status message. + + 4. Other incorrect find commands to try: `find n/`, `find n/x`, `...` (where x is not in the list)
+ Expected: Similar to previous. + +### 7.4. Listing all contacts + +1. Listing all contacts in your address book + + 1. Prerequisites: Find a limited number of persons using the `find n/` command. No persons will be listed. + + 2. Test case: `list`
+ Expected: All contacts will be shown. + + 3. Other incorrect find commands to try: `list n/`, `list n/x`, `...`
+ Expected: Error message shown. + +### 7.5. Creating a group + +1. Creating a group that does not exist + + 1. Test case: `new g/CS2103`
+ Expected: New group called CS2103 is added to ProjectPRO. + + 2. Test case: `new g/CS2103`, after CS2103 has been created
+ Expected: No group is created. Error details shown in the status message. Status bar remains the same. + + 3. Other incorrect delete commands to try: `new g/`, `new g/x`, `...` (where x is a group in the ProjectPRO)
+ Expected: Similar to previous. + +### 7.6. Deleting a Group + +1. Deleting a Group + 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. + 2. Test case: `delete g/CS2103`
+ Expected: CS2103 is deleted from the list. Details of the deleted group shown in the status message. Group tag under contacts in that group is removed. - 1. Test case: `delete 0`
- Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + 3. Test case: `delete g/CS2103`, after CS2103 has been deleted
+ Expected: No group is deleted. Error details shown in the status message. Status bar remains the same. - 1. Other incorrect delete commands to try: `delete`, `delete x`, `...` (where x is larger than the list size)
+ 4. Other incorrect delete commands to try: `delete`, `delete n/x`, `...` (where x is not in the list)
Expected: Similar to previous. -1. _{ more test cases …​ }_ +### 7.7. Adding a group remark + +1. Adding a group remark + + 1. Test case: `remark g/CS2103 r/Lecture on friday`
+ Expected: New remark with "Lecture on friday" is added to CS2103. + + 2. Test case: `remark g/CS2103 r/Lecture on thursday`, after CS2103 has a remark added
+ Expected: Old remark is replaced with new remark. + + 3. Incorrect remark commands to try: `remark g/`, `remark g/x r/Lecture on wednesday`, `...` (where x is a group in the ProjectPRO)
+ Expected: Error message displayed. + +### 7.8. Finding a group + +1. Find a particular group in your address book + + 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list. + + 2. Test case: `find g/CS2103`
+ Expected: Only contacts in the group is shown in the list. Group remark of the group is displayed. + + 3. Test case: `find g/CS2103`, after CS2103 has been searched
+ Expected: No change to the status message. + + 4. Other incorrect find commands to try: `find g/`, `...`
+ Expected: Error message displayed. + +### 7.9. Listing all groups + +1. Listing all groups in your address book + + 1. Test case: `listgroup`
+ Expected: All groups will be shown in the contact box. + + 2. Other incorrect find commands to try: `listgroup g/`, `listgroup cs2103`, `...`
+ Expected: Error message shown. + +### 7.10. Adding contacts to groups -### Saving data +1. Adding a contact to a group + + 1. Prerequisite: `list`
+ Expected: All contacts will be shown in address book. + + 2. Test case: `group n/Alex Yeoh g/CS2103`
+ Expected: Alex is added to the group. A CS2103 group tag is added below his name. + + 3. Incorrect group commands to try: `group n/ g/`, `group n/Alex Yeoh g/x`, `group n/x g/CS2103` (where x is an unadded contact / group)
+ Expected: Error message shown. + +### 7.11. Removing a contact from a group + +1. Ungrouping a contact + + 1. Prerequisite: `list`
+ Expected: All contacts will be shown in address book. + + 2. Test case: `ungroup n/Alex Yeoh g/CS2103`
+ Expected: Alex Yeoh's group tag is removed. + + 3. Test case: `ungroup n/Alex Yeoh g/CS2103`, after Alex has been ungrouped
+ Expected: No change to the address book, error message shown in output box. + + 4. Other incorrect find commands to try: `ungroup n/Alex Yeoh g/`, `ungroup n/ g/CS2103`, `...`
+ Expected: Error message displayed. + +### 7.12. Adding free time to contact + +1. Add free time to contact + + 1. Prerequisite: `list`
+ Expected: All contacts will be shown in address book. + + 2. Test case: `addtime n/Alex Yeoh t/mon 1300 - mon 1400`
+ Expected: Free time added to contact, contact has a time interval tag underneath his name. + + 3. Test case: `addtime n/Alex Yeoh t/mon 1300 - mon 1400`, after previous command
+ Expected: No change to the address book, error message shown in output box stating there is a clash in timing. + + 4. Other incorrect find commands to try: `addtime n/Alex Yeoh t/mon 1300 - mon 1200`, `addtime n/Alex Yeoh t/monday 1300 - monday 1400`, `...`
+ Expected: Command format error. + +### 7.13. Deleting free time from contact + +1. Delete free time to contact + + 1. + 2. Prerequisite: `list`
+ Expected: All contacts will be shown in address book. + 3. Prerequisite: `addtime n/Alex Yeoh t/mon 1300 - mon 1400`
+ Expected: Contact has a time interval tag beneath his name. + + 2. Test case: `deletetime n/Alex Yeoh t/mon 1300 - mon 1400`
+ Expected: Free time deleted from contact, contact's time interval tag underneath his name is removed. + + 3. Test case: `deletetime n/Alex Yeoh t/mon 1300 - mon 1400`, after previous command
+ Expected: No change to the address book, error message shown in output box stating time is not in his list. + + 4. Other incorrect find commands to try: `deletetime n/Alex Yeoh t/mon 1300 - mon 1200`, `deletetime n/Alex Yeoh t/monday 1300 - monday 1400`, `...`
+ Expected: Command format error. + +### 7.14. List free time of contact + +1. Add free time to contact + + 1. + 2. Prerequisite: `list`
+ Expected: All contacts will be shown in address book. + 3. Prerequisite: `addtime n/Alex Yeoh t/mon 1300 - mon 1400`
+ Expected: Contact has a time interval tag beneath his name. + + 2. Test case: `listtime n/Alex Yeoh`
+ Expected: All time intervals under contact is listed in output box. + + 3. Test case: `listtime n/Alex Yeoh`, after previous command
+ Expected: No change to the address book. + + 4. Other incorrect find commands to try: `listtime n/Alex Yeoh t/mon 1300 - mon 1200`, `...`
+ Expected: Error message. + +### 7.15. Adding meeting time to group + +1. Add free time to contact + + 1. Prerequisite: `list`
+ Expected: All contacts will be shown in address book. + + 2. Test case: `addmeeting g/CS2103 t/mon 1300 - mon 1400`
+ Expected: Meeting time added to group, day card on the right has a time interval tag under monday. + + 3. Test case: `addmeeting g/CS2103 t/mon 1300 - mon 1400`, after previous command
+ Expected: No change to the address book, error message shown in output box stating there is a clash in timing. + + 4. Other incorrect find commands to try: `addmeeting g/CS2103 t/mon 1300 - mon 1200`, `addmeeting g/CS2103 t/monday 1300 - monday 1400`, `addmeeting g/x t/mon 1300 - mon 1400`, `...` (where x is not an existing group)
+ Expected: Command format error. + +### 7.16. Deleting meeting time from group + +1. Delete meeting time from group + +1. + 2. Prerequisite: `list`
+ Expected: All contacts will be shown in address book. + 3. Prerequisite: `addmeeting g/CS2103 t/mon 1300 - mon 1400`
+ Expected: Meeting time added to group, day card on the right has a time interval tag under monday. + +2. Test case: `deletetime g/CS2103 t/mon 1300 - mon 1400`
+ Expected: Meeting time deleted from contact, time interval tag under the day card on the right is removed. + +3. Test case: `deletetime g/CS2103 t/mon 1300 - mon 1400`, after previous command
+ Expected: No change to the address book, error message shown in output box stating time is not in the list. + +4. Other incorrect find commands to try: `deletetime g/CS2103 t/mon 1300 - mon 1200`, `deletetime g/CS2103 t/monday 1300 - monday 1400`, `deletetime g/x t/mon 1300 - mon 1400`, `...` (where x is not an existing group)
+ Expected: Command format error. + +### 7.17. List meeting time of group + +1. Add free time to contact + + 1. + 2. Prerequisite: `list`
+ Expected: All contacts will be shown in address book. + 3. Prerequisite: `addtime g/CS2103 t/mon 1300 - mon 1400`
+ Expected: Meeting time added to group, day card on the right has a time interval tag under monday. + +2. Test case: `listtime g/CS2103`
+ Expected: All time intervals under group is listed in output box. + +3. Test case: `listtime g/CS2103`, after previous command
+ Expected: No change to the address book. + +4. Other incorrect find commands to try: `listtime g/CS2103 t/mon 1300 - mon 1200`, `...`
+ Expected: Error message. + +### 7.17. Find free time of group +1. Add free time to contact + + 1. + 2. Prerequisite: `list`
+ Expected: All contacts will be shown in address book. + 3. Prerequisite: `group n/Alex Yeoh g/CS2103`, `group n/Bernice Yu g/CS2103`
+ Expected: Both contacts are added to the group. + 4. Prerequisite: `addtime n/Alex Yeoh t/mon 1200 - mon 1300`, `addtime n/Bernice Yu t/mon 1200 - mon 1300`
+ Expected: Free time added to both contacts. + +2. Test case: `findfreetime g/CS2103 d/60`
+ Expected: mon 1200 - mon 1300 is listed in output box + +3. Test case: `findfreetime g/CS2103 d/1`, after previous command
+ Expected: No change to the address book. + +4. Test case: `findfreetime g/CS2103 d/70`, after previous command
+ Expected: No available timeslots will be shown in output box. + +4. Other incorrect find commands to try: `findfreetime g/CS2103 d/`, `findfreetime g/ d/70`, `findfreetime g/CS2103 d/0`, `...`
+ Expected: Error message. + +### 7.18. Saving data 1. Dealing with missing/corrupted data files - 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_ + 1. Edit the JSON file by adding a number "1" to the top of the file. + 2. Run the file. + +-------------------------------------------------------------------------------------------------------------------- + +## 8 Appendix Effort + +ProjectPRO is a project built upon AB3, which was built out of the SE-EDU initiative. Our group has been actively working on ProjectPRO this semester, meeting regularly to discuss about our application to meet deadlines punctually. + + +In this section, we will detail some challenges we faced throughout the process of creating ProjectPRO. + +### **8.1 Design Challenges** +As ProjectPRO is a brownfield project built from AB3, it was necessary to identify the pros and cons of AB3, and use them to come up with a better application more suited for university students. Some challenges include: +* Designing a nice user interface with JavaFX: One big change we wanted to incorporate into our application was the change of the User Interface. We wanted a more lively colour scheme to make our product not only convenient, but pretty to create a better user experience. Despite not being fluent in JavaFX, we trialed around with colours and different components and eventually created a mini weekly calendar to document our weekly group meetings, while having a warm colour scheme to make our application enticing to users. +* Creating more relevant functions and fields for university students: Since our application targeted University students, we decided to add group fields to allow students to record who is in their group. After identifying finding meeting time as a common problem faced by university students in group projects, we also added a time field and added functions to help find common free times that groupmates are free to help users better find common free time to hold meetings. -1. _{ more test cases …​ }_ +### **8.1 Design Challenges** +* Given the substantial size of AB3, comprehending its code structure and intricacies posed an initial challenge for us. In response, we proactively initiated project meetings early on. This early engagement provided us with additional time to grasp the intricacies of AB3's implementation and facilitated collaborative discussions to clarify any uncertainties we had about AB3. This enhanced understanding played a crucial role in minimizing the learning curve when undertaking the implementation of new features for ProjectPRO. Since some features shared similar concepts, understanding how they were implemented in AB3 significantly eased the difficulty in implementing corresponding methods for ProjectPRO's enhancements. diff --git a/docs/Documentation.md b/docs/Documentation.md index 3e68ea364e7..082e652d947 100644 --- a/docs/Documentation.md +++ b/docs/Documentation.md @@ -1,29 +1,21 @@ --- -layout: page -title: Documentation guide + layout: default.md + title: "Documentation guide" + pageNav: 3 --- -**Setting up and maintaining the project website:** - -* We use [**Jekyll**](https://jekyllrb.com/) to manage documentation. -* The `docs/` folder is used for documentation. -* To learn how set it up and maintain the project website, follow the guide [_[se-edu/guides] **Using Jekyll for project documentation**_](https://se-education.org/guides/tutorials/jekyll.html). -* Note these points when adapting the documentation to a different project/product: - * The 'Site-wide settings' section of the page linked above has information on how to update site-wide elements such as the top navigation bar. - * :bulb: In addition to updating content files, you might have to update the config files `docs\_config.yml` and `docs\_sass\minima\_base.scss` (which contains a reference to `AB-3` that comes into play when converting documentation pages to PDF format). -* If you are using Intellij for editing documentation files, you can consider enabling 'soft wrapping' for `*.md` files, as explained in [_[se-edu/guides] **Intellij IDEA: Useful settings**_](https://se-education.org/guides/tutorials/intellijUsefulSettings.html#enabling-soft-wrapping) +# Documentation Guide +* We use [**MarkBind**](https://markbind.org/) to manage documentation. +* The `docs/` folder contains the source files for the documentation website. +* To learn how set it up and maintain the project website, follow the guide [[se-edu/guides] Working with Forked MarkBind sites](https://se-education.org/guides/tutorials/markbind-forked-sites.html) for project documentation. **Style guidance:** * Follow the [**_Google developer documentation style guide_**](https://developers.google.com/style). +* Also relevant is the [_se-edu/guides **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html). -* Also relevant is the [_[se-edu/guides] **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html) - -**Diagrams:** - -* See the [_[se-edu/guides] **Using PlantUML**_](https://se-education.org/guides/tutorials/plantUml.html) -**Converting a document to the PDF format:** +**Converting to PDF** -* See the guide [_[se-edu/guides] **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html) +* See the guide [_se-edu/guides **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html). diff --git a/docs/Gemfile b/docs/Gemfile deleted file mode 100644 index c8385d85874..00000000000 --- a/docs/Gemfile +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } - -gem 'jekyll' -gem 'github-pages', group: :jekyll_plugins -gem 'wdm', '~> 0.1.0' if Gem.win_platform? -gem 'webrick' diff --git a/docs/Logging.md b/docs/Logging.md index 5e4fb9bc217..589644ad5c6 100644 --- a/docs/Logging.md +++ b/docs/Logging.md @@ -1,8 +1,10 @@ --- -layout: page -title: Logging guide + layout: default.md + title: "Logging guide" --- +# Logging guide + * We are using `java.util.logging` package for logging. * The `LogsCenter` class is used to manage the logging levels and logging destinations. * The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level. diff --git a/docs/SettingUp.md b/docs/SettingUp.md index 275445bd551..03df0295bd2 100644 --- a/docs/SettingUp.md +++ b/docs/SettingUp.md @@ -1,27 +1,32 @@ --- -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 to ensure Intellij is configured to use **JDK 11**. -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 +39,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..78ddc57e670 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 57437026c7b..555ac908791 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,197 +1,1001 @@ --- -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. -* Table of Contents -{:toc} +# Welcome to ProjectPRO's User Guide -------------------------------------------------------------------------------------------------------------------- -## Quick start -1. Ensure you have Java `11` or above installed in your Computer. +## Introduction -1. Download the latest `addressbook.jar` from [here](https://github.com/se-edu/addressbook-level3/releases). +Achieve simplicity while optimizing efficiency. -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.
- A GUI similar to the below should appear in a few seconds. Note how the app contains some sample data.
- ![Ui](images/Ui.png) +ProjectPRO is a desktop application designed to help university students organize their projects. Here is how ProjectPRO can streamline your project management process. -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. +- Categorize your contacts into various project groups. +- Track key information from your projects. +- Find free time for your group meetings. - * `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. - * `delete 3` : Deletes the 3rd contact shown in the current list. +ProjectPRO is optimized for use via a Command Line Interface (CLI) while preserving the advantages of maintaining an attractive user interface. ProjectPRO utilizes simple and easy-to-remember commands to execute different tasks, enhancing our user's experience. For instance, adding a contact is executed with a simple `add` command, as listed in our Features Section. Unlock more time in your day, store your information the smart way. - * `clear` : Deletes all contacts. +-------------------------------------------------------------------------------------------------------------------- + + +**New here?** View our instructions for first-time users [here](#first-time-user). + + +**Used ProjectPRO before?** Click [here](#experienced-user) to recall what features we have! + + +-------------------------------------------------------------------------------------------------------------------- + +## Table of Contents +- [1. Introduction](#introduction) +- [2. Table of Contents](#table-of-contents) +- [3. Using Our Guide](#using-our-guide) +- [4. Quick Start](#quick-start) +- [5. Glossary](#glossary) +- [6. Features](#features) +- [7. Commands to Manage Contacts](#commands-to-manage-contacts) + - [7.1 Adding a Contact `add`](#adding-a-contact-add) + - [7.2 Deleting a Contact `delete`](#deleting-a-contact-delete) + - [7.3 Finding a Contact `find`](#finding-a-contact-find) + - [7.4 Listing all Contacts `list`](#listing-contacts-list) +- [8. Commands to Manage Groups](#commands-to-manage-groups) + - [8.1 Adding a Group `new`](#adding-a-group-new) + - [8.2 Deleting a Group `delete`](#deleting-a-group-delete) + - [8.3 Adding remarks to a Group `remark`](#adding-remarks-to-a-group-remark) + - [8.4 Finding a Group `find`](#finding-a-group-find) + - [8.5 Listing a Group `list`](#listing-a-group-listgroup) + - [8.6 Grouping a Contact `group`](#grouping-a-contact-group) + - [8.7 Ungrouping a Contact `ungroup`](#ungrouping-a-contact-ungroup) +- [9. Commands to Manage Time](#commands-to-manage-time) + - [9.1 Adding Time to a Contact `addtime`](#adding-time-to-a-contact-addtime) + - [9.2 Removing Time from a Contact `deletetime`](#removing-time-from-a-contact-deletetime) + - [9.3 Listing Time from a Contact `listtime`](#listing-time-from-a-contact-listtime) + - [9.4 Add Meeting to Group `addmeeting`](#add-meeting-to-a-group-addmeeting) + - [9.5 Remove Meeting Time from a Group `deletetime`](#remove-meeting-time-from-a-group-deletetime) + - [9.6 Listing Meeting Time from a Group `listtime`](#listing-meeting-time-from-a-group-listtime) + - [9.7 Finding Free Time of a Group `findfreetime`](#finding-free-time-of-a-group-findfreetime) +- [10. General Commands](#general-commands) + - [10.1 Viewing Help `help`](#viewing-help-help) + - [10.2 Clearing All Data `clear`](#clearing-all-data-clear) + - [10.3 Exit ProjectPRO `exit`](#exit-projectpro-exit) + + +-------------------------------------------------------------------------------------------------------------------- + + +## Using Our Guide + +-------------------------------------------------------------------------------------------------------------------- + + +This guide explains how you can use ProjectPRO to add contacts and manage your groups of contacts effectively by using our commands. + +Here are some annotations used in this guide: - * `exit` : Exits the app. -1. Refer to the [Features](#features) below for details of each command. + + +

+
+ +
+ +
Acceptable value(s)
+
+

USER_INPUT + Description of what you are allowed to type.

+
+

+
+
+ +
Warning
+
+

INLINE_CODE + Pay attention to these points as they could lead to unexpected issues.

+
-------------------------------------------------------------------------------------------------------------------- +#### First Time User + +-------------------------------------------------------------------------------------------------------------------- + + +We are happy to have you on board as a first-time user of ProjectPRO! To get started, visit our [Quick Start](#quick-start) to set up ProjectPRO. Once it's done, you can dive into the Features section to explore the full range of commands and learn more about what ProjectPRO has to offer. Refer to the image below to get acquainted with our user interface. + +![Image 1: ProjectPRO's user interface](images/UG/Overview_of_GUI.png) +
+

ProjectPRO user interface

+
+ +-------------------------------------------------------------------------------------------------------------------- + + +#### Experienced User + +-------------------------------------------------------------------------------------------------------------------- + + +Welcome back to ProjectPRO. Simply head over to our Features section to gain insights into our commands. If you are looking for a quick reference, click [here](#table-of-contents) for our table of contents. + +-------------------------------------------------------------------------------------------------------------------- + +## Quick Start + +-------------------------------------------------------------------------------------------------------------------- + +1. Ensure you have Java 11 or above installed on your computer. + - Simply go to your terminal and type `java -version`. ![here](images/UG/QuickStart1.png) + +2. Download the latest ProjectPRO.jar from [here](https://github.com/AY2324S1-CS2103T-T10-3/tp/releases). + - ![here](images/UG/QUICKSTART_2.1.png) + +3. Move the JAR file to a folder where you want to store your project details. For example, create a folder named ProjectPRO and place it on your desktop. + - For Mac Users: + - ![MACGUIDE](images/UG/QUICKSTART3.png) + - For Windows Users: + - ![WINDOWSGUIDE](images/UG/QUICKSTART4.png) +4. Launch ProjectPRO. + 1. Open a command terminal. + 2. Type `cd Desktop` , followed by `cd ProjectPro`. + 3. Type `java -jar ProjectPRO.jar` to run the application. +

+5. Start using ProjectPRO! + +-------------------------------------------------------------------------------------------------------------------- + + +## Glossary + +-------------------------------------------------------------------------------------------------------------------- + + +**Definitions** + + +Written below are the definitions of the terminology used throughout the User Guide: + + +| Term | Definition | +|-----------|-------------------------------------------------------------------------------------------| +| Parameter | Parameter indicates the type of information required for a particular command. | +| Command | A special word used together with parameters to execute a particular action. E.g. `find`. | +| GUI | Graphical User Interface, the visual display of ProjectPRO. | + + +**Parameter Information** + + +Written below are some commonly used parameters, what they represent, and examples: + + +| Parameter | Description | Constraints | Valid Examples | Invalid Examples | +|-----------|----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------|-------------------------------------------------| +| `n/` | Contact name of the student | Alphanumeric characters (a to z, A to Z, 0 to 9) | John Doe, David Li 2 | Kishen s/o Kasinathan, ナルト, அசிங்கமான | +| `p/` | Phone number of the student | Positive integer with 3 or more digits | 999, 98765432, 18003569377 | 1-800-356-9377, 0, -1, 98431234.5 | +| `e/` | Email of the student | Email prefix: Alphanumeric characters (a to z, A to Z, 0 to 9), @, Email Domain | example@gmail.com, example@moe.edu.sg | example@!.com, example@moed.edu.s | +| `g/` | Name of the group | Alphanumeric characters (a to z, A to Z, 0 to 9) | CS2103T, Group 3 | Group 3!, 1 | +| `r/` | Group remark | N/A | Zoom link: CS2101.zoom, 123!@#$#@ | N/A | +| `t/` | Time interval of student / group | timings are written with the first 3 letters of the day and time in 24 hour format, with a `-` between the timings. Start time cannot be after end time | mon 1300 - mon 1400, sat 1000 - sun 1300 | monday 1300 - tuesday 1200, wed 1300 - wed 1000 | + +**Command format** + +Written below are some extra information regarding our command formats: + +| Format | Explanation | Examples | +|-----------------------------|--------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| +| Words in `UPPER_CASE` | These are values written by you | `new g/GROUP_NAME` can be written as new `g/CS2103T` | +| Items in `SQUARE_BRACKETS` | These are optional values that can be left empty | `add n/NAME p/PHONE e/EMAIL [g/GROUP_NAME]` can be used as `add n/John p/999 e/example@gmail.com` and `add n/John p/999 e/example@gmail.com g/CS2103` | +| Items with `...` after them | These are values that can be used multiple times | `addtime n/NAME t/time...` can be used as `addtime n/John t/mon 1200 - mon 1300 t/tue 1400 - tue 1500` | + +

+
+
+ +
Notes
+
+
  • All user inputs are case-sensitive, unless stated otherwise.
  • +
  • Command prefixes can be in any order.
  • +
    + +-------------------------------------------------------------------------------------------------------------------- + + ## Features -
    +-------------------------------------------------------------------------------------------------------------------- -**:information_source: Notes about the command format:**
    +## Commands to Manage Contacts -* Words in `UPPER_CASE` are the parameters to be supplied by the user.
    - e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +-------------------------------------------------------------------------------------------------------------------- -* Items in square brackets are optional.
    - e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* 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. +### Adding a contact: `add` +You can create a contact in your contact list. -* 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. -* 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`. +**Format:** `add n/NAME p/PHONE e/EMAIL [g/GROUP_NAME]` -* 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. + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    NAME + must be alphanumeric, cannot be blank and must not be an existing contact in the contact list.

    +

    PHONE + must be a positive integer with at least 3 digits and must not exist in the contact list.

    +

    EMAIL + must be alphanumeric with a @ domain, end with a domain label at least 2 characters long and must not exist in the contact list.

    +

    GROUP_NAME + must be alphanumeric. This is an optional parameter.

    -### Viewing help : `help` -Shows a message explaning how to access the help page. +**Example(s):** +- `add n/John Doe p/98765432 e/johnd@example.com g/CS2103T` + This creates a contact in the contact list, named John Doe, who is in group CS2103T. +- `add n/John Doe p/98765432 e/johnd@example.com` + This creates a contact in the contact list, named John Doe, who is not in any group. -![help message](images/helpMessage.png) +![Manage Contacts: Add](images/features/Managecontacts_add.png) +
    +

    You have added John Doe to your contacts, at contact number 7.

    +
    -Format: `help` +**Potential error(s):** +- Incorrect format (e.g., no prefix, duplicate prefixes). +- The contact you are trying to add already exists in your contact list: `This person already exists in your contact list`. +-------------------------------------------------------------------------------------------------------------------- -### Adding a person: `add` +### Deleting a contact: `delete` +You can delete a contact from your contact list. -Adds a person to the address book. -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…​` +**Format:** `delete n/NAME` -
    :bulb: **Tip:** -A person can have any number of tags (including 0) + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    NAME + must be alphanumeric, cannot be blank, and must be an existing contact in the contact list.

    -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` -### Listing all persons : `list` +**Example(s):** +- `delete n/John Doe` + This deletes John Doe from the contact list. -Shows a list of all persons in the address book. +![Manage Contacts: Delete](images/features/Managecontacts_delete.png) +
    +

    You have deleted John Doe, previously contact number 7, from your contacts.

    +
    -Format: `list` +**Potential error(s):** +- Invalid format (e.g., no prefix, duplicate prefixes). +- The contact you are trying to delete does not exist in your contact list: `No person with such name found. Please provide the person's full name as in the existing contact list`. -### Editing a person : `edit` +-------------------------------------------------------------------------------------------------------------------- -Edits an existing person in the address book. +### Finding a contact: `find` -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` +You can find all the contacts from your contact list with the matching keywords. -* 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. +**Format:** `find n/KEYWORDS_IN_NAME` -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. +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    KEYWORDS_IN_NAME + must be alphanumeric and cannot be blank. It is not case-sensitive.

    +
    -### Locating persons by name: `find` +**Example(s):** +- `find n/alex bernice` -Finds persons whose names contain any of the given keywords. + This displays all the contacts with names containing Alex or Bernice. -Format: `find KEYWORD [MORE_KEYWORDS]` +![Manage Contacts: Find](images/features/Managecontacts_find.png) +
    +

    You have found all your contacts with the name Alex or Bernice.

    +
    -* 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` +**Potential error(s):** +- Invalid format (e.g., no prefix, duplicate prefixes). -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` +### Listing contacts: `list` +You can list all the contacts in your contact list. -Deletes the specified person from the address book. +**Format:** `list` -Format: `delete INDEX` +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    + No additional input required.

    +
    -* 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, …​ +**Example(s):** +- `list` + This displays all the contacts in the contact list. -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. +![Manage Contacts: List](images/features/Managecontacts_list.png) +
    +

    You have listed all the contacts in your contact list.

    +
    -### Clearing all entries : `clear` +**Potential error(s):** +- Extra inputs detected. -Clears all entries from the address book. +[Back to Table of Contents](#table-of-contents) -Format: `clear` +-------------------------------------------------------------------------------------------------------------------- -### Exiting the program : `exit` -Exits the program. +## Commands to Manage Groups -Format: `exit` +-------------------------------------------------------------------------------------------------------------------- -### Saving the data -AddressBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually. +### Adding a group: `new` + +You can create a new group in your contact list. + +**Format:** `new g/GROUP_NAME` + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    GROUP_NAME + must be alphanumeric, cannot be blank, and must not be an existing group in your contact list.

    +
    + +**Example(s):** +- `new g/CS2103T tp` + This creates a new group named "CS2103T tp". + +![Manage Groups: New](images/features/Managegroup_new.png) +
    +

    You have created a new group called CS2103T tp.

    +
    + +**Potential error(s):** +- Invalid command format (e.g., no prefix, duplicate prefixes). +- The group you are trying to add already exists in your contact list: `This group already exists in the contact list`. + +-------------------------------------------------------------------------------------------------------------------- + + +### Deleting a group: `delete` +You can delete a group in your contact list. + +**Format:** `delete g/GROUP_NAME` + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    GROUP_NAME + must be alphanumeric, cannot be blank and must be an existing group in your contact list.

    +
    + + +**Example(s):** +- `delete g/CS2103T tp` + This deletes the group "CS2103T tp" from your contact list, given "CS2103T tp" is an existing group in your contact list. + +![Manage Group: Delete](images/features/Managegroup_delete.png) +
    +

    You have deleted the group "CS2103T tp" from your contact list.

    +
    + +**Potential error(s):** +- Incorrect format (e.g., no prefix, duplicate prefixes). +- The group you are trying to delete does not exist in your contact list: `No group with such name found. Please provide the group's full name as in the existing contact list`. + +-------------------------------------------------------------------------------------------------------------------- -### Editing the data file -AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file. +### Adding remarks to a group: `remark` -
    :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. +You can add remarks to a group in your contact list. + +**Format:** `remark g/GROUP_NAME r/REMARK` + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    GROUP_NAME + must be alphanumeric, cannot be blank and must be an existing group in your contact list.

    +

    REMARK + can take on any value.

    -### Archiving data files `[coming in v2.0]` -_Details coming soon ..._ +**Example(s):** +- `remark g/CS2103T r/quiz tomorrow` + This adds the remark "quiz tomorrow" to the existing "CS2103T" group in your contact list. + +![Manage Group: Remark](images/features/Managegroup_remark.png) +
    +

    You have added the group remark "quiz tomorrow" to the group "CS2103T" in your contact list.

    +
    + +**Potential errors(s):** +- Incorrect format (e.g. no prefix, duplicate prefixes). +- The group you entered does not exist in your contact list: `No group with such name found. Please provide the group's full name as in the existing contact list.` -------------------------------------------------------------------------------------------------------------------- -## FAQ -**Q**: How do I transfer my data to another Computer?
    -**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder. +### Finding a group: `find` +You can find a group in your contact list. This allows you to view the group's members and remarks. + + +**Format:** `find g/GROUP_NAME` + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    GROUP_NAME + must be alphanumeric, cannot be blank and must be an existing group in your contact list.

    +
    + +**Example(s):** +- `find g/CS2103T` + This returns the members and remarks of the existing "CS2103T" group in your contact list. + +![Manage Group: Find](images/features/Managegroup_find.png) +
    +

    You are now able to view all details regarding the group "CS2103T" in your contact list.

    +
    + + +**Potential error(s):** +- Incorrect format (e.g. no prefix, duplicate prefixes). +- The group you are trying to find does not exist in your contact list: `No group with such name found. Please provide the group's full name as in the existing contact list`. + + +### Listing a group: `listgroup` +You can list all the groups in your contact list. + +**Format:** `listgroup` + +

    +
    +
    + +
    Acceptable value(s)
    +

    + No additional parameters. +

    + + +**Example(s):** +- `listgroup` + This lists all the groups in your contact list. + +![Manage Group: List Group](images/features/Managegroup_listgroup.png) +
    +

    You have listed all the groups in your contact list.

    +
    + + +**Potential error(s):** +- Extra inputs detected. -------------------------------------------------------------------------------------------------------------------- -## Known issues -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. +### Grouping a Contact: `group` + +You can add an existing contact to an existing group. + +**Format:** `group n/NAME g/GROUP_NAME` + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    NAME + must be alphanumeric, cannot be blank, must be an existing contact in your contact list, and must not be a member of GROUP_NAME.

    +

    GROUP_NAME + must be alphanumeric, cannot be blank, and must be an existing group in your contact list.

    +
    + + +**Example(s):** +- `group n/Bernice Yu g/CS2103T` + This adds your contact "Bernice Yu" into the group "CS2103T". + +![Manage Group: Group](images/features/Managegroup_group.png) +
    +

    You have added "Bernice Yu" to the group "CS2103T" in your contact list.

    +
    + + +**Potential error(s):** +- Incorrect format (e.g., no prefix, duplicate prefixes). +- The contact you are trying to add is already a member of the group: `Bernice Yu is already in this group: CS2103T`. -------------------------------------------------------------------------------------------------------------------- -## Command summary +### Ungrouping a Contact: `ungroup` + +You can remove a contact from a group. + +**Format:** `ungroup n/NAME g/GROUP_NAME` + + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    NAME + must be alphanumeric, cannot be blank, must be an existing contact in your contact list, and must be a member of GROUP_NAME.

    +

    GROUP_NAME + must be alphanumeric, cannot be blank, and must be an existing group in your contact list.

    +
    + + +**Example(s):** +- `ungroup n/Alex Yeoh g/CS2103T` + This removes your contact "Alex Yeoh" from the group "CS2103T". + +![Manage Group: Ungroup](images/features/Managegroup_ungroup.png) +
    +

    You have removed "Alex Yeoh" from the group "CS2103T".

    +
    + +**Potential error(s):** +- Incorrect format (e.g., no prefix, duplicate prefixes). +- The contact you are trying to remove is not a member of the group: `Alex Yeoh is not in this group: CS2103T`. + +[Back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- + +## Commands to Manage Time + +-------------------------------------------------------------------------------------------------------------------- + +### Adding Time to a Contact: `addtime` +You can add time slots when your contacts are available. + +**Format:** `addtime n/NAME t/FREE_TIME` + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    NAME + must be alphanumeric, cannot be blank and must be an existing contact in your contact list. +

    +

    FREE_TIME + must be a time slot within the current weekly schedule. E.g. Sat 1000 - Mon 1000 is not allowed as the monday here refers to next week, violating the current weekly schedule pattern.

    +

    FREE_TIME + must be a time slot with some duration. E.g. Mon 1200 - Mon 1200 is not valid as there is no duration.Mon 1300- Mon 1400 is valid. +It is not case-sensitive.

    +
    + +

    +
    +
    + +
    Warning
    +
    +

    +FREE_TIME + must not clash with the other inputted/existing time slots of the contact. E.g. If time slot Mon 1200 - Mon 1300 has been added, then Mon 1300 - Mon 1400 can not be added. +

    + +**Example(s):** +- `addtime n/Alex Yeoh t/mon 1400 - mon 1600` +This adds a time slot when Alex Yeoh is available in your contact list. + +![](images/features/Managetime_addtime.png) + +
    +

    You have added the Monday 2 pm - 4 pm timeslot to Alex Yeoh to indicate that he is free during that time.

    +
    + +**Potential error(s):** +- Contact does not exist in the contact list. +- The time slot you are trying to add is not valid. + + +### Removing Time from a Contact: `deletetime` +You can remove available time slots of your contacts. + + +**Format:** `deletetime n/NAME t/FREE_TIME` + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    NAME + must be alphanumeric, cannot be blank and must be an existing contact in your contact list. +

    +

    FREE_TIME + must match the contact's existing time. It is not case-sensitive.

    +
    + +

    +
    +
    + +
    Warning
    +
    +

    FREE_TIME + must not clash with other inputted time slots.

    +
    + +**Example(s):** +- `deletetime n/Alex Yeoh t/mon 1400 - mon 1600` + This removes a time slot when Alex Yeoh is available from your contact list. + +![](images/features/Managetime_deletetimecontact.png) + +
    +

    You have deleted the Monday 2 pm - 4 pm time slot from Alex Yeoh to indicate that he is not free during that time anymore.

    +
    + +**Potential error(s):** +- Contact does not exist in the contact list. +- Time slot does not exist for the contact. +- Invalid time slot format. + +-------------------------------------------------------------------------------------------------------------------- + +### Listing Time from a Contact: `listtime` +You list all available time slots of your contacts. + +**Format:** `listtime n/NAME` +- Provide the full name of the contact using the `n/` prefix. +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    NAME + must be alphanumeric, cannot be blank and must be an existing contact in your contact list.

    +
    + +

    + + +**Example(s):** +- `listtime n/Alex Yeoh` + This lists all time slots when Alex Yeoh is available from your contact list. + +![](images/features/Managetime_listtimecontact.png) + +
    +

    You have listed Alex Yeoh's free times.

    +
    + +**Potential error(s):** +- Contact does not exist in the contact list. + +-------------------------------------------------------------------------------------------------------------------- + +### Add Meeting to a Group: `addmeeting` +You can add a meeting time slot for your group. + + +**Format:** `addmeeting g/GROUP_NAME t/MEETING_TIME` + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    GROUP_NAME + must be alphanumeric, cannot be blank and must be an existing group in your contact list.

    +

    MEETING_TIME + must be a time slot within the weekly schedule and must not clash with the group's existing meeting times. It is not case-sensitive.

    +
    +

    + +
    +
    + +
    Warning
    +
    +

    MEETING_TIME + can be added even if some members are not available for the time slot. Do coordinate with your group members on the most suitable time slots.

    +
    + +**Example(s):** +- `addmeeting g/CS2103T t/mon 1400 - mon 1600` + This adds a meeting for the group "CS2103T". + +![](images/features/Managetime_addmeeting.png) + +
    +

    You have added a Monday 2 pm - 4 pm meeting for the group "CS2103T".

    +
    + +**Potential error(s):** +- Group does not exist in the contact list. +- Invalid time slot format. + +-------------------------------------------------------------------------------------------------------------------- + +### Remove Meeting Time from a Group: `deletetime` +You can remove meeting times from your groups. + +**Format:** `deletetime g/GROUP_NAME t/MEETING_TIME` + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    GROUP_NAME + must be alphanumeric, cannot be blank and must be an existing group in your contact list.

    +

    MEETING_TIME + must be a time slot within a weekly schedule and must be an existing meeting slot of the group. It is not case-sensitive.

    +
    +

    + +**Example(s):** +- `deletetime g/CS2103T t/mon 1400 - mon 1600` + This removes a meeting time slot from the group "CS2103T". + +![](images/features/Managetime_deletetimegroup.png) + +
    +

    You have deleted the Monday 2 pm - 4 pm meeting for the group "CS2103T".

    +
    + +**Potential error(s):** +- Group does not exist in the contact list. +- Group does not contain meeting time. +- Invalid time slot format. + +-------------------------------------------------------------------------------------------------------------------- + +### Listing Meeting Time from a Group: `listtime` +You list meeting time for your groups. + +**Format:** `listtime g/GROUP_NAME` + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    GROUP_NAME + must be alphanumeric, cannot be blank and must be an existing group in your contact list.

    +
    +

    + + +**Example(s):** +- `listtime g/CS2103T` + This lists all meeting times of the CS2100 group. + +![](images/features/Managetime_listtimegroup.png) + +
    +

    You can see all the meetings you have for CS2103T/p> +

    + +**Potential error(s):** +- Group does not exist in the contact list. + +-------------------------------------------------------------------------------------------------------------------- + +### Finding Free Time of a Group: `findfreetime` +You can find a meeting time slot for your group where everyone is available. + + +**Format:** `findfreetime g/GROUP_NAME d/DURATION` + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    GROUP_NAME + must be alphanumeric, cannot be blank and must be an existing group in your contact list.

    +

    DURATION + must be a whole number representing the meeting duration in minutes.

    +
    +

    +
    +
    + +
    Warning
    +
    +

    GROUP_NAME + must contain contacts with their free time slots.

    +
    + +**Example(s):** +- `findfreetime g/CS2103T d/60` + This shows you all possible 60-minute meeting slots for your CS2103T group. + +![](images/features/Managetime_findfreetime.png) + +
    +

    You are now able to view all possible meeting times of at least 60 minutes for the group "CS2103T".

    +
    + + +**Potential error(s):** +- Group does not exist in the contact list. +- Contacts in the group did not add their available time slots. + +[Back to Table of Contents](#table-of-contents) + +-------------------------------------------------------------------------------------------------------------------- + +## General Commands + +-------------------------------------------------------------------------------------------------------------------- + +### Viewing Help: `help` +You can view the link to our User Guide. + + +**Format:** `help` + + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    + No additional input required.

    +
    +

    + +**Example(s):** +- `help` + + This displays the link to the User Guide. + +![](images/features/Generalcommand_help.png) + +
    +

    You are now able to copy the URL of the User Guide.

    +
    + + +**Potential error(s):** +- Extra inputs detected. + +-------------------------------------------------------------------------------------------------------------------- + + +### Clearing All Data: `clear` +Clears all entries from the contact list. + + +**Format:** `clear` + + +

    +
    +
    + +
    Acceptable value(s)
    +
    +

    + No additional input required.

    +
    +

    + + +**Example(s):** +- `clear` + + This clears all the data in your contact list. + +![](images/features/Generalcommand_clear.png) + +
    +

    You have cleared all the data in your contact list.

    +
    + + +**Potential error(s):** +- Extra inputs detected. + + +### Exit ProjectPRO: `exit` +You can exit ProjectPRO. + +**Format:** `exit` + + +

    +
    +
    + +
    Acceptable values
    +
    +

    + No additional input required.

    +
    +

    + + +**Potential error(s):** +- Extra inputs detected. + +-------------------------------------------------------------------------------------------------------------------- + +### Saving the data +ProjectPRO saves automatically after any command changes the data. There is no need to save manually. + +-------------------------------------------------------------------------------------------------------------------- + +### Editing the data file +ProjectPRO's 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. +
    +
    + +
    Warning
    +
    +

    + If your changes to the data file makes its format invalid, ProjectPRO will discard all data and start with a sample data file. Hence, it is recommended to take a backup of the file before editing it. +

    +
    -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` +[Back to Table of Contents](#table-of-contents) 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..030ccd39c2a --- /dev/null +++ b/docs/_markbind/layouts/default.md @@ -0,0 +1,62 @@ + + + + +
    + + ProjectPRO +
  • 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/box.html b/docs/box.html new file mode 100644 index 00000000000..aa43ccc0776 --- /dev/null +++ b/docs/box.html @@ -0,0 +1,6 @@ +
    +

    {{ include.title }}

    + {% if include.body %} +

    {{ include.body | markdownify }}

    + {% endif %} +
    diff --git a/docs/diagrams/AddCommandSequenceDiagram.puml b/docs/diagrams/AddCommandSequenceDiagram.puml new file mode 100644 index 00000000000..d55c56c9c59 --- /dev/null +++ b/docs/diagrams/AddCommandSequenceDiagram.puml @@ -0,0 +1,114 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":AddCommandParser" as AddCommandParser LOGIC_COLOR +participant "a:AddCommand" as AddCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +participant "Alex Yeoh:Person" as Person LOGIC_COLOR +participant "g:Group" as Group LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute(add n/Alex Yeoh ...) +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand(add n/Alex Yeoh ...) +activate AddressBookParser + +create AddCommandParser +AddressBookParser -> AddCommandParser +activate AddCommandParser + +AddCommandParser --> AddressBookParser +deactivate AddCommandParser + +AddressBookParser -> AddCommandParser : parse(n/Alex Yeoh ...) +activate AddCommandParser + +create Person +AddCommandParser -> Person +activate Person + +Person --> AddCommandParser +deactivate Person + +create Group + +AddCommandParser -> Group +activate Group + +Group --> AddCommandParser +deactivate Group + + +Create AddCommand +AddCommandParser -> AddCommand +activate AddCommand + +AddCommand --> AddCommandParser : a +deactivate AddCommand + +AddCommandParser --> AddressBookParser : a +deactivate AddCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +AddCommandParser -[hidden]-> AddressBookParser +destroy AddCommandParser + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> AddCommand : execute() +activate AddCommand + +AddCommand -> Model : addPerson(Alex Yeoh) +activate Model + +Model --> AddCommand +deactivate Model + +AddCommand -> Person : getGroups() +activate Person + +Person --> AddCommand : g +deactivate Person + +AddCommand -> Model : addGroup(g) +activate Model + +Model --> AddCommand +deactivate Model + +AddCommand -> Group : addPerson(Alex Yeoh) +activate Group + +Group --> AddCommand +deactivate Group + + +create CommandResult +AddCommand -> CommandResult +activate CommandResult + +CommandResult --> AddCommand +deactivate CommandResult + + +AddCommand --> LogicManager : result +deactivate AddCommand + +AddCommand -[hidden]-> AddressBookParser +destroy AddCommand + +[<--LogicManager +deactivate LogicManager + + + +@enduml diff --git a/docs/diagrams/AddGroupMeetingTimeSequenceDiagram.puml b/docs/diagrams/AddGroupMeetingTimeSequenceDiagram.puml new file mode 100644 index 00000000000..90373e42f14 --- /dev/null +++ b/docs/diagrams/AddGroupMeetingTimeSequenceDiagram.puml @@ -0,0 +1,74 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":AddGroupMeetingTimeCommandParser" as AddGroupMeetingTimeCommandParser LOGIC_COLOR +participant "a:AddGroupMeetingTimeCommand" as AddGroupMeetingTimeCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("addmeeting g/CS2103T") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("addmeeting g/CS2103T") +activate AddressBookParser + +create AddGroupMeetingTimeCommandParser +AddressBookParser -> AddGroupMeetingTimeCommandParser +activate AddGroupMeetingTimeCommandParser + +AddGroupMeetingTimeCommandParser --> AddressBookParser +deactivate AddGroupMeetingTimeCommandParser + +AddressBookParser -> AddGroupMeetingTimeCommandParser : parse("g/CS2103T") +activate AddGroupMeetingTimeCommandParser + +Create AddGroupMeetingTimeCommand +AddGroupMeetingTimeCommandParser -> AddGroupMeetingTimeCommand +activate AddGroupMeetingTimeCommand + +AddGroupMeetingTimeCommand --> AddGroupMeetingTimeCommandParser : a +deactivate AddGroupMeetingTimeCommand + +AddGroupMeetingTimeCommandParser --> AddressBookParser : a +deactivate AddGroupMeetingTimeCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar.' +AddGroupMeetingTimeCommandParser -[hidden]-> AddressBookParser +destroy AddGroupMeetingTimeCommandParser + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> AddGroupMeetingTimeCommand : execute() +activate AddGroupMeetingTimeCommand + +AddGroupMeetingTimeCommand -> Model : addTimeToGroup(args) +activate Model + +Model --> AddGroupMeetingTimeCommand +deactivate Model + +create CommandResult +AddGroupMeetingTimeCommand -> CommandResult +activate CommandResult + +CommandResult --> AddGroupMeetingTimeCommand +deactivate CommandResult + +AddGroupMeetingTimeCommand --> LogicManager : result +deactivate AddGroupMeetingTimeCommand + +AddGroupMeetingTimeCommand-[hidden]-> AddressBookParser +destroy AddGroupMeetingTimeCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/AddTimeActivityDiagram.puml b/docs/diagrams/AddTimeActivityDiagram.puml new file mode 100644 index 00000000000..bf8de2725c9 --- /dev/null +++ b/docs/diagrams/AddTimeActivityDiagram.puml @@ -0,0 +1,25 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:Start Add Time to Person; +:Check if input times clash with each other; +if () then ([No clashes]) + :Check if person exists; + if () then ([Person exists]) + :Check if input times clash with existing times; + if () then ([No clashes]) + :ProjectPRO adds times to person; + else ([else]) + :ProjectPRO throws error; + endif + else ([else]) + :ProjectPRO throws error; + endif +else ([else]) + :ProjectPRO throws Error; +endif + +stop + +@enduml diff --git a/docs/diagrams/AddTimeSequenceDiagram.puml b/docs/diagrams/AddTimeSequenceDiagram.puml new file mode 100644 index 00000000000..a8d0828e94b --- /dev/null +++ b/docs/diagrams/AddTimeSequenceDiagram.puml @@ -0,0 +1,74 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":AddTimeCommandParser" as AddTimeCommandParser LOGIC_COLOR +participant "a:AddTimeCommand" as AddTimeCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute(addtime n/Alex Yeoh ...) +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand(addtime n/Alex Yeoh ...) +activate AddressBookParser + +create AddTimeCommandParser +AddressBookParser -> AddTimeCommandParser +activate AddTimeCommandParser + +AddTimeCommandParser --> AddressBookParser +deactivate AddTimeCommandParser + +AddressBookParser -> AddTimeCommandParser : parse(n/Alex Yeoh t/mon 1200 - ...) +activate AddTimeCommandParser + +Create AddTimeCommand +AddTimeCommandParser -> AddTimeCommand +activate AddTimeCommand + +AddTimeCommand --> AddTimeCommandParser : a +deactivate AddTimeCommand + +AddTimeCommandParser --> AddressBookParser : a +deactivate AddTimeCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar.' +AddTimeCommandParser -[hidden]-> AddressBookParser +destroy AddTimeCommandParser + +AddressBookParser --> LogicManager : a +deactivate AddressBookParser + +LogicManager -> AddTimeCommand : execute() +activate AddTimeCommand + +AddTimeCommand -> Model : addTimeToPerson(args) +activate Model + +Model --> AddTimeCommand +deactivate Model + +create CommandResult +AddTimeCommand -> CommandResult +activate CommandResult + +CommandResult --> AddTimeCommand +deactivate CommandResult + +AddTimeCommand --> LogicManager : result +deactivate AddTimeCommand + +AddTimeCommand -[hidden]-> AddressBookParser +destroy AddTimeCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml index 48b6cc4333c..55e1d600b17 100644 --- a/docs/diagrams/ArchitectureSequenceDiagram.puml +++ b/docs/diagrams/ArchitectureSequenceDiagram.puml @@ -8,13 +8,13 @@ Participant ":Logic" as logic LOGIC_COLOR Participant ":Model" as model MODEL_COLOR Participant ":Storage" as storage STORAGE_COLOR -user -[USER_COLOR]> ui : "delete 1" +user -[USER_COLOR]> ui : "delete n/Alex" activate ui UI_COLOR -ui -[UI_COLOR]> logic : execute("delete 1") +ui -[UI_COLOR]> logic : execute("delete n/Alex") activate logic LOGIC_COLOR -logic -[LOGIC_COLOR]> model : deletePerson(p) +logic -[LOGIC_COLOR]> model : deletePerson(Alex) activate model MODEL_COLOR model -[MODEL_COLOR]-> logic diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml deleted file mode 100644 index 598474a5c82..00000000000 --- a/docs/diagrams/BetterModelClassDiagram.puml +++ /dev/null @@ -1,21 +0,0 @@ -@startuml -!include style.puml -skinparam arrowThickness 1.1 -skinparam arrowColor MODEL_COLOR -skinparam classBackgroundColor MODEL_COLOR - -AddressBook *-right-> "1" UniquePersonList -AddressBook *-right-> "1" UniqueTagList -UniqueTagList -[hidden]down- UniquePersonList -UniqueTagList -[hidden]down- UniquePersonList - -UniqueTagList -right-> "*" Tag -UniquePersonList -right-> Person - -Person -up-> "*" Tag - -Person *--> Name -Person *--> Phone -Person *--> Email -Person *--> Address -@enduml diff --git a/docs/diagrams/ComponentManagers.puml b/docs/diagrams/ComponentManagers.puml index 564dd1ae32f..6111383e1e9 100644 --- a/docs/diagrams/ComponentManagers.puml +++ b/docs/diagrams/ComponentManagers.puml @@ -9,7 +9,7 @@ Class "<>\nLogic" as Logic Class LogicManager } -package Model as ModelPackage { +package ProjectPROModel as ModelPackage { Class "<>\nModel" as Model Class ModelManager } diff --git a/docs/diagrams/CreateGroupActivityDiagram.puml b/docs/diagrams/CreateGroupActivityDiagram.puml new file mode 100644 index 00000000000..dccc7a9ad55 --- /dev/null +++ b/docs/diagrams/CreateGroupActivityDiagram.puml @@ -0,0 +1,20 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:User wants to create a new group; +:User inputs "new g/GROUPNAME"; +:Check if one group specified; +if () then ([else]) +:ProjectPRO throws an error; +else ([One group specified]) +:Check if group exists; +if () then ([Group does not exist]) +:ProjectPro creates new group; +else([Else]) +:ProjectPRO throws an error; +endif +endif +stop + +@enduml diff --git a/docs/diagrams/CreateGroupSequenceDiagram.puml b/docs/diagrams/CreateGroupSequenceDiagram.puml new file mode 100644 index 00000000000..174dc4b4143 --- /dev/null +++ b/docs/diagrams/CreateGroupSequenceDiagram.puml @@ -0,0 +1,73 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":CreateGroupCommandParser" as CreateGroupCommandParser LOGIC_COLOR +participant ":CreateGroupCommand" as CreateGroupCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("new g/groupName") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("new g/groupName") +activate AddressBookParser + +create CreateGroupCommandParser +AddressBookParser -> CreateGroupCommandParser +activate CreateGroupCommandParser + +CreateGroupCommandParser --> AddressBookParser +deactivate CreateGroupCommandParser + +AddressBookParser -> CreateGroupCommandParser : parse("groupName") +activate CreateGroupCommandParser + +create CreateGroupCommand +CreateGroupCommandParser -> CreateGroupCommand +activate CreateGroupCommand + +CreateGroupCommand --> CreateGroupCommandParser : g +deactivate CreateGroupCommand + +CreateGroupCommandParser --> AddressBookParser : g +deactivate CreateGroupCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +CreateGroupCommandParser -[hidden]-> AddressBookParser +destroy CreateGroupCommandParser + +AddressBookParser --> LogicManager : g +deactivate AddressBookParser + +LogicManager -> CreateGroupCommand : execute() +activate CreateGroupCommand + +CreateGroupCommand -> Model : addGroup(groupName) +activate Model + +Model --> CreateGroupCommand +deactivate Model + +create CommandResult +CreateGroupCommand -> CommandResult +activate CommandResult + +CommandResult --> CreateGroupCommand +deactivate CommandResult + +CreateGroupCommand --> LogicManager : result +deactivate CreateGroupCommand + +CreateGroupCommand -[hidden]-> AddressBookParser +destroy CreateGroupCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteCommandActivityDiagram.puml b/docs/diagrams/DeleteCommandActivityDiagram.puml new file mode 100644 index 00000000000..5db42dfb78e --- /dev/null +++ b/docs/diagrams/DeleteCommandActivityDiagram.puml @@ -0,0 +1,29 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:User wants to delete a group or contact; +:User runs the "delete" command; +if () then ([Invalid command format]) +:ProjectPRO throws an error; +else (["delete g/GROUPNAME" or "delete n/NAME"]) +:ProjectPRO checks if User is deleting a group or contact; +if () then (["delete g/GROUPNAME"]) +:ProjectPRO checks if group exists; + if () then ([Group exists]) + : ProjectPRO deletes specified group; + else([Else]) + : ProjectPRO throws an error; + endif +else(["delete n/NAME"]) +: ProjectPRO checks if contact exists; + if () then ([Contact exists]) + : ProjectPRO deletes specified contact; + else([Else]) + : ProjectPRO throws an error; + endif +endif +endif +stop + +@enduml diff --git a/docs/diagrams/DeletePersonSequenceDiagram.puml b/docs/diagrams/DeletePersonSequenceDiagram.puml new file mode 100644 index 00000000000..d61b92b6bd8 --- /dev/null +++ b/docs/diagrams/DeletePersonSequenceDiagram.puml @@ -0,0 +1,87 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR +participant "d:DeletePersonCommand" as DeletePersonCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":AddressBook" as AddressBook MODEL_COLOR +participant ":Group" as Group MODEL_COLOR +end box + +[-> LogicManager : execute("delete n/Alex Yeoh") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("delete n/Alex Yeoh") +activate AddressBookParser + +create DeleteCommandParser +AddressBookParser -> DeleteCommandParser +activate DeleteCommandParser + +DeleteCommandParser --> AddressBookParser +deactivate DeleteCommandParser + +AddressBookParser -> DeleteCommandParser : parse("n/Alex Yeoh") +activate DeleteCommandParser + +create DeletePersonCommand +DeleteCommandParser -> DeletePersonCommand +activate DeletePersonCommand + +DeletePersonCommand --> DeleteCommandParser : d +deactivate DeletePersonCommand + +DeleteCommandParser --> AddressBookParser : d +deactivate DeleteCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteCommandParser -[hidden]-> AddressBookParser +destroy DeleteCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> DeletePersonCommand : execute() +activate DeletePersonCommand + +DeletePersonCommand -> Model : deletePerson("Alex Yeoh") +activate Model + +Model -> AddressBook : removePerson(Alex Yeoh) +activate AddressBook + +AddressBook -> Group : removePerson(Alex Yeoh) +activate Group + +Group --> AddressBook +deactivate Group + +AddressBook --> Model +deactivate AddressBook + +Model --> DeletePersonCommand +deactivate Model + +create CommandResult +DeletePersonCommand -> CommandResult +activate CommandResult + +CommandResult --> DeletePersonCommand +deactivate CommandResult + +DeletePersonCommand --> LogicManager : result +deactivate DeletePersonCommand + +DeletePersonCommand -[hidden]-> AddressBookParser +destroy DeletePersonCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeletePersonTimeActivityDiagram.puml b/docs/diagrams/DeletePersonTimeActivityDiagram.puml new file mode 100644 index 00000000000..97a234104da --- /dev/null +++ b/docs/diagrams/DeletePersonTimeActivityDiagram.puml @@ -0,0 +1,25 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:Start Delete Time from Person; +:Check if one person specified; +if () then ([One person specified]) + :Check if person exists; + if () then ([Person exists]) + :Check if time exists; + if () then ([Time exists]) + :ProjectPRO deletes time from person; + else ([else]) + :ProjectPRO throws Error; + endif + else ([else]) + :ProjectPRO throws Error; + endif +else ([else]) + :ProjectPRO throws Error; +endif + +stop + +@enduml diff --git a/docs/diagrams/DeletePersonTimeSequenceDiagram.puml b/docs/diagrams/DeletePersonTimeSequenceDiagram.puml new file mode 100644 index 00000000000..1efb31234ff --- /dev/null +++ b/docs/diagrams/DeletePersonTimeSequenceDiagram.puml @@ -0,0 +1,73 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":DeleteTimeCommandParser" as DeleteTimeCommandParser LOGIC_COLOR +participant "d:DeleteTimeCommand" as DeleteTimeCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("deleteTime n/Alex Yeoh t/mon 1200 - mon 1400") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("deleteTime n/Alex Yeoh t/mon 1200 - mon 1400") +activate AddressBookParser + +create DeleteTimeCommandParser +AddressBookParser -> DeleteTimeCommandParser +activate DeleteTimeCommandParser + +DeleteTimeCommandParser --> AddressBookParser +deactivate DeleteTimeCommandParser + +AddressBookParser -> DeleteTimeCommandParser : parse("n/Alex Yeoh t/mon 1200 - mon 1400") +activate DeleteTimeCommandParser + +create DeleteTimeCommand +DeleteTimeCommandParser -> DeleteTimeCommand +activate DeleteTimeCommand + +DeleteTimeCommand --> DeleteTimeCommandParser : d +deactivate DeleteTimeCommand + +DeleteTimeCommandParser --> AddressBookParser : d +deactivate DeleteTimeCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +DeleteTimeCommandParser -[hidden]-> AddressBookParser +destroy DeleteTimeCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> DeleteTimeCommand : execute() +activate DeleteTimeCommand + +DeleteTimeCommand -> Model : deleteTimeFromPerson() +activate Model + +Model --> DeleteTimeCommand +deactivate Model + +create CommandResult +DeleteTimeCommand -> CommandResult +activate CommandResult + +CommandResult --> DeleteTimeCommand +deactivate CommandResult + +DeleteTimeCommand --> LogicManager : result +deactivate DeleteTimeCommand + +DeleteTimeCommand -[hidden]-> AddressBookParser +destroy DeleteTimeCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml deleted file mode 100644 index 40ea6c9dc4c..00000000000 --- a/docs/diagrams/DeleteSequenceDiagram.puml +++ /dev/null @@ -1,70 +0,0 @@ -@startuml -!include style.puml -skinparam ArrowFontStyle plain - -box Logic LOGIC_COLOR_T1 -participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant ":DeleteCommandParser" as DeleteCommandParser LOGIC_COLOR -participant "d:DeleteCommand" as DeleteCommand LOGIC_COLOR -participant ":CommandResult" as CommandResult LOGIC_COLOR -end box - -box Model MODEL_COLOR_T1 -participant ":Model" as Model MODEL_COLOR -end box - -[-> LogicManager : execute("delete 1") -activate LogicManager - -LogicManager -> AddressBookParser : parseCommand("delete 1") -activate AddressBookParser - -create DeleteCommandParser -AddressBookParser -> DeleteCommandParser -activate DeleteCommandParser - -DeleteCommandParser --> AddressBookParser -deactivate DeleteCommandParser - -AddressBookParser -> DeleteCommandParser : parse("1") -activate DeleteCommandParser - -create DeleteCommand -DeleteCommandParser -> DeleteCommand -activate DeleteCommand - -DeleteCommand --> DeleteCommandParser : d -deactivate DeleteCommand - -DeleteCommandParser --> AddressBookParser : d -deactivate DeleteCommandParser -'Hidden arrow to position the destroy marker below the end of the activation bar. -DeleteCommandParser -[hidden]-> AddressBookParser -destroy DeleteCommandParser - -AddressBookParser --> LogicManager : d -deactivate AddressBookParser - -LogicManager -> DeleteCommand : execute() -activate DeleteCommand - -DeleteCommand -> Model : deletePerson(1) -activate Model - -Model --> DeleteCommand -deactivate Model - -create CommandResult -DeleteCommand -> CommandResult -activate CommandResult - -CommandResult --> DeleteCommand -deactivate CommandResult - -DeleteCommand --> LogicManager : result -deactivate DeleteCommand - -[<--LogicManager -deactivate LogicManager -@enduml diff --git a/docs/diagrams/FindCommandActivityDiagram.puml b/docs/diagrams/FindCommandActivityDiagram.puml new file mode 100644 index 00000000000..e12b8f5a25d --- /dev/null +++ b/docs/diagrams/FindCommandActivityDiagram.puml @@ -0,0 +1,26 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:User wants to filter their contact list; +:User runs the "find" command; +if () then ([Invalid command format]) +:ProjectPro throws an error; +else (["find g/GROUPNAME" or "find n/KEYWORDS"]) +:ProjectPRO checks if User is filtering by keywords in name or group; +if () then (["find g/GROUPNAME"]) +:ProjectPRO checks if group exists; + if () then ([Group exists]) + : ProjectPRO displays list of contacts + in specified group; + else([Else]) + : ProjectPRO throws an error; + endif +else(["find n/KEYWORDS"]) +: ProjectPRO displays list of contacts whose + name contains one of the KEYWORDS; +endif +endif +stop + +@enduml diff --git a/docs/diagrams/FindFreeTimeActivityDiagram.puml b/docs/diagrams/FindFreeTimeActivityDiagram.puml new file mode 100644 index 00000000000..e8942f641ef --- /dev/null +++ b/docs/diagrams/FindFreeTimeActivityDiagram.puml @@ -0,0 +1,29 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:Start FindFreeTime of Group 2103 for duration 60 minutes; +:Check if duration is valid; +if () then ([Duration valid]) + :Check if group exists; + if () then ([Group exists]) + :Check that all group members have free time slot; + if () then ([All Members have time slot keyed in]) + :Find free time of members in Group 2103; + if () then ([Members common time slot allow 60 minutes]) + :Add common timeslot to list; + else ([else]) + :Don't add common timeslot to list; + endif + else ([else]) + :ProjectPRO throws Error; + endif + else ([else]) + :ProjectPRO throws Error; + endif +else ([else]) + :ProjectPRO throws Error; +endif +stop + +@enduml diff --git a/docs/diagrams/FindFreeTimeSequenceDiagram.puml b/docs/diagrams/FindFreeTimeSequenceDiagram.puml new file mode 100644 index 00000000000..16ff7491b5f --- /dev/null +++ b/docs/diagrams/FindFreeTimeSequenceDiagram.puml @@ -0,0 +1,94 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FindFreeTimeParser" as FindFreeTimeParser LOGIC_COLOR +participant "a:FindFreeTimeCommand" as FindFreeTimeCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":AddressBook" as AddressBook MODEL_COLOR +participant ":Group" as Group MODEL_COLOR +end box + +[-> LogicManager : execute(findfreetime g/CS2100 d/60) +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand(findfreetime g/CS2100 d/60 ...) +activate AddressBookParser + +create FindFreeTimeParser +AddressBookParser -> FindFreeTimeParser +activate FindFreeTimeParser + +FindFreeTimeParser --> AddressBookParser +deactivate FindFreeTimeParser + + +AddressBookParser -> FindFreeTimeParser: parse("n/personName g/groupName") +activate FindFreeTimeParser + +create FindFreeTimeCommand +FindFreeTimeParser -> FindFreeTimeCommand +activate FindFreeTimeCommand + +FindFreeTimeCommand --> FindFreeTimeParser : f +deactivate FindFreeTimeCommand + +FindFreeTimeParser --> AddressBookParser : f +deactivate FindFreeTimeParser + +FindFreeTimeParser-[hidden]-> AddressBookParser +destroy FindFreeTimeParser + +AddressBookParser -> LogicManager : f +deactivate AddressBookParser + + +LogicManager -> FindFreeTimeCommand : execute() +activate FindFreeTimeCommand + +FindFreeTimeCommand -> Model : findGroup("CS2100") +activate Model + +Model -> AddressBook : findGroup("CS2100") +activate AddressBook + +AddressBook --> Model : g +deactivate AddressBook + +Model -> Group : areallfree(...) +activate Group +Group --> Model +deactivate Group + +Model -> Group :findfreetime(..) +activate Group +Group --> Model : TimeIntervalList t +deactivate Group + +Model -> FindFreeTimeCommand : TimeIntervalList t +deactivate Model + +create CommandResult +FindFreeTimeCommand -> CommandResult +activate CommandResult + +CommandResult --> FindFreeTimeCommand +deactivate CommandResult + +FindFreeTimeCommand --> LogicManager +deactivate FindFreeTimeCommand + +FindFreeTimeCommand-[hidden]-> AddressBookParser +destroy FindFreeTimeCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/FindPersonSequenceDiagram.puml b/docs/diagrams/FindPersonSequenceDiagram.puml new file mode 100644 index 00000000000..de0d451ae0b --- /dev/null +++ b/docs/diagrams/FindPersonSequenceDiagram.puml @@ -0,0 +1,73 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR +participant "d:FindPersonCommand" as FindPersonCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("find n/Alex John") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("find n/Alex John") +activate AddressBookParser + +create FindCommandParser +AddressBookParser -> FindCommandParser +activate FindCommandParser + +FindCommandParser --> AddressBookParser +deactivate FindCommandParser + +AddressBookParser -> FindCommandParser : parse("n/Alex John") +activate FindCommandParser + +create FindPersonCommand +FindCommandParser -> FindPersonCommand +activate FindPersonCommand + +FindPersonCommand --> FindCommandParser : d +deactivate FindPersonCommand + +FindCommandParser --> AddressBookParser : d +deactivate FindCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +FindCommandParser -[hidden]-> AddressBookParser +destroy FindCommandParser + +AddressBookParser --> LogicManager : d +deactivate AddressBookParser + +LogicManager -> FindPersonCommand : execute() +activate FindPersonCommand + +FindPersonCommand -> Model : updateFilteredPersonList(CORRESPONDING_PREDICATE) +activate Model + +Model --> FindPersonCommand +deactivate Model + +create CommandResult +FindPersonCommand -> CommandResult +activate CommandResult + +CommandResult --> FindPersonCommand +deactivate CommandResult + +FindPersonCommand --> LogicManager : result +deactivate FindPersonCommand + +FindPersonCommand -[hidden]-> AddressBookParser +destroy FindPersonCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/GroupPersonActivityDiagram.puml b/docs/diagrams/GroupPersonActivityDiagram.puml new file mode 100644 index 00000000000..ac21b88d866 --- /dev/null +++ b/docs/diagrams/GroupPersonActivityDiagram.puml @@ -0,0 +1,30 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:Start Add Person to Group; +:Check if person exists; +if () then ([Person exists]) + :Check if group exists; + if () then ([Group exists]) + :Check if person is in group's list of members; + if () then ([Person is in group]) + :Check if group is in person's list of groups; + if () then ([Group is in person]) + :Add person to group; + else ([else]) + :ProjectPRO throws Error; + endif + else ([else]) + :ProjectPRO throws Error; + endif + else ([else]) + :ProjectPRO throws Error; + endif +else ([else]) + :ProjectPRO throws Error; +endif + +stop + +@enduml diff --git a/docs/diagrams/GroupPersonSequenceDiagram.puml b/docs/diagrams/GroupPersonSequenceDiagram.puml new file mode 100644 index 00000000000..1ce0ebc31eb --- /dev/null +++ b/docs/diagrams/GroupPersonSequenceDiagram.puml @@ -0,0 +1,84 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":GroupPersonCommandParser" as GroupPersonCommandParser LOGIC_COLOR +participant ":GroupPersonCommand" as GroupPersonCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +participant ":AddressBook" as AddressBook MODEL_COLOR +end box + + +[-> LogicManager : execute("group n/personName g/groupName") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("group n/personName g/groupName") +activate AddressBookParser + +create GroupPersonCommandParser +AddressBookParser -> GroupPersonCommandParser +activate GroupPersonCommandParser + +GroupPersonCommandParser --> AddressBookParser +deactivate GroupPersonCommandParser + +AddressBookParser -> GroupPersonCommandParser : parse("n/personName g/groupName") +activate GroupPersonCommandParser + +create GroupPersonCommand +GroupPersonCommandParser -> GroupPersonCommand +activate GroupPersonCommand + +GroupPersonCommand --> GroupPersonCommandParser : g +deactivate GroupPersonCommand + +GroupPersonCommandParser --> AddressBookParser : g +deactivate GroupPersonCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +GroupPersonCommandParser -[hidden]-> AddressBookParser +destroy GroupPersonCommandParser + +AddressBookParser --> LogicManager : g +deactivate AddressBookParser + +LogicManager -> GroupPersonCommand : execute() +activate GroupPersonCommand + +GroupPersonCommand -> Model : groupPerson(personName, groupName) +activate Model + +Model -> AddressBook : getPerson(personName) +activate AddressBook +AddressBook --> Model : p +Model -> AddressBook : getGroup(groupName) +AddressBook --> Model : g +deactivate AddressBook +Model -> Model : assignGroup(p, g) +Model --> GroupPersonCommand +deactivate Model + +create CommandResult +GroupPersonCommand -> CommandResult +activate CommandResult + + +CommandResult --> GroupPersonCommand +deactivate CommandResult + +GroupPersonCommand --> LogicManager + +deactivate GroupPersonCommand + +GroupPersonCommand -[hidden]-> AddressBookParser +destroy GroupPersonCommand + +[<--LogicManager +deactivate LogicManager +@enduml diff --git a/docs/diagrams/GroupRemarkSequenceDiagram.puml b/docs/diagrams/GroupRemarkSequenceDiagram.puml new file mode 100644 index 00000000000..3c87c6edaa6 --- /dev/null +++ b/docs/diagrams/GroupRemarkSequenceDiagram.puml @@ -0,0 +1,87 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":GroupRemarkCommandParser" as GroupRemarkCommandParser LOGIC_COLOR +participant ":GroupRemarkCommand" as GroupRemarkCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +[-> LogicManager: execute("remark g/CS2103T r/Quiz tomorrow") +activate LogicManager + +LogicManager -> AddressBookParser: parseCommand "remark g/CS2103T r/Quiz tomorrow" +activate AddressBookParser + +create GroupRemarkCommandParser +AddressBookParser -> GroupRemarkCommandParser +activate GroupRemarkCommandParser + +GroupRemarkCommandParser --> AddressBookParser +deactivate GroupRemarkCommandParser + +AddressBookParser -> GroupRemarkCommandParser: parse g/CS2103T r/Quiz tomorrow +activate GroupRemarkCommandParser + +create GroupRemarkCommand +GroupRemarkCommandParser -> GroupRemarkCommand +activate GroupRemarkCommand + +GroupRemarkCommand --> GroupRemarkCommandParser: r +deactivate GroupRemarkCommand + +GroupRemarkCommandParser --> AddressBookParser: r +deactivate GroupRemarkCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +GroupRemarkCommandParser -[hidden]-> AddressBookParser +destroy GroupRemarkCommandParser + +AddressBookParser --> LogicManager: r +deactivate AddressBookParser + +LogicManager -> GroupRemarkCommand: execute() +activate GroupRemarkCommand + +GroupRemarkCommand -> Model: addGroupRemark(groupName, groupRemark) +activate Model + +'Model -> AddressBook: getGroup(groupName) +'activate AddressBook +' +'AddressBook --> Model: g +'deactivate AddressBook + +'Model -> Group: setGroupRemark(groupRemark) +'activate Group +'deactivate Group + +Model --> GroupRemarkCommand: g +deactivate Model + +create CommandResult +GroupRemarkCommand -> CommandResult +activate CommandResult + +CommandResult --> GroupRemarkCommand +deactivate CommandResult + + + +GroupRemarkCommand --> LogicManager: result +deactivate GroupRemarkCommand + +GroupRemarkCommand -[hidden]-> AddressBookParser +destroy GroupRemarkCommand + +[<-- LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/ListCommandSequenceDiagram.puml b/docs/diagrams/ListCommandSequenceDiagram.puml new file mode 100644 index 00000000000..1f39a2b3e59 --- /dev/null +++ b/docs/diagrams/ListCommandSequenceDiagram.puml @@ -0,0 +1,79 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ListCommandParser" as ListCommandParser LOGIC_COLOR +participant "c:ListCommand" as ListCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + + +[-> LogicManager : execute("list") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("list") +activate AddressBookParser + +create ListCommandParser +AddressBookParser -> ListCommandParser +activate ListCommandParser + +ListCommandParser --> AddressBookParser +deactivate ListCommandParser + +AddressBookParser -> ListCommandParser : parse("list") +activate ListCommandParser + +create ListCommand +ListCommandParser -> ListCommand +activate ListCommand + +ListCommand --> ListCommandParser : c +deactivate ListCommand + +ListCommandParser --> AddressBookParser : c +deactivate ListCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +ListCommandParser -[hidden]-> AddressBookParser +destroy ListCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> ListCommand : execute() +activate ListCommand + + +ListCommand -> Model : updateFilteredPersonList(PREDICATE ...) +activate Model + + +Model --> ListCommand +deactivate Model + +create CommandResult +ListCommand -> CommandResult +activate CommandResult + +CommandResult --> ListCommand +deactivate CommandResult + +ListCommand --> LogicManager : result +deactivate ListCommand + +ListCommand -[hidden]-> AddressBookParser +destroy ListCommand + + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/ListGroupSequenceDiagram.puml b/docs/diagrams/ListGroupSequenceDiagram.puml new file mode 100644 index 00000000000..b8ebb65a698 --- /dev/null +++ b/docs/diagrams/ListGroupSequenceDiagram.puml @@ -0,0 +1,75 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ListGroupCommandParser" as ListGroupCommandParser LOGIC_COLOR +participant "c:ListGroupCommand" as ListGroupCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +[-> LogicManager : execute("listgroup") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("listgroup") +activate AddressBookParser + +create ListGroupCommandParser +AddressBookParser -> ListGroupCommandParser +activate ListGroupCommandParser + +ListGroupCommandParser --> AddressBookParser +deactivate ListGroupCommandParser + +AddressBookParser -> ListGroupCommandParser : parse("listgroup") +activate ListGroupCommandParser + +create ListGroupCommand +ListGroupCommandParser -> ListGroupCommand +activate ListGroupCommand + +ListGroupCommand --> ListGroupCommandParser : c +deactivate ListGroupCommand + +ListGroupCommandParser --> AddressBookParser : c +deactivate ListGroupCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +ListGroupCommandParser -[hidden]-> AddressBookParser +destroy ListGroupCommandParser + +AddressBookParser --> LogicManager : c +deactivate AddressBookParser + +LogicManager -> ListGroupCommand : execute() +activate ListGroupCommand + +ListGroupCommand -> Model : getFilteredGroupList() +activate Model + +Model --> ListGroupCommand +deactivate Model + +create CommandResult +ListGroupCommand -> CommandResult +activate CommandResult + +CommandResult --> ListGroupCommand +deactivate CommandResult + +ListGroupCommand --> LogicManager : result +deactivate ListGroupCommand + +ListGroupCommand -[hidden]->AddressBookParser +destroy ListGroupCommand + +[<--LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/ListTimeActivityDiagram.puml b/docs/diagrams/ListTimeActivityDiagram.puml new file mode 100644 index 00000000000..0db8257dedd --- /dev/null +++ b/docs/diagrams/ListTimeActivityDiagram.puml @@ -0,0 +1,29 @@ +@startuml +'https://plantuml.com/activity-diagram-beta + +start +:User wants to list the times of a group or contact; +:User runs the "listtime" command; +if () then ([Invalid command format]) +:ProjectPRO throws an error; +else (["listtime g/GROUPNAME" or "listtime n/NAME"]) +:ProjectPRO checks if User is listing time of a group or contact; +if () then (["listtime g/GROUPNAME"]) +:ProjectPRO checks if group exists; + if () then ([Group exists]) + : ProjectPRO lists times of specified group; + else([Else]) + : ProjectPRO throws an error; + endif +else(["listtime n/NAME"]) +: ProjectPRO checks if contact exists; + if () then ([Contact exists]) + : ProjectPRO lists times of specified contact; + else([Else]) + : ProjectPRO throws an error; + endif +endif +endif +stop + +@enduml diff --git a/docs/diagrams/ListTimePersonSequenceDiagram.puml b/docs/diagrams/ListTimePersonSequenceDiagram.puml new file mode 100644 index 00000000000..ea6f7762268 --- /dev/null +++ b/docs/diagrams/ListTimePersonSequenceDiagram.puml @@ -0,0 +1,75 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":ListTimeCommandParser" as ListTimeCommandParser LOGIC_COLOR +participant ":ListTimePersonCommand" as ListTimePersonCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +[-> LogicManager: execute("listtime n/Alex Yeoh") +activate LogicManager + +LogicManager -> AddressBookParser: parseCommand "listtime n/Alex Yeoh" +activate AddressBookParser + +create ListTimeCommandParser +AddressBookParser -> ListTimeCommandParser +activate ListTimeCommandParser + +ListTimeCommandParser --> AddressBookParser +deactivate ListTimeCommandParser + +AddressBookParser -> ListTimeCommandParser: parse n/Alex Yeoh +activate ListTimeCommandParser + +create ListTimePersonCommand +ListTimeCommandParser -> ListTimePersonCommand +activate ListTimePersonCommand + +ListTimePersonCommand --> ListTimeCommandParser: l +deactivate ListTimePersonCommand + +ListTimeCommandParser --> AddressBookParser: l +deactivate ListTimeCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +ListTimeCommandParser -[hidden]-> AddressBookParser +destroy ListTimeCommandParser + +AddressBookParser --> LogicManager: l +deactivate AddressBookParser + +LogicManager -> ListTimePersonCommand: execute() +activate ListTimePersonCommand + +ListTimePersonCommand -> Model: getTimeFromPerson(personName) +activate Model + +Model --> ListTimePersonCommand +deactivate Model + +create CommandResult +ListTimePersonCommand -> CommandResult +activate CommandResult + +CommandResult --> ListTimePersonCommand +deactivate CommandResult + +ListTimePersonCommand --> LogicManager: result +deactivate ListTimePersonCommand + +ListTimePersonCommand -[hidden]->AddressBookParser +destroy ListTimePersonCommand + +[<-- LogicManager +deactivate LogicManager + +@enduml diff --git a/docs/diagrams/ModelClassDiagram.puml b/docs/diagrams/ModelClassDiagram.puml index 0de5673070d..3e0233d4449 100644 --- a/docs/diagrams/ModelClassDiagram.puml +++ b/docs/diagrams/ModelClassDiagram.puml @@ -14,12 +14,15 @@ Class UserPrefs Class UniquePersonList Class Person -Class Address Class Email +Class Group Class Name Class Phone -Class Tag - +Class GroupList +class TimeIntervalList +class TimeInterval +class Time +class GroupRemark Class I #FFFFFF } @@ -37,18 +40,33 @@ UserPrefs .up.|> ReadOnlyUserPrefs AddressBook *--> "1" UniquePersonList UniquePersonList --> "~* all" Person + +AddressBook *--> "1" GroupList +GroupList --> "*" Group + Person *--> Name Person *--> Phone +Person *--> "1" GroupList Person *--> Email -Person *--> Address -Person *--> "*" Tag +Person *--> "1" TimeIntervalList + +Group *--> TimeIntervalList +Group -->"*" Person +Group *--> GroupRemark + +TimeIntervalList --> "*" TimeInterval +TimeInterval *--> Time Person -[hidden]up--> I UniquePersonList -[hidden]right-> I Name -[hidden]right-> Phone -Phone -[hidden]right-> Address -Address -[hidden]right-> Email +Phone -[hidden]right-> Email + + ModelManager --> "~* filtered" Person +ModelManager --> "~* filtered" Group + + @enduml diff --git a/docs/diagrams/StorageClassDiagram.puml b/docs/diagrams/StorageClassDiagram.puml index a821e06458c..20be1f136fe 100644 --- a/docs/diagrams/StorageClassDiagram.puml +++ b/docs/diagrams/StorageClassDiagram.puml @@ -19,7 +19,9 @@ Class "<>\nAddressBookStorage" as AddressBookStorage Class JsonAddressBookStorage Class JsonSerializableAddressBook Class JsonAdaptedPerson -Class JsonAdaptedTag +Class JsonAdaptedGroup +Class JsonAdaptedTime +Class GroupRemark } } @@ -37,7 +39,12 @@ Storage -right-|> AddressBookStorage JsonUserPrefsStorage .up.|> UserPrefsStorage JsonAddressBookStorage .up.|> AddressBookStorage JsonAddressBookStorage ..> JsonSerializableAddressBook +JsonSerializableAddressBook --> "*" JsonAdaptedGroup JsonSerializableAddressBook --> "*" JsonAdaptedPerson -JsonAdaptedPerson --> "*" JsonAdaptedTag +JsonAdaptedPerson --> "*" JsonAdaptedTime +JsonAdaptedGroup --> "*" JsonAdaptedTime +JsonAdaptedGroup --> "*" GroupRemark +JsonAdaptedGroup --> "*" JsonAdaptedPerson +JsonAdaptedPerson --> "*" JsonAdaptedGroup @enduml diff --git a/docs/diagrams/UiClassDiagram.puml b/docs/diagrams/UiClassDiagram.puml index 95473d5aa19..31f5b486f50 100644 --- a/docs/diagrams/UiClassDiagram.puml +++ b/docs/diagrams/UiClassDiagram.puml @@ -15,6 +15,10 @@ Class PersonListPanel Class PersonCard Class StatusBarFooter Class CommandBox +Class Calendar +Class DayCard +Class EachDayTaskLine +Class GroupTimeContainer } package Model <> { @@ -34,10 +38,17 @@ MainWindow *-down-> "1" CommandBox MainWindow *-down-> "1" ResultDisplay MainWindow *-down-> "1" PersonListPanel MainWindow *-down-> "1" StatusBarFooter +MainWindow *-down-> "1" Calendar MainWindow --> "0..1" HelpWindow PersonListPanel -down-> "*" PersonCard +Calendar *-down-> "7" DayCard +Calendar *-> GroupTimeContainer + +DayCard *-down-> "*" EachDayTaskLine +DayCard -left-> GroupTimeContainer + MainWindow -left-|> UiPart ResultDisplay --|> UiPart @@ -46,8 +57,12 @@ PersonListPanel --|> UiPart PersonCard --|> UiPart StatusBarFooter --|> UiPart HelpWindow --|> UiPart +Calendar --|> UiPart +DayCard --|> UiPart +EachDayTaskLine --|> UiPart PersonCard ..> Model +Calendar ..> Model UiManager -right-> Logic MainWindow -left-> Logic diff --git a/docs/diagrams/UndoRedoState0.puml b/docs/diagrams/UndoRedoState0.puml deleted file mode 100644 index 43a45903ac9..00000000000 --- a/docs/diagrams/UndoRedoState0.puml +++ /dev/null @@ -1,21 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 -skinparam ClassBackgroundColor #FFFFAA - -title Initial state - -package States { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" -} -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 -hide State2 -hide State3 - -class Pointer as "Current State" #FFFFFF -Pointer -up-> State1 -@end diff --git a/docs/diagrams/UndoRedoState1.puml b/docs/diagrams/UndoRedoState1.puml deleted file mode 100644 index 5a41e9e1651..00000000000 --- a/docs/diagrams/UndoRedoState1.puml +++ /dev/null @@ -1,23 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 -skinparam ClassBackgroundColor #FFFFAA - -title After command "delete 5" - -package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" -} - -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 - -hide State3 - -class Pointer as "Current State" #FFFFFF - -Pointer -up-> State2 -@end diff --git a/docs/diagrams/UndoRedoState2.puml b/docs/diagrams/UndoRedoState2.puml deleted file mode 100644 index ad32fce1b0b..00000000000 --- a/docs/diagrams/UndoRedoState2.puml +++ /dev/null @@ -1,21 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 -skinparam ClassBackgroundColor #FFFFAA - -title After command "add n/David" - -package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" -} - -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 - -class Pointer as "Current State" #FFFFFF - -Pointer -up-> State3 -@end diff --git a/docs/diagrams/UndoRedoState3.puml b/docs/diagrams/UndoRedoState3.puml deleted file mode 100644 index 9187a690036..00000000000 --- a/docs/diagrams/UndoRedoState3.puml +++ /dev/null @@ -1,21 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 -skinparam ClassBackgroundColor #FFFFAA - -title After command "undo" - -package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" -} - -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 - -class Pointer as "Current State" #FFFFFF - -Pointer -up-> State2 -@end diff --git a/docs/diagrams/UndoRedoState4.puml b/docs/diagrams/UndoRedoState4.puml deleted file mode 100644 index 2bc631ffcd0..00000000000 --- a/docs/diagrams/UndoRedoState4.puml +++ /dev/null @@ -1,21 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 -skinparam ClassBackgroundColor #FFFFAA - -title After command "list" - -package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab2:AddressBook" -} - -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 - -class Pointer as "Current State" #FFFFFF - -Pointer -up-> State2 -@end diff --git a/docs/diagrams/UndoRedoState5.puml b/docs/diagrams/UndoRedoState5.puml deleted file mode 100644 index e77b04104aa..00000000000 --- a/docs/diagrams/UndoRedoState5.puml +++ /dev/null @@ -1,22 +0,0 @@ -@startuml -!include style.puml -skinparam ClassFontColor #000000 -skinparam ClassBorderColor #000000 -skinparam ClassBackgroundColor #FFFFAA - -title After command "clear" - -package States <> { - class State1 as "ab0:AddressBook" - class State2 as "ab1:AddressBook" - class State3 as "ab3:AddressBook" -} - -State1 -[hidden]right-> State2 -State2 -[hidden]right-> State3 - -class Pointer as "Current State" #FFFFFF - -Pointer -up-> State3 -note right on link: State ab2 deleted. -@end diff --git a/docs/diagrams/UndoSequenceDiagram.puml b/docs/diagrams/UndoSequenceDiagram.puml deleted file mode 100644 index 87ff3e9237e..00000000000 --- a/docs/diagrams/UndoSequenceDiagram.puml +++ /dev/null @@ -1,54 +0,0 @@ -@startuml -!include style.puml -skinparam ArrowFontStyle plain - -box Logic LOGIC_COLOR_T1 -participant ":LogicManager" as LogicManager LOGIC_COLOR -participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR -participant "u:UndoCommand" as UndoCommand LOGIC_COLOR -end box - -box Model MODEL_COLOR_T1 -participant ":Model" as Model MODEL_COLOR -participant ":VersionedAddressBook" as VersionedAddressBook MODEL_COLOR -end box -[-> LogicManager : execute(undo) -activate LogicManager - -LogicManager -> AddressBookParser : parseCommand(undo) -activate AddressBookParser - -create UndoCommand -AddressBookParser -> UndoCommand -activate UndoCommand - -UndoCommand --> AddressBookParser -deactivate UndoCommand - -AddressBookParser --> LogicManager : u -deactivate AddressBookParser - -LogicManager -> UndoCommand : execute() -activate UndoCommand - -UndoCommand -> Model : undoAddressBook() -activate Model - -Model -> VersionedAddressBook : undo() -activate VersionedAddressBook - -VersionedAddressBook -> VersionedAddressBook :resetData(ReadOnlyAddressBook) -VersionedAddressBook --> Model : -deactivate VersionedAddressBook - -Model --> UndoCommand -deactivate Model - -UndoCommand --> LogicManager : result -deactivate UndoCommand -UndoCommand -[hidden]-> LogicManager : result -destroy UndoCommand - -[<--LogicManager -deactivate LogicManager -@enduml diff --git a/docs/diagrams/UngroupPersonSequenceDiagram.puml b/docs/diagrams/UngroupPersonSequenceDiagram.puml new file mode 100644 index 00000000000..957a7ed0f08 --- /dev/null +++ b/docs/diagrams/UngroupPersonSequenceDiagram.puml @@ -0,0 +1,79 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain + +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR +participant ":UngroupPersonCommandParser" as UngroupPersonCommandParser LOGIC_COLOR +participant ":UngroupPersonCommand" as UngroupPersonCommand LOGIC_COLOR +participant ":CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant ":Model" as Model MODEL_COLOR +end box + + +[-> LogicManager : execute("ungroup n/personName g/groupName") +activate LogicManager + +LogicManager -> AddressBookParser : parseCommand("ungroup n/personName g/groupName") +activate AddressBookParser + +create UngroupPersonCommandParser +AddressBookParser -> UngroupPersonCommandParser +activate UngroupPersonCommandParser + +UngroupPersonCommandParser --> AddressBookParser +deactivate UngroupPersonCommandParser + +AddressBookParser -> UngroupPersonCommandParser : parse("n/personName g/groupName") +activate UngroupPersonCommandParser + +create UngroupPersonCommand +UngroupPersonCommandParser -> UngroupPersonCommand +activate UngroupPersonCommand + +UngroupPersonCommand --> UngroupPersonCommandParser : g +deactivate UngroupPersonCommand + +UngroupPersonCommandParser --> AddressBookParser : g +deactivate UngroupPersonCommandParser +'Hidden arrow to position the destroy marker below the end of the activation bar. +UngroupPersonCommandParser -[hidden]-> AddressBookParser +destroy UngroupPersonCommandParser + +AddressBookParser --> LogicManager : g +deactivate AddressBookParser + +LogicManager -> UngroupPersonCommand : execute() +activate UngroupPersonCommand + +UngroupPersonCommand -> Model : ungroupPerson(personName, groupName) +activate Model + + +Model -> Model : ungroupPerson(personName, groupName) +Model --> UngroupPersonCommand +deactivate Model + +create CommandResult +UngroupPersonCommand -> CommandResult +activate CommandResult + + +CommandResult --> UngroupPersonCommand +deactivate CommandResult +UngroupPersonCommand --> LogicManager + +deactivate UngroupPersonCommand + +UngroupPersonCommand -[hidden]-> AddressBookParser +destroy UngroupPersonCommand + +[<--LogicManager +deactivate LogicManager + + +@enduml diff --git a/docs/diagrams/add-remark/ParserClass.puml b/docs/diagrams/add-remark/ParserClass.puml deleted file mode 100644 index 24d390a4023..00000000000 --- a/docs/diagrams/add-remark/ParserClass.puml +++ /dev/null @@ -1,14 +0,0 @@ -@startuml -hide circle -skinparam classAttributeIconSize 0 - -Class "<>\nParser" as Parser -Class RemarkCommandParser { - +parse(): RemarkCommand -} -Class ParserException - -RemarkCommandParser .up.|> Parser -Parser .right.> ParserException: throws > -RemarkCommandParser .right.> ParserException: throws > -@enduml diff --git a/docs/diagrams/add-remark/RemarkClass.puml b/docs/diagrams/add-remark/RemarkClass.puml deleted file mode 100644 index 019c1ecbbf1..00000000000 --- a/docs/diagrams/add-remark/RemarkClass.puml +++ /dev/null @@ -1,19 +0,0 @@ -@startuml -hide circle -skinparam classAttributeIconSize 0 - -Class "{abstract}\nCommand" as Command { - +execute(Model): CommandResult -} -Class RemarkCommand { - +COMMAND_WORD: String - +MESSAGE_USAGE: String - +MESSAGE_NOT_IMPLEMENTED_YET: String - +execute(Model): CommandResult -} -Class CommandException - -RemarkCommand -up-|> Command -Command ..> CommandException: throws > -RemarkCommand .right.> CommandException: throws > -@enduml diff --git a/docs/icons/checked.png b/docs/icons/checked.png new file mode 100644 index 00000000000..deea08d9c68 Binary files /dev/null and b/docs/icons/checked.png differ diff --git a/docs/icons/exclamation.png b/docs/icons/exclamation.png new file mode 100644 index 00000000000..76d1e34b6cc Binary files /dev/null and b/docs/icons/exclamation.png differ diff --git a/docs/icons/writing.png b/docs/icons/writing.png new file mode 100644 index 00000000000..59ef13ce6ed Binary files /dev/null and b/docs/icons/writing.png differ 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 e186f7ba096..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 e3b784310fe..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 edfd1ff7897..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/UG/Overview_of_GUI.png b/docs/images/UG/Overview_of_GUI.png new file mode 100644 index 00000000000..64e0af99959 Binary files /dev/null and b/docs/images/UG/Overview_of_GUI.png differ diff --git a/docs/images/UG/QUICKSTART2.png b/docs/images/UG/QUICKSTART2.png new file mode 100644 index 00000000000..5a464d93521 Binary files /dev/null and b/docs/images/UG/QUICKSTART2.png differ diff --git a/docs/images/UG/QUICKSTART3.png b/docs/images/UG/QUICKSTART3.png new file mode 100644 index 00000000000..7a7cec93ebd Binary files /dev/null and b/docs/images/UG/QUICKSTART3.png differ diff --git a/docs/images/UG/QUICKSTART4.png b/docs/images/UG/QUICKSTART4.png new file mode 100644 index 00000000000..93cf4a8e5e7 Binary files /dev/null and b/docs/images/UG/QUICKSTART4.png differ diff --git a/docs/images/UG/QUICKSTART_2.1.png b/docs/images/UG/QUICKSTART_2.1.png new file mode 100644 index 00000000000..41aae5bcbcf Binary files /dev/null and b/docs/images/UG/QUICKSTART_2.1.png differ diff --git a/docs/images/UG/QuickStart1.png b/docs/images/UG/QuickStart1.png new file mode 100644 index 00000000000..cce22ad6c14 Binary files /dev/null and b/docs/images/UG/QuickStart1.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5bd77847aa2..ff669a9213c 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/UndoSequenceDiagram.png b/docs/images/UndoSequenceDiagram.png deleted file mode 100644 index c7a7e637266..00000000000 Binary files a/docs/images/UndoSequenceDiagram.png and /dev/null differ diff --git a/docs/images/coderhuang559.png b/docs/images/coderhuang559.png new file mode 100644 index 00000000000..1b34ea613e3 Binary files /dev/null and b/docs/images/coderhuang559.png differ diff --git a/docs/images/features/Generalcommand_clear.png b/docs/images/features/Generalcommand_clear.png new file mode 100644 index 00000000000..e19b9d55079 Binary files /dev/null and b/docs/images/features/Generalcommand_clear.png differ diff --git a/docs/images/features/Generalcommand_help.png b/docs/images/features/Generalcommand_help.png new file mode 100644 index 00000000000..dd6947ab9f3 Binary files /dev/null and b/docs/images/features/Generalcommand_help.png differ diff --git a/docs/images/features/Managecontacts_add.png b/docs/images/features/Managecontacts_add.png new file mode 100644 index 00000000000..05dee305f87 Binary files /dev/null and b/docs/images/features/Managecontacts_add.png differ diff --git a/docs/images/features/Managecontacts_addwithg.png b/docs/images/features/Managecontacts_addwithg.png new file mode 100644 index 00000000000..c3ec0e8f12d Binary files /dev/null and b/docs/images/features/Managecontacts_addwithg.png differ diff --git a/docs/images/features/Managecontacts_delete.png b/docs/images/features/Managecontacts_delete.png new file mode 100644 index 00000000000..ae4017d6396 Binary files /dev/null and b/docs/images/features/Managecontacts_delete.png differ diff --git a/docs/images/features/Managecontacts_find.png b/docs/images/features/Managecontacts_find.png new file mode 100644 index 00000000000..b5f37e6b594 Binary files /dev/null and b/docs/images/features/Managecontacts_find.png differ diff --git a/docs/images/features/Managecontacts_list.png b/docs/images/features/Managecontacts_list.png new file mode 100644 index 00000000000..23fd96ba987 Binary files /dev/null and b/docs/images/features/Managecontacts_list.png differ diff --git a/docs/images/features/Managegroup_delete.png b/docs/images/features/Managegroup_delete.png new file mode 100644 index 00000000000..57fc0f5386e Binary files /dev/null and b/docs/images/features/Managegroup_delete.png differ diff --git a/docs/images/features/Managegroup_find.png b/docs/images/features/Managegroup_find.png new file mode 100644 index 00000000000..1f267564e29 Binary files /dev/null and b/docs/images/features/Managegroup_find.png differ diff --git a/docs/images/features/Managegroup_group.png b/docs/images/features/Managegroup_group.png new file mode 100644 index 00000000000..006cead04c6 Binary files /dev/null and b/docs/images/features/Managegroup_group.png differ diff --git a/docs/images/features/Managegroup_listgroup.png b/docs/images/features/Managegroup_listgroup.png new file mode 100644 index 00000000000..9bf32019377 Binary files /dev/null and b/docs/images/features/Managegroup_listgroup.png differ diff --git a/docs/images/features/Managegroup_new.png b/docs/images/features/Managegroup_new.png new file mode 100644 index 00000000000..866a8982e55 Binary files /dev/null and b/docs/images/features/Managegroup_new.png differ diff --git a/docs/images/features/Managegroup_remark.png b/docs/images/features/Managegroup_remark.png new file mode 100644 index 00000000000..de01f181f2a Binary files /dev/null and b/docs/images/features/Managegroup_remark.png differ diff --git a/docs/images/features/Managegroup_ungroup.png b/docs/images/features/Managegroup_ungroup.png new file mode 100644 index 00000000000..b3a7438f27e Binary files /dev/null and b/docs/images/features/Managegroup_ungroup.png differ diff --git a/docs/images/features/Managetime_addmeeting.png b/docs/images/features/Managetime_addmeeting.png new file mode 100644 index 00000000000..645e42d1370 Binary files /dev/null and b/docs/images/features/Managetime_addmeeting.png differ diff --git a/docs/images/features/Managetime_addtime.png b/docs/images/features/Managetime_addtime.png new file mode 100644 index 00000000000..0e566662847 Binary files /dev/null and b/docs/images/features/Managetime_addtime.png differ diff --git a/docs/images/features/Managetime_deletetimecontact.png b/docs/images/features/Managetime_deletetimecontact.png new file mode 100644 index 00000000000..15709a61127 Binary files /dev/null and b/docs/images/features/Managetime_deletetimecontact.png differ diff --git a/docs/images/features/Managetime_deletetimegroup.png b/docs/images/features/Managetime_deletetimegroup.png new file mode 100644 index 00000000000..080283dd62b Binary files /dev/null and b/docs/images/features/Managetime_deletetimegroup.png differ diff --git a/docs/images/features/Managetime_findfreetime.png b/docs/images/features/Managetime_findfreetime.png new file mode 100644 index 00000000000..891cb74faa6 Binary files /dev/null and b/docs/images/features/Managetime_findfreetime.png differ diff --git a/docs/images/features/Managetime_listtimecontact.png b/docs/images/features/Managetime_listtimecontact.png new file mode 100644 index 00000000000..b982cb368b7 Binary files /dev/null and b/docs/images/features/Managetime_listtimecontact.png differ diff --git a/docs/images/features/Managetime_listtimegroup.png b/docs/images/features/Managetime_listtimegroup.png new file mode 100644 index 00000000000..1f22ca9d7d2 Binary files /dev/null and b/docs/images/features/Managetime_listtimegroup.png differ diff --git a/docs/images/helpMessage.png b/docs/images/helpMessage.png index b1f70470137..c2e712b0e31 100644 Binary files a/docs/images/helpMessage.png and b/docs/images/helpMessage.png differ diff --git a/docs/images/kailash201.png b/docs/images/kailash201.png new file mode 100644 index 00000000000..77cb0cac1cf Binary files /dev/null and b/docs/images/kailash201.png differ diff --git a/docs/images/lerxuann.png b/docs/images/lerxuann.png new file mode 100644 index 00000000000..06e7d2d7cb3 Binary files /dev/null and b/docs/images/lerxuann.png differ diff --git a/docs/images/nicholastng010601.png b/docs/images/nicholastng010601.png new file mode 100644 index 00000000000..2a8cd263110 Binary files /dev/null and b/docs/images/nicholastng010601.png differ diff --git a/docs/images/tracing/LogicSequenceDiagram.png b/docs/images/tracing/LogicSequenceDiagram.png deleted file mode 100644 index 25c8b66b9f1..00000000000 Binary files a/docs/images/tracing/LogicSequenceDiagram.png and /dev/null differ diff --git a/docs/images/zd292.png b/docs/images/zd292.png new file mode 100644 index 00000000000..492d8de84ad Binary files /dev/null and b/docs/images/zd292.png differ diff --git a/docs/index.md b/docs/index.md index 7601dbaad0d..7163df96470 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,8 +1,10 @@ --- -layout: page -title: AddressBook Level-3 + layout: default.md + title: "" --- +# ProjectPRO + [![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) 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..af13b086c46 --- /dev/null +++ b/docs/site.json @@ -0,0 +1,29 @@ +{ + "baseUrl": "", + "titlePrefix": "MyProduct", + "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..147b12d28a4 --- /dev/null +++ b/docs/stylesheets/main.css @@ -0,0 +1,144 @@ +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; + } +} + +h2, +h3, +h4, +h5, +h6 { + color: #56494C; +} diff --git a/docs/team/coderhuang559.md b/docs/team/coderhuang559.md new file mode 100644 index 00000000000..d3148b7cb52 --- /dev/null +++ b/docs/team/coderhuang559.md @@ -0,0 +1,45 @@ +--- + layout: default.md + title: "Huang Yixin's Project Portfolio Page" +--- + +### Project: ProjectPRO + +ProjectPRO is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to remove a person from a group. + * What it does: Allows the user to remove people from groups so that they can accurately keep track of any changes in their groupings. This function also provides a convenient way for users to remove members from a group should they add them by mistake. + * Justification: This is a core feature which improves the product significantly because it will help our target audience, university students, keep track of their groups and members involved should there be any changes after creating the group. + * Highlights: This feature required an in-depth analysis of design principles. Since it involved a new class of objects, it required careful planning and utilising of OOP principles. + +* **New Feature**: Added the ability to add remarks to a group. + * What it does: Allows the user to add remarks to groups. + * Justification: This feature improves the product significantly because a user can keep track of specific details of each group to better manage their projects. + * Highlights: This enhancement required an in-depth analysis of the existing classes and parser. As this function involved a new class of objects, and required the creation of a new class of object, it required the careful planning and utilising of OOP principles. We considered allowing the users to edit current remarks, but decided that it could be confusing to implement it in a user-friendly way, and could cause inconvenience if the remark is too long, or if they want to make extensive changes to the remark. + +* **New Feature**: Added the ability to list the free times of a person or a group. + * What it does: Allows the user to see the existing free times and meeting times of the people and groups in their contact list, so that they can better keep track of their contacts' and groups' schedules. + * Justification: This feature improves the product significantly as without it, users would not be able to keep track of their group members' free times or their groups' meeting times. + * Highlights: This enhancement required the in-depth analysis of the existing classes and storage formats. As this function is purely for users to see the time intervals, it required careful consideration to produce the most user-friendly and comprehensible output result. We considered many formats for showing the times, but ultimately settled on the current as it is the most readable and informative at the same time. + +* **New Feature**: Added the ability to add meeting times to a group. + * What it does: Allows the user to add meeting times to the groups in their contact list, so that they can record and track all meeting times of all groups in one place. + * Justification: This feature improves the product significantly as without it, users would not be able to track all the meeting times of their groups. + * Highlights: This enhancement required the in-depth analysis of the existing classes. We considered limiting the acceptable times to be ones where everyone is free, or strictly non-clashing, but decided against these as ultimately, we want this feature to be for the user's convenience and setting so many boundaries would inconvenience them instead. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/#/widget/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=authorship&tabAuthor=coderhuang559&tabRepo=AY2324S1-CS2103T-T10-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false&chartGroupIndex=36&chartIndex=1) + +* **Project management**: + * Managed releases `v1.3.0` - `v1.4.1` (6 releases) on GitHub + +* **Documentation**: + * User Guide: + * Added documentation for the features `deletetime` for contacts, `listtime` for contacts, `addmeeting` and `deletetime` for groups. [\#144](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/144) + * Developer Guide: + * Added implementation details of the `remark`, `listtime` for contacts and groups, `addtime`, and `addmeeting` features [\#270](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/270/files). + * Added activity and sequence diagrams for the above functions. + +* **Contributions beyond project team** + * Meticulously review other groups work and provided feedback on potential bugs and how they could improve their product [here](https://github.com/coderhuang559/ped/tree/main/files). diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index 773a07794e2..00000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: page -title: John Doe's Project Portfolio Page ---- - -### Project: AddressBook Level 3 - -AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -Given below are my contributions to the project. - -* **New Feature**: Added the ability to undo/redo previous commands. - * What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. - * Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. - * Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. - * Credits: *{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}* - -* **New Feature**: Added a history command that allows the user to navigate to previous commands using up/down keys. - -* **Code contributed**: [RepoSense link]() - -* **Project management**: - * Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub - -* **Enhancements to existing features**: - * Updated the GUI color scheme (Pull requests [\#33](), [\#34]()) - * Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests [\#36](), [\#38]()) - -* **Documentation**: - * User Guide: - * Added documentation for the features `delete` and `find` [\#72]() - * Did cosmetic tweaks to existing documentation of features `clear`, `exit`: [\#74]() - * Developer Guide: - * Added implementation details of the `delete` feature. - -* **Community**: - * PRs reviewed (with non-trivial review comments): [\#12](), [\#32](), [\#19](), [\#42]() - * Contributed to forum discussions (examples: [1](), [2](), [3](), [4]()) - * Reported bugs and suggestions for other teams in the class (examples: [1](), [2](), [3]()) - * Some parts of the history feature I added was adopted by several other class mates ([1](), [2]()) - -* **Tools**: - * Integrated a third party library (Natty) to the project ([\#42]()) - * Integrated a new Github plugin (CircleCI) to the team repo - -* _{you can add/remove categories in the list above}_ diff --git a/docs/team/kailash201.md b/docs/team/kailash201.md new file mode 100644 index 00000000000..2dcca03e0b2 --- /dev/null +++ b/docs/team/kailash201.md @@ -0,0 +1,45 @@ +--- + layout: default.md + title: "Kailash's Project Portfolio Page" +--- + +### Project: ProjectPRO + +ProjectPRO is a desktop application designed to help university students organize their projects. ProjectPRO is optimized for use via a Command Line Interface (CLI) while preserving the advantages of maintaining an attractive user interface. Utilizing simple and easy-to-remember commands to execute different tasks, it helps users streamline their project management. + + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to allow user add free time interval into their contacts. + * What it does: allows the user to add non-clashing free time interval into their contacts. + * Justification: This feature allows the user to see when their contacts or group mates are free. A lot of consideration was gone through in deciding whether we should allow user to add overlapping time intervals or not overlapping time interval. In the end it was decided to be non-clashing free time interval to reduce the complexity needed to support the `findfreetime` feature. + * Highlights: This feature needed to ensure that the time interval added to a contact by the user does not clash with other time interval that contact has. An algorithm was made to detect any clashes in the time interval. In addition to that, this feature allows multiple non-clashing time intervals to be added in a single input. Due to that, a lot of challenges were faced in the parsing logic as making it convenient for the user in terms of input will open up room for more invalid errors. However, these errors were managed. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&chartGroupIndex=36&chartIndex=2%23%2F%23%2F) + +* **Project management**: + * Managed releases `v1.3.0` - `v1.4.1` (6 releases) on GitHub + +* **Enhancements to existing features**: + **Improving `Add` feature**: Added the ability to allow the user to add an optional Group name along with other attributes. + * What it does: Allows the user to add contact into the contact list and add that contact to the group at the same time. + * Justification: This feature makes it convenient for the user to add their new contacts into the group at the same time with just one command. + * Highlights: This feature needed to handle input when there is no group name type and when there is group name type. It also needed to ensure that when the group name is typed, the contact needed to be added inside the `uniquePersonList` and the group inside the `addressbook#grouplist` if the group does not exist. It also needed to ensure that in the group was added into contact and the contact was added into the group because group and contact are dependent with one another. +* **Updated the GUI to fit ProjectPRO requirements** (Pull requests [\#70](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/70), [\#100](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/100), [\#136](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/136)) + * Justification: This update allows the user to see a weekly schedule and track when their next group meeting is. + * Highlights: The challenges faced was converting the data to GUI friendly and ensuring the GUI gets updated when new group meeting time was added to a group. + + +* **Documentation**: + * User Guide: + * Added documentation for the features `list`, `new`, `delete`, `find`. [\#145](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/145) + * Made some updates to the documentation for the final release. [\#290](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/290), [\#277](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/277), [\#271](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/271) + * Developer Guide: + * Added implementation details of the `add` and `addtime`feature. [\#271](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/271/files). + * Made diagrams for `add` and `addtime` feature. + * Updated old architecture models to projectPROs implementation for `model` and `UI` [\#263](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/263/files). + * Added and updated the planned enhancements section. [\#248](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/248/files). + +* **Community**: + * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/AY2324S1-CS2103T-T10-4/tp/issues/232), [2](https://github.com/AY2324S1-CS2103T-T10-4/tp/issues/231), [3](https://github.com/AY2324S1-CS2103T-T10-4/tp/issues/234)). + diff --git a/docs/team/lerxuann.md b/docs/team/lerxuann.md new file mode 100644 index 00000000000..650132f4585 --- /dev/null +++ b/docs/team/lerxuann.md @@ -0,0 +1,50 @@ +--- + layout: default.md + title: "Goh Ler Xuan's Project Portfolio Page" +--- + +### Project: ProjectPRO + +ProjectPRO is a desktop application designed to help university students organize their projects. ProjectPRO is optimized for use via a Command Line Interface (CLI) while preserving the advantages of maintaining an attractive user interface. Utilizing simple and easy-to-remember commands to execute different tasks, it helps users streamline their project management. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to delete added contacts. + * What it does: allows the user to delete contacts added into the address book. + * Justification: This is a core feature that improves the product significantly because a user may want to remove contacts that they no longer want to keep track of. + * Highlights: This enhancement affected existing commands (deleting contacts needed to remove them from the groups they were part of as well) and commands to be added in future (deleting added groups). It required utilising of OOP principles and the creation of an abstract class, as the 'delete' command word had to be shared between commands. + +* **New Feature**: Added the ability to delete added groups. + * What it does: allows the user to delete groups added into the address book, removing any members from those groups as well. + * Justification: This is a core feature that improves the product significantly because a user may want to remove groups or projects that they no longer want to keep track of. + * Highlights: Aside from the creation of an abstract class, as the 'delete' command word had to be shared between commands, this enhancement required the utilising of OOP principles as it involved the accessing and sharing of data between the group and person classes. + +* **New Feature**: Added the ability to find groups. + * What it does: allows the user to view members of the specified group, as well as the group remarks. + * Justification: This is a feature that improves the product significantly because a user can then filter the address book to view the information of only the relevant contacts. They can also see the remarks that they had added into the groups beforehand. + * Highlights: This enhancement affected existing commands (finding contacts by keywords). It required utilising of OOP principles and the creation of an abstract class, as the 'find' command word had to be shared between commands. + +* **New Feature**: Added the ability to list all groups. + * What it does: allows the user to view all the groups currently added into the address book, to keep better track of them. + * Justification: This is a feature that improves the product because a user can then better track and manage the groups or projects that they have. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/#/widget/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&chartGroupIndex=36&chartIndex=0) + +* **Project management**: + * Managed releases `v1.3.0` - `v1.4.1` (6 releases) on GitHub + +* **Enhancements to existing features**: + * Modified the general commands so no additional inputs can be accepted (Pull request [\#143](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/143)) + * Modified the find person command to inherit from an abstract class and share the same command word as the 'find group' command (Pull request [\#103](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/103)) + +* **Documentation**: + * User Guide: + * Added app introductions and quick-start instructions, as well as the glossary and table of contents.[\#147](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/147) + * Added documentation for the features `add contact` and `delete contact`. [\#147](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/147) + * Resolved inconsistencies in existing documentation in multiple features, including `delete contact`, `find contact` and `list`: [\#228](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/228) + * Developer Guide: + * Added implementation details of the `delete`, `find`, `list` [\#265](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/265) and `listgroup` [\#268](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/268) features. + +* **Contributions beyond project team** + * Meticulously reviewed other groups' works and provided feedback on potential bugs [here](https://github.com/lerxuann/ped). + diff --git a/docs/team/nicholastng010601.md b/docs/team/nicholastng010601.md new file mode 100644 index 00000000000..5407fb845a1 --- /dev/null +++ b/docs/team/nicholastng010601.md @@ -0,0 +1,52 @@ +--- + layout: default.md + title: "Nicholas Tng's Project Portfolio Page" +--- + +### Project: ProjectPRO + +ProjectPRO is a desktop application designed to help university students organize their projects. ProjectPRO is optimized for use via a Command Line Interface (CLI) while preserving the advantages of maintaining an attractive user interface. Utilizing simple and easy-to-remember commands to execute different tasks, it helps users streamline their project management. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to add a group. + * What it does: allows the user to add various groups into the address book so that the user can group contacts into the various project groups they are in. + * Justification: This is a core features which improves the product significantly because it will help our target audience, university students, group their contacts into their various project groups so that they do not mix up contacts with similar names. It is especially relevant as university students tend to have many projects. + * Highlights: This enhancement affects existing commands and commands to be added in the future. It required an in-depth analysis of design principles. Since it involved creating a new class of objects, it required careful planning and utilising of OOP principles. + +* **New Feature**: Added the ability to delete time. + * What it does: allows the user to delete time which had been added to a contact or group. + * Justification: This feature improves the product significantly because a user can make mistakes in adding free time to contacts and this function provides a convenient way for users to remove the error. + * Highlights: This enhancement required an in-depth analysis of parser and various conditions whereby the command would be carried out. For example, the time keyed in must exist within the contact. Various designs were also considered, such as allowing multiple time slots to be keyed in at once to improve the user experience. In such a case, we also wanted to allow time that was keyed in correctly to be deleted, even if the command had certain time slots which were wrong. The implementation too was difficult as it required the involvement of our time interval class and comparing the various time slots to come up with a error-free yet efficient implementation. + +* **Improved Feature**: Added the ability to save not only contacts, but groups and time intervals. + * What it does: allows the user's application to save groups and time intervals in the form of JSON, so that when the user reboots their application, their data will be saved and loaded up properly. + * Justification: This feature is a core feature as without it, users would have to retype all their groups and times everytime they on their application. + * Highlights: This enhancement required the in-depth analysis of the existing process of saving and loading of the JSON file. It required meticulousness and attention to detail as it involved complex logical processes such as adding a person to the group he is in, while also adding the group to the person's own internal list, whereby any error would result in unexpected results. It also required careful considerations regarding the important variables that had to be stored for the save function to fully capture all the details and allow the user to load the application just as he or she stored it. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/#/widget/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&chartGroupIndex=36&chartIndex=3) + +* **Project management**: + * Managed releases `v1.3.0` - `v1.4.1` (6 releases) on GitHub + +* **Enhancements to existing features**: + * Updated the GUI color scheme (Pull requests [\#82](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/82), [\#83](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/83), [\#85](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/85)) + * Wrote additional tests for existing features (Pull requests [\#132](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/132), [\#139](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/139), [\#141](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/141)) + +* **Documentation**: + * User Guide: + * Added documentation for the features `find`, `list`, `group`, `ungroup` and `addtime`. [\#145](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/145) + * Developer Guide: + * Added implementation details of the `deletetime` [\#112](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/112/files) and `addgroup` [\#226](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/226/files) feature. + * Added use cases, test cases and the appendix.[\#251](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/251) + * Added activity and sequence diagrams for the above functions. This includes for delete person time command, shown here. + * + * + * Documentation: + * Meticulously edited style errors and java docs for `commands`[/#238](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/238) and `tests`[/#250](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/250). +* **Contributions beyond project team** + * Meticulously review other groups work and provided feedback on potential bugs and how they could improve their product [here](https://github.com/nicholastng010601/ped/tree/main/files). + + +* **Community**: + * Reported bugs and suggestions for other teams in the class (examples: [1](https://github.com/AY2324S1-CS2103T-T10-4/tp/issues/242), [2](https://github.com/AY2324S1-CS2103T-T10-4/tp/issues/243), [3](https://github.com/AY2324S1-CS2103T-T10-4/tp/issues/240)) diff --git a/docs/team/zd292.md b/docs/team/zd292.md new file mode 100644 index 00000000000..c835c5d2b68 --- /dev/null +++ b/docs/team/zd292.md @@ -0,0 +1,41 @@ +--- + layout: default.md + title: "John Doe's Project Portfolio Page" +--- + +### Project: AddressBook Level 3 + +AddressBook - Level 3 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +Given below are my contributions to the project. + +* **New Feature**: Added the ability to group a contact. + * What it does: Allows the user to group a contact. + * Justification: This is a core features which improves the product significantly because it will help our target audience, university students, organise their contacts into their various project groups so that they do not mix up contacts with similar names. This allows university students to neatly organise their contacts. + * Highlights: This enhancement affects existing commands and commands to be added in the future. For example, the relationship between group and person is that a group has a list of group members and a person has a list of groups that they are in. + +* **New Feature**: Added the ability to find a free time slot for group meetings. + * What it does: Allows the user to find a common time slot within their group. + * Justification: This feature improves the product significantly because users are now able to find a common meeting time slot, which solves one of the pain points, being difficulty in coordinating meetings. + * Highlights: This feature involved careful planning and coordination between members of how we wanted to implement the concept of time and consequently how I would implement the feature to find a common time slot. + +* **Code contributed**: [RepoSense link](https://nus-cs2103-ay2324s1.github.io/tp-dashboard/?search=zd292&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code&since=2023-09-22&tabOpen=true&tabType=authorship&tabAuthor=ZD292&tabRepo=AY2324S1-CS2103T-T10-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +* **Project management**: + * Managed releases `v1.3.0` - `v1.4.1` (6 releases) on GitHub + * Review and merge Pull requests + +* **Documentation**: + * User Guide: + * Added documentation for the features [\#142](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/142) + * Listing Meeting Time From a Group `listtime` + * Finding Free Time of a Group `findfreetime` + * Viewing Help: `help` + * Clearing all Data: `clear` + + * Developer Guide: + * Add sequence diagram for grouping a contact [\#296](https://github.com/AY2324S1-CS2103T-T10-3/tp/pull/296) + * Add sequence diagram for FindFreeTime command + * Add activity diagram for FindFreeTime command + diff --git a/docs/tutorials/AddRemark.md b/docs/tutorials/AddRemark.md index d98f38982e7..8b18f27946b 100644 --- a/docs/tutorials/AddRemark.md +++ b/docs/tutorials/AddRemark.md @@ -1,8 +1,11 @@ --- -layout: page -title: "Tutorial: Adding a command" + layout: default.md + title: "Tutorial: Adding a command" + pageNav: 3 --- +# Tutorial: Adding a command + Let's walk you through the implementation of a new command — `remark`. This command allows users of the AddressBook application to add optional remarks to people in their address book and edit it if required. The command should have the following format: @@ -22,7 +25,7 @@ For now, let’s keep `RemarkCommand` as simple as possible and print some outpu **`RemarkCommand.java`:** -``` java +```java package seedu.address.logic.commands; import seedu.address.model.Model; @@ -57,13 +60,13 @@ Run `Main#main` and try out your new `RemarkCommand`. If everything went well, y While we have successfully printed a message to `ResultDisplay`, the command does not do what it is supposed to do. Let’s change the command to throw a `CommandException` to accurately reflect that our command is still a work in progress. -![The relationship between RemarkCommand and Command](../images/add-remark/RemarkCommandClass.png) + Following the convention in other commands, we add relevant messages as constants and use them. **`RemarkCommand.java`:** -``` java +```java public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the person identified " + "by the index number used in the last person listing. " @@ -90,7 +93,7 @@ Let’s change `RemarkCommand` to parse input from the user. We start by modifying the constructor of `RemarkCommand` to accept an `Index` and a `String`. While we are at it, let’s change the error message to echo the values. While this is not a replacement for tests, it is an obvious way to tell if our code is functioning as intended. -``` java +```java import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; //... public class RemarkCommand extends Command { @@ -142,13 +145,13 @@ Now let’s move on to writing a parser that will extract the index and remark f Create a `RemarkCommandParser` class in the `seedu.address.logic.parser` package. The class must extend the `Parser` interface. -![The relationship between Parser and RemarkCommandParser](../images/add-remark/RemarkCommandParserClass.png) + Thankfully, `ArgumentTokenizer#tokenize()` makes it trivial to parse user input. Let’s take a look at the JavaDoc provided for the function to understand what it does. **`ArgumentTokenizer.java`:** -``` java +```java /** * Tokenizes an arguments string and returns an {@code ArgumentMultimap} * object that maps prefixes to their respective argument values. Only the @@ -166,7 +169,7 @@ We can tell `ArgumentTokenizer#tokenize()` to look out for our new prefix `r/` a **`ArgumentMultimap.java`:** -``` java +```java /** * Returns the last value of {@code prefix}. */ @@ -181,7 +184,7 @@ This appears to be what we need to get a String of the remark. But what about th **`DeleteCommandParser.java`:** -``` java +```java Index index = ParserUtil.parseIndex(args); return new DeleteCommand(index); ``` @@ -192,7 +195,7 @@ Now that we have the know-how to extract the data that we need from the user’s **`RemarkCommandParser.java`:** -``` java +```java public RemarkCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, @@ -212,11 +215,11 @@ public RemarkCommand parse(String args) throws ParseException { } ``` -
    + -:information_source: Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`! +Don’t forget to update `AddressBookParser` to use our new `RemarkCommandParser`! -
    + If you are stuck, check out the sample [here](https://github.com/se-edu/addressbook-level3/commit/dc6d5139d08f6403da0ec624ea32bd79a2ae0cbf#diff-8bf239e8e9529369b577701303ddd96af93178b4ed6735f91c2d8488b20c6b4a). @@ -244,7 +247,7 @@ Simply add the following to [`seedu.address.ui.PersonCard`](https://github.com/s **`PersonCard.java`:** -``` java +```java @FXML private Label remark; ``` @@ -276,11 +279,11 @@ We change the constructor of `Person` to take a `Remark`. We will also need to d Unfortunately, a change to `Person` will cause other commands to break, you will have to modify these commands to use the updated `Person`! -
    + -:bulb: Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands. +Use the `Find Usages` feature in IntelliJ IDEA on the `Person` class to find these commands. -
    + Refer to [this commit](https://github.com/se-edu/addressbook-level3/commit/ce998c37e65b92d35c91d28c7822cd139c2c0a5c) and check that you have got everything in order! @@ -291,11 +294,11 @@ AddressBook stores data by serializing `JsonAdaptedPerson` into `json` with the While the changes to code may be minimal, the test data will have to be updated as well. -
    + -:exclamation: You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book! +You must delete AddressBook’s storage file located at `/data/addressbook.json` before running it! Not doing so will cause AddressBook to default to an empty address book! -
    + Check out [this commit](https://github.com/se-edu/addressbook-level3/commit/556cbd0e03ff224d7a68afba171ad2eb0ce56bbf) to see what the changes entail. @@ -308,7 +311,7 @@ Just add [this one line of code!](https://github.com/se-edu/addressbook-level3/c **`PersonCard.java`:** -``` java +```java public PersonCard(Person person, int displayedIndex) { //... remark.setText(person.getRemark().value); @@ -328,7 +331,7 @@ save it with `Model#setPerson()`. **`RemarkCommand.java`:** -``` java +```java //... public static final String MESSAGE_ADD_REMARK_SUCCESS = "Added remark to Person: %1$s"; public static final String MESSAGE_DELETE_REMARK_SUCCESS = "Removed remark from Person: %1$s"; diff --git a/docs/tutorials/RemovingFields.md b/docs/tutorials/RemovingFields.md index f29169bc924..c73bd379e5e 100644 --- a/docs/tutorials/RemovingFields.md +++ b/docs/tutorials/RemovingFields.md @@ -1,8 +1,11 @@ --- -layout: page -title: "Tutorial: Removing Fields" + layout: default.md + title: "Tutorial: Removing Fields" + pageNav: 3 --- +# Tutorial: Removing Fields + > Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away. > > — Antoine de Saint-Exupery @@ -10,17 +13,17 @@ title: "Tutorial: Removing Fields" When working on an existing code base, you will most likely find that some features that are no longer necessary. This tutorial aims to give you some practice on such a code 'removal' activity by removing the `address` field from `Person` class. -
    + **If you have done the [Add `remark` command tutorial](AddRemark.html) already**, you should know where the code had to be updated to add the field `remark`. From that experience, you can deduce where the code needs to be changed to _remove_ that field too. The removing of the `address` field can be done similarly.

    However, if you have no such prior knowledge, removing a field can take a quite a bit of detective work. This tutorial takes you through that process. **At least have a read even if you don't actually do the steps yourself.** -
    + -* Table of Contents -{:toc} + + ## Safely deleting `Address` @@ -50,10 +53,10 @@ Let’s try removing references to `Address` in `EditPersonDescriptor`. 1. Remove the usages of `address` and select `Do refactor` when you are done. -
    + - :bulb: **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor. -
    + **Tip:** Removing usages may result in errors. Exercise discretion and fix them. For example, removing the `address` field from the `Person` class will require you to modify its constructor. + 1. Repeat the steps for the remaining usages of `Address` @@ -71,7 +74,7 @@ A quick look at the `PersonCard` class and its `fxml` file quickly reveals why i **`PersonCard.java`** -``` java +```java ... @FXML private Label address; diff --git a/docs/tutorials/TracingCode.md b/docs/tutorials/TracingCode.md index 4fb62a83ef6..2b1b0f2d6b7 100644 --- a/docs/tutorials/TracingCode.md +++ b/docs/tutorials/TracingCode.md @@ -1,26 +1,30 @@ --- -layout: page -title: "Tutorial: Tracing code" + layout: default.md + title: "Tutorial: Tracing code" + pageNav: 3 --- +# Tutorial: Tracing code + + > Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …​\[Therefore,\] making it easy to read makes it easier to write. > > — Robert C. Martin Clean Code: A Handbook of Agile Software Craftsmanship When trying to understand an unfamiliar code base, one common strategy used is to trace some representative execution path through the code base. One easy way to trace an execution path is to use a debugger to step through the code. In this tutorial, you will be using the IntelliJ IDEA’s debugger to trace the execution path of a specific user command. -* Table of Contents -{:toc} + + ## Before we start Before we jump into the code, it is useful to get an idea of the overall structure and the high-level behavior of the application. This is provided in the 'Architecture' section of the developer guide. In particular, the architecture diagram (reproduced below), tells us that the App consists of several components. -![ArchitectureDiagram](../images/ArchitectureDiagram.png) + It also has a sequence diagram (reproduced below) that tells us how a command propagates through the App. - + Note how the diagram shows only the execution flows _between_ the main components. That is, it does not show details of the execution path *inside* each component. By hiding those details, the diagram aims to inform the reader about the overall execution path of a command without overwhelming the reader with too much details. In this tutorial, you aim to find those omitted details so that you get a more in-depth understanding of how the code works. @@ -37,16 +41,16 @@ As you know, the first step of debugging is to put in a breakpoint where you wan In our case, we would want to begin the tracing at the very point where the App start processing user input (i.e., somewhere in the UI component), and then trace through how the execution proceeds through the UI component. However, the execution path through a GUI is often somewhat obscure due to various *event-driven mechanisms* used by GUI frameworks, which happens to be the case here too. Therefore, let us put the breakpoint where the `UI` transfers control to the `Logic` component. - + According to the sequence diagram you saw earlier (and repeated above for reference), the `UI` component yields control to the `Logic` component through a method named `execute`. Searching through the code base for an `execute()` method that belongs to the `Logic` component yields a promising candidate in `seedu.address.logic.Logic`. -
    + -:bulb: **Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`. -
    +**Intellij Tip:** The ['**Search Everywhere**' feature](https://www.jetbrains.com/help/idea/searching-everywhere.html) can be used here. In particular, the '**Find Symbol**' ('Symbol' here refers to methods, variables, classes etc.) variant of that feature is quite useful here as we are looking for a _method_ named `execute`, not simply the text `execute`. + A quick look at the `seedu.address.logic.Logic` (an extract given below) confirms that this indeed might be what we’re looking for. @@ -67,14 +71,14 @@ public interface Logic { But apparently, this is an interface, not a concrete implementation. That should be fine because the [Architecture section of the Developer Guide](../DeveloperGuide.html#architecture) tells us that components interact through interfaces. Here's the relevant diagram: - + Next, let's find out which statement(s) in the `UI` code is calling this method, thus transferring control from the `UI` to the `Logic`. -
    + -:bulb: **Intellij Tip:** The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used. -
    +**Intellij Tip:** The ['**Find Usages**' feature](https://www.jetbrains.com/help/idea/find-highlight-usages.html#find-usages) can find from which parts of the code a class/method/variable is being used. + ![`Find Usages` tool window. `Edit` \> `Find` \> `Find Usages`.](../images/tracing/FindUsages.png) @@ -87,10 +91,10 @@ Now let’s set the breakpoint. First, double-click the item to reach the corres Recall from the User Guide that the `edit` command has the format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…​` For this tutorial we will be issuing the command `edit 1 n/Alice Yeoh`. -
    + -:bulb: **Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component. -
    +**Tip:** Over the course of the debugging session, you will encounter every major component in the application. Try to keep track of what happens inside the component and where the execution transfers to another component. + 1. To start the debugging session, simply `Run` \> `Debug Main` @@ -110,7 +114,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ **LogicManager\#execute().** - ``` java + ```java @Override public CommandResult execute(String commandText) throws CommandException, ParseException { @@ -142,7 +146,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ ![StepOver](../images/tracing/StepOver.png) 1. _Step into_ the line where user input in parsed from a String to a Command, which should bring you to the `AddressBookParser#parseCommand()` method (partial code given below): - ``` java + ```java public Command parseCommand(String userInput) throws ParseException { ... final String commandWord = matcher.group("commandWord"); @@ -157,7 +161,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ 1. Stepping through the `switch` block, we end up at a call to `EditCommandParser().parse()` as expected (because the command we typed is an edit command). - ``` java + ```java ... case EditCommand.COMMAND_WORD: return new EditCommandParser().parse(arguments); @@ -166,8 +170,10 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ 1. Let’s see what `EditCommandParser#parse()` does by stepping into it. You might have to click the 'step into' button multiple times here because there are two method calls in that statement: `EditCommandParser()` and `parse()`. -
    :bulb: **Intellij Tip:** Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them! -
    + + + **Intellij Tip:** Sometimes, you might end up stepping into functions that are not of interest. Simply use the `step out` button to get out of them! + 1. Stepping through the method shows that it calls `ArgumentTokenizer#tokenize()` and `ParserUtil#parseIndex()` to obtain the arguments and index required. @@ -175,17 +181,17 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ ![EditCommand](../images/tracing/EditCommand.png) 1. As you just traced through some code involved in parsing a command, you can take a look at this class diagram to see where the various parsing-related classes you encountered fit into the design of the `Logic` component. - + 1. Let’s continue stepping through until we return to `LogicManager#execute()`. The sequence diagram below shows the details of the execution path through the Logic component. Does the execution path you traced in the code so far match the diagram?
    - ![Tracing an `edit` command through the Logic component](../images/tracing/LogicSequenceDiagram.png) + 1. Now, step over until you read the statement that calls the `execute()` method of the `EditCommand` object received, and step into that `execute()` method (partial code given below): **`EditCommand#execute()`:** - ``` java + ```java @Override public CommandResult execute(Model model) throws CommandException { ... @@ -205,25 +211,28 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ * it uses the `updateFilteredPersonList` method to ask the `Model` to populate the 'filtered list' with _all_ persons.
    FYI, The 'filtered list' is the list of persons resulting from the most recent operation that will be shown to the user immediately after. For the `edit` command, we populate it with all the persons so that the user can see the edited person along with all other persons. If this was a `find` command, we would be setting that list to contain the search results instead.
    To provide some context, given below is the class diagram of the `Model` component. See if you can figure out where the 'filtered list' of persons is being tracked. -
    +
    * :bulb: This may be a good time to read through the [`Model` component section of the DG](../DeveloperGuide.html#model-component) 1. As you step through the rest of the statements in the `EditCommand#execute()` method, you'll see that it creates a `CommandResult` object (containing information about the result of the execution) and returns it.
    Advancing the debugger by one more step should take you back to the middle of the `LogicManager#execute()` method.
    1. Given that you have already seen quite a few classes in the `Logic` component in action, see if you can identify in this partial class diagram some of the classes you've encountered so far, and see how they fit into the class structure of the `Logic` component: - + + * :bulb: This may be a good time to read through the [`Logic` component section of the DG](../DeveloperGuide.html#logic-component) 1. Similar to before, you can step over/into statements in the `LogicManager#execute()` method to examine how the control is transferred to the `Storage` component and what happens inside that component. -
    :bulb: **Intellij Tip:** When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into. -
    + + + **Intellij Tip:** When trying to step into a statement such as `storage.saveAddressBook(model.getAddressBook())` which contains multiple method calls, Intellij will let you choose (by clicking) which one you want to step into. + -1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability): +1. As you step through the code inside the `Storage` component, you will eventually arrive at the `JsonAddressBook#saveAddressBook()` method which calls the `JsonSerializableAddressBook` constructor, to create an object that can be _serialized_ (i.e., stored in storage medium) in JSON format. That constructor is given below (with added line breaks for easier readability): **`JsonSerializableAddressBook` constructor:** - ``` java + ```java /** * Converts a given {@code ReadOnlyAddressBook} into this class for Jackson use. * @@ -243,7 +252,8 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ This is because regular Java objects need to go through an _adaptation_ for them to be suitable to be saved in JSON format. 1. While you are stepping through the classes in the `Storage` component, here is the component's class diagram to help you understand how those classes fit into the structure of the component.
    - + + * :bulb: This may be a good time to read through the [`Storage` component section of the DG](../DeveloperGuide.html#storage-component) 1. We can continue to step through until you reach the end of the `LogicManager#execute()` method and return to the `MainWindow#executeCommand()` method (the place where we put the original breakpoint). @@ -251,7 +261,7 @@ Recall from the User Guide that the `edit` command has the format: `edit INDEX [ 1. Stepping into `resultDisplay.setFeedbackToUser(commandResult.getFeedbackToUser());`, we end up in: **`ResultDisplay#setFeedbackToUser()`** - ``` java + ```java public void setFeedbackToUser(String feedbackToUser) { requireNonNull(feedbackToUser); resultDisplay.setText(feedbackToUser); diff --git a/gradle.properties b/gradle.properties index 40764dc1791..7c665812f01 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ org.gradle.parallel=false +org.gradle.crlf=lf org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Xmx1024m -Dfile.encoding=utf-8 # TODO: This is a workaround for a JDK11 bug which causes test coverage upload to fail. diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023e..41d9927a4d4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradlew b/gradlew index 1b6c787337f..1139e619203 100755 --- a/gradlew +++ b/gradlew @@ -232,3 +232,4 @@ eval "set -- $( )" '"$@"' exec "$JAVACMD" "$@" + diff --git a/gradlew.bat b/gradlew.bat index 107acd32c4e..9bd7eb66b4a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -87,3 +87,4 @@ exit /b 1 if "%OS%"=="Windows_NT" endlocal :omega + diff --git a/src/.idea/.gitignore b/src/.idea/.gitignore new file mode 100644 index 00000000000..26d33521af1 --- /dev/null +++ b/src/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/src/.idea/misc.xml b/src/.idea/misc.xml new file mode 100644 index 00000000000..30309d9d2a4 --- /dev/null +++ b/src/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/.idea/modules.xml b/src/.idea/modules.xml new file mode 100644 index 00000000000..a9622e65148 --- /dev/null +++ b/src/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/.idea/src.iml b/src/.idea/src.iml new file mode 100644 index 00000000000..bdf896d56a0 --- /dev/null +++ b/src/.idea/src.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/.idea/vcs.xml b/src/.idea/vcs.xml new file mode 100644 index 00000000000..54e4b961ee0 --- /dev/null +++ b/src/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/java/seedu/address/Main.java b/src/main/java/seedu/address/Main.java index ec1b7958746..eb6216546c8 100644 --- a/src/main/java/seedu/address/Main.java +++ b/src/main/java/seedu/address/Main.java @@ -34,7 +34,6 @@ public static void main(String[] args) { // The warning however, can be safely ignored. Thus, the following log informs // the user (if looking at the log output) that the said warning appearing in the log // can be ignored. - logger.warning("The warning about Unsupported JavaFX configuration below can be ignored."); Application.launch(MainApp.class, args); } diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index 3d6bd06d5af..d9bd24423c0 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -15,7 +15,6 @@ import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; import seedu.address.model.Model; import seedu.address.model.ModelManager; import seedu.address.model.ReadOnlyAddressBook; @@ -36,7 +35,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 2, 2, true); + public static final Version VERSION = new Version(1, 4, 1, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -86,8 +85,8 @@ private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) { initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); } catch (DataLoadingException e) { logger.warning("Data file at " + storage.getAddressBookFilePath() + " could not be loaded." - + " Will be starting with an empty AddressBook."); - initialData = new AddressBook(); + + " Will be starting with a sample AddressBook."); + initialData = SampleDataUtil.getSampleAddressBook(); } return new ModelManager(initialData, userPrefs); diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java index a97a86ee8d7..b91ee2d6183 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/address/commons/core/GuiSettings.java @@ -12,8 +12,8 @@ */ public class GuiSettings implements Serializable { - private static final double DEFAULT_HEIGHT = 600; - private static final double DEFAULT_WIDTH = 740; + private static final double DEFAULT_HEIGHT = 800; + private static final double DEFAULT_WIDTH = 1000; private final double windowWidth; private final double windowHeight; diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java deleted file mode 100644 index dd170d8b68d..00000000000 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ /dev/null @@ -1,69 +0,0 @@ -package seedu.address.commons.core.index; - -import seedu.address.commons.util.ToStringBuilder; - -/** - * Represents a zero-based or one-based index. - * - * {@code Index} should be used right from the start (when parsing in a new user input), so that if the current - * component wants to communicate with another component, it can send an {@code Index} to avoid having to know what - * base the other component is using for its index. However, after receiving the {@code Index}, that component can - * convert it back to an int if the index will not be passed to a different component again. - */ -public class Index { - private int zeroBasedIndex; - - /** - * Index can only be created by calling {@link Index#fromZeroBased(int)} or - * {@link Index#fromOneBased(int)}. - */ - private Index(int zeroBasedIndex) { - if (zeroBasedIndex < 0) { - throw new IndexOutOfBoundsException(); - } - - this.zeroBasedIndex = zeroBasedIndex; - } - - public int getZeroBased() { - return zeroBasedIndex; - } - - public int getOneBased() { - return zeroBasedIndex + 1; - } - - /** - * Creates a new {@code Index} using a zero-based index. - */ - public static Index fromZeroBased(int zeroBasedIndex) { - return new Index(zeroBasedIndex); - } - - /** - * Creates a new {@code Index} using a one-based index. - */ - public static Index fromOneBased(int oneBasedIndex) { - return new Index(oneBasedIndex - 1); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof Index)) { - return false; - } - - Index otherIndex = (Index) other; - return zeroBasedIndex == otherIndex.zeroBasedIndex; - } - - @Override - public String toString() { - return new ToStringBuilder(this).add("zeroBasedIndex", zeroBasedIndex).toString(); - } -} diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 92cd8fa605a..18d16a9d778 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -8,6 +8,7 @@ import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.group.Group; import seedu.address.model.person.Person; /** @@ -33,6 +34,8 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList getFilteredPersonList(); + ObservableList getFilteredGroupList(); + /** * 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..8b28747c12a 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -15,6 +15,7 @@ import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.group.Group; import seedu.address.model.person.Person; import seedu.address.storage.Storage; @@ -71,6 +72,11 @@ public ObservableList getFilteredPersonList() { return model.getFilteredPersonList(); } + @Override + public ObservableList getFilteredGroupList() { + return model.getFilteredGroupList(); + } + @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..15fe17e9022 100644 --- a/src/main/java/seedu/address/logic/Messages.java +++ b/src/main/java/seedu/address/logic/Messages.java @@ -5,6 +5,8 @@ import java.util.stream.Stream; import seedu.address.logic.parser.Prefix; +import seedu.address.model.group.Group; +import seedu.address.model.person.Name; import seedu.address.model.person.Person; /** @@ -18,6 +20,14 @@ public class Messages { public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; public static final String MESSAGE_DUPLICATE_FIELDS = "Multiple values specified for the following single-valued field(s): "; + public static final String MESSAGE_NO_PERSON_WITH_NAME_FOUND = "No person with such name found.\n" + + "Please provide the person's full name as in the existing contactlist."; + public static final java.lang.String MESSAGE_DUPLICATE_PERSON_IN_GROUP = + "Error, invalid input entered, unable to put the person into group"; + public static final String MESSAGE_NO_GROUP_WITH_NAME_FOUND = "No group with such name found.\n" + + "Please provide the group's full name as in the existing contactlist."; + + /** * Returns an error message indicating the duplicate prefixes. @@ -27,7 +37,6 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref Set duplicateFields = Stream.of(duplicatePrefixes).map(Prefix::toString).collect(Collectors.toSet()); - return MESSAGE_DUPLICATE_FIELDS + String.join(" ", duplicateFields); } @@ -41,11 +50,27 @@ public static String format(Person person) { .append(person.getPhone()) .append("; Email: ") .append(person.getEmail()) - .append("; Address: ") - .append(person.getAddress()) - .append("; Tags: "); - person.getTags().forEach(builder::append); + .append("; Groups: "); + person.getGroups().toStream().map(Group::getGroupName).forEach(builder::append); + return builder.toString(); + } + + /** + * Formats the {@code group} for display to the user. + */ + public static String format(Group group) { + final StringBuilder builder = new StringBuilder(); + builder.append(group.getGroupName()); return builder.toString(); } + /** + * Formats the {@code name} for display to the user. + */ + public static String format(Name personName) { + //add print function + return personName.fullName; + } + + } diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index 5d7185a9680..707ddd6b372 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,11 +1,10 @@ package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; import seedu.address.commons.util.ToStringBuilder; import seedu.address.logic.Messages; @@ -20,23 +19,24 @@ 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. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\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"; + 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_GROUPTAG + "GROUPNAME \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_PHONE + "98765432 " + + PREFIX_EMAIL + "johnd@example.com " + + PREFIX_GROUPTAG + "CS2103T"; 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_DUPLICATE_PERSON = "%1$s is already in the contact list"; + public static final String MESSAGE_DUPLICATE_EMAIL = "This email: %1$s already belongs to some " + + "one in the contact list"; + public static final String MESSAGE_DUPLICATE_PHONE = "This phone number: %1$s belongs to some one " + + "in the contact list"; private final Person toAdd; @@ -53,10 +53,28 @@ public CommandResult execute(Model model) throws CommandException { requireNonNull(model); if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + throw new CommandException(String.format(MESSAGE_DUPLICATE_PERSON, toAdd.getName().toString())); + } + + if (model.hasEmail(toAdd)) { + throw new CommandException(String.format(MESSAGE_DUPLICATE_EMAIL, toAdd.getEmail().toString())); + } + + if (model.hasPhone(toAdd)) { + throw new CommandException(String.format(MESSAGE_DUPLICATE_PHONE, toAdd.getPhone().toString())); } model.addPerson(toAdd); + toAdd.getGroups().toStream().findFirst().ifPresent(group -> { + if (!model.hasGroup(group)) { + model.addGroup(group); + } + try { + model.findGroup(group.getGroupName()).addPerson(toAdd); + } catch (CommandException e) { + throw new RuntimeException(e); + } + }); return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); } @@ -78,7 +96,7 @@ public boolean equals(Object other) { @Override public String toString() { return new ToStringBuilder(this) - .add("toAdd", toAdd) - .toString(); + .add("toAdd", toAdd) + .toString(); } } diff --git a/src/main/java/seedu/address/logic/commands/AddGroupCommand.java b/src/main/java/seedu/address/logic/commands/AddGroupCommand.java new file mode 100644 index 00000000000..c0564075ded --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddGroupCommand.java @@ -0,0 +1,71 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; + +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.group.Group; + +/** + * Adds a group to projectPRO + */ +public class AddGroupCommand extends Command { + public static final String COMMAND_WORD = "new"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a group to the address book. \n" + + "Parameters: " + + PREFIX_GROUPTAG + "GROUPNAME \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_GROUPTAG + "CS2103T"; + + public static final String MESSAGE_SUCCESS = "New group added: %1$s"; + public static final String MESSAGE_DUPLICATE_GROUP = "This group already exists in the address book"; + + private final Group toAdd; + + /** + * Creates an newCommand to add the specified {@code Group} + */ + public AddGroupCommand(Group group) { + requireNonNull(group); + toAdd = group; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddGroupCommand)) { + return false; + } + + AddGroupCommand otherAddCommand = (AddGroupCommand) other; + return toAdd.equals(otherAddCommand.toAdd); + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + if (model.hasGroup(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_GROUP); + } + + model.addGroup(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd))); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAdd", toAdd) + .toString(); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/AddGroupMeetingTimeCommand.java b/src/main/java/seedu/address/logic/commands/AddGroupMeetingTimeCommand.java new file mode 100644 index 00000000000..67cde9d27e0 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddGroupMeetingTimeCommand.java @@ -0,0 +1,91 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FREETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; + +import java.util.ArrayList; + +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.TimeInterval; +import seedu.address.model.group.Group; + +/** + * Adds meeting time(s) to a group. + */ +public class AddGroupMeetingTimeCommand extends Command { + public static final String COMMAND_WORD = "addmeeting"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Add free time to a group.\n" + + "Parameters: " + + PREFIX_GROUPTAG + "GROUP " + + PREFIX_FREETIME + "MEETING_TIME \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_GROUPTAG + "CS2103T " + + PREFIX_FREETIME + "mon 1200 - mon 1400 " + PREFIX_FREETIME + "wed 1000 - wed 1600"; + + public static final String MESSAGE_NO_GROUP_WITH_NAME_FOUND = "No group with such name found.\n" + + "Please provide the group's full name as in the existing contact list."; + + public static final String MESSAGE_SUCCESS = "Free time status for: %1$s \n"; + + private final Group toAdd; + private final ArrayList toAddFreeTime; + + /** + * AddGroupMeetingTimeCommand constructor. + * @param toAdd The group object to be added to. + * @param toAddFreeTime ArrayList of time intervals to be added to group. + */ + public AddGroupMeetingTimeCommand(Group toAdd, ArrayList toAddFreeTime) { + requireNonNull(toAdd); + requireNonNull(toAddFreeTime); + this.toAddFreeTime = toAddFreeTime; + this.toAdd = toAdd; + } + + /** + * Executes the AddGroupMeetingTimeCommand. + * + * @param model {@code Model} which the command should operate on. + * @return Result of command. + * @throws CommandException if there is an error. + */ + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + String status; + if (model.hasGroup(toAdd)) { + status = model.addTimeToGroup(toAdd, toAddFreeTime); + } else { + throw new CommandException(MESSAGE_NO_GROUP_WITH_NAME_FOUND); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS + status, Messages.format(toAdd))); + } + + /** + * Checks if an AddGroupMeetingTimeCommand object is the same as another object. + * + * @param other The other object. + * @return Whether the two objects are equal. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddGroupMeetingTimeCommand)) { + return false; + } + + AddGroupMeetingTimeCommand otherAddGroupMeetingTimeCommand = (AddGroupMeetingTimeCommand) other; + return toAdd.equals(otherAddGroupMeetingTimeCommand.toAdd) + && toAddFreeTime.equals(otherAddGroupMeetingTimeCommand.toAddFreeTime); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddTimeCommand.java b/src/main/java/seedu/address/logic/commands/AddTimeCommand.java new file mode 100644 index 00000000000..69674c9f4bb --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddTimeCommand.java @@ -0,0 +1,59 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FREETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.ArrayList; + +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.TimeInterval; +import seedu.address.model.person.Name; + +/** + * Adds free time to person. + */ +public class AddTimeCommand extends Command { + + public static final String COMMAND_WORD = "addtime"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds free time to an existing person. \n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_FREETIME + "FREE_TIME \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "Alex Yeoh " + + PREFIX_FREETIME + "mon 1200 - mon 1400 " + PREFIX_FREETIME + "tue 1000 - wed 1600"; + + public static final String MESSAGE_SUCCESS = "Free time status for: %1$s \n"; + + private final ArrayList toAddFreeTimes; + private final Name toAddPerson; + + /** + * AddTimeCommand constructor. + * @param toAddPerson The person object to be added to. + * @param toAddFreeTimes ArrayList of time intervals to be added to person. + */ + public AddTimeCommand(Name toAddPerson, ArrayList toAddFreeTimes) { + requireNonNull(toAddPerson); + requireNonNull(toAddFreeTimes); + this.toAddFreeTimes = toAddFreeTimes; + this.toAddPerson = toAddPerson; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + String status; + if (model.hasPerson(toAddPerson)) { + status = model.addTimeToPerson(toAddPerson, toAddFreeTimes); + } else { + throw new CommandException("Person does not exists"); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS + status, Messages.format(toAddPerson))); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index 9c86b1fa6e4..aa8649ce597 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -11,6 +11,10 @@ public class ClearCommand extends Command { public static final String COMMAND_WORD = "clear"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Clears the address book of all persons, groups, and times. " + + "Should not contain any extra inputs.\n" + + "Example: " + COMMAND_WORD; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 64f18992160..52ee2b06430 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -1,6 +1,7 @@ package seedu.address.logic.commands; import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; /** @@ -15,6 +16,6 @@ public abstract class Command { * @return feedback message of the operation result for display * @throws CommandException If an error occurs during command execution. */ - public abstract CommandResult execute(Model model) throws CommandException; + public abstract CommandResult execute(Model model) throws CommandException, ParseException; } diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java index 249b6072d0d..83b024dd522 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/address/logic/commands/CommandResult.java @@ -13,10 +13,14 @@ public class CommandResult { private final String feedbackToUser; - /** Help information should be shown to the user. */ + /** + * Help information should be shown to the user. + */ private final boolean showHelp; - /** The application should exit. */ + /** + * The application should exit. + */ private final boolean exit; /** @@ -61,8 +65,8 @@ public boolean equals(Object other) { CommandResult otherCommandResult = (CommandResult) other; return feedbackToUser.equals(otherCommandResult.feedbackToUser) - && showHelp == otherCommandResult.showHelp - && exit == otherCommandResult.exit; + && showHelp == otherCommandResult.showHelp + && exit == otherCommandResult.exit; } @Override @@ -73,10 +77,10 @@ public int hashCode() { @Override public String toString() { return new ToStringBuilder(this) - .add("feedbackToUser", feedbackToUser) - .add("showHelp", showHelp) - .add("exit", exit) - .toString(); + .add("feedbackToUser", feedbackToUser) + .add("showHelp", showHelp) + .add("exit", exit) + .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..35c9852b68f 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -1,69 +1,42 @@ package seedu.address.logic.commands; -import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import java.util.List; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; -import seedu.address.model.person.Person; /** * Deletes a person identified using it's displayed index from the address book. */ -public class DeleteCommand extends Command { +public abstract class DeleteCommand extends Command { public static final String COMMAND_WORD = "delete"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; + + ": Deletes the person/group with the name provided.\n" + + "Use 'delete n/NAME' to delete a person and 'delete g/GROUPNAME' to delete a group.\n" + + "Parameters: " + PREFIX_NAME + + "NAME (full name of an existing person)\n" + + "Parameters: " + PREFIX_GROUPTAG + + "GROUPNAME (full name of an existing group)\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "Nicholas Lee \n" + + "Example: " + COMMAND_WORD + " " + PREFIX_GROUPTAG + "CS2103T"; + + public static final String MESSAGE_TWO_PARAMETERS = "Multiple prefixes! " + + COMMAND_WORD + " can only handle one person/group at a time.\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "Nicholas Lee \n" + + "Example: " + COMMAND_WORD + " " + PREFIX_GROUPTAG + "CS2103T"; + + public DeleteCommand() { } @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete))); - } + public abstract CommandResult execute(Model model) throws CommandException; @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof DeleteCommand)) { - return false; - } - - DeleteCommand otherDeleteCommand = (DeleteCommand) other; - return targetIndex.equals(otherDeleteCommand.targetIndex); - } + public abstract boolean equals(Object other); @Override - public String toString() { - return new ToStringBuilder(this) - .add("targetIndex", targetIndex) - .toString(); - } + public abstract String toString(); } diff --git a/src/main/java/seedu/address/logic/commands/DeleteGroupCommand.java b/src/main/java/seedu/address/logic/commands/DeleteGroupCommand.java new file mode 100644 index 00000000000..04f8fb4f7ff --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteGroupCommand.java @@ -0,0 +1,50 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; + +/** + * Removes group from the addressbook and person. + */ +public class DeleteGroupCommand extends DeleteCommand { + public static final String MESSAGE_DELETE_GROUP_SUCCESS = "Deleted Group: %1$s"; + private final String groupName; + + public DeleteGroupCommand(String groupName) { + this.groupName = groupName; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Group groupToDelete = model.deleteGroup(this.groupName); + + return new CommandResult(String.format(MESSAGE_DELETE_GROUP_SUCCESS, groupToDelete.getGroupName())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteGroupCommand)) { + return false; + } + + DeleteGroupCommand otherDeleteGroupCommand = (DeleteGroupCommand) other; + return groupName.equals(otherDeleteGroupCommand.groupName); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("group name", groupName) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteGroupTimeCommand.java b/src/main/java/seedu/address/logic/commands/DeleteGroupTimeCommand.java new file mode 100644 index 00000000000..1fe9e9bf530 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteGroupTimeCommand.java @@ -0,0 +1,64 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.TimeInterval; +import seedu.address.model.group.Group; + +/** + * Deletes a group's meeting time + */ +public class DeleteGroupTimeCommand extends DeleteTimeCommand { + private final ArrayList timeIntervalsToDelete; + private final Group group; + + /** + * Creates a DeleteGroupTimeCommand to Delete the specified {@code timeIntervalsToDelete} + */ + public DeleteGroupTimeCommand(Group group, ArrayList timeIntervalsToDelete) { + requireNonNull(group); + requireNonNull(timeIntervalsToDelete); + this.group = group; + this.timeIntervalsToDelete = timeIntervalsToDelete; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + String commandOutcome; + if (model.hasGroup(group)) { + commandOutcome = model.deleteTimeFromGroup(group, timeIntervalsToDelete); + } else { + throw new CommandException("Group does not exists"); + } + return new CommandResult(String.format(MESSAGE_DELETE_TIME + "\n" + commandOutcome, group.getGroupName())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeleteGroupTimeCommand)) { + return false; + } + + DeleteGroupTimeCommand otherDeleteGroupTimeCommand = (DeleteGroupTimeCommand) other; + if (timeIntervalsToDelete.size() != otherDeleteGroupTimeCommand.timeIntervalsToDelete.size()) { + return false; + } + boolean isSameArray = true; + for (int i = 0; i < timeIntervalsToDelete.size(); i++) { + isSameArray = isSameArray && timeIntervalsToDelete.get(i).equals( + otherDeleteGroupTimeCommand.timeIntervalsToDelete.get(i)); + } + return group.nameEquals(otherDeleteGroupTimeCommand.group.getGroupName()) + && isSameArray; + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeletePersonCommand.java b/src/main/java/seedu/address/logic/commands/DeletePersonCommand.java new file mode 100644 index 00000000000..dd7d714086e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeletePersonCommand.java @@ -0,0 +1,50 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.person.Person; + +/** + * Deletes person from the addressbook and group. + */ +public class DeletePersonCommand extends DeleteCommand { + public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + private final String personName; + + public DeletePersonCommand(String personName) { + this.personName = personName; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Person personToDelete = model.deletePerson(this.personName); + + return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete.getName().fullName)); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeletePersonCommand)) { + return false; + } + + DeletePersonCommand otherDeletePersonCommand = (DeletePersonCommand) other; + return personName.equals(otherDeletePersonCommand.personName); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("name", personName) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeletePersonTimeCommand.java b/src/main/java/seedu/address/logic/commands/DeletePersonTimeCommand.java new file mode 100644 index 00000000000..a919910f275 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeletePersonTimeCommand.java @@ -0,0 +1,67 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.TimeInterval; +import seedu.address.model.person.Name; + + + +/** + * Deletes a person's free time + */ +public class DeletePersonTimeCommand extends DeleteTimeCommand { + private final ArrayList timeIntervalsToDelete; + private final Name personName; + + /** + * Creates a DeletePersonTimeCommand to Delete the specified {@code timeIntervalsToDelete} + */ + public DeletePersonTimeCommand(Name personName, ArrayList timeIntervalsToDelete) { + requireNonNull(personName); + requireNonNull(timeIntervalsToDelete); + this.personName = personName; + this.timeIntervalsToDelete = timeIntervalsToDelete; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + String commandOutcome; + if (model.hasPerson(personName)) { + commandOutcome = model.deleteTimeFromPerson(personName, timeIntervalsToDelete); + } else { + throw new CommandException("Person does not exist \n"); + } + return new CommandResult(String.format(MESSAGE_DELETE_TIME + "\n" + commandOutcome, personName.toString())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof DeletePersonTimeCommand)) { + return false; + } + + DeletePersonTimeCommand otherDeletePersonTimeCommand = (DeletePersonTimeCommand) other; + if (timeIntervalsToDelete.size() != otherDeletePersonTimeCommand.timeIntervalsToDelete.size()) { + return false; + } + boolean isSameArray = true; + for (int i = 0; i < timeIntervalsToDelete.size(); i++) { + isSameArray = isSameArray && timeIntervalsToDelete.get(i).equals( + otherDeletePersonTimeCommand.timeIntervalsToDelete.get(i)); + } + return personName.equals(otherDeletePersonTimeCommand.personName) + && isSameArray; + } + +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteTimeCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTimeCommand.java new file mode 100644 index 00000000000..2387026f7fa --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteTimeCommand.java @@ -0,0 +1,34 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_FREETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Deletes time from the individual and group. + */ +public abstract class DeleteTimeCommand extends Command { + public static final String MESSAGE_DELETE_TIME = "Attempted to delete Time from: %1$s"; + public static final String COMMAND_WORD = "deletetime"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes free time from an existing person or group. \n" + + "For person, parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_FREETIME + "FREE TIME \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "Alex Yeoh " + + PREFIX_FREETIME + "mon 1200 - mon 1400 " + PREFIX_FREETIME + "tue 1000 - wed 1600 \n" + + "For group, parameters: " + + PREFIX_GROUPTAG + "GROUPNAME " + + PREFIX_FREETIME + "FREE TIME \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_GROUPTAG + "CS2103T " + + PREFIX_FREETIME + "mon 1200 - mon 1400 " + PREFIX_FREETIME + "tue 1000 - wed 1600"; + + @Override + public abstract CommandResult execute(Model model) throws CommandException; + + +} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index 4b581c7331e..00000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,242 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -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.CollectionUtil; -import seedu.address.commons.util.ToStringBuilder; -import seedu.address.logic.Messages; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - 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) " - + "[" + 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"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.setPerson(personToEdit, editedPerson); - 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}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - 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); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - EditCommand otherEditCommand = (EditCommand) other; - return index.equals(otherEditCommand.index) - && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("index", index) - .add("editPersonDescriptor", editPersonDescriptor) - .toString(); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - 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); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - EditPersonDescriptor otherEditPersonDescriptor = (EditPersonDescriptor) 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); - } - - @Override - public String toString() { - return new ToStringBuilder(this) - .add("name", name) - .add("phone", phone) - .add("email", email) - .add("address", address) - .add("tags", tags) - .toString(); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index 3dd85a8ba90..bfd1a5766b7 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -9,7 +9,11 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Exits ProjectPRO. " + + "Should not contain any extra inputs.\n" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting ProjectPRO as requested ..."; @Override public CommandResult execute(Model model) { diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index 72b9eddd3a7..6b019c7b1ce 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -1,58 +1,41 @@ package seedu.address.logic.commands; -import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -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.NameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. + * Lists all the people either in a specific group or whose name contains any keywords. */ -public class FindCommand extends Command { +public abstract class FindCommand extends Command { - public static final String COMMAND_WORD = "find"; + public static final java.lang.String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + public static final java.lang.String MESSAGE_USAGE = COMMAND_WORD + + ": Finds all persons whose names contain any of the specified keywords (case insensitive)\n" + + "or all persons in a specified group (case sensitive) and displays them as a list.\n" + + "Parameters: " + PREFIX_NAME + + "KEYWORDS_IN_NAME (case insensitive)\n" + + "Parameters: " + PREFIX_GROUPTAG + + "GROUPNAME (full name of an existing group, case sensitive)\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "alice alex john \n" + + "Example: " + COMMAND_WORD + " " + PREFIX_GROUPTAG + "CS2103T"; - private final NameContainsKeywordsPredicate predicate; + public static final String MESSAGE_TWO_PARAMETERS = "Multiple prefixes! " + + COMMAND_WORD + " can only handle one person/group at a time.\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "nicholas\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_GROUPTAG + "CS2103T"; - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } + public static final String MESSAGE_EMPTY_NAME = "Person's name cannot be empty"; @Override - public CommandResult execute(Model model) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } + public abstract CommandResult execute(Model model) throws CommandException; @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof FindCommand)) { - return false; - } - - FindCommand otherFindCommand = (FindCommand) other; - return predicate.equals(otherFindCommand.predicate); - } + public abstract boolean equals(Object other); @Override - public String toString() { - return new ToStringBuilder(this) - .add("predicate", predicate) - .toString(); - } + public abstract String toString(); } diff --git a/src/main/java/seedu/address/logic/commands/FindFreeTimeCommand.java b/src/main/java/seedu/address/logic/commands/FindFreeTimeCommand.java new file mode 100644 index 00000000000..0b160eb3b30 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindFreeTimeCommand.java @@ -0,0 +1,102 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Duration; +import seedu.address.model.Model; +import seedu.address.model.TimeIntervalList; +import seedu.address.model.group.Group; +import seedu.address.model.group.exceptions.GroupNotFoundException; + +/** + * Adds a person to a group. + */ +public class FindFreeTimeCommand extends Command { + + public static final String COMMAND_WORD = "findfreetime"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds a free time slot given a group and duration \n" + + "Parameters: " + PREFIX_GROUPTAG + "GROUPNAME " + PREFIX_DURATION + "DURATION"; + + public static final String MESSAGE_DURATION_USAGE = "Enter Duration in minutes"; + + + public static final String MESSAGE_NOT_ALL_FREE = "%s has not input their free time yet\n"; + + public static final String MESSAGE_SUCCESS = "These are the available timeslots \n"; + public static final String MESSAGE_INTERVAL_DISPLAY = "%d. %s\n"; + + private final String groupName; + private final Duration duration; + + + /** + * Creates an AddCommand to add the specified {@code Person} + */ + public FindFreeTimeCommand(String groupName, Duration duration) { + requireNonNull(groupName); + requireNonNull(duration); + this.groupName = groupName; + this.duration = duration; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + // 3 steps + // find group, if group exists check everybody input time, use find free time algo + requireNonNull(model); + Group g; + // br stores message + StringBuilder br = new StringBuilder(); + int intervalCounter = 1; + try { + g = model.findGroup(groupName); + } catch (GroupNotFoundException e) { + throw new CommandException(e.getMessage()); + } + + // check everybody input time, modify br should somebody not key in their free time + g.areAllFree(br, MESSAGE_NOT_ALL_FREE); + if (br.length() != 0) { + throw new CommandException(br.toString()); + } + + // use algorithm to findFreeTime() + TimeIntervalList freeTime = g.findFreeTime(duration); + freeTime.getMessage(br, MESSAGE_INTERVAL_DISPLAY); + + return new CommandResult(MESSAGE_SUCCESS + br.toString()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindFreeTimeCommand)) { + return false; + } + + FindFreeTimeCommand otherFindFreeTimeCommand = (FindFreeTimeCommand) other; + // to check + return this.duration.getDurationInMin() == otherFindFreeTimeCommand.duration.getDurationInMin() + && this.groupName.equals(otherFindFreeTimeCommand.groupName); + + } + + // to fix + @Override + public String toString() { + return new ToStringBuilder(this) + .add("group name", groupName) + .add("duration", duration.toString()) + .toString(); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/FindGroupCommand.java b/src/main/java/seedu/address/logic/commands/FindGroupCommand.java new file mode 100644 index 00000000000..29d96ee6d06 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindGroupCommand.java @@ -0,0 +1,70 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.function.Predicate; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.person.Person; + + + +/** + * Finds and lists all persons in address book who is inside the specified Group. + * Displays group remarks of the specified group. + * Keyword matching is case sensitive. + */ +public class FindGroupCommand extends FindCommand { + + public static final String MESSAGE_GROUP_FOUND = "All persons from Group %1$s listed!\n" + + "Group remarks for %1$s: \n" + + "%2$s"; + private final String groupName; + + /** + * @param groupName of the group to find the groupmates and remark of + */ + public FindGroupCommand(String groupName) { + this.groupName = groupName; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Group groupToFind = model.findGroup(this.groupName); + + //List all people in group + Predicate inGroupPred = (p) -> p.containsGroup(groupToFind); + model.updateFilteredPersonList(inGroupPred); + + //Display group remark + return new CommandResult( + java.lang.String.format(MESSAGE_GROUP_FOUND, + groupToFind.getGroupName(), groupToFind.getGroupRemark())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindGroupCommand)) { + return false; + } + + FindGroupCommand otherFindGroupCommand = (FindGroupCommand) other; + return groupName.equals(otherFindGroupCommand.groupName); + } + + @Override + public java.lang.String toString() { + return new ToStringBuilder(this) + .add("group name", groupName) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/FindPersonCommand.java b/src/main/java/seedu/address/logic/commands/FindPersonCommand.java new file mode 100644 index 00000000000..be8a6bc173f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/FindPersonCommand.java @@ -0,0 +1,53 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.Messages; +import seedu.address.model.Model; +import seedu.address.model.person.NameContainsKeywordsPredicate; + + +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class FindPersonCommand extends FindCommand { + + private final NameContainsKeywordsPredicate predicate; + + public FindPersonCommand(NameContainsKeywordsPredicate predicate) { + this.predicate = predicate; + } + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + model.updateFilteredPersonList(predicate); + return new CommandResult( + java.lang.String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, + model.getFilteredPersonList().size())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof FindPersonCommand)) { + return false; + } + + FindPersonCommand otherFindPersonCommand = (FindPersonCommand) other; + return predicate.equals(otherFindPersonCommand.predicate); + } + + @Override + public java.lang.String toString() { + return new ToStringBuilder(this) + .add("predicate", predicate) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/GroupPersonCommand.java b/src/main/java/seedu/address/logic/commands/GroupPersonCommand.java new file mode 100644 index 00000000000..67c205963f8 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/GroupPersonCommand.java @@ -0,0 +1,76 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import javafx.util.Pair; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.person.Person; + +/** + * Adds a person to a group. + */ +public class GroupPersonCommand extends Command { + + public static final String COMMAND_WORD = "group"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to a group in " + + "the address book.\n" + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_GROUPTAG + "GROUP "; + + public static final String MESSAGE_SUCCESS = "%1$s is now a part of %2$s"; + + private final String personName; + private final String groupName; + + + /** + * Creates an AddCommand to add the specified {@code Person} + */ + public GroupPersonCommand(String personName, String groupName) { + requireNonNull(personName); + requireNonNull(groupName); + this.personName = personName; + this.groupName = groupName; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + + Pair output = model.groupPerson(this.personName, this.groupName); + Person person = output.getKey(); + Group group = output.getValue(); + return new CommandResult(String.format(MESSAGE_SUCCESS, person.getName().fullName, group.getGroupName())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GroupPersonCommand)) { + return false; + } + + GroupPersonCommand otherGroupPersonCommand = (GroupPersonCommand) other; + + return this.personName.equals(otherGroupPersonCommand.personName) + && this.groupName.equals(otherGroupPersonCommand.groupName); + + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toAddToGroup", "") + .toString(); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/GroupRemarkCommand.java b/src/main/java/seedu/address/logic/commands/GroupRemarkCommand.java new file mode 100644 index 00000000000..d54110499d3 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/GroupRemarkCommand.java @@ -0,0 +1,71 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPREMARK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupRemark; + +/** + * Changes the remark of an existing group in the address book. + */ +public class GroupRemarkCommand extends Command { + + public static final String COMMAND_WORD = "remark"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the remark of the group identified " + + "by the name of the group. " + + "Existing remark will be overwritten by the input.\n" + + "Parameters: " + + PREFIX_GROUPTAG + "GROUPNAME " + + PREFIX_GROUPREMARK + "REMARK\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_GROUPTAG + "CS2103T " + + PREFIX_GROUPREMARK + "Finals on 30 Nov"; + + public static final String MESSAGE_SUCCESS = "Added remark to group %1$s: %2$s"; + private final String groupName; + private final GroupRemark groupRemark; + + /** + * GroupRemarkCommand constructor. + * @param groupName of the group to edit the remark + * @param groupRemark of the group to be updated to + */ + public GroupRemarkCommand(String groupName, GroupRemark groupRemark) { + requireAllNonNull(groupName, groupRemark); + + this.groupName = groupName; + this.groupRemark = groupRemark; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Group editedGroup = model.addGroupRemark(this.groupName, this.groupRemark); + return new CommandResult(String.format(MESSAGE_SUCCESS, editedGroup.getGroupName(), + editedGroup.getGroupRemark())); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GroupRemarkCommand)) { + return false; + } + + // state check + GroupRemarkCommand e = (GroupRemarkCommand) other; + return groupRemark.equals(e.groupRemark) + && groupName.equals(e.groupName); + } +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index bf824f91bd0..bc10bb6e6fa 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -9,7 +9,8 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions. " + + "Should not contain any extra inputs.\n" + "Example: " + COMMAND_WORD; public static final String SHOWING_HELP_MESSAGE = "Opened help window."; diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 84be6ad2596..0e1d69bc5ed 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -14,6 +14,10 @@ public class ListCommand extends Command { public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all persons in the contact list. " + + "Should not contain any extra inputs.\n" + + "Example: " + COMMAND_WORD; + @Override public CommandResult execute(Model model) { diff --git a/src/main/java/seedu/address/logic/commands/ListGroupCommand.java b/src/main/java/seedu/address/logic/commands/ListGroupCommand.java new file mode 100644 index 00000000000..7e78d4db6d0 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListGroupCommand.java @@ -0,0 +1,34 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import javafx.collections.ObservableList; +import seedu.address.model.Model; +import seedu.address.model.group.Group; + +/** + * Lists all groups in the address book to the user. + */ +public class ListGroupCommand extends Command { + + public static final String COMMAND_WORD = "listgroup"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all groups in the contact list. " + + "Should not contain any extra inputs.\n" + + "Example: " + COMMAND_WORD; + + public static final StringBuilder MESSAGE_SUCCESS = new StringBuilder(); + + + @Override + public CommandResult execute(Model model) { + requireNonNull(model); + MESSAGE_SUCCESS.setLength(0); + MESSAGE_SUCCESS.append("Groups in address book:\n"); + ObservableList groupList = model.getFilteredGroupList(); + groupList.stream().forEach( + g -> MESSAGE_SUCCESS.append(g.getGroupName() + "\n") + ); + return new CommandResult(MESSAGE_SUCCESS.toString()); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListTimeCommand.java b/src/main/java/seedu/address/logic/commands/ListTimeCommand.java new file mode 100644 index 00000000000..825248f0aaa --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListTimeCommand.java @@ -0,0 +1,37 @@ +package seedu.address.logic.commands; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Lists the time of a person or group + */ +public abstract class ListTimeCommand extends Command { + + public static final String COMMAND_WORD = "listtime"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Lists the times of the person/group with the name provided.\n" + + "Use 'listtime n/NAME' to list time of a person and 'listtime g/GROUPNAME' to list time of a group.\n" + + "Parameters: " + PREFIX_NAME + + "NAME (full name of an existing person)\n" + + "Parameters: " + PREFIX_GROUPTAG + + "GROUPNAME (full name of an existing group)\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "Nicholas Lee \n" + + "Example: " + COMMAND_WORD + " " + PREFIX_GROUPTAG + "CS2103T"; + + public ListTimeCommand() { + } + + @Override + public abstract CommandResult execute(Model model) throws CommandException; + + @Override + public abstract boolean equals(Object other); + + @Override + public abstract String toString(); +} diff --git a/src/main/java/seedu/address/logic/commands/ListTimeGroupCommand.java b/src/main/java/seedu/address/logic/commands/ListTimeGroupCommand.java new file mode 100644 index 00000000000..3dd580f285e --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListTimeGroupCommand.java @@ -0,0 +1,59 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.TimeIntervalList; +import seedu.address.model.group.Group; + +/** + * Lists the time of a group + */ +public class ListTimeGroupCommand extends ListTimeCommand { + public static final String MESSAGE_LISTTIME_GROUP_SUCCESS = "Listed times of Group: %1$s"; + public static final String MESSAGE_NO_GROUP_WITH_NAME_FOUND = "No group with such name found.\n" + + "Please provide the group's full name as in the existing contactlist."; + private final Group group; + + /** + * Creates a ListTimeGroupCommand to list the times of the group. + */ + public ListTimeGroupCommand(Group group) { + requireNonNull(group); + this.group = group; + } + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (!model.hasGroup(group)) { + throw new CommandException(MESSAGE_NO_GROUP_WITH_NAME_FOUND); + } + TimeIntervalList timeIntervalList = model.getTimeFromGroup(group); + return new CommandResult(String.format(MESSAGE_LISTTIME_GROUP_SUCCESS, + group.getGroupName()) + timeIntervalList); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ListTimeGroupCommand)) { + return false; + } + + ListTimeGroupCommand otherListTimeGroupCommand = (ListTimeGroupCommand) other; + return group.equals(otherListTimeGroupCommand.group); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("group name", group) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ListTimePersonCommand.java b/src/main/java/seedu/address/logic/commands/ListTimePersonCommand.java new file mode 100644 index 00000000000..1f925e390db --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ListTimePersonCommand.java @@ -0,0 +1,59 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.TimeIntervalList; +import seedu.address.model.person.Name; + +/** + * Lists the time of a person + */ +public class ListTimePersonCommand extends ListTimeCommand { + public static final String MESSAGE_LISTTIME_PERSON_SUCCESS = "Listed times of Person: %1$s"; + public static final String MESSAGE_NO_PERSON_WITH_NAME_FOUND = "No person with such name found.\n" + + "Please provide the person's full name as in the existing contactlist."; + private final Name personName; + + /** + * Creates a ListTimePersonCommand to list the times of the person. + */ + public ListTimePersonCommand(Name personName) { + requireNonNull(personName); + this.personName = personName; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + if (!model.hasPerson(personName)) { + throw new CommandException(MESSAGE_NO_PERSON_WITH_NAME_FOUND); + } + TimeIntervalList freetime = model.getTimeFromPerson(personName); + return new CommandResult(String.format(MESSAGE_LISTTIME_PERSON_SUCCESS, personName.fullName) + freetime); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ListTimePersonCommand)) { + return false; + } + + ListTimePersonCommand otherListTimePersonCommand = (ListTimePersonCommand) other; + return personName.equals(otherListTimePersonCommand.personName); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("person name", personName) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/UngroupPersonCommand.java b/src/main/java/seedu/address/logic/commands/UngroupPersonCommand.java new file mode 100644 index 00000000000..37c32dac4ca --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/UngroupPersonCommand.java @@ -0,0 +1,75 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import javafx.util.Pair; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.group.Group; +import seedu.address.model.person.Person; + +/** + * Removes a person from a group. + */ +public class UngroupPersonCommand extends Command { + + public static final String COMMAND_WORD = "ungroup"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Removes a person from a group in the address book.\n" + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_GROUPTAG + "GROUP "; + + public static final String MESSAGE_SUCCESS = "%1$s is no longer a part of %2$s"; + private final String personName; + private final String groupName; + + + /** + * Creates an UngroupPersonCommand to add the specified {@code Person} + * to the specified {@code Group} + */ + public UngroupPersonCommand(String personName, String groupName) { + requireNonNull(personName); + requireNonNull(groupName); + this.personName = personName; + this.groupName = groupName; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + Pair output = model.ungroupPerson(this.personName, this.groupName); + Person person = output.getKey(); + Group group = output.getValue(); + + return new CommandResult(String.format(MESSAGE_SUCCESS, person.getName().fullName, group.getGroupName())); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof UngroupPersonCommand)) { + return false; + } + + UngroupPersonCommand otherUngroupPersonCommand = (UngroupPersonCommand) other; + return this.personName.equals(otherUngroupPersonCommand.personName) + && this.groupName.equals(otherUngroupPersonCommand.groupName); + + } + + // to fix + @Override + public String toString() { + return new ToStringBuilder(this) + .add("toRemoveFromGroup", "") + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 4ff1a97ed77..0b30cffa125 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -1,23 +1,21 @@ 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_GROUPTAG; import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import java.util.Set; import java.util.stream.Stream; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupList; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; /** * Parses input arguments and creates a new AddCommand object @@ -30,22 +28,26 @@ public class AddCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public AddCommand parse(String args) throws ParseException { + GroupList groupList = new GroupList(); 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_GROUPTAG); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, 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_GROUPTAG); 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); + if (arePrefixesPresent(argMultimap, PREFIX_GROUPTAG)) { + Group group = ParserUtil.parseGroup(argMultimap.getValue(PREFIX_GROUPTAG).get()); + groupList.add(group); + } + + Person person = new Person(name, phone, email, groupList); return new AddCommand(person); } diff --git a/src/main/java/seedu/address/logic/parser/AddGroupCommandParser.java b/src/main/java/seedu/address/logic/parser/AddGroupCommandParser.java new file mode 100644 index 00000000000..ec7d199f8c2 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddGroupCommandParser.java @@ -0,0 +1,44 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddGroupCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.group.Group; + +/** + * Parses input arguments and creates a new newCommand object + */ +public class AddGroupCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CreateGroupCommand + * and returns a CreateGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public AddGroupCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_GROUPTAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_GROUPTAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddGroupCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_GROUPTAG); + Group group = ParserUtil.parseGroup(argMultimap.getValue(PREFIX_GROUPTAG).get()); + + return new AddGroupCommand(group); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddGroupMeetingTimeCommandParser.java b/src/main/java/seedu/address/logic/parser/AddGroupMeetingTimeCommandParser.java new file mode 100644 index 00000000000..16fe27db332 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddGroupMeetingTimeCommandParser.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.CliSyntax.PREFIX_FREETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; + +import java.util.ArrayList; +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddGroupMeetingTimeCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.TimeInterval; +import seedu.address.model.group.Group; + +/** + * Parses input arguments and creates a new AddGroupMeetingTimeCommand object + */ +public class AddGroupMeetingTimeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddGroupMeetingTimeCommand + * and returns a AddGroupMeetingTimeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddGroupMeetingTimeCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_GROUPTAG, PREFIX_FREETIME); + + if (!arePrefixesPresent(argMultimap, PREFIX_GROUPTAG, PREFIX_FREETIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + AddGroupMeetingTimeCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_GROUPTAG); + Group group = ParserUtil.parseSingleGroup(argMultimap.getValue(PREFIX_GROUPTAG).get()); + ArrayList timeInterval = ParserUtil.parseInterval(argMultimap.getAllValues(PREFIX_FREETIME)); + + if (TimeInterval.isTimeIntervalOverlap(timeInterval)) { + throw new ParseException(TimeInterval.MESSAGE_CONSTRAINTS_OVERLAP); + } + + return new AddGroupMeetingTimeCommand(group, timeInterval); + } + + 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/AddTimeCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTimeCommandParser.java new file mode 100644 index 00000000000..37268f5d53a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddTimeCommandParser.java @@ -0,0 +1,44 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FREETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.ArrayList; +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddTimeCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.TimeInterval; +import seedu.address.model.person.Name; + +/** + * Parses input arguments and creates a new AddTimeCommand object + */ +public class AddTimeCommandParser implements Parser { + + @Override + public AddTimeCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_FREETIME); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_FREETIME) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTimeCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME); + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + ArrayList timeInterval = ParserUtil.parseInterval(argMultimap.getAllValues(PREFIX_FREETIME)); + + if (TimeInterval.isTimeIntervalOverlap(timeInterval)) { + throw new ParseException(TimeInterval.MESSAGE_CONSTRAINTS_OVERLAP); + } + + return new AddTimeCommand(name, timeInterval); + } + + 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..885cd917c90 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -9,14 +9,23 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.AddGroupCommand; +import seedu.address.logic.commands.AddGroupMeetingTimeCommand; +import seedu.address.logic.commands.AddTimeCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.DeleteTimeCommand; import seedu.address.logic.commands.ExitCommand; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindFreeTimeCommand; +import seedu.address.logic.commands.GroupPersonCommand; +import seedu.address.logic.commands.GroupRemarkCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.ListGroupCommand; +import seedu.address.logic.commands.ListTimeCommand; +import seedu.address.logic.commands.UngroupPersonCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -56,26 +65,53 @@ public Command parseCommand(String userInput) throws ParseException { case AddCommand.COMMAND_WORD: return new AddCommandParser().parse(arguments); - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); + case AddTimeCommand.COMMAND_WORD: + return new AddTimeCommandParser().parse(arguments); + + case DeleteTimeCommand.COMMAND_WORD: + return new DeleteTimeCommandParser().parse(arguments); + + case AddGroupCommand.COMMAND_WORD: + return new AddGroupCommandParser().parse(arguments); + + case GroupPersonCommand.COMMAND_WORD: + return new GroupPersonCommandParser().parse(arguments); + + case UngroupPersonCommand.COMMAND_WORD: + return new UngroupPersonCommandParser().parse(arguments); case DeleteCommand.COMMAND_WORD: return new DeleteCommandParser().parse(arguments); + case GroupRemarkCommand.COMMAND_WORD: + return new GroupRemarkCommandParser().parse(arguments); + + case ListTimeCommand.COMMAND_WORD: + return new ListTimeCommandParser().parse(arguments); + case ClearCommand.COMMAND_WORD: - return new ClearCommand(); + return new ClearCommandParser().parse(arguments); case FindCommand.COMMAND_WORD: return new FindCommandParser().parse(arguments); + case AddGroupMeetingTimeCommand.COMMAND_WORD: + return new AddGroupMeetingTimeCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: - return new ListCommand(); + return new ListCommandParser().parse(arguments); + + case ListGroupCommand.COMMAND_WORD: + return new ListGroupCommandParser().parse(arguments); + + case FindFreeTimeCommand.COMMAND_WORD: + return new FindFreeTimeCommandParser().parse(arguments); case ExitCommand.COMMAND_WORD: - return new ExitCommand(); + return new ExitCommandParser().parse(arguments); case HelpCommand.COMMAND_WORD: - return new HelpCommand(); + return new HelpCommandParser().parse(arguments); default: logger.finer("This user input caused a ParseException: " + userInput); diff --git a/src/main/java/seedu/address/logic/parser/ClearCommandParser.java b/src/main/java/seedu/address/logic/parser/ClearCommandParser.java new file mode 100644 index 00000000000..05060fb88e4 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ClearCommandParser.java @@ -0,0 +1,26 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ClearCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new ClearCommand object + */ +public class ClearCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ClearCommand + * and returns an ClearCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public ClearCommand parse(String args) throws ParseException { + if (args.length() != 0) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ClearCommand.MESSAGE_USAGE)); + } + + return new ClearCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf119..9aff696acaa 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -9,7 +9,10 @@ public class CliSyntax { public static final Prefix PREFIX_NAME = new Prefix("n/"); public static final Prefix PREFIX_PHONE = new Prefix("p/"); public static final Prefix PREFIX_EMAIL = new Prefix("e/"); + public static final Prefix PREFIX_GROUPTAG = new Prefix("g/"); public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_FREETIME = new Prefix("t/"); + public static final Prefix PREFIX_GROUPREMARK = new Prefix("r/"); + public static final Prefix PREFIX_DURATION = new Prefix("d/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index 3527fe76a3e..e0c4146efe7 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -1,9 +1,14 @@ package seedu.address.logic.parser; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.stream.Stream; -import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteGroupCommand; +import seedu.address.logic.commands.DeletePersonCommand; import seedu.address.logic.parser.exceptions.ParseException; /** @@ -17,13 +22,38 @@ public class DeleteCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { - try { - Index index = ParserUtil.parseIndex(args); - return new DeleteCommand(index); - } catch (ParseException pe) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_GROUPTAG); + + //duplicate parameters + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_GROUPTAG); + + // check if either n/ or g/ are present + if ((!arePrefixesPresent(argMultimap, PREFIX_NAME) || !argMultimap.getPreamble().isEmpty()) + && !arePrefixesPresent(argMultimap, PREFIX_GROUPTAG)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_USAGE)); + } + + // if n/ and g/ present + if (arePrefixesPresent(argMultimap, PREFIX_NAME) && arePrefixesPresent(argMultimap, PREFIX_GROUPTAG)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + DeleteCommand.MESSAGE_TWO_PARAMETERS)); } + + // if n/ present + if (arePrefixesPresent(argMultimap, PREFIX_NAME)) { + String personName = argMultimap.getValue(PREFIX_NAME).get(); + return new DeletePersonCommand(personName); + } + + // n/ not present, g/ should be present + String groupName = argMultimap.getValue(PREFIX_GROUPTAG).get(); + return new DeleteGroupCommand(groupName); + } + 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/DeleteTimeCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTimeCommandParser.java new file mode 100644 index 00000000000..68a875d38ab --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteTimeCommandParser.java @@ -0,0 +1,61 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FREETIME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.ArrayList; +import java.util.stream.Stream; + +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.DeleteGroupTimeCommand; +import seedu.address.logic.commands.DeletePersonTimeCommand; +import seedu.address.logic.commands.DeleteTimeCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.TimeInterval; +import seedu.address.model.group.Group; +import seedu.address.model.person.Name; + +/** + * Parses input arguments and creates a new DeleteTimeCommandParser object + */ +public class DeleteTimeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the DeleteTimeCommand + * and returns a DeleteTimeCommand object for execution. * + * @throws ParseException if the user input does not conform the expected format + */ + public DeleteTimeCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_GROUPTAG, PREFIX_FREETIME); + + if (!arePrefixesPresent(argMultimap, PREFIX_FREETIME) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTimeCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_GROUPTAG); + ArrayList timeInterval = ParserUtil.parseInterval(argMultimap.getAllValues(PREFIX_FREETIME)); + if ((arePrefixesPresent(argMultimap, PREFIX_NAME) && arePrefixesPresent(argMultimap, PREFIX_GROUPTAG))) { + throw new ParseException( + String.format(DeleteCommand.MESSAGE_TWO_PARAMETERS, DeleteTimeCommand.MESSAGE_USAGE)); + } + if (arePrefixesPresent(argMultimap, PREFIX_NAME)) { + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get()); + return new DeletePersonTimeCommand(name, timeInterval); + } + if (arePrefixesPresent(argMultimap, PREFIX_GROUPTAG)) { + Group group = ParserUtil.parseGroup(argMultimap.getValue(PREFIX_GROUPTAG).get()); + return new DeleteGroupTimeCommand(group, timeInterval); + } + + // Should not reach here + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTimeCommand.MESSAGE_USAGE)); + + } + + 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/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java deleted file mode 100644 index 46b3309a78b..00000000000 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ /dev/null @@ -1,85 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new EditCommand object - */ -public class EditCommandParser implements Parser { - - /** - * Parses the given {@code String} of arguments in the context of the EditCommand - * and returns an EditCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public EditCommand parse(String args) throws ParseException { - requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - Index index; - - try { - index = ParserUtil.parseIndex(argMultimap.getPreamble()); - } catch (ParseException pe) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe); - } - - argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS); - - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); - - if (argMultimap.getValue(PREFIX_NAME).isPresent()) { - editPersonDescriptor.setName(ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME).get())); - } - if (argMultimap.getValue(PREFIX_PHONE).isPresent()) { - editPersonDescriptor.setPhone(ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE).get())); - } - if (argMultimap.getValue(PREFIX_EMAIL).isPresent()) { - editPersonDescriptor.setEmail(ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL).get())); - } - if (argMultimap.getValue(PREFIX_ADDRESS).isPresent()) { - editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get())); - } - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); - - if (!editPersonDescriptor.isAnyFieldEdited()) { - throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); - } - - return new EditCommand(index, editPersonDescriptor); - } - - /** - * Parses {@code Collection tags} into a {@code Set} if {@code tags} is non-empty. - * If {@code tags} contain only one element which is an empty string, it will be parsed into a - * {@code Set} containing zero tags. - */ - private Optional> parseTagsForEdit(Collection tags) throws ParseException { - assert tags != null; - - if (tags.isEmpty()) { - return Optional.empty(); - } - Collection tagSet = tags.size() == 1 && tags.contains("") ? Collections.emptySet() : tags; - return Optional.of(ParserUtil.parseTags(tagSet)); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/ExitCommandParser.java b/src/main/java/seedu/address/logic/parser/ExitCommandParser.java new file mode 100644 index 00000000000..4274a6450e7 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ExitCommandParser.java @@ -0,0 +1,26 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ExitCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new ExitCommand object + */ +public class ExitCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ExitCommand + * and returns an ExitCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public ExitCommand parse(String args) throws ParseException { + if (args.length() != 0) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ExitCommand.MESSAGE_USAGE)); + } + + return new ExitCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index 2867bde857b..b39c8801363 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -1,10 +1,16 @@ package seedu.address.logic.parser; import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.commands.FindCommand.MESSAGE_EMPTY_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; import java.util.Arrays; +import java.util.stream.Stream; import seedu.address.logic.commands.FindCommand; +import seedu.address.logic.commands.FindGroupCommand; +import seedu.address.logic.commands.FindPersonCommand; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.person.NameContainsKeywordsPredicate; @@ -19,15 +25,42 @@ public class FindCommandParser implements Parser { * @throws ParseException if the user input does not conform the expected format */ public FindCommand parse(String args) throws ParseException { - String trimmedArgs = args.trim(); - if (trimmedArgs.isEmpty()) { - throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_GROUPTAG); + + //duplicate parameters + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_GROUPTAG); + + // check if either n/ or g/ are present + if ((!arePrefixesPresent(argMultimap, PREFIX_NAME) || !argMultimap.getPreamble().isEmpty()) + && !arePrefixesPresent(argMultimap, PREFIX_GROUPTAG)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindCommand.MESSAGE_USAGE)); + } + + // if n/ is present + if (arePrefixesPresent(argMultimap, PREFIX_NAME)) { + String personName = argMultimap.getValue(PREFIX_NAME).get(); + // check if g/ is present + if (arePrefixesPresent(argMultimap, PREFIX_GROUPTAG)) { // g/ present + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindCommand.MESSAGE_TWO_PARAMETERS)); + } else if (personName.trim().isEmpty()) { + throw new ParseException(String.format(MESSAGE_EMPTY_NAME)); + } else { + String[] nameKeywords = personName.split("\\s+"); + return new FindPersonCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + } } - String[] nameKeywords = trimmedArgs.split("\\s+"); + // n/ not present, g/ should be present + String groupName = argMultimap.getValue(PREFIX_GROUPTAG).get(); + return new FindGroupCommand(groupName); + + } - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + 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/FindFreeTimeCommandParser.java b/src/main/java/seedu/address/logic/parser/FindFreeTimeCommandParser.java new file mode 100644 index 00000000000..381f5d1e184 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/FindFreeTimeCommandParser.java @@ -0,0 +1,75 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.FindFreeTimeCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Duration; + +/** + * Parses input arguments and creates a new FindFreeTimeCommand object + */ +public class FindFreeTimeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the FindFreeTimeCommand + * and returns an FindFreeTimeCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + + public FindFreeTimeCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindFreeTimeCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_DURATION, PREFIX_GROUPTAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_DURATION, PREFIX_GROUPTAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindFreeTimeCommand.MESSAGE_USAGE)); + } + String groupName = argMultimap.getValue(PREFIX_GROUPTAG).get(); + String durationName = argMultimap.getValue(PREFIX_DURATION).get(); + int duration = 0; + + try { + duration = Integer.parseInt(durationName); + if (duration == 0) { + throw new IllegalArgumentException("You can't have a meeting without specifying a duration"); + } + if (duration < 0) { + throw new IllegalArgumentException("Duration specified is less than 0"); + } + // there are 10079 mins from mon 0000 to sun 1159 + if (duration > 10079) { + throw new IllegalArgumentException( + String.format("The value you entered, %d is beyond the time you have in a week", duration)); + } + } catch (NumberFormatException e) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindFreeTimeCommand.MESSAGE_DURATION_USAGE)); + } catch (IllegalArgumentException i) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, i.getMessage())); + } + // 0 is duration placeholder, should not enter this line if duration is 0 + return new FindFreeTimeCommand(groupName, new Duration(duration)); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + +} diff --git a/src/main/java/seedu/address/logic/parser/GroupPersonCommandParser.java b/src/main/java/seedu/address/logic/parser/GroupPersonCommandParser.java new file mode 100644 index 00000000000..d9b48532841 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/GroupPersonCommandParser.java @@ -0,0 +1,55 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.GroupPersonCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class GroupPersonCommandParser implements Parser { + + + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public GroupPersonCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, GroupPersonCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_GROUPTAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_GROUPTAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, GroupPersonCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_GROUPTAG); + String personName = argMultimap.getValue(PREFIX_NAME).get(); + String groupName = argMultimap.getValue(PREFIX_GROUPTAG).get(); + + return new GroupPersonCommand(personName, groupName); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + + +} diff --git a/src/main/java/seedu/address/logic/parser/GroupRemarkCommandParser.java b/src/main/java/seedu/address/logic/parser/GroupRemarkCommandParser.java new file mode 100644 index 00000000000..ee716f992d3 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/GroupRemarkCommandParser.java @@ -0,0 +1,47 @@ +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_GROUPREMARK; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.GroupRemarkCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.group.GroupRemark; + +/** + * Parses input arguments and creates a new {@code GroupRemarkCommand} object + */ +public class GroupRemarkCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the {@code GroupRemarkCommand} + * and returns a {@code GroupRemarkCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public GroupRemarkCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_GROUPTAG, PREFIX_GROUPREMARK); + + if (!arePrefixesPresent(argMultimap, PREFIX_GROUPTAG, PREFIX_GROUPREMARK) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, GroupRemarkCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_GROUPTAG, PREFIX_GROUPREMARK); + + String groupName = argMultimap.getValue(PREFIX_GROUPTAG).get(); + String groupRemark = argMultimap.getValue(PREFIX_GROUPREMARK).orElse(""); + + return new GroupRemarkCommand(groupName, new GroupRemark(groupRemark)); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/logic/parser/HelpCommandParser.java b/src/main/java/seedu/address/logic/parser/HelpCommandParser.java new file mode 100644 index 00000000000..124cd2e4f69 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/HelpCommandParser.java @@ -0,0 +1,25 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.HelpCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new HelpCommand object + */ +public class HelpCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the HelpCommand + * and returns an HelpCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public HelpCommand parse(String args) throws ParseException { + if (args.length() != 0) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + return new HelpCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ListCommandParser.java b/src/main/java/seedu/address/logic/parser/ListCommandParser.java new file mode 100644 index 00000000000..dbce4ace2a7 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListCommandParser.java @@ -0,0 +1,26 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new ListCommand object + */ +public class ListCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ListCommand + * and returns an ListCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public ListCommand parse(String args) throws ParseException { + if (args.length() != 0) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListCommand.MESSAGE_USAGE)); + } + + return new ListCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ListGroupCommandParser.java b/src/main/java/seedu/address/logic/parser/ListGroupCommandParser.java new file mode 100644 index 00000000000..e1f0a08f718 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListGroupCommandParser.java @@ -0,0 +1,26 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.ListGroupCommand; +import seedu.address.logic.parser.exceptions.ParseException; + + +/** + * Parses input arguments and creates a new ListCommand object + */ +public class ListGroupCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ListGroupCommand + * and returns an ListGroupCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + @Override + public ListGroupCommand parse(String args) throws ParseException { + if (args.length() != 0) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListGroupCommand.MESSAGE_USAGE)); + } + return new ListGroupCommand(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/ListTimeCommandParser.java b/src/main/java/seedu/address/logic/parser/ListTimeCommandParser.java new file mode 100644 index 00000000000..406184a4d93 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ListTimeCommandParser.java @@ -0,0 +1,73 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.ListTimeCommand; +import seedu.address.logic.commands.ListTimeGroupCommand; +import seedu.address.logic.commands.ListTimePersonCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.group.Group; +import seedu.address.model.person.Name; + +/** + * Parses input arguments and creates a new ListTimeCommand object + */ +public class ListTimeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ListTimeCommand + * and returns a ListTimeCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ListTimeCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListTimeCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_GROUPTAG); + + if (args.length() < 2) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListTimeCommand.MESSAGE_USAGE)); + } + + // check if either n/ or g/ are present + if (!arePrefixesPresent(argMultimap, PREFIX_NAME) + || !argMultimap.getPreamble().isEmpty()) { + if (!arePrefixesPresent(argMultimap, PREFIX_GROUPTAG)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListTimeCommand.MESSAGE_USAGE)); + } + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_GROUPTAG); + + // if n/ is present + if (arePrefixesPresent(argMultimap, PREFIX_NAME)) { + // check if g/ is present + if (arePrefixesPresent(argMultimap, PREFIX_GROUPTAG)) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListTimeCommand.MESSAGE_USAGE)); + } else { + String personName = argMultimap.getValue(PREFIX_NAME).get(); + Name name = ParserUtil.parseName(personName); + return new ListTimePersonCommand(name); + } + } + + + // n/ not present, g/ should be present + String groupName = argMultimap.getValue(PREFIX_GROUPTAG).get(); + Group group = ParserUtil.parseSingleGroup(groupName); + return new ListTimeGroupCommand(group); + } + + 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/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index b117acb9c55..ce0acb651d1 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -2,18 +2,19 @@ import static java.util.Objects.requireNonNull; +import java.time.DayOfWeek; +import java.time.LocalTime; +import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.StringUtil; import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Time; +import seedu.address.model.TimeInterval; +import seedu.address.model.group.Group; import seedu.address.model.person.Address; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; /** * Contains utility methods used for parsing strings in the various *Parser classes. @@ -22,19 +23,6 @@ public class ParserUtil { public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; - /** - * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be - * trimmed. - * @throws ParseException if the specified index is invalid (not non-zero unsigned integer). - */ - public static Index parseIndex(String oneBasedIndex) throws ParseException { - String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new ParseException(MESSAGE_INVALID_INDEX); - } - return Index.fromOneBased(Integer.parseInt(trimmedIndex)); - } - /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. @@ -42,6 +30,7 @@ public static Index parseIndex(String oneBasedIndex) throws ParseException { * @throws ParseException if the given {@code name} is invalid. */ public static Name parseName(String name) throws ParseException { + System.out.println(name); requireNonNull(name); String trimmedName = name.trim(); if (!Name.isValidName(trimmedName)) { @@ -96,29 +85,86 @@ public static Email parseEmail(String email) throws ParseException { } /** - * Parses a {@code String tag} into a {@code Tag}. + * Parses a {@code String groupName} into an {@code Group}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code groupName} is invalid. + */ + public static Group parseGroup(String groupName) throws ParseException { + requireNonNull(groupName); + String trimmedGroupName = groupName.trim(); + if (!Group.isValidGroupName(trimmedGroupName)) { + throw new ParseException(Group.MESSAGE_CONSTRAINTS); + } + Group group = new Group(trimmedGroupName); + return group; + } + + /** + * Parses a {@code String groupName} into a {@code Group}. * Leading and trailing whitespaces will be trimmed. * - * @throws ParseException if the given {@code tag} is invalid. + * @throws ParseException if the given {@code groupName} is invalid. */ - public static Tag parseTag(String tag) throws ParseException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new ParseException(Tag.MESSAGE_CONSTRAINTS); + public static Group parseSingleGroup(String groupName) throws ParseException { + requireNonNull(groupName); + String trimmedGroup = groupName.trim(); + if (!Group.isValidGroupName(trimmedGroup)) { + throw new ParseException(Group.MESSAGE_CONSTRAINTS); } - return new Tag(trimmedTag); + return new Group(trimmedGroup); } /** - * Parses {@code Collection tags} into a {@code Set}. + * Parses a {@code String timeString} into a {@code Time}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code timeString} 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 Time parseTime(String timeString) throws ParseException { + requireNonNull(timeString); + String trimmedTimeString = timeString.trim(); + if (!Time.isValidTime(trimmedTimeString)) { + throw new ParseException(Time.MESSAGE_CONSTRAINTS); + } + String dayString = trimmedTimeString.substring(0, timeString.indexOf(" ")); + String time = trimmedTimeString.substring(timeString.indexOf(" ") + 1); + DayOfWeek day = Time.decodeDay(dayString); + LocalTime hour = LocalTime.parse(time.substring(0, 2) + ":" + time.substring(2, 4)); + return new Time(day, hour); + } + + /** + * Parses {@code Collection tags} into a {@code ArrayList}. + */ + public static ArrayList parseInterval(Collection timeIntervals) throws ParseException { + requireNonNull(timeIntervals); + ArrayList timeIntervalsAl = new ArrayList<>(); + for (String intString: timeIntervals) { + timeIntervalsAl.add(parseEachInterval(intString)); + } + return timeIntervalsAl; + } + + /** + * Parses a {@code String interval} into a {@code TimeInterval}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws ParseException if the given {@code interval} is invalid. + */ + public static TimeInterval parseEachInterval(String interval) throws ParseException { + requireNonNull(interval); + String trimmedInterval = interval.trim(); + if (!TimeInterval.isValidTimeIntervalSyntax(trimmedInterval)) { + throw new ParseException(TimeInterval.MESSAGE_CONSTRAINTS_SYNTAX); + } + String start = interval.substring(0, interval.indexOf("-")).trim(); + Time startTime = parseTime(start); + String end = interval.substring(interval.indexOf('-') + 1).trim(); + Time endTime = parseTime(end); + if (!TimeInterval.isValidTimeIntervalLogic(startTime, endTime)) { + throw new ParseException(TimeInterval.MESSAGE_CONSTRAINTS_LOGIC); } - return tagSet; + return new TimeInterval(startTime, endTime); } } diff --git a/src/main/java/seedu/address/logic/parser/UngroupPersonCommandParser.java b/src/main/java/seedu/address/logic/parser/UngroupPersonCommandParser.java new file mode 100644 index 00000000000..527e490ae99 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/UngroupPersonCommandParser.java @@ -0,0 +1,52 @@ +package seedu.address.logic.parser; + +import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_GROUPTAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.UngroupPersonCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class UngroupPersonCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the UngroupPersonCommand + * and returns an UngroupPersonCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UngroupPersonCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UngroupPersonCommand.MESSAGE_USAGE)); + } + + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_GROUPTAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_GROUPTAG) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, UngroupPersonCommand.MESSAGE_USAGE)); + } + + argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_GROUPTAG); + + String personName = argMultimap.getValue(PREFIX_NAME).get(); + String groupName = argMultimap.getValue(PREFIX_GROUPTAG).get(); + + return new UngroupPersonCommand(personName, groupName); + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } +} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java index 73397161e84..4243df4c7f1 100644 --- a/src/main/java/seedu/address/model/AddressBook.java +++ b/src/main/java/seedu/address/model/AddressBook.java @@ -6,6 +6,10 @@ import javafx.collections.ObservableList; import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupList; +import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.UniquePersonList; @@ -16,19 +20,23 @@ public class AddressBook implements ReadOnlyAddressBook { private final UniquePersonList persons; + private final GroupList groups; /* * The 'unusual' code block below is a non-static initialization block, sometimes used to avoid duplication * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html * * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. + * among constructors. */ { persons = new UniquePersonList(); + groups = new GroupList(); } - public AddressBook() {} + public AddressBook() { + + } /** * Creates an AddressBook using the Persons in the {@code toBeCopied} @@ -48,12 +56,20 @@ public void setPersons(List persons) { this.persons.setPersons(persons); } + /** + * Replaces the contents of the group list with {@code groups}. + * {@code groups} must not contain duplicate groups. + */ + public void setGroups(List groups) { + this.groups.setGroups(groups); + } + /** * Resets the existing data of this {@code AddressBook} with {@code newData}. */ public void resetData(ReadOnlyAddressBook newData) { requireNonNull(newData); - + setGroups(newData.getGroupList()); setPersons(newData.getPersonList()); } @@ -67,6 +83,23 @@ public boolean hasPerson(Person person) { return persons.contains(person); } + /** + * Returns true if a person with the same name exists in the address book. + * + * @param personName Name of the person. + * @return Returns true if a person with the same name exists in the address book. + */ + public boolean hasPerson(Name personName) { + requireNonNull(personName); + for (Person person : persons) { + if (person.getName().equals(personName)) { + return true; + } + } + return false; + } + + /** * Adds a person to the address book. * The person must not already exist in the address book. @@ -75,6 +108,28 @@ public void addPerson(Person p) { persons.add(p); } + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public void removePerson(Person key) { + persons.remove(key); + groups.toStream().forEach(f -> { + try { + if (f.contains(key)) { + f.removePerson(key); + } + } catch (CommandException e) { + throw new RuntimeException(e); + } + }); + } + + public Person getPerson(String personName) throws CommandException { + // person list get that person object with same name + return persons.getPerson(personName); + } + /** * Replaces the given person {@code target} in the list with {@code editedPerson}. * {@code target} must exist in the address book. @@ -87,11 +142,75 @@ public void setPerson(Person target, Person editedPerson) { } /** - * Removes {@code key} from this {@code AddressBook}. - * {@code key} must exist in the address book. + * Checks if email is already used by another person. + * @param toAdd Person to be checked. + * @return returns true if email already exist in the addressBook. */ - public void removePerson(Person key) { - persons.remove(key); + public boolean hasEmail(Person toAdd) { + return persons.containsEmail(toAdd); + } + + /** + * Checks if phone is already used by another person. + * @param toAdd Person to be checked. + * @return returns true if phone already exist in the addressBook. + */ + public boolean hasPhone(Person toAdd) { + return persons.containsPhoneNumber(toAdd); + } + + //// group-level operations + + /** + * Adds a group to the address book. + * The group must not already exist in the address book. + */ + public void addGroup(Group g) { + groups.add(g); + } + + /** + * Removes {@code group} from this {@code AddressBook}. + * {@code group} must exist in the address book. + */ + public void removeGroup(Group g) { + groups.remove(g); + + g.toStream().forEach(p -> { + try { + p.removeGroup(g); + } catch (CommandException e) { + throw new RuntimeException(); + } + }); + } + + /** + * Returns a group with {@code groupName} from this {@code AddressBook}. + * @param groupName Name of group to search + * @return The group + * @throws CommandException If group is not in address book + */ + public Group getGroup(String groupName) throws CommandException { + // group list get that group object with same name + return groups.getGroup(groupName); + } + /** + * Returns a {@code group} from this {@code AddressBook}. + * @param group Group to search for + * @return The group + * @throws CommandException If group is not in address book + */ + public Group getGroup(Group group) throws CommandException { + return groups.getGroup(group); + } + + /** + * Returns true if a group with the same identity as {@code group} exists in the address book. + */ + public boolean hasGroup(Group group) { + requireNonNull(group); + return groups.contains(group); } //// util methods @@ -99,15 +218,21 @@ public void removePerson(Person key) { @Override public String toString() { return new ToStringBuilder(this) - .add("persons", persons) - .toString(); + .add("persons", persons) + .toString(); } + @Override public ObservableList getPersonList() { return persons.asUnmodifiableObservableList(); } + @Override + public ObservableList getGroupList() { + return groups.asUnmodifiableObservableList(); + } + @Override public boolean equals(Object other) { if (other == this) { @@ -127,4 +252,6 @@ public boolean equals(Object other) { public int hashCode() { return persons.hashCode(); } + + } diff --git a/src/main/java/seedu/address/model/Duration.java b/src/main/java/seedu/address/model/Duration.java new file mode 100644 index 00000000000..76c5482c210 --- /dev/null +++ b/src/main/java/seedu/address/model/Duration.java @@ -0,0 +1,27 @@ +package seedu.address.model; + +/** + * A class returning the duration of time + */ +public class Duration { + + private final int duration; + + public Duration(int duration) { + this.duration = duration; + } + + /** + * Returns the length of duration + * @return duration in minutes + */ + public int getDurationInMin() { + return this.duration; + } + + public String toString() { + return Integer.toString(duration); + } + + +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index d54df471c1f..bada1c06f17 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -1,17 +1,25 @@ package seedu.address.model; import java.nio.file.Path; +import java.util.ArrayList; import java.util.function.Predicate; import javafx.collections.ObservableList; +import javafx.util.Pair; import seedu.address.commons.core.GuiSettings; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupRemark; +import seedu.address.model.person.Name; import seedu.address.model.person.Person; /** * The API of the Model component. */ public interface Model { - /** {@code Predicate} that always evaluate to true */ + /** + * {@code Predicate} that always evaluate to true + */ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true; /** @@ -49,39 +57,109 @@ public interface Model { */ void setAddressBook(ReadOnlyAddressBook addressBook); - /** Returns the AddressBook */ + /** + * Returns the AddressBook + */ ReadOnlyAddressBook getAddressBook(); + //=========== Person functions =========================================================================== + /** - * Returns true if a person with the same identity as {@code person} exists in the address book. + * Adds the given person. + * {@code person} must not already exist in the address book. */ - boolean hasPerson(Person person); + void addPerson(Person person); /** * Deletes the given person. * The person must exist in the address book. */ - void deletePerson(Person target); + Person deletePerson(String personName) throws CommandException; /** - * Adds the given person. - * {@code person} must not already exist in the address book. + * Returns true if a person with the same identity as {@code person} exists in the address book. */ - void addPerson(Person person); + boolean hasPerson(Person person); + + boolean hasPerson(Name personName); + + /** + * Assign person to group and return corresponding person and group object in a pair + * + * @param personName String representing person name + * @param groupName String representing group name + * @return Pair representing Person and Group object of interest + */ + public Pair groupPerson(String personName, String groupName) throws CommandException; + + /** + * Unassign group and return corresponding person and group object in a pair + * + * @param personName String representing person name + * @param groupName String representing group name + * @return Pair representing Person and Group object of interest + */ + Pair ungroupPerson(String personName, String groupName) throws CommandException; + + String addTimeToPerson(Name toAddPerson, ArrayList toAddTime) throws CommandException; + TimeIntervalList getTimeFromPerson(Name personName) throws CommandException; + String deleteTimeFromPerson(Name personName, ArrayList toDeleteTime) throws CommandException; + + //=========== Group functions ============================================================================ /** - * Replaces the given person {@code target} with {@code editedPerson}. - * {@code target} must exist in the address book. - * The person identity of {@code editedPerson} must not be the same as another existing person in the address book. + * Adds a group to the address book. + * The group must not already exist in the address book. */ - void setPerson(Person target, Person editedPerson); + public void addGroup(Group g); - /** Returns an unmodifiable view of the filtered person list */ + /** + * Removes {@code key} from this {@code AddressBook}. + * {@code key} must exist in the address book. + */ + public Group deleteGroup(String groupName) throws CommandException; + + /** + * Returns true if a group with the same identity as {@code group} exists in the address book. + */ + public boolean hasGroup(Group group); + + /** + * Returns group if a group with the same name exists in the address book. + */ + public Group findGroup(String groupName) throws CommandException; + + Group addGroupRemark(String groupName, GroupRemark groupRemark) throws CommandException; + + String addTimeToGroup(Group toAdd, ArrayList toAddTime) throws CommandException; + + String deleteTimeFromGroup(Group group, ArrayList toDeleteTime) throws CommandException; + + TimeIntervalList getTimeFromGroup(Group group) throws CommandException; + + //=========== Filtered Person List Accessors ============================================================= + /** + * Returns an unmodifiable view of the filtered person list + */ ObservableList getFilteredPersonList(); + /** + * Returns an unmodifiable view of the filtered group list + */ + ObservableList getFilteredGroupList(); + /** * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * * @throws NullPointerException if {@code predicate} is null. */ void updateFilteredPersonList(Predicate predicate); + + void updateFilteredGroupList(Predicate predicate); + + boolean hasEmail(Person toAdd); + + boolean hasPhone(Person toAdd); + + } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 57bc563fde6..199f739f9f7 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -4,13 +4,19 @@ import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; import java.nio.file.Path; +import java.util.ArrayList; import java.util.function.Predicate; import java.util.logging.Logger; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; +import javafx.util.Pair; import seedu.address.commons.core.GuiSettings; import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupRemark; +import seedu.address.model.person.Name; import seedu.address.model.person.Person; /** @@ -22,6 +28,7 @@ public class ModelManager implements Model { private final AddressBook addressBook; private final UserPrefs userPrefs; private final FilteredList filteredPersons; + private final FilteredList filteredGroups; /** * Initializes a ModelManager with the given addressBook and userPrefs. @@ -30,10 +37,10 @@ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs requireAllNonNull(addressBook, userPrefs); logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - this.addressBook = new AddressBook(addressBook); this.userPrefs = new UserPrefs(userPrefs); filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); + filteredGroups = new FilteredList<>(this.addressBook.getGroupList()); } public ModelManager() { @@ -87,30 +94,261 @@ public ReadOnlyAddressBook getAddressBook() { return addressBook; } + //=========== Person functions =========================================================================== + + /** + * Adds the specified person to the address book. + * + * @param person Person to be added. + */ + @Override + public void addPerson(Person person) { + addressBook.addPerson(person); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + } + + /** + * Deletes the specified person from the address book. + * + * @param personName String representing name of person to be deleted. + * @return The deleted Person. + * @throws CommandException if the person cannot be deleted. + */ + @Override + public Person deletePerson(String personName) throws CommandException { + Person person = addressBook.getPerson(personName); + addressBook.removePerson(person); + return person; + } + + /** + * Returns whether the person is in the address book. + * + * @param person Person to be checked. + * @return Whether the address book contains the person. + */ @Override public boolean hasPerson(Person person) { requireNonNull(person); return addressBook.hasPerson(person); } + /** + * Returns whether the person is in the address book. + * + * @param personName String representing person name. + * @return Whether the address book contains the specified person. + */ + @Override + public boolean hasPerson(Name personName) { + requireNonNull(personName); + return addressBook.hasPerson(personName); + } + + /** + * Assigns person to group. + * + * @param person Person to be grouped. + * @param group Group in consideration. + * @throws CommandException if person has already been assigned to group. + */ + public void assignGroup(Person person, Group group) throws CommandException { + group.addPerson(person); + person.addGroup(group); + } + + /** + * Unassigns person to group. + * + * @param person Person to be grouped. + * @param group Group in consideration. + * @throws CommandException if person has already been assigned to group. + */ + public void unassignGroup(Person person, Group group) throws CommandException { + group.removePerson(person); + person.removeGroup(group); + } + + /** + * Adds person into group. + * + * @param personName String representing person name. + * @param groupName String representing group name. + * @return Pair containing the Person and the Group. + * @throws CommandException if the person cannot be added to the group. + */ @Override - public void deletePerson(Person target) { - addressBook.removePerson(target); + public Pair groupPerson(String personName, String groupName) throws CommandException { + // both throw exception if not exists exact match + Person person = addressBook.getPerson(personName); + Group group = addressBook.getGroup(groupName); + + this.assignGroup(person, group); + forceUpdateList(); + Pair output = new Pair<>(person, group); + return output; } + /** + * Removes person from group. + * + * @param personName String representing person name. + * @param groupName String representing group name. + * @return Pair containing the Person and the Group. + * @throws CommandException if the person cannot be removed from the group. + */ @Override - public void addPerson(Person person) { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + public Pair ungroupPerson(String personName, String groupName) throws CommandException { + Person person = addressBook.getPerson(personName); + Group group = addressBook.getGroup(groupName); + this.unassignGroup(person, group); + forceUpdateList(); + Pair output = new Pair<>(person, group); + return output; + } + + /** + * Adds free time to a person. + * + * @param toAddPerson String representing name of person. + * @param toAddTime ArrayList containing all Time Intervals. + * @return String showing added times and clashes (if any). + * @throws CommandException if the times cannot be added. + */ + @Override + public String addTimeToPerson(Name toAddPerson, ArrayList toAddTime) throws CommandException { + requireNonNull(toAddPerson); + Person person = addressBook.getPerson(toAddPerson.fullName); + String msg = person.addFreeTime(toAddTime); + forceUpdateList(); + return msg; + + } + + /** + * Deletes free time from person. + * + * @param personName Person to delete time from. + * @param toDeleteTime Time to be deleted. + * @throws CommandException if time does not exist. + */ + @Override + public String deleteTimeFromPerson(Name personName, + ArrayList toDeleteTime) throws CommandException { + requireNonNull(personName); + Person person = addressBook.getPerson(personName.fullName); + try { + String commandOutcome = person.deleteFreeTime(toDeleteTime); + forceUpdateList(); + return commandOutcome; + } catch (CommandException e) { + throw new CommandException(e.getMessage()); + } } + public TimeIntervalList getTimeFromPerson(Name personName) throws CommandException { + requireNonNull(personName); + Person person = addressBook.getPerson(personName.toString()); + return person.getTime(); + } + + //=========== Group functions ============================================================================ + + /** + * Adds a group to the address book. + * + * @param group Group to be added. + */ + public void addGroup(Group group) { + addressBook.addGroup(group); + } + + /** + * Removes {@code group} from this {@code AddressBook}. + * {@code group} must exist in the address book. + */ + public Group deleteGroup(String groupName) throws CommandException { + Group group = addressBook.getGroup(groupName); + addressBook.removeGroup(group); + forceUpdateList(); + return group; + } + + /** + * Returns true if a group with the same identity as {@code group} exists in the address book. + */ + public boolean hasGroup(Group group) { + requireNonNull(group); + return addressBook.hasGroup(group); + } + + public Group findGroup(String groupName) throws CommandException { + return addressBook.getGroup(groupName); + } + + /** + * Adds meeting time to a group. + * + * @param toAdd The group to be modified. + * @param toAddTime ArrayList of Time Intervals to be added. + * @return String showing added meeting times and clashes (if any). + * @throws CommandException if the times cannot be added. + */ + public String addTimeToGroup(Group toAdd, ArrayList toAddTime) throws CommandException { + requireNonNull(toAdd); + Group groupToAdd = addressBook.getGroup(toAdd.getGroupName()); + try { + String status = groupToAdd.addTime(toAddTime); + forceUpdateList(); + return status; + } catch (CommandException e) { + throw new CommandException(e.getMessage()); + } + } + + /** + * Deletes meeting time from group. + * + * @param group Group to delete time from. + * @param toDeleteTime Time to be deleted. + * @throws CommandException if time does not exist. + */ @Override - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); + public String deleteTimeFromGroup(Group group, + ArrayList toDeleteTime) throws CommandException { + requireNonNull(group); + Group groupToDeleteTime = addressBook.getGroup(group.getGroupName()); + try { + String commandOutcome = groupToDeleteTime.deleteTime(toDeleteTime); + forceUpdateList(); + return commandOutcome; + } catch (CommandException e) { + forceUpdateList(); + throw new CommandException(e.getMessage()); + } + } - addressBook.setPerson(target, editedPerson); + public TimeIntervalList getTimeFromGroup(Group group) throws CommandException { + requireNonNull(group); + Group toAdd = addressBook.getGroup(group.getGroupName()); + return toAdd.getTime(); } + /** + * Adds remarks to a group. + * + * @param groupName Group to be modified. + * @param groupRemark Remark to be added. + * @return The modified group. + * @throws CommandException if the remark cannot be added. + */ + public Group addGroupRemark(String groupName, GroupRemark groupRemark) throws CommandException { + Group group = addressBook.getGroup(groupName); + group.setGroupRemark(groupRemark); + return group; + } + + //=========== Filtered Person List Accessors ============================================================= /** @@ -122,12 +360,44 @@ public ObservableList getFilteredPersonList() { return filteredPersons; } + /** + * Returns an unmodifiable view of the list of {@code Group} backed by the internal list of + * {@code versionedAddressBook} + */ + @Override + public ObservableList getFilteredGroupList() { + return filteredGroups; + } + @Override public void updateFilteredPersonList(Predicate predicate) { requireNonNull(predicate); filteredPersons.setPredicate(predicate); } + @Override + public void updateFilteredGroupList(Predicate predicate) { + requireNonNull(predicate); + filteredGroups.setPredicate(predicate); + } + + @Override + public boolean hasEmail(Person toAdd) { + return addressBook.hasEmail(toAdd); + } + + @Override + public boolean hasPhone(Person toAdd) { + return addressBook.hasPhone(toAdd); + } + + private void forceUpdateList() { + updateFilteredPersonList(user -> false); + updateFilteredPersonList(user -> true); + updateFilteredGroupList(group -> false); + updateFilteredGroupList(group -> true); + } + @Override public boolean equals(Object other) { if (other == this) { @@ -141,8 +411,7 @@ public boolean equals(Object other) { ModelManager otherModelManager = (ModelManager) other; return addressBook.equals(otherModelManager.addressBook) - && userPrefs.equals(otherModelManager.userPrefs) - && filteredPersons.equals(otherModelManager.filteredPersons); + && userPrefs.equals(otherModelManager.userPrefs) + && filteredPersons.equals(otherModelManager.filteredPersons); } - } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java index 6ddc2cd9a29..9bb010b727b 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java @@ -1,6 +1,7 @@ package seedu.address.model; import javafx.collections.ObservableList; +import seedu.address.model.group.Group; import seedu.address.model.person.Person; /** @@ -14,4 +15,6 @@ public interface ReadOnlyAddressBook { */ ObservableList getPersonList(); + ObservableList getGroupList(); + } diff --git a/src/main/java/seedu/address/model/Time.java b/src/main/java/seedu/address/model/Time.java new file mode 100644 index 00000000000..3af1f320cd2 --- /dev/null +++ b/src/main/java/seedu/address/model/Time.java @@ -0,0 +1,121 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.time.DayOfWeek; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +/** + * A class representing a time object + */ +public class Time { + + public static final String MESSAGE_CONSTRAINTS = "The format of a time should be: mon 1200,\n" + + " the day is the first three " + + "letters of a day and the time is in 24 hour format"; + + private static final String DAY_REGEX = "^(?i)(mon|tue|wed|thu|fri|sat|sun)"; + private static final String TIME_REGEX = "(0[0-9]|1[0-9]|2[0-3])[0-5][0-9]"; + public static final String VALIDATION_REGEX = DAY_REGEX + " " + TIME_REGEX; + + private DayOfWeek day; + private LocalTime hour; + + /** + * Time constructor. + * @param day The day of the week. + * @param hour The time of the day. + */ + public Time(DayOfWeek day, LocalTime hour) { + requireNonNull(day); + requireNonNull(hour); + this.day = day; + this.hour = hour; + } + + /** + * Checks if the time is valid. + * @param timeString Time in string format. + * @return true if the sting is valid. + */ + public static boolean isValidTime(String timeString) { + return timeString.matches(VALIDATION_REGEX); + } + + /** + * Convert either start or end time of timeInterval into an int + * Use dayOfWeek enum from 1-7 representing Mon-Sun + * @return Int representing duration of interval in minute + */ + public int getDurationInMin() { + int durationInMin = this.day.getValue() * 24 * 60 + this.hour.getHour() * 60 + this.hour.getMinute(); + return durationInMin; + } + + /** + * Compares this with another time object. + * @param otherTime The other time object. + * @return Returns an int to indicate if it less than/more than/equal. + */ + public int compareTo(Time otherTime) { + requireNonNull(otherTime); + return this.day.compareTo(otherTime.day) == 0 ? this.hour.compareTo(otherTime.hour) + : this.day.compareTo(otherTime.day); + } + + @Override + public String toString() { + return day.toString().substring(0, 3) + " " + hour.format(DateTimeFormatter.ofPattern("HHmm")); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Time)) { + return false; + } else if (object == this) { + return true; + } else { + Time otherTime = (Time) object; + return this.day.equals(otherTime.day) && this.hour.equals(otherTime.hour); + } + } + + /** + * Converts string to DayOfWeek object. + * @param day In String. + * @return day in DayOfWeek object. + */ + public static DayOfWeek decodeDay(String day) { + day = day.toLowerCase(); + if (DayOfWeek.MONDAY.toString().toLowerCase().contains(day)) { + return DayOfWeek.MONDAY; + } else if (DayOfWeek.TUESDAY.toString().toLowerCase().contains(day)) { + return DayOfWeek.TUESDAY; + } else if (DayOfWeek.WEDNESDAY.toString().toLowerCase().contains(day)) { + return DayOfWeek.WEDNESDAY; + } else if (DayOfWeek.THURSDAY.toString().toLowerCase().contains(day)) { + return DayOfWeek.THURSDAY; + } else if (DayOfWeek.FRIDAY.toString().toLowerCase().contains(day)) { + return DayOfWeek.FRIDAY; + } else if (DayOfWeek.SATURDAY.toString().toLowerCase().contains(day)) { + return DayOfWeek.SATURDAY; + } else { + return DayOfWeek.SUNDAY; + } + } + + /** + * Returns the day of the week. + */ + public DayOfWeek getDay() { + return this.day; + } + + /** + * Returns the time. + */ + public LocalTime getTime() { + return this.hour; + } +} diff --git a/src/main/java/seedu/address/model/TimeInterval.java b/src/main/java/seedu/address/model/TimeInterval.java new file mode 100644 index 00000000000..187f8107f67 --- /dev/null +++ b/src/main/java/seedu/address/model/TimeInterval.java @@ -0,0 +1,207 @@ +package seedu.address.model; + + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FREETIME; + +import java.time.DayOfWeek; +import java.util.ArrayList; + +/** + * A class representing a time interval object + */ +public class TimeInterval { + + public static final String MESSAGE_CONSTRAINTS_SYNTAX = "The format of an interval should be: mon 1200 - tue 1400"; + + public static final String MESSAGE_CONSTRAINTS_LOGIC = "Your end time cannot be before or equal your start time "; + + public static final String MESSAGE_CONSTRAINTS_OVERLAP = "No overlap is allowed in your interval. \n " + + PREFIX_FREETIME + "mon 1200 - mon 1600 " + PREFIX_FREETIME + "mon 1400 - mon 1800 is not allowed. " + + "Write it as mon 1200 - mon 1800"; + + + public static final String VALIDATION_REGEX = ".* .* - .* .*"; + private final Time start; + private final Time end; + + /** + * TimeInterval constructor. + * + * @param start The start time of the interval. + * @param end The end time of the interval. + */ + public TimeInterval(Time start, Time end) { + requireNonNull(start); + requireNonNull(end); + this.start = start; + this.end = end; + } + + /** + * Returns true if the timeInterval overlaps with one another. + * + * @param intervals ArrayList of TimeIntervals. + * @return Returns true if the timeInterval overlaps with one another. + */ + public static boolean isTimeIntervalOverlap(ArrayList intervals) { + intervals.sort(TimeInterval::compareStart); + for (int i = 0; i < intervals.size() - 1; i++) { + if (intervals.get(i).isClash(intervals.get(i + 1))) { + return true; + } + } + return false; + } + + /** + * Check whether an interval overlaps another interval in any way. + * Interval overlapping at start and end points not considered as overlap does not form interval. + * + * @param t time interval of interest. + * @return boolean on whether this time interval overlaps with the time interval of interest t. + */ + public boolean isTimeIntervalOverlapWithTimeInterval(TimeInterval t) { + boolean firstCase = this.start.compareTo(t.end) < 0 && this.end.compareTo(t.start) > 0; + boolean secondCase = this.start.compareTo(t.end) > 0 && this.start.compareTo(t.end) < 0; + boolean overLap = firstCase || secondCase; + return overLap; + } + + + /** + * Check if Intervals are equal in terms of time representation and not exact object comparison. + * + * @param otherInterval that we are comparing to. + * @return boolean whether otherInterval is equal in terms of time representation. + */ + public boolean equalStartAndEnd(TimeInterval otherInterval) { + return this.start.compareTo(otherInterval.start) == 0 && this.end.compareTo(otherInterval.end) == 0; + } + + public static TimeInterval getMaxStart(TimeInterval first, TimeInterval second) { + // if both have the same MaxStart then by default return first + return first.getStart().compareTo(second.getStart()) >= 0 ? first : second; + } + + public static TimeInterval getMinEnd(TimeInterval first, TimeInterval second) { + // if both have the same MinEnd then by default return first + return first.getEnd().compareTo(second.getEnd()) <= 0 ? first : second; + } + + /** + * Create new time interval with Maximum Start and Minimum End. + * + * @param otherInterval of interest that we want the overlap with. + * @return TimeInterval representing the overlap between this TimeInterval and otherInterval. + */ + public TimeInterval getIntersect(TimeInterval otherInterval) { + return new TimeInterval(this.getStart(), otherInterval.getEnd()); + } + + /** + * Check if time interval can accommodate for a given duration. + * Whether duration can fit in interval. + * Assume start < end always hold, property of timeInterval + * + * @param duration of meeting in consideration. + * @return boolean whether meeting is permissible. + */ + public boolean allows(Duration duration) { + int durationInMin = duration.getDurationInMin(); + int durationStart = this.start.getDurationInMin(); + int durationEnd = this.end.getDurationInMin(); + boolean allows = durationEnd - durationStart >= durationInMin; + return allows; + } + + /** + * Returns true if the interval string is in right format. + * + * @param timeInterval The timeInterval to be checked. + * @return Returns true if the interval string is in right format. + */ + public static boolean isValidTimeIntervalSyntax(String timeInterval) { + return timeInterval.matches(VALIDATION_REGEX); + } + + /** + * Returns true if start time is less than end time. + * + * @param start Start time. + * @param end End time. + * @return Returns true if start time is less than end time. + */ + public static boolean isValidTimeIntervalLogic(Time start, Time end) { + return start.compareTo(end) <= -1; + } + + /** + * Returns if TimeInterval clashes with otherTime TimeInterval. + * + * @param otherTime is the TimeInterval of interest + * @return boolean whether there is a clash between this and otherTime TimeInterval + */ + public boolean isClash(TimeInterval otherTime) { + boolean isBefore = this.start.compareTo(otherTime.start) < 0 && this.end.compareTo(otherTime.end) < 0 + && this.end.compareTo(otherTime.start) < 0; + boolean isAfter = this.start.compareTo(otherTime.start) > 0 && this.end.compareTo(otherTime.end) > 0 + && this.start.compareTo(otherTime.end) > 0; + return !(isBefore || isAfter); + } + + /** + * Check whether start if before, equal or after otherTime. + * @param otherTime is the TimeInterval that we want to compare to. + * + * @return int representing whether this TimeInterval start is before, equal to, or after otherTime TimeInterval. + */ + public int compareStart(TimeInterval otherTime) { + return this.start.compareTo(otherTime.start); + } + + /** + * Return the DayOfWeek for start of TimeInterval + * + * @return DayOfWeek for the start + */ + public DayOfWeek getStartTimeDay() { + return this.start.getDay(); + } + + /** + * Gets the start time of the interval + * + * @return Start time + */ + public Time getStart() { + return this.start; + } + + /** + * Gets the end time of the interval + * + * @return end time + */ + public Time getEnd() { + return this.end; + } + + @Override + public String toString() { + return start.toString() + " - " + end.toString() + " "; + } + + + @Override + public boolean equals(Object object) { + if (!(object instanceof TimeInterval)) { + return false; + } else if (object == this) { + return true; + } else { + TimeInterval otherTime = (TimeInterval) object; + return this.start.equals(otherTime.start) && this.end.equals(otherTime.end); + } + } +} diff --git a/src/main/java/seedu/address/model/TimeIntervalList.java b/src/main/java/seedu/address/model/TimeIntervalList.java new file mode 100644 index 00000000000..e8d3057eab7 --- /dev/null +++ b/src/main/java/seedu/address/model/TimeIntervalList.java @@ -0,0 +1,265 @@ +package seedu.address.model; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.stream.Stream; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.logic.commands.exceptions.CommandException; + +/** + * Class representing the list of time intervals. + */ +public class TimeIntervalList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + public void addTime(TimeInterval timeInterval) { + internalList.add(timeInterval); + } + + /** + * Adds time to the current TimeInterval list. + * + * @param timeIntervals ArrayList of timeIntervals. + * @return the status after adding the time. + */ + public String addTime(ArrayList timeIntervals) { + boolean isPass = false; + boolean isFail = false; + StringBuilder errorMessage = new StringBuilder("There is a clash in these input timings with " + + "your existing timings:\n"); + StringBuilder passMessage = new StringBuilder("These times have been added:\n"); + for (TimeInterval interval : timeIntervals) { + if (isTimeIntervalOverlap(interval)) { + isFail = true; + errorMessage.append(interval + "\n"); + } else { + isPass = true; + internalList.add(interval); + passMessage.append(interval.toString() + "\n"); + } + } + if (isFail && isPass) { + return errorMessage.append(passMessage).toString(); + } else if (isFail) { + return errorMessage.toString(); + } else { + return passMessage.toString(); + } + } + + /** + * Converts the internal list to streams. + * + * @return Internal list into streams. + */ + public Stream toStream() { + return internalList.stream(); + } + + /** + * Adds all the timeInterval in to current timeInterval list. + * + * @param timeIntervalList TimeIntervalList to be added. + */ + public void addAll(TimeIntervalList timeIntervalList) { + for (TimeInterval timeInterval : timeIntervalList) { + this.internalList.add(timeInterval); + } + } + + /** + * Deletes a list of time intervals from the time interval list. + * + * @param timeIntervals The list of time intervals to be deleted. + * @return The message of time that has and has not been deleted. + * @throws CommandException Throws exception if time interval list does not contain the time when being removed, + * which should not happen as we check using the contains method. + */ + public String deleteTime(ArrayList timeIntervals) throws CommandException { + boolean isPass = false; + boolean isFail = false; + StringBuilder errorMessage = new StringBuilder("These times were not in the list:\n"); + StringBuilder passMessage = new StringBuilder("These times have been deleted:\n"); + for (TimeInterval interval : timeIntervals) { + if (!internalList.contains(interval)) { + isFail = true; + errorMessage.append(interval + "\n"); + } else { + isPass = true; + internalList.remove(interval); + passMessage.append(interval.toString() + "\n"); + } + } + if (isFail && isPass) { + return errorMessage.append(passMessage).toString(); + } else if (isFail) { + return errorMessage.toString(); + } else { + return passMessage.toString(); + } + } + + /** + * Checks whether timeInterval contains the time. + * + * @param timeInterval The time Interval to check. + * @return Whether time interval is in list. + */ + public boolean hasTime(TimeInterval timeInterval) { + return internalList.contains(timeInterval); + } + + /** + * Checks if time interval overlaps with internal list. + * + * @param interval The time iunterval to check + * @return Whether time interval overlaps with internal list. + */ + public boolean isTimeIntervalOverlap(TimeInterval interval) { + for (TimeInterval time : internalList) { + if (interval.isClash(time)) { + return true; + } + } + return false; + } + + + /** + * Check whether no time is stored. + * + * @return boolean representing whether no time is stored. + */ + public boolean isEmpty() { + return internalList.isEmpty(); + } + + + /** + * Generate String representing list of intervals. + * + * @param br StringBuilder to store message. + * @param format specify Message format. + */ + public void getMessage(StringBuilder br, String format) { + int intervalCount = 1; + for (TimeInterval t : this.internalList) { + br.append(String.format(format, intervalCount, t.toString())); + intervalCount++; + } + + } + + /** + * Filter a TimeIntervalList to contain only intervals that fit the duration. + * + * @param duration represent time in minutes. + * @return TimeIntervalList with duration greater than duration specified. + */ + public TimeIntervalList fitDuration(Duration duration) { + TimeIntervalList personTime = new TimeIntervalList(); + for (TimeInterval interval : internalList) { + if (interval.allows(duration)) { + personTime.addTime(interval); + } + } + return personTime; + } + + /** + * Finds the overlap between 2 free times and a duration. + * First sort by start time. + * Use minimum start and minimum end pointers to retrieve overlap. + * Check that interval >= duration and take those intervals that satisfy this condition. + * + * + * @param otherTime the other free times to compare with. + * @param duration the duration of overlap needed. + * @return a list of times that there is an overlap in time interval. + */ + public TimeIntervalList findOverlap(TimeIntervalList otherTime, Duration duration) { + this.internalList.sort(TimeInterval::compareStart); + otherTime.internalList.sort(TimeInterval::compareStart); + TimeIntervalList newFreeTime = new TimeIntervalList(); + + int p1 = 0; + int p2 = 0; + + while (p1 < this.internalList.size() && p2 < otherTime.internalList.size()) { + TimeInterval firstListInterval = this.internalList.get(p1); + TimeInterval secondListInterval = otherTime.internalList.get(p2); + + boolean overLap = firstListInterval.isTimeIntervalOverlapWithTimeInterval(secondListInterval); + // given 2 intervals, return the one with smaller end to increment pointer + TimeInterval intervalWithMaxStart = TimeInterval.getMaxStart(firstListInterval, secondListInterval); + TimeInterval intervalWithMinEnd = TimeInterval.getMinEnd(firstListInterval, secondListInterval); + // store the intersect, get intersect then see if it can fit duration + if (overLap) { + TimeInterval intersect = intervalWithMaxStart.getIntersect(intervalWithMinEnd); + if (intersect.allows(duration)) { + newFreeTime.addTime(intersect); + } + } + // increment pointers + if (intervalWithMinEnd.equalStartAndEnd(firstListInterval)) { + p1++; + } else { + p2++; + } + } + + return newFreeTime; + } + + /** + * Returns size of time interval list. + * @return int representing size of time interval list. + */ + public int size() { + return this.internalList.size(); + } + + @Override + public Iterator iterator() { + return this.internalList.iterator(); + } + + @Override + public String toString() { + String toString = ""; + for (TimeInterval timeInterval : internalList) { + toString += "\n" + timeInterval; + } + return toString; + } + + /** + * Returns if the list being compared to have equal time intervals. + * + * @param other other list in view. + * @return whether list being compared to is equal in terms of time intervals. + */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles null + if (!(other instanceof TimeIntervalList)) { + return false; + } + + TimeIntervalList otherTime = (TimeIntervalList) other; + boolean sameList = true; + for (TimeInterval time : this.internalList) { + sameList &= (otherTime.hasTime(time)); + } + return sameList; + } +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 6be655fb4c7..bc01b3b67cf 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -14,12 +14,13 @@ public class UserPrefs implements ReadOnlyUserPrefs { private GuiSettings guiSettings = new GuiSettings(); - private Path addressBookFilePath = Paths.get("data" , "addressbook.json"); + private Path addressBookFilePath = Paths.get("data", "projectPRO.json"); /** * Creates a {@code UserPrefs} with default values. */ - public UserPrefs() {} + public UserPrefs() { + } /** * Creates a {@code UserPrefs} with the prefs in {@code userPrefs}. @@ -69,7 +70,7 @@ public boolean equals(Object other) { UserPrefs otherUserPrefs = (UserPrefs) other; return guiSettings.equals(otherUserPrefs.guiSettings) - && addressBookFilePath.equals(otherUserPrefs.addressBookFilePath); + && addressBookFilePath.equals(otherUserPrefs.addressBookFilePath); } @Override diff --git a/src/main/java/seedu/address/model/group/Group.java b/src/main/java/seedu/address/model/group/Group.java new file mode 100644 index 00000000000..e32c70dd0c7 --- /dev/null +++ b/src/main/java/seedu/address/model/group/Group.java @@ -0,0 +1,297 @@ +package seedu.address.model.group; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Duration; +import seedu.address.model.TimeInterval; +import seedu.address.model.TimeIntervalList; +import seedu.address.model.person.Person; + +/** + * Class representing a group + */ +public class Group { + public static final String MESSAGE_CONSTRAINTS = "Group names should be alphanumeric and must not be blank"; + public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + private final ObservableList listOfGroupMates = FXCollections.observableArrayList(); + private final String groupName; + private GroupRemark groupRemark; + private final TimeIntervalList timeIntervalList = new TimeIntervalList(); + + /** + * Constructs a group with the name, name must not be null + * @param groupName Name of group + */ + public Group(String groupName) { + requireNonNull(groupName); + this.groupName = groupName; + this.groupRemark = new GroupRemark(""); + } + + /** + * Constructs a group with the name and remark, name must not be null + * @param groupName Name of group + * @param groupRemark Remark of group + */ + public Group(String groupName, GroupRemark groupRemark) { + requireNonNull(groupName); + this.groupName = groupName; + this.groupRemark = groupRemark; + } + + /** + * Name field must be present and not null. + */ + public Group(String groupName, GroupRemark groupRemark, TimeIntervalList timeIntervalList) { + requireNonNull(groupName); + this.groupName = groupName; + this.groupRemark = groupRemark; + this.timeIntervalList.addAll(timeIntervalList); + } + + /** + * Name field and listOfGroupMates must be present and not null. + */ + public Group(String groupName, GroupRemark groupRemark, List listOfGroupMates) { + requireNonNull(groupName); + requireNonNull(listOfGroupMates); + this.groupName = groupName; + this.groupRemark = groupRemark; + this.listOfGroupMates.addAll(listOfGroupMates); + } + + /** + * Name field, listOfGroupMates and timeIntervalList must be present and not null. + */ + public Group(String groupName, GroupRemark groupRemark, List listOfGroupMates, + TimeIntervalList timeIntervalList) { + requireNonNull(groupName); + requireNonNull(listOfGroupMates); + requireNonNull(timeIntervalList); + checkArgument(groupName.matches(VALIDATION_REGEX), MESSAGE_CONSTRAINTS); + this.groupName = groupName; + this.groupRemark = groupRemark; + this.timeIntervalList.addAll(timeIntervalList); + this.listOfGroupMates.addAll(listOfGroupMates); + } + + public String getGroupName() { + return groupName; + } + + public TimeIntervalList getTimeIntervalList() { + return timeIntervalList; + } + + /** + * Converts the internal list to streams. + * + * @return Internal list into streams. + */ + public Stream toStream() { + return this.listOfGroupMates.stream(); + } + + /** + * Returns true if both groups have the same name. + * This defines a weaker notion of equality between two groups. + */ + public boolean isSameGroup(Group otherGroup) { + return this.equals(otherGroup); + } + + /** + * Check if same group according to name since groupName is unique + * + * @param groupName of interest + * @return whether group is the same group + */ + public boolean nameEquals(String groupName) { + return this.groupName.equals(groupName); + } + + /** + * Returns if the name of the group is valid. + * + * @param name The name of the group + * @return The validity of the group name. + */ + //For now no constraints + public static boolean isValidGroupName(String name) { + requireNonNull(name); + + return !name.isBlank() && name.matches(VALIDATION_REGEX); + } + + /** + * Removes the person from the group. + * The person must exist in the group. + */ + public void removePerson(Person toRemove) throws CommandException { + requireNonNull(toRemove); + if (!this.listOfGroupMates.contains(toRemove)) { + throw new CommandException( + String.format("%s is not in this group: %s", toRemove.getName().fullName, this.groupName)); + } + listOfGroupMates.remove(toRemove); + } + + /** + * Returns true if the list contains an equivalent person as the given argument. + */ + public boolean contains(Person toCheck) { + requireNonNull(toCheck); + return listOfGroupMates.stream().anyMatch(toCheck::isSamePerson); + } + + /** + * Adds a person to the list. + * The person must not already exist in the list. + */ + public void addPerson(Person personToAdd) throws CommandException { + requireNonNull(personToAdd); + if (this.contains(personToAdd)) { + throw new CommandException( + String.format("%s is already in this group: %s", personToAdd.getName().fullName, this.groupName)); + } + listOfGroupMates.add(personToAdd); + } + + public ObservableList getListOfGroupMates() { + return this.listOfGroupMates; + } + + public GroupRemark getGroupRemark() { + return this.groupRemark; + } + + public void setGroupRemark(GroupRemark groupRemark) { + this.groupRemark = groupRemark; + } + + /** + * Modify StringBuilder to display message should any groupMate not input their free time + * + * @param br StringBuilder + * @param format Format specifier + */ + public void areAllFree(StringBuilder br, String format) { + for (Person p : this.listOfGroupMates) { + if (p.isNotFree()) { + br.append(String.format(format, p.getName().fullName)); + } + } + } + + /** + * Compare each person in group to get overlap + * Accumulate the result + * + * @param duration represent duration in minutes + * @return TimeInterval that can fit duration specified + */ + public TimeIntervalList findFreeTime(Duration duration) throws CommandException { + // compare person to person get overlap + // will use one getter for freeTime + TimeIntervalList freeTime = new TimeIntervalList(); + // nobody in group + if (listOfGroupMates.isEmpty()) { + throw new CommandException("Group is empty"); + } + // only 1 person in group + if (listOfGroupMates.size() == 1) { + Person person = listOfGroupMates.get(0); + TimeIntervalList personFreeTime = person.getTime(); + // check whether fits duration or not + personFreeTime = personFreeTime.fitDuration(duration); + return personFreeTime; + } + + for (int i = 0; i < listOfGroupMates.size(); i++) { + if (i + 1 == listOfGroupMates.size()) { + break; + } + Person firstPerson = listOfGroupMates.get(i); + Person secondPerson = listOfGroupMates.get(i + 1); + TimeIntervalList first = firstPerson.getTime(); + TimeIntervalList second = secondPerson.getTime(); + // uninitialised freeTime e.g. first and second person in group + if (freeTime.isEmpty()) { + freeTime = first.findOverlap(second, duration); + } else { + freeTime = freeTime.findOverlap(second, duration); + } + } + + return freeTime; + } + + /** + * Adds a single time interval to the group + * + * @param toAddTime time interval to add + */ + public void addTime(TimeInterval toAddTime) throws CommandException { + this.timeIntervalList.addTime(toAddTime); + } + + /** + * Adds a list of free time to the group + * + * @param toAddTime List of time intervals to add + * @throws CommandException When there is a clash in timings within the list + */ + public String addTime(ArrayList toAddTime) throws CommandException { + return this.timeIntervalList.addTime(toAddTime); + } + + /** + * Checks if the group has the time interval + * + * @param timeInterval time interval to check + * @return result of check + */ + public boolean hasTime(TimeInterval timeInterval) { + return this.timeIntervalList.hasTime(timeInterval); + } + + public TimeIntervalList getTime() { + return this.timeIntervalList; + } + + public String deleteTime(ArrayList toDeleteTime) throws CommandException { + return this.timeIntervalList.deleteTime(toDeleteTime); + } + + @Override + public boolean equals(Object group) { + if (group == this) { + return true; + } + + // instanceof handles nulls + if (!(group instanceof Group)) { + return false; + } + + Group otherGroup = (Group) group; + return this.groupName.equals(otherGroup.getGroupName()); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("Group name", groupName) + .toString(); + } + +} diff --git a/src/main/java/seedu/address/model/group/GroupList.java b/src/main/java/seedu/address/model/group/GroupList.java new file mode 100644 index 00000000000..d9aba26eba3 --- /dev/null +++ b/src/main/java/seedu/address/model/group/GroupList.java @@ -0,0 +1,158 @@ +package seedu.address.model.group; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Iterator; +import java.util.List; +import java.util.stream.Stream; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.group.exceptions.DuplicateGroupException; +import seedu.address.model.group.exceptions.GroupNotFoundException; + +/** + * A list containing groups + */ +public class GroupList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + private final ObservableList internalUnmodifiableList = + FXCollections.unmodifiableObservableList(internalList); + + /** + * Returns true if the list contains an equivalent group as the given argument. + */ + public boolean contains(Group toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameGroup); + } + + /** + * Adds a group to the list. + * The group must not already exist in the list. + */ + public void add(Group toAdd) { + requireNonNull(toAdd); + if (this.contains(toAdd)) { + throw new DuplicateGroupException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the contents of this list with {@code persons}. + * {@code persons} must not contain duplicate persons. + */ + public void setGroups(List groups) { + requireAllNonNull(groups); + if (!groupsAreUnique(groups)) { + throw new DuplicateGroupException(); + } + + internalList.setAll(groups); + } + + /** + * Returns true if {@code groups} contains only unique persons. + */ + private boolean groupsAreUnique(List groups) { + for (int i = 0; i < groups.size() - 1; i++) { + for (int j = i + 1; j < groups.size(); j++) { + if (groups.get(i).isSameGroup(groups.get(j))) { + return false; + } + } + } + return true; + } + + /** + * Removes the equivalent group from the list. + * The group must exist in the list. + */ + public void remove(Group toRemove) { + requireNonNull(toRemove); + boolean isRemoved = internalList.remove(toRemove); + if (!isRemoved) { + throw new GroupNotFoundException(); + } + } + + /** + * Returns a group with the {@code groupName} from the GroupList. + * @param groupName Name of group to look for + * @return The group with the group name + * @throws CommandException If GroupList does not contain a group with the name + */ + public Group getGroup(String groupName) throws CommandException { + for (Group group : this.internalList) { + if (group.nameEquals(groupName)) { + return group; + } + } + throw new CommandException(Messages.MESSAGE_NO_GROUP_WITH_NAME_FOUND); + } + + /** + * Returns a group with the {@code groupName} from the GroupList. + * @param groupToGet Group to look for + * @return The group with the group name + * @throws CommandException If GroupList does not contain a group with the name + */ + public Group getGroup(Group groupToGet) throws CommandException { + for (Group group : this.internalList) { + if (group.nameEquals(groupToGet.getGroupName())) { + return group; + } + } + throw new CommandException(Messages.MESSAGE_NO_GROUP_WITH_NAME_FOUND); + } + + /** + * Converts the internal list to streams. + * + * @return Internal list into streams. + */ + public Stream toStream() { + return internalList.stream(); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + public ObservableList asUnmodifiableObservableList() { + return internalUnmodifiableList; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof GroupList)) { + return false; + } + + GroupList otherGroupList = (GroupList) other; + return internalList.equals(otherGroupList.internalList); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + @Override + public String toString() { + return internalList.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/group/GroupRemark.java b/src/main/java/seedu/address/model/group/GroupRemark.java new file mode 100644 index 00000000000..25fd12f65ce --- /dev/null +++ b/src/main/java/seedu/address/model/group/GroupRemark.java @@ -0,0 +1,36 @@ +package seedu.address.model.group; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a Group's remark in the address book. + * Guarantees: immutable; is always valid + */ +public class GroupRemark { + public final String value; + + /** + * Creates a group remark object with the string provided + */ + public GroupRemark(String groupRemark) { + requireNonNull(groupRemark); + value = groupRemark; + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GroupRemark // instanceof handles nulls + && value.equals(((GroupRemark) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/group/exceptions/DuplicateGroupException.java b/src/main/java/seedu/address/model/group/exceptions/DuplicateGroupException.java new file mode 100644 index 00000000000..f652bfdf6bd --- /dev/null +++ b/src/main/java/seedu/address/model/group/exceptions/DuplicateGroupException.java @@ -0,0 +1,11 @@ +package seedu.address.model.group.exceptions; + +/** + * Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same + * identity). + */ +public class DuplicateGroupException extends RuntimeException { + public DuplicateGroupException() { + super("Operation would result in person being assigned to duplicate groups"); + } +} diff --git a/src/main/java/seedu/address/model/group/exceptions/GroupNotFoundException.java b/src/main/java/seedu/address/model/group/exceptions/GroupNotFoundException.java new file mode 100644 index 00000000000..28b04c64198 --- /dev/null +++ b/src/main/java/seedu/address/model/group/exceptions/GroupNotFoundException.java @@ -0,0 +1,10 @@ +package seedu.address.model.group.exceptions; + +/** + * Exception thrown when Group specified can not be found + */ +public class GroupNotFoundException extends RuntimeException { + public GroupNotFoundException() { + super("Group not found"); + } +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java index c62e512bc29..bf6e414fa93 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/address/model/person/Email.java @@ -11,22 +11,22 @@ public class Email { private static final String SPECIAL_CHARACTERS = "+_.-"; public static final String MESSAGE_CONSTRAINTS = "Emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + "). The local-part may not start or end with any special " - + "characters.\n" - + "2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels " - + "separated by periods.\n" - + "The domain name must:\n" - + " - 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."; + + "and adhere to the following constraints:\n" + + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " + + "the parentheses, (" + SPECIAL_CHARACTERS + "). The local-part may not start or end with any special " + + "characters.\n" + + "2. This is followed by a '@' and then a domain name. The domain name is made up of domain labels " + + "separated by periods.\n" + + "The domain name must:\n" + + " - 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 + "]" - + ALPHANUMERIC_NO_UNDERSCORE + ")*"; + + ALPHANUMERIC_NO_UNDERSCORE + ")*"; private static final String DOMAIN_PART_REGEX = ALPHANUMERIC_NO_UNDERSCORE - + "(-" + ALPHANUMERIC_NO_UNDERSCORE + ")*"; + + "(-" + ALPHANUMERIC_NO_UNDERSCORE + ")*"; private static final String DOMAIN_LAST_PART_REGEX = "(" + DOMAIN_PART_REGEX + "){2,}$"; // At least two chars private static final String DOMAIN_REGEX = "(" + DOMAIN_PART_REGEX + "\\.)*" + DOMAIN_LAST_PART_REGEX; public static final String VALIDATION_REGEX = LOCAL_PART_REGEX + "@" + DOMAIN_REGEX; diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/person/Name.java index 173f15b9b00..6bd915b6653 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/person/Name.java @@ -10,7 +10,7 @@ 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 alphanumeric characters and spaces, and it should not be blank"; /* * The first character of the address must not be a whitespace, @@ -38,9 +38,13 @@ public static boolean isValidName(String test) { return test.matches(VALIDATION_REGEX); } + public boolean nameEquals(String personName) { + return (this.fullName).equals(personName); + } + @Override - public String toString() { + public java.lang.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..bff4e86c2eb 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java @@ -19,7 +19,7 @@ public NameContainsKeywordsPredicate(List keywords) { @Override public boolean test(Person person) { return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); } @Override diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java index abe8c46b535..f3c95e7db15 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/address/model/person/Person.java @@ -1,14 +1,17 @@ package seedu.address.model.person; +import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; -import java.util.Collections; -import java.util.HashSet; +import java.util.ArrayList; import java.util.Objects; -import java.util.Set; import seedu.address.commons.util.ToStringBuilder; -import seedu.address.model.tag.Tag; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.TimeInterval; +import seedu.address.model.TimeIntervalList; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupList; /** * Represents a Person in the address book. @@ -21,20 +24,35 @@ public class Person { private final Phone phone; private final Email email; + // Data fields - private final Address address; - private final Set tags = new HashSet<>(); + private GroupList personGroups; + + private final TimeIntervalList timeIntervalList; + + + /** + * Every field must be present and not null. + */ + public Person(Name name, Phone phone, Email email, GroupList personGroups) { + requireAllNonNull(name, phone, email, personGroups); + this.name = name; + this.phone = phone; + this.email = email; + this.personGroups = personGroups; + this.timeIntervalList = new TimeIntervalList(); + } /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Phone phone, Email email, GroupList personGroups, TimeIntervalList timeIntervalList) { + requireAllNonNull(name, phone, email, personGroups, timeIntervalList); this.name = name; this.phone = phone; this.email = email; - this.address = address; - this.tags.addAll(tags); + this.personGroups = personGroups; + this.timeIntervalList = timeIntervalList; } public Name getName() { @@ -49,29 +67,96 @@ public Email getEmail() { return email; } - public Address getAddress() { - return address; + public GroupList getGroups() { + return this.personGroups; } /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. + * Adds group to persons existing groupList + * + * @param group to be added to person groupList */ - public Set getTags() { - return Collections.unmodifiableSet(tags); + public void addGroup(Group group) throws CommandException { + requireNonNull(group); + if (this.personGroups.contains(group)) { + throw new CommandException( + String.format("%s is already in this group: %s", this.name.fullName, group.getGroupName())); + } + this.personGroups.add(group); } /** - * Returns true if both persons have the same name. + * Removes group from persons existing groupList + * + * @param group to be removed from person groupList + */ + public void removeGroup(Group group) throws CommandException { + requireNonNull(group); + if (!this.personGroups.contains(group)) { + throw new CommandException( + String.format("%s is not in this group: %s", this.name.fullName, group.getGroupName())); + } + this.personGroups.remove(group); + } + + /** + * Check whether person is part of group + * + * @param group group to check + * @return boolean depending on whether person is in group + */ + public boolean containsGroup(Group group) { + return personGroups.contains(group); + } + + /** + * Returns true if both persons have an identical field. * This defines a weaker notion of equality between two persons. */ public boolean isSamePerson(Person otherPerson) { + return isSameName(otherPerson); + } + + /** + * Returns true if both persons have the same name. + * This defines a weaker notion of equality between two persons. + */ + public boolean isSameName(Person otherPerson) { + if (otherPerson == this) { + return true; + } + + return otherPerson != null + && otherPerson.getName().equals(getName()); + } + + /** + * Returns true if both persons have the same number. + * This defines a weaker notion of equality between two persons. + */ + public boolean isSamePhone(Person otherPerson) { if (otherPerson == this) { return true; } return otherPerson != null - && otherPerson.getName().equals(getName()); + && otherPerson.getPhone().equals(getPhone()); + } + + /** + * Returns true if both persons have the same email. + * This defines a weaker notion of equality between two persons. + */ + public boolean isSameEmail(Person otherPerson) { + if (otherPerson == this) { + return true; + } + return otherPerson != null + && otherPerson.getEmail().equals(getEmail()); + } + + public boolean nameEquals(String personName) { + return name.nameEquals(personName); } /** @@ -91,27 +176,53 @@ 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); + && phone.equals(otherPerson.phone) + && email.equals(otherPerson.email) + && personGroups.equals(otherPerson.personGroups) + && timeIntervalList.equals(otherPerson.timeIntervalList); } @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, personGroups); } @Override public String toString() { return new ToStringBuilder(this) - .add("name", name) - .add("phone", phone) - .add("email", email) - .add("address", address) - .add("tags", tags) - .toString(); + .add("name", name) + .add("phone", phone) + .add("email", email) + .add("groups", personGroups) + .toString(); + } + + public TimeIntervalList getTime() { + return this.timeIntervalList; + } + + public void addFreeTime(TimeInterval toAddFreeTime) { + this.timeIntervalList.addTime(toAddFreeTime); + } + + public String addFreeTime(ArrayList toAddFreeTime) { + return this.timeIntervalList.addTime(toAddFreeTime); } + public String deleteFreeTime(ArrayList toAddFreeTime) throws CommandException { + return this.timeIntervalList.deleteTime(toAddFreeTime); + } + + public boolean hasFreeTime(TimeInterval freeTime) { + return this.timeIntervalList.hasTime(freeTime); + } + + /** + * Check whether Person has input their free time + * @return boolean, whether person is not free + */ + public boolean isNotFree() { + return this.timeIntervalList.isEmpty(); + } } diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java index d733f63d739..15bb4da1991 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/address/model/person/Phone.java @@ -11,7 +11,7 @@ public class Phone { public static final String MESSAGE_CONSTRAINTS = - "Phone numbers should only contain numbers, and it should be at least 3 digits long"; + "Phone numbers should only contain numbers, and it should be at least 3 digits long"; public static final String VALIDATION_REGEX = "\\d{3,}"; public final String value; diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java index cc0a68d79f9..91448794c58 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/address/model/person/UniquePersonList.java @@ -8,16 +8,19 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import seedu.address.logic.Messages; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.person.exceptions.DuplicatePersonException; import seedu.address.model.person.exceptions.PersonNotFoundException; /** * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * A person is considered unique by comparing using {@code Person#isSamePerson(Person)}. As such, adding and updating of - * persons uses Person#isSamePerson(Person) for equality so as to ensure that the person being added or updated is - * unique in terms of identity in the UniquePersonList. However, the removal of a person uses Person#equals(Object) so + * A person is considered unique by comparing using {@code Person#isNumberTaken(Person), Person#isNameTaken(Person) + * and Person#isEmailTaken(Person)}. As such, adding and updating of persons uses the three tests for equality + * so as to ensure that the person being added or updated is unique in terms of fields in the UniquePersonList. + * However, the removal of a person uses Person#equals(Object) so * as to ensure that the person with exactly the same fields will be removed. - * + *

    * Supports a minimal set of list operations. * * @see Person#isSamePerson(Person) @@ -26,7 +29,7 @@ public class UniquePersonList implements Iterable { private final ObservableList internalList = FXCollections.observableArrayList(); private final ObservableList internalUnmodifiableList = - FXCollections.unmodifiableObservableList(internalList); + FXCollections.unmodifiableObservableList(internalList); /** * Returns true if the list contains an equivalent person as the given argument. @@ -36,6 +39,22 @@ public boolean contains(Person toCheck) { return internalList.stream().anyMatch(toCheck::isSamePerson); } + /** + * Returns true if the list contains an equivalent email as the given argument. + */ + public boolean containsEmail(Person toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSameEmail); + } + + /** + * Returns true if the list contains an equivalent phonenumber as the given argument. + */ + public boolean containsPhoneNumber(Person toCheck) { + requireNonNull(toCheck); + return internalList.stream().anyMatch(toCheck::isSamePhone); + } + /** * Adds a person to the list. * The person must not already exist in the list. @@ -48,33 +67,14 @@ public void add(Person toAdd) { internalList.add(toAdd); } - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * {@code target} must exist in the list. - * The person identity of {@code editedPerson} must not be the same as another existing person in the list. - */ - public void setPerson(Person target, Person editedPerson) { - requireAllNonNull(target, editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.isSamePerson(editedPerson) && contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - /** * Removes the equivalent person from the list. * The person must exist in the list. */ public void remove(Person toRemove) { requireNonNull(toRemove); - if (!internalList.remove(toRemove)) { + boolean isRemoved = internalList.remove(toRemove); + if (!isRemoved) { throw new PersonNotFoundException(); } } @@ -97,6 +97,28 @@ public void setPersons(List persons) { internalList.setAll(persons); } + /** + * Replaces the person {@code target} in the list with {@code editedPerson}. + * {@code target} must exist in the list. + * The person identity of {@code editedPerson} must not be the same as another existing person in the list. + */ + public void setPerson(Person target, Person editedPerson) { + requireAllNonNull(target, editedPerson); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PersonNotFoundException(); + } + + if (!target.isSamePerson(editedPerson) + && contains(editedPerson)) { + throw new DuplicatePersonException(); + } + + internalList.set(index, editedPerson); + } + + /** * Returns the backing list as an unmodifiable {@code ObservableList}. */ @@ -147,4 +169,13 @@ private boolean personsAreUnique(List persons) { } return true; } + + public Person getPerson(String personName) throws CommandException { + for (Person person : this.internalList) { + if (person.nameEquals(personName)) { + return person; + } + } + throw new CommandException(Messages.MESSAGE_NO_PERSON_WITH_NAME_FOUND); + } } diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java index fa764426ca7..588cfb5fbf8 100644 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java @@ -3,4 +3,5 @@ /** * Signals that the operation is unable to find the specified person. */ -public class PersonNotFoundException extends RuntimeException {} +public class PersonNotFoundException extends RuntimeException { +} 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..2a4c5e40795 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -1,17 +1,15 @@ package seedu.address.model.util; import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupList; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; /** * Contains utility methods for populating {@code AddressBook} with sample data. @@ -20,23 +18,17 @@ public class SampleDataUtil { public static Person[] getSamplePersons() { return new Person[] { new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), + getGroupList()), 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")), + getGroupList()), 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")), + getGroupList()), 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")), + getGroupList()), 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")), + getGroupList()), 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")) + getGroupList()), }; } @@ -49,12 +41,15 @@ public static ReadOnlyAddressBook getSampleAddressBook() { } /** - * Returns a tag set containing the list of strings given. + * Returns a GroupList containing the list of strings given. + * + * @param groupNames List of groupNames. + * @return GroupList containing the list of strings given. */ - public static Set getTagSet(String... strings) { - return Arrays.stream(strings) - .map(Tag::new) - .collect(Collectors.toSet()); + public static GroupList getGroupList(String... groupNames) { + GroupList gL = new GroupList(); + Arrays.stream(groupNames).forEach(group -> gL.add(new Group(group))); + return gL; } } diff --git a/src/main/java/seedu/address/storage/JsonAdaptedGroup.java b/src/main/java/seedu/address/storage/JsonAdaptedGroup.java new file mode 100644 index 00000000000..0f4781435e4 --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedGroup.java @@ -0,0 +1,82 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.TimeIntervalList; +import seedu.address.model.group.Group; +import seedu.address.model.group.GroupRemark; + +/** + * Jackson-friendly version of {@link Group}. + */ +class JsonAdaptedGroup { + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Group's %s field is missing!"; + + private final String groupName; + private final String groupRemark; + private final List meetingTimeList = new ArrayList<>(); + + /** + * Constructs a {@code JsonAdaptedGroup} with the given {@code groupName}. + */ + @JsonCreator + public JsonAdaptedGroup(@JsonProperty("name") String groupName, @JsonProperty("groupRemark") String groupRemark, + @JsonProperty("meetingTimeList") List meetingTimeList) { + this.groupName = groupName; + this.groupRemark = groupRemark; + + if (meetingTimeList != null) { + this.meetingTimeList.addAll(meetingTimeList); + } + } + + /** + * Converts a given {@code Group} into this class for Jackson use. + */ + public JsonAdaptedGroup(Group source) { + requireNonNull(source); + groupName = source.getGroupName(); + groupRemark = source.getGroupRemark().value; + + if (meetingTimeList != null) { + meetingTimeList.addAll(source.getTime().toStream() + .map(JsonAdaptedTime::new) + .collect(Collectors.toList())); + } + } + + /** + * Converts this Jackson-friendly adapted tag object into the model's {@code JsonAdaptedGroup} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted tag. + */ + public Group toModelType() throws IllegalValueException { + if (groupName == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Group.class.getSimpleName())); + } + if (!Group.isValidGroupName(groupName)) { + throw new IllegalValueException(Group.MESSAGE_CONSTRAINTS); + } + + if (groupRemark == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + GroupRemark.class.getSimpleName())); + } + + TimeIntervalList modelTimeIntervalListList = new TimeIntervalList(); + for (JsonAdaptedTime freeTime : meetingTimeList) { + modelTimeIntervalListList.addTime(freeTime.toModelType()); + } + + return new Group(groupName, new GroupRemark(groupRemark), modelTimeIntervalListList); + } + +} diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java index bd1ca0f56c8..907d4b6f238 100644 --- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java +++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java @@ -1,21 +1,19 @@ package seedu.address.storage; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; +import seedu.address.model.TimeIntervalList; +import seedu.address.model.group.GroupList; import seedu.address.model.person.Email; import seedu.address.model.person.Name; import seedu.address.model.person.Person; import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; /** * Jackson-friendly version of {@link Person}. @@ -27,22 +25,26 @@ class JsonAdaptedPerson { private final String name; private final String phone; private final String email; - private final String address; - private final List tags = new ArrayList<>(); + private final List groupList = new ArrayList<>(); + private final List freeTimeList = 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) { + @JsonProperty("email") String email, + @JsonProperty("groupList") List groupList, + @JsonProperty("freeTimeList") List freeTimeList + ) { this.name = name; this.phone = phone; this.email = email; - this.address = address; - if (tags != null) { - this.tags.addAll(tags); + if (groupList != null) { + this.groupList.addAll(groupList); + } + if (freeTimeList != null) { + this.freeTimeList.addAll(freeTimeList); } } @@ -53,9 +55,11 @@ public JsonAdaptedPerson(Person source) { name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; - address = source.getAddress().value; - tags.addAll(source.getTags().stream() - .map(JsonAdaptedTag::new) + groupList.addAll(source.getGroups().toStream() + .map(JsonAdaptedGroup::new) + .collect(Collectors.toList())); + freeTimeList.addAll(source.getTime().toStream() + .map(JsonAdaptedTime::new) .collect(Collectors.toList())); } @@ -65,11 +69,6 @@ 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()); - } - if (name == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); } @@ -94,16 +93,27 @@ public Person toModelType() throws IllegalValueException { } final Email modelEmail = new Email(email); - if (address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + if (groupList == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + GroupList.class.getSimpleName())); } - if (!Address.isValidAddress(address)) { - throw new IllegalValueException(Address.MESSAGE_CONSTRAINTS); + + GroupList modelGroupList = new GroupList(); + for (JsonAdaptedGroup group : groupList) { + modelGroupList.add(group.toModelType()); + } + + if (freeTimeList == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + TimeIntervalList.class.getSimpleName())); + } + + TimeIntervalList modelTimeIntervalListList = new TimeIntervalList(); + for (JsonAdaptedTime freeTime : freeTimeList) { + modelTimeIntervalListList.addTime(freeTime.toModelType()); } - final Address modelAddress = new Address(address); - final Set modelTags = new HashSet<>(personTags); - return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags); + return new Person(modelName, modelPhone, modelEmail, modelGroupList, modelTimeIntervalListList); } } 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/JsonAdaptedTime.java b/src/main/java/seedu/address/storage/JsonAdaptedTime.java new file mode 100644 index 00000000000..979250a1e0f --- /dev/null +++ b/src/main/java/seedu/address/storage/JsonAdaptedTime.java @@ -0,0 +1,79 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.time.DayOfWeek; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.Time; +import seedu.address.model.TimeInterval; + +/** + * Jackson-friendly version of {@link Time}. + */ +public class JsonAdaptedTime { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Interval's %s time field is missing!"; + + private final String start; + private final String end; + + /** + * Constructs a {@code JsonAdaptedTime} with the given time details. + */ + @JsonCreator + public JsonAdaptedTime(@JsonProperty("start") String start, @JsonProperty("end") String end) { + this.start = start; + this.end = end; + } + + /** + * Converts a given {@code TimeInterval} into this class for Jackson use. + */ + public JsonAdaptedTime(TimeInterval source) { + requireNonNull(source); + this.start = source.getStart().toString(); + this.end = source.getEnd().toString(); + } + + /** + * Converts this Jackson-friendly adapted person object into the model's {@code TimeInterval} object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person. + */ + public TimeInterval toModelType() throws IllegalValueException { + if (start == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "start")); + } + if (!Time.isValidTime(start)) { + throw new IllegalValueException(Time.MESSAGE_CONSTRAINTS); + } + if (end == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, "end")); + } + if (!Time.isValidTime(end)) { + throw new IllegalValueException(Time.MESSAGE_CONSTRAINTS); + } + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HHmm"); + String[] startArray = start.split(" "); + String[] endArray = end.split(" "); + DayOfWeek startDay = Time.decodeDay(startArray[0]); + DayOfWeek endDay = Time.decodeDay(endArray[0]); + LocalTime startTime = LocalTime.parse(startArray[1], formatter); + LocalTime endTime = LocalTime.parse(endArray[1], formatter); + + Time start = new Time(startDay, startTime); + Time end = new Time(endDay, endTime); + if (!TimeInterval.isValidTimeIntervalLogic(start, end)) { + throw new ParseException(TimeInterval.MESSAGE_CONSTRAINTS_LOGIC); + } + return new TimeInterval(start, end); + } +} diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java index 5efd834091d..f806decaa13 100644 --- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java +++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java @@ -9,9 +9,12 @@ import com.fasterxml.jackson.annotation.JsonRootName; import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.AddressBook; import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.group.exceptions.DuplicateGroupException; import seedu.address.model.person.Person; +import seedu.address.model.person.exceptions.DuplicatePersonException; /** * An Immutable AddressBook that is serializable to JSON format. @@ -20,15 +23,19 @@ class JsonSerializableAddressBook { public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s)."; + public static final String MESSAGE_DUPLICATE_GROUP = "Group list contains duplicate group(s)."; private final List persons = new ArrayList<>(); + private final List groups = new ArrayList<>(); /** * Constructs a {@code JsonSerializableAddressBook} with the given persons. */ @JsonCreator - public JsonSerializableAddressBook(@JsonProperty("persons") List persons) { + public JsonSerializableAddressBook(@JsonProperty("persons") List persons, + @JsonProperty("groups") List groups) { this.persons.addAll(persons); + this.groups.addAll(groups); } /** @@ -38,6 +45,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List { + try { + addressBook.getGroup(group).addPerson(person); + } catch (CommandException e) { + throw new RuntimeException(e); + } + }); } return addressBook; } diff --git a/src/main/java/seedu/address/ui/Calendar.java b/src/main/java/seedu/address/ui/Calendar.java new file mode 100644 index 00000000000..1890b405dc3 --- /dev/null +++ b/src/main/java/seedu/address/ui/Calendar.java @@ -0,0 +1,96 @@ +package seedu.address.ui; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +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.model.group.Group; +import seedu.address.ui.util.GroupTimeContainer; + +/** + * The UI component that is responsible for the calendar. + */ +public class Calendar extends UiPart { + + private static final String FXML = "Calendar.fxml"; + private final ObservableList groupList; + private final ObservableList dayTaskMon = FXCollections.observableArrayList(); + private final ObservableList dayTaskTue = FXCollections.observableArrayList(); + private final ObservableList dayTaskWed = FXCollections.observableArrayList(); + private final ObservableList dayTaskThu = FXCollections.observableArrayList(); + private final ObservableList dayTaskFri = FXCollections.observableArrayList(); + private final ObservableList dayTaskSat = FXCollections.observableArrayList(); + private final ObservableList dayTaskSun = FXCollections.observableArrayList(); + private final ObservableList> day = FXCollections.observableArrayList(); + + @FXML + private ListView> dayListView; + + /** + * Creates a {@code Calendar with tasks} with the given {@code groupList}. + */ + public Calendar(ObservableList groupList) { + super(FXML); + this.groupList = groupList; + day.add(dayTaskMon); //mon + day.add(dayTaskTue); //tue + day.add(dayTaskWed); //wed + day.add(dayTaskThu); //thu + day.add(dayTaskFri); //fri + day.add(dayTaskSat); //sat + day.add(dayTaskSun); //sun + groupList.addListener(new ListChangeListener() { + @Override + public void onChanged(Change c) { + clearDays(); + convertGrpListToContainer(); + } + }); + convertGrpListToContainer(); + dayListView.setItems(day); + dayListView.setCellFactory(listView -> new DayListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code ObservableList} + * using a {@code DayCard}. + */ + class DayListViewCell extends ListCell> { + @Override + protected void updateItem(ObservableList grpTimeContainer, boolean empty) { + super.updateItem(grpTimeContainer, empty); + if (empty || grpTimeContainer == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new DayCard(grpTimeContainer, getIndex() + 1).getRoot()); + } + } + } + + private void convertGrpListToContainer() { + groupList.iterator().forEachRemaining(group -> { + group.getTime().iterator().forEachRemaining(timeInterval -> { + int startDay = timeInterval.getStartTimeDay().getValue() - 1; + ObservableList curDay = day.get(startDay); + if (!curDay.contains(new GroupTimeContainer(group, timeInterval))) { + curDay.add(new GroupTimeContainer(group, timeInterval)); + } + }); + }); + } + + private void clearDays() { + dayTaskMon.clear(); + dayTaskTue.clear(); + dayTaskWed.clear(); + dayTaskThu.clear(); + dayTaskFri.clear(); + dayTaskSat.clear(); + dayTaskSun.clear(); + } + +} diff --git a/src/main/java/seedu/address/ui/DayCard.java b/src/main/java/seedu/address/ui/DayCard.java new file mode 100644 index 00000000000..36413766207 --- /dev/null +++ b/src/main/java/seedu/address/ui/DayCard.java @@ -0,0 +1,60 @@ +package seedu.address.ui; + +import java.time.DayOfWeek; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.ui.util.GroupTimeContainer; + +/** + * The UI component that is responsible for each day in the calendar + */ +public class DayCard extends UiPart { + private static final String FXML = "DayCard.fxml"; + + public final ObservableList dayTaskList; + @FXML + private HBox dayCard; + @FXML + private Label day; + @FXML + private Label task; + @FXML + private ListView eachDayTaskList; + + /** + * Creates a {@code DayCard in the calendar} with the given {@code dayTaskList} and index to display day. + */ + public DayCard(ObservableList dayTaskList, int dayIndex) { + super(FXML); + this.dayTaskList = dayTaskList; + day.setText(DayOfWeek.of(dayIndex).toString().substring(0, 3)); + eachDayTaskList.setItems(dayTaskList); + eachDayTaskList.setCellFactory(listview -> new EachDayTaskListCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code GroupTimeContainer} using a + * {@code EachDayTaskLine}. + */ + class EachDayTaskListCell extends ListCell { + @Override + protected void updateItem(GroupTimeContainer task, boolean empty) { + super.updateItem(task, empty); + if (empty || task == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new EachDayTaskLine(task).getRoot()); + } + } + } + + + +} diff --git a/src/main/java/seedu/address/ui/EachDayTaskLine.java b/src/main/java/seedu/address/ui/EachDayTaskLine.java new file mode 100644 index 00000000000..d0d75be1f83 --- /dev/null +++ b/src/main/java/seedu/address/ui/EachDayTaskLine.java @@ -0,0 +1,36 @@ +package seedu.address.ui; + + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.ui.util.GroupTimeContainer; + +/** + * The UI component that is responsible for each line in each day task list + */ +public class EachDayTaskLine extends UiPart { + + private static final String FXML = "EachDayTaskLine.fxml"; + private final GroupTimeContainer task; + @FXML + private HBox individualTaskLine; + @FXML + private Label dot; + @FXML + private Label groupInSch; + @FXML + private Label taskLine; + + /** + * Creates a {@code line of group name and meeting time} with the given {@code task}. + */ + public EachDayTaskLine(GroupTimeContainer task) { + super(FXML); + this.task = task; + groupInSch.setText(task.getGroup().getGroupName()); + taskLine.setText(task.getTimeInterval().toString()); + } + +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 3f16b2fcf26..1483d590600 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://ay2324s1-cs2103t-t10-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..a751523ef82 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -34,6 +34,7 @@ public class MainWindow extends UiPart { private PersonListPanel personListPanel; private ResultDisplay resultDisplay; private HelpWindow helpWindow; + private Calendar calendar; @FXML private StackPane commandBoxPlaceholder; @@ -47,6 +48,9 @@ public class MainWindow extends UiPart { @FXML private StackPane resultDisplayPlaceholder; + @FXML + private StackPane dayListPanelPlaceholder; + @FXML private StackPane statusbarPlaceholder; @@ -113,6 +117,9 @@ void fillInnerParts() { personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + calendar = new Calendar(logic.getFilteredGroupList()); + dayListPanelPlaceholder.getChildren().add(calendar.getRoot()); + resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java index 094c42cda82..fc65cac1671 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/PersonCard.java @@ -7,6 +7,7 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; +import seedu.address.model.group.Group; import seedu.address.model.person.Person; /** @@ -14,7 +15,7 @@ */ public class PersonCard extends UiPart { - private static final String FXML = "PersonListCard.fxml"; + private static final String FXML = "PersonCard.fxml"; /** * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. @@ -39,7 +40,9 @@ public class PersonCard extends UiPart { @FXML private Label email; @FXML - private FlowPane tags; + private FlowPane groups; + @FXML + private FlowPane freeTime; /** * Creates a {@code PersonCode} with the given {@code Person} and index to display. @@ -50,10 +53,12 @@ public PersonCard(Person person, int displayedIndex) { id.setText(displayedIndex + ". "); name.setText(person.getName().fullName); phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); email.setText(person.getEmail().value); - person.getTags().stream() - .sorted(Comparator.comparing(tag -> tag.tagName)) - .forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); + person.getGroups() + .toStream() + .sorted(Comparator.comparing(Group::getGroupName)) + .forEach(group -> groups.getChildren().add(new Label(group.getGroupName()))); + person.getTime().iterator().forEachRemaining(interval -> + freeTime.getChildren().add(new Label(interval.toString()))); } } diff --git a/src/main/java/seedu/address/ui/util/GroupTimeContainer.java b/src/main/java/seedu/address/ui/util/GroupTimeContainer.java new file mode 100644 index 00000000000..cfde78e9336 --- /dev/null +++ b/src/main/java/seedu/address/ui/util/GroupTimeContainer.java @@ -0,0 +1,44 @@ +package seedu.address.ui.util; + +import seedu.address.model.TimeInterval; +import seedu.address.model.group.Group; + +/** + * A helper class to create a object that contains a group and a single timeInterval + */ +public class GroupTimeContainer { + private final Group group; + + private final TimeInterval timeInterval; + + /** + * Constructor for GroupTimeContainer + */ + public GroupTimeContainer(Group group, TimeInterval timeInterval) { + this.group = group; + this.timeInterval = timeInterval; + } + + public Group getGroup() { + return group; + } + + public TimeInterval getTimeInterval() { + return timeInterval; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + // instanceof handles nulls + if (!(other instanceof GroupTimeContainer)) { + return false; + } + GroupTimeContainer otherGrpTimeContainer = (GroupTimeContainer) other; + return this.group.equals(otherGrpTimeContainer.group) + && this.timeInterval.equals(otherGrpTimeContainer.timeInterval); + } + +} diff --git a/src/main/main.iml b/src/main/main.iml new file mode 100644 index 00000000000..b89a3f5b0a6 --- /dev/null +++ b/src/main/main.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/main/resources/view/Calendar.fxml b/src/main/resources/view/Calendar.fxml new file mode 100644 index 00000000000..96a9f6701e6 --- /dev/null +++ b/src/main/resources/view/Calendar.fxml @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 124283a392e..a71320de2d4 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -3,7 +3,8 @@ - - + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index 36e6b001cd8..561567675d9 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,7 +1,3 @@ -.background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ -} .label { -fx-font-size: 11pt; @@ -27,6 +23,8 @@ .text-field { -fx-font-size: 12pt; -fx-font-family: "Segoe UI Semibold"; + -fx-prompt-text-fill: black; + } .tab-pane { @@ -42,25 +40,13 @@ .table-view { -fx-base: #1d1d1d; -fx-control-inner-background: #1d1d1d; - -fx-background-color: #1d1d1d; - -fx-table-cell-border-color: transparent; - -fx-table-header-border-color: transparent; -fx-padding: 5; } -.table-view .column-header-background { - -fx-background-color: transparent; -} - .table-view .column-header, .table-view .filler { -fx-size: 35; -fx-border-width: 0 0 1 0; - -fx-background-color: transparent; - -fx-border-color: - transparent - transparent - derive(-fx-base, 80%) - transparent; + -fx-border-color: derive(-fx-base, 80%); -fx-border-insets: 0 10 1 0; } @@ -72,27 +58,6 @@ -fx-opacity: 1; } -.table-view:focused .table-row-cell:filled:focused:selected { - -fx-background-color: -fx-focus-color; -} - -.split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: transparent transparent transparent #4d4d4d; -} - -.split-pane { - -fx-border-radius: 1; - -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); -} - -.list-view { - -fx-background-insets: 0; - -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); -} - .list-cell { -fx-label-padding: 0 0 0 0; -fx-graphic-text-gap : 0; @@ -100,25 +65,20 @@ } .list-cell:filled:even { - -fx-background-color: #3c3e3f; -} - -.list-cell:filled:odd { - -fx-background-color: #515658; -} + -fx-background-color: rgba(255, 255, 255, 0.4); + -fx-effect: dropshadow(gaussian, #ffd4d4, 50, 0, 0, 0); + -fx-background-insets: 0; + -fx-background-radius: 0; -.list-cell:filled:selected { - -fx-background-color: #424d5f; } -.list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; - -fx-border-width: 1; +.list-cell:filled:odd { + -fx-background-color: rgba(255, 255, 255, 0.4); + -fx-effect: dropshadow(gaussian, #ffd4d4, 50, 0, 0, 0); + -fx-background-insets: 0; + -fx-background-radius: 0; } -.list-cell .label { - -fx-text-fill: white; -} .cell_big_label { -fx-font-family: "Segoe UI Semibold"; @@ -132,22 +92,16 @@ -fx-text-fill: #010504; } -.stack-pane { - -fx-background-color: derive(#1d1d1d, 20%); -} - .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); - -fx-border-top-width: 1px; + -fx-background-color: #56494c; + -fx-border-color: #56494c; } .status-bar { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: derive(#56494c, 5%); } .result-display { - -fx-background-color: transparent; -fx-font-family: "Segoe UI Light"; -fx-font-size: 13pt; -fx-text-fill: white; @@ -159,7 +113,7 @@ .status-bar .label { -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-text-fill: black; -fx-padding: 4px; -fx-pref-height: 30px; } @@ -181,25 +135,26 @@ } .grid-pane .stack-pane { - -fx-background-color: derive(#1d1d1d, 30%); + -fx-background-color: #818080; } .context-menu { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: #818080; } .context-menu .label { -fx-text-fill: white; } +// top bar .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#56494c, 5%); } .menu-bar .label { -fx-font-size: 14pt; -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-text-fill: black; -fx-opacity: 0.9; } @@ -282,11 +237,11 @@ } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#c2c2c2, 80%); } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); + -fx-background-color: derive(#d4a9a9, 0%); -fx-background-insets: 3; } @@ -317,34 +272,91 @@ -fx-text-fill: #F70D1A; } +//input box #commandTextField { - -fx-background-color: transparent #383838 transparent #383838; - -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; - -fx-border-insets: 0; - -fx-border-width: 1; + -fx-background-color: #FFFFFF; + -fx-border-color: #FFFFFF; + -fx-text-fill: black; + -fx-background-radius: 50px; + -fx-border-radius: 50px; -fx-font-family: "Segoe UI Light"; -fx-font-size: 13pt; - -fx-text-fill: white; } #filterField, #personListPanel, #personWebpage { -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); } -#resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; - -fx-background-radius: 0; +#personListView { + -fx-padding: 0 30 30 30; + -fx-background-color: #D2B48C +} + +#dayListView { + -fx-padding: 0 10 10 10; + -fx-background-color: #D2B48C +} + +#resultDisplay .content{ + -fx-background-color: #9B6572; + border-color: #B88491; + border-radius: 0; +} + +#groups { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#day { + -fx-text-fill: black; + -fx-font-size: 18; + +} + +#groups .label { + -fx-text-fill: white; + -fx-background-color: #E18888; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; } -#tags { +#freeTime { -fx-hgap: 7; -fx-vgap: 3; } -#tags .label { +#freeTime .label { -fx-text-fill: white; - -fx-background-color: #3e7b91; + -fx-background-color: #B488E1; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + +#ra { + -fx-text-fill: White; + -fx-font-size: 20; +} + +#eachDayTaskList { + -fx-background-color: transparent; +} + +#eachDayTaskList .list-cell:filled { + -fx-background-color: transparent; +} + +#eachDayTaskList .list-cell { + -fx-background-color: transparent; +} + +#groupInSch { + -fx-text-fill: white; + -fx-background-color: #E18888; -fx-padding: 1 3 1 3; -fx-border-radius: 2; -fx-background-radius: 2; diff --git a/src/main/resources/view/DayCard.fxml b/src/main/resources/view/DayCard.fxml new file mode 100644 index 00000000000..3b09c5c3120 --- /dev/null +++ b/src/main/resources/view/DayCard.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/EachDayTaskLine.fxml b/src/main/resources/view/EachDayTaskLine.fxml new file mode 100644 index 00000000000..d5477ab50d5 --- /dev/null +++ b/src/main/resources/view/EachDayTaskLine.fxml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css index bfe82a85964..616bea241f3 100644 --- a/src/main/resources/view/Extensions.css +++ b/src/main/resources/view/Extensions.css @@ -5,7 +5,12 @@ .list-cell:empty { /* Empty cells will not have alternating colours */ - -fx-background: #383838; + -fx-background: #D2B48C; +} + +#eachDayTaskList .list-cell:empty{ + /* Empty cells will not have alternating colours */ + -fx-background: transparent; } .tag-selector { diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 7778f666a0a..ecc030437d6 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -11,8 +11,9 @@ + + title="ProjectPRO" minWidth="450" minHeight="600" onCloseRequest="#handleExit"> @@ -23,38 +24,50 @@ - - -

    - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - diff --git a/src/main/resources/view/PersonCard.fxml b/src/main/resources/view/PersonCard.fxml new file mode 100644 index 00000000000..ec4c7ef788d --- /dev/null +++ b/src/main/resources/view/PersonCard.fxml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml deleted file mode 100644 index f5e812e25e6..00000000000 --- a/src/main/resources/view/PersonListCard.fxml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml index a1bb6bbace8..86d09537179 100644 --- a/src/main/resources/view/PersonListPanel.fxml +++ b/src/main/resources/view/PersonListPanel.fxml @@ -2,7 +2,12 @@ + + - - + + + + diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index 01b691792a9..23fbf85c954 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -3,7 +3,9 @@ - -