` 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.
+
+
+**Note:** An alternative (arguably, a more OOP) model is given below. It has a `Tag` list in the `ContactBook`, which `Contact` references. This allows `ContactBook` to only require one `Tag` object per unique tag, instead of each `Contact` needing their own `Tag` objects.
-
+
-
+
### Storage component
**API** : [`Storage.java`](https://github.com/se-edu/addressbook-level3/tree/master/src/main/java/seedu/address/storage/Storage.java)
-
+
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).
+* inherits from both `ContactBookStorage` and `UserPrefStorage`, which means it can be treated as either one (if only the functionality of only one is needed).
* depends on some classes in the `Model` component (because the `Storage` component's job is to save/retrieve objects that belong to the `Model`)
### Common classes
@@ -153,96 +156,128 @@ Classes used by multiple components are in the `seedu.address.commons` package.
## **Implementation**
-This section describes some noteworthy details on how certain features are implemented.
+> This section describes some noteworthy details on how certain features are implemented.
-### \[Proposed\] Undo/redo feature
+### Trip Management System
-#### Proposed Implementation
+The Trip management system allows travel agents to create, edit, delete, and list trips, which are essential for organizing travel plans for customers. Below are the details of its 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:
+#### Trip and Contact Relationship
-* `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.
+In the current implementation, there is intentionally no direct relationship between Trip and Contact entities. This design decision was made to provide:
-These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively.
+- **Flexibility**: Allows trips to be created without requiring contacts to exist in the system first
+- **Simplicity**: Reduces implementation complexity by avoiding dependencies between data models
+- **Independent Management**: Enables users to manage trips and contacts independently
-Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
+This approach supports the current use case where a travel agent might quickly create a trip record with customer names before fully registering those customers as contacts in the system.
-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.
+#### Notes Parameter Behavior
-
+Both Contact and Trip entities support notes through the `nts/` parameter prefix. When parsing commands like `addContact`, `editContact`, `addTrip`, and `editTrip`, developers should be aware of the following important behavior:
-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.
+- If any parameter prefixes that is used in either Contact or Trip (e.g., `n/`, `p/`, `e/`, `a/`, `t/`, `acc/`, `i/`, `d/`, `c/`) appear within the note content, they will be interpreted as separate parameters rather than part of the note text.
+- For example, in a command like `addTrip n/Europe Trip acc/Grand Hotel i/Sightseeing d/1/6/2024 nts/Remember to book n/train tickets`, the text "n/train tickets" would not be part of the note - instead, "n/train tickets" would be treated as a separate name parameter.
+- This behavior is by design and is consistent across all commands that accept the note parameter.
-
+#### Adding a Trip to the Trip Book
-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`.
+
-
+The implementation follows these steps:
-: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`.
+1. `AddressBookParser` identifies that the command type is `addTrip` based on the command word and creates an instance of `AddTripCommandParser` to parse the user input.
-
+2. `AddTripCommandParser` extracts values corresponding to the required prefixes:
+ - The **name prefix** `n/` must contain a non-empty trip name.
+ - The **accommodation prefix** `a/` must contain a non-empty accommodation.
+ - The **itinerary prefix** `i/` must contain a non-empty itinerary.
+ - The **date prefix** `d/` must contain a valid trip date in the format d/M/YYYY.
+ - The **customer name prefix** `cn/` is optional and can be specified multiple times.
+ - The **note prefix** `note/` is optional and contains additional information about the trip.
+kj
+3. If any of the required prefixes are missing or invalid, `AddTripCommandParser` throws a `ParseException`. Otherwise, it creates a new instance of `AddTripCommand` based on the parsed input.
-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.
+4. When executed, `AddTripCommand`:
+ - Checks if the trip already exists in the model by name (case-insensitive)
+ - If it's a duplicate, throws a `CommandException`
+ - Otherwise, adds the trip to the trip book via the model
-
+> **_NOTE:_** TravelHub identifies a trip as duplicate if their trip names match (case-insensitive) with an existing trip in the trip book.
-: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.
+#### Deleting a Trip from the Trip Book
-
+The delete trip feature allows users to remove trips from the trip book by specifying the trip's index in the displayed list.
-The following sequence diagram shows how an undo operation goes through the `Logic` component:
+
-
+1. `AddressBookParser` identifies that the command type is `deleteTrip` based on the command word and creates an instance of `DeleteTripCommandParser` to parse the user input.
-: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.
+2. `DeleteTripCommandParser` extracts the index from the command arguments and ensures:
+ - The **Index** is a valid positive integer.
-
+3. If the index is invalid, `DeleteTripCommandParser` throws a `ParseException`. Otherwise, it creates a new instance of `DeleteTripCommand` based on the user input.
-Similarly, how an undo operation goes through the `Model` component is shown below:
+4. Upon execution, `DeleteTripCommand`:
+ - Checks if the index is within the bounds of the filtered trip list
+ - If the index is invalid, throws a `CommandException`
+ - Retrieves the trip to be deleted from the filtered trip list
+ - Calls `model.deleteTrip(tripToDelete)` to remove the trip from the trip book
+ - Returns a `CommandResult` with a success message
-
+#### Editing a Trip's Details
-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.
+The edit trip feature allows users to modify the details of an existing trip by specifying the trip's index in the displayed list and the new details.
-: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.
+
-
+1. `AddressBookParser` identifies that the command type is `editTrip` based on the command word and creates an instance of `EditTripCommandParser` to parse the user input.
-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.
+2. `EditTripCommandParser` extracts the index and provided fields from the command arguments and ensures:
+ - The **Index** is a valid positive integer.
+ - At least one field is provided for editing.
-
+3. If the input is invalid, `EditTripCommandParser` throws a `ParseException`. Otherwise, it creates a new instance of `EditTripCommand` based on the user input.
-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.
+4. Upon execution, `EditTripCommand`:
+ - Checks if the index is within the bounds of the filtered trip list
+ - If the index is invalid, throws a `CommandException`
+ - Retrieves the trip to be modified from the list
+ - Creates a new `Trip` instance with the updated details
+ - Checks for duplicate trip entries in the system and throws a `CommandException` if a duplicate is found
+ - Updates the model with the modified trip information
+ - Returns a `CommandResult` confirming the successful edit
-
+#### Listing Trips
-The following activity diagram summarizes what happens when a user executes a new command:
+The list trip feature allows users to view all trips or filter trips by date.
-
+
-#### Design considerations:
+1. `AddressBookParser` identifies that the command type is `listTrip` based on the command word and creates an instance of `ListTripCommandParser` to parse the user input.
-**Aspect: How undo & redo executes:**
+2. `ListTripCommandParser` checks if additional arguments are provided:
+ - If no arguments are provided, it creates a `ListTripCommand` that will show all trips
+ - If a date argument is provided, it parses the date and creates a `ListTripCommand` with the filter date
-* **Alternative 1 (current choice):** Saves the entire address book.
- * Pros: Easy to implement.
- * Cons: May have performance issues in terms of memory usage.
+3. Upon execution, `ListTripCommand`:
+ - If no filter date is specified, updates the model to show all trips
+ - If a filter date is specified, updates the model to show only trips on that date
+ - Returns a `CommandResult` indicating the trips are listed
-* **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.
+#### Trip Storage System
-_{more aspects and alternatives to be added}_
+Trips are stored in a JSON format similar to contacts. The `TripBook` class maintains a list of trips in an `UniqueTripList`, which ensures that there are no duplicate trips. The `Storage` component handles saving and loading of trips from disk.
-### \[Proposed\] Data archiving
-
-_{Explain here how the data archiving feature will be implemented}_
+Each `Trip` object contains:
+- A mandatory `TripName` that uniquely identifies the trip
+- A mandatory `Accommodation` specifying where customers will stay
+- A mandatory `Itinerary` describing the planned activities
+- A mandatory `TripDate` in the format d/M/YYYY
+- An optional set of customer names associated with the trip
+- An optional `Note` containing additional information about the trip
+All trip data is automatically saved when changes are made and loaded when the application starts.
--------------------------------------------------------------------------------------------------------------------
@@ -260,84 +295,366 @@ _{Explain here how the data archiving feature will be implemented}_
### Product scope
-**Target user profile**:
+**Target User Profile:**
+
+* Travel agents who struggle with **problem 1: managing a fragmented workflow involving multiple clients and service providers across various platforms.**
+* Travel agents who face **problem 2: difficulty in rapidly accessing and organizing detailed client preferences and trip specifics, especially during time-sensitive client calls.**
+
+**Value Proposition:**
-* 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
+TravelHub addresses these challenges by providing:
-**Value proposition**: manage contacts faster than a typical mouse/GUI driven app
+* **Solution to problem 1:** A centralized application that streamlines the travel planning workflow, consolidating client and service provider management, trip organization, and note-keeping into a single, efficient system.
+* **Solution to problem 2:** Tools for rapid client information retrieval, detailed preference tracking through a flexible notes system, and chronological trip management, enabling quick and informed responses during client consultations.
+**Scope boundaries**:
+* Focuses on contact and trip management, not financial transactions or booking confirmations
+* Designed for individual travel agents or small agencies (not enterprise-scale operations)
+* Optimized for managing up to several hundred contacts and trips
+* Not intended for end-client usage or self-service booking
+* Will not generate travel documents or automatically communicate with service providers
### User stories
Priorities: High (must have) - `* * *`, Medium (nice to have) - `* *`, Low (unlikely to have) - `*`
-| Priority | As a … | I want to … | So that I can… |
-| -------- | ------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------- |
-| `* * *` | new user | see usage instructions | refer to instructions when I forget how to use the App |
-| `* * *` | user | add a new person | |
-| `* * *` | user | delete a person | remove entries that I no longer need |
-| `* * *` | user | find a person by name | locate details of persons without having to go through the entire list |
-| `* *` | user | hide private contact details | minimize chance of someone else seeing them by accident |
-| `*` | user with many persons in the address book | sort persons by name | locate a person easily |
-
-*{More to be added}*
+| Priority | As a … | I want to … | So that I can… |
+|----------|-------------------------------------------|---------------------------------------------------------------------------------|-----------------------------------------------------------------------------|
+| `* * *` | travel agent | add contacts along with comprehensive details | profile and contact them |
+| `* * *` | travel agent | delete contacts | remove invalid or non-existent contacts |
+| `* * *` | travel agent | add trips with information like dates, customers, accommodation and itineraries | check all the consolidated information for a trip |
+| `* * *` | travel agent | delete trips | remove outdated or irrelevant information |
+| `* * *` | travel agent | tag contacts | know whether they are a customer or service |
+| `* * *` | travel agent | add notes to customer profiles or trips | keep track of special requests or important details |
+| `* *` | travel agent | update contact information | keep up-to-date information when their details change |
+| `* *` | travel agent | update trip information | keep up-to-date information when trip details change |
+| `* *` | travel agent | search for specific contacts | quickly locate the information I need |
+| `* *` | travel agent | change the date of the trip | accommodate flexibility in plans |
+| `* *` | travel agent | refer to all possible commands | refer to instructions when I forget how to use the app |
+| `* *` | travel agent | view the list of trips for a specific date | get information in a accesible and organised manner |
+| `*` | potential travel agent | see the app populated with sample customer profiles and trips | understand how the data is organized and what I can achieve with the system |
+| `*` | new travel agent | clear the sample data with a single command | start with a clean slate for my customer data and trip records |
+| `*` | long-time travel agent | remove completed trips and inactive customer profiles | keep my workspace uncluttered and focused on current travel plans |
+| `*` | clumsy travel agent | change my previous actions | correct my mistakes |
+| `*` | travel agent | view my customers by their tags | easily compare contacts of the same tag with one another |
### Use cases
+(For all use cases below, the **System** is the `TravelHub` and the **Actor** is the `Travel Agent`, unless specified otherwise)
+
+**Use case: See Usage Instructions**
+
+**MSS**
+
+1. Travel Agent requests to see usage instructions.
+2. System displays a list of available commands and their formats.
+3. Travel Agent reads the instructions.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The system fails to load the instructions.
+ * 2a1. System displays an error message: "Unable to load usage instructions. Please try again later."
+ * 2a2. Use case ends.
+* 1a. Travel Agent includes additional text after the help command (e.g., "help xyz").
+ * 1a1. System ignores the additional text and processes the command as "help".
+ * 1a2. Use case continues at step 2.
+
+**Use case: Add a Contact**
+
+**MSS**
+
+1. Travel Agent requests to add a new contact with details (name, phone number, email, address, tag and notes).
+2. System validates the contact details.
+3. System adds the contact to the contact list.
+4. System displays a success message for adding the contact.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The contact details are entered in an invalid format
+ * 2a1. System displays an error message stating the add contact format is invalid.
+ * 2a2. Use case resumes at step 1.
+* 2b. The contact already exists in the system (same email).
+ * 2b1. System displays an error message that the email is already in use.
+ * 2b2. Use case ends.
+* 2c. The email entered is invalid (i.e. email does not follow the required standard).
+ * 2c1. System displays an error message on the right email format.
+ * 2c2. Use case resumes at step 1.
+
+**Use case: Add a Trip**
+
+**MSS**
+
+1. Travel Agent requests to add a new trip with details (trip name, accommodation, itinerary, date, customer names and notes).
+2. System validates the trip details.
+3. System adds the trip to the trip list.
+4. System displays a success message for adding the trip.
+
+ Use case ends.
+
+**Extensions**
+
+* 2a. The trip details are invalid (e.g., invalid date format or customer index).
+ * 2a1. System displays an error message that the add trip format is invalid.
+ * 2a2. Use case resumes at step 1.
+* 2b. The trip already exists in the system (same trip name).
+ * 2b1. System displays an error message.
+ * 2b2. Use case ends.
+
+**Use case: Delete a Contact**
+
+**MSS**
+
+1. Travel Agent requests to delete a specific contact by index.
+2. System deletes the contact.
+3. System displays a success message for deleting the contact.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given index is invalid.
+ * 1a1. System displays an error message that the index is invalid.
+ * 1a2. Use case resumes at step 2.
+
+**Use case: Delete a Trip**
+
+**MSS**
+
+1. Travel Agent requests to delete a specific trip by index.
+2. System deletes the trip.
+3. System displays a success message for deleting the trip.
+
+ Use case ends.
+
+**Extensions**
+
+* 1a. The given index is invalid.
+ * 1a1. System displays an error message that the index is invalid.
+ * 1a2. Use case resumes at step 2.
+
+**Use case: Edit a Contact**
+
+**MSS**
+
+1. Travel Agent requests to list all contacts.
+2. TravelHub shows a list of contacts.
+3. Travel Agent requests to edit a specific contact via an index.
+4. System validates the index and new contact details.
+5. System updates the contact with the new details.
+6. System displays a success message for editing the contact.
+
+ Use case ends.
-(For all use cases below, the **System** is the `AddressBook` and the **Actor** is the `user`, unless specified otherwise)
+**Extensions**
-**Use case: Delete a person**
+* 2a. The contact list is empty.
+ * 2a1. System displays a message that there are no contacts in the addressbook.
+ * Use case ends.
+* 3a. The index provided is invalid.
+ * 3a1. System displays an error message that the index provided is invalid.
+ * Use case resumes at step 2.
+* 3b. Input argument(s) provided are invalid.
+ * 3b1. System displays an error messasage for the invalid argument(s).
+ * Use case resumes at step 2.
+* 3c. The contact is a duplicate (same email).
+ * 3c1. System displays an error message that the email already exists in the addressbook.
+ * Use case resumes at step 2.
+
+**Use case: Edit a Trip**
**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. Travel Agent requests to list all trips.
+2. TravelHub shows a list of trips.
+3. Travel Agent requests to edit a specific trip via an index.
+4. System validates the index and new trip details.
+5. System updates the trip with the new details.
+6. System displays a success message for editing the trip.
Use case ends.
**Extensions**
-* 2a. The list is empty.
+* 2a. The trip list is empty.
+ * 2a1. System displays a message that there are no trips found.
+ * Use case ends.
+* 3a. The index provided is invalid.
+ * 3a1. System displays an error message that the index provided is invalid.
+ * Use case resumes at step 2.
+* 3b. Input argument(s) provided are invalid.
+ * 3b1. System displays an error messasage for the invalid argument(s).
+ * Use case resumes at step 2.
+* 3c. The trip is a duplicate (same name).
+ * 3c1. System displays an error message that the trip name already exists in the tripbook.
+ * Use case resumes at step 2.
+
+**Use case: Find a Contact**
+
+**MSS**
+
+1. Travel Agent find for contacts using a keyword.
+2. System searches for matching contacts and trips.
+3. System displays a list of matching results.
+
+ Use case ends.
+
+**Use case: Clear All Data**
- Use case ends.
+**MSS**
-* 3a. The given index is invalid.
+1. Travel Agent enters the clear command to remove all contacts and trips.
+2. System displays a confirmation pop-up asking if the user really wants to clear all data.
+3. Travel Agent confirms by clicking "Yes".
+4. System clears all contact and trip data from the application.
+5. System displays a success message: "Address book and trip book have been cleared!"
- * 3a1. AddressBook shows an error message.
+ Use case ends.
- Use case resumes at step 2.
+**Extensions**
-*{More to be added}*
+* 3a. Travel Agent cancels the operation by clicking "No".
+ * 3a1. System does not clear any data.
+ * 3a2. System returns to the previous state.
+ * 3a3. Use case ends.
+* 1a. Travel Agent includes additional text after the clear command (e.g., "clear xyz").
+ * 1a1. System ignores the additional text and processes the command as "clear".
+ * 1a2. Use case continues at step 2.
### Non-Functional Requirements
-1. Should work on any _mainstream OS_ as long as it has Java `17` or above installed.
-2. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage.
-3. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.
+1. Compatability: Should work on any _Mainstream OS_ as long as it has Java `17` or above installed.
+2. Performance: Should be able to hold up to 1000 contacts and trips without a noticeable sluggishness in performance for typical usage.
+3. Performance: Should respond within three seconds for any command executed.
+4. Usability: A typists with at least 70 WPM for regular text can enter the commands faster than by using a mouse.
+5. Usability: A user with basic understanding of the english language can utilise this application.
+6. Reliability: The application should automatically save all contact and trip data after each operation to prevent data loss.
+7. Data Security: All travel agent and customer data must remain on the local machine and not be transmitted to external services.
+8. Maintainability: All code must adhere to the coding standard to ensure readability and facilitate future scalability.
+
-*{More to be added}*
### Glossary
-* **Mainstream OS**: Windows, Linux, Unix, MacOS
-* **Private contact detail**: A contact detail that is not meant to be shared with others
+This section defines key terms used in the user guide to ensure clarity and understanding.
+
+| **Term** | **Definition** |
+|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|
+| **CLI** | Command Line Interface. A text-based interface where users interact with the application by typing commands. |
+| **GUI** | Graphical User Interface. A visual interface where users interact with the application through graphical elements like buttons and menus. |
+| **Mainstream OS** | Operating systems that are widely used, such as Windows, Linux, Unix, and macOS. |
+| **JSON** | JavaScript Object Notation. A lightweight data format used for storing and transferring data in a human-readable format. |
+| **API** | Application Programming Interface. A set of rules and protocols that allow different software components to communicate with each other. |
+| **OOP** | Object-Oriented Programming. A programming paradigm based on the concept of "objects," which can contain data and code to manipulate that data.|
+| **Model** | A component in the application that manages the data and business logic. |
+| **UI** | User Interface. The part of the application that users interact with, including screens, buttons, and other visual elements. |
+| **Storage** | A component in the application responsible for saving and retrieving data, such as contact information and user preferences. |
+| **Command** | An instruction given by the user to the application to perform a specific action, such as adding or deleting a contact. |
+| **Parser** | A component that interprets user input and converts it into commands that the application can execute. |
+| **ObservableList** | A list that allows external components to observe changes to its contents, typically used in the UI to automatically update when data changes. |
+| **UserPref** | User Preferences. Settings or configurations that the user can customize, such as the application's appearance or behavior. |
+| **Customer Contact** | A contact tagged as "customer," representing an individual who is a client of the travel agency. |
+| **Service Contact** | A contact tagged as "service," representing a business or service provider (e.g., hotels, resorts, restaurants and attractions). |
+| **Trip** | A planned journey or vacation, including details such as trip name, start date, customers, accommodation, itineraries and notes |
+| **Note** | Additional information or details added to a customer profile or trip, such as special requests or important reminders. |
--------------------------------------------------------------------------------------------------------------------
+## Planned Enhancement
+
+### Trip and Contact Relationship Implementation
+
+Currently, there is no relationship between Trip and Contact entities. In future iterations, we plan to implement a proper relationship between these entities to enhance data integrity and enable more powerful features.
+
+**Proposed Implementation:**
+- Add a relationship between Trip and Contact entities where trips reference actual Contact objects instead of just customer names
+- Implement validation to ensure customer references in trips point to existing contacts
+- Add UI elements to easily select contacts when creating or editing trips
+- Create a bidirectional relationship allowing users to see all trips associated with a contact
+
+**Benefits:**
+- Improves data integrity by ensuring trips only reference valid contacts
+- Enables powerful queries such as "show all trips for a specific contact"
+- Provides better tracking of customer trip history
+- Facilitates trip planning by leveraging existing contact information
+
+**Implementation Challenges:**
+- Need to handle migration of existing trips that only store customer names
+- Must address UI complexity for selecting multiple contacts from a list
+- Should consider backward compatibility for older data formats
+- Need to handle deletion scenarios (e.g., what happens to trips when a referenced contact is deleted)
+
+### Escape Character for Notes
+
+Currently, if parameter prefixes (e.g., `n/`, `p/`, `e/`, `a/`, `t/`, `acc/`, `i/`, `d/`, `c/`) appear within note content, they are treated as separate parameters rather than as part of the note. To allow users to include these prefix patterns in their notes, we plan to implement an escape character mechanism.
+
+**Proposed Implementation:**
+- Introduce a special escape character (e.g., backslash `\`) that users can place before parameter prefixes in notes
+- For example, `nts/Remember to call \p/12345678` would include "p/12345678" as part of the note text
+- The parser will detect the escape character and interpret the following prefix as literal text rather than a parameter marker
+
+**Benefits:**
+- Users can include parameter-like text in their notes without causing unexpected parsing behavior
+- Improves flexibility in note content without compromising the existing command structure
+- Maintains backward compatibility with existing commands
+
+**Implementation Challenges:**
+- Need to modify the parser to recognize and handle the escape character
+- Must ensure proper handling of edge cases, such as multiple consecutive escape characters
+
+### Add End Date for Trips
+
+Currently, trips only have a start date. To better support trip planning and tracking, we plan to enhance the Trip model to include an end date.
+
+**Proposed Implementation:**
+- Add a new `TripEndDate` class similar to the existing `TripDate` class
+- Extend the Trip model to include an end date field
+- Update relevant parsers to accept an end date parameter (e.g., `ed/5/6/2024`)
+- Modify the trip display to show both start and end dates
+- Update storage to persist the end date information
+
+**Benefits:**
+- Allows users to track the full duration of trips
+- Enables future enhancements such as trip duration calculations and trip overlap detection
+- Provides more complete trip information at a glance
+
+**Implementation Challenges:**
+- Need to ensure the end date is not earlier than the start date
+- Must update existing trip displays and storage format while maintaining backward compatibility
+- Should modify trip filtering to consider both start and end dates
+
+### Make Commands Case-Insensitive
+
+Currently, all commands must be entered in exact camelCase format (e.g., addTrip, editContact, help), which can be error-prone for users. Typing ADDTRIP or Addtrip or addtrip, which hurts usability—especially for new users or CLI users accustomed to case-insensitive input.
+
+**Proposed Implementation:**
+- In the main AddressBook Parser, normalise only the command word to lower case before matching
+- Update all defined command keywords to lowercase for matching
+- Convert all `COMMAND_WORD` constants in command classes to lowercase (e.g., `COMMAND_WORD = "addtrip";`)
+- Keep displaying commands in camelCase (e.g., in help messages) for readability
+- Update unit tests for various case combinations (i.e. `addtrip`, `AddTrip`, `ADDTRIP`, `AdDtRiP` — all should work)
+
+**Benefits:**
+- Allows better usability as users can type commands without worrying about case.
+- Reduces frustration from subtle casing issues.
+- Makes the CLI interface more forgiving and beginner-friendly.
+
+**Implementation Challenges:**
+- Must ensure only the command word is normalized—arguments (e.g., names, places, tags) must remain case-sensitive to preserve user data fidelity.
+- All command recognition logic must adapt to use lowercased comparisons without affecting internal casing conventions elsewhere.
+
## **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
@@ -354,29 +671,29 @@ testers are expected to do more *exploratory* testing.
1. Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-1. _{ more test cases … }_
-
-### Deleting a person
+### Deleting a contact
-1. Deleting a person while all persons are being shown
+1. Deleting a contact while all contacts are being shown
- 1. Prerequisites: List all persons using the `list` command. Multiple persons in the list.
+ 1. Prerequisites: List all contacts using the `listContact` command. Multiple contacts 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: `deleteContact 1`
+ Expected: First contact is deleted from the list. Command Output Box displays the details of the deleted contact.
- 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: `deleteContact 0`
+ Expected: No contact is deleted. Error message shown: The contact index provided is invalid.
- 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: `deleteContact`, `deleteContact x`, `...` (where x is larger than the list size)
Expected: Similar to previous.
-1. _{ more test cases … }_
-
### Saving data
-1. Dealing with missing/corrupted data files
+1. Simulating a corrupted data file
- 1. _{explain how to simulate a missing/corrupted file, and the expected behavior}_
+ 1. Prerequisites: Have at least 1 contact in the addressbook.
+ 1. Navigate to the folder which contains the `TravelHub.jar` file.
+ 1. Navigate into the `data/` folder and open the `addressbook.json` file.
+ 1. Append any letter to the end of the value in the phone field, e.g., `"phone" : "98765432Z",`
+ 1. Start up the appplication.
-1. _{ more test cases … }_
+ Expected: Application starts up with an empty address book.
diff --git a/docs/Documentation.md b/docs/Documentation.md
index 3e68ea364e7..082e652d947 100644
--- a/docs/Documentation.md
+++ b/docs/Documentation.md
@@ -1,29 +1,21 @@
---
-layout: page
-title: Documentation guide
+ layout: default.md
+ title: "Documentation guide"
+ pageNav: 3
---
-**Setting up and maintaining the project website:**
-
-* We use [**Jekyll**](https://jekyllrb.com/) to manage documentation.
-* The `docs/` folder is used for documentation.
-* To learn how set it up and maintain the project website, follow the guide [_[se-edu/guides] **Using Jekyll for project documentation**_](https://se-education.org/guides/tutorials/jekyll.html).
-* Note these points when adapting the documentation to a different project/product:
- * The 'Site-wide settings' section of the page linked above has information on how to update site-wide elements such as the top navigation bar.
- * :bulb: In addition to updating content files, you might have to update the config files `docs\_config.yml` and `docs\_sass\minima\_base.scss` (which contains a reference to `AB-3` that comes into play when converting documentation pages to PDF format).
-* If you are using Intellij for editing documentation files, you can consider enabling 'soft wrapping' for `*.md` files, as explained in [_[se-edu/guides] **Intellij IDEA: Useful settings**_](https://se-education.org/guides/tutorials/intellijUsefulSettings.html#enabling-soft-wrapping)
+# Documentation Guide
+* We use [**MarkBind**](https://markbind.org/) to manage documentation.
+* The `docs/` folder contains the source files for the documentation website.
+* To learn how set it up and maintain the project website, follow the guide [[se-edu/guides] Working with Forked MarkBind sites](https://se-education.org/guides/tutorials/markbind-forked-sites.html) for project documentation.
**Style guidance:**
* Follow the [**_Google developer documentation style guide_**](https://developers.google.com/style).
+* Also relevant is the [_se-edu/guides **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html).
-* Also relevant is the [_[se-edu/guides] **Markdown coding standard**_](https://se-education.org/guides/conventions/markdown.html)
-
-**Diagrams:**
-
-* See the [_[se-edu/guides] **Using PlantUML**_](https://se-education.org/guides/tutorials/plantUml.html)
-**Converting a document to the PDF format:**
+**Converting to PDF**
-* See the guide [_[se-edu/guides] **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html)
+* See the guide [_se-edu/guides **Saving web documents as PDF files**_](https://se-education.org/guides/tutorials/savingPdf.html).
diff --git a/docs/Gemfile b/docs/Gemfile
deleted file mode 100644
index c8385d85874..00000000000
--- a/docs/Gemfile
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-source "https://rubygems.org"
-
-git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
-
-gem 'jekyll'
-gem 'github-pages', group: :jekyll_plugins
-gem 'wdm', '~> 0.1.0' if Gem.win_platform?
-gem 'webrick'
diff --git a/docs/Logging.md b/docs/Logging.md
index 5e4fb9bc217..589644ad5c6 100644
--- a/docs/Logging.md
+++ b/docs/Logging.md
@@ -1,8 +1,10 @@
---
-layout: page
-title: Logging guide
+ layout: default.md
+ title: "Logging guide"
---
+# Logging guide
+
* We are using `java.util.logging` package for logging.
* The `LogsCenter` class is used to manage the logging levels and logging destinations.
* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level.
diff --git a/docs/SettingUp.md b/docs/SettingUp.md
index aef33ec72fd..5e8b9d11991 100644
--- a/docs/SettingUp.md
+++ b/docs/SettingUp.md
@@ -1,27 +1,33 @@
---
-layout: page
-title: Setting up and getting started
+ layout: default.md
+ title: "Setting up and getting started"
+ pageNav: 3
---
-* Table of Contents
-{:toc}
+# Setting up and getting started
+
+
--------------------------------------------------------------------------------------------------------------------
## Setting up the project in your computer
-:exclamation: **Caution:**
+
+**Caution:**
Follow the steps in the following guide precisely. Things will not work out if you deviate in some steps.
-
+
First, **fork** this repo, and **clone** the fork into your computer.
If you plan to use Intellij IDEA (highly recommended):
+
1. **Configure the JDK**: Follow the guide [_[se-edu/guides] IDEA: Configuring the JDK_](https://se-education.org/guides/tutorials/intellijJdk.html) to ensure Intellij is configured to use **JDK 17**.
-1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
- :exclamation: Note: Importing a Gradle project is slightly different from importing a normal Java project.
+1. **Import the project as a Gradle project**: Follow the guide [_[se-edu/guides] IDEA: Importing a Gradle project_](https://se-education.org/guides/tutorials/intellijImportGradleProject.html) to import the project into IDEA.
+
+ Note: Importing a Gradle project is slightly different from importing a normal Java project.
+
1. **Verify the setup**:
1. Run the `seedu.address.Main` and try a few commands.
1. [Run the tests](Testing.md) to ensure they all pass.
@@ -34,10 +40,11 @@ If you plan to use Intellij IDEA (highly recommended):
If using IDEA, follow the guide [_[se-edu/guides] IDEA: Configuring the code style_](https://se-education.org/guides/tutorials/intellijCodeStyle.html) to set up IDEA's coding style to match ours.
- :bulb: **Tip:**
+
+ **Tip:**
Optionally, you can follow the guide [_[se-edu/guides] Using Checkstyle_](https://se-education.org/guides/tutorials/checkstyle.html) to find how to use the CheckStyle within IDEA e.g., to report problems _as_ you write code.
-
+
1. **Set up CI**
diff --git a/docs/Testing.md b/docs/Testing.md
index 8a99e82438a..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 27c2d1cf16c..06e0bdac8f7 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -1,125 +1,272 @@
---
-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.
+# TravelHub User Guide
-* Table of Contents
-{:toc}
+TravelHub is a contact management app designed to help travel agents efficiently manage customer information and service details, such as addresses and contact information. Using a simple command-line interface, it supports **adding, deleting, tagging of contact profiles and trips**.
+
+
+
--------------------------------------------------------------------------------------------------------------------
## Quick start
-1. Ensure you have Java `17` or above installed in your Computer.
+1. Ensure you have Java `17` or above installed in your Computer.
+ **Windows users:** Download and install JDK 17 from [here](https://www.oracle.com/java/technologies/downloads/#java17)
**Mac users:** Ensure you have the precise JDK version prescribed [here](https://se-education.org/guides/tutorials/javaInstallationMac.html).
+ * Open a command terminal (Windows: search for _cmd_ , Mac: use Spotlight to find _Terminal_ )
+ * Verify installation by typing `java -version` in your terminal
+
+
+2. Download the `TravelHub.jar` file from the latest release [here](https://github.com/AY2425S2-CS2103-F09-1/tp/releases).
+ * The `.jar` file will be listed under the Assets dropdown of the latest version - click on it to begin your download
+
-1. Download the latest `.jar` file from [here](https://github.com/se-edu/addressbook-level3/releases).
+3. Copy the `TravelHub.jar` file to the folder you want to use as the _home folder_ for TravelHub. Other additional folders (e.g. data folder) will be created within this _home folder_.
-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.
+4. Open a command terminal, `cd` into the folder you put the jar file in, type the `java -jar travelhub.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.

-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.
+1. Type the command in the command box and press Enter to execute it. e.g. typing **`help`** and pressing Enter will list all the available commands.
Some example commands you can try:
- * `list` : Lists all contacts.
+ * `listContact` : Lists all contacts.
- * `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : Adds a contact named `John Doe` to the Address Book.
+ * `addContact 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.
+ * `deleteContact 3` : Deletes the 3rd contact shown in the current list.
- * `clear` : Deletes all contacts.
+ * `clear` : Deletes all contacts and trips.
* `exit` : Exits the app.
-1. Refer to the [Features](#features) below for details of each command.
+1. Refer to the Features below for details of each command.
--------------------------------------------------------------------------------------------------------------------
+## Graphical User Interface Layout
+
+
+
+* **Command Box:** For users to enter commands.
+* **Command Output Box:** Display the output/error message of the executed command.
+* **Contact List Panel:** This panel displays the list of contacts.
+* **Contact List Card:** Details of a contact displayed in a card.
+* **Trip List Panel:** This panel displays the list of trips.
+* **Trip List Card:** Details of a trip displayed in a card.
## Features
-
+
+
+**IMPORTANT: All commands are case-sensitive**
-**:information_source: Notes about the command format:**
+**Notes about the command format:**
* Words in `UPPER_CASE` are the parameters to be supplied by the user.
- e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`.
+ e.g. in `addContact n/NAME`, `NAME` is a parameter which can be used as `addContact n/John Doe`.
+
+* The parameter `nts/NOTE` is optional for Trip and Contact.
+ However, if added, it **must** be the final input parameter.
* Items in square brackets are optional.
- e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`.
+ e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/customer` 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.
+ e.g. `[t/TAG]…` can be used as ` ` (i.e. 0 times), `t/customer`, `t/customer t/service` etc.
* Parameters can be in any order.
e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable.
-* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit` and `clear`) will be ignored.
+* Extraneous parameters for commands that do not take in parameters (such as `help`, `exit` and `clear`) will be ignored.
e.g. if the command specifies `help 123`, it will be interpreted as `help`.
* If you are using a PDF version of this document, be careful when copying and pasting commands that span multiple lines as space characters surrounding line-breaks may be omitted when copied over to the application.
-
+
-### Viewing help : `help`
+### Contact Parameters
+A contact consists the following parameters: name, phone, email, address, tags and notes.
+The parameters follow immediately after their corresponding prefixes and are useful for the `addContact` and `editContact` commands.
-Shows a message explaning how to access the help page.
+| Parameter | Prefix | Description |
+|-----------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `NAME` | `n/` | Specifies the name of the contact. **Requirements:**Name is a mandatory parameter and cannot be empty. Names can only contain alphanumeric characters, spaces, hyphens (-), apostrophes ('), and dots (.). |
+| `PHONE` | `p/` | Specifies the phone number of the contact. **Requirements:** Phone number is a mandatory parameter and cannot be empty. Phone numbers should only contain numbers. The `+` sign for country code should be omitted. Phone numbers should be 3-17 digits (inclusive) long. |
+| `EMAIL` | `e/` | Specifies the email of the contact **Requirements:** **Contacts are uniquely identified by their email.** Email is a mandatory parameter and cannot be empty. Emails should be of the format `local-part@domain`. Local-part should contain only alphanumeric characters and these special characters, excluding the parentheses, (+_.-). The local-part may not start or end with any special characters. Special characters can only appear between alphanumeric characters and cannot be placed next to each other. The domain should contain only alphanumeric characters, hyphens (-) and dots (.). The domain must be at least 2 characters long. The domain must start and end with alphanumeric characters. |
+| `ADDRESS` | `a/` | Specifies the address of the contact. **Requirements:**Address is a mandatory parameter and cannot be empty. |
+| `TAG` | `t/` | Specifies the tag of a customer. **Requirements:** Tag is an optional parameter and can be omitted when adding a contact. Tags can only be specified as `t/customer` or `t/service`. You may include both by specifying `t/customer t/service`. |
+| `NOTE` | `nts/` | Specifies additional notes related to the contact. **Requirements:** Note is an optional parameter and can be omitted when adding a contact. Note content can be empty (e.g., `nts/` is valid). **Important:** If your note contains any parameter prefixes (e.g., n/, p/, e/, a/, t/), they will be treated as separate parameters rather than part of the note. For example, in `addContact n/John p/12345 e/john@example.com a/123 Main St nts/Contact prefers p/morning calls`, the text "p/morning calls" would not be part of the note - "p/morning" would be treated as a separate phone parameter. |
-
+### Trip Parameters
+A trip consists the following parameters: trip name, accommodation, itineraries date, customers, and notes.
+The parameters follow immediately after their corresponding prefixes and are useful for the `addTrip` and `editTrip` commands.
-Format: `help`
+| Parameter | Prefix | Description |
+|-----------------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `NAME` | `n/` | Specifies the name of the trip. **Requirements:****Trips are uniquely identified by their trip name.** Name is a mandatory parameter and cannot be empty. Trip names can only contain alphanumeric characters and spaces. |
+| `ACCOMMODATION` | `acc/` | Specifies the accomodation for the trip. **Requirements:**Accomodation is a mandatory parameter and cannot be empty. |
+| `ITINERARY` | `i/` | Specifies the itinerary for the trip. **Requirements:**Itinerary is a mandatory parameter and cannot be empty. |
+| `DATE` | `d/` | Specifies the date of the trip. **Requirements:**Date is a mandatory parameter and cannot be empty. Date must follow the format `d/M/yyyy`. A valid date allowed is between year 1950 and 2100 inclusive. |
+| `CUSTOMER_NAME` | `c/` | Specifies the name of the customers participating in the trip. **Requirements:** Customer name is an optional parameter and can be omitted when adding a trip. Customer name follows the requirements of the `NAME` parameter in contact. You can specify multiple customer names by repeating the c/ prefix separated by a space, e.g., `c/John Doe c/Jane Doe c/Joe Doe`. |
+| `NOTE` | `nts/` | Specifies additional notes related to the trip. **Requirements:** Note is an optional parameter and can be omitted when adding a trip. Note content can be empty (e.g., `nts/` is valid). **Important:** If your note contains any parameter prefixes (e.g., n/, acc/, i/, d/, c/), they will be treated as separate parameters rather than part of the note. For example, in `addTrip n/Europe Trip acc/Grand Hotel i/Sightseeing d/1/6/2024 nts/Remember to book n/train tickets`, the text "n/train tickets" would not be part of the note - "n/train tickets" would be treated as a separate name parameter. |
+
+## Command Descriptions
+All commands are **case-sensitive** and listed in **alphabetical order**. Please follow the specified format for each command carefully
+### Adding a contact: `addContact`
-### Adding a person: `add`
+Adds a contact to the address book.
-Adds a person to the address book.
+Format: `addContact n/NAME p/PHONE e/EMAIL a/ADDRESS [t/TAG]… [nts/NOTE]`
-Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…`
+* Email must be unique across all contacts.
+* Tags must be 'customer' or 'service' e.g., 't/customer t/service'.
+* A contact can have no tags, 1 tag or both the customer and service tag.
+* You can add optional notes about the contact using the nts/ prefix.
+* Note that if your note contains any parameter prefixes (n/, p/, e/, a/, t/), they will be treated as separate parameters and not as part of the note text.
-:bulb: **Tip:**
-A person can have any number of tags (including 0)
-
Examples:
-* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
-* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal`
+* `addContact n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01`
+* `addContact n/XYZ Restaurant e/xyz_cuisine@example.com a/XYZ Street p/67654321 t/service nts/Serves western cuisine`
+* `addContact n/Betty's Gift Shop e/betty_biz@example.com a/Sunshine Street 3 p/67654321 t/service t/customer`
+ 
-### Listing all persons : `list`
+### Adding a trip : `addTrip`
-Shows a list of all persons in the address book.
+Adds a trip to the trip book.
-Format: `list`
+Format: `addTrip n/NAME acc/ACCOMMODATION i/ITINERARY d/DATE [c/CUSTOMER_NAME]... [nts/NOTE]`
+
+* Adds a trip with the specified details.
+* Trip name must be unique across all trips.
+* The date should be in the format of D/M/YYYY, signifying the trip's start date.
+* A valid date ranges from 1950 to 2100, as past trips can also be logged.
+* Customer names are optional. You can specify multiple customer names by using the c/ prefix multiple times.
+* You can add optional notes about the trip using the nts/ prefix.
+* Note that if your note contains any parameter prefixes (n/, acc/, i/, d/, c/), they will be treated as separate parameters and not as part of the note text.
+
+Examples:
+* `addTrip n/Paris 2025 acc/Hotel Sunshine i/Visit Eiffel Tower; Eat baguette d/01/1/2025 c/Jane Doe c/John Doe nts/Remember to book tickets`
+* `addTrip n/Beach Vacation acc/Beach Resort i/Relax by the beach; Snorkeling d/15/3/2024 c/Alice Smith nts/All-inclusive package`
+* `addTrip n/Business Conference acc/City Hotel i/Attend presentations; Networking d/10/5/2024 nts/Corporate rate applies`
+ 
+
+### Clearing all entries : `clear`
+
+Clears all contact and trip entries stored in the application.
+
+Format: `clear`
+
+Upon execution of this command, a confirmation pop-up will appear:
+* Select "OK" to confirm and proceed with clearing all data.
+* Select "Cancel" to abort and retain existing data.
+
+Any additional text after `clear` (e.g., `clear abc`) will be ignored. The command will still be processed as `clear`.
+
+
+
+### Deleting a contact : `deleteContact`
+
+Deletes the specified contact from the address book.
+
+Format: `deleteContact INDEX`
+
+* Deletes the contact at the specified `INDEX`.
+* The index refers to the number shown in the currently displayed contact list.
+* The index **must be a positive integer** 1, 2, 3, …
+* The index **must be within the range* of the displayed contact list,
+
+Examples:
+* `listContact` followed by `deleteContact 2` deletes the 2nd contact in the address book.
+* `find Betsy` followed by `deleteContact 1` deletes the 1st contact in the results of the `find` command.
+ 
+
+### Deleting a trip : `deleteTrip`
+
+Deletes the specified trip from the trip book.
+
+Format: `deleteTrip INDEX`
+
+* Deletes the trip at the specified `INDEX`.
+* The index refers to the number shown in the currently displayed trip list.
+* The index **must be a positive integer** 1, 2, 3, …
+* The index **must be within the range** of the displayed trip list.
+
+Examples:
+* `listTrip` followed by `deleteTrip 2` deletes the 2nd trip in the trip book.
+ 
-### Editing a person : `edit`
+### Editing a contact : `editContact`
-Edits an existing person in the address book.
+Edits an existing contact in the address book.
-Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]…`
+Format: `editContact INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]… [nts/NOTE]`
-* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index **must be a positive integer** 1, 2, 3, …
+* Edits the contact at the specified `INDEX`.
+* The index refers to the number shown in the currently displayed contact list.
+* The index **must be a positive integer** 1, 2, 3, …
+* The index **must be within the range** of the displayed contact list,
+* Email must be unique across all contacts.
+* 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 contact will be removed i.e. adding of tags is not cumulative.
+* You can remove all the contact's tags by typing `t/` without specifying any tags after it.
+* You can remove all the contact's notes by typing `nts/` without specifying anything after it.
+* Note that if your note contains any parameter prefixes (n/, p/, e/, a/, t/), they will be treated as separate parameters and not as part of the note text.
+
+Examples:
+* `editContact 1 p/91234567 e/johndoe@example.com` Edits the phone number and email address of the 1st contact to be `91234567` and `johndoe@example.com` respectively.
+* `editContact 2 n/Betsy Crower t/` Edits the name of the 2nd contact to be `Betsy Crower` and clears all existing tags.
+ 
+
+### Editing a trip : `editTrip`
+
+Edits an existing trip in the trip book.
+
+Format: `editTrip INDEX [n/NAME] [acc/ACCOMMODATION] [i/ITINERARY] [d/DATE] [c/CUSTOMER_NAME]... [nts/NOTE]`
+
+* Edits the trip at the specified `INDEX`.
+* The index refers to the number shown in the currently displayed trip list.
+* The index **must be a positive integer** 1, 2, 3, …
+* The index **must be within the range** of the displayed trip list.
+* Trip name must be unique across all trips.
* At least one of the optional fields must be provided.
* Existing values will be updated to the input values.
-* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative.
-* You can remove all the person’s tags by typing `t/` without
- specifying any tags after it.
+* When editing customer names, the existing customer names of the trip will be removed and replaced with the new ones.
+* You can remove all customer names by not including any c/ prefixes.
+* You can remove all trip notes by typing `nts/` without specifying anything after it.
+* Note that if your note contains any parameter prefixes (n/, acc/, i/, d/, c/), they will be treated as separate parameters and not as part of the note text.
+* Customer names are optional.
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.
+* `editTrip 1 acc/Grand Hotel i/Visit Louvre; Visit Seine River nts/Changed hotel due to availability` Edits the accommodation, itinerary, and adds a note for the 1st trip.
+* `editTrip 2 n/London Trip 2025 c/Jane Doe c/Bob Smith` Edits the name and changes the customer names for the 2nd trip.
+ 
+
+### Exiting the program : `exit`
+
+Exits the program.
+
+Format: `exit`
-### Locating persons by name: `find`
+Any additional text after `exit` (e.g., `exit xyz`) will be ignored. The command will still be processed as `exit`.
-Finds persons whose names contain any of the given keywords.
+### Locating contacts by name: `find`
+
+Finds contacts whose names contain any of the given keywords.
Format: `find KEYWORD [MORE_KEYWORDS]`
* The search is case-insensitive. e.g `hans` will match `Hans`
* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans`
* Only the name is searched.
-* Only full words will be matched e.g. `Han` will not match `Hans`
+* Only exact 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`
@@ -128,73 +275,100 @@ Examples:
* `find alex david` returns `Alex Yeoh`, `David Li`

-### Deleting a person : `delete`
+### Viewing help : `help`
-Deletes the specified person from the address book.
+Shows a list of all available commands.
-Format: `delete INDEX`
+Format: `help`
-* 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, …
+Any additional text after `help` (e.g., `help xyz`) will be ignored. The command will still be processed as `help`.
-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.
+Expected output:
-### Clearing all entries : `clear`
+```
+Available commands:
+- addContact: Adds a new contact
+- addTrip: Adds a new trip with name, accommodation, itinerary, date, optional customer names and optional note
+- clear: Clear all contacts and trips
+- deleteContact: Removes a contact at a specified index
+- deleteTrip: Removes a trip at a specified index
+- editContact: Edits a contact at a specified index
+- editTrip: Edits a trip at a specified index
+- exit: Exits the program
+- find: Find contacts whose names contain any of the given keywords
+- help: Shows program usage instructions
+- listContact: Lists all contacts [can specify tag type]
+- listTrip: Lists all trips [can specify date]
+```
-Clears all entries from the address book.
+
-Format: `clear`
+### Listing contacts : `listContact`
-### Exiting the program : `exit`
+Shows a list of contacts in the address book.
-Exits the program.
+Format: `listContact [customer/service]`
-Format: `exit`
+* Without specifying the optional parameter, all contacts will be displayed.
+* By specifying the `[customer/service]` parameter, only contacts with the tag will be displayed.
+ 
+
+### Listing all trips : `listTrip`
+
+Shows a list of all trips in the trip book.
+
+Format: `listTrip [DATE]`
+
+* Without specifying the optional parameter, all trips will be displayed.
+* By specifying the `[DATE]` parameter, only trips on that specific date will be displayed.
+* The date should be in the format of D/M/YYYY.
+ 
### 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.
+ContactBook and TripBook data are saved in the hard disk automatically after any command that changes the data. There is no need to save manually.
### Editing the data file
-AddressBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Advanced users are welcome to update data directly by editing that data file.
+ContactBook data are saved automatically as a JSON file `[JAR file location]/data/addressbook.json`. Similarly, TripBook data are saved automatically as a JSON file `[JAR file location]/data/tripbook.json`. Advanced users are welcome to update data directly by editing that data file.
-:exclamation: **Caution:**
-If your changes to the data file makes its format invalid, AddressBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
-Furthermore, certain edits can cause the AddressBook to behave in unexpected ways (e.g., if a value entered is outside of the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
-
+
-### Archiving data files `[coming in v2.0]`
-
-_Details coming soon ..._
+**Caution:**
+If your changes to the data file makes its format invalid, ContactBook and TripBook will discard all data and start with an empty data file at the next run. Hence, it is recommended to take a backup of the file before editing it.
+Furthermore, certain edits can cause the ContactBook and TripBook to behave in unexpected ways (e.g., if a value entered is outside the acceptable range). Therefore, edit the data file only if you are confident that you can update it correctly.
+
--------------------------------------------------------------------------------------------------------------------
## FAQ
**Q**: How do I transfer my data to another Computer?
-**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous AddressBook home folder.
+**A**: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous ContactBook and TripBook home folder.
--------------------------------------------------------------------------------------------------------------------
## 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.
-2. **If you minimize the Help Window** and then run the `help` command (or use the `Help` menu, or the keyboard shortcut `F1`) again, the original Help Window will remain minimized, and no new Help Window will appear. The remedy is to manually restore the minimized Help Window.
+2. **If you minimize the Help Window** and then run the `help` command (or use the `User Guide` menu, or the keyboard shortcut `F1`) again, the original Help Window will remain minimized, and no new Help Window will appear. The remedy is to manually restore the minimized Help Window.
--------------------------------------------------------------------------------------------------------------------
## Command summary
-
-Action | Format, Examples
---------|------------------
-**Add** | `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]…` e.g., `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague`
-**Clear** | `clear`
-**Delete** | `delete INDEX` e.g., `delete 3`
-**Edit** | `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]…` e.g.,`edit 2 n/James Lee e/jameslee@example.com`
-**Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake`
-**List** | `list`
-**Help** | `help`
+All commands are **case-sensitive** and are listed in **alphabetical order** below.
+
+Action | Format, Examples
+-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------
+**Add Contact** | `addContact n/NAME p/PHONE e/EMAIL a/ADDRESS [t/TAG]… [nts/NOTE]` e.g., `addContact n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/customer`
+**Add Trip** | `addTrip n/NAME acc/ACCOMMODATION i/ITINERARY d/DATE [c/CUSTOMER_NAME]... [nts/NOTE]` e.g., `addTrip n/Paris 2025 acc/Hotel Sunshine i/Visit Eiffel Tower; Eat baguette d/01/1/2025 c/Jane Doe c/John Doe nts/Customer prefers window seat`
+**Clear** | `clear`
+**Delete Contact** | `deleteContact INDEX` e.g., `deleteContact 3`
+**Delete Trip** | `deleteTrip INDEX` e.g., `deleteTrip 3`
+**Edit Contact** | `editContact INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]… [nts/NOTE]` e.g.,`editContact 2 n/James Lee e/jameslee@example.com` or `editContact 5 p/98765432 t/service nts/Allergic to fish`
+**Edit Trip** | `editTrip INDEX [n/NAME] [acc/ACCOMMODATION] [i/ITINERARY] [d/DATE] [c/CUSTOMER_NAME]... [nts/NOTE]` e.g., `editTrip 1 acc/Grand Hotel i/Visit Louvre; Visit Seine River nts/Changed hotel due to availability` or `editTrip 3 n/Batam Trip d/17/12/2025`
+**Exit** | `exit`
+**Find** | `find KEYWORD [MORE_KEYWORDS]` e.g., `find James Jake` or `find Alex`
+**Help** | `help` |
+**List Contact** | `listContact [customer/service]` e.g., `listContact` or `listContact service` or `listContact customer`
+**List Trip** | `listTrip [DATE]` e.g., `listTrip` or `listTrip 15/12/2023`
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
----
-
-
-
-
-
- {{ 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
----
-
-
-
-
-
- {{ content }}
-
-
-
diff --git a/docs/_markbind/layouts/default.md b/docs/_markbind/layouts/default.md
new file mode 100644
index 00000000000..16ff8ac38d1
--- /dev/null
+++ b/docs/_markbind/layouts/default.md
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+* [Home]({{ baseUrl }}/index.html)
+* [User Guide]({{ baseUrl }}/UserGuide.html) :expanded:
+ * [Quick Start]({{ baseUrl }}/UserGuide.html#quick-start)
+ * [Features]({{ baseUrl }}/UserGuide.html#features)
+ * [FAQ]({{ baseUrl }}/UserGuide.html#faq)
+ * [Command Summary]({{ baseUrl }}/UserGuide.html#faq)
+* [Developer Guide]({{ baseUrl }}/DeveloperGuide.html) :expanded:
+ * [Acknowledgements]({{ baseUrl }}/DeveloperGuide.html#acknowledgements)
+ * [Setting Up]({{ baseUrl }}/DeveloperGuide.html#setting-up-getting-started)
+ * [Design]({{ baseUrl }}/DeveloperGuide.html#design)
+ * [Implementation]({{ baseUrl }}/DeveloperGuide.html#implementation)
+ * [Documentation, logging, testing, configuration, dev-ops]({{ baseUrl }}/DeveloperGuide.html#documentation-logging-testing-configuration-dev-ops)
+ * [Appendix: Requirements]({{ baseUrl }}/DeveloperGuide.html#appendix-requirements)
+ * [Appendix: Instructions for manual testing]({{ baseUrl }}/DeveloperGuide.html#appendix-instructions-for-manual-testing)
+* Tutorials
+ * [Tracing code]({{ baseUrl }}/tutorials/TracingCode.html)
+ * [Adding a command]({{ baseUrl }}/tutorials/AddRemark.html)
+ * [Removing Fields]({{ baseUrl }}/tutorials/RemovingFields.html)
+* [About Us]({{ baseUrl }}/AboutUs.html)
+
+
+
+
+ {{ content }}
+
+
+
+
+
+
+
+
+
+
+
[**Powered by** {{MarkBind}}, generated on {{timestamp}}]
+
+
diff --git a/docs/_markbind/variables.json b/docs/_markbind/variables.json
new file mode 100644
index 00000000000..9d89eb0358b
--- /dev/null
+++ b/docs/_markbind/variables.json
@@ -0,0 +1,3 @@
+{
+ "jsonVariableExample": "Your variables can be defined here as well"
+}
diff --git a/docs/_markbind/variables.md b/docs/_markbind/variables.md
new file mode 100644
index 00000000000..89ae5318fa4
--- /dev/null
+++ b/docs/_markbind/variables.md
@@ -0,0 +1,4 @@
+
+To inject this HTML segment in your markbind files, use {{ example }} where you want to place it.
+More generally, surround the segment's id with double curly braces.
+
diff --git a/docs/_sass/minima/_base.scss b/docs/_sass/minima/_base.scss
deleted file mode 100644
index 0d3f6e80ced..00000000000
--- a/docs/_sass/minima/_base.scss
+++ /dev/null
@@ -1,295 +0,0 @@
-html {
- font-size: $base-font-size;
-}
-
-/**
- * Reset some basic elements
- */
-body, h1, h2, h3, h4, h5, h6,
-p, blockquote, pre, hr,
-dl, dd, ol, ul, figure {
- margin: 0;
- padding: 0;
-
-}
-
-
-
-/**
- * Basic styling
- */
-body {
- font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family;
- color: $text-color;
- background-color: $background-color;
- -webkit-text-size-adjust: 100%;
- -webkit-font-feature-settings: "kern" 1;
- -moz-font-feature-settings: "kern" 1;
- -o-font-feature-settings: "kern" 1;
- font-feature-settings: "kern" 1;
- font-kerning: normal;
- display: flex;
- min-height: 100vh;
- flex-direction: column;
- overflow-wrap: break-word;
-}
-
-
-
-/**
- * Set `margin-bottom` to maintain vertical rhythm
- */
-h1, h2, h3, h4, h5, h6,
-p, blockquote, pre,
-ul, ol, dl, figure,
-%vertical-rhythm {
- margin-bottom: $spacing-unit / 2;
-}
-
-hr {
- margin-top: $spacing-unit;
- margin-bottom: $spacing-unit;
-}
-
-/**
- * `main` element
- */
-main {
- display: block; /* Default value of `display` of `main` element is 'inline' in IE 11. */
-}
-
-
-
-/**
- * Images
- */
-img {
- max-width: 100%;
- vertical-align: middle;
-}
-
-
-
-/**
- * Figures
- */
-figure > img {
- display: block;
-}
-
-figcaption {
- font-size: $small-font-size;
-}
-
-
-
-/**
- * Lists
- */
-ul, ol {
- margin-left: $spacing-unit;
-}
-
-li {
- > ul,
- > ol {
- margin-bottom: 0;
- }
-}
-
-
-
-/**
- * Headings
- */
-h1, h2, h3, h4, h5, h6 {
- font-weight: $base-font-weight;
-}
-
-
-
-/**
- * Links
- */
-a {
- color: $link-base-color;
- text-decoration: none;
-
- &:visited {
- color: $link-visited-color;
- }
-
- &:hover {
- color: $text-color;
- text-decoration: underline;
- }
-
- .social-media-list &:hover {
- text-decoration: none;
-
- .username {
- text-decoration: underline;
- }
- }
-}
-
-
-/**
- * Blockquotes
- */
-blockquote {
- color: $brand-color;
- border-left: 4px solid $brand-color-light;
- padding-left: $spacing-unit / 2;
- @include relative-font-size(1.125);
- font-style: italic;
-
- > :last-child {
- margin-bottom: 0;
- }
-
- i, em {
- font-style: normal;
- }
-}
-
-
-
-/**
- * Code formatting
- */
-pre,
-code {
- font-family: $code-font-family;
- font-size: 0.9375em;
- border: 1px solid $brand-color-light;
- border-radius: 3px;
- background-color: $code-background-color;
-}
-
-code {
- padding: 1px 5px;
-}
-
-pre {
- padding: 8px 12px;
- overflow-x: auto;
-
- > code {
- border: 0;
- padding-right: 0;
- padding-left: 0;
- }
-}
-
-.highlight {
- border-radius: 3px;
- background: $code-background-color;
- @extend %vertical-rhythm;
-
- .highlighter-rouge & {
- background: $code-background-color;
- }
-}
-
-
-
-/**
- * Wrapper
- */
-.wrapper {
- max-width: calc(#{$content-width} - (#{$spacing-unit}));
- margin-right: auto;
- margin-left: auto;
- padding-right: $spacing-unit / 2;
- padding-left: $spacing-unit / 2;
- @extend %clearfix;
-
- @media screen and (min-width: $on-large) {
- max-width: calc(#{$content-width} - (#{$spacing-unit} * 2));
- padding-right: $spacing-unit;
- padding-left: $spacing-unit;
- }
-}
-
-
-
-/**
- * Clearfix
- */
-%clearfix:after {
- content: "";
- display: table;
- clear: both;
-}
-
-
-
-/**
- * Icons
- */
-
-.orange {
- color: #f66a0a;
-}
-
-.grey {
- color: #828282;
-}
-
-/**
- * Tables
- */
-table {
- margin-bottom: $spacing-unit;
- width: 100%;
- text-align: $table-text-align;
- color: $table-text-color;
- border-collapse: collapse;
- border: 1px solid $table-border-color;
- tr {
- &:nth-child(even) {
- background-color: $table-zebra-color;
- }
- }
- th, td {
- padding: ($spacing-unit / 3) ($spacing-unit / 2);
- }
- th {
- background-color: $table-header-bg-color;
- border: 1px solid $table-header-border;
- }
- td {
- border: 1px solid $table-border-color;
- }
-
- @include media-query($on-laptop) {
- display: block;
- overflow-x: auto;
- -webkit-overflow-scrolling: touch;
- -ms-overflow-style: -ms-autohiding-scrollbar;
- }
-}
-
-@media print {
- /**
- * Prevents page break from cutting through content when printing
- */
- body {
- display: block;
- }
- /**
- * Replaces the top navigation menu with the project name when printing
- */
- .site-header .wrapper {
- display: none;
- }
- .site-header {
- text-align: center;
- }
- .site-header:before {
- content: "AB-3";
- font-size: 32px;
- }
-}
-
diff --git a/docs/_sass/minima/_layout.scss b/docs/_sass/minima/_layout.scss
deleted file mode 100644
index ca99f981701..00000000000
--- a/docs/_sass/minima/_layout.scss
+++ /dev/null
@@ -1,263 +0,0 @@
-/**
- * Site header
- */
-.site-header {
- border-top: 5px solid $brand-color-dark;
- border-bottom: 1px solid $brand-color-light;
- min-height: $spacing-unit * 1.865;
- line-height: $base-line-height * $base-font-size * 2.25;
-
- // Positioning context for the mobile navigation icon
- position: relative;
-}
-
-.site-title {
- @include relative-font-size(1.625);
- font-weight: 300;
- letter-spacing: -1px;
- margin-bottom: 0;
- float: left;
-
- @include media-query($on-palm) {
- padding-right: 45px;
- }
-
- &,
- &:visited {
- color: $brand-color-dark;
- }
-}
-
-.site-nav {
- position: absolute;
- top: 9px;
- right: $spacing-unit / 2;
- background-color: $background-color;
- border: 1px solid $brand-color-light;
- border-radius: 5px;
- text-align: right;
-
- .nav-trigger {
- display: none;
- }
-
- .menu-icon {
- float: right;
- width: 36px;
- height: 26px;
- line-height: 0;
- padding-top: 10px;
- text-align: center;
-
- > svg path {
- fill: $brand-color-dark;
- }
- }
-
- label[for="nav-trigger"] {
- display: block;
- float: right;
- width: 36px;
- height: 36px;
- z-index: 2;
- cursor: pointer;
- }
-
- input ~ .trigger {
- clear: both;
- display: none;
- }
-
- input:checked ~ .trigger {
- display: block;
- padding-bottom: 5px;
- }
-
- .page-link {
- color: $text-color;
- line-height: $base-line-height;
- display: block;
- padding: 5px 10px;
-
- // Gaps between nav items, but not on the last one
- &:not(:last-child) {
- margin-right: 0;
- }
- margin-left: 20px;
- }
-
- @media screen and (min-width: $on-medium) {
- position: static;
- float: right;
- border: none;
- background-color: inherit;
-
- label[for="nav-trigger"] {
- display: none;
- }
-
- .menu-icon {
- display: none;
- }
-
- input ~ .trigger {
- display: block;
- }
-
- .page-link {
- display: inline;
- padding: 0;
-
- &:not(:last-child) {
- margin-right: 20px;
- }
- margin-left: auto;
- }
- }
-}
-
-
-
-/**
- * Page content
- */
-.page-content {
- padding: $spacing-unit 0;
- flex: 1 0 auto;
-}
-
-.page-heading {
- @include relative-font-size(2);
-}
-
-.post-list-heading {
- @include relative-font-size(1.75);
-}
-
-.post-list {
- margin-left: 0;
- list-style: none;
-
- > li {
- margin-bottom: $spacing-unit;
- }
-}
-
-.post-meta {
- font-size: $small-font-size;
- color: $brand-color;
-}
-
-.post-link {
- display: block;
- @include relative-font-size(1.5);
-}
-
-
-
-/**
- * Posts
- */
-.post-header {
- margin-bottom: $spacing-unit;
-}
-
-.post-title,
-.post-content h1 {
- @include relative-font-size(2.625);
- letter-spacing: -1px;
- line-height: 1.15;
-
- @media screen and (min-width: $on-large) {
- @include relative-font-size(2.625);
- }
-}
-
-.post-content {
- margin-bottom: $spacing-unit;
-
- h1, h2, h3 { margin-top: $spacing-unit * 2 }
- h4, h5, h6 { margin-top: $spacing-unit }
-
- h2 {
- @include relative-font-size(1.75);
-
- @media screen and (min-width: $on-large) {
- @include relative-font-size(2);
- }
- }
-
- h3 {
- @include relative-font-size(1.375);
-
- @media screen and (min-width: $on-large) {
- @include relative-font-size(1.625);
- }
- }
-
- h4 {
- @include relative-font-size(1.25);
- }
-
- h5 {
- @include relative-font-size(1.125);
- }
- h6 {
- @include relative-font-size(1.0625);
- }
-}
-
-
-.social-media-list {
- display: table;
- margin: 0 auto;
- li {
- float: left;
- margin: 5px 10px 5px 0;
- &:last-of-type { margin-right: 0 }
- a {
- display: block;
- padding: $spacing-unit / 4;
- border: 1px solid $brand-color-light;
- &:hover { border-color: darken($brand-color-light, 10%) }
- }
- }
-}
-
-
-
-/**
- * Pagination navbar
- */
-.pagination {
- margin-bottom: $spacing-unit;
- @extend .social-media-list;
- li {
- a, div {
- min-width: 41px;
- text-align: center;
- box-sizing: border-box;
- }
- div {
- display: block;
- padding: $spacing-unit / 4;
- border: 1px solid transparent;
-
- &.pager-edge {
- color: darken($brand-color-light, 5%);
- border: 1px dashed;
- }
- }
- }
-}
-
-
-
-/**
- * Grid helpers
- */
-@media screen and (min-width: $on-large) {
- .one-half {
- width: calc(50% - (#{$spacing-unit} / 2));
- }
-}
diff --git a/docs/_sass/minima/custom-mixins.scss b/docs/_sass/minima/custom-mixins.scss
deleted file mode 100644
index 9d4bedc1c67..00000000000
--- a/docs/_sass/minima/custom-mixins.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-@mixin alert-variant($background, $border, $color) {
- color: $color;
- @include gradient-bg($background);
- border-color: $border;
-
- .alert-link {
- color: darken($color, 10%);
- }
-}
-
-@mixin gradient-bg($color, $foreground: null) {
- @if $enable-gradients {
- @if $foreground {
- background-image: $foreground, linear-gradient(180deg, mix($body-bg, $color, 15%), $color);
- } @else {
- background-image: linear-gradient(180deg, mix($body-bg, $color, 15%), $color);
- }
- } @else {
- background-color: $color;
- }
-}
diff --git a/docs/_sass/minima/custom-styles.scss b/docs/_sass/minima/custom-styles.scss
deleted file mode 100644
index 56b5d56b430..00000000000
--- a/docs/_sass/minima/custom-styles.scss
+++ /dev/null
@@ -1,34 +0,0 @@
-// Placeholder to allow defining custom styles that override everything else.
-// (Use `_sass/minima/custom-variables.scss` to override variable defaults)
-h2, h3, h4, h5, h6 {
- color: #e46c0a;
-}
-
-// Bootstrap style alerts
-.alert {
- position: relative;
- padding: $alert-padding-y $alert-padding-x;
- margin-bottom: $alert-margin-bottom;
- border: $alert-border-width solid transparent;
- border-radius : $alert-border-radius;
-}
-
-// Headings for larger alerts
-.alert-heading {
- // Specified to prevent conflicts of changing $headings-color
- color: inherit;
-}
-
-// Provide class for links that match alerts
-.alert-link {
- font-weight: $alert-link-font-weight;
-}
-
-// Generate contextual modifier classes for colorizing the alert.
-
-@each $color, $value in $theme-colors {
- .alert-#{$color} {
- @include alert-variant(color-level($value, $alert-bg-level), color-level($value, $alert-border-level), color-level($value, $alert-color-level));
- }
-}
-
diff --git a/docs/_sass/minima/custom-variables.scss b/docs/_sass/minima/custom-variables.scss
deleted file mode 100644
index a128970cbe7..00000000000
--- a/docs/_sass/minima/custom-variables.scss
+++ /dev/null
@@ -1,76 +0,0 @@
-// Placeholder to allow overriding predefined variables smoothly.
-
-//Bootstrap's default
-$white: #fff !default;
-$gray-100: #f8f9fa !default;
-$gray-200: #e9ecef !default;
-$gray-300: #dee2e6 !default;
-$gray-400: #ced4da !default;
-$gray-500: #adb5bd !default;
-$gray-600: #6c757d !default;
-$gray-700: #495057 !default;
-$gray-800: #343a40 !default;
-$gray-900: #212529 !default;
-$black: #000 !default;
-$blue: #0d6efd !default;
-$indigo: #6610f2 !default;
-$purple: #6f42c1 !default;
-$pink: #d63384 !default;
-$red: #dc3545 !default;
-$orange: #fd7e14 !default;
-$yellow: #ffc107 !default;
-$green: #28a745 !default;
-$teal: #20c997 !default;
-$cyan: #17a2b8 !default;
-
-$primary: $blue !default;
-$secondary: $gray-600 !default;
-$success: $green !default;
-$info: $cyan !default;
-$warning: $yellow !default;
-$danger: $red !default;
-$light: $gray-100 !default;
-$dark: $gray-800 !default;
-
-$theme-colors: (
- "primary": $primary,
- "secondary": $secondary,
- "success": $success,
- "info": $info,
- "warning": $warning,
- "danger": $danger,
- "light": $light,
- "dark": $dark
-) !default;
-
-$theme-color-interval: 8% !default;
-
-$body-bg: $white !default;
-$body-color: $gray-900 !default;
-$body-text-align: null !default;
-
-$enable-gradients: true;
-
-// Define alert colors, border radius, and padding.
-$border-radius: .25rem !default;
-$border-width: 1px !default;
-$font-weight-bold: 700 !default;
-
-$alert-padding-y: .75rem !default;
-$alert-padding-x: 1.25rem !default;
-$alert-margin-bottom: 1rem !default;
-$alert-border-radius: $border-radius !default;
-$alert-link-font-weight: $font-weight-bold !default;
-$alert-border-width: $border-width !default;
-
-$alert-bg-level: -10 !default;
-$alert-border-level: -9 !default;
-$alert-color-level: 6 !default;
-
-// Request a color level
-// scss-docs-start color-level
-@function color-level($color: $primary, $level: 0) {
- $color-base: if($level > 0, $black, $white);
- $level: abs($level);
- @return mix($color-base, $color, $level * $theme-color-interval);
-}
diff --git a/docs/_sass/minima/initialize.scss b/docs/_sass/minima/initialize.scss
deleted file mode 100644
index 30288811151..00000000000
--- a/docs/_sass/minima/initialize.scss
+++ /dev/null
@@ -1,51 +0,0 @@
-@charset "utf-8";
-
-// Define defaults for each variable.
-
-$base-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Segoe UI Symbol", "Segoe UI Emoji", "Apple Color Emoji", Roboto, Helvetica, Arial, sans-serif !default;
-$code-font-family: "Menlo", "Inconsolata", "Consolas", "Roboto Mono", "Ubuntu Mono", "Liberation Mono", "Courier New", monospace;
-$base-font-size: 16px !default;
-$base-font-weight: 400 !default;
-$small-font-size: $base-font-size * 0.875 !default;
-$base-line-height: 1.5 !default;
-
-$spacing-unit: 30px !default;
-
-$table-text-align: left !default;
-
-// Width of the content area
-$content-width: 800px !default;
-
-$on-palm: 600px !default;
-$on-laptop: 800px !default;
-
-$on-medium: $on-palm !default;
-$on-large: $on-laptop !default;
-
-// Use media queries like this:
-// @include media-query($on-palm) {
-// .wrapper {
-// padding-right: $spacing-unit / 2;
-// padding-left: $spacing-unit / 2;
-// }
-// }
-// Notice the following mixin uses max-width, in a deprecated, desktop-first
-// approach, whereas media queries used elsewhere now use min-width.
-@mixin media-query($device) {
- @media screen and (max-width: $device) {
- @content;
- }
-}
-
-@mixin relative-font-size($ratio) {
- font-size: #{$ratio}rem;
-}
-
-// Import pre-styling-overrides hook and style-partials.
-@import
- "minima/custom-variables", // Hook to override predefined variables.
- "minima/custom-mixins", // Hook to add custom mixins.
- "minima/base", // Defines element resets.
- "minima/layout", // Defines structure and style based on CSS selectors.
- "minima/custom-styles" // Hook to override existing styles.
-;
diff --git a/docs/_sass/minima/skins/classic.scss b/docs/_sass/minima/skins/classic.scss
deleted file mode 100644
index 37ea9c5244c..00000000000
--- a/docs/_sass/minima/skins/classic.scss
+++ /dev/null
@@ -1,84 +0,0 @@
-@charset "utf-8";
-
-$brand-color: #828282 !default;
-$brand-color-light: lighten($brand-color, 40%) !default;
-$brand-color-dark: darken($brand-color, 25%) !default;
-
-$text-color: #111 !default;
-$background-color: #fdfdfd !default;
-$code-background-color: #eef !default;
-
-$link-base-color: #2a7ae2 !default;
-$link-visited-color: darken($link-base-color, 15%) !default;
-
-$table-text-color: lighten($text-color, 18%) !default;
-$table-zebra-color: lighten($brand-color, 46%) !default;
-$table-header-bg-color: lighten($brand-color, 43%) !default;
-$table-header-border: lighten($brand-color, 36%) !default;
-$table-border-color: $brand-color-light !default;
-
-
-// Syntax highlighting styles should be adjusted appropriately for every "skin"
-// ----------------------------------------------------------------------------
-
-.highlight {
- .c { color: #998; font-style: italic } // Comment
- .err { color: #a61717; background-color: #e3d2d2 } // Error
- .k { font-weight: bold } // Keyword
- .o { font-weight: bold } // Operator
- .cm { color: #998; font-style: italic } // Comment.Multiline
- .cp { color: #999; font-weight: bold } // Comment.Preproc
- .c1 { color: #998; font-style: italic } // Comment.Single
- .cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special
- .gd { color: #000; background-color: #fdd } // Generic.Deleted
- .gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific
- .ge { font-style: italic } // Generic.Emph
- .gr { color: #a00 } // Generic.Error
- .gh { color: #999 } // Generic.Heading
- .gi { color: #000; background-color: #dfd } // Generic.Inserted
- .gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific
- .go { color: #888 } // Generic.Output
- .gp { color: #555 } // Generic.Prompt
- .gs { font-weight: bold } // Generic.Strong
- .gu { color: #aaa } // Generic.Subheading
- .gt { color: #a00 } // Generic.Traceback
- .kc { font-weight: bold } // Keyword.Constant
- .kd { font-weight: bold } // Keyword.Declaration
- .kp { font-weight: bold } // Keyword.Pseudo
- .kr { font-weight: bold } // Keyword.Reserved
- .kt { color: #458; font-weight: bold } // Keyword.Type
- .m { color: #099 } // Literal.Number
- .s { color: #d14 } // Literal.String
- .na { color: #008080 } // Name.Attribute
- .nb { color: #0086B3 } // Name.Builtin
- .nc { color: #458; font-weight: bold } // Name.Class
- .no { color: #008080 } // Name.Constant
- .ni { color: #800080 } // Name.Entity
- .ne { color: #900; font-weight: bold } // Name.Exception
- .nf { color: #900; font-weight: bold } // Name.Function
- .nn { color: #555 } // Name.Namespace
- .nt { color: #000080 } // Name.Tag
- .nv { color: #008080 } // Name.Variable
- .ow { font-weight: bold } // Operator.Word
- .w { color: #bbb } // Text.Whitespace
- .mf { color: #099 } // Literal.Number.Float
- .mh { color: #099 } // Literal.Number.Hex
- .mi { color: #099 } // Literal.Number.Integer
- .mo { color: #099 } // Literal.Number.Oct
- .sb { color: #d14 } // Literal.String.Backtick
- .sc { color: #d14 } // Literal.String.Char
- .sd { color: #d14 } // Literal.String.Doc
- .s2 { color: #d14 } // Literal.String.Double
- .se { color: #d14 } // Literal.String.Escape
- .sh { color: #d14 } // Literal.String.Heredoc
- .si { color: #d14 } // Literal.String.Interpol
- .sx { color: #d14 } // Literal.String.Other
- .sr { color: #009926 } // Literal.String.Regex
- .s1 { color: #d14 } // Literal.String.Single
- .ss { color: #990073 } // Literal.String.Symbol
- .bp { color: #999 } // Name.Builtin.Pseudo
- .vc { color: #008080 } // Name.Variable.Class
- .vg { color: #008080 } // Name.Variable.Global
- .vi { color: #008080 } // Name.Variable.Instance
- .il { color: #099 } // Literal.Number.Integer.Long
-}
diff --git a/docs/_sass/minima/skins/solarized-dark.scss b/docs/_sass/minima/skins/solarized-dark.scss
deleted file mode 100644
index f3b1f387de0..00000000000
--- a/docs/_sass/minima/skins/solarized-dark.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-@charset "utf-8";
-
-$sol-is-dark: true;
-@import "minima/skins/solarized";
diff --git a/docs/_sass/minima/skins/solarized.scss b/docs/_sass/minima/skins/solarized.scss
deleted file mode 100644
index 982bd7f2990..00000000000
--- a/docs/_sass/minima/skins/solarized.scss
+++ /dev/null
@@ -1,133 +0,0 @@
-@charset "utf-8";
-
-// Solarized skin
-// ==============
-// Created by Sander Voerman using the Solarized
-// color scheme by Ethan Schoonover .
-
-// This style sheet implements two options for the minima.skin setting:
-// "solarized" for light mode and "solarized-dark" for dark mode.
-$sol-is-dark: false !default;
-
-
-// Color scheme
-// ------------
-// The inline comments show the canonical L*a*b values for each color.
-
-$sol-base03: #002b36; // 15 -12 -12
-$sol-base02: #073642; // 20 -12 -12
-$sol-base01: #586e75; // 45 -07 -07
-$sol-base00: #657b83; // 50 -07 -07
-$sol-base0: #839496; // 60 -06 -03
-$sol-base1: #93a1a1; // 65 -05 -02
-$sol-base2: #eee8d5; // 92 -00 10
-$sol-base3: #fdf6e3; // 97 00 10
-$sol-yellow: #b58900; // 60 10 65
-$sol-orange: #cb4b16; // 50 50 55
-$sol-red: #dc322f; // 50 65 45
-$sol-magenta: #d33682; // 50 65 -05
-$sol-violet: #6c71c4; // 50 15 -45
-$sol-blue: #268bd2; // 55 -10 -45
-$sol-cyan: #2aa198; // 60 -35 -05
-$sol-green: #859900; // 60 -20 65
-
-$sol-mono3: $sol-base3;
-$sol-mono2: $sol-base2;
-$sol-mono1: $sol-base1;
-$sol-mono00: $sol-base00;
-$sol-mono01: $sol-base01;
-
-@if $sol-is-dark {
- $sol-mono3: $sol-base03;
- $sol-mono2: $sol-base02;
- $sol-mono1: $sol-base01;
- $sol-mono00: $sol-base0;
- $sol-mono01: $sol-base1;
-}
-
-
-// Minima color variables
-// ----------------------
-
-$brand-color: $sol-mono1 !default;
-$brand-color-light: mix($sol-mono1, $sol-mono3) !default;
-$brand-color-dark: $sol-mono00 !default;
-
-$text-color: $sol-mono01 !default;
-$background-color: $sol-mono3 !default;
-$code-background-color: $sol-mono2 !default;
-
-$link-base-color: $sol-blue !default;
-$link-visited-color: mix($sol-blue, $sol-mono00) !default;
-
-$table-text-color: $sol-mono00 !default;
-$table-zebra-color: mix($sol-mono2, $sol-mono3) !default;
-$table-header-bg-color: $sol-mono2 !default;
-$table-header-border: $sol-mono1 !default;
-$table-border-color: $sol-mono1 !default;
-
-
-// Syntax highlighting styles
-// --------------------------
-
-.highlight {
- .c { color: $sol-mono1; font-style: italic } // Comment
- .err { color: $sol-red } // Error
- .k { color: $sol-mono01; font-weight: bold } // Keyword
- .o { color: $sol-mono01; font-weight: bold } // Operator
- .cm { color: $sol-mono1; font-style: italic } // Comment.Multiline
- .cp { color: $sol-mono1; font-weight: bold } // Comment.Preproc
- .c1 { color: $sol-mono1; font-style: italic } // Comment.Single
- .cs { color: $sol-mono1; font-weight: bold; font-style: italic } // Comment.Special
- .gd { color: $sol-red } // Generic.Deleted
- .gd .x { color: $sol-red } // Generic.Deleted.Specific
- .ge { color: $sol-mono00; font-style: italic } // Generic.Emph
- .gr { color: $sol-red } // Generic.Error
- .gh { color: $sol-mono1 } // Generic.Heading
- .gi { color: $sol-green } // Generic.Inserted
- .gi .x { color: $sol-green } // Generic.Inserted.Specific
- .go { color: $sol-mono00 } // Generic.Output
- .gp { color: $sol-mono00 } // Generic.Prompt
- .gs { color: $sol-mono01; font-weight: bold } // Generic.Strong
- .gu { color: $sol-mono1 } // Generic.Subheading
- .gt { color: $sol-red } // Generic.Traceback
- .kc { color: $sol-mono01; font-weight: bold } // Keyword.Constant
- .kd { color: $sol-mono01; font-weight: bold } // Keyword.Declaration
- .kp { color: $sol-mono01; font-weight: bold } // Keyword.Pseudo
- .kr { color: $sol-mono01; font-weight: bold } // Keyword.Reserved
- .kt { color: $sol-violet; font-weight: bold } // Keyword.Type
- .m { color: $sol-cyan } // Literal.Number
- .s { color: $sol-magenta } // Literal.String
- .na { color: $sol-cyan } // Name.Attribute
- .nb { color: $sol-blue } // Name.Builtin
- .nc { color: $sol-violet; font-weight: bold } // Name.Class
- .no { color: $sol-cyan } // Name.Constant
- .ni { color: $sol-violet } // Name.Entity
- .ne { color: $sol-violet; font-weight: bold } // Name.Exception
- .nf { color: $sol-blue; font-weight: bold } // Name.Function
- .nn { color: $sol-mono00 } // Name.Namespace
- .nt { color: $sol-blue } // Name.Tag
- .nv { color: $sol-cyan } // Name.Variable
- .ow { color: $sol-mono01; font-weight: bold } // Operator.Word
- .w { color: $sol-mono1 } // Text.Whitespace
- .mf { color: $sol-cyan } // Literal.Number.Float
- .mh { color: $sol-cyan } // Literal.Number.Hex
- .mi { color: $sol-cyan } // Literal.Number.Integer
- .mo { color: $sol-cyan } // Literal.Number.Oct
- .sb { color: $sol-magenta } // Literal.String.Backtick
- .sc { color: $sol-magenta } // Literal.String.Char
- .sd { color: $sol-magenta } // Literal.String.Doc
- .s2 { color: $sol-magenta } // Literal.String.Double
- .se { color: $sol-magenta } // Literal.String.Escape
- .sh { color: $sol-magenta } // Literal.String.Heredoc
- .si { color: $sol-magenta } // Literal.String.Interpol
- .sx { color: $sol-magenta } // Literal.String.Other
- .sr { color: $sol-green } // Literal.String.Regex
- .s1 { color: $sol-magenta } // Literal.String.Single
- .ss { color: $sol-magenta } // Literal.String.Symbol
- .bp { color: $sol-mono1 } // Name.Builtin.Pseudo
- .vc { color: $sol-cyan } // Name.Variable.Class
- .vg { color: $sol-cyan } // Name.Variable.Global
- .vi { color: $sol-cyan } // Name.Variable.Instance
- .il { color: $sol-cyan } // Literal.Number.Integer.Long
-}
diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss
deleted file mode 100644
index b5ec6976efa..00000000000
--- a/docs/assets/css/style.scss
+++ /dev/null
@@ -1,12 +0,0 @@
----
-# Only the main Sass file needs front matter (the dashes are enough)
----
-
-@import
- "minima/skins/{{ site.minima.skin | default: 'classic' }}",
- "minima/initialize";
-
-.icon {
- height: 21px;
- width: 21px
-}
diff --git a/docs/diagrams/AddTripSequenceDiagram.puml b/docs/diagrams/AddTripSequenceDiagram.puml
new file mode 100644
index 00000000000..a1cb15740c5
--- /dev/null
+++ b/docs/diagrams/AddTripSequenceDiagram.puml
@@ -0,0 +1,88 @@
+@startuml AddTripSequenceDiagram
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":AddTripCommandParser" as AddTripParser LOGIC_COLOR
+participant ":ParserUtil" as ParserUtil LOGIC_COLOR
+participant "a:AddTripCommand" as AddCommand LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute()
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand()
+activate AddressBookParser
+
+AddressBookParser -> AddTripParser : parse()
+activate AddTripParser
+
+AddTripParser -> ParserUtil : parseTripName()
+activate ParserUtil
+ParserUtil --> AddTripParser
+deactivate ParserUtil
+
+AddTripParser -> ParserUtil : parseAccommodation()
+activate ParserUtil
+ParserUtil --> AddTripParser
+deactivate ParserUtil
+
+AddTripParser -> ParserUtil : parseItinerary()
+activate ParserUtil
+ParserUtil --> AddTripParser
+deactivate ParserUtil
+
+AddTripParser -> ParserUtil : parseTripDate()
+activate ParserUtil
+ParserUtil --> AddTripParser
+deactivate ParserUtil
+
+AddTripParser -> ParserUtil : parseName()
+activate ParserUtil
+ParserUtil --> AddTripParser
+deactivate ParserUtil
+
+AddTripParser -> ParserUtil : parseNote()
+activate ParserUtil
+ParserUtil --> AddTripParser
+deactivate ParserUtil
+
+create AddCommand
+AddTripParser -> AddCommand
+activate AddCommand
+
+AddCommand --> AddTripParser
+deactivate AddCommand
+
+AddTripParser --> AddressBookParser : a
+deactivate AddTripParser
+
+AddressBookParser --> LogicManager : a
+deactivate AddressBookParser
+
+LogicManager -> AddCommand : execute()
+activate AddCommand
+
+AddCommand -> Model : hasTrip()
+activate Model
+Model --> AddCommand
+deactivate Model
+
+AddCommand -> Model : addTrip()
+activate Model
+Model --> AddCommand
+deactivate Model
+
+AddCommand --> LogicManager : result
+deactivate AddCommand
+AddCommand -[hidden]-> LogicManager : result
+
+[<-- LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/ArchitectureSequenceDiagram.puml b/docs/diagrams/ArchitectureSequenceDiagram.puml
index 48b6cc4333c..81ae82160e8 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 : "deleteContact 1"
activate ui UI_COLOR
-ui -[UI_COLOR]> logic : execute("delete 1")
+ui -[UI_COLOR]> logic : execute("deleteContact 1")
activate logic LOGIC_COLOR
-logic -[LOGIC_COLOR]> model : deletePerson(p)
+logic -[LOGIC_COLOR]> model : deleteContact(p)
activate model MODEL_COLOR
model -[MODEL_COLOR]-> logic
diff --git a/docs/diagrams/BetterModelClassDiagram.puml b/docs/diagrams/BetterModelClassDiagram.puml
index 598474a5c82..ad1f875f21a 100644
--- a/docs/diagrams/BetterModelClassDiagram.puml
+++ b/docs/diagrams/BetterModelClassDiagram.puml
@@ -18,4 +18,18 @@ Person *--> Name
Person *--> Phone
Person *--> Email
Person *--> Address
+
+TripBook *-right-> "1" UniqueTripList
+UniqueTripList -[hidden]down- UniquePersonList
+UniqueTripList -[hidden]down- UniqueTagList
+
+UniqueTripList -right-> Trip
+
+Trip *--> TripName
+Trip *--> Accommodation
+Trip *--> Itinerary
+Trip *--> TripDate
+Trip *--> Note
+Trip *--> CustomerName
+
@enduml
diff --git a/docs/diagrams/DeleteContactDiagram.puml b/docs/diagrams/DeleteContactDiagram.puml
new file mode 100644
index 00000000000..18c7bfafa85
--- /dev/null
+++ b/docs/diagrams/DeleteContactDiagram.puml
@@ -0,0 +1,57 @@
+@startuml DeleteContactSequenceDiagram
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant "d:DeleteContactCommand" as DeleteCommand 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("deleteContact 1")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("deleteContact 1")
+activate AddressBookParser
+
+create DeleteCommand
+AddressBookParser -> DeleteCommand
+activate DeleteCommand
+
+DeleteCommand --> AddressBookParser
+deactivate DeleteCommand
+
+AddressBookParser --> LogicManager : d
+deactivate AddressBookParser
+
+LogicManager -> DeleteCommand : execute()
+activate DeleteCommand
+
+DeleteCommand -> Model : getFilteredPersonList()
+activate Model
+Model --> DeleteCommand : filteredList
+deactivate Model
+
+DeleteCommand -> DeleteCommand : validate(1)
+
+DeleteCommand -> Model : deletePerson(contact)
+activate Model
+Model -> AddressBook : removePerson(contact)
+activate AddressBook
+AddressBook --> Model
+deactivate AddressBook
+Model --> DeleteCommand
+deactivate Model
+
+DeleteCommand --> LogicManager : result
+deactivate DeleteCommand
+DeleteCommand -[hidden]-> LogicManager : result
+
+[<-- LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/DeleteSequenceDiagram.puml b/docs/diagrams/DeleteSequenceDiagram.puml
index 5241e79d7da..ee8978ede84 100644
--- a/docs/diagrams/DeleteSequenceDiagram.puml
+++ b/docs/diagrams/DeleteSequenceDiagram.puml
@@ -5,8 +5,8 @@ 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 ":DeleteContactCommandParser" as DeleteCommandParser LOGIC_COLOR
+participant "d:DeleteContactCommand" as DeleteCommand LOGIC_COLOR
participant "r:CommandResult" as CommandResult LOGIC_COLOR
end box
@@ -14,10 +14,10 @@ box Model MODEL_COLOR_T1
participant "m:Model" as Model MODEL_COLOR
end box
-[-> LogicManager : execute("delete 1")
+[-> LogicManager : execute("deleteContact 1")
activate LogicManager
-LogicManager -> AddressBookParser : parseCommand("delete 1")
+LogicManager -> AddressBookParser : parseCommand("deleteContact 1")
activate AddressBookParser
create DeleteCommandParser
@@ -49,7 +49,7 @@ deactivate AddressBookParser
LogicManager -> DeleteCommand : execute(m)
activate DeleteCommand
-DeleteCommand -> Model : deletePerson(1)
+DeleteCommand -> Model : deleteContact(1)
activate Model
Model --> DeleteCommand
diff --git a/docs/diagrams/DeleteTripSequenceDiagram.puml b/docs/diagrams/DeleteTripSequenceDiagram.puml
new file mode 100644
index 00000000000..8100d9eaa9a
--- /dev/null
+++ b/docs/diagrams/DeleteTripSequenceDiagram.puml
@@ -0,0 +1,39 @@
+@startuml DeleteTripSequenceDiagram
+
+title Sequence Diagram for Delete Trip Command
+
+actor User
+participant ":MainWindow" as MainWindow
+participant ":LogicManager" as LogicManager
+participant ":DeleteTripCommand" as DeleteCommand
+participant ":ModelManager" as ModelManager
+participant ":TripBook" as TripBook
+
+User -> MainWindow : enters "deleteTrip 1"
+MainWindow -> LogicManager : execute("deleteTrip 1")
+
+group Command Execution
+ LogicManager -> DeleteCommand : execute(model)
+ activate DeleteCommand
+
+ DeleteCommand -> ModelManager : getFilteredTripList()
+ ModelManager --> DeleteCommand : filteredList
+ DeleteCommand -> DeleteCommand : validate(1)
+
+ alt Index valid
+ DeleteCommand -> ModelManager : deleteTrip(index)
+ ModelManager -> TripBook : removeTrip(index)
+ TripBook --> ModelManager : updatedTripBook
+ ModelManager --> DeleteCommand : void
+ DeleteCommand --> LogicManager : CommandResult\n("Deleted Trip: [Trip 1 Details]")
+ else Index invalid
+ DeleteCommand --> LogicManager : CommandException\n("The trip index provided is invalid")
+ end
+
+ deactivate DeleteCommand
+end
+
+LogicManager --> MainWindow : result feedback
+MainWindow -> User : shows result
+
+@enduml
diff --git a/docs/diagrams/EditTripSequenceDiagram.puml b/docs/diagrams/EditTripSequenceDiagram.puml
new file mode 100644
index 00000000000..02053e69244
--- /dev/null
+++ b/docs/diagrams/EditTripSequenceDiagram.puml
@@ -0,0 +1,91 @@
+@startuml EditTripSequenceDiagram
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":EditTripCommandParser" as EditTripParser LOGIC_COLOR
+participant "<>\nParserUtil" as ParserUtil LOGIC_COLOR
+participant "a:EditTripCommand" as EditCommand LOGIC_COLOR
+
+end box
+
+box Model MODEL_COLOR_T1
+participant ":Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute()
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand()
+activate AddressBookParser
+
+AddressBookParser -> EditTripParser : parse()
+activate EditTripParser
+
+EditTripParser -> ParserUtil : parseTripName()
+EditTripParser -> ParserUtil : parseAccommodation()
+EditTripParser -> ParserUtil : parseItinerary()
+EditTripParser -> ParserUtil : parseTripDate()
+EditTripParser -> ParserUtil : parseName()
+EditTripParser -> ParserUtil : parseNote()
+
+create EditCommand
+EditTripParser -> EditCommand
+activate EditCommand
+
+EditCommand --> EditTripParser
+deactivate EditCommand
+
+EditTripParser --> AddressBookParser : a
+deactivate EditTripParser
+
+AddressBookParser --> LogicManager : a
+deactivate AddressBookParser
+
+LogicManager -> EditCommand : execute()
+activate EditCommand
+
+EditCommand -> Model : getFilteredTripList()
+activate Model
+Model --> EditCommand : List
+deactivate Model
+
+EditCommand -> EditCommand : createEditedTrip()
+
+note right of EditCommand
+Check for duplicates
+end note
+EditCommand -> EditCommand : isSameTrip()
+
+EditCommand -> Model : hasTrip(editedTrip)
+activate Model
+Model --> EditCommand : boolean
+deactivate Model
+
+alt Duplicate Trip Found
+ EditCommand -> EditCommand : throw CommandException
+ EditCommand --> LogicManager : exception
+ [<-- LogicManager : exception
+ deactivate EditCommand
+ deactivate LogicManager
+end
+
+EditCommand -> Model : setTrip(tripToEdit, editedTrip)
+activate Model
+Model --> EditCommand
+deactivate Model
+
+EditCommand -> Model : updateFilteredTripList(PREDICATE_SHOW_ALL_TRIPS)
+activate Model
+Model --> EditCommand
+deactivate Model
+
+EditCommand --> LogicManager : CommandResult
+deactivate EditCommand
+EditCommand -[hidden]-> LogicManager : result
+
+[<-- LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/ListContactSequenceDiagram.puml b/docs/diagrams/ListContactSequenceDiagram.puml
new file mode 100644
index 00000000000..44ef1efdb8a
--- /dev/null
+++ b/docs/diagrams/ListContactSequenceDiagram.puml
@@ -0,0 +1,69 @@
+@startuml ListContactSequenceDiagram
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":ListContactCommandParser" as ListContactCommandParser LOGIC_COLOR
+participant "l:ListContactCommand" as ListContactCommand LOGIC_COLOR
+participant "r:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "m:Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("listContact service")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("listContact service")
+activate AddressBookParser
+
+create ListContactCommandParser
+AddressBookParser -> ListContactCommandParser
+activate ListContactCommandParser
+
+ListContactCommandParser --> AddressBookParser
+deactivate ListContactCommandParser
+
+AddressBookParser -> ListContactCommandParser : parse("service")
+activate ListContactCommandParser
+
+create ListContactCommand
+ListContactCommandParser -> ListContactCommand
+activate ListContactCommand
+
+ListContactCommand --> ListContactCommandParser :
+deactivate ListContactCommand
+
+ListContactCommandParser --> AddressBookParser : l
+deactivate ListContactCommandParser
+'Hidden arrow to position the destroy marker below the end of the activation bar.
+ListContactCommandParser -[hidden]-> AddressBookParser
+destroy ListContactCommandParser
+
+AddressBookParser --> LogicManager : l
+deactivate AddressBookParser
+
+LogicManager -> ListContactCommand : execute(m)
+activate ListContactCommand
+
+ListContactCommand -> Model : updateFilteredPersonList(show_service)
+activate Model
+
+deactivate Model
+
+create CommandResult
+ListContactCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> ListContactCommand
+deactivate CommandResult
+
+ListContactCommand --> LogicManager : r
+deactivate ListContactCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/ListTripSequenceDiagram.puml b/docs/diagrams/ListTripSequenceDiagram.puml
new file mode 100644
index 00000000000..c4b97969ec0
--- /dev/null
+++ b/docs/diagrams/ListTripSequenceDiagram.puml
@@ -0,0 +1,73 @@
+@startuml ListTripSequenceDiagram
+!include style.puml
+skinparam ArrowFontStyle plain
+
+box Logic LOGIC_COLOR_T1
+participant ":LogicManager" as LogicManager LOGIC_COLOR
+participant ":AddressBookParser" as AddressBookParser LOGIC_COLOR
+participant ":ListTripCommandParser" as ListTripCommandParser LOGIC_COLOR
+participant "l:ListTripCommand" as ListTripCommand LOGIC_COLOR
+participant "r:CommandResult" as CommandResult LOGIC_COLOR
+end box
+
+box Model MODEL_COLOR_T1
+participant "m:Model" as Model MODEL_COLOR
+end box
+
+[-> LogicManager : execute("listTrip 01/01/2025")
+activate LogicManager
+
+LogicManager -> AddressBookParser : parseCommand("listTrip 01/01/2025")
+activate AddressBookParser
+
+create ListTripCommandParser
+AddressBookParser -> ListTripCommandParser
+activate ListTripCommandParser
+
+ListTripCommandParser --> AddressBookParser
+deactivate ListTripCommandParser
+
+AddressBookParser -> ListTripCommandParser : parse("01/01/2025")
+activate ListTripCommandParser
+
+create ListTripCommand
+ListTripCommandParser -> ListTripCommand
+activate ListTripCommand
+
+ListTripCommand --> ListTripCommandParser : l
+deactivate ListTripCommand
+
+ListTripCommandParser --> AddressBookParser : l
+deactivate ListTripCommandParser
+
+AddressBookParser --> LogicManager : l
+deactivate AddressBookParser
+
+LogicManager -> ListTripCommand : execute(m)
+activate ListTripCommand
+
+alt date == null
+ ListTripCommand -> Model : updateFilteredTripList(PREDICATE_SHOW_ALL_TRIPS)
+else date != null
+ ListTripCommand -> Model : updateFilteredTripList(predicate by date)
+end
+
+activate Model
+Model --> ListTripCommand : confirmation of filter.
+ListTripCommand -> Model : getFilteredTripList()
+Model --> ListTripCommand : list of trips.
+deactivate Model
+
+create CommandResult
+ListTripCommand -> CommandResult
+activate CommandResult
+
+CommandResult --> ListTripCommand
+deactivate CommandResult
+
+ListTripCommand --> LogicManager : r
+deactivate ListTripCommand
+
+[<--LogicManager
+deactivate LogicManager
+@enduml
diff --git a/docs/diagrams/tracing/LogicSequenceDiagram.puml b/docs/diagrams/tracing/LogicSequenceDiagram.puml
index 42bf46d3ce8..ded70e1a1ce 100644
--- a/docs/diagrams/tracing/LogicSequenceDiagram.puml
+++ b/docs/diagrams/tracing/LogicSequenceDiagram.puml
@@ -4,8 +4,8 @@ skinparam ArrowFontStyle plain
Participant ":LogicManager" as logic LOGIC_COLOR
Participant ":AddressBookParser" as abp LOGIC_COLOR
-Participant ":EditCommandParser" as ecp LOGIC_COLOR
-Participant "command:EditCommand" as ec LOGIC_COLOR
+Participant ":EditContactCommandParser" as ecp LOGIC_COLOR
+Participant "command:EditContactCommand" as ec LOGIC_COLOR
[-> logic : execute
activate logic
diff --git a/docs/images/Annotated-Ui.png b/docs/images/Annotated-Ui.png
new file mode 100644
index 00000000000..f95cbad5163
Binary files /dev/null and b/docs/images/Annotated-Ui.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 ac2ae217c51..00000000000
Binary files a/docs/images/DeleteSequenceDiagram.png and /dev/null differ
diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png
deleted file mode 100644
index fe91c69efe7..00000000000
Binary files a/docs/images/LogicClassDiagram.png and /dev/null differ
diff --git a/docs/images/LogicStorageDIP.png b/docs/images/LogicStorageDIP.png
deleted file mode 100644
index 871157f5a9c..00000000000
Binary files a/docs/images/LogicStorageDIP.png and /dev/null differ
diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png
deleted file mode 100644
index a19fb1b4ac8..00000000000
Binary files a/docs/images/ModelClassDiagram.png and /dev/null differ
diff --git a/docs/images/ParserClasses.png b/docs/images/ParserClasses.png
deleted file mode 100644
index 2caeeb1a067..00000000000
Binary files a/docs/images/ParserClasses.png and /dev/null differ
diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png
deleted file mode 100644
index 18fa4d0d51f..00000000000
Binary files a/docs/images/StorageClassDiagram.png and /dev/null differ
diff --git a/docs/images/Ui.png b/docs/images/Ui.png
index 5bd77847aa2..056eba68dea 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/addContactCommand.PNG b/docs/images/addContactCommand.PNG
new file mode 100644
index 00000000000..92d148e2993
Binary files /dev/null and b/docs/images/addContactCommand.PNG differ
diff --git a/docs/images/addTripCommand.PNG b/docs/images/addTripCommand.PNG
new file mode 100644
index 00000000000..2342dcd0a56
Binary files /dev/null and b/docs/images/addTripCommand.PNG differ
diff --git a/docs/images/adwinang.png b/docs/images/adwinang.png
new file mode 100644
index 00000000000..faa1ea8f27a
Binary files /dev/null and b/docs/images/adwinang.png differ
diff --git a/docs/images/clearCommand.PNG b/docs/images/clearCommand.PNG
new file mode 100644
index 00000000000..e6917feef09
Binary files /dev/null and b/docs/images/clearCommand.PNG differ
diff --git a/docs/images/clearCommandDialog.PNG b/docs/images/clearCommandDialog.PNG
new file mode 100644
index 00000000000..b4ecaf12bc5
Binary files /dev/null and b/docs/images/clearCommandDialog.PNG differ
diff --git a/docs/images/deleteContactCommand.PNG b/docs/images/deleteContactCommand.PNG
new file mode 100644
index 00000000000..98cc108b437
Binary files /dev/null and b/docs/images/deleteContactCommand.PNG differ
diff --git a/docs/images/deleteTripCommand.PNG b/docs/images/deleteTripCommand.PNG
new file mode 100644
index 00000000000..b0f60850905
Binary files /dev/null and b/docs/images/deleteTripCommand.PNG differ
diff --git a/docs/images/dexterleng.png b/docs/images/dexterleng.png
new file mode 100644
index 00000000000..2e8d4d0bb93
Binary files /dev/null and b/docs/images/dexterleng.png differ
diff --git a/docs/images/editContactCommand.PNG b/docs/images/editContactCommand.PNG
new file mode 100644
index 00000000000..9bcd2cc07be
Binary files /dev/null and b/docs/images/editContactCommand.PNG differ
diff --git a/docs/images/editTripCommand.PNG b/docs/images/editTripCommand.PNG
new file mode 100644
index 00000000000..aa97cec8dc2
Binary files /dev/null and b/docs/images/editTripCommand.PNG differ
diff --git a/docs/images/findAlexDavidResult.png b/docs/images/findAlexDavidResult.png
index 235da1c273e..8fb73ec2470 100644
Binary files a/docs/images/findAlexDavidResult.png and b/docs/images/findAlexDavidResult.png differ
diff --git a/docs/images/helpCommand.PNG b/docs/images/helpCommand.PNG
new file mode 100644
index 00000000000..de1e0cdd358
Binary files /dev/null and b/docs/images/helpCommand.PNG differ
diff --git a/docs/images/kararei.png b/docs/images/kararei.png
new file mode 100644
index 00000000000..b2795a9c4b0
Binary files /dev/null and b/docs/images/kararei.png differ
diff --git a/docs/images/listContactCommand.PNG b/docs/images/listContactCommand.PNG
new file mode 100644
index 00000000000..3aad8a3ade0
Binary files /dev/null and b/docs/images/listContactCommand.PNG differ
diff --git a/docs/images/listTripCommand.PNG b/docs/images/listTripCommand.PNG
new file mode 100644
index 00000000000..8bcc77fffa7
Binary files /dev/null and b/docs/images/listTripCommand.PNG differ
diff --git a/docs/images/suspectblue.png b/docs/images/suspectblue.png
new file mode 100644
index 00000000000..0f7a3c9bd22
Binary files /dev/null and b/docs/images/suspectblue.png differ
diff --git a/docs/images/zyonwee.png b/docs/images/zyonwee.png
new file mode 100644
index 00000000000..eb16b5abd48
Binary files /dev/null and b/docs/images/zyonwee.png differ
diff --git a/docs/img.png b/docs/img.png
new file mode 100644
index 00000000000..c4cd4dbce55
Binary files /dev/null and b/docs/img.png differ
diff --git a/docs/index.md b/docs/index.md
index 7601dbaad0d..7bf64e2f996 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,19 +1,22 @@
---
-layout: page
-title: AddressBook Level-3
+ layout: default.md
+ title: ""
---
-[](https://github.com/se-edu/addressbook-level3/actions)
+# TravelHub
+
+[](https://github.com/AY2425S2-CS2103-F09-1/tp/actions)
[](https://codecov.io/gh/se-edu/addressbook-level3)

-**AddressBook is a desktop application for managing your contact details.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
+**TravelHub is a desktop application to manage customer profiles and trip details, making it easy to customize itineraries, access daily schedules, manage contacts for services like accommodations, transport, and activities - ensuring a personalized and efficient travel planning experience.** While it has a GUI, most of the user interactions happen using a CLI (Command Line Interface).
-* If you are interested in using AddressBook, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
-* If you are interested about developing AddressBook, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
+* If you are interested in using TravelHub, head over to the [_Quick Start_ section of the **User Guide**](UserGuide.html#quick-start).
+* If you are interested about developing TravelHub, the [**Developer Guide**](DeveloperGuide.html) is a good place to start.
**Acknowledgements**
* Libraries used: [JavaFX](https://openjfx.io/), [Jackson](https://github.com/FasterXML/jackson), [JUnit5](https://github.com/junit-team/junit5)
+* This project is based on the AddressBook-Level3 project created by the [SE-EDU initiative](https://se-education.org).
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..00c0118f638
--- /dev/null
+++ b/docs/site.json
@@ -0,0 +1,29 @@
+{
+ "baseUrl": "",
+ "titlePrefix": "TravelHub",
+ "titleSuffix": "",
+ "faviconPath": "images/SeEduLogo.png",
+ "style": {
+ "codeTheme": "light"
+ },
+ "ignore": [
+ "_markbind/layouts/*",
+ "_markbind/logs/*",
+ "_site/*",
+ "site.json",
+ "*.md",
+ "*.njk",
+ ".git/*",
+ "node_modules/*"
+ ],
+ "pagesExclude": ["node_modules/*"],
+ "pages": [
+ {
+ "glob": ["**/index.md", "**/*.md"]
+ }
+ ],
+ "deploy": {
+ "message": "Site Update."
+ },
+ "timeZone": "Asia/Singapore"
+}
diff --git a/docs/stylesheets/main.css b/docs/stylesheets/main.css
new file mode 100644
index 00000000000..ba6f8385d2d
--- /dev/null
+++ b/docs/stylesheets/main.css
@@ -0,0 +1,170 @@
+mark {
+ background-color: #ff0;
+ border-radius: 5px;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.indented {
+ padding-left: 20px;
+}
+
+.theme-card img {
+ width: 100%;
+}
+
+/* Scrollbar */
+
+.slim-scroll::-webkit-scrollbar {
+ width: 5px;
+}
+
+.slim-scroll::-webkit-scrollbar-thumb {
+ background: #808080;
+ border-radius: 20px;
+}
+
+.slim-scroll::-webkit-scrollbar-track {
+ background: transparent;
+ border-radius: 20px;
+}
+
+.slim-scroll-blue::-webkit-scrollbar {
+ width: 5px;
+}
+
+.slim-scroll-blue::-webkit-scrollbar-thumb {
+ background: #00b0ef;
+ border-radius: 20px;
+}
+
+.slim-scroll-blue::-webkit-scrollbar-track {
+ background: transparent;
+ border-radius: 20px;
+}
+
+/* Layout containers */
+
+#flex-body {
+ display: flex;
+ flex: 1;
+ align-items: start;
+}
+
+#content-wrapper {
+ flex: 1;
+ margin: 0 auto;
+ min-width: 0;
+ max-width: 1000px;
+ overflow-x: auto;
+ padding: 0.8rem 20px 0 20px;
+ transition: 0.4s;
+ -webkit-transition: 0.4s;
+}
+
+#site-nav,
+#page-nav {
+ display: flex;
+ flex-direction: column;
+ position: sticky;
+ top: var(--sticky-header-height);
+ flex: 0 0 auto;
+ max-width: 300px;
+ max-height: calc(100vh - var(--sticky-header-height));
+ width: 300px;
+}
+
+#site-nav {
+ border-right: 1px solid lightgrey;
+ padding-bottom: 20px;
+ z-index: 999;
+}
+
+.site-nav-top {
+ margin: 0.8rem 0;
+ padding: 0 12px 12px 12px;
+}
+
+.nav-component {
+ overflow-y: auto;
+}
+
+#page-nav {
+ border-left: 1px solid lightgrey;
+}
+
+@media screen and (max-width: 1299.98px) {
+ #page-nav {
+ display: none;
+ }
+}
+
+/* Bootstrap medium(md) responsive breakpoint */
+@media screen and (max-width: 991.98px) {
+ #site-nav {
+ display: none;
+ }
+}
+
+/* Bootstrap small(sm) responsive breakpoint */
+@media (max-width: 767.98px) {
+ .indented {
+ padding-left: 10px;
+ }
+
+ #content-wrapper {
+ padding: 0 10px;
+ }
+}
+
+/* Bootstrap extra small(xs) responsive breakpoint */
+@media screen and (max-width: 575.98px) {
+ #site-nav {
+ display: none;
+ }
+}
+
+/* Hide site navigation when printing */
+@media print {
+ #site-nav {
+ display: none;
+ }
+
+ #page-nav {
+ display: none;
+ }
+
+ /* Reduce font size when printing */
+ h1 {
+ font-size: 1.2rem !important;
+ }
+ h2 {
+ font-size: 1.0rem !important;
+ }
+ h3 {
+ font-size: 0.9rem !important;
+ }
+ h4 {
+ font-size: 0.8rem !important;
+ }
+ h5 {
+ font-size: 0.7rem !important;
+ }
+ body {
+ font-size: 0.65rem !important;
+ }
+ .btn {
+ font-size: 0.65rem !important;
+ }
+ img {
+ zoom: 0.8; /* might not work on some browsers */
+ }
+}
+
+h2,
+h3,
+h4,
+h5,
+h6 {
+ color: #e46c0a;
+}
diff --git a/docs/team/angYongXiangAdwin.md b/docs/team/angYongXiangAdwin.md
new file mode 100644
index 00000000000..fa0c130e48c
--- /dev/null
+++ b/docs/team/angYongXiangAdwin.md
@@ -0,0 +1,6 @@
+
+Hi my name is Ang Yong Xiang Adwin, or you can call me Adwin for short.
+
+I am a Year 2 Computer Science Student in NUS. I came from Ngee Ann Polytechnic where I studied Information Technology Diploma.
+
+Fun Fact about myself, the two times I have been to US were both sponsored by Ngee Ann Polytechnic (got quite lucky there).
diff --git a/docs/team/dexterleng.md b/docs/team/dexterleng.md
new file mode 100644
index 00000000000..180d527a8f5
--- /dev/null
+++ b/docs/team/dexterleng.md
@@ -0,0 +1,45 @@
+---
+ layout: default.md
+ title: "Dexter Leng's Project Portfolio Page"
+---
+
+### Project: TravelHub
+
+- TravelHub is a desktop application that targets travel agents, which aim to help with the management of customer and service contacts through a collated address book. 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.
+
+## To be updated!
+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
diff --git a/docs/team/kararei.md b/docs/team/kararei.md
new file mode 100644
index 00000000000..741aaeac465
--- /dev/null
+++ b/docs/team/kararei.md
@@ -0,0 +1,45 @@
+---
+ layout: default.md
+ title: "Kara Rei's Project Portfolio Page"
+---
+
+### Project: TravelHub
+
+- TravelHub is a desktop application that targets travel agents, which aim to help with the management of customer and service contacts through a collated address book. 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.
+
+## To be updated!
+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
diff --git a/docs/team/johndoe.md b/docs/team/suspectblue.md
similarity index 87%
rename from docs/team/johndoe.md
rename to docs/team/suspectblue.md
index 773a07794e2..e14c710475e 100644
--- a/docs/team/johndoe.md
+++ b/docs/team/suspectblue.md
@@ -1,11 +1,11 @@
---
-layout: page
-title: John Doe's Project Portfolio Page
+ layout: default.md
+ title: "Jovan Yo's Project Portfolio Page"
---
-### Project: AddressBook Level 3
+### Project: TravelHub
-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.
+TravelHub is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC.
Given below are my contributions to the project.
diff --git a/docs/team/zyonwee.md b/docs/team/zyonwee.md
new file mode 100644
index 00000000000..5d9d1bbd3f7
--- /dev/null
+++ b/docs/team/zyonwee.md
@@ -0,0 +1,78 @@
+---
+ layout: default.md
+ title: "Zyon Wee's Project Portfolio Page"
+---
+
+### Project: TravelHub
+
+**PPP: TravelHub Project Contributions - Zyon Wee**
+
+**Overview:**
+
+TravelHub is a command-line application designed to efficiently manage trip and contact information, streamlining travel planning. It allows users to add, edit, delete, and find trip and contact details, ensuring organized and accessible travel data.
+
+**Summary of Contributions:**
+
+* **Code Contributed:**
+ * Link to tP Code Dashboard:
+ [link](https://nus-cs2103-ay2425s2.github.io/tp-dashboard/?search=zyonwee&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2025-02-21&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other)
+* **Enhancements Implemented:**
+ * **Delete Trip and Delete Contact Commands:** Implemented the functionality to delete trips and contacts, including robust error handling and user feedback. This involved significant logic modifications and test case implementations to ensure reliability.
+ * **Date Validation and Logging:** Enhanced the `TripDate` class by adding detailed logging for date validation, improving debugging and maintainability. Implemented assertions and strengthened validation logic to prevent erroneous data entry.
+ * **Phone Number Length Validation:** Added validation and message updates for phone number length, improving data integrity and user experience.
+ * **Refactoring and Renaming:** Performed extensive refactoring to improve code clarity and maintainability, including renaming classes, methods, and variables for consistency (e.g., renaming "Address Book" to "TravelHub", "Delete" to "Delete Contact").
+ * **Checkstyle Enforcement:** Consistently enforced Checkstyle rules to maintain code quality and consistency across the project.
+ * **Bug Fixes:** Resolved multiple bugs related to command functionality and user interface elements.
+* **Contributions to the UG:**
+ * Updated the "Delete Trip" and "Delete Contact" sections of the User Guide, providing clear instructions and examples for users.
+* **Contributions to the DG:**
+ * Added a "Glossary" section to the Developer Guide, defining key terms for better understanding of the project's architecture.
+ * Added Use Cases
+ * Use Case: See Usage Instructions
+ * Use Case: Add a Contact
+ * Use Case: Add a Trip
+ * Use Case: Delete a Contact
+ * Use Case: Delete a Trip
+ * Use Case: Mark a Trip as Ongoing
+ * Use Case: Mark a Trip as Completed
+ * Use Case: Search for Contacts and Trips
+ * Use Case: Export Customer and Service Information
+ * Use Case: Add Notes to Customer Profiles or Trips
+ * Use Case: Restrict Adding Service Contacts as Trip Members
+ * Use Case: Restrict Adding Customer Contacts as Trip Location Businesses
+ * Use Case: Sort Contacts by Name
+ * Added UML diagrams for delete functionality.
+* **Contributions to Team-Based Tasks:**
+ * Contributed to the team by merging pull requests and resolving merge conflicts.
+ * Updated code coverage.
+* **Review/Mentoring Contributions:**
+ * Reviewed pull requests, providing constructive feedback to team members.
+* **Contributions Beyond the Project Team:**
+ * Contributed to the "About Us" section, enhancing team presentation.
+
+**OPTIONAL Contributions to the Developer Guide (Extracts):**
+
+* **Glossary Section:**
+ * Included definitions for key terms such as "Trip," "Contact," and "Command" to ensure consistent understanding among developers.
+* **UML Diagrams:**
+ * Added UML activity and sequence diagrams for the delete contact and delete trip commands.
+ * Removed unnecessary termination nodes from UML diagrams.
+* **Use Case Diagrams:**
+ * Added use case diagrams for the various functions of the application.
+
+**OPTIONAL Contributions to the User Guide (Extracts):**
+
+* **Delete Trip/Contact Section:**
+ * Provided detailed steps and examples on how to use the `delete trip` and `delete contact` commands, including error handling scenarios.
+ * Example:
+ * `delete trip 1` : deletes the first trip in the list.
+ * `delete contact 2`: deletes the second contact in the list.
+
+**Explanation of Effort:**
+
+* The implementation of the `delete trip` and `delete contact` commands required careful consideration of data structures, command logic, and user feedback, ensuring that data integrity was maintained.
+* Enhancing date validation and adding logging significantly improved the robustness of the `TripDate` class, preventing potential errors and simplifying debugging.
+* Refactoring and renaming efforts improved code clarity and consistency, contributing to the overall maintainability of the project.
+* Creating clean and effective UML diagrams required a deep understanding of the code base.
+* The creation of the glossary in the Developer Guide was a important contribution to the overall understanding of the project.
+* The various checkstyle enforcement commits, while small individually, show a dedication to code quality across the entire project.
diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java
index 678ddc8c218..f98fd670b80 100644
--- a/src/main/java/seedu/address/MainApp.java
+++ b/src/main/java/seedu/address/MainApp.java
@@ -15,18 +15,20 @@
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.Logic;
import seedu.address.logic.LogicManager;
-import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTripBook;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
-import seedu.address.model.util.SampleDataUtil;
+import seedu.address.model.util.DataLoadingUtil;
import seedu.address.storage.AddressBookStorage;
import seedu.address.storage.JsonAddressBookStorage;
+import seedu.address.storage.JsonTripBookStorage;
import seedu.address.storage.JsonUserPrefsStorage;
import seedu.address.storage.Storage;
import seedu.address.storage.StorageManager;
+import seedu.address.storage.TripBookStorage;
import seedu.address.storage.UserPrefsStorage;
import seedu.address.ui.Ui;
import seedu.address.ui.UiManager;
@@ -36,7 +38,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, 3, 0, true);
private static final Logger logger = LogsCenter.getLogger(MainApp.class);
@@ -58,7 +60,8 @@ public void init() throws Exception {
UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath());
UserPrefs userPrefs = initPrefs(userPrefsStorage);
AddressBookStorage addressBookStorage = new JsonAddressBookStorage(userPrefs.getAddressBookFilePath());
- storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ TripBookStorage tripBookStorage = new JsonTripBookStorage(userPrefs.getTripBookFilePath());
+ storage = new StorageManager(addressBookStorage, userPrefsStorage, tripBookStorage);
model = initModelManager(storage, userPrefs);
@@ -73,24 +76,9 @@ public void init() throws Exception {
* or an empty address book will be used instead if errors occur when reading {@code storage}'s address book.
*/
private Model initModelManager(Storage storage, ReadOnlyUserPrefs userPrefs) {
- logger.info("Using data file : " + storage.getAddressBookFilePath());
-
- Optional addressBookOptional;
- ReadOnlyAddressBook initialData;
- try {
- addressBookOptional = storage.readAddressBook();
- if (!addressBookOptional.isPresent()) {
- logger.info("Creating a new data file " + storage.getAddressBookFilePath()
- + " populated with a sample AddressBook.");
- }
- 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();
- }
-
- return new ModelManager(initialData, userPrefs);
+ ReadOnlyAddressBook initialData = DataLoadingUtil.loadAddressBook(storage);
+ ReadOnlyTripBook initialTripData = DataLoadingUtil.loadTripBook(storage);
+ return new ModelManager(initialData, initialTripData, userPrefs);
}
private void initLogging(Config config) {
diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java
index 8cf8e15a0f0..35ade964c0c 100644
--- a/src/main/java/seedu/address/commons/core/LogsCenter.java
+++ b/src/main/java/seedu/address/commons/core/LogsCenter.java
@@ -20,7 +20,7 @@
public class LogsCenter {
private static final int MAX_FILE_COUNT = 5;
private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB
- private static final String LOG_FILE = "addressbook.log";
+ private static final String LOG_FILE = "travel_hub.log";
private static final Logger logger; // logger for this class
private static Logger baseLogger; // to be used as the parent of all other loggers created by this class.
private static Level currentLogLevel = Level.INFO;
diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java
index 61cc8c9a1cb..80f738ab60e 100644
--- a/src/main/java/seedu/address/commons/util/StringUtil.java
+++ b/src/main/java/seedu/address/commons/util/StringUtil.java
@@ -65,4 +65,20 @@ public static boolean isNonZeroUnsignedInteger(String s) {
return false;
}
}
+ /**
+ * Returns true if the given string represents a valid integer (positive, negative, or zero).
+ *
+ * @param s The string to test.
+ * @return True if {@code s} can be parsed as an integer using {@link Integer#parseInt(String)}, false otherwise.
+ * @throws NullPointerException if {@code s} is null.
+ */
+ public static boolean isInteger(String s) {
+ requireNonNull(s);
+ try {
+ int value = Integer.parseInt(s);
+ return true;
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ }
}
diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java
index 92cd8fa605a..1b2baa41477 100644
--- a/src/main/java/seedu/address/logic/Logic.java
+++ b/src/main/java/seedu/address/logic/Logic.java
@@ -8,7 +8,8 @@
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.trip.Trip;
/**
* API of the Logic component
@@ -31,7 +32,10 @@ public interface Logic {
ReadOnlyAddressBook getAddressBook();
/** Returns an unmodifiable view of the filtered list of persons */
- ObservableList getFilteredPersonList();
+ ObservableList getFilteredPersonList();
+
+ /** Returns an unmodifiable view of the filtered list of trip */
+ ObservableList getFilteredTripList();
/**
* 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..4475650ae19 100644
--- a/src/main/java/seedu/address/logic/LogicManager.java
+++ b/src/main/java/seedu/address/logic/LogicManager.java
@@ -15,7 +15,8 @@
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.trip.Trip;
import seedu.address.storage.Storage;
/**
@@ -40,6 +41,7 @@ public LogicManager(Model model, Storage storage) {
this.model = model;
this.storage = storage;
addressBookParser = new AddressBookParser();
+ logger.info("LogicManager initialized with model and storage");
}
@Override
@@ -52,9 +54,13 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
try {
storage.saveAddressBook(model.getAddressBook());
+ storage.saveTripBook(model.getTripBook());
+ logger.info("Command executed successfully: " + command.getClass().getSimpleName());
} catch (AccessDeniedException e) {
+ logger.warning("Command execution failed: " + e.getMessage());
throw new CommandException(String.format(FILE_OPS_PERMISSION_ERROR_FORMAT, e.getMessage()), e);
} catch (IOException ioe) {
+ logger.warning("Command execution failed: " + ioe.getMessage());
throw new CommandException(String.format(FILE_OPS_ERROR_FORMAT, ioe.getMessage()), ioe);
}
@@ -63,26 +69,37 @@ public CommandResult execute(String commandText) throws CommandException, ParseE
@Override
public ReadOnlyAddressBook getAddressBook() {
+ logger.fine("Retrieving address book");
return model.getAddressBook();
}
@Override
- public ObservableList getFilteredPersonList() {
+ public ObservableList getFilteredPersonList() {
+ logger.fine("Retrieving filtered person list");
return model.getFilteredPersonList();
}
+ @Override
+ public ObservableList getFilteredTripList() {
+ logger.fine("Retrieving filtered trip list");
+ return model.getFilteredTripList();
+ }
+
@Override
public Path getAddressBookFilePath() {
+ logger.fine("Retrieving address book file path");
return model.getAddressBookFilePath();
}
@Override
public GuiSettings getGuiSettings() {
+ logger.fine("Retrieving GUI settings");
return model.getGuiSettings();
}
@Override
public void setGuiSettings(GuiSettings guiSettings) {
+ logger.info("Updating GUI settings");
model.setGuiSettings(guiSettings);
}
}
diff --git a/src/main/java/seedu/address/logic/Messages.java b/src/main/java/seedu/address/logic/Messages.java
index ecd32c31b53..e85c872a093 100644
--- a/src/main/java/seedu/address/logic/Messages.java
+++ b/src/main/java/seedu/address/logic/Messages.java
@@ -5,7 +5,8 @@
import java.util.stream.Stream;
import seedu.address.logic.parser.Prefix;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.trip.Trip;
/**
* Container for user visible messages.
@@ -14,8 +15,10 @@ public class Messages {
public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command";
public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
- public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid";
- public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!";
+ public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The contact index provided is invalid";
+ public static final String MESSAGE_INVALID_TRIP_DISPLAYED_INDEX = "The trip index provided is invalid";
+ public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d person listed!";
+ public static final String MESSAGE_NO_MATCHING_NAMES_FOUND = "No matching names found!";
public static final String MESSAGE_DUPLICATE_FIELDS =
"Multiple values specified for the following single-valued field(s): ";
@@ -32,19 +35,45 @@ public static String getErrorMessageForDuplicatePrefixes(Prefix... duplicatePref
}
/**
- * Formats the {@code person} for display to the user.
+ * Formats the {@code contact} for display to the user.
*/
- public static String format(Person person) {
+ public static String format(Contact contact) {
final StringBuilder builder = new StringBuilder();
- builder.append(person.getName())
+ builder.append(contact.getName())
.append("; Phone: ")
- .append(person.getPhone())
+ .append(contact.getPhone())
.append("; Email: ")
- .append(person.getEmail())
+ .append(contact.getEmail())
.append("; Address: ")
- .append(person.getAddress())
- .append("; Tags: ");
- person.getTags().forEach(builder::append);
+ .append(contact.getAddress());
+ if (!contact.getTags().isEmpty()) {
+ builder.append("; Tags: ");
+ contact.getTags().forEach(builder::append);
+ }
+ if (!contact.getNote().isEmpty()) {
+ builder.append("; Note: ")
+ .append(contact.getNote());
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Formats the {@code trip} for display to the user.
+ */
+ public static String format(Trip trip) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(trip.getName())
+ .append("; Accommodation: ")
+ .append(trip.getAccommodation())
+ .append("; Itinerary: ")
+ .append(trip.getItinerary())
+ .append("; Date: ")
+ .append(trip.getDate())
+ .append("; Customers: ");
+ builder.append(trip.getCustomerNames().stream().map(customer -> customer.fullName)
+ .collect(Collectors.joining(", ")))
+ .append("; Note: ")
+ .append(trip.getNote());
return builder.toString();
}
diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddContactCommand.java
similarity index 59%
rename from src/main/java/seedu/address/logic/commands/AddCommand.java
rename to src/main/java/seedu/address/logic/commands/AddContactCommand.java
index 5d7185a9680..8e2b25c80b1 100644
--- a/src/main/java/seedu/address/logic/commands/AddCommand.java
+++ b/src/main/java/seedu/address/logic/commands/AddContactCommand.java
@@ -4,6 +4,7 @@
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_NOTE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
@@ -11,48 +12,51 @@
import seedu.address.logic.Messages;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
/**
- * Adds a person to the address book.
+ * Adds a contact to the address book.
*/
-public class AddCommand extends Command {
+public class AddContactCommand extends Command {
- public static final String COMMAND_WORD = "add";
+ public static final String COMMAND_WORD = "addContact";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a contact to the address book. "
+ "Parameters: "
+ PREFIX_NAME + "NAME "
+ PREFIX_PHONE + "PHONE "
+ PREFIX_EMAIL + "EMAIL "
+ PREFIX_ADDRESS + "ADDRESS "
- + "[" + PREFIX_TAG + "TAG]...\n"
+ + "[" + PREFIX_TAG + "TAG] (Must be 'customer' or 'service', or both) "
+ + "[" + PREFIX_NOTE + "NOTE]\n"
+ "Example: " + COMMAND_WORD + " "
+ PREFIX_NAME + "John Doe "
+ PREFIX_PHONE + "98765432 "
+ PREFIX_EMAIL + "johnd@example.com "
+ PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 "
- + PREFIX_TAG + "friends "
- + PREFIX_TAG + "owesMoney";
+ + PREFIX_TAG + "customer "
+ + PREFIX_TAG + "service "
+ + PREFIX_NOTE + "Preferred contact method is email";
- 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_SUCCESS = "New contact added: %1$s";
+ public static final String MESSAGE_DUPLICATE_PERSON = "A contact using this email address "
+ + "already exists in the address book.";
- private final Person toAdd;
+ private final Contact toAdd;
/**
- * Creates an AddCommand to add the specified {@code Person}
+ * Creates an AddContactCommand to add the specified {@code Contact}
*/
- public AddCommand(Person person) {
- requireNonNull(person);
- toAdd = person;
+ public AddContactCommand(Contact contact) {
+ requireNonNull(contact);
+ toAdd = contact;
}
@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
- if (model.hasPerson(toAdd)) {
+ if (model.hasContact(toAdd)) {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
@@ -67,12 +71,12 @@ public boolean equals(Object other) {
}
// instanceof handles nulls
- if (!(other instanceof AddCommand)) {
+ if (!(other instanceof AddContactCommand)) {
return false;
}
- AddCommand otherAddCommand = (AddCommand) other;
- return toAdd.equals(otherAddCommand.toAdd);
+ AddContactCommand otherAddContactCommand = (AddContactCommand) other;
+ return toAdd.equals(otherAddContactCommand.toAdd);
}
@Override
diff --git a/src/main/java/seedu/address/logic/commands/AddTripCommand.java b/src/main/java/seedu/address/logic/commands/AddTripCommand.java
new file mode 100644
index 00000000000..e406c778c93
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/AddTripCommand.java
@@ -0,0 +1,88 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ACCOMMODATION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ITINERARY;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
+
+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.trip.Trip;
+
+/**
+ * Adds a trip to the address book.
+ */
+public class AddTripCommand extends Command {
+
+ public static final String COMMAND_WORD = "addTrip";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a trip to the trip book. "
+ + "Parameters: "
+ + PREFIX_NAME + "NAME "
+ + PREFIX_ACCOMMODATION + "ACCOMMODATION "
+ + PREFIX_ITINERARY + "ITINERARY "
+ + PREFIX_DATE + "DATE "
+ + "[" + PREFIX_CUSTOMER_NAME + "CUSTOMER_NAME] "
+ + "[" + PREFIX_NOTE + "NOTE]\n"
+ + "Example: " + COMMAND_WORD + " "
+ + PREFIX_NAME + "Paris 2025 "
+ + PREFIX_ACCOMMODATION + "Hotel Sunshine "
+ + PREFIX_ITINERARY + "Visit Eiffel Tower; Eat baguette. "
+ + PREFIX_DATE + "01/1/2025 "
+ + PREFIX_CUSTOMER_NAME + "Jane Doe "
+ + PREFIX_CUSTOMER_NAME + "John Doe "
+ + PREFIX_NOTE + "Customer prefers window seat";
+
+ public static final String MESSAGE_SUCCESS = "New trip added: %1$s";
+ public static final String MESSAGE_DUPLICATE_TRIP = "This trip already exists in the trip book "
+ + "(name has to be unique and it is case-insensitive)";
+
+ private final Trip toAdd;
+
+ /**
+ * Creates an AddTripCommand to add the specified {@code Trip}
+ */
+ public AddTripCommand(Trip trip) {
+ requireNonNull(trip);
+ toAdd = trip;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (model.hasTrip(toAdd)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TRIP);
+ }
+
+ model.addTrip(toAdd);
+ return new CommandResult(String.format(MESSAGE_SUCCESS, Messages.format(toAdd)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof AddTripCommand)) {
+ return false;
+ }
+
+ AddTripCommand otherAddCommand = (AddTripCommand) other;
+ return toAdd.equals(otherAddCommand.toAdd);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("toAdd", toAdd)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java
index 9c86b1fa6e4..7ccf8ab511c 100644
--- a/src/main/java/seedu/address/logic/commands/ClearCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java
@@ -4,20 +4,36 @@
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
+import seedu.address.model.TripBook;
/**
- * Clears the address book.
+ * Clears the address book and trip book.
*/
public class ClearCommand extends Command {
public static final String COMMAND_WORD = "clear";
- public static final String MESSAGE_SUCCESS = "Address book has been cleared!";
+ public static final String MESSAGE_SUCCESS = "Address book and Trip book has been cleared!";
+ public static final String MESSAGE_CONFIRMATION = "Are you sure you want to clear all contacts and trips?";
+ private final boolean isConfirmed;
+ public ClearCommand() {
+ this.isConfirmed = false;
+ }
+
+ public ClearCommand(boolean isConfirmed) {
+ this.isConfirmed = isConfirmed;
+ }
@Override
public CommandResult execute(Model model) {
requireNonNull(model);
+
+ if (!isConfirmed) {
+ return new CommandResult("", true, MESSAGE_CONFIRMATION);
+ }
+
model.setAddressBook(new AddressBook());
+ model.setTripBook(new TripBook());
return new CommandResult(MESSAGE_SUCCESS);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/address/logic/commands/CommandResult.java
index 249b6072d0d..c34281f91d0 100644
--- a/src/main/java/seedu/address/logic/commands/CommandResult.java
+++ b/src/main/java/seedu/address/logic/commands/CommandResult.java
@@ -19,13 +19,22 @@ public class CommandResult {
/** The application should exit. */
private final boolean exit;
+ /** A confirmation dialog should be shown to the user. */
+ private final boolean showConfirmation;
+
+ /** The text to display in the confirmation dialog. */
+ private final String confirmationText;
+
/**
* Constructs a {@code CommandResult} with the specified fields.
*/
- public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
+ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit,
+ boolean showConfirmation, String confirmationText) {
this.feedbackToUser = requireNonNull(feedbackToUser);
this.showHelp = showHelp;
this.exit = exit;
+ this.showConfirmation = showConfirmation;
+ this.confirmationText = confirmationText;
}
/**
@@ -33,7 +42,14 @@ public CommandResult(String feedbackToUser, boolean showHelp, boolean exit) {
* and other fields set to their default value.
*/
public CommandResult(String feedbackToUser) {
- this(feedbackToUser, false, false);
+ this(feedbackToUser, false, false, false, null);
+ }
+
+ /**
+ * Constructs a {@code CommandResult} with the specified fields for confirmation.
+ */
+ public CommandResult(String feedbackToUser, boolean showConfirmation, String confirmationText) {
+ this(feedbackToUser, false, false, showConfirmation, confirmationText);
}
public String getFeedbackToUser() {
@@ -48,6 +64,14 @@ public boolean isExit() {
return exit;
}
+ public boolean isShowConfirmation() {
+ return showConfirmation;
+ }
+
+ public String getConfirmationText() {
+ return confirmationText;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -62,12 +86,14 @@ public boolean equals(Object other) {
CommandResult otherCommandResult = (CommandResult) other;
return feedbackToUser.equals(otherCommandResult.feedbackToUser)
&& showHelp == otherCommandResult.showHelp
- && exit == otherCommandResult.exit;
+ && exit == otherCommandResult.exit
+ && showConfirmation == otherCommandResult.showConfirmation
+ && Objects.equals(confirmationText, otherCommandResult.confirmationText);
}
@Override
public int hashCode() {
- return Objects.hash(feedbackToUser, showHelp, exit);
+ return Objects.hash(feedbackToUser, showHelp, exit, showConfirmation, confirmationText);
}
@Override
@@ -76,6 +102,8 @@ public String toString() {
.add("feedbackToUser", feedbackToUser)
.add("showHelp", showHelp)
.add("exit", exit)
+ .add("showConfirmation", showConfirmation)
+ .add("confirmationText", confirmationText)
.toString();
}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteContactCommand.java b/src/main/java/seedu/address/logic/commands/DeleteContactCommand.java
new file mode 100644
index 00000000000..4247b2f0be6
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/DeleteContactCommand.java
@@ -0,0 +1,69 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.contact.Contact;
+
+/**
+ * Deletes a contact identified using it's displayed index from the address book.
+ */
+public class DeleteContactCommand extends Command {
+
+ public static final String COMMAND_WORD = "deleteContact";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Deletes the contact identified by the index number used in the displayed contact list.\n"
+ + "Parameters: INDEX (must be a positive integer)\n"
+ + "Example: " + COMMAND_WORD + " 1";
+
+ public static final String MESSAGE_DELETE_CONTACT_SUCCESS = "Deleted Contact: %1$s";
+
+ private final Index targetIndex;
+
+ public DeleteContactCommand(Index targetIndex) {
+ this.targetIndex = targetIndex;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredPersonList();
+
+ if (targetIndex.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ Contact contactToDelete = lastShownList.get(targetIndex.getZeroBased());
+ model.deleteContact(contactToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_CONTACT_SUCCESS, Messages.format(contactToDelete)));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof DeleteContactCommand)) {
+ return false;
+ }
+
+ DeleteContactCommand otherDeleteCommand = (DeleteContactCommand) other;
+ return targetIndex.equals(otherDeleteCommand.targetIndex);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("targetIndex", targetIndex)
+ .toString();
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteTripCommand.java
similarity index 59%
rename from src/main/java/seedu/address/logic/commands/DeleteCommand.java
rename to src/main/java/seedu/address/logic/commands/DeleteTripCommand.java
index 1135ac19b74..05e4d0f439c 100644
--- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java
+++ b/src/main/java/seedu/address/logic/commands/DeleteTripCommand.java
@@ -9,40 +9,40 @@
import seedu.address.logic.Messages;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
-import seedu.address.model.person.Person;
+import seedu.address.model.trip.Trip;
/**
- * Deletes a person identified using it's displayed index from the address book.
+ * Deletes a trip identified using it's displayed index from the trip book.
*/
-public class DeleteCommand extends Command {
+public class DeleteTripCommand extends Command {
- public static final String COMMAND_WORD = "delete";
+ public static final String COMMAND_WORD = "deleteTrip";
public static final String MESSAGE_USAGE = COMMAND_WORD
- + ": Deletes the person identified by the index number used in the displayed person list.\n"
+ + ": Deletes the trip identified by the index number used in the displayed trip 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";
+ public static final String MESSAGE_DELETE_TRIP_SUCCESS = "Deleted Trip: %1$s";
private final Index targetIndex;
- public DeleteCommand(Index targetIndex) {
+ public DeleteTripCommand(Index targetIndex) {
this.targetIndex = targetIndex;
}
@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
+ List lastShownList = model.getFilteredTripList();
if (targetIndex.getZeroBased() >= lastShownList.size()) {
- throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ throw new CommandException(Messages.MESSAGE_INVALID_TRIP_DISPLAYED_INDEX);
}
- Person personToDelete = lastShownList.get(targetIndex.getZeroBased());
- model.deletePerson(personToDelete);
- return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, Messages.format(personToDelete)));
+ Trip tripToDelete = lastShownList.get(targetIndex.getZeroBased());
+ model.deleteTrip(tripToDelete);
+ return new CommandResult(String.format(MESSAGE_DELETE_TRIP_SUCCESS, Messages.format(tripToDelete)));
}
@Override
@@ -52,11 +52,11 @@ public boolean equals(Object other) {
}
// instanceof handles nulls
- if (!(other instanceof DeleteCommand)) {
+ if (!(other instanceof DeleteTripCommand)) {
return false;
}
- DeleteCommand otherDeleteCommand = (DeleteCommand) other;
+ DeleteTripCommand otherDeleteCommand = (DeleteTripCommand) other;
return targetIndex.equals(otherDeleteCommand.targetIndex);
}
diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditContactCommand.java
similarity index 69%
rename from src/main/java/seedu/address/logic/commands/EditCommand.java
rename to src/main/java/seedu/address/logic/commands/EditContactCommand.java
index 4b581c7331e..e180bc1e4a2 100644
--- a/src/main/java/seedu/address/logic/commands/EditCommand.java
+++ b/src/main/java/seedu/address/logic/commands/EditContactCommand.java
@@ -4,6 +4,7 @@
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_NOTE;
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;
@@ -21,45 +22,47 @@
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.contact.Address;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Name;
+import seedu.address.model.contact.Note;
+import seedu.address.model.contact.Phone;
import seedu.address.model.tag.Tag;
/**
- * Edits the details of an existing person in the address book.
+ * Edits the details of an existing contact in the address book.
*/
-public class EditCommand extends Command {
+public class EditContactCommand extends Command {
- public static final String COMMAND_WORD = "edit";
+ public static final String COMMAND_WORD = "editContact";
- 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. "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the contact identified "
+ + "by the index number used in the displayed contact 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"
+ + "[" + PREFIX_TAG + "TAG] (Must be 'customer' or 'service', or both) "
+ + "[" + PREFIX_NOTE + "NOTE]\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_EDIT_PERSON_SUCCESS = "Edited Contact: %1$s";
public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
- public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book.";
+ public static final String MESSAGE_DUPLICATE_PERSON = "This contact 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
+ * @param index of the contact in the filtered contact list to edit
+ * @param editPersonDescriptor details to edit the contact with
*/
- public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
+ public EditContactCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
requireNonNull(index);
requireNonNull(editPersonDescriptor);
@@ -70,38 +73,39 @@ public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) {
@Override
public CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
- List lastShownList = model.getFilteredPersonList();
+ 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);
+ Contact contactToEdit = lastShownList.get(index.getZeroBased());
+ Contact editedContact = createEditedPerson(contactToEdit, editPersonDescriptor);
- if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) {
+ if (!contactToEdit.isSamePerson(editedContact) && model.hasContact(editedContact)) {
throw new CommandException(MESSAGE_DUPLICATE_PERSON);
}
- model.setPerson(personToEdit, editedPerson);
+ model.setPerson(contactToEdit, editedContact);
model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson)));
+ return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedContact)));
}
/**
- * Creates and returns a {@code Person} with the details of {@code personToEdit}
+ * Creates and returns a {@code Contact} with the details of {@code contactToEdit}
* edited with {@code editPersonDescriptor}.
*/
- private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) {
- assert personToEdit != null;
+ private static Contact createEditedPerson(Contact contactToEdit, EditPersonDescriptor editPersonDescriptor) {
+ assert contactToEdit != 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());
+ Name updatedName = editPersonDescriptor.getName().orElse(contactToEdit.getName());
+ Phone updatedPhone = editPersonDescriptor.getPhone().orElse(contactToEdit.getPhone());
+ Email updatedEmail = editPersonDescriptor.getEmail().orElse(contactToEdit.getEmail());
+ Address updatedAddress = editPersonDescriptor.getAddress().orElse(contactToEdit.getAddress());
+ Set updatedTags = editPersonDescriptor.getTags().orElse(contactToEdit.getTags());
+ Note updatedNote = editPersonDescriptor.getNote().orElse(contactToEdit.getNote());
- return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags);
+ return new Contact(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags, updatedNote);
}
@Override
@@ -111,13 +115,13 @@ public boolean equals(Object other) {
}
// instanceof handles nulls
- if (!(other instanceof EditCommand)) {
+ if (!(other instanceof EditContactCommand)) {
return false;
}
- EditCommand otherEditCommand = (EditCommand) other;
- return index.equals(otherEditCommand.index)
- && editPersonDescriptor.equals(otherEditCommand.editPersonDescriptor);
+ EditContactCommand otherEditContactCommand = (EditContactCommand) other;
+ return index.equals(otherEditContactCommand.index)
+ && editPersonDescriptor.equals(otherEditContactCommand.editPersonDescriptor);
}
@Override
@@ -129,8 +133,8 @@ public String toString() {
}
/**
- * Stores the details to edit the person with. Each non-empty field value will replace the
- * corresponding field value of the person.
+ * Stores the details to edit the contact with. Each non-empty field value will replace the
+ * corresponding field value of the contact.
*/
public static class EditPersonDescriptor {
private Name name;
@@ -138,6 +142,7 @@ public static class EditPersonDescriptor {
private Email email;
private Address address;
private Set tags;
+ private Note note;
public EditPersonDescriptor() {}
@@ -151,13 +156,14 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) {
setEmail(toCopy.email);
setAddress(toCopy.address);
setTags(toCopy.tags);
+ setNote(toCopy.note);
}
/**
* Returns true if at least one field is edited.
*/
public boolean isAnyFieldEdited() {
- return CollectionUtil.isAnyNonNull(name, phone, email, address, tags);
+ return CollectionUtil.isAnyNonNull(name, phone, email, address, tags, note);
}
public void setName(Name name) {
@@ -209,6 +215,14 @@ public Optional> getTags() {
return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty();
}
+ public void setNote(Note note) {
+ this.note = note;
+ }
+
+ public Optional getNote() {
+ return Optional.ofNullable(note);
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -225,7 +239,8 @@ public boolean equals(Object other) {
&& Objects.equals(phone, otherEditPersonDescriptor.phone)
&& Objects.equals(email, otherEditPersonDescriptor.email)
&& Objects.equals(address, otherEditPersonDescriptor.address)
- && Objects.equals(tags, otherEditPersonDescriptor.tags);
+ && Objects.equals(tags, otherEditPersonDescriptor.tags)
+ && Objects.equals(note, otherEditPersonDescriptor.note);
}
@Override
@@ -236,6 +251,7 @@ public String toString() {
.add("email", email)
.add("address", address)
.add("tags", tags)
+ .add("note", note)
.toString();
}
}
diff --git a/src/main/java/seedu/address/logic/commands/EditTripCommand.java b/src/main/java/seedu/address/logic/commands/EditTripCommand.java
new file mode 100644
index 00000000000..53834f36ad2
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/EditTripCommand.java
@@ -0,0 +1,265 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ACCOMMODATION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ITINERARY;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TRIPS;
+
+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.contact.Name;
+import seedu.address.model.trip.Accommodation;
+import seedu.address.model.trip.Itinerary;
+import seedu.address.model.trip.Note;
+import seedu.address.model.trip.Trip;
+import seedu.address.model.trip.TripDate;
+import seedu.address.model.trip.TripName;
+
+/**
+ * Edits the details of an existing trip in the address book.
+ */
+public class EditTripCommand extends Command {
+
+ public static final String COMMAND_WORD = "editTrip";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the trip identified "
+ + "by the index number used in the displayed trip list. "
+ + "Existing values will be overwritten by the input values.\n"
+ + "Parameters: INDEX (must be a positive integer) "
+ + PREFIX_NAME + "NAME "
+ + PREFIX_ACCOMMODATION + "ACCOMMODATION "
+ + PREFIX_ITINERARY + "ITINERARY "
+ + PREFIX_DATE + "DATE "
+ + PREFIX_CUSTOMER_NAME + "CUSTOMER_NAME (>= 1) "
+ + "[" + PREFIX_NOTE + "NOTE]\n"
+ + "Example: " + COMMAND_WORD + " 1 "
+ + PREFIX_NAME + "Paris 2025 "
+ + PREFIX_ACCOMMODATION + "Hotel Sunshine "
+ + PREFIX_ITINERARY + "Visit Eiffel Tower; Eat baguette. "
+ + PREFIX_DATE + "01/1/2025 "
+ + PREFIX_CUSTOMER_NAME + "Jane Doe "
+ + PREFIX_CUSTOMER_NAME + "John Doe "
+ + PREFIX_NOTE + "Customer prefers window seat";
+
+ public static final String MESSAGE_EDIT_TRIP_SUCCESS = "Edited Trip: %1$s";
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+ public static final String MESSAGE_DUPLICATE_TRIP = "This trip already exists in the address book.";
+
+ private final Index index;
+ private final EditTripDescriptor editTripDescriptor;
+
+ /**
+ * @param index of the trip in the filtered trip list to edit
+ * @param editTripDescriptor details to edit the trip with
+ */
+ public EditTripCommand(Index index, EditTripDescriptor editTripDescriptor) {
+ requireNonNull(index);
+ requireNonNull(editTripDescriptor);
+
+ this.index = index;
+ this.editTripDescriptor = new EditTripDescriptor(editTripDescriptor);
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+ List lastShownList = model.getFilteredTripList();
+
+ if (index.getZeroBased() >= lastShownList.size()) {
+ throw new CommandException(Messages.MESSAGE_INVALID_TRIP_DISPLAYED_INDEX);
+ }
+
+ Trip tripToEdit = lastShownList.get(index.getZeroBased());
+ Trip editedTrip = createEditedTrip(tripToEdit, editTripDescriptor);
+
+ if (!tripToEdit.isSameTrip(editedTrip) && model.hasTrip(editedTrip)) {
+ throw new CommandException(MESSAGE_DUPLICATE_TRIP);
+ }
+
+ model.setTrip(tripToEdit, editedTrip);
+ model.updateFilteredTripList(PREDICATE_SHOW_ALL_TRIPS);
+ return new CommandResult(String.format(MESSAGE_EDIT_TRIP_SUCCESS, Messages.format(editedTrip)));
+ }
+
+ /**
+ * Creates and returns a {@code Trip} with the details of {@code tripToEdit}
+ * edited with {@code editTripDescriptor}.
+ */
+ private static Trip createEditedTrip(Trip tripToEdit, EditTripDescriptor editTripDescriptor) {
+ assert tripToEdit != null;
+
+ TripName updatedName = editTripDescriptor.getName().orElse(tripToEdit.getName());
+ Accommodation updatedAccommodation = editTripDescriptor.getAccommodation()
+ .orElse(tripToEdit.getAccommodation());
+ Itinerary updatedItinerary = editTripDescriptor.getItinerary().orElse(tripToEdit.getItinerary());
+ TripDate updatedDate = editTripDescriptor.getDate().orElse(tripToEdit.getDate());
+ Set updatedCustomerNames = editTripDescriptor.getCustomerNames().orElse(tripToEdit.getCustomerNames());
+ Note updatedNote = editTripDescriptor.getNote().orElse(tripToEdit.getNote());
+
+ return new Trip(updatedName, updatedAccommodation,
+ updatedItinerary, updatedDate, updatedCustomerNames, updatedNote);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditTripCommand)) {
+ return false;
+ }
+
+ EditTripCommand otherEditCommand = (EditTripCommand) other;
+ return index.equals(otherEditCommand.index)
+ && editTripDescriptor.equals(otherEditCommand.editTripDescriptor);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("index", index)
+ .add("editTripDescriptor", editTripDescriptor)
+ .toString();
+ }
+
+ /**
+ * Stores the details to edit the trip with. Each non-empty field value will replace the
+ * corresponding field value of the trip.
+ */
+ public static class EditTripDescriptor {
+ private TripName name;
+ private Accommodation accommodation;
+ private Itinerary itinerary;
+ private TripDate date;
+ private Set customerNames;
+ private Note note;
+
+ public EditTripDescriptor() {}
+
+ /**
+ * Copy constructor.
+ * A defensive copy of {@code customerNames} is used internally.
+ */
+ public EditTripDescriptor(EditTripDescriptor toCopy) {
+ setName(toCopy.name);
+ setAccommodation(toCopy.accommodation);
+ setItinerary(toCopy.itinerary);
+ setDate(toCopy.date);
+ setCustomerNames(toCopy.customerNames);
+ setNote(toCopy.note);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(name, accommodation, itinerary, date, customerNames, note);
+ }
+
+ public void setName(TripName name) {
+ this.name = name;
+ }
+
+ public Optional getName() {
+ return Optional.ofNullable(name);
+ }
+
+ public void setAccommodation(Accommodation accommodation) {
+ this.accommodation = accommodation;
+ }
+
+ public Optional getAccommodation() {
+ return Optional.ofNullable(accommodation);
+ }
+
+ public void setItinerary(Itinerary itinerary) {
+ this.itinerary = itinerary;
+ }
+
+ public Optional getItinerary() {
+ return Optional.ofNullable(itinerary);
+ }
+
+ public void setDate(TripDate date) {
+ this.date = date;
+ }
+
+ public Optional getDate() {
+ return Optional.ofNullable(date);
+ }
+
+ /**
+ * Sets {@code customerNames} to this object's {@code customerNames}.
+ * A defensive copy of {@code customerNames} is used internally.
+ */
+ public void setCustomerNames(Set customerNames) {
+ this.customerNames = (customerNames != null) ? new HashSet<>(customerNames) : null;
+ }
+
+ /**
+ * Returns an unmodifiable name set, which throws {@code UnsupportedOperationException}
+ * if modification is attempted.
+ * Returns {@code Optional#empty()} if {@code customerNames} is null.
+ */
+ public Optional> getCustomerNames() {
+ return (customerNames != null) ? Optional.of(Collections.unmodifiableSet(customerNames)) : Optional.empty();
+ }
+
+ public void setNote(Note note) {
+ this.note = note;
+ }
+
+ public Optional getNote() {
+ return Optional.ofNullable(note);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof EditTripDescriptor)) {
+ return false;
+ }
+
+ EditTripDescriptor otherEditTripDescriptor = (EditTripDescriptor) other;
+ return Objects.equals(name, otherEditTripDescriptor.name)
+ && Objects.equals(accommodation, otherEditTripDescriptor.accommodation)
+ && Objects.equals(itinerary, otherEditTripDescriptor.itinerary)
+ && Objects.equals(date, otherEditTripDescriptor.date)
+ && Objects.equals(customerNames, otherEditTripDescriptor.customerNames)
+ && Objects.equals(note, otherEditTripDescriptor.note);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("name", name)
+ .add("accommodation", accommodation)
+ .add("itinerary", itinerary)
+ .add("date", date)
+ .add("customerNames", customerNames)
+ .add("note", note)
+ .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..d755ce8d850 100644
--- a/src/main/java/seedu/address/logic/commands/ExitCommand.java
+++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java
@@ -13,7 +13,7 @@ public class ExitCommand extends Command {
@Override
public CommandResult execute(Model model) {
- return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, false, null);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java
index 72b9eddd3a7..d07f7014f19 100644
--- a/src/main/java/seedu/address/logic/commands/FindCommand.java
+++ b/src/main/java/seedu/address/logic/commands/FindCommand.java
@@ -5,20 +5,19 @@
import seedu.address.commons.util.ToStringBuilder;
import seedu.address.logic.Messages;
import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.contact.NameContainsKeywordsPredicate;
/**
- * Finds and lists all persons in address book whose name contains any of the argument keywords.
+ * Finds and lists all contacts in address book whose name contains any of the argument keywords.
* Keyword matching is case insensitive.
*/
public class FindCommand extends Command {
public static final String COMMAND_WORD = "find";
- public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of "
+ public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all contacts 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";
+ + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + "Example: " + COMMAND_WORD + " alice bob charlie";
private final NameContainsKeywordsPredicate predicate;
@@ -26,16 +25,17 @@ public FindCommand(NameContainsKeywordsPredicate predicate) {
this.predicate = predicate;
}
- @Override
- public CommandResult execute(Model model) {
+ @Override public CommandResult execute(Model model) {
requireNonNull(model);
model.updateFilteredPersonList(predicate);
- return new CommandResult(
- String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size()));
+
+ String message = model.getFilteredPersonList().size() > 0
+ ? String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())
+ : Messages.MESSAGE_NO_MATCHING_NAMES_FOUND;
+ return new CommandResult(message);
}
- @Override
- public boolean equals(Object other) {
+ @Override public boolean equals(Object other) {
if (other == this) {
return true;
}
@@ -49,10 +49,7 @@ public boolean equals(Object other) {
return predicate.equals(otherFindCommand.predicate);
}
- @Override
- public String toString() {
- return new ToStringBuilder(this)
- .add("predicate", predicate)
- .toString();
+ @Override public String toString() {
+ return new ToStringBuilder(this).add("predicate", predicate).toString();
}
}
diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java
index bf824f91bd0..9b3b3e0af35 100644
--- a/src/main/java/seedu/address/logic/commands/HelpCommand.java
+++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java
@@ -12,10 +12,23 @@ public class HelpCommand extends Command {
public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n"
+ "Example: " + COMMAND_WORD;
- public static final String SHOWING_HELP_MESSAGE = "Opened help window.";
+ public static final String SHOWING_HELP_MESSAGE = "Available commands:\n"
+ + "- addContact: Adds a new contact\n"
+ + "- addTrip: Adds a new trip with name, accommodation,"
+ + " itinerary, date, optional customer names and optional note\n"
+ + "- clear: Clear all contacts and trips\n"
+ + "- deleteContact: Removes a contact at a specified index\n"
+ + "- deleteTrip: Removes a trip at a specified index\n"
+ + "- editContact: Edits a contact at a specified index\n"
+ + "- editTrip: Edits a trip at a specified index\n"
+ + "- exit: Exits the program\n"
+ + "- find: Find contacts whose names contain any of the given keywords \n"
+ + "- help: Shows program usage instructions\n"
+ + "- listContact: Lists all contacts [can specify tag type]\n"
+ + "- listTrip: Lists all trips ";
@Override
public CommandResult execute(Model model) {
- return new CommandResult(SHOWING_HELP_MESSAGE, true, false);
+ return new CommandResult(SHOWING_HELP_MESSAGE, false, false, false, null);
}
}
diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java
deleted file mode 100644
index 84be6ad2596..00000000000
--- a/src/main/java/seedu/address/logic/commands/ListCommand.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package seedu.address.logic.commands;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
-
-import seedu.address.model.Model;
-
-/**
- * Lists all persons in the address book to the user.
- */
-public class ListCommand extends Command {
-
- public static final String COMMAND_WORD = "list";
-
- public static final String MESSAGE_SUCCESS = "Listed all persons";
-
-
- @Override
- public CommandResult execute(Model model) {
- requireNonNull(model);
- model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
- return new CommandResult(MESSAGE_SUCCESS);
- }
-}
diff --git a/src/main/java/seedu/address/logic/commands/ListContactCommand.java b/src/main/java/seedu/address/logic/commands/ListContactCommand.java
new file mode 100644
index 00000000000..4c4c66a3072
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListContactCommand.java
@@ -0,0 +1,75 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CUSTOMER;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_SERVICE;
+
+import seedu.address.model.Model;
+
+/**
+ * Lists all contacts in the address book to the user.
+ */
+public class ListContactCommand extends Command {
+
+ public static final String COMMAND_WORD = "listContact";
+
+ public static final String MESSAGE_USAGE = COMMAND_WORD
+ + ": Lists all the contacts or contacts that have a specific tag.\n"
+ + "Parameters: TAGNAME (optional)\n"
+ + "Examples: " + COMMAND_WORD + " OR " + COMMAND_WORD + " customer" + " OR " + COMMAND_WORD + " service";
+
+ public static final String MESSAGE_SUCCESS = "Listed all %scontacts.";
+
+ private final String tagName;
+
+ /**
+ * Creates a ListContactCommand to list contacts based on the specified tag.
+ * If no tag is specified, all contacts will be listed.
+ *
+ * @param tagname The tag name to filter contacts by. Can be empty, "customer", or "service".
+ * @throws AssertionError if tagname is null.
+ */
+ public ListContactCommand(String tagname) {
+ assert tagname != null : "Tag name cannot be null";
+ this.tagName = tagname;
+ }
+
+ @Override
+ public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ if (tagName.equals("customer")) {
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_CUSTOMER);
+ assert model.getFilteredPersonList().stream().allMatch(contact -> contact.isCustomer());
+ } else if (tagName.equals("service")) {
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_SERVICE);
+ assert model.getFilteredPersonList().stream().allMatch(contact -> contact.isService());
+ } else {
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ assert model.getFilteredPersonList().size() == model.getAddressBook().getPersonList().size();
+ }
+
+ if (model.getFilteredPersonList().isEmpty()) {
+ return new CommandResult(String.format("There are currently no %scontacts in the addressbook.", (
+ tagName.equals("") ? "" : tagName + " ")));
+ }
+ return new CommandResult(String.format(MESSAGE_SUCCESS, (tagName.equals("") ? "" : tagName + " ")));
+
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ListContactCommand)) {
+ return false;
+ }
+
+ ListContactCommand otherListContactCommand = (ListContactCommand) other;
+ return tagName.equals(otherListContactCommand.tagName);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/ListTripCommand.java b/src/main/java/seedu/address/logic/commands/ListTripCommand.java
new file mode 100644
index 00000000000..abb72676589
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/ListTripCommand.java
@@ -0,0 +1,69 @@
+package seedu.address.logic.commands;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_TRIPS;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.function.Predicate;
+
+import seedu.address.model.Model;
+import seedu.address.model.trip.Trip;
+import seedu.address.model.trip.TripDate;
+
+/**
+ * Lists all trips in the address book to the user.
+ */
+public class ListTripCommand extends Command {
+
+ public static final String COMMAND_WORD = "listTrip";
+
+ public static final String MESSAGE_USAGE =
+ COMMAND_WORD + ": Lists all the trips\n" + "Parameters: [DATE in d/M/yyyy] (optional)\n" + "Examples: "
+ + COMMAND_WORD + " OR \n" + COMMAND_WORD + " 1/1/2025";
+
+ private final LocalDate date;
+
+ public ListTripCommand() {
+ this.date = null;
+ }
+
+ public ListTripCommand(LocalDate date) {
+ this.date = date;
+ }
+
+ @Override public CommandResult execute(Model model) {
+ requireNonNull(model);
+
+ Predicate predicate;
+ if (date == null) {
+ predicate = PREDICATE_SHOW_ALL_TRIPS;
+ } else {
+ predicate = trip -> trip.getDate().date.equals(date);
+ }
+
+ model.updateFilteredTripList(predicate);
+ List trips = model.getFilteredTripList();
+
+ String message = (trips.isEmpty()) ? "No trips found. Use the addTrip command to create a new trip."
+ : (date == null) ? "All trips are listed." : "Listed trips on " + date.format(TripDate.DATE_FORMATTER);
+
+ return new CommandResult(message);
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof ListTripCommand)) {
+ return false;
+ }
+
+ ListTripCommand otherListTripCommand = (ListTripCommand) other;
+ return (date == null && otherListTripCommand.date == null) || (date != null && date.equals(
+ otherListTripCommand.date));
+ }
+}
+
diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddContactCommandParser.java
similarity index 66%
rename from src/main/java/seedu/address/logic/parser/AddCommandParser.java
rename to src/main/java/seedu/address/logic/parser/AddContactCommandParser.java
index 4ff1a97ed77..73de427e608 100644
--- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddContactCommandParser.java
@@ -4,38 +4,41 @@
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_NOTE;
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.commands.AddContactCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
+import seedu.address.model.contact.Address;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Name;
+import seedu.address.model.contact.Note;
+import seedu.address.model.contact.Phone;
import seedu.address.model.tag.Tag;
/**
- * Parses input arguments and creates a new AddCommand object
+ * Parses input arguments and creates a new AddContactCommand object
*/
-public class AddCommandParser implements Parser {
+public class AddContactCommandParser implements Parser {
/**
- * Parses the given {@code String} of arguments in the context of the AddCommand
- * and returns an AddCommand object for execution.
+ * Parses the given {@code String} of arguments in the context of the AddContactCommand
+ * and returns an AddContactCommand object for execution.
* @throws ParseException if the user input does not conform the expected format
*/
- public AddCommand parse(String args) throws ParseException {
+ public AddContactCommand parse(String args) throws ParseException {
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_ADDRESS, PREFIX_TAG, PREFIX_NOTE);
if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL)
|| !argMultimap.getPreamble().isEmpty()) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddContactCommand.MESSAGE_USAGE));
}
argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
@@ -44,10 +47,11 @@ public AddCommand parse(String args) throws ParseException {
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));
+ Note note = new Note(argMultimap.getValue(PREFIX_NOTE).orElse(""));
- Person person = new Person(name, phone, email, address, tagList);
+ Contact contact = new Contact(name, phone, email, address, tagList, note);
- return new AddCommand(person);
+ return new AddContactCommand(contact);
}
/**
diff --git a/src/main/java/seedu/address/logic/parser/AddTripCommandParser.java b/src/main/java/seedu/address/logic/parser/AddTripCommandParser.java
new file mode 100644
index 00000000000..e52c6761543
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/AddTripCommandParser.java
@@ -0,0 +1,81 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ACCOMMODATION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ITINERARY;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.AddTripCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.contact.Name;
+import seedu.address.model.trip.Accommodation;
+import seedu.address.model.trip.Itinerary;
+import seedu.address.model.trip.Note;
+import seedu.address.model.trip.Trip;
+import seedu.address.model.trip.TripDate;
+import seedu.address.model.trip.TripName;
+
+/**
+ * Parses input arguments and creates a new AddTripCommand object
+ */
+public class AddTripCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the AddTripCommand
+ * and returns an AddTripCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public AddTripCommand parse(String args) throws ParseException {
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_ACCOMMODATION, PREFIX_ITINERARY,
+ PREFIX_DATE, PREFIX_CUSTOMER_NAME, PREFIX_NOTE);
+
+ if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ACCOMMODATION, PREFIX_ITINERARY,
+ PREFIX_DATE)
+ || !argMultimap.getPreamble().isEmpty()) {
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddTripCommand.MESSAGE_USAGE));
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_ACCOMMODATION,
+ PREFIX_ITINERARY, PREFIX_DATE, PREFIX_NOTE);
+ TripName name = ParserUtil.parseTripName(argMultimap.getValue(PREFIX_NAME).get());
+ Accommodation accommodation = ParserUtil.parseAccommodation(argMultimap.getValue(PREFIX_ACCOMMODATION).get());
+ Itinerary itinerary = ParserUtil.parseItinerary(argMultimap.getValue(PREFIX_ITINERARY).get());
+ TripDate date = ParserUtil.parseTripDate(argMultimap.getValue(PREFIX_DATE).get());
+
+ // Note is optional, use default value if not provided
+ Note note;
+ if (argMultimap.getValue(PREFIX_NOTE).isPresent()) {
+ note = ParserUtil.parseNote(argMultimap.getValue(PREFIX_NOTE).get());
+ } else {
+ note = new Note("");
+ }
+
+ // Customer names are optional
+ List customerNames = argMultimap.getAllValues(PREFIX_CUSTOMER_NAME);
+ Set customerNameSet = new HashSet<>();
+ for (String customerName : customerNames) {
+ customerNameSet.add(ParserUtil.parseName(customerName));
+ }
+
+ Trip trip = new Trip(name, accommodation, itinerary, date, customerNameSet, note);
+ return new AddTripCommand(trip);
+ }
+
+ /**
+ * Returns true if none of the prefixes contains empty {@code Optional} values in the given
+ * {@code ArgumentMultimap}.
+ */
+ private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) {
+ return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent());
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
index 3149ee07e0b..c1704a613b3 100644
--- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java
+++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java
@@ -8,15 +8,19 @@
import java.util.regex.Pattern;
import seedu.address.commons.core.LogsCenter;
-import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddContactCommand;
+import seedu.address.logic.commands.AddTripCommand;
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.DeleteContactCommand;
+import seedu.address.logic.commands.DeleteTripCommand;
+import seedu.address.logic.commands.EditContactCommand;
+import seedu.address.logic.commands.EditTripCommand;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
-import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ListContactCommand;
+import seedu.address.logic.commands.ListTripCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
@@ -53,23 +57,29 @@ public Command parseCommand(String userInput) throws ParseException {
switch (commandWord) {
- case AddCommand.COMMAND_WORD:
- return new AddCommandParser().parse(arguments);
+ case AddContactCommand.COMMAND_WORD:
+ return new AddContactCommandParser().parse(arguments);
- case EditCommand.COMMAND_WORD:
- return new EditCommandParser().parse(arguments);
+ case EditContactCommand.COMMAND_WORD:
+ return new EditContactCommandParser().parse(arguments);
- case DeleteCommand.COMMAND_WORD:
- return new DeleteCommandParser().parse(arguments);
+ case DeleteContactCommand.COMMAND_WORD:
+ return new DeleteContactCommandParser().parse(arguments);
case ClearCommand.COMMAND_WORD:
+ if (arguments.trim().equals("confirmed")) {
+ return new ClearCommand(true);
+ }
return new ClearCommand();
case FindCommand.COMMAND_WORD:
return new FindCommandParser().parse(arguments);
- case ListCommand.COMMAND_WORD:
- return new ListCommand();
+ case ListContactCommand.COMMAND_WORD:
+ return new ListContactCommandParser().parse(arguments);
+
+ case ListTripCommand.COMMAND_WORD:
+ return new ListTripCommandParser().parse(arguments);
case ExitCommand.COMMAND_WORD:
return new ExitCommand();
@@ -77,10 +87,18 @@ public Command parseCommand(String userInput) throws ParseException {
case HelpCommand.COMMAND_WORD:
return new HelpCommand();
+ case AddTripCommand.COMMAND_WORD:
+ return new AddTripCommandParser().parse(arguments);
+
+ case EditTripCommand.COMMAND_WORD:
+ return new EditTripCommandParser().parse(arguments);
+
+ case DeleteTripCommand.COMMAND_WORD:
+ return new DeleteTripCommandParser().parse(arguments);
+
default:
logger.finer("This user input caused a ParseException: " + userInput);
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
}
}
-
}
diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java
index 75b1a9bf119..3eff62b2681 100644
--- a/src/main/java/seedu/address/logic/parser/CliSyntax.java
+++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java
@@ -5,11 +5,19 @@
*/
public class CliSyntax {
- /* Prefix definitions */
+ /* Shared prefix definitions */
public static final Prefix PREFIX_NAME = new Prefix("n/");
+ public static final Prefix PREFIX_NOTE = new Prefix("nts/");
+
+ /* Customer prefix definitions */
public static final Prefix PREFIX_PHONE = new Prefix("p/");
public static final Prefix PREFIX_EMAIL = new Prefix("e/");
public static final Prefix PREFIX_ADDRESS = new Prefix("a/");
public static final Prefix PREFIX_TAG = new Prefix("t/");
+ /* Trip prefix definitions */
+ public static final Prefix PREFIX_ACCOMMODATION = new Prefix("acc/");
+ public static final Prefix PREFIX_ITINERARY = new Prefix("i/");
+ public static final Prefix PREFIX_CUSTOMER_NAME = new Prefix("c/");
+ public static final Prefix PREFIX_DATE = new Prefix("d/");
}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteContactCommandParser.java
similarity index 50%
rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
rename to src/main/java/seedu/address/logic/parser/DeleteContactCommandParser.java
index 3527fe76a3e..c9621182645 100644
--- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/DeleteContactCommandParser.java
@@ -1,28 +1,32 @@
package seedu.address.logic.parser;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
import seedu.address.commons.core.index.Index;
-import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteContactCommand;
import seedu.address.logic.parser.exceptions.ParseException;
/**
- * Parses input arguments and creates a new DeleteCommand object
+ * Parses input arguments and creates a new DeleteContactCommand object
*/
-public class DeleteCommandParser implements Parser {
+public class DeleteContactCommandParser implements Parser {
/**
* Parses the given {@code String} of arguments in the context of the DeleteCommand
* and returns a DeleteCommand object for execution.
* @throws ParseException if the user input does not conform the expected format
*/
- public DeleteCommand parse(String args) throws ParseException {
+ public DeleteContactCommand parse(String args) throws ParseException {
try {
- Index index = ParserUtil.parseIndex(args);
- return new DeleteCommand(index);
+ Index index = ParserUtil.parseContactIndex(args);
+ return new DeleteContactCommand(index);
} catch (ParseException pe) {
+ if (pe.getMessage().equals(MESSAGE_INVALID_PERSON_DISPLAYED_INDEX)) {
+ throw pe;
+ }
throw new ParseException(
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE), pe);
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteContactCommand.MESSAGE_USAGE), pe);
}
}
diff --git a/src/main/java/seedu/address/logic/parser/DeleteTripCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteTripCommandParser.java
new file mode 100644
index 00000000000..7ed32ff0931
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/DeleteTripCommandParser.java
@@ -0,0 +1,33 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_TRIP_DISPLAYED_INDEX;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.DeleteTripCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new DeleteTripCommand object
+ */
+public class DeleteTripCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the DeleteTripCommand
+ * and returns a DeleteTripCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public DeleteTripCommand parse(String args) throws ParseException {
+ try {
+ Index index = ParserUtil.parseTripIndex(args);
+ return new DeleteTripCommand(index);
+ } catch (ParseException pe) {
+ if (pe.getMessage().equals(MESSAGE_INVALID_TRIP_DISPLAYED_INDEX)) {
+ throw pe;
+ }
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTripCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditContactCommandParser.java
similarity index 69%
rename from src/main/java/seedu/address/logic/parser/EditCommandParser.java
rename to src/main/java/seedu/address/logic/parser/EditContactCommandParser.java
index 46b3309a78b..59f42c679f5 100644
--- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/EditContactCommandParser.java
@@ -2,9 +2,11 @@
import static java.util.Objects.requireNonNull;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
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_NOTE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
@@ -14,32 +16,38 @@
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.commands.EditContactCommand;
+import seedu.address.logic.commands.EditContactCommand.EditPersonDescriptor;
import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.contact.Note;
import seedu.address.model.tag.Tag;
/**
- * Parses input arguments and creates a new EditCommand object
+ * Parses input arguments and creates a new EditContactCommand object
*/
-public class EditCommandParser implements Parser {
+public class EditContactCommandParser implements Parser {
/**
- * Parses the given {@code String} of arguments in the context of the EditCommand
- * and returns an EditCommand object for execution.
+ * Parses the given {@code String} of arguments in the context of the EditContactCommand
+ * and returns an EditContactCommand object for execution.
* @throws ParseException if the user input does not conform the expected format
*/
- public EditCommand parse(String args) throws ParseException {
+ public EditContactCommand parse(String args) throws ParseException {
requireNonNull(args);
ArgumentMultimap argMultimap =
- ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG);
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL,
+ PREFIX_ADDRESS, PREFIX_TAG, PREFIX_NOTE);
Index index;
try {
- index = ParserUtil.parseIndex(argMultimap.getPreamble());
+ index = ParserUtil.parseContactIndex(argMultimap.getPreamble());
} catch (ParseException pe) {
- throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE), pe);
+ if (pe.getMessage().equals(MESSAGE_INVALID_PERSON_DISPLAYED_INDEX)) {
+ throw pe;
+ }
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT,
+ EditContactCommand.MESSAGE_USAGE), pe);
}
argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS);
@@ -59,12 +67,15 @@ public EditCommand parse(String args) throws ParseException {
editPersonDescriptor.setAddress(ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS).get()));
}
parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags);
+ if (argMultimap.getValue(PREFIX_NOTE).isPresent()) {
+ editPersonDescriptor.setNote(new Note(argMultimap.getValue(PREFIX_NOTE).get()));
+ }
if (!editPersonDescriptor.isAnyFieldEdited()) {
- throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
+ throw new ParseException(EditContactCommand.MESSAGE_NOT_EDITED);
}
- return new EditCommand(index, editPersonDescriptor);
+ return new EditContactCommand(index, editPersonDescriptor);
}
/**
diff --git a/src/main/java/seedu/address/logic/parser/EditTripCommandParser.java b/src/main/java/seedu/address/logic/parser/EditTripCommandParser.java
new file mode 100644
index 00000000000..833381925ba
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/EditTripCommandParser.java
@@ -0,0 +1,105 @@
+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.Messages.MESSAGE_INVALID_TRIP_DISPLAYED_INDEX;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ACCOMMODATION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ITINERARY;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.commands.EditTripCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.contact.Name;
+import seedu.address.model.trip.Note;
+
+/**
+ * Parses input arguments and creates a new EditTripCommand object
+ */
+public class EditTripCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the EditTripCommand
+ * and returns an EditTripCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public EditTripCommand parse(String args) throws ParseException {
+ requireNonNull(args);
+ ArgumentMultimap argMultimap =
+ ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_ACCOMMODATION, PREFIX_ITINERARY,
+ PREFIX_DATE, PREFIX_CUSTOMER_NAME, PREFIX_NOTE);
+
+ Index index;
+
+ try {
+ index = ParserUtil.parseTripIndex(argMultimap.getPreamble());
+ } catch (ParseException pe) {
+ if (pe.getMessage().equals(MESSAGE_INVALID_TRIP_DISPLAYED_INDEX)) {
+ throw pe;
+ }
+ throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTripCommand.MESSAGE_USAGE), pe);
+ }
+
+ argMultimap.verifyNoDuplicatePrefixesFor(PREFIX_NAME, PREFIX_ACCOMMODATION,
+ PREFIX_ITINERARY, PREFIX_DATE, PREFIX_NOTE);
+ EditTripCommand.EditTripDescriptor editTripDescriptor = new EditTripCommand.EditTripDescriptor();
+
+ if (argMultimap.getValue(PREFIX_NAME).isPresent()) {
+ editTripDescriptor.setName(ParserUtil.parseTripName(argMultimap.getValue(PREFIX_NAME).get()));
+ }
+ if (argMultimap.getValue(PREFIX_ACCOMMODATION).isPresent()) {
+ editTripDescriptor.setAccommodation(ParserUtil.parseAccommodation(
+ argMultimap.getValue(PREFIX_ACCOMMODATION).get()));
+ }
+ if (argMultimap.getValue(PREFIX_ITINERARY).isPresent()) {
+ editTripDescriptor.setItinerary(ParserUtil.parseItinerary(argMultimap.getValue(PREFIX_ITINERARY).get()));
+ }
+ if (argMultimap.getValue(PREFIX_DATE).isPresent()) {
+ editTripDescriptor.setDate(ParserUtil.parseTripDate(argMultimap.getValue(PREFIX_DATE).get()));
+ }
+ parseCustomerNamesForEdit(argMultimap.getAllValues(PREFIX_CUSTOMER_NAME))
+ .ifPresent(editTripDescriptor::setCustomerNames);
+
+ // Handle note field
+ if (!argMultimap.getAllValues(PREFIX_NOTE).isEmpty()) {
+ // If note prefix is present, create a note with whatever value is there (could be empty)
+ String noteValue = argMultimap.getValue(PREFIX_NOTE).orElse("");
+ editTripDescriptor.setNote(new Note(noteValue));
+ }
+
+ if (!editTripDescriptor.isAnyFieldEdited()) {
+ throw new ParseException(EditTripCommand.MESSAGE_NOT_EDITED);
+ }
+
+ return new EditTripCommand(index, editTripDescriptor);
+ }
+
+ /**
+ * Parses {@code Collection customerNames} into a {@code Set} if {@code customerNames} is non-empty.
+ * If {@code customerNames} contain only one element which is an empty string, it will be parsed into a
+ * {@code Set} containing zero names.
+ */
+ private Optional> parseCustomerNamesForEdit(Collection customerNames) throws ParseException {
+ assert customerNames != null;
+
+ if (customerNames.isEmpty()) {
+ return Optional.empty();
+ }
+ Collection nameSet = customerNames.size() == 1 && customerNames.contains("")
+ ? Collections.emptySet() : customerNames;
+ Set customerNameSet = new HashSet<>();
+ for (String customerName : nameSet) {
+ customerNameSet.add(ParserUtil.parseName(customerName));
+ }
+ return Optional.of(customerNameSet);
+ }
+}
diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
index 2867bde857b..4bffa076c12 100644
--- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java
+++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java
@@ -6,7 +6,7 @@
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.contact.NameContainsKeywordsPredicate;
/**
* Parses input arguments and creates a new FindCommand object
diff --git a/src/main/java/seedu/address/logic/parser/ListContactCommandParser.java b/src/main/java/seedu/address/logic/parser/ListContactCommandParser.java
new file mode 100644
index 00000000000..4f2c8f70fc2
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ListContactCommandParser.java
@@ -0,0 +1,29 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import seedu.address.logic.commands.ListContactCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+
+/**
+ * Parses input arguments and creates a new ListContactCommand object
+ */
+public class ListContactCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ListContactCommand
+ * and returns a ListContactCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ListContactCommand parse(String args) throws ParseException {
+ assert args != null : "Input args should not be null";
+ try {
+ String tag = ParserUtil.parseTagName(args);
+ return new ListContactCommand(tag);
+ } catch (ParseException pe) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListContactCommand.MESSAGE_USAGE), pe);
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/ListTripCommandParser.java b/src/main/java/seedu/address/logic/parser/ListTripCommandParser.java
new file mode 100644
index 00000000000..7daab495930
--- /dev/null
+++ b/src/main/java/seedu/address/logic/parser/ListTripCommandParser.java
@@ -0,0 +1,40 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import seedu.address.logic.commands.ListTripCommand;
+import seedu.address.logic.parser.exceptions.ParseException;
+import seedu.address.model.trip.TripDate;
+
+/**
+ * Parses input arguments and creates a new ListContactCommand object
+ */
+public class ListTripCommandParser implements Parser {
+
+ /**
+ * Parses the given {@code String} of arguments in the context of the ListContactCommand
+ * and returns a ListContactCommand object for execution.
+ * @throws ParseException if the user input does not conform the expected format
+ */
+ public ListTripCommand parse(String args) throws ParseException {
+ String trimmedArgs = args.trim();
+
+ if (trimmedArgs.isEmpty()) {
+ return new ListTripCommand();
+ }
+ try {
+
+ DateTimeFormatter formatter = TripDate.DATE_FORMATTER;
+ LocalDate filterDate = LocalDate.parse(trimmedArgs, formatter);
+ return new ListTripCommand(filterDate);
+ } catch (DateTimeParseException e) {
+ throw new ParseException(
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListTripCommand.MESSAGE_USAGE));
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java
index b117acb9c55..859335d1f45 100644
--- a/src/main/java/seedu/address/logic/parser/ParserUtil.java
+++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java
@@ -1,6 +1,8 @@
package seedu.address.logic.parser;
import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_TRIP_DISPLAYED_INDEX;
import java.util.Collection;
import java.util.HashSet;
@@ -9,11 +11,16 @@
import seedu.address.commons.core.index.Index;
import seedu.address.commons.util.StringUtil;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Phone;
+import seedu.address.model.contact.Address;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Name;
+import seedu.address.model.contact.Phone;
import seedu.address.model.tag.Tag;
+import seedu.address.model.trip.Accommodation;
+import seedu.address.model.trip.Itinerary;
+import seedu.address.model.trip.Note;
+import seedu.address.model.trip.TripDate;
+import seedu.address.model.trip.TripName;
/**
* Contains utility methods used for parsing strings in the various *Parser classes.
@@ -21,32 +28,55 @@
public class ParserUtil {
public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer.";
+ private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-";
/**
- * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be
- * trimmed.
+ * Parses {@code oneBasedIndex} for a contact into an {@code Index} and returns it. Leading and trailing
+ * whitespaces will be trimmed.
+ * @throws ParseException if the specified index is invalid (not a non-zero unsigned integer).
+ */
+ public static Index parseContactIndex(String oneBasedIndex) throws ParseException {
+ String trimmedIndex = oneBasedIndex.trim();
+ if (!StringUtil.isInteger(trimmedIndex)) {
+ throw new ParseException(MESSAGE_INVALID_INDEX);
+ }
+ int value = Integer.parseInt(trimmedIndex);
+ if (value <= 0) {
+ throw new ParseException(MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+ return Index.fromOneBased(Integer.parseInt(trimmedIndex));
+ }
+ /**
+ * Parses {@code oneBasedIndex} for a trip 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 {
+ public static Index parseTripIndex(String oneBasedIndex) throws ParseException {
String trimmedIndex = oneBasedIndex.trim();
- if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) {
+ if (!StringUtil.isInteger(trimmedIndex)) {
throw new ParseException(MESSAGE_INVALID_INDEX);
}
+ int value = Integer.parseInt(trimmedIndex);
+ if (value <= 0) {
+ throw new ParseException(MESSAGE_INVALID_TRIP_DISPLAYED_INDEX);
+ }
return Index.fromOneBased(Integer.parseInt(trimmedIndex));
}
/**
* Parses a {@code String name} into a {@code Name}.
- * Leading and trailing whitespaces will be trimmed.
+ * Leading, trailing, and excessive internal whitespace will be trimmed.
*
* @throws ParseException if the given {@code name} is invalid.
*/
public static Name parseName(String name) throws ParseException {
requireNonNull(name);
- String trimmedName = name.trim();
+ String trimmedName = name.trim().replaceAll("\\s+", " ");
+
if (!Name.isValidName(trimmedName)) {
throw new ParseException(Name.MESSAGE_CONSTRAINTS);
}
+
return new Name(trimmedName);
}
@@ -110,6 +140,23 @@ public static Tag parseTag(String tag) throws ParseException {
return new Tag(trimmedTag);
}
+ /**
+ * Parses a {@code String tag}
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code tag} is invalid.
+ */
+ public static String parseTagName(String tag) throws ParseException {
+ if (tag.equals("")) {
+ return tag;
+ }
+ String trimmedTagName = tag.trim().toLowerCase();
+ if (!Tag.isValidTagName(trimmedTagName)) {
+ throw new ParseException(Tag.TAGNAME_SPECIFICATION);
+ }
+ return trimmedTagName;
+ }
+
/**
* Parses {@code Collection tags} into a {@code Set}.
*/
@@ -121,4 +168,73 @@ public static Set parseTags(Collection tags) throws ParseException
}
return tagSet;
}
+
+ /**
+ * Parses a {@code String name} into a {@code TripName}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code name} is invalid.
+ */
+ public static TripName parseTripName(String tripName) throws ParseException {
+ requireNonNull(tripName);
+ String trimmedTripName = tripName.trim();
+ if (!TripName.isValidName(trimmedTripName)) {
+ throw new ParseException(TripName.MESSAGE_CONSTRAINTS);
+ }
+ return new TripName(trimmedTripName);
+ }
+
+ /**
+ * Parses a {@code String itinerary} into a {@code Itinerary}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code itinerary} is invalid.
+ */
+ public static Itinerary parseItinerary(String itinerary) throws ParseException {
+ requireNonNull(itinerary);
+ String trimmedItinerary = itinerary.trim();
+ if (!Itinerary.isValidItinerary(trimmedItinerary)) {
+ throw new ParseException(Itinerary.MESSAGE_CONSTRAINTS);
+ }
+ return new Itinerary(trimmedItinerary);
+ }
+
+ /**
+ * Parses a {@code String accommodation} into a {@code Accommodation}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code accommodation} is invalid.
+ */
+ public static Accommodation parseAccommodation(String accommodation) throws ParseException {
+ requireNonNull(accommodation);
+ String trimmedAccommodation = accommodation.trim();
+ if (!Accommodation.isValidAccommodation(trimmedAccommodation)) {
+ throw new ParseException(Accommodation.MESSAGE_CONSTRAINTS);
+ }
+ return new Accommodation(trimmedAccommodation);
+ }
+
+ /**
+ * Parses a {@code String tripDate} into a {@code TripDate}.
+ * Leading and trailing whitespaces will be trimmed.
+ *
+ * @throws ParseException if the given {@code tripDate} is invalid.
+ */
+ public static TripDate parseTripDate(String tripDate) throws ParseException {
+ requireNonNull(tripDate);
+ String trimmedTripDate = tripDate.trim();
+ if (!TripDate.isValidTripDate(trimmedTripDate)) {
+ throw new ParseException(TripDate.MESSAGE_CONSTRAINTS);
+ }
+ return new TripDate(trimmedTripDate);
+ }
+
+ /**
+ * Parses a {@code String note} into a {@code Note}.
+ * Leading and trailing whitespaces will be trimmed.
+ */
+ public static Note parseNote(String note) throws ParseException {
+ requireNonNull(note);
+ return new Note(note);
+ }
}
diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/address/logic/parser/Prefix.java
index 348b7686c8a..d2a5b9b1031 100644
--- a/src/main/java/seedu/address/logic/parser/Prefix.java
+++ b/src/main/java/seedu/address/logic/parser/Prefix.java
@@ -2,7 +2,7 @@
/**
* A prefix that marks the beginning of an argument in an arguments string.
- * E.g. 't/' in 'add James t/ friend'.
+ * E.g. 't/' in 'add James t/ contact'.
*/
public class Prefix {
private final String prefix;
diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java
index 73397161e84..53df18597cb 100644
--- a/src/main/java/seedu/address/model/AddressBook.java
+++ b/src/main/java/seedu/address/model/AddressBook.java
@@ -6,8 +6,8 @@
import javafx.collections.ObservableList;
import seedu.address.commons.util.ToStringBuilder;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.UniquePersonList;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.UniquePersonList;
/**
* Wraps all data at the address-book level
@@ -41,11 +41,11 @@ public AddressBook(ReadOnlyAddressBook toBeCopied) {
//// list overwrite operations
/**
- * Replaces the contents of the person list with {@code persons}.
- * {@code persons} must not contain duplicate persons.
+ * Replaces the contents of the contact list with {@code contacts}.
+ * {@code contacts} must not contain duplicate contacts.
*/
- public void setPersons(List persons) {
- this.persons.setPersons(persons);
+ public void setPersons(List contacts) {
+ this.persons.setPersons(contacts);
}
/**
@@ -57,40 +57,42 @@ public void resetData(ReadOnlyAddressBook newData) {
setPersons(newData.getPersonList());
}
- //// person-level operations
+ //// contact-level operations
/**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
+ * Returns true if a contact with the same identity as {@code contact} exists in the address book.
*/
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return persons.contains(person);
+ public boolean hasContact(Contact contact) {
+ requireNonNull(contact);
+ return persons.contains(contact);
}
/**
- * Adds a person to the address book.
- * The person must not already exist in the address book.
+ * Adds a contact to the address book.
+ * The contact must not already exist in the address book.
*/
- public void addPerson(Person p) {
+ public void addPerson(Contact p) {
+
persons.add(p);
}
/**
- * Replaces the given person {@code target} in the list with {@code editedPerson}.
+ * Replaces the given contact {@code target} in the list with {@code editedContact}.
* {@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.
+ * The contact identity of {@code editedContact} must not be the same as another existing contact
+ * in the address book.
*/
- public void setPerson(Person target, Person editedPerson) {
- requireNonNull(editedPerson);
+ public void setContact(Contact target, Contact editedContact) {
+ requireNonNull(editedContact);
- persons.setPerson(target, editedPerson);
+ persons.setPerson(target, editedContact);
}
/**
* Removes {@code key} from this {@code AddressBook}.
* {@code key} must exist in the address book.
*/
- public void removePerson(Person key) {
+ public void removeContact(Contact key) {
persons.remove(key);
}
@@ -99,12 +101,12 @@ public void removePerson(Person key) {
@Override
public String toString() {
return new ToStringBuilder(this)
- .add("persons", persons)
+ .add("contacts", persons)
.toString();
}
@Override
- public ObservableList getPersonList() {
+ public ObservableList getPersonList() {
return persons.asUnmodifiableObservableList();
}
diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java
index d54df471c1f..4802048015f 100644
--- a/src/main/java/seedu/address/model/Model.java
+++ b/src/main/java/seedu/address/model/Model.java
@@ -5,14 +5,21 @@
import javafx.collections.ObservableList;
import seedu.address.commons.core.GuiSettings;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.trip.Trip;
/**
* The API of the Model component.
*/
public interface Model {
/** {@code Predicate} that always evaluate to true */
- Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ Predicate PREDICATE_SHOW_ALL_PERSONS = unused -> true;
+ /** {@code Predicate} that checks for a customer tag */
+ Predicate PREDICATE_SHOW_ALL_CUSTOMER = Contact::isCustomer;
+ /** {@code Predicate} that checks for a service tag */
+ Predicate PREDICATE_SHOW_ALL_SERVICE = Contact::isService;
+ /** {@code Predicate} that always evaluate to true */
+ Predicate PREDICATE_SHOW_ALL_TRIPS = unused -> true;
/**
* Replaces user prefs data with the data in {@code userPrefs}.
@@ -44,6 +51,16 @@ public interface Model {
*/
void setAddressBookFilePath(Path addressBookFilePath);
+ /**
+ * Returns the user prefs' trip book file path.
+ */
+ Path getTripBookFilePath();
+
+ /**
+ * Sets the user prefs' trip book file path.
+ */
+ void setTripBookFilePath(Path tripBookFilePath);
+
/**
* Replaces address book data with the data in {@code addressBook}.
*/
@@ -53,35 +70,77 @@ public interface Model {
ReadOnlyAddressBook getAddressBook();
/**
- * Returns true if a person with the same identity as {@code person} exists in the address book.
+ * Returns true if a contact with the same identity as {@code contact} exists in the address book.
*/
- boolean hasPerson(Person person);
+ boolean hasContact(Contact contact);
/**
- * Deletes the given person.
- * The person must exist in the address book.
+ * Deletes the given contact.
+ * The contact must exist in the address book.
*/
- void deletePerson(Person target);
+ void deleteContact(Contact target);
/**
- * Adds the given person.
- * {@code person} must not already exist in the address book.
+ * Adds the given contact.
+ * {@code contact} must not already exist in the address book.
*/
- void addPerson(Person person);
+ void addPerson(Contact contact);
/**
- * Replaces the given person {@code target} with {@code editedPerson}.
+ * Replaces the given contact {@code target} with {@code editedContact}.
* {@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.
+ * The contact identity of {@code editedContact} must not be the same as another existing contact
+ * in the address book.
+ */
+ void setPerson(Contact target, Contact editedContact);
+
+ /** Returns an unmodifiable view of the filtered contact list */
+ ObservableList getFilteredPersonList();
+
+ /**
+ * Updates the filter of the filtered contact list to filter by the given {@code predicate}.
+ * @throws NullPointerException if {@code predicate} is null.
+ */
+ void updateFilteredPersonList(Predicate predicate);
+
+ /**
+ * Replaces trip book data with the data in {@code tripBook}.
+ */
+ void setTripBook(ReadOnlyTripBook tripBook);
+
+ /** Returns the TripBook */
+ ReadOnlyTripBook getTripBook();
+
+ /**
+ * Returns true if a trip with the same identity as {@code trip} exists in the trip book.
+ */
+ boolean hasTrip(Trip trip);
+
+ /**
+ * Deletes the given trip.
+ * The trip must exist in the trip book.
+ */
+ void deleteTrip(Trip target);
+
+ /**
+ * Adds the given trip.
+ * {@code trip} must not already exist in the trip book.
+ */
+ void addTrip(Trip trip);
+
+ /**
+ * Replaces the given trip {@code target} with {@code editedTrip}.
+ * {@code target} must exist in the trip book.
+ * The trip identity of {@code editedTrip} must not be the same as another existing trip in the trip book.
*/
- void setPerson(Person target, Person editedPerson);
+ void setTrip(Trip target, Trip editedTrip);
- /** Returns an unmodifiable view of the filtered person list */
- ObservableList getFilteredPersonList();
+ /** Returns an unmodifiable view of the filtered trip list */
+ ObservableList getFilteredTripList();
/**
- * Updates the filter of the filtered person list to filter by the given {@code predicate}.
+ * Updates the filter of the filtered trip list to filter by the given {@code predicate}.
* @throws NullPointerException if {@code predicate} is null.
*/
- void updateFilteredPersonList(Predicate predicate);
+ void updateFilteredTripList(Predicate predicate);
}
diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java
index 57bc563fde6..a0d3f58294e 100644
--- a/src/main/java/seedu/address/model/ModelManager.java
+++ b/src/main/java/seedu/address/model/ModelManager.java
@@ -11,7 +11,8 @@
import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.trip.Trip;
/**
* Represents the in-memory model of the address book data.
@@ -20,24 +21,34 @@ public class ModelManager implements Model {
private static final Logger logger = LogsCenter.getLogger(ModelManager.class);
private final AddressBook addressBook;
+ private final TripBook tripBook;
private final UserPrefs userPrefs;
- private final FilteredList filteredPersons;
+ private final FilteredList filteredContacts;
+ private final FilteredList filteredTrips;
/**
- * Initializes a ModelManager with the given addressBook and userPrefs.
+ * Initializes a ModelManager with the given addressBook, tripBook and userPrefs.
*/
- public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyUserPrefs userPrefs) {
- requireAllNonNull(addressBook, userPrefs);
+ public ModelManager(ReadOnlyAddressBook addressBook, ReadOnlyTripBook tripBook, ReadOnlyUserPrefs userPrefs) {
+ requireAllNonNull(addressBook, tripBook, userPrefs);
- logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);
+ logger.fine("Initializing with address book: " + addressBook + ", trip book: " + tripBook
+ + " and user prefs " + userPrefs);
this.addressBook = new AddressBook(addressBook);
+ this.tripBook = new TripBook(tripBook);
this.userPrefs = new UserPrefs(userPrefs);
- filteredPersons = new FilteredList<>(this.addressBook.getPersonList());
+ filteredContacts = new FilteredList<>(this.addressBook.getPersonList());
+ filteredTrips = new FilteredList<>(this.tripBook.getTripList());
+ logger.info("ModelManager initialized successfully");
}
+ /**
+ * Creates a new ModelManager with empty data.
+ */
public ModelManager() {
- this(new AddressBook(), new UserPrefs());
+ this(new AddressBook(), new TripBook(), new UserPrefs());
+ logger.info("Created new ModelManager with empty data");
}
//=========== UserPrefs ==================================================================================
@@ -45,104 +56,190 @@ public ModelManager() {
@Override
public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
requireNonNull(userPrefs);
+ logger.info("Updating user preferences");
this.userPrefs.resetData(userPrefs);
}
@Override
public ReadOnlyUserPrefs getUserPrefs() {
+ logger.fine("Retrieving user preferences");
return userPrefs;
}
@Override
public GuiSettings getGuiSettings() {
+ logger.fine("Retrieving GUI settings");
return userPrefs.getGuiSettings();
}
@Override
public void setGuiSettings(GuiSettings guiSettings) {
requireNonNull(guiSettings);
+ logger.info("Updating GUI settings");
userPrefs.setGuiSettings(guiSettings);
}
@Override
public Path getAddressBookFilePath() {
+ logger.fine("Retrieving address book file path");
return userPrefs.getAddressBookFilePath();
}
@Override
public void setAddressBookFilePath(Path addressBookFilePath) {
requireNonNull(addressBookFilePath);
+ logger.info("Updating address book file path to: " + addressBookFilePath);
userPrefs.setAddressBookFilePath(addressBookFilePath);
}
+ @Override
+ public Path getTripBookFilePath() {
+ return userPrefs.getTripBookFilePath();
+ }
+
+ @Override
+ public void setTripBookFilePath(Path tripBookFilePath) {
+ requireNonNull(tripBookFilePath);
+ userPrefs.setTripBookFilePath(tripBookFilePath);
+ }
+
//=========== AddressBook ================================================================================
@Override
public void setAddressBook(ReadOnlyAddressBook addressBook) {
this.addressBook.resetData(addressBook);
+ logger.info("Address book data reset");
}
@Override
public ReadOnlyAddressBook getAddressBook() {
+ logger.fine("Retrieving address book");
return addressBook;
}
@Override
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return addressBook.hasPerson(person);
+ public boolean hasContact(Contact contact) {
+ requireNonNull(contact);
+ logger.fine("Checking if contact exists: " + contact.getName());
+ return addressBook.hasContact(contact);
}
@Override
- public void deletePerson(Person target) {
- addressBook.removePerson(target);
+ public void deleteContact(Contact target) {
+ addressBook.removeContact(target);
+ logger.info("Deleted contact: " + target.getName());
}
@Override
- public void addPerson(Person person) {
- addressBook.addPerson(person);
+ public void addPerson(Contact contact) {
+ addressBook.addPerson(contact);
updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
+ logger.info("Added new contact: " + contact.getName());
+ }
+
+ @Override
+ public void setPerson(Contact target, Contact editedContact) {
+ requireAllNonNull(target, editedContact);
+ addressBook.setContact(target, editedContact);
+ logger.info("Updated contact: " + target.getName() + " to: " + editedContact.getName());
+ }
+
+ //=========== TripBook ================================================================================
+
+ @Override
+ public void setTripBook(ReadOnlyTripBook newData) {
+ tripBook.resetData(newData);
+ logger.info("Trip book data reset");
+ }
+
+ @Override
+ public ReadOnlyTripBook getTripBook() {
+ logger.fine("Retrieving trip book");
+ return tripBook;
}
@Override
- public void setPerson(Person target, Person editedPerson) {
- requireAllNonNull(target, editedPerson);
+ public boolean hasTrip(Trip trip) {
+ requireNonNull(trip);
+ logger.fine("Checking if trip exists: " + trip.getName());
+ return tripBook.hasTrip(trip);
+ }
- addressBook.setPerson(target, editedPerson);
+ @Override
+ public void deleteTrip(Trip target) {
+ tripBook.removeTrip(target);
+ logger.info("Deleted trip: " + target.getName());
}
- //=========== Filtered Person List Accessors =============================================================
+ @Override
+ public void addTrip(Trip trip) {
+ tripBook.addTrip(trip);
+ updateFilteredTripList(PREDICATE_SHOW_ALL_TRIPS);
+ logger.info("Added new trip: " + trip.getName());
+ }
+
+ @Override
+ public void setTrip(Trip target, Trip editedTrip) {
+ requireAllNonNull(target, editedTrip);
+ tripBook.setTrip(target, editedTrip);
+ logger.info("Updated trip: " + target.getName() + " to: " + editedTrip.getName());
+ }
+
+ //=========== Filtered Contact List Accessors =============================================================
/**
- * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of
+ * Returns an unmodifiable view of the list of {@code Contact} backed by the internal list of
* {@code versionedAddressBook}
*/
@Override
- public ObservableList getFilteredPersonList() {
- return filteredPersons;
+ public ObservableList getFilteredPersonList() {
+ logger.fine("Retrieving filtered person list");
+ return filteredContacts;
+ }
+
+ @Override
+ public void updateFilteredPersonList(Predicate predicate) {
+ requireNonNull(predicate);
+ logger.info("Updating filtered person list with new predicate");
+ filteredContacts.setPredicate(predicate);
+ }
+
+ //=========== Filtered Trip List Accessors =============================================================
+
+ /**
+ * Returns an unmodifiable view of the list of {@code Trip} backed by the internal list of
+ * {@code versionedTripBook}
+ */
+ @Override
+ public ObservableList getFilteredTripList() {
+ logger.fine("Retrieving filtered trip list");
+ return filteredTrips;
}
@Override
- public void updateFilteredPersonList(Predicate predicate) {
+ public void updateFilteredTripList(Predicate predicate) {
requireNonNull(predicate);
- filteredPersons.setPredicate(predicate);
+ logger.info("Updating filtered trip list with new predicate");
+ filteredTrips.setPredicate(predicate);
}
@Override
- public boolean equals(Object other) {
- if (other == this) {
+ public boolean equals(Object obj) {
+ if (obj == this) {
return true;
}
// instanceof handles nulls
- if (!(other instanceof ModelManager)) {
+ if (!(obj instanceof ModelManager)) {
return false;
}
- ModelManager otherModelManager = (ModelManager) other;
- return addressBook.equals(otherModelManager.addressBook)
- && userPrefs.equals(otherModelManager.userPrefs)
- && filteredPersons.equals(otherModelManager.filteredPersons);
+ ModelManager other = (ModelManager) obj;
+ return addressBook.equals(other.addressBook)
+ && tripBook.equals(other.tripBook)
+ && userPrefs.equals(other.userPrefs)
+ && filteredContacts.equals(other.filteredContacts)
+ && filteredTrips.equals(other.filteredTrips);
}
}
diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
index 6ddc2cd9a29..6dd1d015ba5 100644
--- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
+++ b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java
@@ -1,7 +1,7 @@
package seedu.address.model;
import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
/**
* Unmodifiable view of an address book
@@ -12,6 +12,6 @@ public interface ReadOnlyAddressBook {
* Returns an unmodifiable view of the persons list.
* This list will not contain any duplicate persons.
*/
- ObservableList getPersonList();
+ ObservableList getPersonList();
}
diff --git a/src/main/java/seedu/address/model/ReadOnlyTripBook.java b/src/main/java/seedu/address/model/ReadOnlyTripBook.java
new file mode 100644
index 00000000000..3001907bf82
--- /dev/null
+++ b/src/main/java/seedu/address/model/ReadOnlyTripBook.java
@@ -0,0 +1,16 @@
+package seedu.address.model;
+
+import javafx.collections.ObservableList;
+import seedu.address.model.trip.Trip;
+
+/**
+ * Unmodifiable view of a trip book
+ */
+public interface ReadOnlyTripBook {
+
+ /**
+ * Returns an unmodifiable view of the trips list.
+ * This list will not contain any duplicate trips.
+ */
+ ObservableList getTripList();
+}
diff --git a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
index befd58a4c73..d03ab535786 100644
--- a/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
+++ b/src/main/java/seedu/address/model/ReadOnlyUserPrefs.java
@@ -13,4 +13,6 @@ public interface ReadOnlyUserPrefs {
Path getAddressBookFilePath();
+ Path getTripBookFilePath();
+
}
diff --git a/src/main/java/seedu/address/model/TripBook.java b/src/main/java/seedu/address/model/TripBook.java
new file mode 100644
index 00000000000..1513a45fe1d
--- /dev/null
+++ b/src/main/java/seedu/address/model/TripBook.java
@@ -0,0 +1,130 @@
+package seedu.address.model;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+import javafx.collections.ObservableList;
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.trip.Trip;
+import seedu.address.model.trip.UniqueTripList;
+
+/**
+ * Wraps all data at the trip-book level
+ * Duplicates are not allowed (by .isSameTrip comparison)
+ */
+public class TripBook implements ReadOnlyTripBook {
+
+ private final UniqueTripList trips;
+
+ /*
+ * 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.
+ */
+ {
+ trips = new UniqueTripList();
+ }
+
+ public TripBook() {}
+
+ /**
+ * Creates a TripBook using the Trips in the {@code toBeCopied}
+ */
+ public TripBook(ReadOnlyTripBook toBeCopied) {
+ this();
+ resetData(toBeCopied);
+ }
+
+ //// list overwrite operations
+
+ /**
+ * Replaces the contents of the trip list with {@code trips}.
+ * {@code trips} must not contain duplicate trips.
+ */
+ public void setTrips(List trips) {
+ this.trips.setTrips(trips);
+ }
+
+ /**
+ * Resets the existing data of this {@code TripBook} with {@code newData}.
+ */
+ public void resetData(ReadOnlyTripBook newData) {
+ requireNonNull(newData);
+
+ setTrips(newData.getTripList());
+ }
+
+ //// trip-level operations
+
+ /**
+ * Returns true if a trip with the same identity as {@code trip} exists in the trip book.
+ */
+ public boolean hasTrip(Trip trip) {
+ requireNonNull(trip);
+ return trips.contains(trip);
+ }
+
+ /**
+ * Adds a trip to the trip book.
+ * The trip must not already exist in the trip book.
+ */
+ public void addTrip(Trip p) {
+ trips.add(p);
+ }
+
+ /**
+ * Replaces the given trip {@code target} in the list with {@code editedTrip}.
+ * {@code target} must exist in the trip book.
+ * The trip identity of {@code editedTrip} must not be the same as another existing trip in the trip book.
+ */
+ public void setTrip(Trip target, Trip editedTrip) {
+ requireNonNull(editedTrip);
+
+ trips.setTrip(target, editedTrip);
+ }
+
+ /**
+ * Removes {@code key} from this {@code TripBook}.
+ * {@code key} must exist in the trip book.
+ */
+ public void removeTrip(Trip key) {
+ trips.remove(key);
+ }
+
+ //// util methods
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("trips", trips)
+ .toString();
+ }
+
+ @Override
+ public ObservableList getTripList() {
+ return trips.asUnmodifiableObservableList();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof TripBook)) {
+ return false;
+ }
+
+ TripBook otherTripBook = (TripBook) other;
+ return trips.equals(otherTripBook.trips);
+ }
+
+ @Override
+ public int hashCode() {
+ return trips.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java
index 6be655fb4c7..dc92101f02c 100644
--- a/src/main/java/seedu/address/model/UserPrefs.java
+++ b/src/main/java/seedu/address/model/UserPrefs.java
@@ -15,6 +15,7 @@ public class UserPrefs implements ReadOnlyUserPrefs {
private GuiSettings guiSettings = new GuiSettings();
private Path addressBookFilePath = Paths.get("data" , "addressbook.json");
+ private Path tripBookFilePath = Paths.get("data" , "tripbook.json");
/**
* Creates a {@code UserPrefs} with default values.
@@ -36,8 +37,10 @@ public void resetData(ReadOnlyUserPrefs newUserPrefs) {
requireNonNull(newUserPrefs);
setGuiSettings(newUserPrefs.getGuiSettings());
setAddressBookFilePath(newUserPrefs.getAddressBookFilePath());
+ setTripBookFilePath(newUserPrefs.getTripBookFilePath());
}
+ @Override
public GuiSettings getGuiSettings() {
return guiSettings;
}
@@ -47,6 +50,7 @@ public void setGuiSettings(GuiSettings guiSettings) {
this.guiSettings = guiSettings;
}
+ @Override
public Path getAddressBookFilePath() {
return addressBookFilePath;
}
@@ -56,6 +60,16 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
this.addressBookFilePath = addressBookFilePath;
}
+ @Override
+ public Path getTripBookFilePath() {
+ return tripBookFilePath;
+ }
+
+ public void setTripBookFilePath(Path tripBookFilePath) {
+ requireNonNull(tripBookFilePath);
+ this.tripBookFilePath = tripBookFilePath;
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
@@ -69,19 +83,21 @@ public boolean equals(Object other) {
UserPrefs otherUserPrefs = (UserPrefs) other;
return guiSettings.equals(otherUserPrefs.guiSettings)
- && addressBookFilePath.equals(otherUserPrefs.addressBookFilePath);
+ && addressBookFilePath.equals(otherUserPrefs.addressBookFilePath)
+ && tripBookFilePath.equals(otherUserPrefs.tripBookFilePath);
}
@Override
public int hashCode() {
- return Objects.hash(guiSettings, addressBookFilePath);
+ return Objects.hash(guiSettings, addressBookFilePath, tripBookFilePath);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Gui Settings : " + guiSettings);
- sb.append("\nLocal data file location : " + addressBookFilePath);
+ sb.append("\nLocal address book data file location : " + addressBookFilePath);
+ sb.append("\nLocal trip book data file location : " + tripBookFilePath);
return sb.toString();
}
diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/contact/Address.java
similarity index 94%
rename from src/main/java/seedu/address/model/person/Address.java
rename to src/main/java/seedu/address/model/contact/Address.java
index 469a2cc9a1e..936de0d6e9a 100644
--- a/src/main/java/seedu/address/model/person/Address.java
+++ b/src/main/java/seedu/address/model/contact/Address.java
@@ -1,10 +1,10 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
/**
- * Represents a Person's address in the address book.
+ * Represents a Contact's address in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)}
*/
public class Address {
diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/contact/Contact.java
similarity index 52%
rename from src/main/java/seedu/address/model/person/Person.java
rename to src/main/java/seedu/address/model/contact/Contact.java
index abe8c46b535..0c218467187 100644
--- a/src/main/java/seedu/address/model/person/Person.java
+++ b/src/main/java/seedu/address/model/contact/Contact.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
@@ -11,10 +11,10 @@
import seedu.address.model.tag.Tag;
/**
- * Represents a Person in the address book.
+ * Represents a Contact in the address book.
* Guarantees: details are present and not null, field values are validated, immutable.
*/
-public class Person {
+public class Contact {
// Identity fields
private final Name name;
@@ -24,23 +24,37 @@ public class Person {
// Data fields
private final Address address;
private final Set tags = new HashSet<>();
+ private final Note note;
+
+ /**
+ * Every field must be present and not null except for note.
+ */
+ public Contact(Name name, Phone phone, Email email, Address address, Set tags, Note note) {
+ requireAllNonNull(name, phone, email, address, tags);
+ this.name = name;
+ this.phone = phone;
+ this.email = email;
+ this.address = address;
+ this.tags.addAll(tags);
+ this.note = note;
+ }
/**
* Every field must be present and not null.
*/
- public Person(Name name, Phone phone, Email email, Address address, Set tags) {
+ public Contact(Name name, Phone phone, Email email, Address address, Set tags) {
requireAllNonNull(name, phone, email, address, tags);
this.name = name;
this.phone = phone;
this.email = email;
this.address = address;
this.tags.addAll(tags);
+ this.note = new Note("");
}
public Name getName() {
return name;
}
-
public Phone getPhone() {
return phone;
}
@@ -61,17 +75,39 @@ public Set getTags() {
return Collections.unmodifiableSet(tags);
}
+ public Note getNote() {
+ return note;
+ }
+
+ /**
+ * Returns an boolean on whether the contact is a customer based on its tag
+ */
+ public boolean isCustomer() {
+ return tags.stream().anyMatch(tag -> tag.tagName.equals("customer"));
+ }
+
/**
- * Returns true if both persons have the same name.
+ * Returns an boolean on whether the contact is a service based on its tag
+ */
+ public boolean isService() {
+ return tags.stream().anyMatch(tag -> tag.tagName.equals("service"));
+ }
+
+ /**
+ * Returns true if both persons have the same email.
* This defines a weaker notion of equality between two persons.
*/
- public boolean isSamePerson(Person otherPerson) {
- if (otherPerson == this) {
+ public boolean isSamePerson(Contact otherContact) {
+ if (otherContact == null) {
+ return false;
+ }
+ if (otherContact == this) {
return true;
}
+ String otherEmail = otherContact.getEmail().toString().toLowerCase();
+ String thisEmail = getEmail().toString().toLowerCase();
- return otherPerson != null
- && otherPerson.getName().equals(getName());
+ return otherEmail.equals(thisEmail);
}
/**
@@ -85,22 +121,23 @@ public boolean equals(Object other) {
}
// instanceof handles nulls
- if (!(other instanceof Person)) {
+ if (!(other instanceof Contact)) {
return false;
}
- Person otherPerson = (Person) other;
- return name.equals(otherPerson.name)
- && phone.equals(otherPerson.phone)
- && email.equals(otherPerson.email)
- && address.equals(otherPerson.address)
- && tags.equals(otherPerson.tags);
+ Contact otherContact = (Contact) other;
+ return name.equals(otherContact.name)
+ && phone.equals(otherContact.phone)
+ && email.equals(otherContact.email)
+ && address.equals(otherContact.address)
+ && tags.equals(otherContact.tags)
+ && note.equals(otherContact.note);
}
@Override
public int hashCode() {
// use this method for custom fields hashing instead of implementing your own
- return Objects.hash(name, phone, email, address, tags);
+ return Objects.hash(name, phone, email, address, tags, note);
}
@Override
@@ -111,6 +148,7 @@ public String toString() {
.add("email", email)
.add("address", address)
.add("tags", tags)
+ .add("note", note)
.toString();
}
diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/contact/Email.java
similarity index 91%
rename from src/main/java/seedu/address/model/person/Email.java
rename to src/main/java/seedu/address/model/contact/Email.java
index c62e512bc29..55c3a9e704d 100644
--- a/src/main/java/seedu/address/model/person/Email.java
+++ b/src/main/java/seedu/address/model/contact/Email.java
@@ -1,10 +1,10 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
/**
- * Represents a Person's email in the address book.
+ * Represents a Contact's email in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)}
*/
public class Email {
@@ -15,6 +15,8 @@ public class Email {
+ "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"
+ + " - These special characters can only appear between alphanumeric characters "
+ + "and cannot be placed next to each other. E.g. 'john+_doe@email.com' is invalid\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"
diff --git a/src/main/java/seedu/address/model/contact/Name.java b/src/main/java/seedu/address/model/contact/Name.java
new file mode 100644
index 00000000000..2b5bc35ec24
--- /dev/null
+++ b/src/main/java/seedu/address/model/contact/Name.java
@@ -0,0 +1,66 @@
+package seedu.address.model.contact;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a Contact's name in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidName(String)}
+ */
+public class Name {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Names should not be blank and may only contain letters, numbers, spaces, hyphens (-), apostrophes ('), "
+ + "and dots ("
+ + ".).";
+
+ /*
+ * The first character of the address must not be a whitespace,
+ * otherwise " " (a blank string) becomes a valid input.
+ */
+ public static final String VALIDATION_REGEX = "^[\\p{L}\\p{N} .’'\\-/]+$";
+
+ public final String fullName;
+
+ /**
+ * Constructs a {@code Name}.
+ *
+ * @param name A valid name.
+ */
+ public Name(String name) {
+ requireNonNull(name);
+ checkArgument(isValidName(name), MESSAGE_CONSTRAINTS);
+ fullName = name;
+ }
+
+ /**
+ * Returns true if a given string is a valid name.
+ */
+ public static boolean isValidName(String test) {
+ return !test.trim().isEmpty() && test.matches(VALIDATION_REGEX);
+ }
+
+
+ @Override public String toString() {
+ return fullName;
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Name)) {
+ return false;
+ }
+
+ Name otherName = (Name) other;
+ return fullName.equals(otherName.fullName);
+ }
+
+ @Override public int hashCode() {
+ return fullName.hashCode();
+ }
+
+}
diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/contact/NameContainsKeywordsPredicate.java
similarity index 83%
rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
rename to src/main/java/seedu/address/model/contact/NameContainsKeywordsPredicate.java
index 62d19be2977..11ce9dd3b45 100644
--- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java
+++ b/src/main/java/seedu/address/model/contact/NameContainsKeywordsPredicate.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import java.util.List;
import java.util.function.Predicate;
@@ -7,9 +7,9 @@
import seedu.address.commons.util.ToStringBuilder;
/**
- * Tests that a {@code Person}'s {@code Name} matches any of the keywords given.
+ * Tests that a {@code Contact}'s {@code Name} matches any of the keywords given.
*/
-public class NameContainsKeywordsPredicate implements Predicate {
+public class NameContainsKeywordsPredicate implements Predicate {
private final List keywords;
public NameContainsKeywordsPredicate(List keywords) {
@@ -17,9 +17,9 @@ public NameContainsKeywordsPredicate(List keywords) {
}
@Override
- public boolean test(Person person) {
+ public boolean test(Contact contact) {
return keywords.stream()
- .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword));
+ .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(contact.getName().fullName, keyword));
}
@Override
diff --git a/src/main/java/seedu/address/model/contact/Note.java b/src/main/java/seedu/address/model/contact/Note.java
new file mode 100644
index 00000000000..09d1bb47802
--- /dev/null
+++ b/src/main/java/seedu/address/model/contact/Note.java
@@ -0,0 +1,63 @@
+package seedu.address.model.contact;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Objects;
+
+/**
+ * Represents a Contact's note in the address book.
+ * Guarantees: immutable.
+ */
+public class Note {
+ private final String note;
+
+ /**
+ * Constructs a {@code Note}.
+ *
+ * @param note A valid note string.
+ */
+ public Note(String note) {
+ requireNonNull(note);
+ if (!note.trim().isEmpty()) {
+ this.note = note.trim();
+ } else {
+ this.note = "";
+ }
+ }
+
+ /**
+ * Returns the note value.
+ */
+ public String getNote() {
+ return note;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Note)) {
+ return false;
+ }
+
+ Note otherNote = (Note) other;
+ return note.equals(otherNote.note);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(note);
+ }
+
+ @Override
+ public String toString() {
+ return note;
+ }
+
+ public boolean isEmpty() {
+ return note.isEmpty();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/contact/Phone.java
similarity index 75%
rename from src/main/java/seedu/address/model/person/Phone.java
rename to src/main/java/seedu/address/model/contact/Phone.java
index d733f63d739..c658da41621 100644
--- a/src/main/java/seedu/address/model/person/Phone.java
+++ b/src/main/java/seedu/address/model/contact/Phone.java
@@ -1,18 +1,21 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
/**
- * Represents a Person's phone number in the address book.
+ * Represents a Contact's phone number in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)}
*/
public class Phone {
public static final String MESSAGE_CONSTRAINTS =
- "Phone numbers should only contain numbers, and it should be at least 3 digits long";
- public static final String VALIDATION_REGEX = "\\d{3,}";
+ "Phone numbers should only contain numbers (no '+' symbol for country codes), "
+ + "and should be at least 3 digits and at most 17 digits long.\n"
+ + "Example: 6598765432 (country code + number)";
+
+ public static final String VALIDATION_REGEX = "^\\d{3,17}$";
public final String value;
/**
diff --git a/src/main/java/seedu/address/model/contact/UniquePersonList.java b/src/main/java/seedu/address/model/contact/UniquePersonList.java
new file mode 100644
index 00000000000..31b43b82d49
--- /dev/null
+++ b/src/main/java/seedu/address/model/contact/UniquePersonList.java
@@ -0,0 +1,150 @@
+package seedu.address.model.contact;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Iterator;
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.contact.exceptions.DuplicatePersonException;
+import seedu.address.model.contact.exceptions.PersonNotFoundException;
+
+/**
+ * A list of contacts that enforces uniqueness between its elements and does not allow nulls.
+ * A contact is considered unique by comparing using {@code Contact#isSamePerson(Contact)}. As such, adding and
+ * updating of contacts uses Contact#isSamePerson(Contact) for equality to ensure that the contact being added
+ * or updated is unique in terms of identity in the UniquePersonList. However, the removal of a contact uses
+ * Contact#equals(Object) to ensure that the contact with exactly the same fields will be removed.
+ *
+ * Supports a minimal set of list operations.
+ *
+ * @see Contact#isSamePerson(Contact)
+ */
+public class UniquePersonList implements Iterable {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+
+ /**
+ * Returns true if the list contains an equivalent contact as the given argument.
+ */
+ public boolean contains(Contact toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSamePerson);
+ }
+
+ /**
+ * Adds a contact to the list.
+ * The contact must not already exist in the list.
+ */
+ public void add(Contact toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicatePersonException();
+ }
+ internalList.add(toAdd);
+ }
+
+ /**
+ * Replaces the contact {@code target} in the list with {@code editedContact}.
+ * {@code target} must exist in the list.
+ * The contact identity of {@code editedContact} must not be the same as another existing contact in the list.
+ */
+ public void setPerson(Contact target, Contact editedContact) {
+ requireAllNonNull(target, editedContact);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new PersonNotFoundException();
+ }
+
+ if (!target.isSamePerson(editedContact) && contains(editedContact)) {
+ throw new DuplicatePersonException();
+ }
+
+ internalList.set(index, editedContact);
+ }
+
+ /**
+ * Removes the equivalent contact from the list.
+ * The contact must exist in the list.
+ */
+ public void remove(Contact toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new PersonNotFoundException();
+ }
+ }
+
+ public void setPersons(UniquePersonList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ }
+
+ /**
+ * Replaces the contents of this list with {@code contacts}.
+ * {@code contacts} must not contain duplicate contacts.
+ */
+ public void setPersons(List contacts) {
+ requireAllNonNull(contacts);
+ if (!personsAreUnique(contacts)) {
+ throw new DuplicatePersonException();
+ }
+
+ internalList.setAll(contacts);
+ }
+
+ /**
+ * Returns the backing list as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return internalUnmodifiableList;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof UniquePersonList)) {
+ return false;
+ }
+
+ UniquePersonList otherUniquePersonList = (UniquePersonList) other;
+ return internalList.equals(otherUniquePersonList.internalList);
+ }
+
+ @Override
+ public int hashCode() {
+ return internalList.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return internalList.toString();
+ }
+
+ /**
+ * Returns true if {@code contacts} contains only unique contacts.
+ */
+ private boolean personsAreUnique(List contacts) {
+ for (int i = 0; i < contacts.size() - 1; i++) {
+ for (int j = i + 1; j < contacts.size(); j++) {
+ if (contacts.get(i).isSamePerson(contacts.get(j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/contact/exceptions/DuplicatePersonException.java
similarity index 86%
rename from src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java
rename to src/main/java/seedu/address/model/contact/exceptions/DuplicatePersonException.java
index d7290f59442..e0bebe0bbdf 100644
--- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java
+++ b/src/main/java/seedu/address/model/contact/exceptions/DuplicatePersonException.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person.exceptions;
+package seedu.address.model.contact.exceptions;
/**
* Signals that the operation will result in duplicate Persons (Persons are considered duplicates if they have the same
diff --git a/src/main/java/seedu/address/model/contact/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/contact/exceptions/PersonNotFoundException.java
new file mode 100644
index 00000000000..04938b0c3e5
--- /dev/null
+++ b/src/main/java/seedu/address/model/contact/exceptions/PersonNotFoundException.java
@@ -0,0 +1,6 @@
+package seedu.address.model.contact.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified contact.
+ */
+public class PersonNotFoundException extends RuntimeException {}
diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java
deleted file mode 100644
index cc0a68d79f9..00000000000
--- a/src/main/java/seedu/address/model/person/UniquePersonList.java
+++ /dev/null
@@ -1,150 +0,0 @@
-package seedu.address.model.person;
-
-import static java.util.Objects.requireNonNull;
-import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
-
-import java.util.Iterator;
-import java.util.List;
-
-import javafx.collections.FXCollections;
-import javafx.collections.ObservableList;
-import seedu.address.model.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
- * 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)
- */
-public class UniquePersonList implements Iterable {
-
- private final ObservableList internalList = FXCollections.observableArrayList();
- private final ObservableList internalUnmodifiableList =
- FXCollections.unmodifiableObservableList(internalList);
-
- /**
- * Returns true if the list contains an equivalent person as the given argument.
- */
- public boolean contains(Person toCheck) {
- requireNonNull(toCheck);
- return internalList.stream().anyMatch(toCheck::isSamePerson);
- }
-
- /**
- * Adds a person to the list.
- * The person must not already exist in the list.
- */
- public void add(Person toAdd) {
- requireNonNull(toAdd);
- if (contains(toAdd)) {
- throw new DuplicatePersonException();
- }
- 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)) {
- throw new PersonNotFoundException();
- }
- }
-
- public void setPersons(UniquePersonList replacement) {
- requireNonNull(replacement);
- internalList.setAll(replacement.internalList);
- }
-
- /**
- * Replaces the contents of this list with {@code persons}.
- * {@code persons} must not contain duplicate persons.
- */
- public void setPersons(List persons) {
- requireAllNonNull(persons);
- if (!personsAreUnique(persons)) {
- throw new DuplicatePersonException();
- }
-
- internalList.setAll(persons);
- }
-
- /**
- * Returns the backing list as an unmodifiable {@code ObservableList}.
- */
- public ObservableList asUnmodifiableObservableList() {
- return internalUnmodifiableList;
- }
-
- @Override
- public Iterator iterator() {
- return internalList.iterator();
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == this) {
- return true;
- }
-
- // instanceof handles nulls
- if (!(other instanceof UniquePersonList)) {
- return false;
- }
-
- UniquePersonList otherUniquePersonList = (UniquePersonList) other;
- return internalList.equals(otherUniquePersonList.internalList);
- }
-
- @Override
- public int hashCode() {
- return internalList.hashCode();
- }
-
- @Override
- public String toString() {
- return internalList.toString();
- }
-
- /**
- * Returns true if {@code persons} contains only unique persons.
- */
- private boolean personsAreUnique(List persons) {
- for (int i = 0; i < persons.size() - 1; i++) {
- for (int j = i + 1; j < persons.size(); j++) {
- if (persons.get(i).isSamePerson(persons.get(j))) {
- return false;
- }
- }
- }
- return true;
- }
-}
diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
deleted file mode 100644
index fa764426ca7..00000000000
--- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package seedu.address.model.person.exceptions;
-
-/**
- * Signals that the operation is unable to find the specified person.
- */
-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
index f1a0d4e233b..31c4a6170fd 100644
--- a/src/main/java/seedu/address/model/tag/Tag.java
+++ b/src/main/java/seedu/address/model/tag/Tag.java
@@ -9,8 +9,10 @@
*/
public class Tag {
- public static final String MESSAGE_CONSTRAINTS = "Tags names should be alphanumeric";
- public static final String VALIDATION_REGEX = "\\p{Alnum}+";
+ public static final String MESSAGE_CONSTRAINTS = "Tags should be specified as 't/customer' or 't/service'. "
+ + "You may include both by specifying 't/customer t/service'";
+ public static final String TAGNAME_SPECIFICATION = "Tag must be either 'customer' or 'service'.";
+ public static final String VALIDATION_REGEX = "customer|service";
public final String tagName;
@@ -32,6 +34,15 @@ public static boolean isValidTagName(String test) {
return test.matches(VALIDATION_REGEX);
}
+ /**
+ * Returns the appropriate CSS class name based on the tagname.
+ * @return A string representing the CSS class name for the tagname.
+ * Returns "customer-tag" for "customer" and "service-tag" for "service".
+ */
+ public String getStyleClass() {
+ return tagName.equalsIgnoreCase("customer") ? "customer-tag" : "service-tag";
+ }
+
@Override
public boolean equals(Object other) {
if (other == this) {
diff --git a/src/main/java/seedu/address/model/trip/Accommodation.java b/src/main/java/seedu/address/model/trip/Accommodation.java
new file mode 100644
index 00000000000..8116553a28d
--- /dev/null
+++ b/src/main/java/seedu/address/model/trip/Accommodation.java
@@ -0,0 +1,60 @@
+package seedu.address.model.trip;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a Trip's accommodation in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidAccommodation(String)}
+ */
+public class Accommodation {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Accommodation should not be blank";
+
+ public final String accommodation;
+
+ /**
+ * Constructs a {@code Accommodation}.
+ *
+ * @param accommodation A valid accommodation.
+ */
+ public Accommodation(String accommodation) {
+ requireNonNull(accommodation);
+ checkArgument(isValidAccommodation(accommodation), MESSAGE_CONSTRAINTS);
+ this.accommodation = accommodation;
+ }
+
+ /**
+ * Returns true if a given string is a valid accommodation.
+ */
+ public static boolean isValidAccommodation(String test) {
+ return test != null && !test.trim().isEmpty();
+ }
+
+
+ @Override
+ public String toString() {
+ return accommodation;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Accommodation)) {
+ return false;
+ }
+
+ Accommodation otherAccommodation = (Accommodation) other;
+ return accommodation.equals(otherAccommodation.accommodation);
+ }
+
+ @Override
+ public int hashCode() {
+ return accommodation.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/trip/Itinerary.java b/src/main/java/seedu/address/model/trip/Itinerary.java
new file mode 100644
index 00000000000..0fc324fc21e
--- /dev/null
+++ b/src/main/java/seedu/address/model/trip/Itinerary.java
@@ -0,0 +1,60 @@
+package seedu.address.model.trip;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+/**
+ * Represents a Trip's itinerary in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidItinerary(String)}
+ */
+public class Itinerary {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Itinerary should not be blank";
+
+ public final String itinerary;
+
+ /**
+ * Constructs a {@code Itinerary}.
+ *
+ * @param itinerary A valid itinerary.
+ */
+ public Itinerary(String itinerary) {
+ requireNonNull(itinerary);
+ checkArgument(isValidItinerary(itinerary), MESSAGE_CONSTRAINTS);
+ this.itinerary = itinerary;
+ }
+
+ /**
+ * Returns true if a given string is a valid itinerary.
+ */
+ public static boolean isValidItinerary(String test) {
+ return test != null && !test.trim().isEmpty();
+ }
+
+
+ @Override
+ public String toString() {
+ return itinerary;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Itinerary)) {
+ return false;
+ }
+
+ Itinerary otherItinerary = (Itinerary) other;
+ return itinerary.equals(otherItinerary.itinerary);
+ }
+
+ @Override
+ public int hashCode() {
+ return itinerary.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/trip/Note.java b/src/main/java/seedu/address/model/trip/Note.java
new file mode 100644
index 00000000000..8e3b4c0c11d
--- /dev/null
+++ b/src/main/java/seedu/address/model/trip/Note.java
@@ -0,0 +1,51 @@
+package seedu.address.model.trip;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Represents a Trip's note in the address book.
+ * Guarantees: immutable
+ */
+public class Note {
+
+ public final String note;
+
+ /**
+ * Constructs a {@code Note}.
+ *
+ * @param note A note, if there is no note, simply just set it to an empty string
+ */
+ public Note(String note) {
+ requireNonNull(note);
+ if (!note.trim().isEmpty()) {
+ this.note = note.trim();
+ } else {
+ this.note = "";
+ }
+ }
+
+ @Override
+ public String toString() {
+ return note;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof Note)) {
+ return false;
+ }
+
+ Note otherNote = (Note) other;
+ return note.equals(otherNote.note);
+ }
+
+ @Override
+ public int hashCode() {
+ return note.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/trip/Trip.java b/src/main/java/seedu/address/model/trip/Trip.java
new file mode 100644
index 00000000000..ac8b0ca34fc
--- /dev/null
+++ b/src/main/java/seedu/address/model/trip/Trip.java
@@ -0,0 +1,124 @@
+package seedu.address.model.trip;
+
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import seedu.address.commons.util.ToStringBuilder;
+import seedu.address.model.contact.Name;
+
+/**
+ * Represents a Trip in the address book.
+ * Guarantees: details are present and not null, field values are validated, immutable.
+ */
+public class Trip {
+ private final TripName name;
+ private final Accommodation accommodation;
+ private final Itinerary itinerary;
+ private final TripDate date;
+ private final Set customerNames = new HashSet<>();
+ private final Note note;
+
+ /**
+ * Every field must be present and not null.
+ */
+ public Trip(TripName name, Accommodation accommodation, Itinerary itinerary, TripDate date,
+ Set customerNames, Note note) {
+ requireAllNonNull(name, accommodation, itinerary, date, customerNames, note);
+ this.name = name;
+ this.accommodation = accommodation;
+ this.itinerary = itinerary;
+ this.date = date;
+ this.customerNames.addAll(customerNames);
+ this.note = note;
+ }
+
+ public TripName getName() {
+ return name;
+ }
+
+ public Accommodation getAccommodation() {
+ return accommodation;
+ }
+
+ public Itinerary getItinerary() {
+ return itinerary;
+ }
+
+ public TripDate getDate() {
+ return date;
+ }
+
+ public Set getCustomerNames() {
+ return Collections.unmodifiableSet(customerNames);
+ }
+
+ public Note getNote() {
+ return note;
+ }
+
+ /**
+ * Returns true if both trips have the same name.
+ * This defines a weaker notion of equality between two trips.
+ */
+ public boolean isSameTrip(Trip otherTrip) {
+ if (otherTrip == this) {
+ return true;
+ }
+
+ return otherTrip != null
+ && otherTrip.getName().name.equalsIgnoreCase(getName().name);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Trip trip = (Trip) o;
+ return Objects.equals(name, trip.name) && Objects.equals(accommodation, trip.accommodation)
+ && Objects.equals(itinerary, trip.itinerary)
+ && Objects.equals(date, trip.date)
+ && Objects.equals(customerNames, trip.customerNames)
+ && Objects.equals(note, trip.note);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, accommodation, itinerary, date, customerNames, note);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .add("name", name)
+ .add("accommodation", accommodation)
+ .add("itinerary", itinerary)
+ .add("date", date)
+ .add("customerNames", customerNames)
+ .add("note", note)
+ .toString();
+ }
+
+ /**
+ * Returns a formatted string representation of the trip, including its name,
+ * accommodation, itinerary, date, and associated customer names.
+ *
+ * @return A string containing trip details in a readable format.
+ */
+ public String toListString() {
+ return String.format("Name = '%s', "
+ + "Accommodation = '%s', "
+ + "Itinerary = '%s', "
+ + "Date = '%s', "
+ + "Customer Names = '%s'",
+ name, accommodation, itinerary, date, customerNames);
+ }
+
+
+
+
+}
diff --git a/src/main/java/seedu/address/model/trip/TripDate.java b/src/main/java/seedu/address/model/trip/TripDate.java
new file mode 100644
index 00000000000..7d5773f7153
--- /dev/null
+++ b/src/main/java/seedu/address/model/trip/TripDate.java
@@ -0,0 +1,106 @@
+package seedu.address.model.trip;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.AppUtil.checkArgument;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.format.ResolverStyle;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+
+/**
+ * Represents a Trip's date in the address book.
+ * Guarantees: immutable; is valid as declared in {@link #isValidTripDate(String)}
+ */
+public class TripDate {
+
+ public static final String MESSAGE_CONSTRAINTS =
+ "Trip date should be in the format of d/M/yyyy and must be a valid date between 1950 and 2100 inclusive.";
+ public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("d/M/uuuu")
+ .withResolverStyle(ResolverStyle.STRICT);
+ private static final Logger logger = LogsCenter.getLogger(TripDate.class);
+
+ public final LocalDate date;
+
+ /**
+ * Constructs a {@code TripDate}.
+ *
+ * @param date A valid date.
+ */
+ public TripDate(String date) {
+ requireNonNull(date);
+ checkArgument(isValidTripDate(date), MESSAGE_CONSTRAINTS);
+
+ logger.fine("Creating new TripDate with input: " + date);
+
+ // Assert that preconditions are met
+ assert !date.trim().isEmpty() : "Date string cannot be empty";
+ assert isValidTripDate(date) : "Date must be valid before parsing";
+
+ try {
+ this.date = LocalDate.parse(date.trim(), DATE_FORMATTER);
+ logger.fine("Successfully parsed date: " + this.date);
+ } catch (DateTimeParseException e) {
+ logger.warning("Failed to parse date: " + date);
+ throw new IllegalArgumentException(MESSAGE_CONSTRAINTS);
+ }
+
+ // Post-construction validation
+ assert this.date.getYear() >= 1000 && this.date.getYear() <= 9999 : "Year must be between 1000-9999";
+ }
+
+ /**
+ * Returns true if a given string is a valid trip date.
+ */
+ public static boolean isValidTripDate(String test) {
+ if (test == null || test.trim().isEmpty()) {
+ logger.fine("Invalid date: null or empty string");
+ return false;
+ }
+
+ try {
+ LocalDate parsedDate = LocalDate.parse(test.trim(), DATE_FORMATTER);
+ // Additional validation for year range
+ int year = parsedDate.getYear();
+ if (year < 1950 || year > 2100) {
+ logger.fine("Invalid year in date: " + test);
+ return false;
+ }
+ return true;
+ } catch (DateTimeParseException e) {
+ logger.fine("Failed to parse date string: " + test);
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ String formatted = date.format(DATE_FORMATTER);
+ logger.finest("Formatting date to string: " + formatted);
+ return formatted;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof TripDate)) {
+ return false;
+ }
+
+ TripDate otherTripDate = (TripDate) other;
+ boolean isEqual = date.equals(otherTripDate.date);
+ logger.finest("Comparing TripDates: " + this + " and " + otherTripDate + " -> " + isEqual);
+ return isEqual;
+ }
+
+ @Override
+ public int hashCode() {
+ return date.hashCode();
+ }
+}
diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/trip/TripName.java
similarity index 76%
rename from src/main/java/seedu/address/model/person/Name.java
rename to src/main/java/seedu/address/model/trip/TripName.java
index 173f15b9b00..2f82f3661f3 100644
--- a/src/main/java/seedu/address/model/person/Name.java
+++ b/src/main/java/seedu/address/model/trip/TripName.java
@@ -1,13 +1,13 @@
-package seedu.address.model.person;
+package seedu.address.model.trip;
import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.AppUtil.checkArgument;
/**
- * Represents a Person's name in the address book.
+ * Represents a Trip's name in the address book.
* Guarantees: immutable; is valid as declared in {@link #isValidName(String)}
*/
-public class Name {
+public class TripName {
public static final String MESSAGE_CONSTRAINTS =
"Names should only contain alphanumeric characters and spaces, and it should not be blank";
@@ -18,17 +18,17 @@ public class Name {
*/
public static final String VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*";
- public final String fullName;
+ public final String name;
/**
* Constructs a {@code Name}.
*
* @param name A valid name.
*/
- public Name(String name) {
+ public TripName(String name) {
requireNonNull(name);
checkArgument(isValidName(name), MESSAGE_CONSTRAINTS);
- fullName = name;
+ this.name = name;
}
/**
@@ -41,7 +41,7 @@ public static boolean isValidName(String test) {
@Override
public String toString() {
- return fullName;
+ return name;
}
@Override
@@ -51,17 +51,16 @@ public boolean equals(Object other) {
}
// instanceof handles nulls
- if (!(other instanceof Name)) {
+ if (!(other instanceof TripName)) {
return false;
}
- Name otherName = (Name) other;
- return fullName.equals(otherName.fullName);
+ TripName otherName = (TripName) other;
+ return name.equals(otherName.name);
}
@Override
public int hashCode() {
- return fullName.hashCode();
+ return name.hashCode();
}
-
}
diff --git a/src/main/java/seedu/address/model/trip/UniqueTripList.java b/src/main/java/seedu/address/model/trip/UniqueTripList.java
new file mode 100644
index 00000000000..8dbfc48c396
--- /dev/null
+++ b/src/main/java/seedu/address/model/trip/UniqueTripList.java
@@ -0,0 +1,145 @@
+package seedu.address.model.trip;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.Iterator;
+import java.util.List;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.trip.exceptions.DuplicateTripException;
+import seedu.address.model.trip.exceptions.TripNotFoundException;
+
+/**
+ * A list of trips that enforces uniqueness between its elements and does not allow nulls.
+ * A trip is considered unique by comparing using {@code Trip#isSameTrip(Trip)}. As such, adding and updating of
+ * trips uses Trip#isSameTrip(Trip) for equality so as to ensure that the trip being added or updated is
+ * unique in terms of identity in the UniqueTripList. However, the removal of a trip uses Trip#equals(Object) so
+ * as to ensure that the trip with exactly the same fields will be removed.
+ *
+ * Supports a minimal set of list operations.
+ *
+ * @see Trip#isSameTrip(Trip)
+ */
+public class UniqueTripList implements Iterable {
+
+ private final ObservableList internalList = FXCollections.observableArrayList();
+ private final ObservableList internalUnmodifiableList =
+ FXCollections.unmodifiableObservableList(internalList);
+
+ /**
+ * Returns true if the list contains an equivalent trip as the given argument.
+ */
+ public boolean contains(Trip toCheck) {
+ requireNonNull(toCheck);
+ return internalList.stream().anyMatch(toCheck::isSameTrip);
+ }
+
+ /**
+ * Adds a trip to the list.
+ * The trip must not already exist in the list.
+ */
+ public void add(Trip toAdd) {
+ requireNonNull(toAdd);
+ if (contains(toAdd)) {
+ throw new DuplicateTripException();
+ }
+ internalList.add(toAdd);
+ }
+
+ /**
+ * Replaces the trip {@code target} in the list with {@code editedTrip}.
+ * {@code target} must exist in the list.
+ * The trip identity of {@code editedTrip} must not be the same as another existing trip in the list.
+ */
+ public void setTrip(Trip target, Trip editedTrip) {
+ requireAllNonNull(target, editedTrip);
+
+ int index = internalList.indexOf(target);
+ if (index == -1) {
+ throw new TripNotFoundException();
+ }
+
+ if (!target.isSameTrip(editedTrip) && contains(editedTrip)) {
+ throw new DuplicateTripException();
+ }
+
+ internalList.set(index, editedTrip);
+ }
+
+ /**
+ * Removes the equivalent trip from the list.
+ * The trip must exist in the list.
+ */
+ public void remove(Trip toRemove) {
+ requireNonNull(toRemove);
+ if (!internalList.remove(toRemove)) {
+ throw new TripNotFoundException();
+ }
+ }
+
+ public void setTrips(UniqueTripList replacement) {
+ requireNonNull(replacement);
+ internalList.setAll(replacement.internalList);
+ }
+
+ /**
+ * Replaces the contents of this list with {@code trips}.
+ * {@code trips} must not contain duplicate trips.
+ */
+ public void setTrips(List trips) {
+ requireAllNonNull(trips);
+ if (!tripsAreUnique(trips)) {
+ throw new DuplicateTripException();
+ }
+
+ internalList.setAll(trips);
+ }
+
+ /**
+ * Returns the backing list as an unmodifiable {@code ObservableList}.
+ */
+ public ObservableList asUnmodifiableObservableList() {
+ return internalUnmodifiableList;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return internalList.iterator();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ // instanceof handles nulls
+ if (!(other instanceof UniqueTripList)) {
+ return false;
+ }
+
+ UniqueTripList otherTripList = (UniqueTripList) other;
+ return internalList.equals(otherTripList.internalList);
+ }
+
+ @Override
+ public int hashCode() {
+ return internalList.hashCode();
+ }
+
+ /**
+ * Returns true if {@code trips} contains only unique trips.
+ */
+ private boolean tripsAreUnique(List trips) {
+ for (int i = 0; i < trips.size() - 1; i++) {
+ for (int j = i + 1; j < trips.size(); j++) {
+ if (trips.get(i).isSameTrip(trips.get(j))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/seedu/address/model/trip/exceptions/DuplicateTripException.java b/src/main/java/seedu/address/model/trip/exceptions/DuplicateTripException.java
new file mode 100644
index 00000000000..2f6692a64c9
--- /dev/null
+++ b/src/main/java/seedu/address/model/trip/exceptions/DuplicateTripException.java
@@ -0,0 +1,11 @@
+package seedu.address.model.trip.exceptions;
+
+/**
+ * Signals that the operation will result in duplicate Trips (Trips are considered duplicates if they have the same
+ * identity).
+ */
+public class DuplicateTripException extends RuntimeException {
+ public DuplicateTripException() {
+ super("Operation would result in duplicate trips");
+ }
+}
diff --git a/src/main/java/seedu/address/model/trip/exceptions/TripNotFoundException.java b/src/main/java/seedu/address/model/trip/exceptions/TripNotFoundException.java
new file mode 100644
index 00000000000..87e1df154a8
--- /dev/null
+++ b/src/main/java/seedu/address/model/trip/exceptions/TripNotFoundException.java
@@ -0,0 +1,6 @@
+package seedu.address.model.trip.exceptions;
+
+/**
+ * Signals that the operation is unable to find the specified trip.
+ */
+public class TripNotFoundException extends RuntimeException {}
diff --git a/src/main/java/seedu/address/model/util/DataLoadingUtil.java b/src/main/java/seedu/address/model/util/DataLoadingUtil.java
new file mode 100644
index 00000000000..9047f44b488
--- /dev/null
+++ b/src/main/java/seedu/address/model/util/DataLoadingUtil.java
@@ -0,0 +1,61 @@
+package seedu.address.model.util;
+
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.model.AddressBook;
+import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTripBook;
+import seedu.address.model.TripBook;
+import seedu.address.storage.Storage;
+
+/**
+ * Utility class for loading data from storage.
+ */
+public class DataLoadingUtil {
+ private static final Logger logger = LogsCenter.getLogger(DataLoadingUtil.class);
+
+ /**
+ * Loads the address book from storage.
+ * If the file doesn't exist or there's an error, returns a sample address book.
+ */
+ public static ReadOnlyAddressBook loadAddressBook(Storage storage) {
+ logger.info("Using data file : " + storage.getAddressBookFilePath());
+
+ Optional addressBookOptional;
+ try {
+ addressBookOptional = storage.readAddressBook();
+ if (!addressBookOptional.isPresent()) {
+ logger.info("Creating a new data file " + storage.getAddressBookFilePath()
+ + " populated with a sample AddressBook.");
+ }
+ return 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.");
+ return new AddressBook();
+ }
+ }
+
+ /**
+ * Loads the trip book from storage.
+ * If the file doesn't exist or there's an error, returns an empty trip book.
+ */
+ public static ReadOnlyTripBook loadTripBook(Storage storage) {
+ Optional tripBookOptional;
+ try {
+ tripBookOptional = storage.readTripBook();
+ if (!tripBookOptional.isPresent()) {
+ logger.info("Creating a new data file " + storage.getTripBookFilePath()
+ + " for TripBook.");
+ }
+ return tripBookOptional.orElseGet(SampleDataUtil::getSampleTripBook);
+ } catch (DataLoadingException e) {
+ logger.warning("Data file at " + storage.getTripBookFilePath() + " could not be loaded."
+ + " Will be starting with an empty TripBook.");
+ return new TripBook();
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java
index 1806da4facf..83670cd5326 100644
--- a/src/main/java/seedu/address/model/util/SampleDataUtil.java
+++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java
@@ -1,53 +1,80 @@
package seedu.address.model.util;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
+import seedu.address.model.ReadOnlyTripBook;
+import seedu.address.model.TripBook;
+import seedu.address.model.contact.Address;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Name;
+import seedu.address.model.contact.Note;
+import seedu.address.model.contact.Phone;
import seedu.address.model.tag.Tag;
+import seedu.address.model.trip.Accommodation;
+import seedu.address.model.trip.Itinerary;
+import seedu.address.model.trip.Trip;
+import seedu.address.model.trip.TripDate;
+import seedu.address.model.trip.TripName;
/**
* Contains utility methods for populating {@code AddressBook} with sample data.
*/
public class SampleDataUtil {
- public static Person[] getSamplePersons() {
- return new Person[] {
- new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
+ public static Contact[] getSamplePersons() {
+ return new Contact[] {
+ new Contact(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"),
new Address("Blk 30 Geylang Street 29, #06-40"),
- getTagSet("friends")),
- new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"),
- new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"),
- getTagSet("colleagues", "friends")),
- new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
+ getTagSet("customer")),
+ new Contact(new Name("XYZ Hotel"), new Phone("63451234"), new Email("xyz_hotel@example.com"),
+ new Address("Blk 30 Lorong 3 Serangoon Gardens"),
+ getTagSet("service"), new Note("Fully booked during the holidays")),
+ new Contact(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"),
new Address("Blk 11 Ang Mo Kio Street 74, #11-04"),
- getTagSet("neighbours")),
- new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"),
+ getTagSet(), new Note("Allergic to peanuts")),
+ new Contact(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"),
new Address("Blk 436 Serangoon Gardens Street 26, #16-43"),
- getTagSet("family")),
- new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"),
- new Address("Blk 47 Tampines Street 20, #17-35"),
- getTagSet("classmates")),
- new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"),
- new Address("Blk 45 Aljunied Street 85, #11-31"),
- getTagSet("colleagues"))
+ getTagSet("customer")),
+ new Contact(new Name("Irfan's Cake Shop"), new Phone("92492021"), new Email("irfan@example.com"),
+ new Address("Blk 47 Tampines Street 20, #01-35"),
+ getTagSet("customer", "service")),
+ new Contact(new Name("Gardens by the Bay"), new Phone("64206848"),
+ new Email("feedback@gardensbythebay.com.sg"), new Address("18 Marina Gardens Drive"),
+ getTagSet("service"), new Note("Opens 9am - 9pm daily"))
};
}
public static ReadOnlyAddressBook getSampleAddressBook() {
AddressBook sampleAb = new AddressBook();
- for (Person samplePerson : getSamplePersons()) {
- sampleAb.addPerson(samplePerson);
+ for (Contact sampleContact : getSamplePersons()) {
+ sampleAb.addPerson(sampleContact);
}
return sampleAb;
}
+ public static ReadOnlyTripBook getSampleTripBook() {
+ TripBook sampleTb = new TripBook();
+
+ TripName name = new TripName("Paris 2025");
+ Accommodation accommodation = new Accommodation("Hotel 81");
+ Itinerary itinerary = new Itinerary("Eat baguettes");
+ TripDate date = new TripDate("12/06/2025");
+ Set customerNames = new HashSet<>();
+ customerNames.add(new Name("John Doe"));
+ customerNames.add(new Name("Jane Doe"));
+
+ Trip trip = new Trip(name, accommodation, itinerary, date, customerNames,
+ new seedu.address.model.trip.Note("Customer prefers window seat"));
+ sampleTb.addTrip(trip);
+
+ return sampleTb;
+ }
+
/**
* Returns a tag set containing the list of strings given.
*/
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
index bd1ca0f56c8..c1c0c4b754f 100644
--- a/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
+++ b/src/main/java/seedu/address/storage/JsonAdaptedPerson.java
@@ -10,33 +10,35 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
+import seedu.address.model.contact.Address;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Name;
+import seedu.address.model.contact.Note;
+import seedu.address.model.contact.Phone;
import seedu.address.model.tag.Tag;
/**
- * Jackson-friendly version of {@link Person}.
+ * Jackson-friendly version of {@link Contact}.
*/
class JsonAdaptedPerson {
- public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!";
+ public static final String MISSING_FIELD_MESSAGE_FORMAT = "Contact's %s field is missing!";
private final String name;
private final String phone;
private final String email;
private final String address;
private final List tags = new ArrayList<>();
+ private final String note;
/**
- * Constructs a {@code JsonAdaptedPerson} with the given person details.
+ * Constructs a {@code JsonAdaptedPerson} with the given contact 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("tags") List tags, @JsonProperty("note") String note) {
this.name = name;
this.phone = phone;
this.email = email;
@@ -44,12 +46,13 @@ public JsonAdaptedPerson(@JsonProperty("name") String name, @JsonProperty("phone
if (tags != null) {
this.tags.addAll(tags);
}
+ this.note = note;
}
/**
- * Converts a given {@code Person} into this class for Jackson use.
+ * Converts a given {@code Contact} into this class for Jackson use.
*/
- public JsonAdaptedPerson(Person source) {
+ public JsonAdaptedPerson(Contact source) {
name = source.getName().fullName;
phone = source.getPhone().value;
email = source.getEmail().value;
@@ -57,14 +60,15 @@ public JsonAdaptedPerson(Person source) {
tags.addAll(source.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList()));
+ note = source.getNote().getNote();
}
/**
- * Converts this Jackson-friendly adapted person object into the model's {@code Person} object.
+ * Converts this Jackson-friendly adapted contact object into the model's {@code Contact} object.
*
- * @throws IllegalValueException if there were any data constraints violated in the adapted person.
+ * @throws IllegalValueException if there were any data constraints violated in the adapted contact.
*/
- public Person toModelType() throws IllegalValueException {
+ public Contact toModelType() throws IllegalValueException {
final List personTags = new ArrayList<>();
for (JsonAdaptedTag tag : tags) {
personTags.add(tag.toModelType());
@@ -103,7 +107,8 @@ public Person toModelType() throws IllegalValueException {
final Address modelAddress = new Address(address);
final Set modelTags = new HashSet<>(personTags);
- return new Person(modelName, modelPhone, modelEmail, modelAddress, modelTags);
+ final Note modelNote = new Note(note != null ? note : "");
+ return new Contact(modelName, modelPhone, modelEmail, modelAddress, modelTags, modelNote);
}
}
diff --git a/src/main/java/seedu/address/storage/JsonAdaptedTrip.java b/src/main/java/seedu/address/storage/JsonAdaptedTrip.java
new file mode 100644
index 00000000000..afdc6c3c1be
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonAdaptedTrip.java
@@ -0,0 +1,145 @@
+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.logic.parser.ParserUtil;
+import seedu.address.model.contact.Name;
+import seedu.address.model.trip.Accommodation;
+import seedu.address.model.trip.Itinerary;
+import seedu.address.model.trip.Note;
+import seedu.address.model.trip.Trip;
+import seedu.address.model.trip.TripDate;
+import seedu.address.model.trip.TripName;
+
+/**
+ * Jackson-friendly version of {@link Trip}.
+ */
+class JsonAdaptedTrip {
+
+ public static final String MISSING_FIELD_MESSAGE_FORMAT = "Trip's %s field is missing!";
+
+ private final String name;
+ private final String accommodation;
+ private final String itinerary;
+ private final String date;
+ private final List customerNames = new ArrayList<>();
+ private final String note;
+
+ /**
+ * Constructs a {@code JsonAdaptedTrip} with the given trip details.
+ */
+ @JsonCreator
+ public JsonAdaptedTrip(@JsonProperty("name") String name, @JsonProperty("accommodation") String accommodation,
+ @JsonProperty("itinerary") String itinerary, @JsonProperty("date") String date,
+ @JsonProperty("customerNames") List customerNames, @JsonProperty("note") String note) {
+ this.name = name;
+ this.accommodation = accommodation;
+ this.itinerary = itinerary;
+ this.date = date;
+ if (customerNames != null) {
+ this.customerNames.addAll(customerNames);
+ }
+ this.note = note;
+ }
+
+ /**
+ * Converts a given {@code Trip} into this class for Jackson use.
+ */
+ public JsonAdaptedTrip(Trip source) {
+ name = source.getName().toString();
+ accommodation = source.getAccommodation().toString();
+ itinerary = source.getItinerary().toString();
+ date = source.getDate().toString();
+ customerNames.addAll(source.getCustomerNames().stream()
+ .map(customer -> customer.fullName)
+ .collect(Collectors.toList()));
+ note = source.getNote().toString();
+ }
+
+ /**
+ * Converts this Jackson-friendly adapted trip object into the model's {@code Trip} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated in the adapted trip.
+ */
+ public Trip toModelType() throws IllegalValueException {
+ final Set modelCustomerNames = new HashSet<>();
+ for (String customerName : customerNames) {
+ try {
+ modelCustomerNames.add(ParserUtil.parseName(customerName));
+ } catch (IllegalValueException e) {
+ throw new IllegalValueException(Name.MESSAGE_CONSTRAINTS);
+ }
+ }
+
+ if (name == null) {
+ throw new IllegalValueException(
+ String.format(MISSING_FIELD_MESSAGE_FORMAT, TripName.class.getSimpleName()));
+ }
+
+ if (accommodation == null) {
+ throw new IllegalValueException(
+ String.format(MISSING_FIELD_MESSAGE_FORMAT, Accommodation.class.getSimpleName()));
+ }
+
+ if (itinerary == null) {
+ throw new IllegalValueException(
+ String.format(MISSING_FIELD_MESSAGE_FORMAT, Itinerary.class.getSimpleName()));
+ }
+
+ if (date == null) {
+ throw new IllegalValueException(
+ String.format(MISSING_FIELD_MESSAGE_FORMAT, TripDate.class.getSimpleName()));
+ }
+
+ if (note == null) {
+ throw new IllegalValueException(
+ String.format(MISSING_FIELD_MESSAGE_FORMAT, Note.class.getSimpleName()));
+ }
+
+ final TripName modelName;
+ final Accommodation modelAccommodation;
+ final Itinerary modelItinerary;
+ final TripDate modelDate;
+ final Note modelNote;
+
+ try {
+ modelName = ParserUtil.parseTripName(name);
+ } catch (IllegalValueException e) {
+ throw new IllegalValueException(TripName.MESSAGE_CONSTRAINTS);
+ }
+
+ try {
+ modelAccommodation = ParserUtil.parseAccommodation(accommodation);
+ } catch (IllegalValueException e) {
+ throw new IllegalValueException(Accommodation.MESSAGE_CONSTRAINTS);
+ }
+
+ try {
+ modelItinerary = ParserUtil.parseItinerary(itinerary);
+ } catch (IllegalValueException e) {
+ throw new IllegalValueException(Itinerary.MESSAGE_CONSTRAINTS);
+ }
+
+ try {
+ modelDate = ParserUtil.parseTripDate(date);
+ } catch (IllegalValueException e) {
+ throw new IllegalValueException(TripDate.MESSAGE_CONSTRAINTS);
+ }
+
+ try {
+ modelNote = ParserUtil.parseNote(note);
+ } catch (IllegalValueException e) {
+ throw new IllegalValueException("Note is invalid");
+ }
+
+ return new Trip(modelName, modelAccommodation, modelItinerary, modelDate, modelCustomerNames, modelNote);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
index 5efd834091d..c772b475775 100644
--- a/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
+++ b/src/main/java/seedu/address/storage/JsonSerializableAddressBook.java
@@ -11,7 +11,7 @@
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.model.AddressBook;
import seedu.address.model.ReadOnlyAddressBook;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
/**
* An Immutable AddressBook that is serializable to JSON format.
@@ -19,7 +19,7 @@
@JsonRootName(value = "addressbook")
class JsonSerializableAddressBook {
- public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate person(s).";
+ public static final String MESSAGE_DUPLICATE_PERSON = "Persons list contains duplicate contact(s).";
private final List persons = new ArrayList<>();
@@ -32,7 +32,7 @@ public JsonSerializableAddressBook(@JsonProperty("persons") List trips = new ArrayList<>();
+
+ /**
+ * Constructs a {@code JsonSerializableTripBook} with the given trips.
+ */
+ @JsonCreator
+ public JsonSerializableTripBook(@JsonProperty("trips") List trips) {
+ this.trips.addAll(trips);
+ }
+
+ /**
+ * Converts a given {@code ReadOnlyTripBook} into this class for Json use.
+ *
+ * @param source future changes to this will not affect the created {@code JsonSerializableTripBook}.
+ */
+ public JsonSerializableTripBook(ReadOnlyTripBook source) {
+ trips.addAll(source.getTripList().stream().map(JsonAdaptedTrip::new).collect(Collectors.toList()));
+ }
+
+ /**
+ * Converts this trip book into the model's {@code TripBook} object.
+ *
+ * @throws IllegalValueException if there were any data constraints violated.
+ */
+ public TripBook toModelType() throws IllegalValueException {
+ TripBook tripBook = new TripBook();
+ for (JsonAdaptedTrip jsonAdaptedTrip : trips) {
+ Trip trip = jsonAdaptedTrip.toModelType();
+ if (tripBook.hasTrip(trip)) {
+ throw new IllegalValueException(MESSAGE_DUPLICATE_TRIP);
+ }
+ tripBook.addTrip(trip);
+ }
+ return tripBook;
+ }
+}
diff --git a/src/main/java/seedu/address/storage/JsonTripBookStorage.java b/src/main/java/seedu/address/storage/JsonTripBookStorage.java
new file mode 100644
index 00000000000..165ddce22da
--- /dev/null
+++ b/src/main/java/seedu/address/storage/JsonTripBookStorage.java
@@ -0,0 +1,79 @@
+package seedu.address.storage;
+
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.logging.Logger;
+
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.commons.util.FileUtil;
+import seedu.address.commons.util.JsonUtil;
+import seedu.address.model.ReadOnlyTripBook;
+
+/**
+ * A class to access TripBook data stored as a json file on the hard disk.
+ */
+public class JsonTripBookStorage implements TripBookStorage {
+
+ private static final Logger logger = LogsCenter.getLogger(JsonTripBookStorage.class);
+
+ private Path filePath;
+
+ public JsonTripBookStorage(Path filePath) {
+ this.filePath = filePath;
+ }
+
+ public Path getTripBookFilePath() {
+ return filePath;
+ }
+
+ @Override
+ public Optional readTripBook() throws DataLoadingException {
+ return readTripBook(filePath);
+ }
+
+ /**
+ * Similar to {@link #readTripBook()}.
+ *
+ * @param filePath location of the data. Cannot be null.
+ * @throws DataLoadingException if loading the data from storage failed.
+ */
+ public Optional readTripBook(Path filePath) throws DataLoadingException {
+ requireNonNull(filePath);
+
+ Optional jsonTripBook = JsonUtil.readJsonFile(
+ filePath, JsonSerializableTripBook.class);
+ if (!jsonTripBook.isPresent()) {
+ return Optional.empty();
+ }
+
+ try {
+ return Optional.of(jsonTripBook.get().toModelType());
+ } catch (IllegalValueException ive) {
+ logger.info("Illegal values found in " + filePath + ": " + ive.getMessage());
+ throw new DataLoadingException(ive);
+ }
+ }
+
+ @Override
+ public void saveTripBook(ReadOnlyTripBook tripBook) throws IOException {
+ saveTripBook(tripBook, filePath);
+ }
+
+ /**
+ * Similar to {@link #saveTripBook(ReadOnlyTripBook)}.
+ *
+ * @param filePath location of the data. Cannot be null.
+ */
+ public void saveTripBook(ReadOnlyTripBook tripBook, Path filePath) throws IOException {
+ requireNonNull(tripBook);
+ requireNonNull(filePath);
+
+ FileUtil.createIfMissing(filePath);
+ JsonUtil.saveJsonFile(new JsonSerializableTripBook(tripBook), filePath);
+ }
+}
diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java
index 9fba0c7a1d6..14d8a57d63c 100644
--- a/src/main/java/seedu/address/storage/Storage.java
+++ b/src/main/java/seedu/address/storage/Storage.java
@@ -6,13 +6,14 @@
import seedu.address.commons.exceptions.DataLoadingException;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTripBook;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
/**
* API of the Storage component
*/
-public interface Storage extends AddressBookStorage, UserPrefsStorage {
+public interface Storage extends AddressBookStorage, UserPrefsStorage, TripBookStorage {
@Override
Optional readUserPrefs() throws DataLoadingException;
@@ -29,4 +30,12 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage {
@Override
void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException;
+ @Override
+ Path getTripBookFilePath();
+
+ @Override
+ Optional readTripBook() throws DataLoadingException;
+
+ @Override
+ void saveTripBook(ReadOnlyTripBook tripBook) throws IOException;
}
diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java
index 8b84a9024d5..cb24e49676f 100644
--- a/src/main/java/seedu/address/storage/StorageManager.java
+++ b/src/main/java/seedu/address/storage/StorageManager.java
@@ -8,6 +8,7 @@
import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.exceptions.DataLoadingException;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTripBook;
import seedu.address.model.ReadOnlyUserPrefs;
import seedu.address.model.UserPrefs;
@@ -19,13 +20,18 @@ public class StorageManager implements Storage {
private static final Logger logger = LogsCenter.getLogger(StorageManager.class);
private AddressBookStorage addressBookStorage;
private UserPrefsStorage userPrefsStorage;
+ private TripBookStorage tripBookStorage;
/**
- * Creates a {@code StorageManager} with the given {@code AddressBookStorage} and {@code UserPrefStorage}.
+ * Creates a {@code StorageManager} with the given
+ * {@code AddressBookStorage}, {@code UserPrefStorage} and {@code TripBookStorage}.
*/
- public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) {
+ public StorageManager(AddressBookStorage addressBookStorage,
+ UserPrefsStorage userPrefsStorage,
+ TripBookStorage tripBookStorage) {
this.addressBookStorage = addressBookStorage;
this.userPrefsStorage = userPrefsStorage;
+ this.tripBookStorage = tripBookStorage;
}
// ================ UserPrefs methods ==============================
@@ -45,7 +51,6 @@ public void saveUserPrefs(ReadOnlyUserPrefs userPrefs) throws IOException {
userPrefsStorage.saveUserPrefs(userPrefs);
}
-
// ================ AddressBook methods ==============================
@Override
@@ -75,4 +80,32 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath) thro
addressBookStorage.saveAddressBook(addressBook, filePath);
}
+ // ================ TripBook methods ==============================
+
+ @Override
+ public Path getTripBookFilePath() {
+ return tripBookStorage.getTripBookFilePath();
+ }
+
+ @Override
+ public Optional readTripBook() throws DataLoadingException {
+ return readTripBook(tripBookStorage.getTripBookFilePath());
+ }
+
+ @Override
+ public Optional readTripBook(Path filePath) throws DataLoadingException {
+ logger.fine("Attempting to read data from file: " + filePath);
+ return tripBookStorage.readTripBook(filePath);
+ }
+
+ @Override
+ public void saveTripBook(ReadOnlyTripBook tripBook) throws IOException {
+ saveTripBook(tripBook, tripBookStorage.getTripBookFilePath());
+ }
+
+ @Override
+ public void saveTripBook(ReadOnlyTripBook tripBook, Path filePath) throws IOException {
+ logger.fine("Attempting to write to data file: " + filePath);
+ tripBookStorage.saveTripBook(tripBook, filePath);
+ }
}
diff --git a/src/main/java/seedu/address/storage/TripBookStorage.java b/src/main/java/seedu/address/storage/TripBookStorage.java
new file mode 100644
index 00000000000..922494e9be9
--- /dev/null
+++ b/src/main/java/seedu/address/storage/TripBookStorage.java
@@ -0,0 +1,44 @@
+package seedu.address.storage;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.model.ReadOnlyTripBook;
+
+/**
+ * Represents a storage for {@link seedu.address.model.TripBook}.
+ */
+public interface TripBookStorage {
+
+ /**
+ * Returns the file path of the data file.
+ */
+ Path getTripBookFilePath();
+
+ /**
+ * Returns TripBook data as a {@link ReadOnlyTripBook}.
+ * Returns {@code Optional.empty()} if storage file is not found.
+ *
+ * @throws DataLoadingException if loading the data from storage failed.
+ */
+ Optional readTripBook() throws DataLoadingException;
+
+ /**
+ * @see #getTripBookFilePath()
+ */
+ Optional readTripBook(Path filePath) throws DataLoadingException;
+
+ /**
+ * Saves the given {@link ReadOnlyTripBook} to the storage.
+ * @param tripBook cannot be null.
+ * @throws IOException if there was any problem writing to the file.
+ */
+ void saveTripBook(ReadOnlyTripBook tripBook) throws IOException;
+
+ /**
+ * @see #saveTripBook(ReadOnlyTripBook)
+ */
+ void saveTripBook(ReadOnlyTripBook tripBook, Path filePath) throws IOException;
+}
diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java
index 3f16b2fcf26..b69439c5d15 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://ay2425s2-cs2103-f09-1.github.io/tp/UserGuide.html";
public static final String HELP_MESSAGE = "Refer to the user guide: " + USERGUIDE_URL;
private static final Logger logger = LogsCenter.getLogger(HelpWindow.class);
diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java
index 79e74ef37c0..bc30a6bf919 100644
--- a/src/main/java/seedu/address/ui/MainWindow.java
+++ b/src/main/java/seedu/address/ui/MainWindow.java
@@ -4,6 +4,7 @@
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
+import javafx.scene.control.Alert;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextInputControl;
import javafx.scene.input.KeyCombination;
@@ -13,6 +14,7 @@
import seedu.address.commons.core.GuiSettings;
import seedu.address.commons.core.LogsCenter;
import seedu.address.logic.Logic;
+import seedu.address.logic.commands.ClearCommand;
import seedu.address.logic.commands.CommandResult;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
@@ -32,6 +34,7 @@ public class MainWindow extends UiPart {
// Independent Ui parts residing in this Ui container
private PersonListPanel personListPanel;
+ private TripListPanel tripListPanel;
private ResultDisplay resultDisplay;
private HelpWindow helpWindow;
@@ -44,6 +47,9 @@ public class MainWindow extends UiPart {
@FXML
private StackPane personListPanelPlaceholder;
+ @FXML
+ private StackPane tripListPanelPlaceholder;
+
@FXML
private StackPane resultDisplayPlaceholder;
@@ -62,9 +68,9 @@ public MainWindow(Stage primaryStage, Logic logic) {
// Configure the UI
setWindowDefaultSize(logic.getGuiSettings());
+ logger.info("MainWindow initialized with primary stage and logic");
setAccelerators();
-
helpWindow = new HelpWindow();
}
@@ -110,29 +116,41 @@ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) {
* Fills up all the placeholders of this window.
*/
void fillInnerParts() {
+ logger.info("Filling inner parts of MainWindow");
+
personListPanel = new PersonListPanel(logic.getFilteredPersonList());
personListPanelPlaceholder.getChildren().add(personListPanel.getRoot());
+ logger.fine("Person list panel initialized");
+
+ tripListPanel = new TripListPanel(logic.getFilteredTripList());
+ tripListPanelPlaceholder.getChildren().add(tripListPanel.getRoot());
+ logger.fine("Trip list panel initialized");
resultDisplay = new ResultDisplay();
resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot());
+ logger.fine("Result display initialized");
StatusBarFooter statusBarFooter = new StatusBarFooter(logic.getAddressBookFilePath());
statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot());
+ logger.fine("Status bar footer initialized");
CommandBox commandBox = new CommandBox(this::executeCommand);
commandBoxPlaceholder.getChildren().add(commandBox.getRoot());
+ logger.fine("Command box initialized");
}
/**
* Sets the default size based on {@code guiSettings}.
*/
private void setWindowDefaultSize(GuiSettings guiSettings) {
+ logger.info("Setting window default size");
primaryStage.setHeight(guiSettings.getWindowHeight());
primaryStage.setWidth(guiSettings.getWindowWidth());
if (guiSettings.getWindowCoordinates() != null) {
primaryStage.setX(guiSettings.getWindowCoordinates().getX());
primaryStage.setY(guiSettings.getWindowCoordinates().getY());
}
+ logger.fine("Window size set to: " + guiSettings.getWindowWidth() + "x" + guiSettings.getWindowHeight());
}
/**
@@ -140,14 +158,18 @@ private void setWindowDefaultSize(GuiSettings guiSettings) {
*/
@FXML
public void handleHelp() {
+ logger.info("Help window requested");
if (!helpWindow.isShowing()) {
helpWindow.show();
+ logger.fine("Help window shown");
} else {
helpWindow.focus();
+ logger.fine("Help window focused");
}
}
void show() {
+ logger.info("Showing main window");
primaryStage.show();
}
@@ -156,11 +178,13 @@ void show() {
*/
@FXML
private void handleExit() {
+ logger.info("Handling application exit");
GuiSettings guiSettings = new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(),
(int) primaryStage.getX(), (int) primaryStage.getY());
logic.setGuiSettings(guiSettings);
helpWindow.hide();
primaryStage.hide();
+ logger.info("Application exit completed");
}
public PersonListPanel getPersonListPanel() {
@@ -186,6 +210,10 @@ private CommandResult executeCommand(String commandText) throws CommandException
handleExit();
}
+ if (commandResult.isShowConfirmation()) {
+ handleConfirmation(commandResult);
+ }
+
return commandResult;
} catch (CommandException | ParseException e) {
logger.info("An error occurred while executing command: " + commandText);
@@ -193,4 +221,37 @@ private CommandResult executeCommand(String commandText) throws CommandException
throw e;
}
}
+
+ /**
+ * Handles confirmation dialog.
+ */
+ private void handleConfirmation(CommandResult commandResult) {
+ logger.info("Showing confirmation dialog");
+ Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
+ alert.getDialogPane().getStylesheets().add("view/DarkTheme.css");
+ alert.setTitle("Confirmation");
+ alert.setHeaderText(null);
+ alert.setContentText(commandResult.getConfirmationText());
+ alert.getDialogPane().setId(UiManager.ALERT_DIALOG_PANE_FIELD_ID);
+
+ // Handle the user's response
+ alert.showAndWait().ifPresent(response -> {
+ if (response == javafx.scene.control.ButtonType.OK) {
+ try {
+ // Check if this is related to the clear command based on confirmation text
+ if (commandResult.getConfirmationText().equals(ClearCommand.MESSAGE_CONFIRMATION)) {
+ logger.info("User confirmed clear command");
+ // Execute the confirmed clear command
+ CommandResult result = logic.execute("clear confirmed");
+ resultDisplay.setFeedbackToUser(result.getFeedbackToUser());
+ }
+ } catch (CommandException | ParseException e) {
+ logger.warning("Error executing confirmed command: " + e.getMessage());
+ resultDisplay.setFeedbackToUser(e.getMessage());
+ }
+ } else {
+ logger.info("User canceled the operation");
+ }
+ });
+ }
}
diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java
index 094c42cda82..a0521b1fa73 100644
--- a/src/main/java/seedu/address/ui/PersonCard.java
+++ b/src/main/java/seedu/address/ui/PersonCard.java
@@ -7,10 +7,10 @@
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
/**
- * An UI component that displays information of a {@code Person}.
+ * An UI component that displays information of a {@code Contact}.
*/
public class PersonCard extends UiPart {
@@ -24,7 +24,7 @@ public class PersonCard extends UiPart {
* @see The issue on AddressBook level 4
*/
- public final Person person;
+ public final Contact contact;
@FXML
private HBox cardPane;
@@ -40,20 +40,27 @@ public class PersonCard extends UiPart {
private Label email;
@FXML
private FlowPane tags;
+ @FXML
+ private Label note;
/**
- * Creates a {@code PersonCode} with the given {@code Person} and index to display.
+ * Creates a {@code PersonCode} with the given {@code Contact} and index to display.
*/
- public PersonCard(Person person, int displayedIndex) {
+ public PersonCard(Contact contact, int displayedIndex) {
super(FXML);
- this.person = person;
+ this.contact = contact;
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()
+ name.setText(contact.getName().fullName);
+ phone.setText(contact.getPhone().value);
+ address.setText(contact.getAddress().value);
+ email.setText(contact.getEmail().value);
+ note.setText(contact.getNote().toString());
+ contact.getTags().stream()
.sorted(Comparator.comparing(tag -> tag.tagName))
- .forEach(tag -> tags.getChildren().add(new Label(tag.tagName)));
+ .forEach(tag -> {
+ Label label = new Label(tag.tagName);
+ label.getStyleClass().add(tag.getStyleClass());
+ tags.getChildren().add(label);
+ });
}
}
diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java
index f4c501a897b..964f5705d99 100644
--- a/src/main/java/seedu/address/ui/PersonListPanel.java
+++ b/src/main/java/seedu/address/ui/PersonListPanel.java
@@ -8,7 +8,7 @@
import javafx.scene.control.ListView;
import javafx.scene.layout.Region;
import seedu.address.commons.core.LogsCenter;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
/**
* Panel containing the list of persons.
@@ -18,30 +18,30 @@ public class PersonListPanel extends UiPart {
private final Logger logger = LogsCenter.getLogger(PersonListPanel.class);
@FXML
- private ListView personListView;
+ private ListView personListView;
/**
* Creates a {@code PersonListPanel} with the given {@code ObservableList}.
*/
- public PersonListPanel(ObservableList personList) {
+ public PersonListPanel(ObservableList contactList) {
super(FXML);
- personListView.setItems(personList);
+ personListView.setItems(contactList);
personListView.setCellFactory(listView -> new PersonListViewCell());
}
/**
- * Custom {@code ListCell} that displays the graphics of a {@code Person} using a {@code PersonCard}.
+ * Custom {@code ListCell} that displays the graphics of a {@code Contact} using a {@code PersonCard}.
*/
- class PersonListViewCell extends ListCell {
+ class PersonListViewCell extends ListCell {
@Override
- protected void updateItem(Person person, boolean empty) {
- super.updateItem(person, empty);
+ protected void updateItem(Contact contact, boolean empty) {
+ super.updateItem(contact, empty);
- if (empty || person == null) {
+ if (empty || contact == null) {
setGraphic(null);
setText(null);
} else {
- setGraphic(new PersonCard(person, getIndex() + 1).getRoot());
+ setGraphic(new PersonCard(contact, getIndex() + 1).getRoot());
}
}
}
diff --git a/src/main/java/seedu/address/ui/TripCard.java b/src/main/java/seedu/address/ui/TripCard.java
new file mode 100644
index 00000000000..1205f6b52f8
--- /dev/null
+++ b/src/main/java/seedu/address/ui/TripCard.java
@@ -0,0 +1,65 @@
+package seedu.address.ui;
+
+import java.util.Comparator;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Region;
+import seedu.address.model.trip.Trip;
+
+/**
+ * An UI component that displays information of a {@code Trip}.
+ */
+public class TripCard extends UiPart {
+
+ private static final String FXML = "TripListCard.fxml";
+
+ /**
+ * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX.
+ * As a consequence, UI elements' variable names cannot be set to such keywords
+ * or an exception will be thrown by JavaFX during runtime.
+ *
+ * @see The issue on AddressBook level 4
+ */
+
+ public final Trip trip;
+
+ @FXML
+ private HBox cardPane;
+ @FXML
+ private Label name;
+ @FXML
+ private Label id;
+ @FXML
+ private Label accommodation;
+ @FXML
+ private Label itinerary;
+ @FXML
+ private Label date;
+ @FXML
+ private Label customerNames;
+ @FXML
+ private Label note;
+
+ /**
+ * Creates a {@code TripCard} with the given {@code Trip} and index to display.
+ */
+ public TripCard(Trip trip, int displayedIndex) {
+ super(FXML);
+ this.trip = trip;
+ id.setText(displayedIndex + ". ");
+ name.setText(trip.getName().name);
+ date.setText("(" + trip.getDate().toString() + ")");
+ itinerary.setText("Itinerary: " + trip.getItinerary().itinerary);
+ accommodation.setText("Accomodation: " + trip.getAccommodation().accommodation);
+ note.setText("Note: " + trip.getNote().note);
+
+ String customerNamesStr = trip.getCustomerNames().stream()
+ .sorted(Comparator.comparing(customerName -> customerName.fullName.toLowerCase()))
+ .map(customerName -> customerName.fullName)
+ .reduce((a, b) -> a + ", " + b)
+ .orElse("");
+ customerNames.setText("Customers: " + customerNamesStr);
+ }
+}
diff --git a/src/main/java/seedu/address/ui/TripListPanel.java b/src/main/java/seedu/address/ui/TripListPanel.java
new file mode 100644
index 00000000000..ebb5c7feaaa
--- /dev/null
+++ b/src/main/java/seedu/address/ui/TripListPanel.java
@@ -0,0 +1,49 @@
+package seedu.address.ui;
+
+import java.util.logging.Logger;
+
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.layout.Region;
+import seedu.address.commons.core.LogsCenter;
+import seedu.address.model.trip.Trip;
+
+/**
+ * Panel containing the list of trips.
+ */
+public class TripListPanel extends UiPart {
+ private static final String FXML = "TripListPanel.fxml";
+ private final Logger logger = LogsCenter.getLogger(TripListPanel.class);
+
+ @FXML
+ private ListView tripListView;
+
+ /**
+ * Creates a {@code TripListPanel} with the given {@code ObservableList}.
+ */
+ public TripListPanel(ObservableList tripList) {
+ super(FXML);
+ tripListView.setItems(tripList);
+ tripListView.setCellFactory(listView -> new TripListViewCell());
+ }
+
+ /**
+ * Custom {@code ListCell} that displays the graphics of a {@code Trip} using a {@code TripCard}.
+ */
+ class TripListViewCell extends ListCell {
+ @Override
+ protected void updateItem(Trip trip, boolean empty) {
+ super.updateItem(trip, empty);
+
+ if (empty || trip == null) {
+ setGraphic(null);
+ setText(null);
+ } else {
+ setGraphic(new TripCard(trip, getIndex() + 1).getRoot());
+ }
+ }
+ }
+
+}
diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css
index 36e6b001cd8..93e7aedf8f3 100644
--- a/src/main/resources/view/DarkTheme.css
+++ b/src/main/resources/view/DarkTheme.css
@@ -350,3 +350,11 @@
-fx-background-radius: 2;
-fx-font-size: 11;
}
+
+#tags .customer-tag {
+ -fx-background-color: #6495ED;
+}
+
+#tags .service-tag {
+ -fx-background-color: lightcoral;
+}
diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml
index e01f330de33..bd3215fd227 100644
--- a/src/main/resources/view/HelpWindow.fxml
+++ b/src/main/resources/view/HelpWindow.fxml
@@ -9,7 +9,7 @@
-
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
index 7778f666a0a..8750912846d 100644
--- a/src/main/resources/view/MainWindow.fxml
+++ b/src/main/resources/view/MainWindow.fxml
@@ -12,7 +12,7 @@
+ title="Travel Hub" minWidth="450" minHeight="600" onCloseRequest="#handleExit">
@@ -28,8 +28,8 @@
-
-
+
+
@@ -46,12 +46,20 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml
index 84e09833a87..ce1b71efd5f 100644
--- a/src/main/resources/view/PersonListCard.fxml
+++ b/src/main/resources/view/PersonListCard.fxml
@@ -25,12 +25,13 @@
-
+
-
+
+
diff --git a/src/main/resources/view/TripListCard.fxml b/src/main/resources/view/TripListCard.fxml
new file mode 100644
index 00000000000..7dbc6e5fc1e
--- /dev/null
+++ b/src/main/resources/view/TripListCard.fxml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/TripListPanel.fxml b/src/main/resources/view/TripListPanel.fxml
new file mode 100644
index 00000000000..b94d91c5b0b
--- /dev/null
+++ b/src/main/resources/view/TripListPanel.fxml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
index a7427fe7aa2..428be9ea1d5 100644
--- a/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/duplicatePersonAddressBook.json
@@ -4,11 +4,12 @@
"phone": "94351253",
"email": "alice@example.com",
"address": "123, Jurong West Ave 6, #08-111",
- "tags": [ "friends" ]
+ "tags": [ "customer" ]
}, {
- "name": "Alice Pauline",
- "phone": "94351253",
- "email": "pauline@example.com",
- "address": "4th street"
+ "name": "Alice Pauline 2",
+ "phone": "12345678",
+ "email": "alice@example.com",
+ "address": "456, Jurong West Ave 6, #08-111",
+ "tags": []
} ]
}
diff --git a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
index 72262099d35..514b6db2172 100644
--- a/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
+++ b/src/test/data/JsonSerializableAddressBookTest/typicalPersonsAddressBook.json
@@ -5,42 +5,49 @@
"phone" : "94351253",
"email" : "alice@example.com",
"address" : "123, Jurong West Ave 6, #08-111",
- "tags" : [ "friends" ]
+ "tags" : [ "service" ],
+ "note" : "Preferred tour guide for European trips"
}, {
"name" : "Benson Meier",
"phone" : "98765432",
"email" : "johnd@example.com",
"address" : "311, Clementi Ave 2, #02-25",
- "tags" : [ "owesMoney", "friends" ]
+ "tags" : [ "customer", "service" ],
+ "note" : "VIP customer, prefers luxury accommodations"
}, {
"name" : "Carl Kurz",
"phone" : "95352563",
"email" : "heinz@example.com",
"address" : "wall street",
- "tags" : [ ]
+ "tags" : [ "customer" ],
+ "note" : "Business traveler, frequent flyer"
}, {
"name" : "Daniel Meier",
"phone" : "87652533",
"email" : "cornelia@example.com",
"address" : "10th street",
- "tags" : [ "friends" ]
+ "tags" : [ "customer" ],
+ "note" : "Family of 4, needs child-friendly activities"
}, {
"name" : "Elle Meyer",
"phone" : "9482224",
"email" : "werner@example.com",
"address" : "michegan ave",
- "tags" : [ ]
+ "tags" : [ "customer" ],
+ "note" : ""
}, {
"name" : "Fiona Kunz",
"phone" : "9482427",
"email" : "lydia@example.com",
"address" : "little tokyo",
- "tags" : [ ]
+ "tags" : [ "customer" ],
+ "note" : ""
}, {
"name" : "George Best",
"phone" : "9482442",
"email" : "anna@example.com",
"address" : "4th street",
- "tags" : [ ]
+ "tags" : [ "customer" ],
+ "note" : ""
} ]
}
diff --git a/src/test/data/JsonSerializableTripBookTest/duplicateTripTripBook.json b/src/test/data/JsonSerializableTripBookTest/duplicateTripTripBook.json
new file mode 100644
index 00000000000..1f6077f334c
--- /dev/null
+++ b/src/test/data/JsonSerializableTripBookTest/duplicateTripTripBook.json
@@ -0,0 +1,18 @@
+{
+ "trips": [
+ {
+ "name": "Paris Adventure",
+ "accommodation": "Hotel de Paris",
+ "itinerary": "Visit Eiffel Tower, Louvre Museum",
+ "date": "15/6/2024",
+ "customerNames": ["Alice Pauline", "Bob Chen"]
+ },
+ {
+ "name": "Paris Adventure",
+ "accommodation": "Different Hotel",
+ "itinerary": "Different Itinerary",
+ "date": "20/6/2024",
+ "customerNames": ["Different Customer"]
+ }
+ ]
+}
diff --git a/src/test/data/JsonSerializableTripBookTest/invalidTripTripBook.json b/src/test/data/JsonSerializableTripBookTest/invalidTripTripBook.json
new file mode 100644
index 00000000000..9f9c74607ca
--- /dev/null
+++ b/src/test/data/JsonSerializableTripBookTest/invalidTripTripBook.json
@@ -0,0 +1,11 @@
+{
+ "trips": [
+ {
+ "name": "Invalid Trip @#$",
+ "accommodation": "",
+ "itinerary": "Invalid itinerary with @#$",
+ "date": "invalid-date",
+ "customerNames": ["Invalid@Customer@Name"]
+ }
+ ]
+}
diff --git a/src/test/data/JsonSerializableTripBookTest/typicalTripsTripBook.json b/src/test/data/JsonSerializableTripBookTest/typicalTripsTripBook.json
new file mode 100644
index 00000000000..c0f244aa748
--- /dev/null
+++ b/src/test/data/JsonSerializableTripBookTest/typicalTripsTripBook.json
@@ -0,0 +1,40 @@
+{
+ "_comment": "TripBook save file which contains the same Trip values as in TypicalTrips#getTypicalTripBook()",
+ "trips": [
+ {
+ "name": "Paris Adventure",
+ "accommodation": "Hotel de Paris",
+ "itinerary": "Visit Eiffel Tower, Louvre Museum",
+ "date": "15/6/2024",
+ "customerNames": ["Alice Pauline", "Bob Chen"]
+ },
+ {
+ "name": "Tokyo Explorer",
+ "accommodation": "Shinjuku Hotel",
+ "itinerary": "Visit Tokyo Tower, Shibuya Crossing",
+ "date": "20/7/2024",
+ "customerNames": ["Charlie Brown", "David Lee"]
+ },
+ {
+ "name": "Singapore Tour",
+ "accommodation": "Marina Bay Sands",
+ "itinerary": "Gardens by the Bay, Sentosa Island",
+ "date": "10/8/2024",
+ "customerNames": ["Eve Tan"]
+ },
+ {
+ "name": "Bali Getaway",
+ "accommodation": "Ubud Resort",
+ "itinerary": "Rice Terraces, Tanah Lot Temple",
+ "date": "5/9/2024",
+ "customerNames": ["Frank Wong", "Grace Liu"]
+ },
+ {
+ "name": "Seoul Discovery",
+ "accommodation": "Gangnam Hotel",
+ "itinerary": "Namsan Tower, Myeongdong Shopping",
+ "date": "1/10/2024",
+ "customerNames": ["Henry Park"]
+ }
+ ]
+}
diff --git a/src/test/data/JsonTripBookStorageTest/invalidAndValidTrip.json b/src/test/data/JsonTripBookStorageTest/invalidAndValidTrip.json
new file mode 100644
index 00000000000..b3fa1439cba
--- /dev/null
+++ b/src/test/data/JsonTripBookStorageTest/invalidAndValidTrip.json
@@ -0,0 +1,17 @@
+{
+ "tripbook" : {
+ "trips" : [ {
+ "name" : "Paris 2025",
+ "accommodation" : "Hotel 81",
+ "itinerary" : "Eat baguettes",
+ "date" : "01/01/2100",
+ "customerNames" : [ "John Doe", "Jane Doe" ]
+ }, {
+ "name" : "R@chel",
+ "accommodation" : " ",
+ "itinerary" : " ",
+ "date" : "32/13/2025",
+ "customerNames" : [ "R@chel" ]
+ } ]
+ }
+}
diff --git a/src/test/data/JsonTripBookStorageTest/invalidAndValidTripTripBook.json b/src/test/data/JsonTripBookStorageTest/invalidAndValidTripTripBook.json
new file mode 100644
index 00000000000..55cd5fd3dec
--- /dev/null
+++ b/src/test/data/JsonTripBookStorageTest/invalidAndValidTripTripBook.json
@@ -0,0 +1,19 @@
+{
+ "trips": [
+ {
+ "name": "Valid Trip",
+ "accommodation": "Valid Hotel",
+ "itinerary": "Valid Itinerary",
+ "date": "1/1/2024",
+ "customerNames": ["Valid Name"]
+ },
+ {
+ "name": "Invalid Trip @#$",
+ "accommodation": "",
+ "itinerary": "Invalid itinerary with @#$",
+ "date": "invalid-date",
+ "customerNames": ["Invalid@Customer@Name"]
+ }
+ ]
+}
+
diff --git a/src/test/data/JsonTripBookStorageTest/invalidTrip.json b/src/test/data/JsonTripBookStorageTest/invalidTrip.json
new file mode 100644
index 00000000000..06fbb64bbf4
--- /dev/null
+++ b/src/test/data/JsonTripBookStorageTest/invalidTrip.json
@@ -0,0 +1,11 @@
+{
+ "tripbook" : {
+ "trips" : [ {
+ "name" : "R@chel",
+ "accommodation" : " ",
+ "itinerary" : " ",
+ "date" : "32/13/2025",
+ "customerNames" : [ "R@chel" ]
+ } ]
+ }
+}
diff --git a/src/test/data/JsonTripBookStorageTest/invalidTripTripBook.json b/src/test/data/JsonTripBookStorageTest/invalidTripTripBook.json
new file mode 100644
index 00000000000..9f9c74607ca
--- /dev/null
+++ b/src/test/data/JsonTripBookStorageTest/invalidTripTripBook.json
@@ -0,0 +1,11 @@
+{
+ "trips": [
+ {
+ "name": "Invalid Trip @#$",
+ "accommodation": "",
+ "itinerary": "Invalid itinerary with @#$",
+ "date": "invalid-date",
+ "customerNames": ["Invalid@Customer@Name"]
+ }
+ ]
+}
diff --git a/src/test/data/JsonTripBookStorageTest/notJsonFormat.json b/src/test/data/JsonTripBookStorageTest/notJsonFormat.json
new file mode 100644
index 00000000000..20912864584
--- /dev/null
+++ b/src/test/data/JsonTripBookStorageTest/notJsonFormat.json
@@ -0,0 +1 @@
+Not a JSON file
diff --git a/src/test/data/JsonTripBookStorageTest/typicalTrips.json b/src/test/data/JsonTripBookStorageTest/typicalTrips.json
new file mode 100644
index 00000000000..81fc2528589
--- /dev/null
+++ b/src/test/data/JsonTripBookStorageTest/typicalTrips.json
@@ -0,0 +1,11 @@
+{
+ "tripbook" : {
+ "trips" : [ {
+ "name" : "Paris Adventure",
+ "accommodation" : "Hotel de Paris",
+ "itinerary" : "Visit Eiffel Tower, Louvre Museum",
+ "date" : "15/6/2024",
+ "customerNames" : [ "Alice Pauline", "Bob Chen" ]
+ } ]
+ }
+}
diff --git a/src/test/java/seedu/address/commons/core/ConfigTest.java b/src/test/java/seedu/address/commons/core/ConfigTest.java
index d3ba2a52a89..461c920e107 100644
--- a/src/test/java/seedu/address/commons/core/ConfigTest.java
+++ b/src/test/java/seedu/address/commons/core/ConfigTest.java
@@ -1,9 +1,14 @@
package seedu.address.commons.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.nio.file.Paths;
+import java.util.logging.Level;
+
import org.junit.jupiter.api.Test;
public class ConfigTest {
@@ -20,8 +25,39 @@ public void toStringMethod() {
public void equalsMethod() {
Config defaultConfig = new Config();
assertNotNull(defaultConfig);
- assertTrue(defaultConfig.equals(defaultConfig));
+ assertTrue(defaultConfig.equals(defaultConfig)); // same object
+
+ // same values -> returns true
+ Config copy = new Config();
+ assertTrue(defaultConfig.equals(copy));
+
+ // different types -> returns false
+ assertFalse(defaultConfig.equals(100));
+
+ // null -> returns false
+ assertFalse(defaultConfig.equals(null));
+
+ // different log level -> returns false
+ Config differentConfig = new Config();
+ differentConfig.setLogLevel(Level.FINE);
+ assertFalse(defaultConfig.equals(differentConfig));
+
+ // different user prefs file path -> returns false
+ differentConfig = new Config();
+ differentConfig.setUserPrefsFilePath(Paths.get("different/path"));
+ assertFalse(defaultConfig.equals(differentConfig));
}
+ @Test
+ public void hashCodeMethod() {
+ Config defaultConfig = new Config();
+ Config copy = new Config();
+ // same values -> returns same hashcode
+ assertEquals(defaultConfig.hashCode(), copy.hashCode());
+
+ // different values -> returns different hashcode
+ copy.setLogLevel(Level.FINE);
+ assertNotEquals(defaultConfig.hashCode(), copy.hashCode());
+ }
}
diff --git a/src/test/java/seedu/address/commons/util/JsonUtilTest.java b/src/test/java/seedu/address/commons/util/JsonUtilTest.java
index d4907539dee..d6b635f5b71 100644
--- a/src/test/java/seedu/address/commons/util/JsonUtilTest.java
+++ b/src/test/java/seedu/address/commons/util/JsonUtilTest.java
@@ -31,15 +31,38 @@ public void serializeObjectToJsonFile_noExceptionThrown() throws IOException {
public void deserializeObjectFromJsonFile_noExceptionThrown() throws IOException {
FileUtil.writeToFile(SERIALIZATION_FILE, SerializableTestClass.JSON_STRING_REPRESENTATION);
- SerializableTestClass serializableTestClass = JsonUtil
- .deserializeObjectFromJsonFile(SERIALIZATION_FILE, SerializableTestClass.class);
+ SerializableTestClass serializableTestClass = JsonUtil.deserializeObjectFromJsonFile(
+ SERIALIZATION_FILE, SerializableTestClass.class);
assertEquals(serializableTestClass.getName(), SerializableTestClass.getNameTestValue());
assertEquals(serializableTestClass.getListOfLocalDateTimes(), SerializableTestClass.getListTestValues());
assertEquals(serializableTestClass.getMapOfIntegerToString(), SerializableTestClass.getHashMapTestValues());
}
- //TODO: @Test jsonUtil_readJsonStringToObjectInstance_correctObject()
+ @Test
+ public void jsonUtil_readJsonStringToObjectInstance_correctObject() throws IOException {
+ SerializableTestClass serializableTestClass = new SerializableTestClass();
+ serializableTestClass.setTestValues();
+
+ String json = JsonUtil.toJsonString(serializableTestClass);
+ SerializableTestClass copy = JsonUtil.fromJsonString(json, SerializableTestClass.class);
- //TODO: @Test jsonUtil_writeThenReadObjectToJson_correctObject()
+ assertEquals(serializableTestClass.getName(), copy.getName());
+ assertEquals(serializableTestClass.getListOfLocalDateTimes(), copy.getListOfLocalDateTimes());
+ assertEquals(serializableTestClass.getMapOfIntegerToString(), copy.getMapOfIntegerToString());
+ }
+
+ @Test
+ public void jsonUtil_writeThenReadObjectToJson_correctObject() throws IOException {
+ SerializableTestClass serializableTestClass = new SerializableTestClass();
+ serializableTestClass.setTestValues();
+
+ JsonUtil.serializeObjectToJsonFile(SERIALIZATION_FILE, serializableTestClass);
+ SerializableTestClass copy = JsonUtil.deserializeObjectFromJsonFile(
+ SERIALIZATION_FILE, SerializableTestClass.class);
+
+ assertEquals(serializableTestClass.getName(), copy.getName());
+ assertEquals(serializableTestClass.getListOfLocalDateTimes(), copy.getListOfLocalDateTimes());
+ assertEquals(serializableTestClass.getMapOfIntegerToString(), copy.getMapOfIntegerToString());
+ }
}
diff --git a/src/test/java/seedu/address/commons/util/StringUtilTest.java b/src/test/java/seedu/address/commons/util/StringUtilTest.java
index c56d407bf3f..71b766a5fa6 100644
--- a/src/test/java/seedu/address/commons/util/StringUtilTest.java
+++ b/src/test/java/seedu/address/commons/util/StringUtilTest.java
@@ -16,7 +16,7 @@ public class StringUtilTest {
public void isNonZeroUnsignedInteger() {
// EP: empty strings
- assertFalse(StringUtil.isNonZeroUnsignedInteger("")); // Boundary value
+ assertFalse(StringUtil.isNonZeroUnsignedInteger(""));
assertFalse(StringUtil.isNonZeroUnsignedInteger(" "));
// EP: not a number
@@ -34,14 +34,14 @@ public void isNonZeroUnsignedInteger() {
assertFalse(StringUtil.isNonZeroUnsignedInteger("+1"));
// EP: numbers with white space
- assertFalse(StringUtil.isNonZeroUnsignedInteger(" 10 ")); // Leading/trailing spaces
- assertFalse(StringUtil.isNonZeroUnsignedInteger("1 0")); // Spaces in the middle
+ assertFalse(StringUtil.isNonZeroUnsignedInteger(" 10 "));
+ assertFalse(StringUtil.isNonZeroUnsignedInteger("1 0"));
// EP: number larger than Integer.MAX_VALUE
assertFalse(StringUtil.isNonZeroUnsignedInteger(Long.toString(Integer.MAX_VALUE + 1)));
// EP: valid numbers, should return true
- assertTrue(StringUtil.isNonZeroUnsignedInteger("1")); // Boundary value
+ assertTrue(StringUtil.isNonZeroUnsignedInteger("1"));
assertTrue(StringUtil.isNonZeroUnsignedInteger("10"));
}
@@ -56,19 +56,20 @@ public void isNonZeroUnsignedInteger() {
@Test
public void containsWordIgnoreCase_nullWord_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> StringUtil.containsWordIgnoreCase("typical sentence", null));
+ assertThrows(NullPointerException.class, () -> StringUtil.containsWordIgnoreCase(
+ "typical sentence", null));
}
@Test
public void containsWordIgnoreCase_emptyWord_throwsIllegalArgumentException() {
- assertThrows(IllegalArgumentException.class, "Word parameter cannot be empty", ()
- -> StringUtil.containsWordIgnoreCase("typical sentence", " "));
+ assertThrows(IllegalArgumentException.class, () -> StringUtil.containsWordIgnoreCase(
+ "typical sentence", " "));
}
@Test
public void containsWordIgnoreCase_multipleWords_throwsIllegalArgumentException() {
- assertThrows(IllegalArgumentException.class, "Word parameter should be a single word", ()
- -> StringUtil.containsWordIgnoreCase("typical sentence", "aaa BBB"));
+ assertThrows(IllegalArgumentException.class, () -> StringUtil.containsWordIgnoreCase(
+ "typical sentence", "aaa bbb"));
}
@Test
@@ -103,24 +104,40 @@ public void containsWordIgnoreCase_nullSentence_throwsNullPointerException() {
@Test
public void containsWordIgnoreCase_validInputs_correctResult() {
-
// Empty sentence
- assertFalse(StringUtil.containsWordIgnoreCase("", "abc")); // Boundary case
- assertFalse(StringUtil.containsWordIgnoreCase(" ", "123"));
-
- // Matches a partial word only
- assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bb")); // Sentence word bigger than query word
- assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bbbb")); // Query word bigger than sentence word
+ assertFalse(StringUtil.containsWordIgnoreCase("", "abc"));
+ assertFalse(StringUtil.containsWordIgnoreCase(" ", "abc"));
// Matches word in the sentence, different upper/lower case letters
- assertTrue(StringUtil.containsWordIgnoreCase("aaa bBb ccc", "Bbb")); // First word (boundary case)
- assertTrue(StringUtil.containsWordIgnoreCase("aaa bBb ccc@1", "CCc@1")); // Last word (boundary case)
- assertTrue(StringUtil.containsWordIgnoreCase(" AAA bBb ccc ", "aaa")); // Sentence has extra spaces
- assertTrue(StringUtil.containsWordIgnoreCase("Aaa", "aaa")); // Only one word in sentence (boundary case)
- assertTrue(StringUtil.containsWordIgnoreCase("aaa bbb ccc", " ccc ")); // Leading/trailing spaces
+ assertTrue(StringUtil.containsWordIgnoreCase("TyPiCaL SeNtEnCe", "typical"));
+ assertTrue(StringUtil.containsWordIgnoreCase("AAA bBb ccc", "aaa"));
+ assertTrue(StringUtil.containsWordIgnoreCase("AAA bBb ccc DDD", "BBB"));
+ assertTrue(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bbb"));
// Matches multiple words in sentence
- assertTrue(StringUtil.containsWordIgnoreCase("AAA bBb ccc bbb", "bbB"));
+ assertTrue(StringUtil.containsWordIgnoreCase("AAA bBb ccc bbb", "bbb"));
+
+ // Matches part of a word only
+ assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "bb"));
+
+ // Does not match
+ assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "ddd"));
+
+ // Non-word characters
+ assertFalse(StringUtil.containsWordIgnoreCase("aaa bbb ccc", "a*a"));
+ assertFalse(StringUtil.containsWordIgnoreCase("1234567890", "a"));
+ assertTrue(StringUtil.containsWordIgnoreCase("aaa123bbb", "aaa123bbb"));
+ }
+
+ @Test
+ public void containsWordIgnoreCase_specialCharacters_correctResult() {
+ // Special characters in sentence
+ assertTrue(StringUtil.containsWordIgnoreCase("$#@ abc !@#", "abc"));
+ assertFalse(StringUtil.containsWordIgnoreCase("$#@ !@#", "abc"));
+
+ // Unicode characters
+ assertTrue(StringUtil.containsWordIgnoreCase("über", "über"));
+ assertTrue(StringUtil.containsWordIgnoreCase("ÜBER", "über"));
}
//---------------- Tests for getDetails --------------------------------------
diff --git a/src/test/java/seedu/address/logic/LogicManagerTest.java b/src/test/java/seedu/address/logic/LogicManagerTest.java
index baf8ce336a2..ff93a0163df 100644
--- a/src/test/java/seedu/address/logic/LogicManagerTest.java
+++ b/src/test/java/seedu/address/logic/LogicManagerTest.java
@@ -9,6 +9,7 @@
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.AMY;
+import static seedu.address.testutil.TypicalTrips.getTypicalTrips;
import java.io.IOException;
import java.nio.file.AccessDeniedException;
@@ -18,17 +19,20 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
-import seedu.address.logic.commands.AddCommand;
+import javafx.collections.ObservableList;
+import seedu.address.logic.commands.AddContactCommand;
import seedu.address.logic.commands.CommandResult;
-import seedu.address.logic.commands.ListCommand;
+import seedu.address.logic.commands.ListContactCommand;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.ReadOnlyAddressBook;
import seedu.address.model.UserPrefs;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.trip.Trip;
import seedu.address.storage.JsonAddressBookStorage;
+import seedu.address.storage.JsonTripBookStorage;
import seedu.address.storage.JsonUserPrefsStorage;
import seedu.address.storage.StorageManager;
import seedu.address.testutil.PersonBuilder;
@@ -48,7 +52,9 @@ public void setUp() {
JsonAddressBookStorage addressBookStorage =
new JsonAddressBookStorage(temporaryFolder.resolve("addressBook.json"));
JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(temporaryFolder.resolve("userPrefs.json"));
- StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ JsonTripBookStorage tripBookStorage =
+ new JsonTripBookStorage(temporaryFolder.resolve("tripBook.json"));
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage, tripBookStorage);
logic = new LogicManager(model, storage);
}
@@ -60,14 +66,15 @@ public void execute_invalidCommandFormat_throwsParseException() {
@Test
public void execute_commandExecutionError_throwsCommandException() {
- String deleteCommand = "delete 9";
+ String deleteCommand = "deleteContact 9";
assertCommandException(deleteCommand, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
@Test
public void execute_validCommand_success() throws Exception {
- String listCommand = ListCommand.COMMAND_WORD;
- assertCommandSuccess(listCommand, ListCommand.MESSAGE_SUCCESS, model);
+ String listContactCommand = ListContactCommand.COMMAND_WORD;
+ assertCommandSuccess(listContactCommand, String.format(
+ "There are currently no %scontacts in the addressbook.", ""), model);
}
@Test
@@ -87,6 +94,33 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException
assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredPersonList().remove(0));
}
+ @Test
+ public void getFilteredTripList_modifyList_throwsUnsupportedOperationException() {
+ assertThrows(UnsupportedOperationException.class, () -> logic.getFilteredTripList().remove(0));
+ }
+
+ @Test
+ public void getFilteredTripList_returnsCorrectList() {
+ for (Trip trip : getTypicalTrips()) {
+ model.addTrip(trip);
+ }
+
+ ObservableList filteredTripList = logic.getFilteredTripList();
+
+ assertEquals(getTypicalTrips().size(), filteredTripList.size());
+ for (int i = 0; i < getTypicalTrips().size(); i++) {
+ assertEquals(getTypicalTrips().get(i), filteredTripList.get(i));
+ }
+ }
+
+ @Test
+ public void getAddressBookFilePath_returnsCorrectPath() {
+
+ Path expectedPath = model.getAddressBookFilePath();
+ Path actualPath = logic.getAddressBookFilePath();
+ assertEquals(expectedPath, actualPath);
+ }
+
/**
* Executes the command and confirms that
* - no exceptions are thrown
@@ -123,7 +157,7 @@ private void assertCommandException(String inputCommand, String expectedMessage)
*/
private void assertCommandFailure(String inputCommand, Class extends Throwable> expectedException,
String expectedMessage) {
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
assertCommandFailure(inputCommand, expectedException, expectedMessage, expectedModel);
}
@@ -160,16 +194,17 @@ public void saveAddressBook(ReadOnlyAddressBook addressBook, Path filePath)
JsonUserPrefsStorage userPrefsStorage =
new JsonUserPrefsStorage(temporaryFolder.resolve("ExceptionUserPrefs.json"));
- StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage);
+ JsonTripBookStorage tripBookStorage = new JsonTripBookStorage(temporaryFolder.resolve("tripBook.json"));
+ StorageManager storage = new StorageManager(addressBookStorage, userPrefsStorage, tripBookStorage);
logic = new LogicManager(model, storage);
// Triggers the saveAddressBook method by executing an add command
- String addCommand = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY
+ String addCommand = AddContactCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY
+ EMAIL_DESC_AMY + ADDRESS_DESC_AMY;
- Person expectedPerson = new PersonBuilder(AMY).withTags().build();
+ Contact expectedContact = new PersonBuilder(AMY).withTags().build();
ModelManager expectedModel = new ModelManager();
- expectedModel.addPerson(expectedPerson);
+ expectedModel.addPerson(expectedContact);
assertCommandFailure(addCommand, CommandException.class, expectedMessage, expectedModel);
}
}
diff --git a/src/test/java/seedu/address/logic/MessagesTest.java b/src/test/java/seedu/address/logic/MessagesTest.java
new file mode 100644
index 00000000000..9287e353b62
--- /dev/null
+++ b/src/test/java/seedu/address/logic/MessagesTest.java
@@ -0,0 +1,58 @@
+package seedu.address.logic;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.trip.Trip;
+import seedu.address.testutil.TripBuilder;
+
+public class MessagesTest {
+
+ @Test
+ public void formatTrip() {
+ Trip trip = new TripBuilder()
+ .withName("Paris 2025")
+ .withAccommodation("Hotel 81")
+ .withItinerary("Eat baguettes")
+ .withDate("1/1/2100")
+ .withCustomerNames("John Doe", "Jane Doe")
+ .withNote("Customer prefers window seat")
+ .build();
+
+ String expected = "Paris 2025; Accommodation: Hotel 81; Itinerary: Eat baguettes; "
+ + "Date: 1/1/2100; Customers: John Doe, Jane Doe; Note: Customer prefers window seat";
+ assertEquals(expected, Messages.format(trip));
+ }
+
+ @Test
+ public void formatTripWithEmptyNote() {
+ Trip trip = new TripBuilder()
+ .withName("Paris 2025")
+ .withAccommodation("Hotel 81")
+ .withItinerary("Eat baguettes")
+ .withDate("1/1/2100")
+ .withCustomerNames("John Doe", "Jane Doe")
+ .build();
+
+ String expected = "Paris 2025; Accommodation: Hotel 81; Itinerary: Eat baguettes; "
+ + "Date: 1/1/2100; Customers: John Doe, Jane Doe; Note: ";
+ assertEquals(expected, Messages.format(trip));
+ }
+
+ @Test
+ public void formatTripWithSingleCustomer() {
+ Trip trip = new TripBuilder()
+ .withName("Paris 2025")
+ .withAccommodation("Hotel 81")
+ .withItinerary("Eat baguettes")
+ .withDate("1/1/2100")
+ .withCustomerNames("John Doe")
+ .withNote("Customer prefers window seat")
+ .build();
+
+ String expected = "Paris 2025; Accommodation: Hotel 81; Itinerary: Eat baguettes; "
+ + "Date: 1/1/2100; Customers: John Doe; Note: Customer prefers window seat";
+ assertEquals(expected, Messages.format(trip));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java b/src/test/java/seedu/address/logic/commands/AddContactCommandIntegrationTest.java
similarity index 53%
rename from src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
rename to src/test/java/seedu/address/logic/commands/AddContactCommandIntegrationTest.java
index 162a0c86031..0d1089dd581 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandIntegrationTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddContactCommandIntegrationTest.java
@@ -3,6 +3,7 @@
import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTrips.getTypicalTripBook;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -11,38 +12,38 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
import seedu.address.testutil.PersonBuilder;
/**
- * Contains integration tests (interaction with the Model) for {@code AddCommand}.
+ * Contains integration tests (interaction with the Model) for {@code AddContactCommand}.
*/
-public class AddCommandIntegrationTest {
+public class AddContactCommandIntegrationTest {
private Model model;
@BeforeEach
public void setUp() {
- model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ model = new ModelManager(getTypicalAddressBook(), getTypicalTripBook(), new UserPrefs());
}
@Test
public void execute_newPerson_success() {
- Person validPerson = new PersonBuilder().build();
+ Contact validContact = new PersonBuilder().build();
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
- expectedModel.addPerson(validPerson);
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ expectedModel.addPerson(validContact);
- assertCommandSuccess(new AddCommand(validPerson), model,
- String.format(AddCommand.MESSAGE_SUCCESS, Messages.format(validPerson)),
+ assertCommandSuccess(new AddContactCommand(validContact), model,
+ String.format(AddContactCommand.MESSAGE_SUCCESS, Messages.format(validContact)),
expectedModel);
}
@Test
public void execute_duplicatePerson_throwsCommandException() {
- Person personInList = model.getAddressBook().getPersonList().get(0);
- assertCommandFailure(new AddCommand(personInList), model,
- AddCommand.MESSAGE_DUPLICATE_PERSON);
+ Contact contactInList = model.getAddressBook().getPersonList().get(0);
+ assertCommandFailure(new AddContactCommand(contactInList), model,
+ AddContactCommand.MESSAGE_DUPLICATE_PERSON);
}
}
diff --git a/src/test/java/seedu/address/logic/commands/AddCommandTest.java b/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java
similarity index 50%
rename from src/test/java/seedu/address/logic/commands/AddCommandTest.java
rename to src/test/java/seedu/address/logic/commands/AddContactCommandTest.java
index 90e8253f48e..97323498bbe 100644
--- a/src/test/java/seedu/address/logic/commands/AddCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/AddContactCommandTest.java
@@ -21,50 +21,53 @@
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTripBook;
import seedu.address.model.ReadOnlyUserPrefs;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.trip.Trip;
import seedu.address.testutil.PersonBuilder;
-public class AddCommandTest {
+public class AddContactCommandTest {
@Test
public void constructor_nullPerson_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> new AddCommand(null));
+ assertThrows(NullPointerException.class, () -> new AddContactCommand(null));
}
@Test
public void execute_personAcceptedByModel_addSuccessful() throws Exception {
ModelStubAcceptingPersonAdded modelStub = new ModelStubAcceptingPersonAdded();
- Person validPerson = new PersonBuilder().build();
+ Contact validContact = new PersonBuilder().build();
- CommandResult commandResult = new AddCommand(validPerson).execute(modelStub);
+ CommandResult commandResult = new AddContactCommand(validContact).execute(modelStub);
- assertEquals(String.format(AddCommand.MESSAGE_SUCCESS, Messages.format(validPerson)),
+ assertEquals(String.format(AddContactCommand.MESSAGE_SUCCESS, Messages.format(validContact)),
commandResult.getFeedbackToUser());
- assertEquals(Arrays.asList(validPerson), modelStub.personsAdded);
+ assertEquals(Arrays.asList(validContact), modelStub.personsAdded);
}
@Test
public void execute_duplicatePerson_throwsCommandException() {
- Person validPerson = new PersonBuilder().build();
- AddCommand addCommand = new AddCommand(validPerson);
- ModelStub modelStub = new ModelStubWithPerson(validPerson);
+ Contact validContact = new PersonBuilder().build();
+ AddContactCommand addContactCommand = new AddContactCommand(validContact);
+ ModelStub modelStub = new ModelStubWithPerson(validContact);
- assertThrows(CommandException.class, AddCommand.MESSAGE_DUPLICATE_PERSON, () -> addCommand.execute(modelStub));
+ assertThrows(CommandException.class, AddContactCommand.MESSAGE_DUPLICATE_PERSON, () ->
+ addContactCommand.execute(modelStub));
}
@Test
public void equals() {
- Person alice = new PersonBuilder().withName("Alice").build();
- Person bob = new PersonBuilder().withName("Bob").build();
- AddCommand addAliceCommand = new AddCommand(alice);
- AddCommand addBobCommand = new AddCommand(bob);
+ Contact alice = new PersonBuilder().withName("Alice").build();
+ Contact bob = new PersonBuilder().withName("Bob").build();
+ AddContactCommand addAliceCommand = new AddContactCommand(alice);
+ AddContactCommand addBobCommand = new AddContactCommand(bob);
// same object -> returns true
assertTrue(addAliceCommand.equals(addAliceCommand));
// same values -> returns true
- AddCommand addAliceCommandCopy = new AddCommand(alice);
+ AddContactCommand addAliceCommandCopy = new AddContactCommand(alice);
assertTrue(addAliceCommand.equals(addAliceCommandCopy));
// different types -> returns false
@@ -73,15 +76,15 @@ public void equals() {
// null -> returns false
assertFalse(addAliceCommand.equals(null));
- // different person -> returns false
+ // different contact -> returns false
assertFalse(addAliceCommand.equals(addBobCommand));
}
@Test
public void toStringMethod() {
- AddCommand addCommand = new AddCommand(ALICE);
- String expected = AddCommand.class.getCanonicalName() + "{toAdd=" + ALICE + "}";
- assertEquals(expected, addCommand.toString());
+ AddContactCommand addContactCommand = new AddContactCommand(ALICE);
+ String expected = AddContactCommand.class.getCanonicalName() + "{toAdd=" + ALICE + "}";
+ assertEquals(expected, addContactCommand.toString());
}
/**
@@ -95,6 +98,7 @@ public void setUserPrefs(ReadOnlyUserPrefs userPrefs) {
@Override
public ReadOnlyUserPrefs getUserPrefs() {
+
throw new AssertionError("This method should not be called.");
}
@@ -119,7 +123,7 @@ public void setAddressBookFilePath(Path addressBookFilePath) {
}
@Override
- public void addPerson(Person person) {
+ public void addPerson(Contact contact) {
throw new AssertionError("This method should not be called.");
}
@@ -134,65 +138,115 @@ public ReadOnlyAddressBook getAddressBook() {
}
@Override
- public boolean hasPerson(Person person) {
+ public boolean hasContact(Contact contact) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void deleteContact(Contact target) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setPerson(Contact target, Contact editedContact) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ObservableList getFilteredPersonList() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void updateFilteredPersonList(Predicate predicate) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public boolean hasTrip(Trip trip) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void addTrip(Trip contact) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public Path getTripBookFilePath() {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setTripBookFilePath(Path tripBookFilePath) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public void setTripBook(ReadOnlyTripBook tripBook) {
+ throw new AssertionError("This method should not be called.");
+ }
+
+ @Override
+ public ReadOnlyTripBook getTripBook() {
throw new AssertionError("This method should not be called.");
}
@Override
- public void deletePerson(Person target) {
+ public void deleteTrip(Trip target) {
throw new AssertionError("This method should not be called.");
}
@Override
- public void setPerson(Person target, Person editedPerson) {
+ public void setTrip(Trip target, Trip editedTrip) {
throw new AssertionError("This method should not be called.");
}
@Override
- public ObservableList getFilteredPersonList() {
+ public ObservableList getFilteredTripList() {
throw new AssertionError("This method should not be called.");
}
@Override
- public void updateFilteredPersonList(Predicate predicate) {
+ public void updateFilteredTripList(Predicate predicate) {
throw new AssertionError("This method should not be called.");
}
}
/**
- * A Model stub that contains a single person.
+ * A Model stub that contains a single contact.
*/
private class ModelStubWithPerson extends ModelStub {
- private final Person person;
+ private final Contact contact;
- ModelStubWithPerson(Person person) {
- requireNonNull(person);
- this.person = person;
+ ModelStubWithPerson(Contact contact) {
+ requireNonNull(contact);
+ this.contact = contact;
}
@Override
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return this.person.isSamePerson(person);
+ public boolean hasContact(Contact contact) {
+ requireNonNull(contact);
+ return this.contact.isSamePerson(contact);
}
}
/**
- * A Model stub that always accept the person being added.
+ * A Model stub that always accept the contact being added.
*/
private class ModelStubAcceptingPersonAdded extends ModelStub {
- final ArrayList personsAdded = new ArrayList<>();
+ final ArrayList personsAdded = new ArrayList<>();
@Override
- public boolean hasPerson(Person person) {
- requireNonNull(person);
- return personsAdded.stream().anyMatch(person::isSamePerson);
+ public boolean hasContact(Contact contact) {
+ requireNonNull(contact);
+ return personsAdded.stream().anyMatch(contact::isSamePerson);
}
@Override
- public void addPerson(Person person) {
- requireNonNull(person);
- personsAdded.add(person);
+ public void addPerson(Contact contact) {
+ requireNonNull(contact);
+ personsAdded.add(contact);
}
@Override
diff --git a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
index 80d9110c03a..ba459e4dd32 100644
--- a/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ClearCommandTest.java
@@ -2,31 +2,51 @@
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTrips.getTypicalTripBook;
import org.junit.jupiter.api.Test;
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
+import seedu.address.model.TripBook;
import seedu.address.model.UserPrefs;
public class ClearCommandTest {
@Test
- public void execute_emptyAddressBook_success() {
+ public void execute_emptyAddressBook_requestsConfirmation() {
+ Model model = new ModelManager();
+
+ CommandResult expectedResult = new CommandResult(
+ "", true, ClearCommand.MESSAGE_CONFIRMATION);
+ assertCommandSuccess(new ClearCommand(), model, expectedResult, model);
+ }
+
+ @Test
+ public void execute_nonEmptyAddressBook_requestsConfirmation() {
+ Model model = new ModelManager(getTypicalAddressBook(), getTypicalTripBook(), new UserPrefs());
+
+ CommandResult expectedResult = new CommandResult(
+ "", true, ClearCommand.MESSAGE_CONFIRMATION);
+ assertCommandSuccess(new ClearCommand(), model, expectedResult, model);
+ }
+
+ @Test
+ public void executeConfirmed_emptyAddressBook_success() {
Model model = new ModelManager();
Model expectedModel = new ModelManager();
- assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel);
+ assertCommandSuccess(new ClearCommand(true), model, ClearCommand.MESSAGE_SUCCESS, expectedModel);
}
@Test
- public void execute_nonEmptyAddressBook_success() {
- Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ public void executeConfirmed_nonEmptyAddressBook_success() {
+ Model model = new ModelManager(getTypicalAddressBook(), getTypicalTripBook(), new UserPrefs());
+ Model expectedModel = new ModelManager(getTypicalAddressBook(), getTypicalTripBook(), new UserPrefs());
expectedModel.setAddressBook(new AddressBook());
+ expectedModel.setTripBook(new TripBook());
- assertCommandSuccess(new ClearCommand(), model, ClearCommand.MESSAGE_SUCCESS, expectedModel);
+ assertCommandSuccess(new ClearCommand(true), model, ClearCommand.MESSAGE_SUCCESS, expectedModel);
}
-
}
diff --git a/src/test/java/seedu/address/logic/commands/CommandResultTest.java b/src/test/java/seedu/address/logic/commands/CommandResultTest.java
index 7b8c7cd4546..f381a045e01 100644
--- a/src/test/java/seedu/address/logic/commands/CommandResultTest.java
+++ b/src/test/java/seedu/address/logic/commands/CommandResultTest.java
@@ -11,10 +11,9 @@ public class CommandResultTest {
@Test
public void equals() {
CommandResult commandResult = new CommandResult("feedback");
-
// same values -> returns true
assertTrue(commandResult.equals(new CommandResult("feedback")));
- assertTrue(commandResult.equals(new CommandResult("feedback", false, false)));
+ assertTrue(commandResult.equals(new CommandResult("feedback", false, false, false, null)));
// same object -> returns true
assertTrue(commandResult.equals(commandResult));
@@ -29,10 +28,16 @@ public void equals() {
assertFalse(commandResult.equals(new CommandResult("different")));
// different showHelp value -> returns false
- assertFalse(commandResult.equals(new CommandResult("feedback", true, false)));
+ assertFalse(commandResult.equals(new CommandResult("feedback", true, false, false, null)));
// different exit value -> returns false
- assertFalse(commandResult.equals(new CommandResult("feedback", false, true)));
+ assertFalse(commandResult.equals(new CommandResult("feedback", false, true, false, null)));
+
+ // different showConfirmation value -> returns false
+ assertFalse(commandResult.equals(new CommandResult("feedback", false, false, true, "confirm")));
+
+ // different confirmationText value -> returns false
+ assertFalse(commandResult.equals(new CommandResult("feedback", false, false, true, "different")));
}
@Test
@@ -46,10 +51,18 @@ public void hashcode() {
assertNotEquals(commandResult.hashCode(), new CommandResult("different").hashCode());
// different showHelp value -> returns different hashcode
- assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", true, false).hashCode());
+ assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", true, false, false, null).hashCode());
// different exit value -> returns different hashcode
- assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true).hashCode());
+ assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, true, false, null).hashCode());
+
+ // different showConfirmation value -> returns different hashcode
+ assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false, false,
+ true, "confirm").hashCode());
+
+ // different confirmationText value -> returns different hashcode
+ assertNotEquals(commandResult.hashCode(), new CommandResult("feedback", false,
+ false, true, "different").hashCode());
}
@Test
@@ -57,7 +70,8 @@ public void toStringMethod() {
CommandResult commandResult = new CommandResult("feedback");
String expected = CommandResult.class.getCanonicalName() + "{feedbackToUser="
+ commandResult.getFeedbackToUser() + ", showHelp=" + commandResult.isShowHelp()
- + ", exit=" + commandResult.isExit() + "}";
+ + ", exit=" + commandResult.isExit() + ", showConfirmation=" + commandResult.isShowConfirmation()
+ + ", confirmationText=" + commandResult.getConfirmationText() + "}";
assertEquals(expected, commandResult.toString());
}
}
diff --git a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
index 643a1d08069..12b04a53587 100644
--- a/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
+++ b/src/test/java/seedu/address/logic/commands/CommandTestUtil.java
@@ -2,9 +2,14 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ACCOMMODATION;
import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ITINERARY;
import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import static seedu.address.testutil.Assert.assertThrows;
@@ -14,12 +19,17 @@
import java.util.List;
import seedu.address.commons.core.index.Index;
+import seedu.address.commons.util.StringUtil;
import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.AddressBook;
import seedu.address.model.Model;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.NameContainsKeywordsPredicate;
+import seedu.address.model.contact.Note;
+import seedu.address.model.trip.Trip;
import seedu.address.testutil.EditPersonDescriptorBuilder;
+import seedu.address.testutil.EditTripDescriptorBuilder;
+import seedu.address.testutil.TripBuilder;
/**
* Contains helper methods for testing commands.
@@ -34,8 +44,8 @@ public class CommandTestUtil {
public static final String VALID_EMAIL_BOB = "bob@example.com";
public static final String VALID_ADDRESS_AMY = "Block 312, Amy Street 1";
public static final String VALID_ADDRESS_BOB = "Block 123, Bobby Street 3";
- public static final String VALID_TAG_HUSBAND = "husband";
- public static final String VALID_TAG_FRIEND = "friend";
+ public static final String VALID_TAG_CUSTOMER = "customer";
+ public static final String VALID_TAG_SERVICE = "service";
public static final String NAME_DESC_AMY = " " + PREFIX_NAME + VALID_NAME_AMY;
public static final String NAME_DESC_BOB = " " + PREFIX_NAME + VALID_NAME_BOB;
@@ -45,8 +55,8 @@ public class CommandTestUtil {
public static final String EMAIL_DESC_BOB = " " + PREFIX_EMAIL + VALID_EMAIL_BOB;
public static final String ADDRESS_DESC_AMY = " " + PREFIX_ADDRESS + VALID_ADDRESS_AMY;
public static final String ADDRESS_DESC_BOB = " " + PREFIX_ADDRESS + VALID_ADDRESS_BOB;
- public static final String TAG_DESC_FRIEND = " " + PREFIX_TAG + VALID_TAG_FRIEND;
- public static final String TAG_DESC_HUSBAND = " " + PREFIX_TAG + VALID_TAG_HUSBAND;
+ public static final String TAG_DESC_CUSTOMER = " " + PREFIX_TAG + VALID_TAG_CUSTOMER;
+ public static final String TAG_DESC_SERVICE = " " + PREFIX_TAG + VALID_TAG_SERVICE;
public static final String INVALID_NAME_DESC = " " + PREFIX_NAME + "James&"; // '&' not allowed in names
public static final String INVALID_PHONE_DESC = " " + PREFIX_PHONE + "911a"; // 'a' not allowed in phones
@@ -57,16 +67,82 @@ public class CommandTestUtil {
public static final String PREAMBLE_WHITESPACE = "\t \r \n";
public static final String PREAMBLE_NON_EMPTY = "NonEmptyPreamble";
- public static final EditCommand.EditPersonDescriptor DESC_AMY;
- public static final EditCommand.EditPersonDescriptor DESC_BOB;
+ public static final EditContactCommand.EditPersonDescriptor DESC_AMY;
+ public static final EditContactCommand.EditPersonDescriptor DESC_BOB;
static {
DESC_AMY = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
.withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
- .withTags(VALID_TAG_FRIEND).build();
+ .withTags(VALID_TAG_SERVICE).withNote(new Note("")).build();
DESC_BOB = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
.withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB)
- .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
+ .withTags(VALID_TAG_CUSTOMER, VALID_TAG_SERVICE).withNote(new Note("")).build();
+ }
+
+ public static final String VALID_TRIP_NAME_PARIS_2025 = "PARIS 2025";
+ public static final String TRIP_NAME_DESC_PARIS_2025 = " " + PREFIX_NAME + VALID_TRIP_NAME_PARIS_2025;
+ public static final String INVALID_TRIP_NAME_DESC = " " + PREFIX_NAME + "PARIS &&&";
+
+ public static final String VALID_ACCOMMODATION_HOTEL_81 = "Hotel 81";
+ public static final String ACCOMMODATION_DESC_HOTEL_81 = " " + PREFIX_ACCOMMODATION
+ + VALID_ACCOMMODATION_HOTEL_81;
+ public static final String INVALID_ACCOMMODATION_DESC = " " + PREFIX_ACCOMMODATION;
+
+ public static final String VALID_ITINERARY_EAT_BAGUETTES = "Eat baguettes";
+ public static final String ITINERARY_DESC_EAT_BAGUETTES = " " + PREFIX_ITINERARY
+ + VALID_ITINERARY_EAT_BAGUETTES;
+ public static final String INVALID_ITINERARY_DESC = " " + PREFIX_ITINERARY;
+
+ public static final String VALID_TRIP_DATE_2025 = "01/01/2025";
+ public static final String TRIP_DATE_DESC_2025 = " " + PREFIX_DATE
+ + VALID_TRIP_DATE_2025;
+ public static final String INVALID_TRIP_DATE_DESC = " " + PREFIX_DATE + "1/1/202";
+
+ public static final String TRIP_CUSTOMER_DESC_AMY = " " + PREFIX_CUSTOMER_NAME + VALID_NAME_AMY;
+ public static final String TRIP_CUSTOMER_DESC_BOB = " " + PREFIX_CUSTOMER_NAME + VALID_NAME_BOB;
+
+ public static final String VALID_NOTE = "Customer prefers window seat";
+
+ public static final String TRIP_NOTE_DESC = " " + PREFIX_NOTE + VALID_NOTE;
+
+ public static final Trip PARIS_2025_TRIP = new TripBuilder()
+ .withName(VALID_TRIP_NAME_PARIS_2025)
+ .withAccommodation(VALID_ACCOMMODATION_HOTEL_81)
+ .withItinerary(VALID_ITINERARY_EAT_BAGUETTES)
+ .withDate(VALID_TRIP_DATE_2025)
+ .withCustomerNames(VALID_NAME_AMY, VALID_NAME_BOB)
+ .withNote(VALID_NOTE)
+ .build();
+
+ public static final String VALID_TRIP_NAME_TOKYO_2026 = "TOKYO 2026";
+ public static final String TRIP_NAME_DESC_TOKYO_2026 = " " + PREFIX_NAME + VALID_TRIP_NAME_TOKYO_2026;
+
+ public static final String VALID_ACCOMMODATION_RITZ = "Ritz Hotel";
+ public static final String ACCOMMODATION_DESC_RITZ = " " + PREFIX_ACCOMMODATION + VALID_ACCOMMODATION_RITZ;
+
+ public static final String VALID_ITINERARY_VISIT_TOWER = "Visit Tokyo Tower";
+ public static final String ITINERARY_DESC_VISIT_TOWER = " " + PREFIX_ITINERARY + VALID_ITINERARY_VISIT_TOWER;
+
+ public static final String VALID_TRIP_DATE_2026 = "01/01/2026";
+ public static final String TRIP_DATE_DESC_2026 = " " + PREFIX_DATE + VALID_TRIP_DATE_2026;
+
+ public static final String VALID_NOTE_TOKYO = "Customer requested Japanese-speaking guide";
+ public static final String TRIP_NOTE_DESC_TOKYO = " " + PREFIX_NOTE + VALID_NOTE_TOKYO;
+
+ public static final EditTripCommand.EditTripDescriptor DESC_PARIS_2025;
+ public static final EditTripCommand.EditTripDescriptor DESC_TOKYO_2026;
+
+ static {
+ // Add this to the existing static block in CommandTestUtil.java
+ DESC_PARIS_2025 = new EditTripDescriptorBuilder().withName(VALID_TRIP_NAME_PARIS_2025)
+ .withAccommodation(VALID_ACCOMMODATION_HOTEL_81).withItinerary(VALID_ITINERARY_EAT_BAGUETTES)
+ .withDate(VALID_TRIP_DATE_2025).withCustomerNames(VALID_NAME_AMY, VALID_NAME_BOB)
+ .withNote(VALID_NOTE).build();
+
+ DESC_TOKYO_2026 = new EditTripDescriptorBuilder().withName(VALID_TRIP_NAME_TOKYO_2026)
+ .withAccommodation(VALID_ACCOMMODATION_RITZ).withItinerary(VALID_ITINERARY_VISIT_TOWER)
+ .withDate(VALID_TRIP_DATE_2026).withCustomerNames(VALID_NAME_AMY)
+ .withNote(VALID_NOTE_TOKYO).build();
}
/**
@@ -99,30 +175,45 @@ public static void assertCommandSuccess(Command command, Model actualModel, Stri
* Executes the given {@code command}, confirms that
* - a {@code CommandException} is thrown
* - the CommandException message matches {@code expectedMessage}
- * - the address book, filtered person list and selected person in {@code actualModel} remain unchanged
+ * - the address book, filtered contact list and selected contact in {@code actualModel} remain unchanged
*/
public static void assertCommandFailure(Command command, Model actualModel, String expectedMessage) {
// we are unable to defensively copy the model for comparison later, so we can
// only do so by copying its components.
AddressBook expectedAddressBook = new AddressBook(actualModel.getAddressBook());
- List expectedFilteredList = new ArrayList<>(actualModel.getFilteredPersonList());
+ List expectedFilteredList = new ArrayList<>(actualModel.getFilteredPersonList());
assertThrows(CommandException.class, expectedMessage, () -> command.execute(actualModel));
assertEquals(expectedAddressBook, actualModel.getAddressBook());
assertEquals(expectedFilteredList, actualModel.getFilteredPersonList());
}
/**
- * Updates {@code model}'s filtered list to show only the person at the given {@code targetIndex} in the
+ * Updates {@code model}'s filtered list to show only the contact at the given {@code targetIndex} in the
* {@code model}'s address book.
*/
public static void showPersonAtIndex(Model model, Index targetIndex) {
assertTrue(targetIndex.getZeroBased() < model.getFilteredPersonList().size());
- Person person = model.getFilteredPersonList().get(targetIndex.getZeroBased());
- final String[] splitName = person.getName().fullName.split("\\s+");
+ Contact contact = model.getFilteredPersonList().get(targetIndex.getZeroBased());
+ final String[] splitName = contact.getName().fullName.split("\\s+");
model.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(splitName[0])));
assertEquals(1, model.getFilteredPersonList().size());
}
+ /**
+ * Updates {@code model}'s filtered list to show only the trip at the given {@code targetIndex} in the
+ * {@code model}'s trip book.
+ */
+ public static void showTripAtIndex(Model model, Index targetIndex) {
+ assertTrue(targetIndex.getZeroBased() < model.getFilteredTripList().size());
+
+ Trip trip = model.getFilteredTripList().get(targetIndex.getZeroBased());
+ String tripName = trip.getName().toString();
+ final String[] splitName = tripName.split("\\s+");
+ model.updateFilteredTripList(t -> StringUtil.containsWordIgnoreCase(t.getName().toString(), splitName[0]));
+
+ assertEquals(1, model.getFilteredTripList().size());
+ }
+
}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
index b6f332eabca..8dfa01da5b0 100644
--- a/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/DeleteCommandTest.java
@@ -9,6 +9,7 @@
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTrips.getTypicalTripBook;
import org.junit.jupiter.api.Test;
@@ -17,7 +18,7 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
/**
* Contains integration tests (interaction with the Model) and unit tests for
@@ -25,18 +26,19 @@
*/
public class DeleteCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), getTypicalTripBook(), new UserPrefs());
@Test
public void execute_validIndexUnfilteredList_success() {
- Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
- DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON);
+ Contact contactToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ DeleteContactCommand deleteCommand = new DeleteContactCommand(INDEX_FIRST_PERSON);
- String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS,
- Messages.format(personToDelete));
+ String expectedMessage = String.format(DeleteContactCommand.MESSAGE_DELETE_CONTACT_SUCCESS,
+ Messages.format(contactToDelete));
- ModelManager expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
- expectedModel.deletePerson(personToDelete);
+
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ expectedModel.deleteContact(contactToDelete);
assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
}
@@ -44,7 +46,7 @@ public void execute_validIndexUnfilteredList_success() {
@Test
public void execute_invalidIndexUnfilteredList_throwsCommandException() {
Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
- DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex);
+ DeleteContactCommand deleteCommand = new DeleteContactCommand(outOfBoundIndex);
assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
@@ -53,14 +55,15 @@ public void execute_invalidIndexUnfilteredList_throwsCommandException() {
public void execute_validIndexFilteredList_success() {
showPersonAtIndex(model, INDEX_FIRST_PERSON);
- Person personToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
- DeleteCommand deleteCommand = new DeleteCommand(INDEX_FIRST_PERSON);
+ Contact contactToDelete = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ DeleteContactCommand deleteCommand = new DeleteContactCommand(INDEX_FIRST_PERSON);
+
+ String expectedMessage = String.format(DeleteContactCommand.MESSAGE_DELETE_CONTACT_SUCCESS,
+ Messages.format(contactToDelete));
- String expectedMessage = String.format(DeleteCommand.MESSAGE_DELETE_PERSON_SUCCESS,
- Messages.format(personToDelete));
- Model expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
- expectedModel.deletePerson(personToDelete);
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ expectedModel.deleteContact(contactToDelete);
showNoPerson(expectedModel);
assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
@@ -74,21 +77,21 @@ public void execute_invalidIndexFilteredList_throwsCommandException() {
// ensures that outOfBoundIndex is still in bounds of address book list
assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
- DeleteCommand deleteCommand = new DeleteCommand(outOfBoundIndex);
+ DeleteContactCommand deleteCommand = new DeleteContactCommand(outOfBoundIndex);
assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
}
@Test
public void equals() {
- DeleteCommand deleteFirstCommand = new DeleteCommand(INDEX_FIRST_PERSON);
- DeleteCommand deleteSecondCommand = new DeleteCommand(INDEX_SECOND_PERSON);
+ DeleteContactCommand deleteFirstCommand = new DeleteContactCommand(INDEX_FIRST_PERSON);
+ DeleteContactCommand deleteSecondCommand = new DeleteContactCommand(INDEX_SECOND_PERSON);
// same object -> returns true
assertTrue(deleteFirstCommand.equals(deleteFirstCommand));
// same values -> returns true
- DeleteCommand deleteFirstCommandCopy = new DeleteCommand(INDEX_FIRST_PERSON);
+ DeleteContactCommand deleteFirstCommandCopy = new DeleteContactCommand(INDEX_FIRST_PERSON);
assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy));
// different types -> returns false
@@ -97,15 +100,15 @@ public void equals() {
// null -> returns false
assertFalse(deleteFirstCommand.equals(null));
- // different person -> returns false
+ // different contact -> returns false
assertFalse(deleteFirstCommand.equals(deleteSecondCommand));
}
@Test
public void toStringMethod() {
Index targetIndex = Index.fromOneBased(1);
- DeleteCommand deleteCommand = new DeleteCommand(targetIndex);
- String expected = DeleteCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex + "}";
+ DeleteContactCommand deleteCommand = new DeleteContactCommand(targetIndex);
+ String expected = DeleteContactCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex + "}";
assertEquals(expected, deleteCommand.toString());
}
diff --git a/src/test/java/seedu/address/logic/commands/DeleteTripCommandTest.java b/src/test/java/seedu/address/logic/commands/DeleteTripCommandTest.java
new file mode 100644
index 00000000000..f4a5085e1d8
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/DeleteTripCommandTest.java
@@ -0,0 +1,121 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.logic.commands.CommandTestUtil.showTripAtIndex;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TRIP;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TRIP;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTrips.getTypicalTripBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.trip.Trip;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for
+ * {@code DeleteTripCommand}.
+ */
+public class DeleteTripCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), getTypicalTripBook(), new UserPrefs());
+
+ @Test
+ public void execute_validIndexUnfilteredList_success() {
+ Trip tripToDelete = model.getFilteredTripList().get(INDEX_FIRST_TRIP.getZeroBased());
+ DeleteTripCommand deleteCommand = new DeleteTripCommand(INDEX_FIRST_TRIP);
+
+ String expectedMessage = String.format(DeleteTripCommand.MESSAGE_DELETE_TRIP_SUCCESS,
+ Messages.format(tripToDelete));
+
+ ModelManager expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ expectedModel.deleteTrip(tripToDelete);
+
+ assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndexUnfilteredList_throwsCommandException() {
+ Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTripList().size() + 1);
+ DeleteTripCommand deleteCommand = new DeleteTripCommand(outOfBoundIndex);
+
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_TRIP_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void execute_validIndexFilteredList_success() {
+ showTripAtIndex(model, INDEX_FIRST_TRIP);
+
+ Trip tripToDelete = model.getFilteredTripList().get(INDEX_FIRST_TRIP.getZeroBased());
+ DeleteTripCommand deleteCommand = new DeleteTripCommand(INDEX_FIRST_TRIP);
+
+ String expectedMessage = String.format(DeleteTripCommand.MESSAGE_DELETE_TRIP_SUCCESS,
+ Messages.format(tripToDelete));
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ expectedModel.deleteTrip(tripToDelete);
+ showNoTrip(expectedModel);
+
+ assertCommandSuccess(deleteCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_invalidIndexFilteredList_throwsCommandException() {
+ showTripAtIndex(model, INDEX_FIRST_TRIP);
+
+ Index outOfBoundIndex = INDEX_SECOND_TRIP;
+ // ensures that outOfBoundIndex is still in bounds of trip book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getTripBook().getTripList().size());
+
+ DeleteTripCommand deleteCommand = new DeleteTripCommand(outOfBoundIndex);
+
+ assertCommandFailure(deleteCommand, model, Messages.MESSAGE_INVALID_TRIP_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void equals() {
+ DeleteTripCommand deleteFirstCommand = new DeleteTripCommand(INDEX_FIRST_TRIP);
+ DeleteTripCommand deleteSecondCommand = new DeleteTripCommand(INDEX_SECOND_TRIP);
+
+ // same object -> returns true
+ assertTrue(deleteFirstCommand.equals(deleteFirstCommand));
+
+ // same values -> returns true
+ DeleteTripCommand deleteFirstCommandCopy = new DeleteTripCommand(INDEX_FIRST_TRIP);
+ assertTrue(deleteFirstCommand.equals(deleteFirstCommandCopy));
+
+ // different types -> returns false
+ assertFalse(deleteFirstCommand.equals(1));
+
+ // null -> returns false
+ assertFalse(deleteFirstCommand.equals(null));
+
+ // different trip -> returns false
+ assertFalse(deleteFirstCommand.equals(deleteSecondCommand));
+ }
+
+ @Test
+ public void toStringMethod() {
+ Index targetIndex = Index.fromOneBased(1);
+ DeleteTripCommand deleteCommand = new DeleteTripCommand(targetIndex);
+ String expected = DeleteTripCommand.class.getCanonicalName() + "{targetIndex=" + targetIndex + "}";
+ assertEquals(expected, deleteCommand.toString());
+ }
+
+ /**
+ * Updates {@code model}'s filtered list to show no trip.
+ */
+ private void showNoTrip(Model model) {
+ model.updateFilteredTripList(p -> false);
+
+ assertTrue(model.getFilteredTripList().isEmpty());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/EditCommandTest.java b/src/test/java/seedu/address/logic/commands/EditCommandTest.java
deleted file mode 100644
index 469dd97daa7..00000000000
--- a/src/test/java/seedu/address/logic/commands/EditCommandTest.java
+++ /dev/null
@@ -1,184 +0,0 @@
-package seedu.address.logic.commands;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY;
-import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
-import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
-import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
-import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
-import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
-import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
-
-import org.junit.jupiter.api.Test;
-
-import seedu.address.commons.core.index.Index;
-import seedu.address.logic.Messages;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.model.AddressBook;
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
-import seedu.address.model.UserPrefs;
-import seedu.address.model.person.Person;
-import seedu.address.testutil.EditPersonDescriptorBuilder;
-import seedu.address.testutil.PersonBuilder;
-
-/**
- * Contains integration tests (interaction with the Model) and unit tests for EditCommand.
- */
-public class EditCommandTest {
-
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
-
- @Test
- public void execute_allFieldsSpecifiedUnfilteredList_success() {
- Person editedPerson = new PersonBuilder().build();
- EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build();
- EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
-
- String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
-
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
- expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
-
- assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
- }
-
- @Test
- public void execute_someFieldsSpecifiedUnfilteredList_success() {
- Index indexLastPerson = Index.fromOneBased(model.getFilteredPersonList().size());
- Person lastPerson = model.getFilteredPersonList().get(indexLastPerson.getZeroBased());
-
- PersonBuilder personInList = new PersonBuilder(lastPerson);
- Person editedPerson = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
- .withTags(VALID_TAG_HUSBAND).build();
-
- EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
- .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_HUSBAND).build();
- EditCommand editCommand = new EditCommand(indexLastPerson, descriptor);
-
- String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
-
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
- expectedModel.setPerson(lastPerson, editedPerson);
-
- assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
- }
-
- @Test
- public void execute_noFieldSpecifiedUnfilteredList_success() {
- EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, new EditPersonDescriptor());
- Person editedPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
-
- String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
-
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
-
- assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
- }
-
- @Test
- public void execute_filteredList_success() {
- showPersonAtIndex(model, INDEX_FIRST_PERSON);
-
- Person personInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
- Person editedPerson = new PersonBuilder(personInFilteredList).withName(VALID_NAME_BOB).build();
- EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON,
- new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build());
-
- String expectedMessage = String.format(EditCommand.MESSAGE_EDIT_PERSON_SUCCESS, Messages.format(editedPerson));
-
- Model expectedModel = new ModelManager(new AddressBook(model.getAddressBook()), new UserPrefs());
- expectedModel.setPerson(model.getFilteredPersonList().get(0), editedPerson);
-
- assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
- }
-
- @Test
- public void execute_duplicatePersonUnfilteredList_failure() {
- Person firstPerson = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
- EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstPerson).build();
- EditCommand editCommand = new EditCommand(INDEX_SECOND_PERSON, descriptor);
-
- assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON);
- }
-
- @Test
- public void execute_duplicatePersonFilteredList_failure() {
- showPersonAtIndex(model, INDEX_FIRST_PERSON);
-
- // edit person in filtered list into a duplicate in address book
- Person personInList = model.getAddressBook().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased());
- EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON,
- new EditPersonDescriptorBuilder(personInList).build());
-
- assertCommandFailure(editCommand, model, EditCommand.MESSAGE_DUPLICATE_PERSON);
- }
-
- @Test
- public void execute_invalidPersonIndexUnfilteredList_failure() {
- Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
- EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build();
- EditCommand editCommand = new EditCommand(outOfBoundIndex, descriptor);
-
- assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- /**
- * Edit filtered list where index is larger than size of filtered list,
- * but smaller than size of address book
- */
- @Test
- public void execute_invalidPersonIndexFilteredList_failure() {
- showPersonAtIndex(model, INDEX_FIRST_PERSON);
- Index outOfBoundIndex = INDEX_SECOND_PERSON;
- // ensures that outOfBoundIndex is still in bounds of address book list
- assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
-
- EditCommand editCommand = new EditCommand(outOfBoundIndex,
- new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build());
-
- assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
- }
-
- @Test
- public void equals() {
- final EditCommand standardCommand = new EditCommand(INDEX_FIRST_PERSON, DESC_AMY);
-
- // same values -> returns true
- EditPersonDescriptor copyDescriptor = new EditPersonDescriptor(DESC_AMY);
- EditCommand commandWithSameValues = new EditCommand(INDEX_FIRST_PERSON, copyDescriptor);
- assertTrue(standardCommand.equals(commandWithSameValues));
-
- // same object -> returns true
- assertTrue(standardCommand.equals(standardCommand));
-
- // null -> returns false
- assertFalse(standardCommand.equals(null));
-
- // different types -> returns false
- assertFalse(standardCommand.equals(new ClearCommand()));
-
- // different index -> returns false
- assertFalse(standardCommand.equals(new EditCommand(INDEX_SECOND_PERSON, DESC_AMY)));
-
- // different descriptor -> returns false
- assertFalse(standardCommand.equals(new EditCommand(INDEX_FIRST_PERSON, DESC_BOB)));
- }
-
- @Test
- public void toStringMethod() {
- Index index = Index.fromOneBased(1);
- EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
- EditCommand editCommand = new EditCommand(index, editPersonDescriptor);
- String expected = EditCommand.class.getCanonicalName() + "{index=" + index + ", editPersonDescriptor="
- + editPersonDescriptor + "}";
- assertEquals(expected, editCommand.toString());
- }
-
-}
diff --git a/src/test/java/seedu/address/logic/commands/EditContactCommandTest.java b/src/test/java/seedu/address/logic/commands/EditContactCommandTest.java
new file mode 100644
index 00000000000..d3dc0adbaec
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/EditContactCommandTest.java
@@ -0,0 +1,189 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_CUSTOMER;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_PERSON;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTrips.getTypicalTripBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.EditContactCommand.EditPersonDescriptor;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.Note;
+import seedu.address.testutil.EditPersonDescriptorBuilder;
+import seedu.address.testutil.PersonBuilder;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for EditContactCommand.
+ */
+public class EditContactCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), getTypicalTripBook(), new UserPrefs());
+
+ @Test
+ public void execute_allFieldsSpecifiedUnfilteredList_success() {
+ Contact editedContact = new PersonBuilder().build();
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedContact).build();
+ EditContactCommand editContactCommand = new EditContactCommand(INDEX_FIRST_PERSON, descriptor);
+
+ String expectedMessage = String.format(EditContactCommand.MESSAGE_EDIT_PERSON_SUCCESS,
+ Messages.format(editedContact));
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ expectedModel.setPerson(model.getFilteredPersonList().get(0), editedContact);
+
+ assertCommandSuccess(editContactCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_someFieldsSpecifiedUnfilteredList_success() {
+ Index indexLastPerson = Index.fromOneBased(model.getFilteredPersonList().size());
+ Contact lastContact = model.getFilteredPersonList().get(indexLastPerson.getZeroBased());
+
+ PersonBuilder personInList = new PersonBuilder(lastContact);
+ Contact editedContact = personInList.withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
+ .withTags(VALID_TAG_CUSTOMER).withNote("New note").build();
+
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB)
+ .withPhone(VALID_PHONE_BOB).withTags(VALID_TAG_CUSTOMER).withNote(new Note("New note")).build();
+ EditContactCommand editContactCommand = new EditContactCommand(indexLastPerson, descriptor);
+
+ String expectedMessage = String.format(EditContactCommand.MESSAGE_EDIT_PERSON_SUCCESS,
+ Messages.format(editedContact));
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ expectedModel.setPerson(lastContact, editedContact);
+
+ assertCommandSuccess(editContactCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_noFieldSpecifiedUnfilteredList_success() {
+ EditContactCommand editContactCommand = new EditContactCommand(INDEX_FIRST_PERSON, new EditPersonDescriptor());
+ Contact editedContact = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+
+ String expectedMessage = String.format(EditContactCommand.MESSAGE_EDIT_PERSON_SUCCESS,
+ Messages.format(editedContact));
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+
+ assertCommandSuccess(editContactCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_filteredList_success() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ Contact contactInFilteredList = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ Contact editedContact = new PersonBuilder(contactInFilteredList).withName(VALID_NAME_BOB).build();
+ EditContactCommand editContactCommand = new EditContactCommand(INDEX_FIRST_PERSON,
+ new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build());
+
+ String expectedMessage = String.format(EditContactCommand.MESSAGE_EDIT_PERSON_SUCCESS,
+ Messages.format(editedContact));
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ expectedModel.setPerson(model.getFilteredPersonList().get(0), editedContact);
+
+ assertCommandSuccess(editContactCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test
+ public void execute_duplicatePersonUnfilteredList_failure() {
+ Contact firstContact = model.getFilteredPersonList().get(INDEX_FIRST_PERSON.getZeroBased());
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(firstContact).build();
+ EditContactCommand editContactCommand = new EditContactCommand(INDEX_SECOND_PERSON, descriptor);
+
+ assertCommandFailure(editContactCommand, model, EditContactCommand.MESSAGE_DUPLICATE_PERSON);
+ }
+
+ @Test
+ public void execute_duplicatePersonFilteredList_failure() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+
+ // edit contact in filtered list into a duplicate in address book
+ Contact contactInList = model.getAddressBook().getPersonList().get(INDEX_SECOND_PERSON.getZeroBased());
+ EditContactCommand editContactCommand = new EditContactCommand(INDEX_FIRST_PERSON,
+ new EditPersonDescriptorBuilder(contactInList).build());
+
+ assertCommandFailure(editContactCommand, model, EditContactCommand.MESSAGE_DUPLICATE_PERSON);
+ }
+
+ @Test
+ public void execute_invalidPersonIndexUnfilteredList_failure() {
+ Index outOfBoundIndex = Index.fromOneBased(model.getFilteredPersonList().size() + 1);
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build();
+ EditContactCommand editContactCommand = new EditContactCommand(outOfBoundIndex, descriptor);
+
+ assertCommandFailure(editContactCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ /**
+ * Edit filtered list where index is larger than size of filtered list,
+ * but smaller than size of address book
+ */
+ @Test
+ public void execute_invalidPersonIndexFilteredList_failure() {
+ showPersonAtIndex(model, INDEX_FIRST_PERSON);
+ Index outOfBoundIndex = INDEX_SECOND_PERSON;
+ // ensures that outOfBoundIndex is still in bounds of address book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getAddressBook().getPersonList().size());
+
+ EditContactCommand editContactCommand = new EditContactCommand(outOfBoundIndex,
+ new EditPersonDescriptorBuilder().withName(VALID_NAME_BOB).build());
+
+ assertCommandFailure(editContactCommand, model, Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
+ }
+
+ @Test
+ public void equals() {
+ final EditContactCommand standardCommand = new EditContactCommand(INDEX_FIRST_PERSON, DESC_AMY);
+
+ // same values -> returns true
+ EditPersonDescriptor copyDescriptor = new EditPersonDescriptor(DESC_AMY);
+ EditContactCommand commandWithSameValues = new EditContactCommand(INDEX_FIRST_PERSON, copyDescriptor);
+ assertTrue(standardCommand.equals(commandWithSameValues));
+
+ // same object -> returns true
+ assertTrue(standardCommand.equals(standardCommand));
+
+ // null -> returns false
+ assertFalse(standardCommand.equals(null));
+
+ // different types -> returns false
+ assertFalse(standardCommand.equals(new ClearCommand()));
+
+ // different index -> returns false
+ assertFalse(standardCommand.equals(new EditContactCommand(INDEX_SECOND_PERSON, DESC_AMY)));
+
+ // different descriptor -> returns false
+ assertFalse(standardCommand.equals(new EditContactCommand(INDEX_FIRST_PERSON, DESC_BOB)));
+ }
+
+ @Test
+ public void toStringMethod() {
+ Index index = Index.fromOneBased(1);
+ EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor();
+ EditContactCommand editContactCommand = new EditContactCommand(index, editPersonDescriptor);
+ String expected = EditContactCommand.class.getCanonicalName() + "{index=" + index + ", editPersonDescriptor="
+ + editPersonDescriptor + "}";
+ assertEquals(expected, editContactCommand.toString());
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java b/src/test/java/seedu/address/logic/commands/EditContactDescriptorTest.java
similarity index 86%
rename from src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
rename to src/test/java/seedu/address/logic/commands/EditContactDescriptorTest.java
index b17c1f3d5c2..fce5a113556 100644
--- a/src/test/java/seedu/address/logic/commands/EditPersonDescriptorTest.java
+++ b/src/test/java/seedu/address/logic/commands/EditContactDescriptorTest.java
@@ -9,14 +9,15 @@
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_CUSTOMER;
import org.junit.jupiter.api.Test;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.logic.commands.EditContactCommand.EditPersonDescriptor;
+import seedu.address.model.contact.Note;
import seedu.address.testutil.EditPersonDescriptorBuilder;
-public class EditPersonDescriptorTest {
+public class EditContactDescriptorTest {
@Test
public void equals() {
@@ -53,7 +54,11 @@ public void equals() {
assertFalse(DESC_AMY.equals(editedAmy));
// different tags -> returns false
- editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTags(VALID_TAG_HUSBAND).build();
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withTags(VALID_TAG_CUSTOMER).build();
+ assertFalse(DESC_AMY.equals(editedAmy));
+
+ // different note -> returns false
+ editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withNote(new Note("Different note")).build();
assertFalse(DESC_AMY.equals(editedAmy));
}
@@ -65,7 +70,8 @@ public void toStringMethod() {
+ editPersonDescriptor.getPhone().orElse(null) + ", email="
+ editPersonDescriptor.getEmail().orElse(null) + ", address="
+ editPersonDescriptor.getAddress().orElse(null) + ", tags="
- + editPersonDescriptor.getTags().orElse(null) + "}";
+ + editPersonDescriptor.getTags().orElse(null) + ", note="
+ + editPersonDescriptor.getNote().orElse(null) + "}";
assertEquals(expected, editPersonDescriptor.toString());
}
}
diff --git a/src/test/java/seedu/address/logic/commands/EditTripCommandTest.java b/src/test/java/seedu/address/logic/commands/EditTripCommandTest.java
new file mode 100644
index 00000000000..82796f20207
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/EditTripCommandTest.java
@@ -0,0 +1,191 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.DESC_PARIS_2025;
+import static seedu.address.logic.commands.CommandTestUtil.DESC_TOKYO_2026;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ACCOMMODATION_HOTEL_81;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TRIP_NAME_TOKYO_2026;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.logic.commands.CommandTestUtil.showTripAtIndex;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TRIP;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TRIP;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTrips.getTypicalTripBook;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.EditTripCommand.EditTripDescriptor;
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.trip.Trip;
+import seedu.address.testutil.EditTripDescriptorBuilder;
+import seedu.address.testutil.TripBuilder;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for EditTripCommand.
+ */
+public class EditTripCommandTest {
+
+ private Model model = new ModelManager(getTypicalAddressBook(), getTypicalTripBook(), new UserPrefs());
+
+ @Test public void execute_allFieldsSpecifiedUnfilteredList_success() {
+ Trip editedTrip = new TripBuilder().build();
+ EditTripDescriptor descriptor = new EditTripDescriptorBuilder(editedTrip).build();
+ EditTripCommand editCommand = new EditTripCommand(INDEX_FIRST_TRIP, descriptor);
+
+ String expectedMessage = String.format(EditTripCommand.MESSAGE_EDIT_TRIP_SUCCESS, Messages.format(editedTrip));
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ expectedModel.setTrip(model.getFilteredTripList().get(0), editedTrip);
+
+ assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test public void execute_someFieldsSpecifiedUnfilteredList_success() {
+ Index indexLastTrip = Index.fromOneBased(model.getFilteredTripList().size());
+ Trip lastTrip = model.getFilteredTripList().get(indexLastTrip.getZeroBased());
+
+ TripBuilder tripInList = new TripBuilder(lastTrip);
+ Trip editedTrip =
+ tripInList.withName(VALID_TRIP_NAME_TOKYO_2026).withAccommodation(VALID_ACCOMMODATION_HOTEL_81)
+ .withNote("Window seat preferred").build();
+
+ EditTripDescriptor descriptor = new EditTripDescriptorBuilder().withName(VALID_TRIP_NAME_TOKYO_2026)
+ .withAccommodation(VALID_ACCOMMODATION_HOTEL_81).withNote("Window seat preferred").build();
+ EditTripCommand editCommand = new EditTripCommand(indexLastTrip, descriptor);
+
+ String expectedMessage = String.format(EditTripCommand.MESSAGE_EDIT_TRIP_SUCCESS, Messages.format(editedTrip));
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ expectedModel.setTrip(lastTrip, editedTrip);
+
+ assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test public void execute_noFieldSpecifiedUnfilteredList_success() {
+ EditTripCommand editCommand = new EditTripCommand(INDEX_FIRST_TRIP, new EditTripDescriptor());
+ Trip editedTrip = model.getFilteredTripList().get(INDEX_FIRST_TRIP.getZeroBased());
+
+ String expectedMessage = String.format(EditTripCommand.MESSAGE_EDIT_TRIP_SUCCESS, Messages.format(editedTrip));
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+
+ assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test public void execute_filteredList_success() {
+ showTripAtIndex(model, INDEX_FIRST_TRIP);
+
+ Trip tripInFilteredList = model.getFilteredTripList().get(INDEX_FIRST_TRIP.getZeroBased());
+ Trip editedTrip = new TripBuilder(tripInFilteredList).withName(VALID_TRIP_NAME_TOKYO_2026).build();
+ EditTripCommand editCommand = new EditTripCommand(INDEX_FIRST_TRIP,
+ new EditTripDescriptorBuilder().withName(VALID_TRIP_NAME_TOKYO_2026).build());
+
+ String expectedMessage = String.format(EditTripCommand.MESSAGE_EDIT_TRIP_SUCCESS, Messages.format(editedTrip));
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ expectedModel.setTrip(model.getFilteredTripList().get(0), editedTrip);
+
+ assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test public void execute_duplicateTripUnfilteredList_failure() {
+ Trip firstTrip = model.getFilteredTripList().get(INDEX_FIRST_TRIP.getZeroBased());
+ EditTripDescriptor descriptor = new EditTripDescriptorBuilder(firstTrip).build();
+ EditTripCommand editCommand = new EditTripCommand(INDEX_SECOND_TRIP, descriptor);
+
+ assertCommandFailure(editCommand, model, EditTripCommand.MESSAGE_DUPLICATE_TRIP);
+ }
+
+ @Test public void execute_duplicateTripFilteredList_failure() {
+ showTripAtIndex(model, INDEX_FIRST_TRIP);
+
+ // edit trip in filtered list into a duplicate in address book
+ Trip tripInList = model.getTripBook().getTripList().get(INDEX_SECOND_TRIP.getZeroBased());
+ EditTripCommand editCommand =
+ new EditTripCommand(INDEX_FIRST_TRIP, new EditTripDescriptorBuilder(tripInList).build());
+
+ assertCommandFailure(editCommand, model, EditTripCommand.MESSAGE_DUPLICATE_TRIP);
+ }
+
+ @Test public void execute_invalidTripIndexUnfilteredList_failure() {
+ Index outOfBoundIndex = Index.fromOneBased(model.getFilteredTripList().size() + 1);
+ EditTripDescriptor descriptor = new EditTripDescriptorBuilder().withName(VALID_TRIP_NAME_TOKYO_2026).build();
+ EditTripCommand editCommand = new EditTripCommand(outOfBoundIndex, descriptor);
+
+ assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_TRIP_DISPLAYED_INDEX);
+ }
+
+ /**
+ * Edit filtered list where index is larger than size of filtered list,
+ * but smaller than size of trip book
+ */
+ @Test public void execute_invalidTripIndexFilteredList_failure() {
+ showTripAtIndex(model, INDEX_FIRST_TRIP);
+ Index outOfBoundIndex = INDEX_SECOND_TRIP;
+ // ensures that outOfBoundIndex is still in bounds of trip book list
+ assertTrue(outOfBoundIndex.getZeroBased() < model.getTripBook().getTripList().size());
+
+ EditTripCommand editCommand = new EditTripCommand(outOfBoundIndex,
+ new EditTripDescriptorBuilder().withName(VALID_TRIP_NAME_TOKYO_2026).build());
+
+ assertCommandFailure(editCommand, model, Messages.MESSAGE_INVALID_TRIP_DISPLAYED_INDEX);
+ }
+
+ @Test public void execute_emptyNoteUnfilteredList_success() {
+ Index indexLastTrip = Index.fromOneBased(model.getFilteredTripList().size());
+ Trip lastTrip = model.getFilteredTripList().get(indexLastTrip.getZeroBased());
+
+ TripBuilder tripInList = new TripBuilder(lastTrip);
+ Trip editedTrip = tripInList.withNote("").build();
+
+ EditTripDescriptor descriptor = new EditTripDescriptorBuilder().withNote("").build();
+ EditTripCommand editCommand = new EditTripCommand(indexLastTrip, descriptor);
+
+ String expectedMessage = String.format(EditTripCommand.MESSAGE_EDIT_TRIP_SUCCESS, Messages.format(editedTrip));
+
+ Model expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ expectedModel.setTrip(lastTrip, editedTrip);
+
+ assertCommandSuccess(editCommand, model, expectedMessage, expectedModel);
+ }
+
+ @Test public void equals() {
+ final EditTripCommand standardCommand = new EditTripCommand(INDEX_FIRST_TRIP, DESC_PARIS_2025);
+
+ // same values -> returns true
+ EditTripDescriptor copyDescriptor = new EditTripDescriptor(DESC_PARIS_2025);
+ EditTripCommand commandWithSameValues = new EditTripCommand(INDEX_FIRST_TRIP, copyDescriptor);
+ assertTrue(standardCommand.equals(commandWithSameValues));
+
+ // same object -> returns true
+ assertTrue(standardCommand.equals(standardCommand));
+
+ // null -> returns false
+ assertFalse(standardCommand.equals(null));
+
+ // different types -> returns false
+ assertFalse(standardCommand.equals(new ClearCommand()));
+
+ // different index -> returns false
+ assertFalse(standardCommand.equals(new EditTripCommand(INDEX_SECOND_TRIP, DESC_PARIS_2025)));
+
+ // different descriptor -> returns false
+ assertFalse(standardCommand.equals(new EditTripCommand(INDEX_FIRST_TRIP, DESC_TOKYO_2026)));
+ }
+
+ @Test public void toStringMethod() {
+ Index index = Index.fromOneBased(1);
+ EditTripDescriptor editTripDescriptor = new EditTripDescriptor();
+ EditTripCommand editCommand = new EditTripCommand(index, editTripDescriptor);
+ String expected = EditTripCommand.class.getCanonicalName() + "{index=" + index + ", editTripDescriptor="
+ + editTripDescriptor + "}";
+ assertEquals(expected, editCommand.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
index 9533c473875..19442f05612 100644
--- a/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/ExitCommandTest.java
@@ -14,7 +14,7 @@ public class ExitCommandTest {
@Test
public void execute_exit_success() {
- CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true);
+ CommandResult expectedCommandResult = new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT, false, true, false, null);
assertCommandSuccess(new ExitCommand(), model, expectedCommandResult, expectedModel);
}
}
diff --git a/src/test/java/seedu/address/logic/commands/FindCommandTest.java b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
index b8b7dbba91a..29101757611 100644
--- a/src/test/java/seedu/address/logic/commands/FindCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/FindCommandTest.java
@@ -3,12 +3,14 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.Messages.MESSAGE_NO_MATCHING_NAMES_FOUND;
import static seedu.address.logic.Messages.MESSAGE_PERSONS_LISTED_OVERVIEW;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.testutil.TypicalPersons.CARL;
import static seedu.address.testutil.TypicalPersons.ELLE;
import static seedu.address.testutil.TypicalPersons.FIONA;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTrips.getTypicalTripBook;
import java.util.Arrays;
import java.util.Collections;
@@ -18,14 +20,14 @@
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.contact.NameContainsKeywordsPredicate;
/**
* Contains integration tests (interaction with the Model) for {@code FindCommand}.
*/
public class FindCommandTest {
- private Model model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- private Model expectedModel = new ModelManager(getTypicalAddressBook(), new UserPrefs());
+ private Model model = new ModelManager(getTypicalAddressBook(), getTypicalTripBook(), new UserPrefs());
+ private Model expectedModel = new ModelManager(getTypicalAddressBook(), getTypicalTripBook(), new UserPrefs());
@Test
public void equals() {
@@ -50,13 +52,13 @@ public void equals() {
// null -> returns false
assertFalse(findFirstCommand.equals(null));
- // different person -> returns false
+ // different contact -> returns false
assertFalse(findFirstCommand.equals(findSecondCommand));
}
@Test
public void execute_zeroKeywords_noPersonFound() {
- String expectedMessage = String.format(MESSAGE_PERSONS_LISTED_OVERVIEW, 0);
+ String expectedMessage = MESSAGE_NO_MATCHING_NAMES_FOUND;
NameContainsKeywordsPredicate predicate = preparePredicate(" ");
FindCommand command = new FindCommand(predicate);
expectedModel.updateFilteredPersonList(predicate);
diff --git a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java
index 4904fc4352e..f1625764e6b 100644
--- a/src/test/java/seedu/address/logic/commands/HelpCommandTest.java
+++ b/src/test/java/seedu/address/logic/commands/HelpCommandTest.java
@@ -14,7 +14,8 @@ public class HelpCommandTest {
@Test
public void execute_help_success() {
- CommandResult expectedCommandResult = new CommandResult(SHOWING_HELP_MESSAGE, true, false);
+ CommandResult expectedCommandResult =
+ new CommandResult(SHOWING_HELP_MESSAGE, false, false, false, null);
assertCommandSuccess(new HelpCommand(), model, expectedCommandResult, expectedModel);
}
}
diff --git a/src/test/java/seedu/address/logic/commands/ListCommandTest.java b/src/test/java/seedu/address/logic/commands/ListCommandTest.java
deleted file mode 100644
index 435ff1f7275..00000000000
--- a/src/test/java/seedu/address/logic/commands/ListCommandTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package seedu.address.logic.commands;
-
-import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
-import static seedu.address.logic.commands.CommandTestUtil.showPersonAtIndex;
-import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
-import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import seedu.address.model.Model;
-import seedu.address.model.ModelManager;
-import seedu.address.model.UserPrefs;
-
-/**
- * Contains integration tests (interaction with the Model) and unit tests for ListCommand.
- */
-public class ListCommandTest {
-
- private Model model;
- private Model expectedModel;
-
- @BeforeEach
- public void setUp() {
- model = new ModelManager(getTypicalAddressBook(), new UserPrefs());
- expectedModel = new ModelManager(model.getAddressBook(), new UserPrefs());
- }
-
- @Test
- public void execute_listIsNotFiltered_showsSameList() {
- assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel);
- }
-
- @Test
- public void execute_listIsFiltered_showsEverything() {
- showPersonAtIndex(model, INDEX_FIRST_PERSON);
- assertCommandSuccess(new ListCommand(), model, ListCommand.MESSAGE_SUCCESS, expectedModel);
- }
-}
diff --git a/src/test/java/seedu/address/logic/commands/ListContactCommandTest.java b/src/test/java/seedu/address/logic/commands/ListContactCommandTest.java
new file mode 100644
index 00000000000..bd2aafc4285
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ListContactCommandTest.java
@@ -0,0 +1,55 @@
+package seedu.address.logic.commands;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_CUSTOMER;
+import static seedu.address.model.Model.PREDICATE_SHOW_ALL_SERVICE;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTrips.getTypicalTripBook;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for ListContactCommand.
+ */
+public class ListContactCommandTest {
+
+ private Model model;
+ private Model expectedModel;
+
+ @BeforeEach
+ public void setUp() {
+ model = new ModelManager(getTypicalAddressBook(), getTypicalTripBook(), new UserPrefs());
+ expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ }
+
+ @Test
+ public void execute_listContactIsNotFiltered_showsSameList() {
+ assertCommandSuccess(new ListContactCommand(""), model,
+ String.format(ListContactCommand.MESSAGE_SUCCESS, ""), expectedModel);
+ }
+
+ @Test
+ public void execute_listContactFilteredByCustomer() {
+ String customerTagName = "customer";
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_CUSTOMER);
+ assertTrue(model.getFilteredPersonList().size() == 6);
+ assertCommandSuccess(new ListContactCommand(customerTagName), model,
+ String.format(ListContactCommand.MESSAGE_SUCCESS, (customerTagName + " ")), model);
+ }
+
+ @Test
+ public void execute_listContactFilteredByService() {
+ String serviceTagName = "service";
+ assertCommandSuccess(new ListContactCommand(serviceTagName), model,
+ String.format(ListContactCommand.MESSAGE_SUCCESS, (serviceTagName + " ")), model);
+ model.updateFilteredPersonList(PREDICATE_SHOW_ALL_SERVICE);
+ assertTrue(model.getFilteredPersonList().size() == 2);
+ }
+
+}
diff --git a/src/test/java/seedu/address/logic/commands/ListTripCommandTest.java b/src/test/java/seedu/address/logic/commands/ListTripCommandTest.java
new file mode 100644
index 00000000000..2694a67a0e0
--- /dev/null
+++ b/src/test/java/seedu/address/logic/commands/ListTripCommandTest.java
@@ -0,0 +1,86 @@
+package seedu.address.logic.commands;
+
+import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
+import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
+import static seedu.address.testutil.TypicalTrips.BALI;
+import static seedu.address.testutil.TypicalTrips.getTypicalTripBook;
+
+import java.time.LocalDate;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.Model;
+import seedu.address.model.ModelManager;
+import seedu.address.model.UserPrefs;
+import seedu.address.model.trip.Trip;
+import seedu.address.model.trip.TripDate;
+import seedu.address.testutil.TripBuilder;
+
+/**
+ * Contains integration tests (interaction with the Model) and unit tests for ListTripCommand.
+ */
+
+public class ListTripCommandTest {
+
+ private Model model;
+ private Model expectedModel;
+
+ /**
+ * Sets up the test environment before each test.
+ * Initializes the model with typical data for trips and address book.
+ */
+ @BeforeEach
+ public void setUp() {
+ model = new ModelManager(getTypicalAddressBook(), getTypicalTripBook(), new UserPrefs());
+ expectedModel = new ModelManager(model.getAddressBook(), model.getTripBook(), new UserPrefs());
+ }
+
+ /**
+ * Tests if executing ListTripCommand on an unfiltered trip list
+ * results in the same list being created.
+ */
+ @Test
+ public void execute_listAllTrips_success() {
+ expectedModel.updateFilteredTripList(trip -> true);
+ assertCommandSuccess(new ListTripCommand(), model,
+ "All trips are listed.", expectedModel);
+ }
+
+ @Test
+ public void execute_filterByDate_success() {
+ LocalDate testDate = BALI.getDate().date;
+ Predicate predicate = trip -> trip.getDate().date.equals(testDate);
+
+ expectedModel.updateFilteredTripList(predicate);
+ String expectedOutput = "Listed trips on " + testDate.format(TripDate.DATE_FORMATTER);
+ assertCommandSuccess(new ListTripCommand(testDate), model,
+ expectedOutput, expectedModel);
+ }
+
+ @Test
+ public void execute_filterByNonExistentDate_showsNoTripsMessage() {
+ LocalDate nonExistentDate = LocalDate.of(2070, 8, 12);
+ Predicate predicate = trip -> trip.getDate().date.equals(nonExistentDate);
+ expectedModel.updateFilteredTripList(predicate);
+
+ String expectedOutput = "No trips found. Use the addTrip command to create a new trip.";
+ assertCommandSuccess(new ListTripCommand(nonExistentDate), model,
+ expectedOutput, expectedModel);
+ }
+
+ @Test
+ public void execute_filterByFutureDateWithTrip_successfullyListsTrip() {
+ Trip t = new TripBuilder().withDate("12/8/2070").build();
+ expectedModel.addTrip(t);
+
+ LocalDate nonExistentDate = LocalDate.of(2070, 8, 12);
+ Predicate predicate = trip -> trip.getDate().date.equals(nonExistentDate);
+ expectedModel.updateFilteredTripList(predicate);
+
+ String expectedOutput = "Listed trips on 12/8/2070";
+ assertCommandSuccess(new ListTripCommand(nonExistentDate), expectedModel,
+ expectedOutput, expectedModel);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddContactCommandParserTest.java
similarity index 83%
rename from src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
rename to src/test/java/seedu/address/logic/parser/AddContactCommandParserTest.java
index 5bc11d3cdaa..5c244bcf058 100644
--- a/src/test/java/seedu/address/logic/parser/AddCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddContactCommandParserTest.java
@@ -16,14 +16,14 @@
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY;
import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE;
-import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
-import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_CUSTOMER;
+import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_SERVICE;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_CUSTOMER;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_SERVICE;
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;
@@ -36,39 +36,39 @@
import org.junit.jupiter.api.Test;
import seedu.address.logic.Messages;
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
+import seedu.address.logic.commands.AddContactCommand;
+import seedu.address.model.contact.Address;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Name;
+import seedu.address.model.contact.Phone;
import seedu.address.model.tag.Tag;
import seedu.address.testutil.PersonBuilder;
-public class AddCommandParserTest {
- private AddCommandParser parser = new AddCommandParser();
+public class AddContactCommandParserTest {
+ private AddContactCommandParser parser = new AddContactCommandParser();
@Test
public void parse_allFieldsPresent_success() {
- Person expectedPerson = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND).build();
+ Contact expectedContact = new PersonBuilder(BOB).withTags(VALID_TAG_SERVICE).build();
// whitespace only preamble
assertParseSuccess(parser, PREAMBLE_WHITESPACE + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND, new AddCommand(expectedPerson));
+ + ADDRESS_DESC_BOB + TAG_DESC_SERVICE, new AddContactCommand(expectedContact));
// multiple tags - all accepted
- Person expectedPersonMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_FRIEND, VALID_TAG_HUSBAND)
+ Contact expectedContactMultipleTags = new PersonBuilder(BOB).withTags(VALID_TAG_SERVICE, VALID_TAG_CUSTOMER)
.build();
assertParseSuccess(parser,
- NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
- new AddCommand(expectedPersonMultipleTags));
+ NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB + TAG_DESC_CUSTOMER
+ + TAG_DESC_SERVICE, new AddContactCommand(expectedContactMultipleTags));
}
@Test
public void parse_repeatedNonTagValue_failure() {
String validExpectedPersonString = NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_FRIEND;
+ + ADDRESS_DESC_BOB + TAG_DESC_SERVICE;
// multiple names
assertParseFailure(parser, NAME_DESC_AMY + validExpectedPersonString,
@@ -132,14 +132,14 @@ public void parse_repeatedNonTagValue_failure() {
@Test
public void parse_optionalFieldsMissing_success() {
// zero tags
- Person expectedPerson = new PersonBuilder(AMY).withTags().build();
+ Contact expectedContact = new PersonBuilder(AMY).withTags().build();
assertParseSuccess(parser, NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + ADDRESS_DESC_AMY,
- new AddCommand(expectedPerson));
+ new AddContactCommand(expectedContact));
}
@Test
public void parse_compulsoryFieldMissing_failure() {
- String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE);
+ String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddContactCommand.MESSAGE_USAGE);
// missing name prefix
assertParseFailure(parser, VALID_NAME_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB,
@@ -166,23 +166,23 @@ public void parse_compulsoryFieldMissing_failure() {
public void parse_invalidValue_failure() {
// invalid name
assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Name.MESSAGE_CONSTRAINTS);
+ + TAG_DESC_CUSTOMER + TAG_DESC_SERVICE, Name.MESSAGE_CONSTRAINTS);
// invalid phone
assertParseFailure(parser, NAME_DESC_BOB + INVALID_PHONE_DESC + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Phone.MESSAGE_CONSTRAINTS);
+ + TAG_DESC_CUSTOMER + TAG_DESC_SERVICE, Phone.MESSAGE_CONSTRAINTS);
// invalid email
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + INVALID_EMAIL_DESC + ADDRESS_DESC_BOB
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Email.MESSAGE_CONSTRAINTS);
+ + TAG_DESC_CUSTOMER + TAG_DESC_SERVICE, Email.MESSAGE_CONSTRAINTS);
// invalid address
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC
- + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, Address.MESSAGE_CONSTRAINTS);
+ + TAG_DESC_CUSTOMER + TAG_DESC_SERVICE, Address.MESSAGE_CONSTRAINTS);
// invalid tag
assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + ADDRESS_DESC_BOB
- + INVALID_TAG_DESC + VALID_TAG_FRIEND, Tag.MESSAGE_CONSTRAINTS);
+ + INVALID_TAG_DESC + VALID_TAG_SERVICE, Tag.MESSAGE_CONSTRAINTS);
// two invalid values, only first invalid value reported
assertParseFailure(parser, INVALID_NAME_DESC + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_ADDRESS_DESC,
@@ -190,7 +190,7 @@ public void parse_invalidValue_failure() {
// non-empty preamble
assertParseFailure(parser, PREAMBLE_NON_EMPTY + NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB
- + ADDRESS_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND,
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE));
+ + ADDRESS_DESC_BOB + TAG_DESC_CUSTOMER + TAG_DESC_SERVICE,
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddContactCommand.MESSAGE_USAGE));
}
}
diff --git a/src/test/java/seedu/address/logic/parser/AddTripCommandParserTest.java b/src/test/java/seedu/address/logic/parser/AddTripCommandParserTest.java
new file mode 100644
index 00000000000..519db23f8cb
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/AddTripCommandParserTest.java
@@ -0,0 +1,298 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.commands.CommandTestUtil.ACCOMMODATION_DESC_HOTEL_81;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_ACCOMMODATION_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_ITINERARY_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_TRIP_DATE_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_TRIP_NAME_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.ITINERARY_DESC_EAT_BAGUETTES;
+import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_NON_EMPTY;
+import static seedu.address.logic.commands.CommandTestUtil.PREAMBLE_WHITESPACE;
+import static seedu.address.logic.commands.CommandTestUtil.TRIP_CUSTOMER_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.TRIP_CUSTOMER_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.TRIP_DATE_DESC_2025;
+import static seedu.address.logic.commands.CommandTestUtil.TRIP_NAME_DESC_PARIS_2025;
+import static seedu.address.logic.commands.CommandTestUtil.TRIP_NOTE_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ACCOMMODATION_HOTEL_81;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ITINERARY_EAT_BAGUETTES;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NOTE;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TRIP_DATE_2025;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TRIP_NAME_PARIS_2025;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ACCOMMODATION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ITINERARY;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.AddTripCommand;
+import seedu.address.model.trip.Accommodation;
+import seedu.address.model.trip.Itinerary;
+import seedu.address.model.trip.Trip;
+import seedu.address.model.trip.TripDate;
+import seedu.address.model.trip.TripName;
+import seedu.address.testutil.TripBuilder;
+
+public class AddTripCommandParserTest {
+ private AddTripCommandParser parser = new AddTripCommandParser();
+
+ @Test
+ public void parse_allFieldsPresent_success() {
+ Trip expectedTrip = new TripBuilder()
+ .withName(VALID_TRIP_NAME_PARIS_2025)
+ .withAccommodation(VALID_ACCOMMODATION_HOTEL_81)
+ .withItinerary(VALID_ITINERARY_EAT_BAGUETTES)
+ .withDate(VALID_TRIP_DATE_2025)
+ .withCustomerNames(VALID_NAME_AMY, VALID_NAME_BOB)
+ .withNote(VALID_NOTE)
+ .build();
+
+ // whitespace only preamble
+ assertParseSuccess(parser, PREAMBLE_WHITESPACE
+ + TRIP_NAME_DESC_PARIS_2025
+ + ACCOMMODATION_DESC_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + TRIP_DATE_DESC_2025
+ + TRIP_CUSTOMER_DESC_AMY
+ + TRIP_CUSTOMER_DESC_BOB
+ + TRIP_NOTE_DESC,
+ new AddTripCommand(expectedTrip));
+ }
+
+ @Test
+ public void parse_allFieldsPresentWithoutNote_success() {
+ Trip expectedTrip = new TripBuilder()
+ .withName(VALID_TRIP_NAME_PARIS_2025)
+ .withAccommodation(VALID_ACCOMMODATION_HOTEL_81)
+ .withItinerary(VALID_ITINERARY_EAT_BAGUETTES)
+ .withDate(VALID_TRIP_DATE_2025)
+ .withCustomerNames(VALID_NAME_AMY, VALID_NAME_BOB)
+ .withNote("")
+ .build();
+
+ // whitespace only preamble
+ assertParseSuccess(parser, PREAMBLE_WHITESPACE
+ + TRIP_NAME_DESC_PARIS_2025
+ + ACCOMMODATION_DESC_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + TRIP_DATE_DESC_2025
+ + TRIP_CUSTOMER_DESC_AMY
+ + TRIP_CUSTOMER_DESC_BOB,
+ new AddTripCommand(expectedTrip));
+ }
+
+ @Test
+ public void parse_allFieldsPresentWithoutCustomerNames_success() {
+ Trip expectedTrip = new TripBuilder()
+ .withName(VALID_TRIP_NAME_PARIS_2025)
+ .withAccommodation(VALID_ACCOMMODATION_HOTEL_81)
+ .withItinerary(VALID_ITINERARY_EAT_BAGUETTES)
+ .withDate(VALID_TRIP_DATE_2025)
+ .withCustomerNames() // Empty customer names
+ .withNote(VALID_NOTE)
+ .build();
+
+ // whitespace only preamble
+ assertParseSuccess(parser, PREAMBLE_WHITESPACE
+ + TRIP_NAME_DESC_PARIS_2025
+ + ACCOMMODATION_DESC_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + TRIP_DATE_DESC_2025
+ + TRIP_NOTE_DESC,
+ new AddTripCommand(expectedTrip));
+ }
+
+ @Test
+ public void parse_minimumFieldsPresent_success() {
+ Trip expectedTrip = new TripBuilder()
+ .withName(VALID_TRIP_NAME_PARIS_2025)
+ .withAccommodation(VALID_ACCOMMODATION_HOTEL_81)
+ .withItinerary(VALID_ITINERARY_EAT_BAGUETTES)
+ .withDate(VALID_TRIP_DATE_2025)
+ .withCustomerNames() // Empty customer names
+ .withNote("") // Empty note
+ .build();
+
+ // whitespace only preamble, only required fields
+ assertParseSuccess(parser, PREAMBLE_WHITESPACE
+ + TRIP_NAME_DESC_PARIS_2025
+ + ACCOMMODATION_DESC_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + TRIP_DATE_DESC_2025,
+ new AddTripCommand(expectedTrip));
+ }
+
+ @Test
+ public void parse_repeatedNonCustomerName_failure() {
+ String validExpectedTripString = TRIP_NAME_DESC_PARIS_2025
+ + ACCOMMODATION_DESC_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + TRIP_DATE_DESC_2025
+ + TRIP_CUSTOMER_DESC_AMY
+ + TRIP_CUSTOMER_DESC_BOB
+ + TRIP_NOTE_DESC;
+
+ // multiple names
+ assertParseFailure(parser, TRIP_NAME_DESC_PARIS_2025 + validExpectedTripString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME));
+
+ // multiple accommodations
+ assertParseFailure(parser, ACCOMMODATION_DESC_HOTEL_81 + validExpectedTripString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ACCOMMODATION));
+
+ // multiple itineraries
+ assertParseFailure(parser, ITINERARY_DESC_EAT_BAGUETTES + validExpectedTripString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ITINERARY));
+
+ // multiple dates
+ assertParseFailure(parser, TRIP_DATE_DESC_2025 + validExpectedTripString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_DATE));
+
+ // multiple notes
+ assertParseFailure(parser, TRIP_NOTE_DESC + validExpectedTripString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NOTE));
+
+ // multiple fields repeated
+ assertParseFailure(parser,
+ validExpectedTripString + TRIP_NAME_DESC_PARIS_2025 + ACCOMMODATION_DESC_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES + TRIP_DATE_DESC_2025 + TRIP_NOTE_DESC
+ + validExpectedTripString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME, PREFIX_ACCOMMODATION,
+ PREFIX_ITINERARY, PREFIX_DATE, PREFIX_NOTE));
+
+ // invalid value followed by valid value
+
+ // invalid name
+ assertParseFailure(parser, INVALID_TRIP_NAME_DESC + validExpectedTripString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME));
+
+ // invalid accommodation
+ assertParseFailure(parser, INVALID_ACCOMMODATION_DESC + validExpectedTripString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ACCOMMODATION));
+
+ // invalid itinerary
+ assertParseFailure(parser, INVALID_ITINERARY_DESC + validExpectedTripString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ITINERARY));
+
+ // invalid date
+ assertParseFailure(parser, INVALID_TRIP_DATE_DESC + validExpectedTripString,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_DATE));
+
+ // invalid name
+ assertParseFailure(parser, validExpectedTripString + INVALID_TRIP_NAME_DESC,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_NAME));
+
+ // invalid accommodation
+ assertParseFailure(parser, validExpectedTripString + INVALID_ACCOMMODATION_DESC,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ACCOMMODATION));
+
+ // invalid itinerary
+ assertParseFailure(parser, validExpectedTripString + INVALID_ITINERARY_DESC,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ITINERARY));
+
+ // invalid date
+ assertParseFailure(parser, validExpectedTripString + INVALID_TRIP_DATE_DESC,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_DATE));
+
+ }
+
+ @Test
+ public void parse_compulsoryFieldMissing_failure() {
+ String expectedMessage = String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, AddTripCommand.MESSAGE_USAGE);
+
+ // missing name prefix
+ assertParseFailure(parser, VALID_TRIP_NAME_PARIS_2025
+ + ACCOMMODATION_DESC_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + TRIP_DATE_DESC_2025,
+ expectedMessage);
+
+ // missing accommodation prefix
+ assertParseFailure(parser, TRIP_NAME_DESC_PARIS_2025
+ + VALID_ACCOMMODATION_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + TRIP_DATE_DESC_2025,
+ expectedMessage);
+
+ // missing itinerary prefix
+ assertParseFailure(parser, TRIP_NAME_DESC_PARIS_2025
+ + ACCOMMODATION_DESC_HOTEL_81
+ + VALID_ITINERARY_EAT_BAGUETTES
+ + TRIP_DATE_DESC_2025,
+ expectedMessage);
+
+ // missing date prefix
+ assertParseFailure(parser, TRIP_NAME_DESC_PARIS_2025
+ + ACCOMMODATION_DESC_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + VALID_TRIP_DATE_2025,
+ expectedMessage);
+
+ // all prefixes missing
+ assertParseFailure(parser, VALID_TRIP_NAME_PARIS_2025
+ + VALID_ACCOMMODATION_HOTEL_81
+ + VALID_ITINERARY_EAT_BAGUETTES
+ + VALID_TRIP_DATE_2025,
+ expectedMessage);
+ }
+
+ @Test
+ public void parse_invalidValue_failure() {
+ // invalid trip name
+ assertParseFailure(parser, INVALID_TRIP_NAME_DESC
+ + ACCOMMODATION_DESC_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + TRIP_DATE_DESC_2025
+ + TRIP_NOTE_DESC,
+ TripName.MESSAGE_CONSTRAINTS);
+
+ // invalid accommodation
+ assertParseFailure(parser, TRIP_NAME_DESC_PARIS_2025
+ + INVALID_ACCOMMODATION_DESC
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + TRIP_DATE_DESC_2025
+ + TRIP_NOTE_DESC,
+ Accommodation.MESSAGE_CONSTRAINTS);
+
+ // invalid itinerary
+ assertParseFailure(parser, TRIP_NAME_DESC_PARIS_2025
+ + ACCOMMODATION_DESC_HOTEL_81
+ + INVALID_ITINERARY_DESC
+ + TRIP_DATE_DESC_2025
+ + TRIP_NOTE_DESC,
+ Itinerary.MESSAGE_CONSTRAINTS);
+
+ // invalid date
+ assertParseFailure(parser, TRIP_NAME_DESC_PARIS_2025
+ + ACCOMMODATION_DESC_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + INVALID_TRIP_DATE_DESC
+ + TRIP_NOTE_DESC,
+ TripDate.MESSAGE_CONSTRAINTS);
+
+ // two invalid values, only first invalid value reported
+ assertParseFailure(parser, INVALID_TRIP_NAME_DESC
+ + ACCOMMODATION_DESC_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + INVALID_TRIP_DATE_DESC
+ + TRIP_NOTE_DESC,
+ TripName.MESSAGE_CONSTRAINTS);
+
+ // non-empty preamble
+ assertParseFailure(parser, PREAMBLE_NON_EMPTY
+ + TRIP_NAME_DESC_PARIS_2025
+ + ACCOMMODATION_DESC_HOTEL_81
+ + ITINERARY_DESC_EAT_BAGUETTES
+ + TRIP_DATE_DESC_2025
+ + TRIP_CUSTOMER_DESC_AMY
+ + TRIP_CUSTOMER_DESC_BOB
+ + TRIP_NOTE_DESC,
+ String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, AddTripCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
index 5a1ab3dbc0c..12b3d487a19 100644
--- a/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java
@@ -13,18 +13,17 @@
import org.junit.jupiter.api.Test;
-import seedu.address.logic.commands.AddCommand;
+import seedu.address.logic.commands.AddContactCommand;
import seedu.address.logic.commands.ClearCommand;
-import seedu.address.logic.commands.DeleteCommand;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
+import seedu.address.logic.commands.DeleteContactCommand;
+import seedu.address.logic.commands.EditContactCommand;
+import seedu.address.logic.commands.EditContactCommand.EditPersonDescriptor;
import seedu.address.logic.commands.ExitCommand;
import seedu.address.logic.commands.FindCommand;
import seedu.address.logic.commands.HelpCommand;
-import seedu.address.logic.commands.ListCommand;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.NameContainsKeywordsPredicate;
import seedu.address.testutil.EditPersonDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;
import seedu.address.testutil.PersonUtil;
@@ -35,9 +34,9 @@ public class AddressBookParserTest {
@Test
public void parseCommand_add() throws Exception {
- Person person = new PersonBuilder().build();
- AddCommand command = (AddCommand) parser.parseCommand(PersonUtil.getAddCommand(person));
- assertEquals(new AddCommand(person), command);
+ Contact contact = new PersonBuilder().build();
+ AddContactCommand command = (AddContactCommand) parser.parseCommand(PersonUtil.getAddCommand(contact));
+ assertEquals(new AddContactCommand(contact), command);
}
@Test
@@ -48,18 +47,18 @@ public void parseCommand_clear() throws Exception {
@Test
public void parseCommand_delete() throws Exception {
- DeleteCommand command = (DeleteCommand) parser.parseCommand(
- DeleteCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased());
- assertEquals(new DeleteCommand(INDEX_FIRST_PERSON), command);
+ DeleteContactCommand command = (DeleteContactCommand) parser.parseCommand(
+ DeleteContactCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased());
+ assertEquals(new DeleteContactCommand(INDEX_FIRST_PERSON), command);
}
@Test
public void parseCommand_edit() throws Exception {
- Person person = new PersonBuilder().build();
- EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(person).build();
- EditCommand command = (EditCommand) parser.parseCommand(EditCommand.COMMAND_WORD + " "
+ Contact contact = new PersonBuilder().build();
+ EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(contact).build();
+ EditContactCommand command = (EditContactCommand) parser.parseCommand(EditContactCommand.COMMAND_WORD + " "
+ INDEX_FIRST_PERSON.getOneBased() + " " + PersonUtil.getEditPersonDescriptorDetails(descriptor));
- assertEquals(new EditCommand(INDEX_FIRST_PERSON, descriptor), command);
+ assertEquals(new EditContactCommand(INDEX_FIRST_PERSON, descriptor), command);
}
@Test
@@ -82,12 +81,6 @@ public void parseCommand_help() throws Exception {
assertTrue(parser.parseCommand(HelpCommand.COMMAND_WORD + " 3") instanceof HelpCommand);
}
- @Test
- public void parseCommand_list() throws Exception {
- assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD) instanceof ListCommand);
- assertTrue(parser.parseCommand(ListCommand.COMMAND_WORD + " 3") instanceof ListCommand);
- }
-
@Test
public void parseCommand_unrecognisedInput_throwsParseException() {
assertThrows(ParseException.class, String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE), ()
diff --git a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteContactCommandParserTest.java
similarity index 52%
rename from src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
rename to src/test/java/seedu/address/logic/parser/DeleteContactCommandParserTest.java
index 6a40e14a649..10ed595386f 100644
--- a/src/test/java/seedu/address/logic/parser/DeleteCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/DeleteContactCommandParserTest.java
@@ -7,26 +7,28 @@
import org.junit.jupiter.api.Test;
-import seedu.address.logic.commands.DeleteCommand;
+import seedu.address.logic.commands.DeleteContactCommand;
+
/**
* As we are only doing white-box testing, our test cases do not cover path variations
- * outside of the DeleteCommand code. For example, inputs "1" and "1 abc" take the
- * same path through the DeleteCommand, and therefore we test only one of them.
+ * outside the DeleteContactCommand code. For example, inputs "1" and "1 abc" take the
+ * same path through the DeleteContactCommand, and therefore we test only one of them.
* The path variation for those two cases occur inside the ParserUtil, and
* therefore should be covered by the ParserUtilTest.
*/
-public class DeleteCommandParserTest {
+public class DeleteContactCommandParserTest {
- private DeleteCommandParser parser = new DeleteCommandParser();
+ private DeleteContactCommandParser parser = new DeleteContactCommandParser();
@Test
public void parse_validArgs_returnsDeleteCommand() {
- assertParseSuccess(parser, "1", new DeleteCommand(INDEX_FIRST_PERSON));
+ assertParseSuccess(parser, "1", new DeleteContactCommand(INDEX_FIRST_PERSON));
}
@Test
- public void parse_invalidArgs_throwsParseException() {
- assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE));
+ public void parse_invalidFormat_throwsParseException() {
+ assertParseFailure(parser, "a",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteContactCommand.MESSAGE_USAGE));
}
}
diff --git a/src/test/java/seedu/address/logic/parser/DeleteTripCommandParserTest.java b/src/test/java/seedu/address/logic/parser/DeleteTripCommandParserTest.java
new file mode 100644
index 00000000000..8d4576d9bc2
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/DeleteTripCommandParserTest.java
@@ -0,0 +1,36 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_TRIP_DISPLAYED_INDEX;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TRIP;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.DeleteTripCommand;
+
+/**
+ * Unit tests for {@code DeleteTripCommandParser}.
+ */
+public class DeleteTripCommandParserTest {
+ private final DeleteTripCommandParser parser = new DeleteTripCommandParser();
+
+ @Test
+ public void parse_validArgs_returnsDeleteTripCommand() {
+ assertParseSuccess(parser, "1", new DeleteTripCommand(INDEX_FIRST_TRIP));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ // Non-integer input
+ assertParseFailure(parser, "a",
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTripCommand.MESSAGE_USAGE));
+
+ // Zero or negative index
+ assertParseFailure(parser, "0",
+ String.format(MESSAGE_INVALID_TRIP_DISPLAYED_INDEX, DeleteTripCommand.MESSAGE_USAGE));
+ assertParseFailure(parser, "-5",
+ String.format(MESSAGE_INVALID_TRIP_DISPLAYED_INDEX, DeleteTripCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditContactCommandParserTest.java
similarity index 76%
rename from src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
rename to src/test/java/seedu/address/logic/parser/EditContactCommandParserTest.java
index cc7175172d4..a910bd4af22 100644
--- a/src/test/java/seedu/address/logic/parser/EditCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/EditContactCommandParserTest.java
@@ -1,6 +1,7 @@
package seedu.address.logic.parser;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.ADDRESS_DESC_BOB;
import static seedu.address.logic.commands.CommandTestUtil.EMAIL_DESC_AMY;
@@ -13,17 +14,18 @@
import static seedu.address.logic.commands.CommandTestUtil.NAME_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_AMY;
import static seedu.address.logic.commands.CommandTestUtil.PHONE_DESC_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_FRIEND;
-import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_CUSTOMER;
+import static seedu.address.logic.commands.CommandTestUtil.TAG_DESC_SERVICE;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_CUSTOMER;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_SERVICE;
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_NOTE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
@@ -36,23 +38,24 @@
import seedu.address.commons.core.index.Index;
import seedu.address.logic.Messages;
-import seedu.address.logic.commands.EditCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Phone;
+import seedu.address.logic.commands.EditContactCommand;
+import seedu.address.logic.commands.EditContactCommand.EditPersonDescriptor;
+import seedu.address.model.contact.Address;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Name;
+import seedu.address.model.contact.Note;
+import seedu.address.model.contact.Phone;
import seedu.address.model.tag.Tag;
import seedu.address.testutil.EditPersonDescriptorBuilder;
-public class EditCommandParserTest {
+public class EditContactCommandParserTest {
private static final String TAG_EMPTY = " " + PREFIX_TAG;
private static final String MESSAGE_INVALID_FORMAT =
- String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE);
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditContactCommand.MESSAGE_USAGE);
- private EditCommandParser parser = new EditCommandParser();
+ private EditContactCommandParser parser = new EditContactCommandParser();
@Test
public void parse_missingParts_failure() {
@@ -60,7 +63,7 @@ public void parse_missingParts_failure() {
assertParseFailure(parser, VALID_NAME_AMY, MESSAGE_INVALID_FORMAT);
// no field specified
- assertParseFailure(parser, "1", EditCommand.MESSAGE_NOT_EDITED);
+ assertParseFailure(parser, "1", EditContactCommand.MESSAGE_NOT_EDITED);
// no index and no field specified
assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT);
@@ -69,10 +72,10 @@ public void parse_missingParts_failure() {
@Test
public void parse_invalidPreamble_failure() {
// negative index
- assertParseFailure(parser, "-5" + NAME_DESC_AMY, MESSAGE_INVALID_FORMAT);
+ assertParseFailure(parser, "-5" + NAME_DESC_AMY, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
// zero index
- assertParseFailure(parser, "0" + NAME_DESC_AMY, MESSAGE_INVALID_FORMAT);
+ assertParseFailure(parser, "0" + NAME_DESC_AMY, MESSAGE_INVALID_PERSON_DISPLAYED_INDEX);
// invalid arguments being parsed as preamble
assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT);
@@ -92,11 +95,11 @@ public void parse_invalidValue_failure() {
// invalid phone followed by valid email
assertParseFailure(parser, "1" + INVALID_PHONE_DESC + EMAIL_DESC_AMY, Phone.MESSAGE_CONSTRAINTS);
- // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Person} being edited,
+ // while parsing {@code PREFIX_TAG} alone will reset the tags of the {@code Contact} being edited,
// parsing it together with a valid tag results in error
- assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_DESC_HUSBAND + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS);
- assertParseFailure(parser, "1" + TAG_DESC_FRIEND + TAG_EMPTY + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
- assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_FRIEND + TAG_DESC_HUSBAND, Tag.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, "1" + TAG_DESC_SERVICE + TAG_DESC_CUSTOMER + TAG_EMPTY, Tag.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, "1" + TAG_DESC_SERVICE + TAG_EMPTY + TAG_DESC_CUSTOMER, Tag.MESSAGE_CONSTRAINTS);
+ assertParseFailure(parser, "1" + TAG_EMPTY + TAG_DESC_SERVICE + TAG_DESC_CUSTOMER, Tag.MESSAGE_CONSTRAINTS);
// multiple invalid values, but only the first invalid value is captured
assertParseFailure(parser, "1" + INVALID_NAME_DESC + INVALID_EMAIL_DESC + VALID_ADDRESS_AMY + VALID_PHONE_AMY,
@@ -106,13 +109,13 @@ public void parse_invalidValue_failure() {
@Test
public void parse_allFieldsSpecified_success() {
Index targetIndex = INDEX_SECOND_PERSON;
- String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_HUSBAND
- + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_FRIEND;
+ String userInput = targetIndex.getOneBased() + PHONE_DESC_BOB + TAG_DESC_CUSTOMER
+ + EMAIL_DESC_AMY + ADDRESS_DESC_AMY + NAME_DESC_AMY + TAG_DESC_SERVICE;
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY)
.withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY)
- .withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND).build();
- EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
+ .withTags(VALID_TAG_CUSTOMER, VALID_TAG_SERVICE).build();
+ EditContactCommand expectedCommand = new EditContactCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
}
@@ -124,7 +127,7 @@ public void parse_someFieldsSpecified_success() {
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_BOB)
.withEmail(VALID_EMAIL_AMY).build();
- EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
+ EditContactCommand expectedCommand = new EditContactCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
}
@@ -135,38 +138,44 @@ public void parse_oneFieldSpecified_success() {
Index targetIndex = INDEX_THIRD_PERSON;
String userInput = targetIndex.getOneBased() + NAME_DESC_AMY;
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withName(VALID_NAME_AMY).build();
- EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
+ EditContactCommand expectedCommand = new EditContactCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
// phone
userInput = targetIndex.getOneBased() + PHONE_DESC_AMY;
descriptor = new EditPersonDescriptorBuilder().withPhone(VALID_PHONE_AMY).build();
- expectedCommand = new EditCommand(targetIndex, descriptor);
+ expectedCommand = new EditContactCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
// email
userInput = targetIndex.getOneBased() + EMAIL_DESC_AMY;
descriptor = new EditPersonDescriptorBuilder().withEmail(VALID_EMAIL_AMY).build();
- expectedCommand = new EditCommand(targetIndex, descriptor);
+ expectedCommand = new EditContactCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
// address
userInput = targetIndex.getOneBased() + ADDRESS_DESC_AMY;
descriptor = new EditPersonDescriptorBuilder().withAddress(VALID_ADDRESS_AMY).build();
- expectedCommand = new EditCommand(targetIndex, descriptor);
+ expectedCommand = new EditContactCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
// tags
- userInput = targetIndex.getOneBased() + TAG_DESC_FRIEND;
- descriptor = new EditPersonDescriptorBuilder().withTags(VALID_TAG_FRIEND).build();
- expectedCommand = new EditCommand(targetIndex, descriptor);
+ userInput = targetIndex.getOneBased() + TAG_DESC_SERVICE;
+ descriptor = new EditPersonDescriptorBuilder().withTags(VALID_TAG_SERVICE).build();
+ expectedCommand = new EditContactCommand(targetIndex, descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // note
+ userInput = targetIndex.getOneBased() + " " + PREFIX_NOTE + "New note";
+ descriptor = new EditPersonDescriptorBuilder().withNote(new Note("New note")).build();
+ expectedCommand = new EditContactCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
}
@Test
public void parse_multipleRepeatedFields_failure() {
// More extensive testing of duplicate parameter detections is done in
- // AddCommandParserTest#parse_repeatedNonTagValue_failure()
+ // AddContactCommandParserTest#parse_repeatedNonTagValue_failure()
// valid followed by invalid
Index targetIndex = INDEX_FIRST_PERSON;
@@ -181,8 +190,8 @@ public void parse_multipleRepeatedFields_failure() {
// mulltiple valid fields repeated
userInput = targetIndex.getOneBased() + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY
- + TAG_DESC_FRIEND + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_FRIEND
- + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_HUSBAND;
+ + TAG_DESC_SERVICE + PHONE_DESC_AMY + ADDRESS_DESC_AMY + EMAIL_DESC_AMY + TAG_DESC_SERVICE
+ + PHONE_DESC_BOB + ADDRESS_DESC_BOB + EMAIL_DESC_BOB + TAG_DESC_CUSTOMER;
assertParseFailure(parser, userInput,
Messages.getErrorMessageForDuplicatePrefixes(PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS));
@@ -201,7 +210,7 @@ public void parse_resetTags_success() {
String userInput = targetIndex.getOneBased() + TAG_EMPTY;
EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder().withTags().build();
- EditCommand expectedCommand = new EditCommand(targetIndex, descriptor);
+ EditContactCommand expectedCommand = new EditContactCommand(targetIndex, descriptor);
assertParseSuccess(parser, userInput, expectedCommand);
}
diff --git a/src/test/java/seedu/address/logic/parser/EditTripCommandParserTest.java b/src/test/java/seedu/address/logic/parser/EditTripCommandParserTest.java
new file mode 100644
index 00000000000..19176302db0
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/EditTripCommandParserTest.java
@@ -0,0 +1,205 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.Messages.MESSAGE_INVALID_TRIP_DISPLAYED_INDEX;
+import static seedu.address.logic.commands.CommandTestUtil.ACCOMMODATION_DESC_HOTEL_81;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_ACCOMMODATION_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_ITINERARY_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_TRIP_DATE_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.INVALID_TRIP_NAME_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.ITINERARY_DESC_EAT_BAGUETTES;
+import static seedu.address.logic.commands.CommandTestUtil.TRIP_CUSTOMER_DESC_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.TRIP_CUSTOMER_DESC_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.TRIP_DATE_DESC_2025;
+import static seedu.address.logic.commands.CommandTestUtil.TRIP_NAME_DESC_PARIS_2025;
+import static seedu.address.logic.commands.CommandTestUtil.TRIP_NOTE_DESC;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ACCOMMODATION_HOTEL_81;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ITINERARY_EAT_BAGUETTES;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TRIP_DATE_2025;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TRIP_NAME_PARIS_2025;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ACCOMMODATION;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CUSTOMER_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_DATE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_ITINERARY;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NOTE;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_TRIP;
+import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_TRIP;
+import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_TRIP;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.core.index.Index;
+import seedu.address.logic.Messages;
+import seedu.address.logic.commands.EditTripCommand;
+import seedu.address.logic.commands.EditTripCommand.EditTripDescriptor;
+import seedu.address.model.trip.Accommodation;
+import seedu.address.model.trip.Itinerary;
+import seedu.address.model.trip.TripDate;
+import seedu.address.model.trip.TripName;
+import seedu.address.testutil.EditTripDescriptorBuilder;
+
+public class EditTripCommandParserTest {
+
+ private static final String CUSTOMER_NAME_EMPTY = " " + PREFIX_CUSTOMER_NAME;
+
+ private static final String MESSAGE_INVALID_FORMAT =
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditTripCommand.MESSAGE_USAGE);
+
+ private final EditTripCommandParser parser = new EditTripCommandParser();
+
+ @Test public void parse_missingParts_failure() {
+ // no index specified
+ assertParseFailure(parser, VALID_TRIP_NAME_PARIS_2025, MESSAGE_INVALID_FORMAT);
+
+ // no field specified
+ assertParseFailure(parser, "1", EditTripCommand.MESSAGE_NOT_EDITED);
+
+ // no index and no field specified
+ assertParseFailure(parser, "", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test public void parse_invalidPreamble_failure() {
+ // negative index
+ assertParseFailure(parser, "-5" + TRIP_NAME_DESC_PARIS_2025, MESSAGE_INVALID_TRIP_DISPLAYED_INDEX);
+
+ // zero index
+ assertParseFailure(parser, "0" + TRIP_NAME_DESC_PARIS_2025, MESSAGE_INVALID_TRIP_DISPLAYED_INDEX);
+
+ // invalid arguments being parsed as preamble
+ assertParseFailure(parser, "1 some random string", MESSAGE_INVALID_FORMAT);
+
+ // invalid prefix being parsed as preamble
+ assertParseFailure(parser, "1 z/ string", MESSAGE_INVALID_FORMAT);
+ }
+
+ @Test public void parse_invalidValue_failure() {
+ assertParseFailure(parser, "1" + INVALID_TRIP_NAME_DESC, TripName.MESSAGE_CONSTRAINTS); // invalid name
+ assertParseFailure(parser, "1" + INVALID_ACCOMMODATION_DESC, Accommodation.MESSAGE_CONSTRAINTS); // invalid
+ // accommodation
+ assertParseFailure(parser, "1" + INVALID_ITINERARY_DESC, Itinerary.MESSAGE_CONSTRAINTS); // invalid itinerary
+ assertParseFailure(parser, "1" + INVALID_TRIP_DATE_DESC, TripDate.MESSAGE_CONSTRAINTS); // invalid date
+
+ // invalid accommodation followed by valid itinerary
+ assertParseFailure(parser, "1" + INVALID_ACCOMMODATION_DESC + ITINERARY_DESC_EAT_BAGUETTES,
+ Accommodation.MESSAGE_CONSTRAINTS);
+
+ // multiple invalid values, but only the first invalid value is captured
+ assertParseFailure(parser, "1" + INVALID_TRIP_NAME_DESC + INVALID_ITINERARY_DESC + VALID_ACCOMMODATION_HOTEL_81
+ + VALID_TRIP_DATE_2025, TripName.MESSAGE_CONSTRAINTS);
+ }
+
+ @Test public void parse_allFieldsSpecified_success() {
+ Index targetIndex = INDEX_SECOND_TRIP;
+ String userInput = targetIndex.getOneBased() + ACCOMMODATION_DESC_HOTEL_81 + TRIP_CUSTOMER_DESC_AMY
+ + ITINERARY_DESC_EAT_BAGUETTES + TRIP_DATE_DESC_2025 + TRIP_NAME_DESC_PARIS_2025
+ + TRIP_CUSTOMER_DESC_BOB + TRIP_NOTE_DESC;
+
+ EditTripDescriptor descriptor = new EditTripDescriptorBuilder().withName(VALID_TRIP_NAME_PARIS_2025)
+ .withAccommodation(VALID_ACCOMMODATION_HOTEL_81).withItinerary(VALID_ITINERARY_EAT_BAGUETTES)
+ .withDate(VALID_TRIP_DATE_2025).withCustomerNames("Amy Bee", "Bob Choo")
+ .withNote("Customer prefers window seat").build();
+ EditTripCommand expectedCommand = new EditTripCommand(targetIndex, descriptor);
+
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test public void parse_someFieldsSpecified_success() {
+ Index targetIndex = INDEX_FIRST_TRIP;
+ String userInput = targetIndex.getOneBased() + ACCOMMODATION_DESC_HOTEL_81 + ITINERARY_DESC_EAT_BAGUETTES;
+
+ EditTripDescriptor descriptor = new EditTripDescriptorBuilder().withAccommodation(VALID_ACCOMMODATION_HOTEL_81)
+ .withItinerary(VALID_ITINERARY_EAT_BAGUETTES).build();
+ EditTripCommand expectedCommand = new EditTripCommand(targetIndex, descriptor);
+
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test public void parse_oneFieldSpecified_success() {
+ // name
+ Index targetIndex = INDEX_THIRD_TRIP;
+ String userInput = targetIndex.getOneBased() + TRIP_NAME_DESC_PARIS_2025;
+ EditTripDescriptor descriptor = new EditTripDescriptorBuilder().withName(VALID_TRIP_NAME_PARIS_2025).build();
+ EditTripCommand expectedCommand = new EditTripCommand(targetIndex, descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // accommodation
+ userInput = targetIndex.getOneBased() + ACCOMMODATION_DESC_HOTEL_81;
+ descriptor = new EditTripDescriptorBuilder().withAccommodation(VALID_ACCOMMODATION_HOTEL_81).build();
+ expectedCommand = new EditTripCommand(targetIndex, descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // itinerary
+ userInput = targetIndex.getOneBased() + ITINERARY_DESC_EAT_BAGUETTES;
+ descriptor = new EditTripDescriptorBuilder().withItinerary(VALID_ITINERARY_EAT_BAGUETTES).build();
+ expectedCommand = new EditTripCommand(targetIndex, descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // date
+ userInput = targetIndex.getOneBased() + TRIP_DATE_DESC_2025;
+ descriptor = new EditTripDescriptorBuilder().withDate(VALID_TRIP_DATE_2025).build();
+ expectedCommand = new EditTripCommand(targetIndex, descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // customer names
+ userInput = targetIndex.getOneBased() + TRIP_CUSTOMER_DESC_AMY;
+ descriptor = new EditTripDescriptorBuilder().withCustomerNames("Amy Bee").build();
+ expectedCommand = new EditTripCommand(targetIndex, descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+
+ // note
+ userInput = targetIndex.getOneBased() + TRIP_NOTE_DESC;
+ descriptor = new EditTripDescriptorBuilder().withNote("Customer prefers window seat").build();
+ expectedCommand = new EditTripCommand(targetIndex, descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test public void parse_multipleRepeatedFields_failure() {
+ // valid followed by invalid
+ Index targetIndex = INDEX_FIRST_TRIP;
+ String userInput = targetIndex.getOneBased() + INVALID_ACCOMMODATION_DESC + ACCOMMODATION_DESC_HOTEL_81;
+
+ assertParseFailure(parser, userInput, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ACCOMMODATION));
+
+ // invalid followed by valid
+ userInput = targetIndex.getOneBased() + ACCOMMODATION_DESC_HOTEL_81 + INVALID_ACCOMMODATION_DESC;
+
+ assertParseFailure(parser, userInput, Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ACCOMMODATION));
+
+ // multiple valid fields repeated
+ userInput = targetIndex.getOneBased() + ACCOMMODATION_DESC_HOTEL_81 + TRIP_DATE_DESC_2025
+ + ITINERARY_DESC_EAT_BAGUETTES + TRIP_NAME_DESC_PARIS_2025 + ACCOMMODATION_DESC_HOTEL_81
+ + TRIP_DATE_DESC_2025 + ITINERARY_DESC_EAT_BAGUETTES;
+
+ assertParseFailure(parser, userInput,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ACCOMMODATION, PREFIX_DATE, PREFIX_ITINERARY));
+
+ // multiple invalid values
+ userInput =
+ targetIndex.getOneBased() + INVALID_ACCOMMODATION_DESC + INVALID_TRIP_DATE_DESC + INVALID_ITINERARY_DESC
+ + INVALID_ACCOMMODATION_DESC + INVALID_TRIP_DATE_DESC + INVALID_ITINERARY_DESC;
+
+ assertParseFailure(parser, userInput,
+ Messages.getErrorMessageForDuplicatePrefixes(PREFIX_ACCOMMODATION, PREFIX_DATE, PREFIX_ITINERARY));
+ }
+
+ @Test public void parse_resetCustomerNames_success() {
+ Index targetIndex = INDEX_THIRD_TRIP;
+ String userInput = targetIndex.getOneBased() + CUSTOMER_NAME_EMPTY;
+
+ EditTripDescriptor descriptor = new EditTripDescriptorBuilder().withCustomerNames().build();
+ EditTripCommand expectedCommand = new EditTripCommand(targetIndex, descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+
+ @Test
+ public void parse_emptyNoteField_success() {
+ Index targetIndex = INDEX_THIRD_TRIP;
+ String userInput = targetIndex.getOneBased() + " " + PREFIX_NOTE;
+
+ EditTripDescriptor descriptor = new EditTripDescriptorBuilder().withNote("").build();
+ EditTripCommand expectedCommand = new EditTripCommand(targetIndex, descriptor);
+ assertParseSuccess(parser, userInput, expectedCommand);
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
index d92e64d12f9..bc30955ad5c 100644
--- a/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
+++ b/src/test/java/seedu/address/logic/parser/FindCommandParserTest.java
@@ -9,7 +9,7 @@
import org.junit.jupiter.api.Test;
import seedu.address.logic.commands.FindCommand;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.contact.NameContainsKeywordsPredicate;
public class FindCommandParserTest {
diff --git a/src/test/java/seedu/address/logic/parser/ListContactCommandParserTest.java b/src/test/java/seedu/address/logic/parser/ListContactCommandParserTest.java
new file mode 100644
index 00000000000..e50612ba146
--- /dev/null
+++ b/src/test/java/seedu/address/logic/parser/ListContactCommandParserTest.java
@@ -0,0 +1,41 @@
+package seedu.address.logic.parser;
+
+import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure;
+import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.logic.commands.ListContactCommand;
+
+/**
+ * As we are only doing white-box testing, our test cases do not cover path variations
+ * outside the DeleteContactCommand code. For example, inputs "1" and "1 abc" take the
+ * same path through the DeleteContactCommand, and therefore we test only one of them.
+ * The path variation for those two cases occur inside the ParserUtil, and
+ * therefore should be covered by the ParserUtilTest.
+ */
+public class ListContactCommandParserTest {
+
+ private static final String INVALID_TAGNAME = "friend";
+ private static final String VALID_TAGNAME_1 = "service";
+ private static final String VALID_TAGNAME_2 = "";
+ private ListContactCommandParser parser = new ListContactCommandParser();
+
+
+ @Test
+ public void parse_validTag_returnsListContactCommand() {
+ assertParseSuccess(parser, "service", new ListContactCommand(VALID_TAGNAME_1));
+ }
+
+ @Test
+ public void parse_validTag2_returnsListContactCommand() {
+ assertParseSuccess(parser, "", new ListContactCommand(VALID_TAGNAME_2));
+ }
+
+ @Test
+ public void parse_invalidArgs_throwsParseException() {
+ assertParseFailure(parser, INVALID_TAGNAME,
+ String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListContactCommand.MESSAGE_USAGE));
+ }
+}
diff --git a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
index 4256788b1a7..17e957af454 100644
--- a/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
+++ b/src/test/java/seedu/address/logic/parser/ParserUtilTest.java
@@ -2,9 +2,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static seedu.address.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX;
import static seedu.address.testutil.Assert.assertThrows;
-import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import java.util.Arrays;
import java.util.Collections;
@@ -13,11 +11,12 @@
import org.junit.jupiter.api.Test;
+import seedu.address.commons.core.index.Index;
import seedu.address.logic.parser.exceptions.ParseException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Phone;
+import seedu.address.model.contact.Address;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Name;
+import seedu.address.model.contact.Phone;
import seedu.address.model.tag.Tag;
public class ParserUtilTest {
@@ -31,29 +30,32 @@ public class ParserUtilTest {
private static final String VALID_PHONE = "123456";
private static final String VALID_ADDRESS = "123 Main Street #0505";
private static final String VALID_EMAIL = "rachel@example.com";
- private static final String VALID_TAG_1 = "friend";
- private static final String VALID_TAG_2 = "neighbour";
+ private static final String VALID_TAG_1 = "service";
+ private static final String VALID_TAG_2 = "customer";
private static final String WHITESPACE = " \t\r\n";
@Test
- public void parseIndex_invalidInput_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseIndex("10 a"));
+ public void parseTripIndex_validInput_success() throws Exception {
+ assertEquals(Index.fromOneBased(1), ParserUtil.parseTripIndex("1"));
+ assertEquals(Index.fromOneBased(3), ParserUtil.parseTripIndex(" 3 "));
}
@Test
- public void parseIndex_outOfRangeInput_throwsParseException() {
- assertThrows(ParseException.class, MESSAGE_INVALID_INDEX, ()
- -> ParserUtil.parseIndex(Long.toString(Integer.MAX_VALUE + 1)));
+ public void parseTripIndex_nonInteger_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseTripIndex("abc"));
+ assertThrows(ParseException.class, () -> ParserUtil.parseTripIndex("1x"));
}
+
@Test
- public void parseIndex_validInput_success() throws Exception {
- // No whitespaces
- assertEquals(INDEX_FIRST_PERSON, ParserUtil.parseIndex("1"));
+ public void parseContactIndex_validInput_success() throws Exception {
+ assertEquals(Index.fromOneBased(1), ParserUtil.parseContactIndex("1"));
+ }
- // Leading and trailing whitespaces
- assertEquals(INDEX_FIRST_PERSON, ParserUtil.parseIndex(" 1 "));
+ @Test
+ public void parseContactIndex_nonInteger_throwsParseException() {
+ assertThrows(ParseException.class, () -> ParserUtil.parseContactIndex("xyz"));
}
@Test
@@ -176,9 +178,29 @@ public void parseTags_null_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> ParserUtil.parseTags(null));
}
+ @Test
+ public void parseTagName_vaild_emptyTag() throws ParseException {
+ assertEquals("", ParserUtil.parseTagName(""));
+ }
+
+ @Test
+ public void parseTagName_vaild_tagName1() throws ParseException {
+ assertEquals("service", ParserUtil.parseTagName(VALID_TAG_1));
+ }
+
+ @Test
+ public void parseTagName_vaild_tagName2() throws ParseException {
+ assertEquals("customer", ParserUtil.parseTagName(VALID_TAG_2));
+ }
+
+ @Test
+ public void parseTagName_invaild_tagName() throws ParseException {
+ assertEquals("customer", ParserUtil.parseTagName(VALID_TAG_2));
+ }
+
@Test
public void parseTags_collectionWithInvalidTags_throwsParseException() {
- assertThrows(ParseException.class, () -> ParserUtil.parseTags(Arrays.asList(VALID_TAG_1, INVALID_TAG)));
+ assertThrows(ParseException.class, () -> ParserUtil.parseTagName(INVALID_TAG));
}
@Test
diff --git a/src/test/java/seedu/address/model/AddressBookTest.java b/src/test/java/seedu/address/model/AddressBookTest.java
index 68c8c5ba4d5..5a5451498e5 100644
--- a/src/test/java/seedu/address/model/AddressBookTest.java
+++ b/src/test/java/seedu/address/model/AddressBookTest.java
@@ -4,7 +4,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_CUSTOMER;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.getTypicalAddressBook;
@@ -18,8 +18,8 @@
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.exceptions.DuplicatePersonException;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.exceptions.DuplicatePersonException;
import seedu.address.testutil.PersonBuilder;
public class AddressBookTest {
@@ -45,37 +45,37 @@ public void resetData_withValidReadOnlyAddressBook_replacesData() {
@Test
public void resetData_withDuplicatePersons_throwsDuplicatePersonException() {
- // Two persons with the same identity fields
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
+ // Two contacts with the same identity fields
+ Contact editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_CUSTOMER)
.build();
- List newPersons = Arrays.asList(ALICE, editedAlice);
- AddressBookStub newData = new AddressBookStub(newPersons);
+ List newContacts = Arrays.asList(ALICE, editedAlice);
+ AddressBookStub newData = new AddressBookStub(newContacts);
assertThrows(DuplicatePersonException.class, () -> addressBook.resetData(newData));
}
@Test
public void hasPerson_nullPerson_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> addressBook.hasPerson(null));
+ assertThrows(NullPointerException.class, () -> addressBook.hasContact(null));
}
@Test
public void hasPerson_personNotInAddressBook_returnsFalse() {
- assertFalse(addressBook.hasPerson(ALICE));
+ assertFalse(addressBook.hasContact(ALICE));
}
@Test
public void hasPerson_personInAddressBook_returnsTrue() {
addressBook.addPerson(ALICE);
- assertTrue(addressBook.hasPerson(ALICE));
+ assertTrue(addressBook.hasContact(ALICE));
}
@Test
public void hasPerson_personWithSameIdentityFieldsInAddressBook_returnsTrue() {
addressBook.addPerson(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
+ Contact editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_CUSTOMER)
.build();
- assertTrue(addressBook.hasPerson(editedAlice));
+ assertTrue(addressBook.hasContact(editedAlice));
}
@Test
@@ -85,23 +85,23 @@ public void getPersonList_modifyList_throwsUnsupportedOperationException() {
@Test
public void toStringMethod() {
- String expected = AddressBook.class.getCanonicalName() + "{persons=" + addressBook.getPersonList() + "}";
+ String expected = AddressBook.class.getCanonicalName() + "{contacts=" + addressBook.getPersonList() + "}";
assertEquals(expected, addressBook.toString());
}
/**
- * A stub ReadOnlyAddressBook whose persons list can violate interface constraints.
+ * A stub ReadOnlyAddressBook whose contacts list can violate interface constraints.
*/
private static class AddressBookStub implements ReadOnlyAddressBook {
- private final ObservableList persons = FXCollections.observableArrayList();
+ private final ObservableList contacts = FXCollections.observableArrayList();
- AddressBookStub(Collection persons) {
- this.persons.setAll(persons);
+ AddressBookStub(Collection contacts) {
+ this.contacts.setAll(contacts);
}
@Override
- public ObservableList getPersonList() {
- return persons;
+ public ObservableList getPersonList() {
+ return contacts;
}
}
diff --git a/src/test/java/seedu/address/model/ModelManagerTest.java b/src/test/java/seedu/address/model/ModelManagerTest.java
index 2cf1418d116..a23b33cb1c2 100644
--- a/src/test/java/seedu/address/model/ModelManagerTest.java
+++ b/src/test/java/seedu/address/model/ModelManagerTest.java
@@ -7,6 +7,7 @@
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BENSON;
+import static seedu.address.testutil.TypicalTrips.PARIS;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -15,8 +16,9 @@
import org.junit.jupiter.api.Test;
import seedu.address.commons.core.GuiSettings;
-import seedu.address.model.person.NameContainsKeywordsPredicate;
+import seedu.address.model.contact.NameContainsKeywordsPredicate;
import seedu.address.testutil.AddressBookBuilder;
+import seedu.address.testutil.TripBookBuilder;
public class ModelManagerTest {
@@ -27,6 +29,7 @@ public void constructor() {
assertEquals(new UserPrefs(), modelManager.getUserPrefs());
assertEquals(new GuiSettings(), modelManager.getGuiSettings());
assertEquals(new AddressBook(), new AddressBook(modelManager.getAddressBook()));
+ assertEquals(new TripBook(), new TripBook(modelManager.getTripBook()));
}
@Test
@@ -38,6 +41,7 @@ public void setUserPrefs_nullUserPrefs_throwsNullPointerException() {
public void setUserPrefs_validUserPrefs_copiesUserPrefs() {
UserPrefs userPrefs = new UserPrefs();
userPrefs.setAddressBookFilePath(Paths.get("address/book/file/path"));
+ userPrefs.setTripBookFilePath(Paths.get("trip/book/file/path"));
userPrefs.setGuiSettings(new GuiSettings(1, 2, 3, 4));
modelManager.setUserPrefs(userPrefs);
assertEquals(userPrefs, modelManager.getUserPrefs());
@@ -72,20 +76,48 @@ public void setAddressBookFilePath_validPath_setsAddressBookFilePath() {
assertEquals(path, modelManager.getAddressBookFilePath());
}
+ @Test
+ public void setTripBookFilePath_nullPath_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> modelManager.setTripBookFilePath(null));
+ }
+
+ @Test
+ public void setTripBookFilePath_validPath_setsTripBookFilePath() {
+ Path path = Paths.get("trip/book/file/path");
+ modelManager.setTripBookFilePath(path);
+ assertEquals(path, modelManager.getTripBookFilePath());
+ }
+
@Test
public void hasPerson_nullPerson_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> modelManager.hasPerson(null));
+ assertThrows(NullPointerException.class, () -> modelManager.hasContact(null));
}
@Test
public void hasPerson_personNotInAddressBook_returnsFalse() {
- assertFalse(modelManager.hasPerson(ALICE));
+ assertFalse(modelManager.hasContact(ALICE));
}
@Test
public void hasPerson_personInAddressBook_returnsTrue() {
modelManager.addPerson(ALICE);
- assertTrue(modelManager.hasPerson(ALICE));
+ assertTrue(modelManager.hasContact(ALICE));
+ }
+
+ @Test
+ public void hasTrip_nullTrip_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> modelManager.hasTrip(null));
+ }
+
+ @Test
+ public void hasTrip_tripNotInTripBook_returnsFalse() {
+ assertFalse(modelManager.hasTrip(PARIS));
+ }
+
+ @Test
+ public void hasTrip_tripInTripBook_returnsTrue() {
+ modelManager.addTrip(PARIS);
+ assertTrue(modelManager.hasTrip(PARIS));
}
@Test
@@ -93,15 +125,22 @@ public void getFilteredPersonList_modifyList_throwsUnsupportedOperationException
assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredPersonList().remove(0));
}
+ @Test
+ public void getFilteredTripList_modifyList_throwsUnsupportedOperationException() {
+ assertThrows(UnsupportedOperationException.class, () -> modelManager.getFilteredTripList().remove(0));
+ }
+
@Test
public void equals() {
AddressBook addressBook = new AddressBookBuilder().withPerson(ALICE).withPerson(BENSON).build();
+ TripBook tripBook = new TripBookBuilder().withTrip(PARIS).build();
AddressBook differentAddressBook = new AddressBook();
+ TripBook differentTripBook = new TripBook();
UserPrefs userPrefs = new UserPrefs();
// same values -> returns true
- modelManager = new ModelManager(addressBook, userPrefs);
- ModelManager modelManagerCopy = new ModelManager(addressBook, userPrefs);
+ modelManager = new ModelManager(addressBook, tripBook, userPrefs);
+ ModelManager modelManagerCopy = new ModelManager(addressBook, tripBook, userPrefs);
assertTrue(modelManager.equals(modelManagerCopy));
// same object -> returns true
@@ -114,19 +153,17 @@ public void equals() {
assertFalse(modelManager.equals(5));
// different addressBook -> returns false
- assertFalse(modelManager.equals(new ModelManager(differentAddressBook, userPrefs)));
+ assertFalse(modelManager.equals(new ModelManager(differentAddressBook, tripBook, userPrefs)));
+
+ // different tripBook -> returns false
+ assertFalse(modelManager.equals(new ModelManager(addressBook, differentTripBook, userPrefs)));
// different filteredList -> returns false
String[] keywords = ALICE.getName().fullName.split("\\s+");
modelManager.updateFilteredPersonList(new NameContainsKeywordsPredicate(Arrays.asList(keywords)));
- assertFalse(modelManager.equals(new ModelManager(addressBook, userPrefs)));
+ assertFalse(modelManager.equals(new ModelManager(addressBook, tripBook, userPrefs)));
// resets modelManager to initial state for upcoming tests
modelManager.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
-
- // different userPrefs -> returns false
- UserPrefs differentUserPrefs = new UserPrefs();
- differentUserPrefs.setAddressBookFilePath(Paths.get("differentFilePath"));
- assertFalse(modelManager.equals(new ModelManager(addressBook, differentUserPrefs)));
}
}
diff --git a/src/test/java/seedu/address/model/TripBookTest.java b/src/test/java/seedu/address/model/TripBookTest.java
new file mode 100644
index 00000000000..05298d14146
--- /dev/null
+++ b/src/test/java/seedu/address/model/TripBookTest.java
@@ -0,0 +1,127 @@
+package seedu.address.model;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalTrips.PARIS;
+import static seedu.address.testutil.TypicalTrips.TOKYO;
+import static seedu.address.testutil.TypicalTrips.getTypicalTripBook;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import seedu.address.model.trip.Trip;
+import seedu.address.model.trip.exceptions.DuplicateTripException;
+import seedu.address.model.trip.exceptions.TripNotFoundException;
+import seedu.address.testutil.TripBuilder;
+
+public class TripBookTest {
+ private final TripBook tripBook = new TripBook();
+
+ @Test
+ public void constructor() {
+ assertEquals(Collections.emptyList(), tripBook.getTripList());
+ }
+
+ @Test
+ public void resetData_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> tripBook.resetData(null));
+ }
+
+ @Test
+ public void resetData_withValidReadOnlyTripBook_replacesData() {
+ TripBook newData = getTypicalTripBook();
+ tripBook.resetData(newData);
+ assertEquals(newData, tripBook);
+ }
+
+ @Test
+ public void resetData_withDuplicateTrips_throwsDuplicateTripException() {
+ List newTrips = Arrays.asList(PARIS, PARIS);
+ TripBookStub newData = new TripBookStub(newTrips);
+ assertThrows(DuplicateTripException.class, () -> tripBook.resetData(newData));
+ }
+
+ @Test
+ public void hasTrip_nullTrip_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> tripBook.hasTrip(null));
+ }
+
+ @Test
+ public void hasTrip_tripNotInTripBook_returnsFalse() {
+ assertFalse(tripBook.hasTrip(PARIS));
+ }
+
+ @Test
+ public void hasTrip_tripInTripBook_returnsTrue() {
+ tripBook.addTrip(PARIS);
+ assertTrue(tripBook.hasTrip(PARIS));
+ }
+
+ @Test
+ public void hasTrip_tripWithSameIdentityFieldsInTripBook_returnsTrue() {
+ tripBook.addTrip(PARIS);
+ Trip editedParis = new TripBuilder(PARIS).withAccommodation("Different Hotel")
+ .withItinerary("Different Itinerary").build();
+ assertTrue(tripBook.hasTrip(editedParis));
+ }
+
+ @Test
+ public void getTrips_modifyList_throwsUnsupportedOperationException() {
+ assertThrows(UnsupportedOperationException.class, () -> tripBook.getTripList().remove(0));
+ }
+
+ @Test
+ public void removeTrip_tripIsRemoved() {
+ tripBook.addTrip(PARIS);
+ tripBook.removeTrip(PARIS);
+ TripBook expectedTripBook = new TripBook();
+ assertEquals(expectedTripBook, tripBook);
+ }
+
+ @Test
+ public void setTrip_targetTripNotInList_throwsTripNotFoundException() {
+ assertThrows(TripNotFoundException.class, () -> tripBook.setTrip(PARIS, PARIS));
+ }
+
+ @Test
+ public void setTrip_editedTripIsSameTrip_success() {
+ tripBook.addTrip(PARIS);
+ tripBook.setTrip(PARIS, PARIS);
+ TripBook expectedTripBook = new TripBook();
+ expectedTripBook.addTrip(PARIS);
+ assertEquals(expectedTripBook, tripBook);
+ }
+
+ @Test
+ public void setTrip_editedTripHasDifferentIdentity_success() {
+ tripBook.addTrip(PARIS);
+ tripBook.setTrip(PARIS, TOKYO);
+ TripBook expectedTripBook = new TripBook();
+ expectedTripBook.addTrip(TOKYO);
+ assertEquals(expectedTripBook, tripBook);
+ }
+
+ /**
+ * A stub ReadOnlyTripBook whose trips list can violate interface constraints.
+ */
+ private static class TripBookStub implements ReadOnlyTripBook {
+ private final ObservableList trips = FXCollections.observableArrayList();
+
+ TripBookStub(Collection trips) {
+ this.trips.setAll(trips);
+ }
+
+ @Override
+ public ObservableList getTripList() {
+ return trips;
+ }
+ }
+}
diff --git a/src/test/java/seedu/address/model/UserPrefsTest.java b/src/test/java/seedu/address/model/UserPrefsTest.java
index b1307a70d52..fddd0b9f2ef 100644
--- a/src/test/java/seedu/address/model/UserPrefsTest.java
+++ b/src/test/java/seedu/address/model/UserPrefsTest.java
@@ -1,9 +1,16 @@
package seedu.address.model;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.testutil.Assert.assertThrows;
+import java.nio.file.Paths;
+
import org.junit.jupiter.api.Test;
+import seedu.address.commons.core.GuiSettings;
+
public class UserPrefsTest {
@Test
@@ -18,4 +25,45 @@ public void setAddressBookFilePath_nullPath_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> userPrefs.setAddressBookFilePath(null));
}
+ @Test
+ public void equals() {
+ UserPrefs userPrefs = new UserPrefs();
+
+ // same values -> returns true
+ UserPrefs copy = new UserPrefs();
+ assertTrue(userPrefs.equals(copy));
+
+ // same object -> returns true
+ assertTrue(userPrefs.equals(userPrefs));
+
+ // null -> returns false
+ assertFalse(userPrefs.equals(null));
+
+ // different type -> returns false
+ assertFalse(userPrefs.equals(5));
+
+ // different addressBookFilePath -> returns false
+ UserPrefs differentUserPrefs = new UserPrefs();
+ differentUserPrefs.setAddressBookFilePath(Paths.get("different/path"));
+ assertFalse(userPrefs.equals(differentUserPrefs));
+
+ // different guiSettings -> returns false
+ differentUserPrefs = new UserPrefs();
+ GuiSettings guiSettings = new GuiSettings(10, 20, 30, 40);
+ differentUserPrefs.setGuiSettings(guiSettings);
+ assertFalse(userPrefs.equals(differentUserPrefs));
+ }
+
+ @Test
+ public void hashcode() {
+ UserPrefs userPrefs = new UserPrefs();
+
+ // same values -> returns same hashcode
+ UserPrefs copy = new UserPrefs();
+ assertEquals(userPrefs.hashCode(), copy.hashCode());
+
+ // different values -> returns different hashcode
+ copy.setAddressBookFilePath(Paths.get("different/path"));
+ assertFalse(userPrefs.hashCode() == copy.hashCode());
+ }
}
diff --git a/src/test/java/seedu/address/model/person/AddressTest.java b/src/test/java/seedu/address/model/contact/AddressTest.java
similarity index 97%
rename from src/test/java/seedu/address/model/person/AddressTest.java
rename to src/test/java/seedu/address/model/contact/AddressTest.java
index 314885eca26..b8fdb4e56cd 100644
--- a/src/test/java/seedu/address/model/person/AddressTest.java
+++ b/src/test/java/seedu/address/model/contact/AddressTest.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
diff --git a/src/test/java/seedu/address/model/person/PersonTest.java b/src/test/java/seedu/address/model/contact/ContactTest.java
similarity index 53%
rename from src/test/java/seedu/address/model/person/PersonTest.java
rename to src/test/java/seedu/address/model/contact/ContactTest.java
index 31a10d156c9..14c58d475e4 100644
--- a/src/test/java/seedu/address/model/person/PersonTest.java
+++ b/src/test/java/seedu/address/model/contact/ContactTest.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -7,21 +7,23 @@
import static seedu.address.logic.commands.CommandTestUtil.VALID_EMAIL_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_CUSTOMER;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BOB;
import org.junit.jupiter.api.Test;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.util.SampleDataUtil;
import seedu.address.testutil.PersonBuilder;
-public class PersonTest {
+public class ContactTest {
@Test
public void asObservableList_modifyList_throwsUnsupportedOperationException() {
- Person person = new PersonBuilder().build();
- assertThrows(UnsupportedOperationException.class, () -> person.getTags().remove(0));
+ Contact contact = new PersonBuilder().build();
+ assertThrows(UnsupportedOperationException.class, () -> contact.getTags().remove(0));
}
@Test
@@ -32,29 +34,53 @@ public void isSamePerson() {
// null -> returns false
assertFalse(ALICE.isSamePerson(null));
- // same name, all other attributes different -> returns true
- Person editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withEmail(VALID_EMAIL_BOB)
- .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND).build();
+ // same email, all other attributes different -> returns true
+ Contact editedAlice = new PersonBuilder(ALICE).withPhone(VALID_PHONE_BOB).withName(VALID_NAME_BOB)
+ .withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_CUSTOMER).build();
assertTrue(ALICE.isSamePerson(editedAlice));
- // different name, all other attributes same -> returns false
- editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build();
+ // different email, all other attributes same -> returns false
+ editedAlice = new PersonBuilder(ALICE).withEmail(VALID_EMAIL_BOB).build();
assertFalse(ALICE.isSamePerson(editedAlice));
- // name differs in case, all other attributes same -> returns false
- Person editedBob = new PersonBuilder(BOB).withName(VALID_NAME_BOB.toLowerCase()).build();
- assertFalse(BOB.isSamePerson(editedBob));
+ // email differs in case, all other attributes same -> returns false
+ Contact editedBob = new PersonBuilder(BOB).withEmail(VALID_EMAIL_BOB.toUpperCase()).build();
+ assertTrue(BOB.isSamePerson(editedBob));
+ }
- // name has trailing spaces, all other attributes same -> returns false
- String nameWithTrailingSpaces = VALID_NAME_BOB + " ";
- editedBob = new PersonBuilder(BOB).withName(nameWithTrailingSpaces).build();
- assertFalse(BOB.isSamePerson(editedBob));
+ @Test
+ public void sampleDataTest() {
+ Contact[] contacts = SampleDataUtil.getSamplePersons();
+ Tag customerTag = new Tag("customer");
+ Tag serviceTag = new Tag("service");
+
+ Contact alex = contacts[0];
+ Contact bernice = contacts[1];
+ Contact charlotte = contacts[2];
+ Contact david = contacts[3];
+ Contact irfan = contacts[4];
+ Contact roy = contacts[5];
+
+ assertTrue(alex.getTags().contains(customerTag));
+ assertTrue(bernice.getTags().contains(serviceTag));
+ assertTrue(charlotte.getTags().isEmpty());
+ assertTrue(david.getTags().contains(customerTag));
+ assertTrue(irfan.getTags().contains(customerTag));
+ assertTrue(irfan.getTags().contains(serviceTag));
+ assertTrue(roy.getTags().contains(serviceTag));
+
+ assertTrue(alex.isCustomer());
+ assertFalse(charlotte.isCustomer());
+ assertFalse(charlotte.isService());
+ assertTrue(irfan.isCustomer());
+ assertTrue(irfan.isService());
+ assertTrue(roy.isService());
}
@Test
public void equals() {
// same values -> returns true
- Person aliceCopy = new PersonBuilder(ALICE).build();
+ Contact aliceCopy = new PersonBuilder(ALICE).build();
assertTrue(ALICE.equals(aliceCopy));
// same object -> returns true
@@ -66,11 +92,11 @@ public void equals() {
// different type -> returns false
assertFalse(ALICE.equals(5));
- // different person -> returns false
+ // different contact -> returns false
assertFalse(ALICE.equals(BOB));
// different name -> returns false
- Person editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build();
+ Contact editedAlice = new PersonBuilder(ALICE).withName(VALID_NAME_BOB).build();
assertFalse(ALICE.equals(editedAlice));
// different phone -> returns false
@@ -86,14 +112,15 @@ public void equals() {
assertFalse(ALICE.equals(editedAlice));
// different tags -> returns false
- editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_HUSBAND).build();
+ editedAlice = new PersonBuilder(ALICE).withTags(VALID_TAG_CUSTOMER).build();
assertFalse(ALICE.equals(editedAlice));
}
@Test
public void toStringMethod() {
- String expected = Person.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone()
- + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags() + "}";
+ String expected = Contact.class.getCanonicalName() + "{name=" + ALICE.getName() + ", phone=" + ALICE.getPhone()
+ + ", email=" + ALICE.getEmail() + ", address=" + ALICE.getAddress() + ", tags=" + ALICE.getTags()
+ + ", note=" + ALICE.getNote() + "}";
assertEquals(expected, ALICE.toString());
}
}
diff --git a/src/test/java/seedu/address/model/person/EmailTest.java b/src/test/java/seedu/address/model/contact/EmailTest.java
similarity index 99%
rename from src/test/java/seedu/address/model/person/EmailTest.java
rename to src/test/java/seedu/address/model/contact/EmailTest.java
index f08cdff0a64..2c2749f24a7 100644
--- a/src/test/java/seedu/address/model/person/EmailTest.java
+++ b/src/test/java/seedu/address/model/contact/EmailTest.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
diff --git a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java b/src/test/java/seedu/address/model/contact/NameContainsKeywordsPredicateTest.java
similarity index 97%
rename from src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
rename to src/test/java/seedu/address/model/contact/NameContainsKeywordsPredicateTest.java
index 6b3fd90ade7..4d12478e8c1 100644
--- a/src/test/java/seedu/address/model/person/NameContainsKeywordsPredicateTest.java
+++ b/src/test/java/seedu/address/model/contact/NameContainsKeywordsPredicateTest.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -35,7 +35,7 @@ public void equals() {
// null -> returns false
assertFalse(firstPredicate.equals(null));
- // different person -> returns false
+ // different contact -> returns false
assertFalse(firstPredicate.equals(secondPredicate));
}
diff --git a/src/test/java/seedu/address/model/person/NameTest.java b/src/test/java/seedu/address/model/contact/NameTest.java
similarity index 65%
rename from src/test/java/seedu/address/model/person/NameTest.java
rename to src/test/java/seedu/address/model/contact/NameTest.java
index 94e3dd726bd..35f756bf4df 100644
--- a/src/test/java/seedu/address/model/person/NameTest.java
+++ b/src/test/java/seedu/address/model/contact/NameTest.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -24,18 +24,27 @@ public void isValidName() {
// null name
assertThrows(NullPointerException.class, () -> Name.isValidName(null));
- // invalid name
+ // invalid names
assertFalse(Name.isValidName("")); // empty string
assertFalse(Name.isValidName(" ")); // spaces only
- assertFalse(Name.isValidName("^")); // only non-alphanumeric characters
- assertFalse(Name.isValidName("peter*")); // contains non-alphanumeric characters
+ assertFalse(Name.isValidName("^")); // only invalid symbol
+ assertFalse(Name.isValidName("peter*")); // contains invalid symbol
+ assertFalse(Name.isValidName("!!!")); // only invalid symbols
+ assertFalse(Name.isValidName("John@Doe")); // disallowed special characters
- // valid name
+ // valid names
assertTrue(Name.isValidName("peter jack")); // alphabets only
assertTrue(Name.isValidName("12345")); // numbers only
assertTrue(Name.isValidName("peter the 2nd")); // alphanumeric characters
assertTrue(Name.isValidName("Capital Tan")); // with capital letters
assertTrue(Name.isValidName("David Roger Jackson Ray Jr 2nd")); // long names
+ assertTrue(Name.isValidName("Jeanne d’Arc")); // with curly apostrophe
+ assertTrue(Name.isValidName("Tharman s/o Soham")); // with slash
+ assertTrue(Name.isValidName("Jean-Luc Picard")); // with hyphen
+ assertTrue(Name.isValidName("O'Connor")); // with straight apostrophe
+ assertTrue(Name.isValidName("Dr. John A. Smith")); // with dots
+ assertTrue(Name.isValidName("李小龙")); // Unicode characters (Chinese)
+ assertTrue(Name.isValidName("Nguyễn Văn A")); // Unicode with accents
}
@Test
diff --git a/src/test/java/seedu/address/model/contact/NoteTest.java b/src/test/java/seedu/address/model/contact/NoteTest.java
new file mode 100644
index 00000000000..0934b2d168f
--- /dev/null
+++ b/src/test/java/seedu/address/model/contact/NoteTest.java
@@ -0,0 +1,77 @@
+package seedu.address.model.contact;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class NoteTest {
+
+ @Test
+ public void equals() {
+ Note note = new Note("Test note");
+
+ // same object -> returns true
+ assertTrue(note.equals(note));
+
+ // null -> returns false
+ assertFalse(note.equals(null));
+
+ // different types -> returns false
+ assertFalse(note.equals(5));
+
+ // different note -> returns false
+ assertFalse(note.equals(new Note("Different note")));
+
+ // same note value -> returns true
+ assertTrue(note.equals(new Note("Test note")));
+ }
+
+ @Test
+ public void hashCode_sameNote_sameHashCode() {
+ Note note1 = new Note("Test note");
+ Note note2 = new Note("Test note");
+ assertEquals(note1.hashCode(), note2.hashCode());
+ }
+
+ @Test
+ public void hashCode_differentNote_differentHashCode() {
+ Note note1 = new Note("Test note");
+ Note note2 = new Note("Different note");
+ assertNotEquals(note1.hashCode(), note2.hashCode());
+ }
+
+ @Test
+ public void constructor_emptyString_returnsEmptyNote() {
+ Note note = new Note("");
+ assertTrue(note.isEmpty());
+ }
+
+ @Test
+ public void constructor_blankString_returnsEmptyNote() {
+ Note note = new Note(" ");
+ assertTrue(note.isEmpty());
+ }
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Note(null));
+ }
+
+ @Test
+ public void getValue() {
+ String validNote = "Test note";
+ Note note = new Note(validNote);
+ assertEquals(validNote, note.getNote());
+ }
+
+ @Test
+ public void testToString() {
+ String validNote = "Test note";
+ Note note = new Note(validNote);
+ assertEquals(validNote, note.toString());
+ }
+}
diff --git a/src/test/java/seedu/address/model/person/PhoneTest.java b/src/test/java/seedu/address/model/contact/PhoneTest.java
similarity index 90%
rename from src/test/java/seedu/address/model/person/PhoneTest.java
rename to src/test/java/seedu/address/model/contact/PhoneTest.java
index deaaa5ba190..ade0e484e0d 100644
--- a/src/test/java/seedu/address/model/person/PhoneTest.java
+++ b/src/test/java/seedu/address/model/contact/PhoneTest.java
@@ -1,4 +1,4 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -31,11 +31,12 @@ public void isValidPhone() {
assertFalse(Phone.isValidPhone("phone")); // non-numeric
assertFalse(Phone.isValidPhone("9011p041")); // alphabets within digits
assertFalse(Phone.isValidPhone("9312 1534")); // spaces within digits
+ assertFalse(Phone.isValidPhone("1242938420331231234")); //19 numbers
// valid phone numbers
assertTrue(Phone.isValidPhone("911")); // exactly 3 numbers
assertTrue(Phone.isValidPhone("93121534"));
- assertTrue(Phone.isValidPhone("124293842033123")); // long phone numbers
+ assertTrue(Phone.isValidPhone("124293842033123")); // 15 numbers phone numbers
}
@Test
diff --git a/src/test/java/seedu/address/model/person/UniquePersonListTest.java b/src/test/java/seedu/address/model/contact/UniqueContactListTest.java
similarity index 89%
rename from src/test/java/seedu/address/model/person/UniquePersonListTest.java
rename to src/test/java/seedu/address/model/contact/UniqueContactListTest.java
index 17ae501df08..ffb1db3e534 100644
--- a/src/test/java/seedu/address/model/person/UniquePersonListTest.java
+++ b/src/test/java/seedu/address/model/contact/UniqueContactListTest.java
@@ -1,10 +1,10 @@
-package seedu.address.model.person;
+package seedu.address.model.contact;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.logic.commands.CommandTestUtil.VALID_ADDRESS_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_CUSTOMER;
import static seedu.address.testutil.Assert.assertThrows;
import static seedu.address.testutil.TypicalPersons.ALICE;
import static seedu.address.testutil.TypicalPersons.BOB;
@@ -15,11 +15,11 @@
import org.junit.jupiter.api.Test;
-import seedu.address.model.person.exceptions.DuplicatePersonException;
-import seedu.address.model.person.exceptions.PersonNotFoundException;
+import seedu.address.model.contact.exceptions.DuplicatePersonException;
+import seedu.address.model.contact.exceptions.PersonNotFoundException;
import seedu.address.testutil.PersonBuilder;
-public class UniquePersonListTest {
+public class UniqueContactListTest {
private final UniquePersonList uniquePersonList = new UniquePersonList();
@@ -42,7 +42,7 @@ public void contains_personInList_returnsTrue() {
@Test
public void contains_personWithSameIdentityFieldsInList_returnsTrue() {
uniquePersonList.add(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
+ Contact editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_CUSTOMER)
.build();
assertTrue(uniquePersonList.contains(editedAlice));
}
@@ -85,7 +85,7 @@ public void setPerson_editedPersonIsSamePerson_success() {
@Test
public void setPerson_editedPersonHasSameIdentity_success() {
uniquePersonList.add(ALICE);
- Person editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND)
+ Contact editedAlice = new PersonBuilder(ALICE).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_CUSTOMER)
.build();
uniquePersonList.setPerson(ALICE, editedAlice);
UniquePersonList expectedUniquePersonList = new UniquePersonList();
@@ -143,14 +143,14 @@ public void setPersons_uniquePersonList_replacesOwnListWithProvidedUniquePersonL
@Test
public void setPersons_nullList_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> uniquePersonList.setPersons((List) null));
+ assertThrows(NullPointerException.class, () -> uniquePersonList.setPersons((List) null));
}
@Test
public void setPersons_list_replacesOwnListWithProvidedList() {
uniquePersonList.add(ALICE);
- List personList = Collections.singletonList(BOB);
- uniquePersonList.setPersons(personList);
+ List contactList = Collections.singletonList(BOB);
+ uniquePersonList.setPersons(contactList);
UniquePersonList expectedUniquePersonList = new UniquePersonList();
expectedUniquePersonList.add(BOB);
assertEquals(expectedUniquePersonList, uniquePersonList);
@@ -158,8 +158,8 @@ public void setPersons_list_replacesOwnListWithProvidedList() {
@Test
public void setPersons_listWithDuplicatePersons_throwsDuplicatePersonException() {
- List listWithDuplicatePersons = Arrays.asList(ALICE, ALICE);
- assertThrows(DuplicatePersonException.class, () -> uniquePersonList.setPersons(listWithDuplicatePersons));
+ List listWithDuplicateContacts = Arrays.asList(ALICE, ALICE);
+ assertThrows(DuplicatePersonException.class, () -> uniquePersonList.setPersons(listWithDuplicateContacts));
}
@Test
diff --git a/src/test/java/seedu/address/model/tag/TagTest.java b/src/test/java/seedu/address/model/tag/TagTest.java
index 64d07d79ee2..0e09ed391bf 100644
--- a/src/test/java/seedu/address/model/tag/TagTest.java
+++ b/src/test/java/seedu/address/model/tag/TagTest.java
@@ -1,5 +1,6 @@
package seedu.address.model.tag;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static seedu.address.testutil.Assert.assertThrows;
import org.junit.jupiter.api.Test;
@@ -23,4 +24,15 @@ public void isValidTagName() {
assertThrows(NullPointerException.class, () -> Tag.isValidTagName(null));
}
+ @Test
+ public void isValidCustomerStyle() {
+ Tag customerTag = new Tag("customer");
+ assertTrue(customerTag.getStyleClass().equals("customer-tag"));
+ }
+
+ public void isValidServiceStyle() {
+ Tag serviceTag = new Tag("service");
+ assertTrue(serviceTag.getStyleClass().equals("service-tag"));
+ }
+
}
diff --git a/src/test/java/seedu/address/model/trip/AccommodationTest.java b/src/test/java/seedu/address/model/trip/AccommodationTest.java
new file mode 100644
index 00000000000..c978d5648fc
--- /dev/null
+++ b/src/test/java/seedu/address/model/trip/AccommodationTest.java
@@ -0,0 +1,62 @@
+package seedu.address.model.trip;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class AccommodationTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Accommodation(null));
+ }
+
+ @Test
+ public void constructor_invalidAccommodation_throwsIllegalArgumentException() {
+ String invalidAccommodation = " "; // only whitespace
+ assertThrows(IllegalArgumentException.class, () -> new Accommodation(invalidAccommodation));
+ }
+
+ @Test
+ public void isValidAccommodation() {
+ // null accommodation
+ assertFalse(Accommodation.isValidAccommodation(null));
+
+ // invalid accommodation
+ assertFalse(Accommodation.isValidAccommodation("")); // empty string
+ assertFalse(Accommodation.isValidAccommodation(" ")); // spaces only
+
+ // valid accommodation
+ assertTrue(Accommodation.isValidAccommodation("Hilton Hotel")); // alphabets only
+ assertTrue(Accommodation.isValidAccommodation("123")); // numbers only
+ assertTrue(Accommodation.isValidAccommodation("Airbnb Apartment 42")); // alphanumeric characters
+ assertTrue(Accommodation.isValidAccommodation("The Grand Budapest Hotel")); // with capital letters
+ assertTrue(Accommodation.isValidAccommodation(
+ "Holiday Inn Express & Suites Downtown")); // with special characters
+ assertTrue(Accommodation.isValidAccommodation("Le Méridien")); // with non-english characters
+ assertTrue(Accommodation.isValidAccommodation(
+ "Four Seasons Resort Maui at Wailea, 3900 Wailea Alanui Dr")); // long name with address
+ }
+
+ @Test
+ public void equals() {
+ Accommodation accommodation = new Accommodation("Hilton Hotel");
+
+ // same values -> returns true
+ assertTrue(accommodation.equals(new Accommodation("Hilton Hotel")));
+
+ // same object -> returns true
+ assertTrue(accommodation.equals(accommodation));
+
+ // null -> returns false
+ assertFalse(accommodation.equals(null));
+
+ // different types -> returns false
+ assertFalse(accommodation.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(accommodation.equals(new Accommodation("Marriott Hotel")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/trip/ItineraryTest.java b/src/test/java/seedu/address/model/trip/ItineraryTest.java
new file mode 100644
index 00000000000..b946960d97a
--- /dev/null
+++ b/src/test/java/seedu/address/model/trip/ItineraryTest.java
@@ -0,0 +1,61 @@
+package seedu.address.model.trip;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class ItineraryTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Itinerary(null));
+ }
+
+ @Test
+ public void constructor_invalidItinerary_throwsIllegalArgumentException() {
+ String invalidItinerary = " "; // only whitespace
+ assertThrows(IllegalArgumentException.class, () -> new Itinerary(invalidItinerary));
+ }
+
+ @Test
+ public void isValidItinerary() {
+ // null itinerary
+ assertFalse(Itinerary.isValidItinerary(null));
+
+ // invalid itinerary
+ assertFalse(Itinerary.isValidItinerary("")); // empty string
+ assertFalse(Itinerary.isValidItinerary(" ")); // spaces only
+
+ // valid itinerary
+ assertTrue(Itinerary.isValidItinerary("Day 1: City tour")); // simple itinerary
+ assertTrue(Itinerary.isValidItinerary("Visit museums and parks")); // activity description
+ assertTrue(Itinerary.isValidItinerary("9:00 - Check out from hotel, 10:30 - Flight to Paris")); // with times
+ assertTrue(Itinerary.isValidItinerary("Day 1-3: Rome\nDay 4-6: Florence\nDay 7-10: Venice")); // with newlines
+ assertTrue(Itinerary.isValidItinerary("Morning: Eiffel Tower, Afternoon: Louvre Museum")); // with colons
+ assertTrue(Itinerary.isValidItinerary("Visit Café de Flore & Notre-Dame")); // with special characters
+ assertTrue(Itinerary.isValidItinerary(
+ "1. Breakfast at hotel 2. City tour 3. Shopping at local market")); // numbered list
+ }
+
+ @Test
+ public void equals() {
+ Itinerary itinerary = new Itinerary("Day 1: City tour");
+
+ // same values -> returns true
+ assertTrue(itinerary.equals(new Itinerary("Day 1: City tour")));
+
+ // same object -> returns true
+ assertTrue(itinerary.equals(itinerary));
+
+ // null -> returns false
+ assertFalse(itinerary.equals(null));
+
+ // different types -> returns false
+ assertFalse(itinerary.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(itinerary.equals(new Itinerary("Day 2: Beach day")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/trip/NoteTest.java b/src/test/java/seedu/address/model/trip/NoteTest.java
new file mode 100644
index 00000000000..85f3729fc70
--- /dev/null
+++ b/src/test/java/seedu/address/model/trip/NoteTest.java
@@ -0,0 +1,63 @@
+package seedu.address.model.trip;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class NoteTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new Note(null));
+ }
+
+ @Test
+ public void equals() {
+ Note note = new Note("Test note");
+
+ // same object -> returns true
+ assertTrue(note.equals(note));
+
+ // null -> returns false
+ assertFalse(note.equals(null));
+
+ // different types -> returns false
+ assertFalse(note.equals(5));
+
+ // different note -> returns false
+ assertFalse(note.equals(new Note("Different note")));
+
+ // same note value -> returns true
+ assertTrue(note.equals(new Note("Test note")));
+ }
+
+ @Test
+ public void hashCode_sameNote_sameHashCode() {
+ Note note1 = new Note("Test note");
+ Note note2 = new Note("Test note");
+ assertEquals(note1.hashCode(), note2.hashCode());
+ }
+
+ @Test
+ public void hashCode_differentNote_differentHashCode() {
+ Note note1 = new Note("Test note");
+ Note note2 = new Note("Different note");
+ assertNotEquals(note1.hashCode(), note2.hashCode());
+ }
+
+ @Test
+ public void constructor_emptyString_returnsEmptyNote() {
+ Note note = new Note("");
+ assertEquals("", note.note);
+ }
+
+ @Test
+ public void constructor_blankString_returnsEmptyNote() {
+ Note note = new Note(" ");
+ assertEquals("", note.note);
+ }
+}
diff --git a/src/test/java/seedu/address/model/trip/TripDateTest.java b/src/test/java/seedu/address/model/trip/TripDateTest.java
new file mode 100644
index 00000000000..48ea55ab0b1
--- /dev/null
+++ b/src/test/java/seedu/address/model/trip/TripDateTest.java
@@ -0,0 +1,119 @@
+package seedu.address.model.trip;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.time.LocalDate;
+
+import org.junit.jupiter.api.Test;
+
+public class TripDateTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new TripDate(null));
+ }
+
+ @Test
+ public void constructor_invalidTripDate_throwsIllegalArgumentException() {
+ // Test empty string
+ assertThrows(IllegalArgumentException.class, () -> new TripDate(""));
+
+ // Test various invalid formats
+ assertThrows(IllegalArgumentException.class, () -> new TripDate("01-01-2023"));
+ assertThrows(IllegalArgumentException.class, () -> new TripDate("32/1/2023"));
+ assertThrows(IllegalArgumentException.class, () -> new TripDate("15/13/2023"));
+ assertThrows(IllegalArgumentException.class, () -> new TripDate("29/2/2023")); // Not a leap year
+ assertThrows(IllegalArgumentException.class, () -> new TripDate("ABC"));
+ assertThrows(IllegalArgumentException.class, () -> new TripDate("2023/05/15"));
+ assertThrows(IllegalArgumentException.class, () -> new TripDate("15.05.2023"));
+ }
+
+ @Test
+ public void constructor_validTripDate_success() {
+ // Test with various valid formats
+ new TripDate("1/1/2023"); // Minimal format
+ new TripDate("31/12/2023"); // End of year
+ new TripDate("29/2/2024"); // Leap year
+ new TripDate("15/6/2025"); // Future date
+ new TripDate(" 25/12/2025 "); // With whitespace
+
+ // Test with single digit day/month
+ TripDate tripDate = new TripDate("5/8/2023");
+ assertEquals(LocalDate.of(2023, 8, 5), tripDate.date);
+ }
+
+ @Test
+ public void isValidTripDate() {
+ // null trip date
+ assertFalse(TripDate.isValidTripDate(null));
+
+ // invalid trip dates
+ assertFalse(TripDate.isValidTripDate("")); // empty string
+ assertFalse(TripDate.isValidTripDate(" ")); // spaces only
+ assertFalse(TripDate.isValidTripDate("15-05-2023")); // wrong format
+ assertFalse(TripDate.isValidTripDate("2023/05/15")); // wrong format
+ assertFalse(TripDate.isValidTripDate("15.05.2023")); // wrong format
+ assertFalse(TripDate.isValidTripDate("32/1/2023")); // invalid day
+ assertFalse(TripDate.isValidTripDate("15/13/2023")); // invalid month
+ assertFalse(TripDate.isValidTripDate("29/2/2023")); // not leap year
+ assertFalse(TripDate.isValidTripDate("ABC")); // non-numeric
+ assertFalse(TripDate.isValidTripDate("1/1/10000")); // year too large
+ assertFalse(TripDate.isValidTripDate("0/1/2023")); // day zero
+ assertFalse(TripDate.isValidTripDate("1/0/2023")); // month zero
+ assertFalse(TripDate.isValidTripDate("1/1/2101")); // >2100
+ assertFalse(TripDate.isValidTripDate("1/1/1949")); // <1950
+
+ // valid trip dates
+ assertTrue(TripDate.isValidTripDate("1/1/2100")); // minimal format
+ assertTrue(TripDate.isValidTripDate("1/1/1950")); // minimal format
+ assertTrue(TripDate.isValidTripDate("1/1/2023")); // minimal format
+ assertTrue(TripDate.isValidTripDate("31/12/2023")); // end of year
+ assertTrue(TripDate.isValidTripDate("29/2/2024")); // leap year
+ assertTrue(TripDate.isValidTripDate("15/6/2025")); // future date
+ assertTrue(TripDate.isValidTripDate(" 25/12/2023 ")); // with whitespace
+ assertTrue(TripDate.isValidTripDate("5/8/2023")); // single digit day/month
+ }
+
+ @Test
+ public void equals() {
+ TripDate tripDate = new TripDate("15/6/2023");
+
+ // same values -> returns true
+ assertTrue(tripDate.equals(new TripDate("15/6/2023")));
+
+ // same object -> returns true
+ assertTrue(tripDate.equals(tripDate));
+
+ // null -> returns false
+ assertFalse(tripDate.equals(null));
+
+ // different types -> returns false
+ assertFalse(tripDate.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(tripDate.equals(new TripDate("16/6/2023"))); // different day
+ assertFalse(tripDate.equals(new TripDate("15/7/2023"))); // different month
+ assertFalse(tripDate.equals(new TripDate("15/6/2024"))); // different year
+ assertFalse(tripDate.equals(new TripDate("1/1/2023"))); // completely different
+ }
+
+ @Test
+ public void toString_returnsDateString() {
+ TripDate tripDate = new TripDate("15/6/2023");
+ assertEquals("15/6/2023", tripDate.toString());
+
+ // Test with single digit day/month
+ TripDate tripDate2 = new TripDate("5/8/2023");
+ assertEquals("5/8/2023", tripDate2.toString());
+ }
+
+ @Test
+ public void hashCode_consistency() {
+ TripDate tripDate1 = new TripDate("15/6/2023");
+ TripDate tripDate2 = new TripDate("15/6/2023");
+ assertEquals(tripDate1.hashCode(), tripDate2.hashCode());
+ }
+}
diff --git a/src/test/java/seedu/address/model/trip/TripNameTest.java b/src/test/java/seedu/address/model/trip/TripNameTest.java
new file mode 100644
index 00000000000..329abd77286
--- /dev/null
+++ b/src/test/java/seedu/address/model/trip/TripNameTest.java
@@ -0,0 +1,60 @@
+package seedu.address.model.trip;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+public class TripNameTest {
+
+ @Test
+ public void constructor_null_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> new TripName(null));
+ }
+
+ @Test
+ public void constructor_invalidName_throwsIllegalArgumentException() {
+ String invalidName = "";
+ assertThrows(IllegalArgumentException.class, () -> new TripName(invalidName));
+ }
+
+ @Test
+ public void isValidName() {
+ // null name
+ assertThrows(NullPointerException.class, () -> TripName.isValidName(null));
+
+ // invalid name
+ assertFalse(TripName.isValidName("")); // empty string
+ assertFalse(TripName.isValidName(" ")); // spaces only
+ assertFalse(TripName.isValidName("^")); // only non-alphanumeric characters
+ assertFalse(TripName.isValidName("europe*")); // contains non-alphanumeric characters
+
+ // valid name
+ assertTrue(TripName.isValidName("summer vacation")); // alphabets only
+ assertTrue(TripName.isValidName("12345")); // numbers only
+ assertTrue(TripName.isValidName("europe trip 2025")); // alphanumeric characters
+ assertTrue(TripName.isValidName("Europe Tour")); // with capital letters
+ assertTrue(TripName.isValidName("Grand ABC 123123123 Holiday Trip 2025")); // long names
+ }
+
+ @Test
+ public void equals() {
+ TripName tripName = new TripName("Europe Trip");
+
+ // same values -> returns true
+ assertTrue(tripName.equals(new TripName("Europe Trip")));
+
+ // same object -> returns true
+ assertTrue(tripName.equals(tripName));
+
+ // null -> returns false
+ assertFalse(tripName.equals(null));
+
+ // different types -> returns false
+ assertFalse(tripName.equals(5.0f));
+
+ // different values -> returns false
+ assertFalse(tripName.equals(new TripName("Asia Tour")));
+ }
+}
diff --git a/src/test/java/seedu/address/model/trip/TripTest.java b/src/test/java/seedu/address/model/trip/TripTest.java
new file mode 100644
index 00000000000..c90182e0783
--- /dev/null
+++ b/src/test/java/seedu/address/model/trip/TripTest.java
@@ -0,0 +1,142 @@
+package seedu.address.model.trip;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.contact.Name;
+import seedu.address.testutil.TripBuilder;
+
+public class TripTest {
+
+ private static final Trip PARIS = new TripBuilder().withName("Paris 2025")
+ .withAccommodation("Hotel 81")
+ .withItinerary("Eat baguettes")
+ .withDate("01/01/2100")
+ .withCustomerNames("John Doe", "Jane Doe")
+ .withNote("Customer prefers window seat")
+ .build();
+
+ private static final Trip TOKYO = new TripBuilder().withName("Tokyo 2025")
+ .withAccommodation("Capsule Inn")
+ .withItinerary("Visit Tokyo Tower")
+ .withDate("02/02/2100")
+ .withCustomerNames("Alice Smith", "Bob Jones")
+ .withNote("Customer prefers aisle seat")
+ .build();
+
+ // Valid values for testing modifications
+ private static final String VALID_NAME_TOKYO = "Tokyo 2025";
+ private static final String VALID_ACCOMMODATION_TOKYO = "Capsule Inn";
+ private static final String VALID_ITINERARY_TOKYO = "Visit Tokyo Tower";
+ private static final String VALID_DATE_TOKYO = "02/02/2100";
+ private static final String VALID_CUSTOMER_BOB = "Bob Jones";
+ private static final String VALID_NOTE_TOKYO = "Customer prefers aisle seat";
+
+ @Test
+ public void getCustomerNames_modifyList_throwsUnsupportedOperationException() {
+ Trip trip = new TripBuilder().build();
+ assertThrows(UnsupportedOperationException.class, () -> trip.getCustomerNames().remove(new Name("John Doe")));
+ }
+
+ @Test
+ public void isSameTrip() {
+ // same object -> returns true
+ assertTrue(PARIS.isSameTrip(PARIS));
+
+ // null -> returns false
+ assertFalse(PARIS.isSameTrip(null));
+
+ // same name, all other attributes different -> returns true
+ Trip editedParis = new TripBuilder(PARIS).withAccommodation(VALID_ACCOMMODATION_TOKYO)
+ .withItinerary(VALID_ITINERARY_TOKYO)
+ .withDate(VALID_DATE_TOKYO)
+ .withCustomerNames(VALID_CUSTOMER_BOB)
+ .withNote(VALID_NOTE_TOKYO)
+ .build();
+ assertTrue(PARIS.isSameTrip(editedParis));
+
+ // different name, all other attributes same -> returns false
+ editedParis = new TripBuilder(PARIS).withName(VALID_NAME_TOKYO).build();
+ assertFalse(PARIS.isSameTrip(editedParis));
+
+ // name differs in case, all other attributes same -> returns true
+ Trip editedTokyo = new TripBuilder(TOKYO).withName(VALID_NAME_TOKYO.toLowerCase()).build();
+ assertTrue(TOKYO.isSameTrip(editedTokyo));
+
+ // name has trailing spaces, all other attributes same -> returns false
+ String nameWithTrailingSpaces = VALID_NAME_TOKYO + " ";
+ editedTokyo = new TripBuilder(TOKYO).withName(nameWithTrailingSpaces).build();
+ assertFalse(TOKYO.isSameTrip(editedTokyo));
+ }
+
+ @Test
+ public void equals() {
+ // same values -> returns true
+ Trip parisCopy = new TripBuilder(PARIS).build();
+ assertTrue(PARIS.equals(parisCopy));
+
+ // same object -> returns true
+ assertTrue(PARIS.equals(PARIS));
+
+ // null -> returns false
+ assertFalse(PARIS.equals(null));
+
+ // different type -> returns false
+ assertFalse(PARIS.equals(5));
+
+ // different trip -> returns false
+ assertFalse(PARIS.equals(TOKYO));
+
+ // different name -> returns false
+ Trip editedParis = new TripBuilder(PARIS).withName(VALID_NAME_TOKYO).build();
+ assertFalse(PARIS.equals(editedParis));
+
+ // different accommodation -> returns false
+ editedParis = new TripBuilder(PARIS).withAccommodation(VALID_ACCOMMODATION_TOKYO).build();
+ assertFalse(PARIS.equals(editedParis));
+
+ // different itinerary -> returns false
+ editedParis = new TripBuilder(PARIS).withItinerary(VALID_ITINERARY_TOKYO).build();
+ assertFalse(PARIS.equals(editedParis));
+
+ // different date -> returns false
+ editedParis = new TripBuilder(PARIS).withDate(VALID_DATE_TOKYO).build();
+ assertFalse(PARIS.equals(editedParis));
+
+ // different customer names -> returns false
+ editedParis = new TripBuilder(PARIS).withCustomerNames(VALID_CUSTOMER_BOB).build();
+ assertFalse(PARIS.equals(editedParis));
+
+ // different note -> returns false
+ editedParis = new TripBuilder(PARIS).withNote(VALID_NOTE_TOKYO).build();
+ assertFalse(PARIS.equals(editedParis));
+ }
+
+ @Test
+ public void toStringMethod() {
+ String expected = Trip.class.getCanonicalName() + "{name=" + PARIS.getName()
+ + ", accommodation=" + PARIS.getAccommodation()
+ + ", itinerary=" + PARIS.getItinerary()
+ + ", date=" + PARIS.getDate()
+ + ", customerNames=" + PARIS.getCustomerNames()
+ + ", note=" + PARIS.getNote() + "}";
+ assertEquals(expected, PARIS.toString());
+ }
+
+ @Test
+ public void toListString_validTrip_returnsCorrectFormat() {
+ String expectedParis = "Name = 'Paris 2025', "
+ + "Accommodation = 'Hotel 81', "
+ + "Itinerary = 'Eat baguettes', "
+ + "Date = '1/1/2100', "
+ + "Customer Names = '[John Doe, Jane Doe]'";
+
+
+
+ assertEquals(expectedParis, PARIS.toListString());
+ }
+}
diff --git a/src/test/java/seedu/address/model/trip/UniqueTripListTest.java b/src/test/java/seedu/address/model/trip/UniqueTripListTest.java
new file mode 100644
index 00000000000..99f9405e536
--- /dev/null
+++ b/src/test/java/seedu/address/model/trip/UniqueTripListTest.java
@@ -0,0 +1,153 @@
+package seedu.address.model.trip;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalTrips.PARIS;
+import static seedu.address.testutil.TypicalTrips.TOKYO;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.trip.exceptions.DuplicateTripException;
+import seedu.address.model.trip.exceptions.TripNotFoundException;
+import seedu.address.testutil.TripBuilder;
+
+public class UniqueTripListTest {
+ private final UniqueTripList uniqueTripList = new UniqueTripList();
+
+ @Test
+ public void contains_nullTrip_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueTripList.contains(null));
+ }
+
+ @Test
+ public void contains_tripNotInList_returnsFalse() {
+ assertFalse(uniqueTripList.contains(PARIS));
+ }
+
+ @Test
+ public void contains_tripInList_returnsTrue() {
+ uniqueTripList.add(PARIS);
+ assertTrue(uniqueTripList.contains(PARIS));
+ }
+
+ @Test
+ public void contains_tripWithSameIdentityFieldsInList_returnsTrue() {
+ uniqueTripList.add(PARIS);
+ Trip editedParis = new TripBuilder(PARIS).withAccommodation("Different Hotel")
+ .withItinerary("Different Itinerary").build();
+ assertTrue(uniqueTripList.contains(editedParis));
+ }
+
+ @Test
+ public void add_nullTrip_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueTripList.add(null));
+ }
+
+ @Test
+ public void add_duplicateTrip_throwsDuplicateTripException() {
+ uniqueTripList.add(PARIS);
+ assertThrows(DuplicateTripException.class, () -> uniqueTripList.add(PARIS));
+ }
+
+ @Test
+ public void setTrip_nullTargetTrip_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueTripList.setTrip(null, PARIS));
+ }
+
+ @Test
+ public void setTrip_nullEditedTrip_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueTripList.setTrip(PARIS, null));
+ }
+
+ @Test
+ public void setTrip_targetTripNotInList_throwsTripNotFoundException() {
+ assertThrows(TripNotFoundException.class, () -> uniqueTripList.setTrip(PARIS, PARIS));
+ }
+
+ @Test
+ public void setTrip_editedTripIsSameTrip_success() {
+ uniqueTripList.add(PARIS);
+ uniqueTripList.setTrip(PARIS, PARIS);
+ UniqueTripList expectedList = new UniqueTripList();
+ expectedList.add(PARIS);
+ assertEquals(expectedList, uniqueTripList);
+ }
+
+ @Test
+ public void setTrip_editedTripHasSameIdentity_success() {
+ uniqueTripList.add(PARIS);
+ Trip editedParis = new TripBuilder(PARIS).withAccommodation("Different Hotel")
+ .withItinerary("Different Itinerary").build();
+ uniqueTripList.setTrip(PARIS, editedParis);
+ UniqueTripList expectedList = new UniqueTripList();
+ expectedList.add(editedParis);
+ assertEquals(expectedList, uniqueTripList);
+ }
+
+ @Test
+ public void setTrip_editedTripHasDifferentIdentity_success() {
+ uniqueTripList.add(PARIS);
+ uniqueTripList.setTrip(PARIS, TOKYO);
+ UniqueTripList expectedList = new UniqueTripList();
+ expectedList.add(TOKYO);
+ assertEquals(expectedList, uniqueTripList);
+ }
+
+ @Test
+ public void setTrip_editedTripHasNonUniqueIdentity_throwsDuplicateTripException() {
+ uniqueTripList.add(PARIS);
+ uniqueTripList.add(TOKYO);
+ assertThrows(DuplicateTripException.class, () -> uniqueTripList.setTrip(PARIS, TOKYO));
+ }
+
+ @Test
+ public void remove_nullTrip_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueTripList.remove(null));
+ }
+
+ @Test
+ public void remove_tripDoesNotExist_throwsTripNotFoundException() {
+ assertThrows(TripNotFoundException.class, () -> uniqueTripList.remove(PARIS));
+ }
+
+ @Test
+ public void remove_existingTrip_removesTrip() {
+ uniqueTripList.add(PARIS);
+ uniqueTripList.remove(PARIS);
+ UniqueTripList expectedList = new UniqueTripList();
+ assertEquals(expectedList, uniqueTripList);
+ }
+
+ @Test
+ public void setTrips_nullList_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> uniqueTripList.setTrips((List) null));
+ }
+
+ @Test
+ public void setTrips_list_replacesOwnListWithProvidedList() {
+ uniqueTripList.add(PARIS);
+ List tripList = Collections.singletonList(TOKYO);
+ uniqueTripList.setTrips(tripList);
+ UniqueTripList expectedList = new UniqueTripList();
+ expectedList.add(TOKYO);
+ assertEquals(expectedList, uniqueTripList);
+ }
+
+ @Test
+ public void setTrips_listWithDuplicateTrips_throwsDuplicateTripException() {
+ List listWithDuplicateTrips = Arrays.asList(PARIS, PARIS);
+ assertThrows(DuplicateTripException.class, () -> uniqueTripList.setTrips(listWithDuplicateTrips));
+ }
+
+ @Test
+ public void asUnmodifiableObservableList_modifyList_throwsUnsupportedOperationException() {
+ assertThrows(UnsupportedOperationException.class, () ->
+ uniqueTripList.asUnmodifiableObservableList().remove(0));
+ }
+}
diff --git a/src/test/java/seedu/address/model/trip/exceptions/DuplicateTripExceptionTest.java b/src/test/java/seedu/address/model/trip/exceptions/DuplicateTripExceptionTest.java
new file mode 100644
index 00000000000..6c755cf6880
--- /dev/null
+++ b/src/test/java/seedu/address/model/trip/exceptions/DuplicateTripExceptionTest.java
@@ -0,0 +1,13 @@
+package seedu.address.model.trip.exceptions;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class DuplicateTripExceptionTest {
+ @Test
+ public void constructor_defaultMessage_messageMatchesExpected() {
+ DuplicateTripException exception = new DuplicateTripException();
+ assertEquals("Operation would result in duplicate trips", exception.getMessage());
+ }
+}
diff --git a/src/test/java/seedu/address/model/trip/exceptions/TripNotFoundExceptionTest.java b/src/test/java/seedu/address/model/trip/exceptions/TripNotFoundExceptionTest.java
new file mode 100644
index 00000000000..d5c3828fb2f
--- /dev/null
+++ b/src/test/java/seedu/address/model/trip/exceptions/TripNotFoundExceptionTest.java
@@ -0,0 +1,13 @@
+package seedu.address.model.trip.exceptions;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class TripNotFoundExceptionTest {
+ @Test
+ public void constructor_extendsRuntimeException_isRuntimeException() {
+ TripNotFoundException exception = new TripNotFoundException();
+ assertTrue(exception instanceof RuntimeException);
+ }
+}
diff --git a/src/test/java/seedu/address/model/util/DataLoadingUtilTest.java b/src/test/java/seedu/address/model/util/DataLoadingUtilTest.java
new file mode 100644
index 00000000000..e87b6084f68
--- /dev/null
+++ b/src/test/java/seedu/address/model/util/DataLoadingUtilTest.java
@@ -0,0 +1,200 @@
+package seedu.address.model.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.testutil.Assert.assertThrows;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.model.AddressBook;
+import seedu.address.model.ReadOnlyAddressBook;
+import seedu.address.model.ReadOnlyTripBook;
+import seedu.address.model.TripBook;
+import seedu.address.model.UserPrefs;
+import seedu.address.storage.AddressBookStorage;
+import seedu.address.storage.JsonAddressBookStorage;
+import seedu.address.storage.JsonTripBookStorage;
+import seedu.address.storage.JsonUserPrefsStorage;
+import seedu.address.storage.Storage;
+import seedu.address.storage.StorageManager;
+import seedu.address.storage.TripBookStorage;
+import seedu.address.storage.UserPrefsStorage;
+import seedu.address.testutil.TypicalTrips;
+
+public class DataLoadingUtilTest {
+ private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data", "DataLoadingUtilTest");
+
+ @TempDir
+ public Path testFolder;
+
+ private Storage storage;
+
+ @BeforeEach
+ public void setUp() {
+ AddressBookStorage addressBookStorage = new JsonAddressBookStorage(getTempFilePath("addressbook.json"));
+ UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(getTempFilePath("preferences.json"));
+ TripBookStorage tripBookStorage = new JsonTripBookStorage(getTempFilePath("tripbook.json"));
+ storage = new StorageManager(addressBookStorage, userPrefsStorage, tripBookStorage);
+ }
+
+ private Path getTempFilePath(String fileName) {
+ return testFolder.resolve(fileName);
+ }
+
+ @Test
+ public void loadAddressBook_nullStorage_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> DataLoadingUtil.loadAddressBook(null));
+ }
+
+ @Test
+ public void loadTripBook_nullStorage_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> DataLoadingUtil.loadTripBook(null));
+ }
+
+ @Test
+ public void loadAddressBook_missingFile_returnsSampleAddressBook() throws DataLoadingException {
+ ReadOnlyAddressBook loaded = DataLoadingUtil.loadAddressBook(storage);
+ assertEquals(SampleDataUtil.getSampleAddressBook(), loaded);
+ }
+
+ @Test
+ public void loadTripBook_missingFile_returnsEmptyTripBook() throws DataLoadingException {
+ ReadOnlyTripBook loaded = DataLoadingUtil.loadTripBook(storage);
+ assertEquals(SampleDataUtil.getSampleTripBook(), loaded);
+ }
+
+ @Test
+ public void loadAddressBook_invalidFile_returnsEmptyAddressBook() throws DataLoadingException {
+ // Create an invalid JSON file
+ try {
+ storage.saveAddressBook(new AddressBook(), getTempFilePath("addressbook.json"));
+ // Corrupt the file by writing invalid JSON
+ storage.saveUserPrefs(new UserPrefs());
+ } catch (IOException e) {
+ throw new AssertionError("Failed to create invalid file", e);
+ }
+
+ ReadOnlyAddressBook loaded = DataLoadingUtil.loadAddressBook(storage);
+ assertEquals(new AddressBook(), loaded);
+ }
+
+ @Test
+ public void loadTripBook_invalidFile_returnsEmptyTripBook() throws DataLoadingException {
+ // Create an invalid JSON file
+ try {
+ storage.saveTripBook(new TripBook(), getTempFilePath("tripbook.json"));
+ // Corrupt the file by writing invalid JSON
+ Files.write(getTempFilePath("tripbook.json"), "invalid json content".getBytes());
+ } catch (IOException e) {
+ throw new AssertionError("Failed to create invalid file", e);
+ }
+
+ ReadOnlyTripBook loaded = DataLoadingUtil.loadTripBook(storage);
+ assertEquals(new TripBook(), loaded);
+ }
+
+ @Test
+ public void loadAddressBook_validFile_returnsLoadedAddressBook() throws DataLoadingException, IOException {
+ ReadOnlyAddressBook expected = SampleDataUtil.getSampleAddressBook();
+ storage.saveAddressBook(new AddressBook(expected), getTempFilePath("addressbook.json"));
+
+ ReadOnlyAddressBook loaded = DataLoadingUtil.loadAddressBook(storage);
+ assertEquals(expected, loaded);
+ }
+
+ @Test
+ public void loadTripBook_validFile_returnsLoadedTripBook() throws DataLoadingException, IOException {
+ TripBook expected = TypicalTrips.getTypicalTripBook();
+ storage.saveTripBook(expected, getTempFilePath("tripbook.json"));
+
+ ReadOnlyTripBook loaded = DataLoadingUtil.loadTripBook(storage);
+ assertEquals(expected, loaded);
+ }
+
+ @Test
+ public void loadAddressBook_emptyFile_returnsEmptyAddressBook() throws DataLoadingException, IOException {
+ // Create an empty file
+ Files.write(getTempFilePath("addressbook.json"), "{}".getBytes());
+
+ ReadOnlyAddressBook loaded = DataLoadingUtil.loadAddressBook(storage);
+ assertEquals(new AddressBook(), loaded);
+ }
+
+ @Test
+ public void loadTripBook_emptyFile_returnsEmptyTripBook() throws DataLoadingException, IOException {
+ // Create an empty file
+ storage.saveTripBook(new TripBook(), getTempFilePath("tripbook.json"));
+ // Clear the file
+ storage.saveTripBook(new TripBook(), getTempFilePath("tripbook.json"));
+
+ ReadOnlyTripBook loaded = DataLoadingUtil.loadTripBook(storage);
+ assertEquals(new TripBook(), loaded);
+ }
+
+ @Test
+ public void loadAddressBook_corruptedFile_returnsEmptyAddressBook() throws DataLoadingException, IOException {
+ // Create a corrupted file with invalid JSON
+ try {
+ storage.saveAddressBook(new AddressBook(), getTempFilePath("addressbook.json"));
+ // Write invalid JSON directly to the file
+ Files.write(getTempFilePath("addressbook.json"), "invalid json content".getBytes());
+ } catch (IOException e) {
+ throw new AssertionError("Failed to create corrupted file", e);
+ }
+
+ ReadOnlyAddressBook loaded = DataLoadingUtil.loadAddressBook(storage);
+ assertEquals(new AddressBook(), loaded);
+ }
+
+ @Test
+ public void loadTripBook_corruptedFile_returnsEmptyTripBook() throws DataLoadingException, IOException {
+ // Create a corrupted file with invalid JSON
+ try {
+ storage.saveTripBook(new TripBook(), getTempFilePath("tripbook.json"));
+ // Write invalid JSON directly to the file
+ Files.write(getTempFilePath("tripbook.json"), "invalid json content".getBytes());
+ } catch (IOException e) {
+ throw new AssertionError("Failed to create corrupted file", e);
+ }
+
+ ReadOnlyTripBook loaded = DataLoadingUtil.loadTripBook(storage);
+ assertEquals(new TripBook(), loaded);
+ }
+
+ @Test
+ public void loadAddressBook_malformedJson_returnsEmptyAddressBook() throws DataLoadingException, IOException {
+ // Create a file with malformed JSON
+ try {
+ storage.saveAddressBook(new AddressBook(), getTempFilePath("addressbook.json"));
+ // Write malformed JSON
+ Files.write(getTempFilePath("addressbook.json"), "{ \"addressbook\": { \"persons\": [ { } } }".getBytes());
+ } catch (IOException e) {
+ throw new AssertionError("Failed to create malformed JSON file", e);
+ }
+
+ ReadOnlyAddressBook loaded = DataLoadingUtil.loadAddressBook(storage);
+ assertEquals(new AddressBook(), loaded);
+ }
+
+ @Test
+ public void loadTripBook_malformedJson_returnsEmptyTripBook() throws DataLoadingException, IOException {
+ // Create a file with malformed JSON
+ try {
+ storage.saveTripBook(new TripBook(), getTempFilePath("tripbook.json"));
+ // Write malformed JSON
+ Files.write(getTempFilePath("tripbook.json"), "{ \"tripbook\": { \"trips\": [ { } } }".getBytes());
+ } catch (IOException e) {
+ throw new AssertionError("Failed to create malformed JSON file", e);
+ }
+
+ ReadOnlyTripBook loaded = DataLoadingUtil.loadTripBook(storage);
+ assertEquals(new TripBook(), loaded);
+ }
+}
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java b/src/test/java/seedu/address/storage/JsonAdaptedContactTest.java
similarity index 79%
rename from src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
rename to src/test/java/seedu/address/storage/JsonAdaptedContactTest.java
index 83b11331cdb..3e9c4416cb2 100644
--- a/src/test/java/seedu/address/storage/JsonAdaptedPersonTest.java
+++ b/src/test/java/seedu/address/storage/JsonAdaptedContactTest.java
@@ -12,12 +12,12 @@
import org.junit.jupiter.api.Test;
import seedu.address.commons.exceptions.IllegalValueException;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Phone;
+import seedu.address.model.contact.Address;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Name;
+import seedu.address.model.contact.Phone;
-public class JsonAdaptedPersonTest {
+public class JsonAdaptedContactTest {
private static final String INVALID_NAME = "R@chel";
private static final String INVALID_PHONE = "+651234";
private static final String INVALID_ADDRESS = " ";
@@ -31,6 +31,7 @@ public class JsonAdaptedPersonTest {
private static final List VALID_TAGS = BENSON.getTags().stream()
.map(JsonAdaptedTag::new)
.collect(Collectors.toList());
+ private static final String VALID_NOTE = BENSON.getNote().toString();
@Test
public void toModelType_validPersonDetails_returnsPerson() throws Exception {
@@ -41,14 +42,15 @@ public void toModelType_validPersonDetails_returnsPerson() throws Exception {
@Test
public void toModelType_invalidName_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(INVALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS, VALID_NOTE);
String expectedMessage = Name.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullName_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(
+ null, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS, VALID_NOTE);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -56,14 +58,15 @@ public void toModelType_nullName_throwsIllegalValueException() {
@Test
public void toModelType_invalidPhone_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, INVALID_PHONE, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS, VALID_NOTE);
String expectedMessage = Phone.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullPhone_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(
+ VALID_NAME, null, VALID_EMAIL, VALID_ADDRESS, VALID_TAGS, VALID_NOTE);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -71,14 +74,15 @@ public void toModelType_nullPhone_throwsIllegalValueException() {
@Test
public void toModelType_invalidEmail_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, INVALID_EMAIL, VALID_ADDRESS, VALID_TAGS, VALID_NOTE);
String expectedMessage = Email.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullEmail_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(
+ VALID_NAME, VALID_PHONE, null, VALID_ADDRESS, VALID_TAGS, VALID_NOTE);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -86,14 +90,15 @@ public void toModelType_nullEmail_throwsIllegalValueException() {
@Test
public void toModelType_invalidAddress_throwsIllegalValueException() {
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, INVALID_ADDRESS, VALID_TAGS, VALID_NOTE);
String expectedMessage = Address.MESSAGE_CONSTRAINTS;
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@Test
public void toModelType_nullAddress_throwsIllegalValueException() {
- JsonAdaptedPerson person = new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS);
+ JsonAdaptedPerson person = new JsonAdaptedPerson(
+ VALID_NAME, VALID_PHONE, VALID_EMAIL, null, VALID_TAGS, VALID_NOTE);
String expectedMessage = String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName());
assertThrows(IllegalValueException.class, expectedMessage, person::toModelType);
}
@@ -103,7 +108,7 @@ public void toModelType_invalidTags_throwsIllegalValueException() {
List invalidTags = new ArrayList<>(VALID_TAGS);
invalidTags.add(new JsonAdaptedTag(INVALID_TAG));
JsonAdaptedPerson person =
- new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags);
+ new JsonAdaptedPerson(VALID_NAME, VALID_PHONE, VALID_EMAIL, VALID_ADDRESS, invalidTags, VALID_NOTE);
assertThrows(IllegalValueException.class, person::toModelType);
}
diff --git a/src/test/java/seedu/address/storage/JsonAdaptedTripTest.java b/src/test/java/seedu/address/storage/JsonAdaptedTripTest.java
new file mode 100644
index 00000000000..27b986aecd4
--- /dev/null
+++ b/src/test/java/seedu/address/storage/JsonAdaptedTripTest.java
@@ -0,0 +1,126 @@
+package seedu.address.storage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalTrips.PARIS;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.contact.Name;
+import seedu.address.model.trip.Accommodation;
+import seedu.address.model.trip.Itinerary;
+import seedu.address.model.trip.Note;
+import seedu.address.model.trip.TripDate;
+import seedu.address.model.trip.TripName;
+
+public class JsonAdaptedTripTest {
+ private static final String INVALID_NAME = "R@chel";
+ private static final String INVALID_ACCOMMODATION = " ";
+ private static final String INVALID_ITINERARY = " ";
+ private static final String INVALID_DATE = "1/1/202";
+ private static final String INVALID_CUSTOMER_NAME = "R@chel";
+
+ private static final String VALID_NAME = PARIS.getName().toString();
+ private static final String VALID_ACCOMMODATION = PARIS.getAccommodation().toString();
+ private static final String VALID_ITINERARY = PARIS.getItinerary().toString();
+ private static final String VALID_DATE = PARIS.getDate().toString();
+ private static final List VALID_CUSTOMER_NAMES = PARIS.getCustomerNames().stream()
+ .map(Name::toString)
+ .toList();
+ private static final String VALID_NOTE = PARIS.getNote().toString();
+
+ @Test
+ public void toModelType_validTripDetails_returnsTrip() throws Exception {
+ JsonAdaptedTrip trip = new JsonAdaptedTrip(PARIS);
+ assertEquals(PARIS, trip.toModelType());
+ }
+
+ @Test
+ public void toModelType_invalidName_throwsIllegalValueException() {
+ JsonAdaptedTrip trip = new JsonAdaptedTrip(INVALID_NAME, VALID_ACCOMMODATION, VALID_ITINERARY,
+ VALID_DATE, VALID_CUSTOMER_NAMES, VALID_NOTE);
+ String expectedMessage = TripName.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, trip::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullName_throwsIllegalValueException() {
+ JsonAdaptedTrip trip = new JsonAdaptedTrip(null, VALID_ACCOMMODATION, VALID_ITINERARY,
+ VALID_DATE, VALID_CUSTOMER_NAMES, VALID_NOTE);
+ String expectedMessage = String.format(JsonAdaptedTrip.MISSING_FIELD_MESSAGE_FORMAT,
+ TripName.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, trip::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidAccommodation_throwsIllegalValueException() {
+ JsonAdaptedTrip trip = new JsonAdaptedTrip(VALID_NAME, INVALID_ACCOMMODATION, VALID_ITINERARY,
+ VALID_DATE, VALID_CUSTOMER_NAMES, VALID_NOTE);
+ String expectedMessage = Accommodation.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, trip::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullAccommodation_throwsIllegalValueException() {
+ JsonAdaptedTrip trip = new JsonAdaptedTrip(VALID_NAME, null, VALID_ITINERARY,
+ VALID_DATE, VALID_CUSTOMER_NAMES, VALID_NOTE);
+ String expectedMessage = String.format(JsonAdaptedTrip.MISSING_FIELD_MESSAGE_FORMAT,
+ Accommodation.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, trip::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidItinerary_throwsIllegalValueException() {
+ JsonAdaptedTrip trip = new JsonAdaptedTrip(VALID_NAME, VALID_ACCOMMODATION, INVALID_ITINERARY,
+ VALID_DATE, VALID_CUSTOMER_NAMES, VALID_NOTE);
+ String expectedMessage = Itinerary.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, trip::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullItinerary_throwsIllegalValueException() {
+ JsonAdaptedTrip trip = new JsonAdaptedTrip(VALID_NAME, VALID_ACCOMMODATION, null,
+ VALID_DATE, VALID_CUSTOMER_NAMES, VALID_NOTE);
+ String expectedMessage = String.format(JsonAdaptedTrip.MISSING_FIELD_MESSAGE_FORMAT,
+ Itinerary.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, trip::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidDate_throwsIllegalValueException() {
+ JsonAdaptedTrip trip = new JsonAdaptedTrip(VALID_NAME, VALID_ACCOMMODATION, VALID_ITINERARY,
+ INVALID_DATE, VALID_CUSTOMER_NAMES, VALID_NOTE);
+ String expectedMessage = TripDate.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, trip::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullDate_throwsIllegalValueException() {
+ JsonAdaptedTrip trip = new JsonAdaptedTrip(VALID_NAME, VALID_ACCOMMODATION, VALID_ITINERARY,
+ null, VALID_CUSTOMER_NAMES, VALID_NOTE);
+ String expectedMessage = String.format(JsonAdaptedTrip.MISSING_FIELD_MESSAGE_FORMAT,
+ TripDate.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, trip::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidCustomerName_throwsIllegalValueException() {
+ List invalidCustomerNames = List.of(INVALID_CUSTOMER_NAME);
+ JsonAdaptedTrip trip = new JsonAdaptedTrip(VALID_NAME, VALID_ACCOMMODATION, VALID_ITINERARY,
+ VALID_DATE, invalidCustomerNames, VALID_NOTE);
+ String expectedMessage = Name.MESSAGE_CONSTRAINTS;
+ assertThrows(IllegalValueException.class, expectedMessage, trip::toModelType);
+ }
+
+ @Test
+ public void toModelType_nullNote_throwsIllegalValueException() {
+ JsonAdaptedTrip trip = new JsonAdaptedTrip(VALID_NAME, VALID_ACCOMMODATION, VALID_ITINERARY,
+ VALID_DATE, VALID_CUSTOMER_NAMES, null);
+ String expectedMessage = String.format(JsonAdaptedTrip.MISSING_FIELD_MESSAGE_FORMAT,
+ Note.class.getSimpleName());
+ assertThrows(IllegalValueException.class, expectedMessage, trip::toModelType);
+ }
+}
diff --git a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java
index 4e5ce9200c8..95f3f3bfd30 100644
--- a/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java
+++ b/src/test/java/seedu/address/storage/JsonAddressBookStorageTest.java
@@ -64,18 +64,18 @@ public void readAddressBook_invalidAndValidPersonAddressBook_throwDataLoadingExc
public void readAndSaveAddressBook_allInOrder_success() throws Exception {
Path filePath = testFolder.resolve("TempAddressBook.json");
AddressBook original = getTypicalAddressBook();
- JsonAddressBookStorage jsonAddressBookStorage = new JsonAddressBookStorage(filePath);
// Save in new file and read back
- jsonAddressBookStorage.saveAddressBook(original, filePath);
- ReadOnlyAddressBook readBack = jsonAddressBookStorage.readAddressBook(filePath).get();
+ JsonAddressBookStorage jsonAddressBookStorage = new JsonAddressBookStorage(filePath);
+ jsonAddressBookStorage.saveAddressBook(original);
+ ReadOnlyAddressBook readBack = jsonAddressBookStorage.readAddressBook().get();
assertEquals(original, new AddressBook(readBack));
// Modify data, overwrite exiting file, and read back
original.addPerson(HOON);
- original.removePerson(ALICE);
- jsonAddressBookStorage.saveAddressBook(original, filePath);
- readBack = jsonAddressBookStorage.readAddressBook(filePath).get();
+ original.removeContact(ALICE);
+ jsonAddressBookStorage.saveAddressBook(original);
+ readBack = jsonAddressBookStorage.readAddressBook().get();
assertEquals(original, new AddressBook(readBack));
// Save and read without specifying file path
@@ -83,7 +83,6 @@ public void readAndSaveAddressBook_allInOrder_success() throws Exception {
jsonAddressBookStorage.saveAddressBook(original); // file path not specified
readBack = jsonAddressBookStorage.readAddressBook().get(); // file path not specified
assertEquals(original, new AddressBook(readBack));
-
}
@Test
@@ -91,20 +90,29 @@ public void saveAddressBook_nullAddressBook_throwsNullPointerException() {
assertThrows(NullPointerException.class, () -> saveAddressBook(null, "SomeFile.json"));
}
- /**
- * Saves {@code addressBook} at the specified {@code filePath}.
- */
+ @Test
+ public void saveAddressBook_nullFilePath_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> saveAddressBook(new AddressBook(), null));
+ }
+
+ @Test
+ public void saveAddressBook_validAddressBookValidPath_success() throws Exception {
+ AddressBook addressBook = new AddressBook();
+ addressBook.addPerson(ALICE);
+
+ JsonAddressBookStorage jsonAddressBookStorage = new JsonAddressBookStorage(
+ testFolder.resolve("validBook.json"));
+ jsonAddressBookStorage.saveAddressBook(addressBook);
+ ReadOnlyAddressBook readBack = jsonAddressBookStorage.readAddressBook().get();
+ assertEquals(addressBook, new AddressBook(readBack));
+ }
+
private void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) {
try {
- new JsonAddressBookStorage(Paths.get(filePath))
- .saveAddressBook(addressBook, addToTestDataPathIfNotNull(filePath));
+ new JsonAddressBookStorage(addToTestDataPathIfNotNull(filePath))
+ .saveAddressBook(addressBook);
} catch (IOException ioe) {
throw new AssertionError("There should not be an error writing to the file.", ioe);
}
}
-
- @Test
- public void saveAddressBook_nullFilePath_throwsNullPointerException() {
- assertThrows(NullPointerException.class, () -> saveAddressBook(new AddressBook(), null));
- }
}
diff --git a/src/test/java/seedu/address/storage/JsonSerializableTripBookTest.java b/src/test/java/seedu/address/storage/JsonSerializableTripBookTest.java
new file mode 100644
index 00000000000..10c8078501f
--- /dev/null
+++ b/src/test/java/seedu/address/storage/JsonSerializableTripBookTest.java
@@ -0,0 +1,44 @@
+package seedu.address.storage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalTrips.PARIS;
+import static seedu.address.testutil.TypicalTrips.TOKYO;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.commons.exceptions.IllegalValueException;
+import seedu.address.model.TripBook;
+
+public class JsonSerializableTripBookTest {
+ private static final List VALID_TRIPS = List.of(
+ new JsonAdaptedTrip(PARIS),
+ new JsonAdaptedTrip(TOKYO));
+
+ @Test
+ public void toModelType_validTripBook_returnsTripBook() throws Exception {
+ JsonSerializableTripBook dataFromFile = new JsonSerializableTripBook(VALID_TRIPS);
+ TripBook tripBookFromFile = dataFromFile.toModelType();
+ TripBook typicalTripBook = new TripBook();
+ typicalTripBook.addTrip(PARIS);
+ typicalTripBook.addTrip(TOKYO);
+ assertEquals(tripBookFromFile, typicalTripBook);
+ }
+
+ @Test
+ public void toModelType_duplicateTrips_throwsIllegalValueException() {
+ JsonSerializableTripBook dataFromFile = new JsonSerializableTripBook(
+ List.of(new JsonAdaptedTrip(PARIS), new JsonAdaptedTrip(PARIS)));
+ assertThrows(IllegalValueException.class, JsonSerializableTripBook.MESSAGE_DUPLICATE_TRIP,
+ dataFromFile::toModelType);
+ }
+
+ @Test
+ public void toModelType_invalidTrip_throwsIllegalValueException() {
+ JsonAdaptedTrip invalidTrip = new JsonAdaptedTrip("", "", "", "", List.of(), "");
+ JsonSerializableTripBook dataFromFile = new JsonSerializableTripBook(List.of(invalidTrip));
+ assertThrows(IllegalValueException.class, dataFromFile::toModelType);
+ }
+}
diff --git a/src/test/java/seedu/address/storage/JsonTripBookStorageTest.java b/src/test/java/seedu/address/storage/JsonTripBookStorageTest.java
new file mode 100644
index 00000000000..b1b8a27464d
--- /dev/null
+++ b/src/test/java/seedu/address/storage/JsonTripBookStorageTest.java
@@ -0,0 +1,143 @@
+package seedu.address.storage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static seedu.address.testutil.Assert.assertThrows;
+import static seedu.address.testutil.TypicalTrips.PARIS;
+import static seedu.address.testutil.TypicalTrips.TOKYO;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import seedu.address.commons.exceptions.DataLoadingException;
+import seedu.address.model.ReadOnlyTripBook;
+import seedu.address.model.TripBook;
+
+public class JsonTripBookStorageTest {
+ private static final Path TEST_DATA_FOLDER = Paths.get("src", "test", "data",
+ JsonTripBookStorageTest.class.getSimpleName());
+ private static final Path TYPICAL_TRIPS_FILE = TEST_DATA_FOLDER.resolve("typicalTrips.json");
+ private static final Path INVALID_TRIP_FILE = TEST_DATA_FOLDER.resolve("invalidTrip.json");
+ private static final Path NONEXISTENT_FILE = TEST_DATA_FOLDER.resolve("nonexistent.json");
+
+ @TempDir
+ public Path testFolder;
+
+ private JsonTripBookStorage jsonTripBookStorage;
+
+ private static Path addToTestDataPathIfNotNull(String prefsFileInTestDataFolder) {
+ return prefsFileInTestDataFolder != null
+ ? Paths.get("src", "test", "data", JsonTripBookStorageTest.class.getSimpleName(),
+ prefsFileInTestDataFolder)
+ : null;
+ }
+
+ @BeforeEach
+ public void setUp() {
+ jsonTripBookStorage = new JsonTripBookStorage(getTempFilePath("ab"));
+ }
+
+ private Path getTempFilePath(String fileName) {
+ return testFolder.resolve(fileName);
+ }
+
+ @Test
+ public void readTripBook_nullFilePath_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> readTripBook(null));
+ }
+
+ private java.util.Optional readTripBook(String filePath) throws DataLoadingException {
+ return jsonTripBookStorage.readTripBook(addToTestDataPathIfNotNull(filePath));
+ }
+
+ @Test
+ public void read_missingFile_emptyResult() throws DataLoadingException {
+ assertFalse(readTripBook("NonExistentFile.json").isPresent());
+ }
+
+ @Test
+ public void read_notJsonFormat_exceptionThrown() {
+ assertThrows(DataLoadingException.class, () -> readTripBook("notJsonFormat.json"));
+ }
+
+ @Test
+ public void readTripBook_invalidTripFile_throwsDataLoadingException() {
+ assertThrows(DataLoadingException.class, () -> readTripBook("invalidTrip.json"));
+ }
+
+ @Test
+ public void readTripBook_invalidAndValidTripFile_throwsDataLoadingException() {
+ assertThrows(DataLoadingException.class, () -> readTripBook("invalidAndValidTrip.json"));
+ }
+
+ @Test
+ public void readAndSaveTripBook_allInOrder_success() throws Exception {
+ Path filePath = testFolder.resolve("TempTripBook.json");
+ TripBook original = getTypicalTripBook();
+ JsonTripBookStorage jsonTripBookStorage = new JsonTripBookStorage(filePath);
+
+ // Save in new file and read back
+ jsonTripBookStorage.saveTripBook(original, filePath);
+ ReadOnlyTripBook readBack = jsonTripBookStorage.readTripBook(filePath).get();
+ assertEquals(original, new TripBook(readBack));
+
+ // Modify data, overwrite exiting file, and read back
+ original.addTrip(TOKYO);
+ original.removeTrip(PARIS);
+ jsonTripBookStorage.saveTripBook(original, filePath);
+ readBack = jsonTripBookStorage.readTripBook(filePath).get();
+ assertEquals(original, new TripBook(readBack));
+
+ // Save and read without specifying file path
+ original.addTrip(PARIS);
+ jsonTripBookStorage.saveTripBook(original); // file path not specified
+ readBack = jsonTripBookStorage.readTripBook().get(); // file path not specified
+ assertEquals(original, new TripBook(readBack));
+ }
+
+ @Test
+ public void saveTripBook_nullTripBook_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> saveTripBook(null, "SomeFile.json"));
+ }
+
+ @Test
+ public void saveTripBook_nullFilePath_throwsNullPointerException() {
+ assertThrows(NullPointerException.class, () -> saveTripBook(new TripBook(), null));
+ }
+
+ private void saveTripBook(ReadOnlyTripBook tripBook, String filePath) {
+ try {
+ jsonTripBookStorage.saveTripBook(tripBook, addToTestDataPathIfNotNull(filePath));
+ } catch (IOException ioe) {
+ throw new AssertionError("There should not be an error writing to the file.", ioe);
+ }
+ }
+
+ @Test
+ public void saveTripBook_allInOrder_success() throws Exception {
+ TripBook original = getTypicalTripBook();
+ Path filePath = testFolder.resolve("TempTripBook.json");
+ JsonTripBookStorage jsonTripBookStorage = new JsonTripBookStorage(filePath);
+
+ // Try writing when the file doesn't exist
+ jsonTripBookStorage.saveTripBook(original, filePath);
+ ReadOnlyTripBook readBack = jsonTripBookStorage.readTripBook(filePath).get();
+ assertEquals(original, new TripBook(readBack));
+
+ // Try reading when there is an existing file with the same data
+ jsonTripBookStorage.saveTripBook(readBack, filePath);
+ ReadOnlyTripBook readBackAgain = jsonTripBookStorage.readTripBook(filePath).get();
+ assertEquals(original, new TripBook(readBackAgain));
+ }
+
+ private TripBook getTypicalTripBook() {
+ TripBook ab = new TripBook();
+ ab.addTrip(PARIS);
+ return ab;
+ }
+}
diff --git a/src/test/java/seedu/address/storage/StorageManagerTest.java b/src/test/java/seedu/address/storage/StorageManagerTest.java
index 99a16548970..bb42c827c00 100644
--- a/src/test/java/seedu/address/storage/StorageManagerTest.java
+++ b/src/test/java/seedu/address/storage/StorageManagerTest.java
@@ -26,7 +26,8 @@ public class StorageManagerTest {
public void setUp() {
JsonAddressBookStorage addressBookStorage = new JsonAddressBookStorage(getTempFilePath("ab"));
JsonUserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(getTempFilePath("prefs"));
- storageManager = new StorageManager(addressBookStorage, userPrefsStorage);
+ JsonTripBookStorage tripBookStorage = new JsonTripBookStorage(getTempFilePath("trips"));
+ storageManager = new StorageManager(addressBookStorage, userPrefsStorage, tripBookStorage);
}
private Path getTempFilePath(String fileName) {
diff --git a/src/test/java/seedu/address/testutil/AddressBookBuilder.java b/src/test/java/seedu/address/testutil/AddressBookBuilder.java
index d53799fd110..49a249f7443 100644
--- a/src/test/java/seedu/address/testutil/AddressBookBuilder.java
+++ b/src/test/java/seedu/address/testutil/AddressBookBuilder.java
@@ -1,7 +1,7 @@
package seedu.address.testutil;
import seedu.address.model.AddressBook;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
/**
* A utility class to help with building Addressbook objects.
@@ -21,10 +21,10 @@ public AddressBookBuilder(AddressBook addressBook) {
}
/**
- * Adds a new {@code Person} to the {@code AddressBook} that we are building.
+ * Adds a new {@code Contact} to the {@code AddressBook} that we are building.
*/
- public AddressBookBuilder withPerson(Person person) {
- addressBook.addPerson(person);
+ public AddressBookBuilder withPerson(Contact contact) {
+ addressBook.addPerson(contact);
return this;
}
diff --git a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
index 4584bd5044e..16a3abb39df 100644
--- a/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
+++ b/src/test/java/seedu/address/testutil/EditPersonDescriptorBuilder.java
@@ -4,12 +4,13 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
+import seedu.address.logic.commands.EditContactCommand.EditPersonDescriptor;
+import seedu.address.model.contact.Address;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Name;
+import seedu.address.model.contact.Note;
+import seedu.address.model.contact.Phone;
import seedu.address.model.tag.Tag;
/**
@@ -28,15 +29,16 @@ public EditPersonDescriptorBuilder(EditPersonDescriptor descriptor) {
}
/**
- * Returns an {@code EditPersonDescriptor} with fields containing {@code person}'s details
+ * Returns an {@code EditPersonDescriptor} with fields containing {@code contact}'s details
*/
- public EditPersonDescriptorBuilder(Person person) {
+ public EditPersonDescriptorBuilder(Contact contact) {
descriptor = new EditPersonDescriptor();
- descriptor.setName(person.getName());
- descriptor.setPhone(person.getPhone());
- descriptor.setEmail(person.getEmail());
- descriptor.setAddress(person.getAddress());
- descriptor.setTags(person.getTags());
+ descriptor.setName(contact.getName());
+ descriptor.setPhone(contact.getPhone());
+ descriptor.setEmail(contact.getEmail());
+ descriptor.setAddress(contact.getAddress());
+ descriptor.setTags(contact.getTags());
+ descriptor.setNote(contact.getNote());
}
/**
@@ -81,6 +83,14 @@ public EditPersonDescriptorBuilder withTags(String... tags) {
return this;
}
+ /**
+ * Sets the {@code Note} of the {@code EditPersonDescriptor} that we are building.
+ */
+ public EditPersonDescriptorBuilder withNote(Note note) {
+ descriptor.setNote(note);
+ return this;
+ }
+
public EditPersonDescriptor build() {
return descriptor;
}
diff --git a/src/test/java/seedu/address/testutil/EditTripDescriptorBuilder.java b/src/test/java/seedu/address/testutil/EditTripDescriptorBuilder.java
new file mode 100644
index 00000000000..8c282225b53
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/EditTripDescriptorBuilder.java
@@ -0,0 +1,106 @@
+package seedu.address.testutil;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import seedu.address.logic.commands.EditTripCommand.EditTripDescriptor;
+import seedu.address.model.contact.Name;
+import seedu.address.model.trip.Accommodation;
+import seedu.address.model.trip.Itinerary;
+import seedu.address.model.trip.Note;
+import seedu.address.model.trip.Trip;
+import seedu.address.model.trip.TripDate;
+import seedu.address.model.trip.TripName;
+
+/**
+ * A utility class to help with building EditTripDescriptor objects.
+ */
+public class EditTripDescriptorBuilder {
+
+ private EditTripDescriptor descriptor;
+
+ public EditTripDescriptorBuilder() {
+ descriptor = new EditTripDescriptor();
+ }
+
+ public EditTripDescriptorBuilder(EditTripDescriptor descriptor) {
+ this.descriptor = new EditTripDescriptor(descriptor);
+ }
+
+ /**
+ * Returns an {@code EditTripDescriptor} with fields containing {@code trip}'s details
+ */
+ public EditTripDescriptorBuilder(Trip trip) {
+ descriptor = new EditTripDescriptor();
+ descriptor.setName(trip.getName());
+ descriptor.setAccommodation(trip.getAccommodation());
+ descriptor.setItinerary(trip.getItinerary());
+ descriptor.setDate(trip.getDate());
+ descriptor.setCustomerNames(trip.getCustomerNames());
+ descriptor.setNote(trip.getNote());
+ }
+
+ /**
+ * Sets the {@code TripName} of the {@code EditTripDescriptor} that we are building.
+ */
+ public EditTripDescriptorBuilder withName(String name) {
+ descriptor.setName(new TripName(name));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Accommodation} of the {@code EditTripDescriptor} that we are building.
+ */
+ public EditTripDescriptorBuilder withAccommodation(String accommodation) {
+ descriptor.setAccommodation(new Accommodation(accommodation));
+ return this;
+ }
+
+ /**
+ * Sets the {@code Itinerary} of the {@code EditTripDescriptor} that we are building.
+ */
+ public EditTripDescriptorBuilder withItinerary(String itinerary) {
+ descriptor.setItinerary(new Itinerary(itinerary));
+ return this;
+ }
+
+ /**
+ * Sets the {@code TripDate} of the {@code EditTripDescriptor} that we are building.
+ */
+ public EditTripDescriptorBuilder withDate(String date) {
+ descriptor.setDate(new TripDate(date));
+ return this;
+ }
+
+ /**
+ * Parses the {@code customerNames} into a {@code Set} and set it to the {@code EditTripDescriptor}
+ * that we are building.
+ */
+ public EditTripDescriptorBuilder withCustomerNames(String... customerNames) {
+ Set nameSet = Stream.of(customerNames).map(Name::new).collect(Collectors.toSet());
+ descriptor.setCustomerNames(nameSet);
+ return this;
+ }
+
+ /**
+ * Sets the {@code customerNames} to empty in the {@code EditTripDescriptor} that we are building.
+ */
+ public EditTripDescriptorBuilder withCustomerNames() {
+ descriptor.setCustomerNames(new HashSet<>());
+ return this;
+ }
+
+ /**
+ * Sets the {@code Note} of the {@code EditTripDescriptor} that we are building.
+ */
+ public EditTripDescriptorBuilder withNote(String note) {
+ descriptor.setNote(new Note(note));
+ return this;
+ }
+
+ public EditTripDescriptor build() {
+ return descriptor;
+ }
+}
diff --git a/src/test/java/seedu/address/testutil/PersonBuilder.java b/src/test/java/seedu/address/testutil/PersonBuilder.java
index 6be381d39ba..fcaf6db0f0f 100644
--- a/src/test/java/seedu/address/testutil/PersonBuilder.java
+++ b/src/test/java/seedu/address/testutil/PersonBuilder.java
@@ -3,16 +3,17 @@
import java.util.HashSet;
import java.util.Set;
-import seedu.address.model.person.Address;
-import seedu.address.model.person.Email;
-import seedu.address.model.person.Name;
-import seedu.address.model.person.Person;
-import seedu.address.model.person.Phone;
+import seedu.address.model.contact.Address;
+import seedu.address.model.contact.Contact;
+import seedu.address.model.contact.Email;
+import seedu.address.model.contact.Name;
+import seedu.address.model.contact.Note;
+import seedu.address.model.contact.Phone;
import seedu.address.model.tag.Tag;
import seedu.address.model.util.SampleDataUtil;
/**
- * A utility class to help with building Person objects.
+ * A utility class to help with building Contact objects.
*/
public class PersonBuilder {
@@ -20,12 +21,14 @@ public class PersonBuilder {
public static final String DEFAULT_PHONE = "85355255";
public static final String DEFAULT_EMAIL = "amy@gmail.com";
public static final String DEFAULT_ADDRESS = "123, Jurong West Ave 6, #08-111";
+ public static final String DEFAULT_NOTE = "";
private Name name;
private Phone phone;
private Email email;
private Address address;
private Set tags;
+ private Note note;
/**
* Creates a {@code PersonBuilder} with the default details.
@@ -35,22 +38,24 @@ public PersonBuilder() {
phone = new Phone(DEFAULT_PHONE);
email = new Email(DEFAULT_EMAIL);
address = new Address(DEFAULT_ADDRESS);
- tags = new HashSet<>();
+ tags = SampleDataUtil.getTagSet("customer");
+ note = new Note(DEFAULT_NOTE);
}
/**
- * Initializes the PersonBuilder with the data of {@code personToCopy}.
+ * Initializes the PersonBuilder with the data of {@code contactToCopy}.
*/
- public PersonBuilder(Person personToCopy) {
- name = personToCopy.getName();
- phone = personToCopy.getPhone();
- email = personToCopy.getEmail();
- address = personToCopy.getAddress();
- tags = new HashSet<>(personToCopy.getTags());
+ public PersonBuilder(Contact contactToCopy) {
+ name = contactToCopy.getName();
+ phone = contactToCopy.getPhone();
+ email = contactToCopy.getEmail();
+ address = contactToCopy.getAddress();
+ tags = new HashSet<>(contactToCopy.getTags());
+ note = contactToCopy.getNote();
}
/**
- * Sets the {@code Name} of the {@code Person} that we are building.
+ * Sets the {@code Name} of the {@code Contact} that we are building.
*/
public PersonBuilder withName(String name) {
this.name = new Name(name);
@@ -58,7 +63,7 @@ public PersonBuilder withName(String name) {
}
/**
- * Parses the {@code tags} into a {@code Set} and set it to the {@code Person} that we are building.
+ * Parses the {@code tags} into a {@code Set} and set it to the {@code Contact} that we are building.
*/
public PersonBuilder withTags(String ... tags) {
this.tags = SampleDataUtil.getTagSet(tags);
@@ -66,7 +71,7 @@ public PersonBuilder withTags(String ... tags) {
}
/**
- * Sets the {@code Address} of the {@code Person} that we are building.
+ * Sets the {@code Address} of the {@code Contact} that we are building.
*/
public PersonBuilder withAddress(String address) {
this.address = new Address(address);
@@ -74,7 +79,7 @@ public PersonBuilder withAddress(String address) {
}
/**
- * Sets the {@code Phone} of the {@code Person} that we are building.
+ * Sets the {@code Phone} of the {@code Contact} that we are building.
*/
public PersonBuilder withPhone(String phone) {
this.phone = new Phone(phone);
@@ -82,15 +87,23 @@ public PersonBuilder withPhone(String phone) {
}
/**
- * Sets the {@code Email} of the {@code Person} that we are building.
+ * Sets the {@code Email} of the {@code Contact} that we are building.
*/
public PersonBuilder withEmail(String email) {
this.email = new Email(email);
return this;
}
- public Person build() {
- return new Person(name, phone, email, address, tags);
+ /**
+ * Sets the {@code Note} of the {@code Contact} that we are building.
+ */
+ public PersonBuilder withNote(String note) {
+ this.note = new Note(note);
+ return this;
+ }
+
+ public Contact build() {
+ return new Contact(name, phone, email, address, tags, note);
}
}
diff --git a/src/test/java/seedu/address/testutil/PersonUtil.java b/src/test/java/seedu/address/testutil/PersonUtil.java
index 90849945183..0ffa0d84ce0 100644
--- a/src/test/java/seedu/address/testutil/PersonUtil.java
+++ b/src/test/java/seedu/address/testutil/PersonUtil.java
@@ -3,38 +3,39 @@
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_NOTE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE;
import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
import java.util.Set;
-import seedu.address.logic.commands.AddCommand;
-import seedu.address.logic.commands.EditCommand.EditPersonDescriptor;
-import seedu.address.model.person.Person;
+import seedu.address.logic.commands.AddContactCommand;
+import seedu.address.logic.commands.EditContactCommand.EditPersonDescriptor;
+import seedu.address.model.contact.Contact;
import seedu.address.model.tag.Tag;
/**
- * A utility class for Person.
+ * A utility class for Contact.
*/
public class PersonUtil {
/**
- * Returns an add command string for adding the {@code person}.
+ * Returns an add command string for adding the {@code contact}.
*/
- public static String getAddCommand(Person person) {
- return AddCommand.COMMAND_WORD + " " + getPersonDetails(person);
+ public static String getAddCommand(Contact contact) {
+ return AddContactCommand.COMMAND_WORD + " " + getPersonDetails(contact);
}
/**
- * Returns the part of command string for the given {@code person}'s details.
+ * Returns the part of command string for the given {@code contact}'s details.
*/
- public static String getPersonDetails(Person person) {
+ public static String getPersonDetails(Contact contact) {
StringBuilder sb = new StringBuilder();
- sb.append(PREFIX_NAME + person.getName().fullName + " ");
- sb.append(PREFIX_PHONE + person.getPhone().value + " ");
- sb.append(PREFIX_EMAIL + person.getEmail().value + " ");
- sb.append(PREFIX_ADDRESS + person.getAddress().value + " ");
- person.getTags().stream().forEach(
+ sb.append(PREFIX_NAME + contact.getName().fullName + " ");
+ sb.append(PREFIX_PHONE + contact.getPhone().value + " ");
+ sb.append(PREFIX_EMAIL + contact.getEmail().value + " ");
+ sb.append(PREFIX_ADDRESS + contact.getAddress().value + " ");
+ contact.getTags().stream().forEach(
s -> sb.append(PREFIX_TAG + s.tagName + " ")
);
return sb.toString();
@@ -57,6 +58,7 @@ public static String getEditPersonDescriptorDetails(EditPersonDescriptor descrip
tags.forEach(s -> sb.append(PREFIX_TAG).append(s.tagName).append(" "));
}
}
+ descriptor.getNote().ifPresent(note -> sb.append(PREFIX_NOTE).append(note).append(" "));
return sb.toString();
}
}
diff --git a/src/test/java/seedu/address/testutil/TestUtil.java b/src/test/java/seedu/address/testutil/TestUtil.java
index 896d103eb0b..0cea00f9efa 100644
--- a/src/test/java/seedu/address/testutil/TestUtil.java
+++ b/src/test/java/seedu/address/testutil/TestUtil.java
@@ -7,7 +7,7 @@
import seedu.address.commons.core.index.Index;
import seedu.address.model.Model;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
/**
* A utility class for test cases.
@@ -33,23 +33,23 @@ public static Path getFilePathInSandboxFolder(String fileName) {
}
/**
- * Returns the middle index of the person in the {@code model}'s person list.
+ * Returns the middle index of the contact in the {@code model}'s contact list.
*/
public static Index getMidIndex(Model model) {
return Index.fromOneBased(model.getFilteredPersonList().size() / 2);
}
/**
- * Returns the last index of the person in the {@code model}'s person list.
+ * Returns the last index of the contact in the {@code model}'s contact list.
*/
public static Index getLastIndex(Model model) {
return Index.fromOneBased(model.getFilteredPersonList().size());
}
/**
- * Returns the person in the {@code model}'s person list at {@code index}.
+ * Returns the contact in the {@code model}'s contact list at {@code index}.
*/
- public static Person getPerson(Model model, Index index) {
+ public static Contact getPerson(Model model, Index index) {
return model.getFilteredPersonList().get(index.getZeroBased());
}
}
diff --git a/src/test/java/seedu/address/testutil/TripBookBuilder.java b/src/test/java/seedu/address/testutil/TripBookBuilder.java
new file mode 100644
index 00000000000..e96fc85fcfe
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TripBookBuilder.java
@@ -0,0 +1,42 @@
+package seedu.address.testutil;
+
+import java.util.List;
+
+import seedu.address.model.TripBook;
+import seedu.address.model.trip.Trip;
+
+/**
+ * A utility class to help with building TripBook objects.
+ */
+public class TripBookBuilder {
+
+ private TripBook tripBook;
+
+ public TripBookBuilder() {
+ tripBook = new TripBook();
+ }
+
+ public TripBookBuilder(TripBook tripBook) {
+ this.tripBook = tripBook;
+ }
+
+ /**
+ * Adds a new {@code Trip} to the {@code TripBook} that we are building.
+ */
+ public TripBookBuilder withTrip(Trip trip) {
+ tripBook.addTrip(trip);
+ return this;
+ }
+
+ /**
+ * Sets the {@code TripBook} we are building to use the {@code trips} argument.
+ */
+ public TripBookBuilder withTrips(List trips) {
+ tripBook.setTrips(trips);
+ return this;
+ }
+
+ public TripBook build() {
+ return tripBook;
+ }
+}
diff --git a/src/test/java/seedu/address/testutil/TripBuilder.java b/src/test/java/seedu/address/testutil/TripBuilder.java
new file mode 100644
index 00000000000..ba4ae5d681d
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TripBuilder.java
@@ -0,0 +1,111 @@
+package seedu.address.testutil;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import seedu.address.model.contact.Name;
+import seedu.address.model.trip.Accommodation;
+import seedu.address.model.trip.Itinerary;
+import seedu.address.model.trip.Note;
+import seedu.address.model.trip.Trip;
+import seedu.address.model.trip.TripDate;
+import seedu.address.model.trip.TripName;
+
+/**
+ * A utility class to help with building Trip objects.
+ */
+public class TripBuilder {
+
+ public static final String DEFAULT_NAME = "Paris 2025";
+ public static final String DEFAULT_ACCOMMODATION = "Hotel 81";
+ public static final String DEFAULT_ITINERARY = "Eat baguettes";
+ public static final String DEFAULT_DATE = "01/01/2100";
+ public static final String DEFAULT_NOTE = "";
+
+ private TripName name;
+ private Accommodation accommodation;
+ private Itinerary itinerary;
+ private TripDate date;
+ private Set customerNames;
+ private Note note;
+
+ /**
+ * Creates a {@code TripBuilder} with the default details.
+ */
+ public TripBuilder() {
+ name = new TripName(DEFAULT_NAME);
+ accommodation = new Accommodation(DEFAULT_ACCOMMODATION);
+ itinerary = new Itinerary(DEFAULT_ITINERARY);
+ date = new TripDate(DEFAULT_DATE);
+ customerNames = new HashSet<>();
+ note = new Note(DEFAULT_NOTE);
+ }
+
+ /**
+ * Initializes the TripBuilder with the data of {@code tripToCopy}.
+ */
+ public TripBuilder(Trip tripToCopy) {
+ name = tripToCopy.getName();
+ accommodation = tripToCopy.getAccommodation();
+ itinerary = tripToCopy.getItinerary();
+ date = tripToCopy.getDate();
+ customerNames = new HashSet<>(tripToCopy.getCustomerNames());
+ note = tripToCopy.getNote();
+ }
+
+ /**
+ * Sets the {@code Name} of the {@code Trip} that we are building.
+ */
+ public TripBuilder withName(String name) {
+ this.name = new TripName(name);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Accommodation} of the {@code Trip} that we are building.
+ */
+ public TripBuilder withAccommodation(String accommodation) {
+ this.accommodation = new Accommodation(accommodation);
+ return this;
+ }
+
+ /**
+ * Sets the {@code Itinerary} of the {@code Trip} that we are building.
+ */
+ public TripBuilder withItinerary(String itinerary) {
+ this.itinerary = new Itinerary(itinerary);
+ return this;
+ }
+
+ /**
+ * Sets the {@code TripDate} of the {@code Trip} that we are building.
+ */
+ public TripBuilder withDate(String date) {
+ this.date = new TripDate(date);
+ return this;
+ }
+
+ /**
+ * Sets the customer names of the {@code Trip} that we are building.
+ */
+ public TripBuilder withCustomerNames(String... customerNames) {
+ Set customerNameSet = new HashSet<>();
+ for (String customerName : customerNames) {
+ customerNameSet.add(new Name(customerName));
+ }
+ this.customerNames = customerNameSet;
+ return this;
+ }
+
+ /**
+ * Sets the {@code Note} of the {@code Trip} that we are building.
+ */
+ public TripBuilder withNote(String note) {
+ this.note = new Note(note);
+ return this;
+ }
+
+ public Trip build() {
+ return new Trip(name, accommodation, itinerary, date, customerNames, note);
+ }
+}
diff --git a/src/test/java/seedu/address/testutil/TypicalIndexes.java b/src/test/java/seedu/address/testutil/TypicalIndexes.java
index 1e613937657..7badc6a6851 100644
--- a/src/test/java/seedu/address/testutil/TypicalIndexes.java
+++ b/src/test/java/seedu/address/testutil/TypicalIndexes.java
@@ -9,4 +9,8 @@ public class TypicalIndexes {
public static final Index INDEX_FIRST_PERSON = Index.fromOneBased(1);
public static final Index INDEX_SECOND_PERSON = Index.fromOneBased(2);
public static final Index INDEX_THIRD_PERSON = Index.fromOneBased(3);
+
+ public static final Index INDEX_FIRST_TRIP = Index.fromOneBased(1);
+ public static final Index INDEX_SECOND_TRIP = Index.fromOneBased(2);
+ public static final Index INDEX_THIRD_TRIP = Index.fromOneBased(3);
}
diff --git a/src/test/java/seedu/address/testutil/TypicalPersons.java b/src/test/java/seedu/address/testutil/TypicalPersons.java
index fec76fb7129..0adecac9c4e 100644
--- a/src/test/java/seedu/address/testutil/TypicalPersons.java
+++ b/src/test/java/seedu/address/testutil/TypicalPersons.java
@@ -8,51 +8,55 @@
import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_AMY;
import static seedu.address.logic.commands.CommandTestUtil.VALID_PHONE_BOB;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_FRIEND;
-import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_HUSBAND;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_CUSTOMER;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TAG_SERVICE;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import seedu.address.model.AddressBook;
-import seedu.address.model.person.Person;
+import seedu.address.model.contact.Contact;
/**
- * A utility class containing a list of {@code Person} objects to be used in tests.
+ * A utility class containing a list of {@code Contact} objects to be used in tests.
*/
public class TypicalPersons {
- public static final Person ALICE = new PersonBuilder().withName("Alice Pauline")
+ public static final Contact ALICE = new PersonBuilder().withName("Alice Pauline")
.withAddress("123, Jurong West Ave 6, #08-111").withEmail("alice@example.com")
.withPhone("94351253")
- .withTags("friends").build();
- public static final Person BENSON = new PersonBuilder().withName("Benson Meier")
+ .withTags("service")
+ .withNote("Preferred tour guide for European trips").build();
+ public static final Contact BENSON = new PersonBuilder().withName("Benson Meier")
.withAddress("311, Clementi Ave 2, #02-25")
.withEmail("johnd@example.com").withPhone("98765432")
- .withTags("owesMoney", "friends").build();
- public static final Person CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563")
- .withEmail("heinz@example.com").withAddress("wall street").build();
- public static final Person DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533")
- .withEmail("cornelia@example.com").withAddress("10th street").withTags("friends").build();
- public static final Person ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224")
+ .withTags("customer", "service")
+ .withNote("VIP customer, prefers luxury accommodations").build();
+ public static final Contact CARL = new PersonBuilder().withName("Carl Kurz").withPhone("95352563")
+ .withEmail("heinz@example.com").withAddress("wall street")
+ .withNote("Business traveler, frequent flyer").build();
+ public static final Contact DANIEL = new PersonBuilder().withName("Daniel Meier").withPhone("87652533")
+ .withEmail("cornelia@example.com").withAddress("10th street").withTags("customer")
+ .withNote("Family of 4, needs child-friendly activities").build();
+ public static final Contact ELLE = new PersonBuilder().withName("Elle Meyer").withPhone("9482224")
.withEmail("werner@example.com").withAddress("michegan ave").build();
- public static final Person FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427")
+ public static final Contact FIONA = new PersonBuilder().withName("Fiona Kunz").withPhone("9482427")
.withEmail("lydia@example.com").withAddress("little tokyo").build();
- public static final Person GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442")
+ public static final Contact GEORGE = new PersonBuilder().withName("George Best").withPhone("9482442")
.withEmail("anna@example.com").withAddress("4th street").build();
// Manually added
- public static final Person HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424")
+ public static final Contact HOON = new PersonBuilder().withName("Hoon Meier").withPhone("8482424")
.withEmail("stefan@example.com").withAddress("little india").build();
- public static final Person IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131")
+ public static final Contact IDA = new PersonBuilder().withName("Ida Mueller").withPhone("8482131")
.withEmail("hans@example.com").withAddress("chicago ave").build();
- // Manually added - Person's details found in {@code CommandTestUtil}
- public static final Person AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY)
- .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_FRIEND).build();
- public static final Person BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
- .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_HUSBAND, VALID_TAG_FRIEND)
+ // Manually added - Contact's details found in {@code CommandTestUtil}
+ public static final Contact AMY = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY)
+ .withEmail(VALID_EMAIL_AMY).withAddress(VALID_ADDRESS_AMY).withTags(VALID_TAG_SERVICE).build();
+ public static final Contact BOB = new PersonBuilder().withName(VALID_NAME_BOB).withPhone(VALID_PHONE_BOB)
+ .withEmail(VALID_EMAIL_BOB).withAddress(VALID_ADDRESS_BOB).withTags(VALID_TAG_CUSTOMER, VALID_TAG_SERVICE)
.build();
public static final String KEYWORD_MATCHING_MEIER = "Meier"; // A keyword that matches MEIER
@@ -64,13 +68,13 @@ private TypicalPersons() {} // prevents instantiation
*/
public static AddressBook getTypicalAddressBook() {
AddressBook ab = new AddressBook();
- for (Person person : getTypicalPersons()) {
- ab.addPerson(person);
+ for (Contact contact : getTypicalPersons()) {
+ ab.addPerson(contact);
}
return ab;
}
- public static List getTypicalPersons() {
+ public static List getTypicalPersons() {
return new ArrayList<>(Arrays.asList(ALICE, BENSON, CARL, DANIEL, ELLE, FIONA, GEORGE));
}
}
diff --git a/src/test/java/seedu/address/testutil/TypicalTrips.java b/src/test/java/seedu/address/testutil/TypicalTrips.java
new file mode 100644
index 00000000000..f26edbc7eee
--- /dev/null
+++ b/src/test/java/seedu/address/testutil/TypicalTrips.java
@@ -0,0 +1,98 @@
+package seedu.address.testutil;
+
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ACCOMMODATION_HOTEL_81;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_ITINERARY_EAT_BAGUETTES;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_AMY;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_NAME_BOB;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TRIP_DATE_2025;
+import static seedu.address.logic.commands.CommandTestUtil.VALID_TRIP_NAME_PARIS_2025;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import seedu.address.model.TripBook;
+import seedu.address.model.trip.Trip;
+
+/**
+ * A utility class containing a list of {@code Trip} objects to be used in tests.
+ */
+public class TypicalTrips {
+
+ public static final Trip PARIS = new TripBuilder().withName("Paris Adventure")
+ .withAccommodation("Hotel de Paris")
+ .withItinerary("Visit Eiffel Tower, Louvre Museum")
+ .withDate("15/6/2024")
+ .withCustomerNames("Alice Pauline", "Bob Chen")
+ .withNote("First time in Paris").build();
+
+ public static final Trip TOKYO = new TripBuilder().withName("Tokyo Explorer")
+ .withAccommodation("Shinjuku Hotel")
+ .withItinerary("Visit Tokyo Tower, Shibuya Crossing")
+ .withDate("20/7/2024")
+ .withCustomerNames("Charlie Brown", "David Lee")
+ .withNote("Business trip").build();
+
+ public static final Trip SINGAPORE = new TripBuilder().withName("Singapore Tour")
+ .withAccommodation("Marina Bay Sands")
+ .withItinerary("Gardens by the Bay, Sentosa Island")
+ .withDate("10/8/2024")
+ .withCustomerNames("Eve Tan")
+ .withNote("Family vacation").build();
+
+ public static final Trip BALI = new TripBuilder().withName("Bali Getaway")
+ .withAccommodation("Ubud Resort")
+ .withItinerary("Rice Terraces, Tanah Lot Temple")
+ .withDate("5/9/2024")
+ .withCustomerNames("Frank Wong", "Grace Liu")
+ .withNote("Honeymoon trip").build();
+
+ public static final Trip SEOUL = new TripBuilder().withName("Seoul Discovery")
+ .withAccommodation("Gangnam Hotel")
+ .withItinerary("Namsan Tower, Myeongdong Shopping")
+ .withDate("1/10/2024")
+ .withCustomerNames("Henry Park")
+ .withNote("Solo trip").build();
+
+ // Manually added
+ public static final Trip LONDON = new TripBuilder().withName("London Explorer")
+ .withAccommodation("Westminster Hotel")
+ .withItinerary("Big Ben, London Eye")
+ .withDate("15/11/2024")
+ .withCustomerNames("Ian Smith")
+ .withNote("Christmas shopping").build();
+
+ public static final Trip NEW_YORK = new TripBuilder().withName("NYC Adventure")
+ .withAccommodation("Manhattan Hotel")
+ .withItinerary("Times Square, Central Park")
+ .withDate("20/12/2024")
+ .withCustomerNames("Jane Wilson")
+ .withNote("New Year celebration").build();
+
+ // Manually added - Trip's details found in {@code CommandTestUtil}
+ public static final Trip PARIS_2025 = new TripBuilder().withName(VALID_TRIP_NAME_PARIS_2025)
+ .withAccommodation(VALID_ACCOMMODATION_HOTEL_81)
+ .withItinerary(VALID_ITINERARY_EAT_BAGUETTES)
+ .withDate(VALID_TRIP_DATE_2025)
+ .withCustomerNames(VALID_NAME_AMY, VALID_NAME_BOB)
+ .withNote("Summer vacation").build();
+
+ public static final String KEYWORD_MATCHING_PARIS = "Paris"; // A keyword that matches PARIS
+
+ private TypicalTrips() {} // prevents instantiation
+
+ /**
+ * Returns a {@code TripBook} with all the typical trips.
+ */
+ public static TripBook getTypicalTripBook() {
+ TripBook tb = new TripBook();
+ for (Trip trip : getTypicalTrips()) {
+ tb.addTrip(trip);
+ }
+ return tb;
+ }
+
+ public static List getTypicalTrips() {
+ return new ArrayList<>(Arrays.asList(PARIS, TOKYO, SINGAPORE, BALI, SEOUL));
+ }
+}
diff --git a/src/test/java/seedu/address/ui/PersonCardTest.java b/src/test/java/seedu/address/ui/PersonCardTest.java
new file mode 100644
index 00000000000..e52fa2b20e7
--- /dev/null
+++ b/src/test/java/seedu/address/ui/PersonCardTest.java
@@ -0,0 +1,64 @@
+package seedu.address.ui;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.address.model.contact.Contact;
+import seedu.address.model.tag.Tag;
+import seedu.address.testutil.PersonBuilder;
+import seedu.address.testutil.TypicalPersons;
+
+public class PersonCardTest {
+
+ @Test
+ public void display() {
+ Contact contact = TypicalPersons.ALICE;
+ TestFxmlObject testObject = new TestFxmlObject(contact.getName().fullName);
+ assertEquals(contact.getName().fullName, testObject.getText());
+ }
+
+ @Test
+ public void display_noteAndTags() {
+ Contact contact = new PersonBuilder()
+ .withNote("Likes the sea")
+ .withTags("customer", "service")
+ .build();
+ TestFxmlObject testObject = new TestFxmlObject(contact.getNote().toString());
+ assertEquals("Likes the sea", testObject.getText());
+
+ Tag customerTag = contact.getTags().stream()
+ .filter(tag -> tag.tagName.equals("customer"))
+ .findFirst()
+ .get();
+ TestFxmlObject customerTagObject = new TestFxmlObject(customerTag.getStyleClass());
+ assertEquals("customer-tag", customerTagObject.getText());
+
+ Tag serviceTag = contact.getTags().stream()
+ .filter(tag -> tag.tagName.equals("service"))
+ .findFirst()
+ .get();
+ TestFxmlObject serviceTagObject = new TestFxmlObject(serviceTag.getStyleClass());
+ assertEquals("service-tag", serviceTagObject.getText());
+ }
+
+ @Test
+ public void testEquals() {
+ Contact contact = new PersonBuilder()
+ .withNote("Allergic to peanuts")
+ .withTags("customer")
+ .build();
+
+ TestFxmlObject testObject = new TestFxmlObject(contact.getName().fullName);
+ TestFxmlObject sameTestObject = new TestFxmlObject(contact.getName().fullName);
+ TestFxmlObject differentTestObject = new TestFxmlObject("differentName");
+
+ assertTrue(testObject.equals(testObject));
+ assertFalse(testObject.equals(null));
+ assertFalse(testObject.equals(2));
+ assertTrue(testObject.equals(sameTestObject));
+ assertFalse(testObject.equals(differentTestObject));
+ }
+}