diff --git a/.gitignore b/.gitignore
index 2873e189e1..e1a4083bda 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,7 @@ bin/
/text-ui-test/ACTUAL.TXT
text-ui-test/EXPECTED-UNIX.TXT
+META-INF/MANIFEST.MF
+
+# storage
+/data/
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index ea82051fab..ff89ac1f45 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,11 +29,11 @@ test {
}
application {
- mainClass.set("seedu.duke.Duke")
+ mainClass.set("essenmakanan.EssenMakanan")
}
shadowJar {
- archiveBaseName.set("duke")
+ archiveBaseName.set("essenMakanan")
archiveClassifier.set("")
}
@@ -43,4 +43,5 @@ checkstyle {
run{
standardInput = System.in
+ enableAssertions = true
}
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 0f072953ea..aeb0cb7adc 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -1,9 +1,8 @@
# About us
-Display | Name | Github Profile | Portfolio
---------|:----:|:--------------:|:---------:
- | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
- | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
- | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
- | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
- | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
+| Display | Name | Github Profile | Portfolio |
+|-------------------------------------------------------------------------------------------------------------------------------------------|:--------------------:|:----------------------------------------:|:--------------------------------:|
+|
| Stanley Wijaya | [Github](https://github.com/StanleyW00) | [Portfolio](team/stanleyw00.md) |
+|
| Charlyn Kwan Ting Yu | [Github](https://github.com/charkty) | [Portfolio](team/charkty.md) |
+|
| Leow Kai Jie | [Github](https://github.com/kaijie0102) | [Portfolio](team/kaijie0102.md) |
+|  | Li Haoyu | [Github](https://github.com/Haoyuli2002) | [Portfolio](team/haoyuli2002.md) |
\ No newline at end of file
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 64e1f0ed2b..fb871d1b51 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -1,38 +1,1050 @@
+---
+layout: default
+title: Developer Guide
+---
+# Table of Contents
+* [Design & Implementation](#design--implementation)
+* [Recipe Component](#recipe-component)
+* [Ingredient Component](#ingredient-component)
+* [Shortcut Component](#shortcut-component)
+* [Storage Component](#storage-component)
+* [Logger Component](#logger-component)
+* [Implementation](#implementation-component)
+* [Appendix](#appendix)
+* [Appendix A - Product Scope](#product-scope)
+* [Appendix B - User Stories](#user-stories)
+* [Appendix C - Non-Functional Requirements](#non-functional)
+* [Appendix D - Glossary](#glossary)
+* [Appendix E - Instructions for Manual Testing](#testing)
+
+--------------------------------------------------------------------------------------------------------------------
+
# Developer Guide
## Acknowledgements
-{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+References
+1. [Developer Guide](https://se-education.org/addressbook-level3/DeveloperGuide.html)
+2. [User Guide](https://se-education.org/addressbook-level3/UserGuide.html)
+
## Design & implementation
-{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.}
+## Design
+### Architecture
+
+The ***Architecture Diagram*** given above explains the high-level design of our EssenMakanan CLI.
+
+Given below is a quick overview of main components and how they interact with each other.
+
+
+
+**Main components of the architecture**
+
+- `UI`: The user interface of the app that handles all print and output commands
+- `Main`: EssenMakanan main code which handles `Storage`, `Parser` and `Ui`
+- `Storage`: Handles stored all `recipe`, `ingredient` and `shortcut` that the user has input
+- `Parser`: Handles user input and checks for formatting/command errors and throws relevant exceptions
+- `Command`: Command handles the user functionality of Essenmakanan
+- `Shortcut`: Shortcuts to be used by user
+- `Ingredient`: Ingredients in inventory and recipe ingredients
+- `Recipe`: Recipes created by user
+- `Logger`: A logger that logs the activities done in storage
+
+
+### Recipe component
+
+1. Upon booting up the application, `RecipeList` will retrieve past recipe data by calling `restoreSavedData`
+in the Storage class. The restored `recipes` will then be stored in `RecipeList`. If the `recipes.txt` file does
+not exist, no data is retrieved and RecipeList will be empty.
+2. Each `RecipeList` consists of zero to as many Recipe in it's ArrayList.
+3. Each `Recipe` in RecipeList consists of exactly one `RecipeIngredientList`, up to four `Tag` and
+exactly one `RecipeStepList`.
+4. `RecipeIngredientList` consists of at least one Ingredient while `RecipeStepList` consists of at least one step.
+
+
+
+
+Two main functionalities that we have are to `add` and `delete` a recipe from the recipeList. Below is an object diagram to illustrate how the add and delete function works.
+
+1. The initial state of `recipes:RecipeList` stores 2 `Recipe`, namely, 'chicken pizza' and 'meatball noodles'

+2. `Add` a new recipe
A new recipe titled "dumpings" will be added to `recipes:RecipeList`. The number of `recipe` stored in `recipes:RecipeList` will increase from 2 to 3. 
+3. `Delete` an existing recipe
+ Recipe titled "chicken pizza" is at index 2 of the `RecipeList`. By giving the command `delete r/2`, `recipes:RecipeList` will remove `chicken pizza:Recipe` from the ArrayList of recipes.

+
+---
+
+### Ingredient component
+
+
+
+
+### Shortcut component
+
+1. Upon booting up the application, `ShortcutList` will retrieve past recipe data by calling `restoreSavedData`
+ in the Storage class. The restored `shortcuts` will then be stored in `ShortcutList`. If the `shortcuts.txt`
+ file does not exist, no data is retrieved and RecipeList will be empty.
+2. Each `ShortcutList` consists of zero to as many Shortcut in it's ArrayList.
+
+
+
+
+
+
+### Storage component
+
+In this application, it uses text files to store all data, i.e, recipes, ingredients and shortcuts. The data will be
+kept in `recipes.txt`, `ingredients.txt` and `shortcuts.txt` in the `data` folder.
+
+When booting up the application, `restoreSavedData` will be called to get both recipes and ingredients
+from the previous session.
+
+
+
+
+
+When a user exits the application, `saveData` will be called and it will convert all data from recipes and
+ingredients into a string which will be put in their own respective text files.
+
+
+
+
+
+
+### Logger component
+
+In this application, a customised logger is used by recipe, ingredient and shortcut storage. This logger will log
+all activity done and errors in the storages. The logger will use `INFO`, `WARNING` or `SEVERE` levels for the logs.
+The logs will be saved in `essenmakanan.log` inside the `data` folder.
+
+
+## Implementation
+### Help Feature
+The help feature is facilitated by the `HelpCommand` class. By calling `executeCommand` on the class, it will invoke the `Ui` class to print the user help commands.
+
+
+- **Step 1**
+
+ Input will be sent from the main `EssenMakanan` class to the `Parser` to identify the command type
+
+
+- **Step 2**
+
+ A new `HelpCommand` object will be created and will be sent back to main
+
+
+- **Step 3**
+
+ `commandObject#executeCommand()` will be called which in turn calls `Ui#showCommands()`
+
+
+- **Step 4**
+
+ Finally `Ui#showCommands()` will call `Ui#showRecipeCommands()`, `Ui#showIngredientCommands()`, `Ui#showOtherCommands()` to print all commands for recipe, ingredient and others respectively
+
+
+
+
+### Exit feature
+The help feature is facilitated by the `ExitCommand` class. By calling `executeCommand` on the class, it will invoke the `Ui` class to print the exit command.
+
+- **Step 1**
+
+ Input will be sent from the main `EssenMakanan` class to the `Parser` to identify the command type
+
+
+- **Step 2**
+
+ A new `ExitCommand` object will be created and will be sent back to main
+
+
+- **Step 3**
+
+ `commandObject#executeCommand()` will be called which in turn calls `Ui#showCommands()`
+
+
+- **Step 4**
+
+ Finally `Ui#showCommands()` will print the exit message
+
+
+
+
+
+### Add Recipe feature
+
+The add recipe feature is facilitated by the `AddRecipeCommand` class. By calling `executeCommand` on the class, the steps will
+be executed as follows:
+- **Step1**
+
+ `AddRecipeCommand` will parse the recipe title using `RecipeParser`. Then, it will return the recipe title.
+
+
+- **Step2**
+
+ `AddRecipeCommand` will create a new `Recipe` with the obtained title.
+
+
+- **Step3**
+
+ `AddRecipeCommand` will add newly created `Recipe` into `RecipeList`. Then, the recipe will be added into an
+ `ArrayList` inside `RecipeList`.
+
+
+- **Step4**
+
+ `AddRecipeCommand` will call `Ui` class to print out the title of the recently added recipe.
+
+
+
+
+### Edit Recipe feature
+The edit recipe feature is facilitated by the `EditRecipeCommand` class. By calling `executeCommand` on the class, the steps will
+be executed as follows:
+
+- **Step1**
+
+ Recipe title will be obtained parsed and obtained using the `getRecipeTitle()` method
+
+
+- **Step2**
+
+ The `Recipe` object to edit will be retrieved from the `RecipeList` using the `getRecipe()` method
+
+
+- **Step3**
+
+ The details to be edited will be obtained with the `getAttributesToEdit()` method
+
+
+- **Step4**
+
+ The `Recipe` object and the array of details will to be passed to the `editRecipe()` method in the `RecipeList` class.
+Every detail in the array will be checked and edited accordingly. Output message will be printed to the user.
+
+
+
+### Duplicate Recipe Feature
+
+The duplicate recipe feature is facilitated by the `DuplicateRecipeCommand` class. By calling `executeCommand` on the
+class, the steps will be executed as follows:
+- **Step1**
+
+ `DuplicateRecipeCommand` will parse the selected index using `RecipeParser` with a recipe title or index.
+
+
+- **Step2**
+
+ `DuplicateRecipeCommand` will get the specified recipe which going to be duplicated.
+
+
+- **Step3**
+
+ `DuplicatedRecipeCommand` will create using the recipe's title with a copy indicate `(copy)`, the recipe's steps
+ , and the recipe's ingredients
+
+
+- **Step4**
+
+ `DuplicateRecipeCommand` will add the recently created recipe into the recipe list.
+
+
+- **Step5**
+
+ `DuplicateRecipeCommand` will call `Ui` class to print out the title of the recently duplicated recipe.
+
+
+
+
+
+
+### View Ingredients feature
+The view ingredient feature is facilitated by the `ViewIngredientCommand` class. Users can input
+"view i" to trigger this command. Users will then be able to see all ingredients stored.
+Example:
+
+``````
+1. bread: 2pcs
+
+2. apple: 500g
+``````
+
+- **Step 1**
+
+ Input will be sent from the main `EssenMakanan` class to the `Parser` to identify the command type.
+
+
+- **Step 2**
+
+ A new `ViewIngredientCommand` object will be created and will be sent back to main
+
+
+- **Step 3**
+
+ `commandObject#executeCommand()` will be called which in turn calls `Ui#printAllIngredients()`
+
+
+- **Step 4**
+
+ Finally `Ui#printAllIngredients()` will call `IngredientList#listIngredients()` to print all the ingredients
+ to standard output
+
+
+
+
+### View an ingredient feature
+
+The view ingredient feature is facilitated by the `ViewSpecificIngredientCommand` class. Users can input
+`view i/INGREDIENT_NAME` to trigger this command. Users will then be able to see the quantity and unit of specific ingredient stored.
+
+
+Note that `INGREDIENT_NAME` can be replaced with `INGREDIENT_ID`.
+
+
+- **Step 1**
+
+ Input will be sent from the main `EssenMakanan` class to the `Parser` to identify the command type.
+
+
+- **Step 2**
+
+ A new `ViewSpecificIngredientCommand` object will be created and will be sent back to main
+
+
+- **Step 3**
+
+ `commandObject#executeCommand()` will be called which in turn calls `IngredientParser#getIngredientIndex()` to get the index of the ingredient.
+ If the ingredient does not exist, an error will be thrown to inform the user that the ingredient does not exist.
+
+
+- **Step 4**
+
+ Finally `ViewSpecificCommand` will print `ingredient` object as there is a `toString` method within `Ingredient` class, allowing for `system.out.println(ingredient)`
+
+
+
+
+### View Recipes feature
+The view recipes feature is facilitated by the `ViewRecipeCommand` class. Users can input
+"view r" to trigger this command
+
+* **Step 1**
+
+ Input will be sent from the main `EssenMakanan` class to the `Parser` to identify the command type.
+
+
+* **Step 2**
+
+ A new `ViewRecipeCommand` object will be created and will be sent back to main
+
+
+* **Step 3**
+
+ `commandObject#executeCommand()` will be called which in turn calls `Ui#printAllRecipes()`
+
+
+* **Step 4**
+
+ Finally, `RecipeList#listRecipeTitles()` will be called to print all the ingredients
+ to standard output
+
+
+
+### Add Ingredient feature
+
+The add Ingredient feature is used by a `AddIngredientCommand` class.
+Multiple ingredients can be added at the same time using the syntax
+`add i/NAME,QUANTITY,UNIT i/INGREDIENT2,.. i/INGREDIENT3...`
+
+By calling `executeCommand` on the class, the steps will
+be executed as follows:
+* **Step1**
+
+ `AddIngredientCommand` will use the "split" method of `String` to get an array of the descriptions of ingredients
+ and iterate all the elements in this array
+
+
+* **Step2**
+
+ `AddIngredientCommand` will get a new `Ingredient` by invoking the method "parseIngredient" of `IngredientParser`
+ for each element of the obtained array
+
+
+* **Step3**
+
+ `AddIngredientCommand` will add this `Ingredient` into `IngredientList`. Then, the ingredient will be added into an
+ `ArrayList` inside `IngredientList`.
+
+
+* **Step4**
+
+ `AddIngredientCommand` will call `Ui` class to print out the name of the recently added ingredient.
+
+
+
+### Edit Ingredient feature
+The edit Ingredient feature is used by a `EditIngredientCommand` class.
+Multiple attributes can be edited at the same time using the syntax
+`edit i/INGREDIENT_NAME [n/NEW_INGREDIENT_NAME] [q/NEW_QUANTITY] [u/NEW_UNIT]`
+
+By calling `executeCommand` on the class, the steps will
+be executed as follows:
+* **Step1**
+
+ Obtain ingredient to edit by name
+
+
+* **Step2**
+
+ Send ingredient and the details to edit to `IngredientList`
+
+
+* **Step3**
+ Switch case that will check the flag, whether to edit name, quantity or unit
+
+
+* **Step4**
+ Perform the update and print the output message
+
+
+
+### Delete recipe feature
+
+The delete recipe feature is facilitated by the `DeleteRecipeCommand` class. Users can input
+"delete r/RECIPE_ID" or "delete r/RECIPE_TITLE" to trigger this command
+
+* **Step 1**
+
+ Input will be sent from the main `EssenMakanan` class to the `Parser` to identify the command type.
+
+
+* **Step 2**
+
+ A new `DeleteRecipeCommand` object will be created and will be sent back to main
+
+
+* **Step 3**
+
+ `commandObject#executeCommand()` will be called which will call `RecipeList#deleteRecipe()` using `recipeIndex`
+
+
+* **Step 4**
+
+ Finally, `RecipeList#deleteRecipe()` will print the recipe being deleted
+ to standard output
+
+
+### Check recipe feature
+
+The check recipe feature is used by the `CheckRecipeCommand` class.
+
+To view if you have all ingredients needed to start on a recipe, use the following command
+`check recipe RECIPE_TITLE` or `check recipe RECIPE_ID`
+
+By calling `executeCommand` on the class, the steps will
+be executed as follows:
+* **Step1**
+
+ `CheckRecipeCommand` will get the index of recipe by calling method `getRecipeIndex()` in the `Parser` class.
+ This method will throw an error if the recipe entered is invalid or does not exist in the recipe database.
-## Product scope
-### Target user profile
-{Describe the target user profile}
+* **Step2**
+
+ `CheckRecipeCommand` will then call its own `getMissingIngredients()` method which will create 3 different array lists - `missingIngredeints`, `insufficientIngredients` and `diffUnitIngredients`.
+ They account for ingredients that are missing, ingredients that you currently don't have enough of in your inventory, and ingredients that cannot be compared because of the difference in unit respectively.
+
+
+* **Step3**
+
+ `CheckRecipeCommand` then call the static method `printRecipeMessage()` in `UI` class to print missing ingredients, ingredients that are insufficient, and ingredients of different units.
+
+
+* **Step4**
+
+ `Ui` will call `listIngredients()` method in `IngredientList` class to print the 3 different array lists.
+
+
+
+### Filter recipe feature
+
+The filter recipe feature is used by the `FilterRecipesCommand`.
+
+To filter recipes based on ingredients that are in it,
+use the command `filter recipe i/INGREDIENT_NAME [i/...]`, where `INGREDIENT_ID` can be used in place of `INGREDIENT_NAME` too.
+
+* **Step1**
+
+ `FilterRecipeCommand` will start a loop that filters all ingredients that the user has input
+
+
+* **Step2**
+
+ In the loop, the recipes will be filtered and be stored in `filteredRecipes` variable which is a `RecipeList` object.
+ `filteredRecipes` will contain all recipes that has the specified ingredient.
+
+
+* **Step3**
+
+ The filteredRecipes will then be passed to the static `Ui` method `printFilteredRecipes()` to display all the recipes with the specified ingredient.
+
+
+* **Step4**
+
+ Steps 2 to 3 will repeat again until all ingredients in the input has been handled and executed. The loop will then stop and process exits the loop.
+
+
+
+
+
+### View Shortcuts Feature
+
+The view shortcuts feature is used by the `ViewShortcutsCommand`. By using `view sc`, users are able to see al the
+current shortcuts in the application.
+
+* **Step 1**
+
+ Input will be sent from the main `EssenMakanan` class to the `Parser` to identify the command type.
+
+
+* **Step 2**
+
+ A new `ViewShortcutsCommand` object will be created and will be sent back to main
+
+
+* **Step 3**
+
+ `commandObject#executeCommand()` will be called which in turn calls `Ui#printAllShortcuts()`
+
+
+* **Step 4**
+
+ Finally, `ShortcutList#listShortcuts()` will be called to print all the ingredients
+ to standard output.
+
+
+
+
+### Add Shortcut Feature
+
+The add shortcut feature is used by the `AddShortcutCommand`. Users can input `add sc/INGREDIENT_NAME,QUANTITY` to
+add a shortcut with a specified ingredient that is **in the list** and a specified quantity to add every time
+the shortcut is used.
+
+By calling `executeCommand` on the class, the steps will
+be executed as follows:
+* **Step1**
+
+ `AddShortcutCommand` will parse a shortcut from a string. The parser will refer to the ingredient list for checking
+ matching ingredient name and checks if the quantity is valid.
+
+* **Step2**
+
+ `AddShortcutCommand` will get a new `Shortcut` if all specifications are met, i.e. valid ingredient name and quantity.
+
+* **Step3**
+
+ `AddShortcutCommand` will add the newly created `Shortcut` into `ShortcutList`. Then, the ingredient will be
+ added into an`ArrayList` inside `ShortcutList`.
+
+
+* **Step4**
+
+ `AddShortcutCommand` will call `Ui` class to print out the name of the recently added shortcut.
+
+
+
+
+
+### Edit Shortcut Feature
+
+The edit shortcut feature is used by the `EditShortcutCommand`. Users can input `edit sc/INGREDIENT_NAME or
+SHORTCUT_INDEX n/INGREDIENT_NAME q/QUANTITY` to edit a shortcut with new ingredient name or new quantity. However, the
+changes for each attribute can only be done once per input line.
+
+By calling `executeCommand` on the class, the steps will
+be executed as follows:
+* **Step1**
+
+ `EditShortcutCommand` will parse an index of the shortcut that the input refers to. Then, `EditShortcutCommand` will
+ get the shortcut from the list based on the index.
+
+* **Step2**
+
+ If the shortcut is found, `EditShortcutCommand` will parse the changes indicated by flags. It will go through all the
+ flags and make changes based on the flags and whether the changes are valid, i.e. valid name or valid quantity.
+
+* **Step3**
+
+ After the changes has been made, `EditShortcutCommand` will call `Ui` class to
+ print out the changes made on the shortcut.
+
+
+
+
+
+### Delete Shortcut Feature
+
+The delete shortcut feature is used by the `DeleteShortcutCommand`. Users can input `delete sc/INGREDIENT_NAME or
+SHORTCUT_INDEX` to delete a shortcut on the list.
+
+By calling `executeCommand` on the class, the steps will
+be executed as follows:
+* **Step1**
+
+ `DeleteShortcutCommand` will parse an index of the shortcut that the input refers to. Then, `DeleteShortcutCommand`
+ will get the shortcut from the list based on the index.
+
+* **Step2**
+
+ If the shortcut is found, `DeleteShortcutCommand` will call `ShortcutList#deleteShortcut`. Then, it will remove the
+ specified shortcut from the arraylist.
+
+* **Step3**
+
+ `ShortcutList#deleteShortcut` will call `Ui` class to print out the deleted shortcut.
+
+
+
+
+
+### Use Shortcut Feature
+
+The use shortcut feature is used by the `UseShortcutCommand`. Users can input `sc INGREDIENT_NAME or SHORTCUT_INDEX` to
+use the shortcut. After using the shortcut, the ingredient that is being referred to will have its quantity added by
+the specified amount in the shortcut.
+
+By calling `executeCommand` on the class, the steps will
+be executed as follows:
+* **Step1**
+
+ `UseShortcutCommand` will parse an index of the shortcut that the input refers to. Then, `UseShortcutCommand`
+ will get the shortcut from the list based on the index.
+
+* **Step2**
+
+ If the shortcut is found, `UseShortcutCommand` will get the ingredient name and quantity from the shortcut. Then,
+ `UseShortcutCommand` will get the unit based on the ingredient from the shortcut.
+
+* **Step3**
+
+ `UseShortcutCommand` will create a new ingredient with the acquired attributes. Then, `UseShortcutCommand` will call
+ `IngredientList#updateIngredient` which will manage the ingredient data based on the name and quantity.
+
+
+
+
+# Appendix
+
+
+## Appendix A - Product scope
+### Target user profile
+* shares kitchen space and ingredients with other cooks.
+* can type fast
+* is comfortable using CLI apps
+* prefers typing
### Value proposition
-{Describe the value proposition: what problem does it solve?}
+Easy and intuitive way to keep track of ingredients you have in your kitchen. This helps avoid buying duplicated ingredients, reminds you of the ingredients you need, and gives a visualisation of the recipe timeline to ensure that advance preparation is done in time, eg marinating.
+
+
+## Appendix B - User Stories
+
+| Version | As a ... | I want to ... | So that I can ... |
+|---------|---------------|---------------------------------------------------------------------------------------|---------------------------------------------------------------|
+| v1.0 | beginner user | see all recipes | learn and try all recipes |
+| v1.0 | beginner user | add new recipes into the list | learn and try out new recipes |
+| v1.0 | beginner user | see all ingredient I have | |
+| v1.0 | beginner user | add ingredients to the empty list | add an item to my kitchen inventory |
+| v2.0a | amateur | delete some recipes | remove a recipe that I no longer want to use |
+| v2.0a | amateur | delete ingredients | remove an ingredient from the list |
+| v2.0a | new user | be greeted with an instruction manual | learn how to navigate through the app|
+| v2.0a | beginner user | view specific recipe ||
+| v2.0a | beginner user | add ingredients to recipe ||
+| v2.0a | amateur | edit ingredients of recipe | change recipe ingredients |
+| v2.0a | amateur | edit steps of recipe | change recipe steps |
+| v2.0a | beginner user | type the quantity of ingredients as well as the unit of measurement | add an item with the quantity to my kitchen inventory |
+| v2.0a | amateur | change the quantity of ingredient available | remove a fixed quantity of an ingredient from the list |
+| v2.0b | beginner user | add milestones with tags on the steps | see the overview of steps to execute in the recipe |
+| v2.0b | amateur | view a timeline of the cooking process, from preparation all the way till actual cooking time | do preparations that are necessary at the time needed |
+| v2.0b | amateur | See all ingredients I am lacking for a specific recipe | get an overview of ingredients i need to buy |
+| v2.0b | new user | exploring the app see default recipes pre-placed in the app | get started with cooking and learn the recipe’s format |
+| v2.0b | amateur | find recipes by ingredients | to give user options on what they can cook based on their cravings |
+| v2.1 | expert | Duplicate a recipe | Save time for creating similar recipes |
+| v2.1 | advanced user | Schedule recipes for a week | plan for weekly grocery shopping |
+| v2.1 | advanced user | cook for more if necessary | scale recipe based on number of pax |
+| v2.1 | advanced user | subtract ingredients automatically when i use it | indicate that i have used an ingredient |
+| v2.1 | expert | can categorise recipes based on ingredients I have | cook without purchasing new ingredients |
+| v2.1 | advanced user | add shortcuts to add commonly used ingredients | make the process of adding ingredients more efficient |
+
+
+
+## Appendix - C Non-Functional Requirements
+
+1. Should work on any mainstream OS as long as it has Java 11 or above installed.
+2. Should be able to hold up to 1000 recipes, ingredients and shortcuts 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.
+4. The app is free for all users.
+5. Should be able to work on a typical CLI and not require any third-party software.
+6. Should response no more than 5 seconds for any user interactions.
+
+
+## Appendix - D Glossary
+
+* *Mainstream OS*: Windows, Linux, Unix, OS-X
+* *CLI* - Command Line Interface
+
+
+## Appendix - E Instructions for Manual Testing
+
+Below are instructions for users who want to test the app manually.
+
+```
+Note: These instructions only provide a starting point for testers to work on; testers are expected to do more *exploratory* testing.
+```
+
+### Launch
+
+Here are the instructions for launching the app:
+* Download the jar file and copy into an empty folder
+* Go to your terminal and make sure the directory is the one with the jar file.
+* Type `java -jar EssenMakanan.jar` and run the command.
+* Expected: A welcome message should appear. If this is your first time running the jar, there will be a few messages
+ related to the app creating a folder, text files for storage and a log file.
+
+*
+### Summary of Commands for testing
+* [Help](#help)
+* [Exit](#exit)
+
+---
+* [Ingredient: Add](#add-i)
+* [Ingredient: View All](#view-all-i)
+* [Ingredient: View Specific](#view-i)
+* [Ingredient: Edit](#edit-i)
+* [Ingredient: Delete](#delete-i)
+* [Ingredient: Use](#use-i)
+
+---
+* [Recipe: Add](#add-r)
+* [Recipe: View All](#view-all-r)
+* [Recipe: View Specific](#view-r)
+* [Recipe: Edit](#edit-r)
+* [Recipe: Delete](#delete-r)
+* [Recipe: Check](#check-r)
+* [Recipe: Filter](#filter-r)
+* [Recipe: Execute](#execute-r)
+* [Recipe: Plan](#plan-r)
+* [Recipe: Duplicate](#duplicate-r)
+
+---
+* [Shortcut: Add](#add-s)
+* [Shortcut: View All](#view-all-s)
+* [Shortcut: Edit](#edit-s)
+* [Shortcut: Delete](#delete-s)
+* [Shortcut: Use](#use-s)
+
+
+### Help
+(
)
+
+- `help`
+**Expected**: All commands will be displayed.
+
+
+### Adding a ingredient
+(
)
+
+**Prerequisites**: Know the valid units of measurement. Some valid units of measurement are: `kg`, `g`, `l`, `ml`, `pc`,
+see [full list](https://ay2324s1-cs2113-f11-2.github.io/tp/UserGuide.html#units) in User Guide.
+- Add ingredient to inventory: `add i/bread,2,pc`
+**Expected**: An ingredient with the name `bread` and quantity `2` and unit "pc" will be added into the inventory.
+
+
+### Viewing all ingredients
+(
)
+
+- `view i`
+**Expected**: All ingredients will be displayed.
+
+
+### Viewing a specific ingredient
+(
)
+
+**Prerequisites**: There should be at least 1 ingredient in the list, and you know the name.
+All ingredient names can be seen using `view i` command.
+- `view i/bread`
+**Expected**: Ingredient with name `bread` will be displayed, along with its quantity and unit.
+
+
+### Edit a specific ingredient
+(
)
+
+**Pre-requisites**: There should be at least 1 ingredient in the list, and you know the name.
+All ingredient names can be seen using `view i` command.
+- `edit i/bread n/apple q/3`
+**Expected**: Ingredient with name `bread` will be changed to `apple` and its quantity to `3`
+
+
+### Delete a specific ingredient
+(
)
+
+**Pre-requisites**: There should be at least 1 ingredient in the list, and you know the name.
+All ingredient names can be seen using `view i` command.
+- `delete i/bread`
+**Expected**: Ingredient with name `bread` will be deleted.
+
+
+### Use Ingredient
+(
)
+
+**Note**: The ingredients for recipes are different from the ingredients that you have in the inventory
+
+**Pre-requisites**: There should be at least 1 ingredient in the list, and you know the name.
+All ingredient names can be seen using `view i` command.
+- `use i/bread,1,pc`
+**Expected**: Ingredient with name `bread` will be used and its quantity will be reduced by 1.
+
+
+### Adding a recipe
+(
)
+
+**Note**: The ingredients for recipes are different from the ingredients that you have in the inventory
+
+- Add basic recipe: `add r/scrambled eggs s/beat the eggs i/egg,2,pc`
+**Expected**: A recipe with the title `scrambled eggs` will be added into the recipe list.
+The recipe will have 1 step and 1 ingredient.
+- Adding a recipe with duration(optional): `add r/scrambled eggs s/beat the eggs d/5min i/egg,2,pc`
+**Expected**: Same as basic recipe but step will have a duration of 5 min "beat the eggs for 5 min"
+- Adding a recipe with tag(optional): `add r/scrambled eggs t/1 s/beat the eggs d/5min i/egg,2,pc`
+**Expected**: Same as add step with duration but step will belong to tag 1: `NIGHT_BEFORE`
+
+
+### Viewing all recipes
+(
)
+
+- `view r`
+**Expected**: All recipes will be displayed.
+
+
+### Viewing a specific recipe
+(
)
+
+**Prerequisites**: There should be at least 1 recipe in the list, and you know the title.
+All recipe titles can be seen using `view r` command.
+
+**Test Case**:
+- `view r/scrambled eggs`
+ **Expected**: Recipe with title `scrambled eggs` will be displayed along with its steps and ingredients
+
+
+
+
+### Edit a specific recipe
+(
)
+
+- Edit recipe title `edit r/scrambled eggs n/poached eggs`
+**Prerequisites**: There should be at least 1 recipe in the list, and you know the title.
+All recipe titles can be seen using `view r` command.
+
+ **Expected**: Recipe title will be changed to "poached eggs"
+
+
+- Edit recipe step `edit r/poached eggs s/1,whisk the eggs`
+**Prerequisites**: view recipe using `view r/poached eggs` command to see step id.
+ **Expected**: Step 1 of recipe will be changed to "whisk the eggs"
+
+
+- Edit recipe title `edit r/poached eggs i/1,n-duck eggs,q-3`
+**Prerequisites**: view recipe using `view r/poached eggs` command to see ingredient id.
+ **Expected**: Ingredient 1's name will be changed to "duck eggs" and quantity to "3"
+
+
+### Delete a specific recipe
+(
)
+
+**Prerequisites**: There should be at least 1 recipe in the list, and you know the title.
+All recipe titles can be seen using `view r` command.
+
+- `delete r/poached eggs`
+**Expected**: Recipe with title `poached eggs` will be deleted.
+
+
+
+
+### Check Recipe Functions
+(
)
+
+**Prerequisites**:
+
+- Ensure that you have the recipe `dumpling soup` added to your list of recipes already
+ - `add r/dumpling soup i/chicken broth,100,g i/water,200,g i/dumpling,5,pc s/put broth into boiling water s/boil dumping in water`
+- Ensure that you don't have "chicken broth" ingredient in your ingredient inventory
+- Ensure that you have 2 dumplings in your inventory
+ - `add i/dumpling,2,pc`
+
+**Test Cases**:
+- `check dumpling soup`
+ **Expected:** A message will appear, indicating that you need to get 3 more pieces of dumplings and taht you are missing chicken broth from your ingredient inventory
+- `check 1`
+ **Expected:** A message will appear indicating the ingredients you need for recipe with index 1. To check which recipe it is, use `view r` command
+
+
+
+### Filter Recipe Functions
+(
)
+
+**Prerequisites**:
+
+- Ensure that the following recipes are added to your `RecipeList` beforehand.
+ - A recipe with bread ingredient: `add r/sausage bread i/bread,1,pc i/sausage,1,pc s/fry sausage in pan s/place sausage in bread`
+ - A recipe without bread ingredient: `add r/dumpling soup i/chicken broth,100,g i/water,200,g i/dumpling,5,pc s/put broth into boiling water s/boil dumping in water`
+
+**Test Cases**:
+
+- `filter recipe i/bread`
+ **Expected:** A message showing all recipes containing bread as an ingredient. In this case, `sausage bread` will definitely be on the list.
+
+
+
+### Execute Recipe Functions
+(
)
+
+**Prerequisites**:
+
+- Ensure that you have sufficient ingredients for "dumpling soup" recipe
+ - `add i/chicken broth,100,g i/water,200,g i/dumpling,5,pc`
+- Ensure that you have added the recipe "dumpling soup"
+ - `add r/dumpling soup i/chicken broth,100,g i/water,200,g i/dumpling,5,pc s/put broth into boiling water s/boil dumping in water`
+
+**Test Cases**:
+- `execute dumpling soup`
+ **Expected:** A message on the updated quantity of your ingredients will be shown
+
+
+
+### Plan Recipe Functions
+(
)
+
+**Prerequisites**:
+
+- Ensure that you have at least 2 recipes in your `RecipeList`, if not, use the following commands
+ - `add r/sausage bread i/bread,1,pc i/sausage,1,pc s/fry sausage in pan s/place sausage in bread`
+ - `add r/dumpling soup i/chicken broth,100,g i/water,200,g i/dumpling,5,pc s/put broth into boiling water s/boil dumping in water`
+
+**Test Cases**:
+- `plan 2 r/1 r/2`
+ **Expected:** A message showing all reicpes you have chosen, all ingredients needed for all recipes, and ingredients that you need to buy as you don't have sufficient
+
+
+
+### Duplicate Recipe
+(
)
+
+**Prerequisites**: There should be at least 1 recipe in the list, and you know the title.
+All recipe titles can be seen using `view r` command.
+
+- `duplicate r/poached eggs`
+ **Expected**: Recipe with title `poached eggs (copy)` will be created.
+
+
+
+### Adding a shortcut
+(
)
+
+**Prerequisites**:
+
+- Ingredient `bread` should be added into the ingredient list beforehand using `add i/bread,2,pc`. Ingredient called
+ `apple` should not be in the list.
+
+**Test Cases**:
+
+- `add sc/bread,2`
+ **Expected**: A shortcut for adding `2` quantity of ingredient `bread` will be added into the shortcut list.
+
+- `add sc/apple,2`
+ **Expected**: Exception will be thrown due to not available ingredient.
+
+
+### Viewing shortcuts
+(
)
+
+**Prerequisites**:
+
+- There should be at least 1 shortcut in the list.
+
+**Test Case**:
+
+- `view sc`
+ **Expected**: All available shortcuts will be displayed.
+
+
+### Editing shortcuts
+(
)
+
+**Prerequisites**:
+
+- Ingredient `bread` and `apple` should be added into the ingredient list beforehand using `add i/bread,2,pc` and
+ `add i/apple,1,kg`.
+
+**Test Cases**:
+
+- `edit i/bread n/apple q/3`
+ **Expected**: Shortcut for `bread` will be changed to `apple` and its quantity will be changed to `3`.
+
+- `edit i/apple q/4 n/bread`
+ **Expected**: Shortcut for `apple` will be changed to `bread` and its quantity will be changed to `4`.
+
+- `edit i/bread q/-3`
+ **Expected**: Exception thrown due to invalid quantity.
+
+- `edit i/egg q/3`
+ **Expected**: Exception thrown due to non-existing shortcut.
+
+
+### Deleting shortcuts
+(
)
+
+**Prerequisites**:
+
+- Ingredient `bread` and `apple` should be added into the ingredient list beforehand using `add i/bread,2,pc` and
+ `add i/apple,1,kg`. Then, shortcuts should be created using `add sc/bread,2` and `add sc/apple,2`. Shortcut for
+ `bread` should be made first.
+
+**Test Cases**:
+
+- `delete sc/apple`
+ **Expected**: Shortcut for `apple` will be deleted.
+
+- `delete sc/1`
+ **Expected**: Shortcut for `bread` will be deleted.
+
+- `delete sc/10000`
+ **Expected**: Exception will be thrown due to accessing out of bounds data.
-## User Stories
+
+### Using shortcuts
+(
)
-|Version| As a ... | I want to ... | So that I can ...|
-|--------|----------|---------------|------------------|
-|v1.0|new user|see usage instructions|refer to them when I forget how to use the application|
-|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list|
+**Prerequisites**:
-## Non-Functional Requirements
+- Ingredient `bread` should be added into the ingredient list beforehand using `add i/bread,2,pc`. Then, shortcut
+ should be created using `add sc/bread,2`.
-{Give non-functional requirements}
+**Test Cases**:
-## Glossary
+- `sc bread`
+ **Expected**: Quantity for ingredient `bread` will be added by `2`.
-* *glossary item* - Definition
+- `sc invalid`
+ **Expected**: Exception thrown because shortcut does not exist.
-## Instructions for manual testing
+
+### Exit
+(
)
-{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing}
+**Test Cases**:
+- `exit`
+- **Expected:** An exit message will be shown and your recipes and ingredients will be saved in your text files
diff --git a/docs/README.md b/docs/README.md
deleted file mode 100644
index bbcc99c1e7..0000000000
--- a/docs/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# Duke
-
-{Give product intro here}
-
-Useful links:
-* [User Guide](UserGuide.md)
-* [Developer Guide](DeveloperGuide.md)
-* [About Us](AboutUs.md)
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index abd9fbe891..d37f3aaadd 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -1,42 +1,545 @@
+---
+layout: default
+title: User Guide
+---
+
+EssenMakanan is a **desktop app for managing recipes and ingredients in your inventory, 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, EssenMakanan can get your recipes and ingredients management tasks done faster than traditional GUI apps.
+
+ **Important Note Before Starting**:
+ Ingredients refer to the items in your inventory, while recipes' ingredients refer to the ingredients needed to cook the recipe.
+
+--------------------------------------------------------------------------------------------------------------------
# User Guide
## Introduction
-{Give a product intro}
+EssenMakanan is an app that keeps track of ingredients that a user has in the kitchen, stores recipes and provides steps on how to cook a specific recipe. This app will include a command line interface to use the available commands in the app.
## Quick Start
-{Give steps to get started quickly}
+1. The app requires Java 11 to be installed into your computer or laptop.
+2. Download the latest `Essenmakanan.jar` from [here](https://github.com/AY2324S1-CS2113-F11-2/tp/releases)
+3. Copy and move the file into the selected folder you want to put the app in.
+4. Open your command line and input the command below to run the app:
+`java -jar Essenmakanan.jar`
-1. Ensure that you have Java 11 or above installed.
-1. Down the latest version of `Duke` from [here](http://link.to/duke).
+## Features
+#### Notes about the command format:
+* Words in `UPPER_CASE` are the parameters to be supplied by the user.
+ e.g. in `add r/RECIPE_TITLE s/STEP i/INGREDIENT`, `RECIPE_TITLE`, `STEP` and `INGREDIENT` are parameters which
+ can be used. Example: `add r/bread s/mix flour i/flour,200,g`.
-## Features
-{Give detailed description of each feature}
+* Items in square brackets are optional.
+ e.g `edit i/INGREDIENT_NAME [n/NEW_NAME] [q/NEW_QUANTITY] `.
+ The commands`edit i/bread n/toast q/10`, `edit i/bread n/toast` or `edit i/bread q/10` are all valid.
-### Adding a todo: `todo`
-Adds a new item to the list of todo items.
-Format: `todo n/TODO_NAME d/DEADLINE`
+* tags with `...` after them can be used multiple times including zero times.
+ e.g. `add i/INGREDIENT_NAME,QUANTITY,UNIT [i/...]` can be used as `i/egg,2,pc`, `i/egg,2,pc i/flour,1,kg i/oil,2,l`.
+ Any number of ingredients after `i/` is valid as long as the format is correct.
-* The `DEADLINE` can be in a natural language format.
-* The `TODO_NAME` cannot contain punctuation.
+
+* Extraneous parameters for commands that do not take in parameters (such as `help`, `list`, `exit`)
+ will be ignored.
+ e.g. if the command specifies `help 123`, it will be interpreted as `help`.
-Example of usage:
+
+### [System Summary of Commands](#feature-system)
-`todo n/Write the rest of the User Guide d/next week`
+| Action | Format | Example |
+|------------------------------------------------|-------------------------------------------------------------|------------------------------------------------------------------------------------|
+| [Help](#help)
(list all commands available) | help | help |
-`todo n/Refactor the User Guide to remove passive voice d/13/04/2020`
+---
-## FAQ
+
+
+### [Recipes Summary of Commands](#feature-recipe)
+
+| Action | Format | Example |
+|----------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
+| [View all recipes](#view-recipes) | view r | view r |
+| [View specific recipe](#view-recipe) | view r/RECIPE_ID | view r/1 |
+| [View all available recipes](#view-available-recipe) | view ar| view ar |
+| [Add recipe](#add-recipe) | add r/RECIPE_TITLE [t/TAG] s/STEP_DESCRIPTION [s/...] [d/DURATION] i/INGREDIENT_NAME,QUANTITY,UNIT [i/...] | add r/scramble egg t/1 s/buy ingredients t/2 s/wash ingredients s/bake ingredients i/egg,2,pc |
+| [Edit recipe](#edit-recipe) | edit r/RECIPE_TITLE [n/NEW_TITLE] [s/STEP_INDEX,NEW_STEP_DESCRIPTION] [ i/INGREDIENT_INDEX, [n-NEW_INGREDIENT_NAME], [q-NEW_INGREDIENT_QUANTITY], [u-NEW_INGREDIENT_UNIT] ] | edit r/bread n/white bread s/1,new step i/1,n-new ingredient name,q-new ingredient quantity, u-new ingredient |
+| [Delete Recipe](#delete-recipe) | delete r/RECIPE_TITLE OR delete r/RECIPE_INDEX | delete r/meatball noodles OR delete r/2 |
+| [Duplicate Recipe](#duplicate-recipe) | duplicate RECIPE_NAME or duplicate RECIPE_INDEX | duplicate sandwich or duplicate 1 |
+| [Check recipe](#check-recipe)
(view missing ingredients from a recipe) | check RECIPE_TITLE
check RECIPE_ID | check dumpling noodles
check 1 |
+| [Filter recipe by ingredients](#filter-recipe) | filter recipe i/INGREDIENT_NAME [i/...] | filter recipe i/chicken i/noodles |
+| [Plan recipes for the week](#plan-recipe) | plan NUMBER_OF_RECIPES r/RECIPE_ID [r/...] | plan 2 r/1 r/3 |
+| [Execute recipe](#execute-recipe) | execute RECIPE_TITLE | execute dumpling noodles |
+
+---
+
+
+### [Ingredient Summary of Commands](#feature-ingredient)
+
+| Action | Format | Example |
+|----------------------------------------------------|-------------------------------------------------------------------|-------------------------------|
+| [View all ingredients](#view-ingredients) | view i | view i |
+| [View specific ingredient](#view-ingredient) | view i/INGREDIENT_NAME
view i/INGREDIENT_ID | view i/bread
view i/1 |
+| [Add ingredient](#add-ingredient) | add i/INGREDIENT_NAME,QUANTITY,VALID_UNIT | add i/eggs,2,pc |
+| [Edit ingredient](#edit-ingredient) | edit i/INGREDIENT_NAME [n/NEW_NAME] [q/NEW_QUANTITY] [u/NEW_UNIT] | edit i/bread n/toast q/3 u/pc |
+| [Delete Ingredient](#delete-ingredient) | delete r/INGREDIENT_NAME OR delete r/INGREDIENT_ID | delete i/egg OR delete i/1 |
+| [Use Ingredient](#use-ingredient) | use i/INGREDIENT_NAME,QUANTITY,VALID_UNIT [i/...] | use i/chicken,1,kg |
+
+
+----
+
+
+### [Shortcut Summary of Commands](#feature-shortcut)
+
+| Action | Format | Example |
+|---------------------------------------|-------------------------------------------------------|------------------------------|
+| [View all shortcuts](#view-shortcuts) | view sc | view sc |
+| [Add shortcut](#add-shortcut) | add sc/INGREDIENT_NAME,QUANTITY | add sc/eggs,2 |
+| [Edit shortcut](#edit-shortcut) | edit sc/INGREDIENT_NAME [n/NEW_NAME] [q/NEW_QUANTITY] | edit sc/bread n/eggs q/3 |
+| [Delete shortcut](#delete-shortcut) | delete sc/INGREDIENT_NAME OR delete sc/SHORTCUT_ID | delete sc/egg OR delete sc/1 |
+| [Use shortcut](#use-shortcut) | sc INGREDIENT_NAME or sc SHORTCUT_ID | sc chicken OR sc 1 |
+
+
+----
+
+### Tags
+
+| Tag Index | Tag Description |
+|-----------|--------------------|
+| 1 | NIGHT_BEFORE |
+| 2 | MORNING_OF_COOKING |
+| 3 | MORE_THAN_ONE_DAY |
+| 4 | ACTUAL_COOKING |
+
+----
+
+### Units
+
+| Unit (to be used) | Full Unit Description |
+|-------------------|-----------------------|
+| g | Gram |
+| kg | Kilogram |
+| ml | Mililitre |
+| l | Litres |
+| tsp | Teaspoon |
+| tbsp | Tablespoon |
+| cup | Cup |
+| pc | Piece |
+
+----
+
+### System Commands
+
+
+
+#### Viewing help – `help`
+(
)
+
+List all commands and the format and brief description of each command.
+
+---
+
+### Recipe Commands
+
+
+#### View all recipes - `view r`
+(
)
+
+List all recipes available inside the app.
+
+---
+
+
+#### View specific recipe - `view r/RECIPE_ID`
+(
)
+
+View the steps of a specified recipe with RECIPE_ID
+
+Examples :
+
+* `view r/1`
+to show a recipe at index ‘1’ of the list.
+
+---
+
+
+### View all available recipes - `view ar`
+(
)
+
+View all recipes you are able to execute given the ingredients you have in your ingredient inventory.
+
+For example
+- With the following ingredients in my inventory,
+
+
+
+- I am able to see which recipes only require at most 200g of flour and 1 egg using the `view ar` command
+
+
+
+- As seen, I am able to cook recipes "bread" and "scrambled egg" with my current inventory ingredients!
+
+---
+
+
+#### Add new recipe - `add r/RECIPE_TITLE [t/TAG] s/STEP_DESCRIPTION [s/...] [d/DURATION] i/INGREDIENT_NAME,QUANTITY,UNIT [i/...]`
+(
)
+
+Add a new recipe to the list of recipes. A user is able to add more than one
+step and ingredient to a recipe in 1 line.
+
+Note
+- Multiple steps and ingredients can be added
+- `RECIPE_TITLE`, `STEPS` and `INGREDIENT` are compulsory fields and at least 1 field must be entered
+- `DURATION (d/)` and `TAGS (t/)` is optional. [Registered tags in our app](#tags)
+- `t/` must come immediately after `s/`, else defaulted as tag 4
+
+
+Examples :
+
+- **`t/` comes before the steps `\s` which belongs to the tag** :
+`add r/apple pie t/1 s/step belongs to tag 1 t/2 s/step belongs to tag 2 s/another step belongs to tag 2 i/apple,1,kg`
+
+
+- **`d/` must come immediately after `s/`**
+
+
+- **Full Example** :
+`add r/meatball pasta t/1 s/defrost meatballs d/30mins t/2 s/Boil pasta s/fry meatballs i/pasta,100,g i/meatball,3,pc`
+
+ 
+
+
+- **Full Example 2 (default tag = 4 = ACTUAL_COOKING)** :
+ `add r/meatball pasta t/1 s/defrost meatballs d/30mins t/2 s/Boil pasta s/fry meatballs i/pasta,100,g i/meatball,3,pc`
+
+
+---
+
+
+#### Delete a specific recipe - `delete r/RECIPE_ID` or `delete r/RECIPE_TITLE`
+(
)
+
+Delete the recipe from the recipe list.
+
+
+Example :
+
+* `delete r/1`
+
+ to delete a recipe at index ‘1’
+
+
+* `delete r/meatball pasta`
+
+ to delete recipe titled "meatball pasta"
+
+---
+
+
+#### Edit a recipe - `edit r/RECIPE_TITLE [n/NEW_TITLE] [s/STEP_INDEX,NEW_STEP_DESCRIPTION] [ i/INGREDIENT_INDEX, [n-NEW_INGREDIENT_NAME], [q-NEW_INGREDIENT_QUANTITY], [u-NEW_INGREDIENT_UNIT] ]`
+(
)
+
+Edit a recipe to change the name, steps or ingredients of a recipe.
+
+Note:
+- `RECIPE_TITLE` must be specified
+- Choose 1 or more field from the following to edit: `n/NEW_NAME`, `s/STEP_ID,NEW_STEP` and `i/INRGEDIENT_ID,NEW_INGREDIENT_NAME`
+- When editing step or ingredient of a recipe, first provide the step and ingredient id. You can check it using `view r/RECIPE_TITLE`.
+In the example below, id of "meatball" is 2, as seen in the list.
+
+- When editing an ingredient, you can choose to edit 1 or more fields:
+`n-NEW_INGREDIENT_NAME`, `q-NEW_INGREDIENT_QUANTITY` and `u-NEW_INGREDIENT_UNIT`.
**Note that the flag is a dash (-) when we are editing an ingredient of a recipe and not slash (/)**
+
+Example :
+
+ * `edit r/bread n/toast` to change `bread` to `toast`
+ * `edit r/bread s/1,beat eggs` to change the first step to "beat eggs"
+ * `edit r/bread i/1,n-flour` to change the first ingredient's name to "flour"
+ * `edit r/bread i/2,q-20.0` to change the second ingredient's quantity to "20.0"
+ * `edit r/bread i/3,u-kg` to change the third ingredient's unit to "kg"
+
+Full Example :
+
+
+ * `edit r/bread s/1,buy ingredients` to change the first step to `buy ingredients`
+
+
+---
+
+
+#### Duplicate a recipe - `duplicate RECIPE_NAME` or `duplicate RECIPE_INDEX`
+(
)
+
+Duplicate a recipe from the recipe list. The duplicated recipe with have `(copy)` within its name.
+
+Example :
+
+* `duplicate sandwich` to duplicate a recipe with the title `sandwich`
+* `duplicate 1` to duplicate the first recipe on the list.
+
+
+---
+
+
+#### Check a recipe - `check RECIPE_TITLE` or `check RECIPE_ID`
+(
)
+
+Use the check command to check if you are all set to start on the recipe.This command will list all missing ingredients from the recipe you want to start on.
+
+
+
+Example:
+
+* `check bread`
+
+ To check if you have all ingredients needed for the recipe named "bread".
+
+
+
+
+* `check 1`
+
+ To check if you have all ingredients needed for the recipe with id 1.
+
+
-**Q**: How do I transfer my data to another computer?
-**A**: {your answer here}
+---
+
+
+#### Filter recipe based by ingredients - `filter recipe i/INGREDIENT_NAME`
+(
)
+
+Filter your recipes by ingredients you are craving for that meal.
+
+
+
+Example:
+* `filter recipe i/egg`
+
+ All recipes containing the ingredient egg will be printed
+
+* `filter recipe i/egg i/vegetable`
+
+
+
+---
+
+
+#### Plan recipes for the week - `plan NUMBER_OF_RECIPES r/RECIPE_ID [r/...]`
+(
)
+
+Schedule recipes for a week or for as many days you want by using our plan command!
+This allows you to plan for your grocery trip in advance.
+
+Upon using the command, we will show you all the ingredients needed for all your planned recipes and ingredients that you are missing.
+
+Note:
+- `NUMBER_OF_RECIPES` must correspond to the total number of recipes ("r/...") you enter. This is a compulsory field.
+- You need to input at least 1 recipe to plan
+- To view your recipe ids, use the `view r` command
+
+Example:
+- `plan 2 r/1 r/2`
+
+
+---
+
+
+#### Execute a recipe `execute RECIPE_TITLE`
+(
)
+
+Use this command to start a recipe and automatically remove the ingredients used from the inventory.
+
+Note:
+- Make sure to use `check` command before using this command to
+ ensure you have all the ingredients needed for the recipe.
+- Add missing ingredients to your inventory using [Add Ingredient](#add-ingredient) command.
+
+
+Example:
+- `execute bread`
+
+
+---
+
+
+### Ingredient Commands
+
+
+#### View all ingredients - `view i`
+(
)
+
+List all ingredients available inside the app.
+
+---
+
+
+#### View a specific ingredient - `view i/INGREDIENT_NAME` or `view i/INGREDIENT_ID`
+(
)
+
+Check the quantity of an ingredient you have available in your kitchen/inventory.
+
+Example :
+
+* `view i/flour`
+
+ 
+
+* `view i/1` to view the quantity of your ingredient with id 1
+
+---
+
+
+#### Add ingredients - `add i/INGREDIENT_NAME,QUANTITY,UNIT`
+(
)
+
+Adds a new item to the list of ingredients. [Registered units in our app](#units)
+
+When entering your unit, a user is required to use any one of the units above.
+
+Note :
+* Make sure when adding more of an existing ingredients, the units match
+
+
+Example :
+
+* `add i/bread,2,pc` to add `2 pieces of bread` into the list
+* `add i/cooking oil,5,l` to add `5 liters of cooking oil` into the list
+
+* `add i/cooking oil,5,l i/bread,2,pc` to add 2 ingredients in one command
+
+---
+
+
+#### Edit ingredient - `edit i/INGREDIENT_NAME edit i/INGREDIENT_NAME n/NEW_NAME q/NEW_QUANTITY u/NEW_UNIT`
+(
)
+
+Edit an ingredient to change the name, quantity or unit. A user is able to edit more than one property of an
+ingredient.
+
+Example :
+
+* `edit i/bread n/toast` to change `bread` to `toast`
+* `edit i/egg q/10 u/kg` to change the quantity to `10` and the unit to `kg`
+
+---
+
+#### Delete ingredient - `delete i/INGREDIENT_INDEX` OR `delete i/INGREDIENT_NAME`
+(
)
+
+Delete an ingredient based on the selected index in the list or the ingredient's name.
+
+Example :
+
+* `delete i/2` to delete the `second` ingredient on the list
+* `delete i/egg` to delete `egg` ingredient
+
+---
+
+
+#### Use ingredient - `use i/chicken,1,kg`
+(
)
+
+Use ingredients in inventory, decrease the quantity of the ingredient by the amount used.
+
+Note :
+* Make sure units match when you are trying to use an ingredient.
+
+
+Examples :
+* use i/chicken,1,kg to use 1kg of chicken from the inventory
+* use i/egg,2,pc i/chicken,1,kg to use 2 pieces of egg and 1g of chicken from the inventory
+
+
+---
+
+
+### Shortcut Commands
+
+
+#### View All Shortcuts - `view sc`
+(
)
+
+List all shortcuts available inside the app.
+
+---
+
+#### Add Shortcut - `add sc/INGREDIENT_NAME,QUANTITY`
+(
)
+
+Add a shortcut into the app with specified ingredient name and quantity.
+
+Note :
+* The ingredient name must be in the ingredient list beforehand.
+
+
+
+Example:
+* `add sc/bread,2` to add a shortcut to ingredient bread that have quantity of `2`
+
+
+
+---
+
+
+#### Edit Shortcut - `edit sc/INGREDIENT_NAME n/NEW_NAME q/NEW_QUANTITY or edit sc/SHORTCUT_INDEX n/NEW_NAME q/NEW_QUANTITY`
+(
)
+
+Edits a shortcut with the given changes. These changes can include a new name, a new quantity or both.
+
+Notes:
+* Only one of each flag is allowed for each input.
+
+
+
+* The new ingredient name must be in the ingredient list.
+
+Example:
+* `edit sc/bread n/egg q/3` to edit shortcut `bread` to ingredient `egg` and the quantity to `3`
+
+
+
+---
+
+
+#### Delete Shortcut - `delete sc/INGREDIENT_NAME OR delete sc/SHORTCUT_INDEX`
+(
)
+
+Deletes a shortcut with the given name or index.
+
+Example:
+* `delete sc/bread` to delete shortcut to ingredient `bread`
+
+
+
+---
+
+
+#### Use Shortcut - `sc INGREDIENT_NAME or sc SHORTCUT_ID`
+(
)
+
+Uses a shortcut to add quantity to the ingredient based on the shortcut's name and quantity.
+
+Example:
+* `sc egg` to add quantity to ingredient `egg`
+
+
+
+---
+
+## FAQ
+
+**Q**: Why can't I execute a recipe?
-## Command Summary
+**A**: Use `check` command to ensure that you have all the ingredients needed for the recipe.
-{Give a 'cheat sheet' of commands here}
+**Q**: Why is there a step ID??
-* Add todo `todo n/TODO_NAME d/DEADLINE`
+**A**: Enable easy referencing when editing steps
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000000..13866cfa24
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1,3 @@
+remote_theme: pages-themes/cayman@v0.2.0
+plugins:
+- jekyll-remote-theme # add this line to the plugins list if you already have one
diff --git a/docs/diagrams/AddNewIngredientSequenceDiagram.puml b/docs/diagrams/AddNewIngredientSequenceDiagram.puml
new file mode 100644
index 0000000000..b38a1dc4d6
--- /dev/null
+++ b/docs/diagrams/AddNewIngredientSequenceDiagram.puml
@@ -0,0 +1,71 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":AddIngredientCommand" as AddIngredientCommand COMMAND_COLOR
+participant "<>\n:IngredientParser" as IngredientParser INGREDIENT_PARSER_COLOR
+participant ":Ingredient" as Ingredient INGREDIENT_COLOR
+participant "ingredients:IngredientList" as ingredients INGREDIENT_LIST_COLOR
+participant "<>\n:Ui" as Ui UI_COLOR
+
+[-> AddIngredientCommand: executeCommand()
+
+activate AddIngredientCommand
+
+loop //all ingredients//
+ AddIngredientCommand -> IngredientParser: parseIngredient(ingredients, newIngredientDetails)
+ activate IngredientParser
+
+ IngredientParser -> IngredientParser : mapIngredientUnit(ingredientUnitValue)
+ activate IngredientParser
+ deactivate IngredientParser
+
+
+ IngredientParser -[hidden]> Ingredient
+ create Ingredient
+ IngredientParser -> Ingredient : new Ingredient(ingredientName, ingredientQuantity, ingredientUnit)
+ activate Ingredient
+
+ Ingredient --> IngredientParser : newIngredient
+ deactivate Ingredient
+
+ IngredientParser --> AddIngredientCommand : newIngredient
+ deactivate IngredientParser
+
+ AddIngredientCommand -> ingredients: addIngredient(newIngredient)
+
+ activate ingredients
+ ingredients -> ingredients: ingredients.add(newIngredient)
+ activate ingredients RECIPE_LIST_COLOR_T1
+ deactivate ingredients
+
+ ingredients --> AddIngredientCommand
+ deactivate ingredients
+
+
+ AddIngredientCommand -> Ui: printAddIngredientsSuccess(newIngredient.getname())
+
+ activate Ui
+
+ Ui -> Ui: print ingredient added success message
+ activate Ui
+ deactivate Ui
+
+ Ui -> Ui: print divider
+ activate Ui
+
+ deactivate Ui
+
+ Ui --> AddIngredientCommand
+ deactivate Ui
+
+end
+
+
+
+[<--AddIngredientCommand
+
+deactivate AddIngredientCommand
+
+
+@enduml
diff --git a/docs/diagrams/AddNewRecipeSequenceDiagram.puml b/docs/diagrams/AddNewRecipeSequenceDiagram.puml
new file mode 100644
index 0000000000..046287ce2e
--- /dev/null
+++ b/docs/diagrams/AddNewRecipeSequenceDiagram.puml
@@ -0,0 +1,54 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":AddRecipeCommand" as AddRecipeCommand COMMAND_COLOR
+participant "<>\nRecipeParser" as RecipeParser RECIPE_PARSER_COLOR
+participant ":Recipe" as Recipe RECIPE_COLOR
+participant "recipes:RecipeList" as Recipes RECIPE_LIST_COLOR
+participant "<>\nUi" as Ui UI_COLOR
+
+[-> AddRecipeCommand : executeCommand()
+activate AddRecipeCommand
+
+AddRecipeCommand -> RecipeParser : parseRecipeTitle(input)
+activate RecipeParser
+
+RecipeParser --> AddRecipeCommand : recipeTitle
+deactivate RecipeParser
+
+AddRecipeCommand -[hidden]> RecipeParser
+
+AddRecipeCommand -[hidden]> Recipe
+create Recipe
+AddRecipeCommand -> Recipe : new Recipe(RecipeTitle)
+activate Recipe
+
+Recipe --> AddRecipeCommand : newRecipe
+deactivate Recipe
+
+AddRecipeCommand -[hidden]> Recipes
+AddRecipeCommand -> Recipes : addRecipe(newRecipe)
+activate Recipes
+
+Recipes -> Recipes: recipes.add(newRecipe)
+activate Recipes
+deactivate Recipes
+
+Recipes --> AddRecipeCommand
+deactivate Recipes
+
+AddRecipeCommand -[hidden]> Ui
+AddRecipeCommand -> Ui : showRecentAddedRecipe(recipeTitle)
+activate Ui
+
+Ui -> Ui: print recent added recipe
+activate Ui
+deactivate Ui
+
+Ui --> AddRecipeCommand
+deactivate Ui
+
+[<--AddRecipeCommand
+deactivate AddRecipeCommand
+@enduml
diff --git a/docs/diagrams/AddNewShortcutSequenceDiagram.puml b/docs/diagrams/AddNewShortcutSequenceDiagram.puml
new file mode 100644
index 0000000000..68d5408c3c
--- /dev/null
+++ b/docs/diagrams/AddNewShortcutSequenceDiagram.puml
@@ -0,0 +1,63 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":AddShortcutCommand" as AddShortcutCommand COMMAND_COLOR
+participant "<>\nShortcutParser" as ShortcutParser SHORTCUT_PARSER_COLOR
+participant "shortcut:Shortcut" as Shortcut SHORTCUT_COLOR
+participant "shortcuts:ShortcutList" as Shortcuts SHORTCUT_LIST_COLOR
+participant "<\nIngredientParser" as IngredientParser INGREDIENT_PARSER_COLOR
+participant "ingredients:IngredientList" as Ingredients INGREDIENT_LIST_COLOR
+participant ":Ingredient" as Ingredient INGREDIENT_COLOR
+participant "<>\nUi" as Ui UI_COLOR
+
+[-> AddShortcutCommand : executeCommand()
+activate AddShortcutCommand
+
+AddShortcutCommand -> ShortcutParser : parseShortcut(ingredients,input)
+activate ShortcutParser
+
+create Shortcut
+ShortcutParser -> Shortcut : new Shortcut(name, quantity)
+activate Shortcut
+
+Shortcut --> ShortcutParser
+deactivate Shortcut
+
+ShortcutParser --> AddShortcutCommand : shortcut
+deactivate ShortcutParser
+
+AddShortcutCommand -> IngredientParser : getIngredientIndex(ingredients, ingredientName)
+activate IngredientParser
+
+IngredientParser --> AddShortcutCommand
+deactivate IngredientParser
+
+AddShortcutCommand -> Ingredients : getIngredient(index)
+activate Ingredients
+
+Ingredients --> AddShortcutCommand
+deactivate Ingredients
+
+AddShortcutCommand -> Ingredient : getUnit()
+activate Ingredient
+
+Ingredient --> AddShortcutCommand
+deactivate Ingredient
+
+AddShortcutCommand -> Shortcuts : addShortcut(shortcut)
+activate Shortcuts
+
+Shortcuts --> AddShortcutCommand
+deactivate Shortcuts
+
+AddShortcutCommand -> Ui : printAddShortcutSuccess(shortcut, unit)
+activate Ui
+
+Ui --> AddShortcutCommand
+deactivate Ui
+
+[<-- AddShortcutCommand
+deactivate AddShortcutCommand
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/ArchitectureDiagram.puml b/docs/diagrams/ArchitectureDiagram.puml
new file mode 100644
index 0000000000..b0544af63e
--- /dev/null
+++ b/docs/diagrams/ArchitectureDiagram.puml
@@ -0,0 +1,47 @@
+@startuml
+!include style.puml
+
+!include
+!include
+
+hide footbox
+hide members
+
+
+Package " "<>{
+ Class UI UI_COLOR
+ Class Main MAIN_COLOR
+ Class Storage STORAGE_COLOR
+ Class Parser PARSER_COLOR
+ Class Command COMMAND_COLOR
+ Class Shortcut SHORTCUT_COLOR
+ Class Ingredient INGREDIENT_COLOR
+ Class Recipe RECIPE_COLOR
+ Class Logger LOGGER_COLOR
+}
+
+Class "<$user>" as User PERSON_COLOR
+Class "<$documents>" as File UI_COLOR
+
+
+UI -[#red]> Main
+
+Main -right[#black]-> UI
+Main -[#black]-> Storage
+Main -[#black]-> Parser
+
+Logger -[#orange].>File
+
+Parser -[#green]-> Command
+Parser -[#green]-> Main
+
+Command -[#blue]-> Recipe
+Command -[#blue]-> Ingredient
+Command -[#blue]-> Shortcut
+
+Storage -[STORAGE_COLOR].> Main
+Storage -[STORAGE_COLOR]-> Logger
+
+Storage .right[STORAGE_COLOR].>File
+User ..> UI
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/CheckRecipeSequenceDiagram.puml b/docs/diagrams/CheckRecipeSequenceDiagram.puml
new file mode 100644
index 0000000000..5f4d1071f9
--- /dev/null
+++ b/docs/diagrams/CheckRecipeSequenceDiagram.puml
@@ -0,0 +1,62 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+actor User
+participant ":EssenMakanan" as EssenMakanan MAIN_COLOR
+participant ":Parser" as Parser PARSER_COLOR
+participant "command:CheckRecipeCommand" as Command COMMAND_COLOR
+participant "<>\n:RecipeParser" as RecipeParser RECIPE_PARSER_COLOR
+participant "<>\nUi" as Ui INTERFACE_COLOR
+participant "ingredients:IngredientList" as Ingredients INGREDIENT_COLOR
+
+User -> EssenMakanan: input: start curry chicken
+activate EssenMakanan
+
+EssenMakanan -> Parser : parseCommand()
+activate Parser
+
+create Command
+Parser -> Command: Command()
+activate Command
+
+Command --> Parser : command
+
+Parser -> EssenMakanan: command
+deactivate Parser
+
+EssenMakanan -> Command: executeCommand()
+
+Command -> RecipeParser : getRecipeIndex()
+activate RecipeParser
+
+RecipeParser --> Command
+deactivate RecipeParser
+
+
+Command -> Command : getmissingIngredients()
+activate Command
+
+Command --> Command : missing ingredients
+deactivate Command
+
+
+Command -> Ui: printStartRecipeMessage()
+activate Ui
+
+Ui -> Ingredients: listIngredients()
+activate Ingredients
+
+Ingredients --> Ui
+deactivate Ingredients
+
+Ui --> Command
+
+deactivate Ui
+
+Command --> EssenMakanan
+deactivate Command
+
+deactivate EssenMakanan
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/DeleteShortcutSequenceDiagram.puml b/docs/diagrams/DeleteShortcutSequenceDiagram.puml
new file mode 100644
index 0000000000..3a5a310459
--- /dev/null
+++ b/docs/diagrams/DeleteShortcutSequenceDiagram.puml
@@ -0,0 +1,35 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":DeleteShortcutCommand" as DeleteShortcutCommand COMMAND_COLOR
+participant "<>\nShortcutParser" as ShortcutParser SHORTCUT_PARSER_COLOR
+participant "shortcut:Shortcut" as Shortcut SHORTCUT_COLOR
+participant "shortcuts:ShortcutList" as Shortcuts SHORTCUT_LIST_COLOR
+participant "<>\nUi" as Ui UI_COLOR
+
+[-> DeleteShortcutCommand : executeCommand()
+activate DeleteShortcutCommand
+
+DeleteShortcutCommand -> ShortcutParser : getShortcutIndex(shortcuts, inputDetail);
+activate ShortcutParser
+
+ShortcutParser --> DeleteShortcutCommand
+deactivate ShortcutParser
+
+DeleteShortcutCommand -> Shortcuts : deleteShortcut(index)
+activate Shortcuts
+
+Shortcuts -> Ui : printDeletedShortcut()
+activate Ui
+
+Ui --> Shortcuts
+deactivate Ui
+
+Shortcuts -> DeleteShortcutCommand
+deactivate Shortcuts
+
+[<-- DeleteShortcutCommand
+deactivate DeleteShortcutCommand
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/DuplicateRecipeSequenceDiagram.puml b/docs/diagrams/DuplicateRecipeSequenceDiagram.puml
new file mode 100644
index 0000000000..84e2bfe5d8
--- /dev/null
+++ b/docs/diagrams/DuplicateRecipeSequenceDiagram.puml
@@ -0,0 +1,48 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":DuplicateRecipeCommand" as DuplicateRecipeCommand COMMAND_COLOR
+participant "<>\n:RecipeParser" as RecipeParser RECIPE_PARSER_COLOR
+participant ":RecipeList" as RecipeList RECIPE_LIST_COLOR
+participant "copiedRecipe:Recipe" as CopiedRecipe RECIPE_COLOR
+participant "<>\n:Ui" as Ui UI_COLOR
+
+[-> DuplicateRecipeCommand : executeCommand()
+activate DuplicateRecipeCommand
+
+DuplicateRecipeCommand -> RecipeParser : getRecipeIndex(recipes, toDuplicate)
+activate RecipeParser
+
+RecipeParser --> DuplicateRecipeCommand
+deactivate RecipeParser
+
+DuplicateRecipeCommand -> RecipeList : getRecipe(index)
+activate RecipeList
+
+RecipeList --> DuplicateRecipeCommand
+deactivate RecipeList
+
+create CopiedRecipe
+DuplicateRecipeCommand -> CopiedRecipe : new Recipe(recipe)
+
+activate CopiedRecipe
+CopiedRecipe --> DuplicateRecipeCommand
+deactivate CopiedRecipe
+
+DuplicateRecipeCommand -> RecipeList : add(copiedRecipe)
+activate RecipeList
+
+RecipeList --> DuplicateRecipeCommand
+deactivate RecipeList
+
+DuplicateRecipeCommand -> Ui : printDuplicatedRecipe(copiedRecipe)
+activate Ui
+
+Ui --> DuplicateRecipeCommand
+deactivate Ui
+
+[<- DuplicateRecipeCommand
+deactivate DuplicateRecipeCommand
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/EditIngredientSequenceDiagram.puml b/docs/diagrams/EditIngredientSequenceDiagram.puml
new file mode 100644
index 0000000000..5f8723c010
--- /dev/null
+++ b/docs/diagrams/EditIngredientSequenceDiagram.puml
@@ -0,0 +1,51 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":EditIngredientCommand" as EditIngredientCommand COMMAND_COLOR
+participant "ingredients:IngredientList" as Ingredients INGREDIENT_LIST_COLOR
+participant "<>\nUi" as Ui UI_COLOR
+
+[-> EditIngredientCommand : executeCommand()
+activate EditIngredientCommand
+
+EditIngredientCommand --> Ingredients: getIngredient(ingredientName)
+activate Ingredients
+Ingredients --> EditIngredientCommand: ingredient object to edit
+
+
+EditIngredientCommand -[hidden]> Ingredients
+EditIngredientCommand -> Ingredients : editIngredient(ingredientToEdit, editDetails)
+
+loop for each editDetail
+alt edit name
+Ingredients -> Ingredients : setName(newName)
+activate Ingredients
+deactivate Ingredients
+
+else edit quantity
+Ingredients -> Ingredients : setQuantity(newQuantity)
+activate Ingredients
+deactivate Ingredients
+
+else edit unit
+Ingredients -> Ingredients : setUnit(newUnit)
+activate Ingredients
+deactivate Ingredients
+
+end alt
+
+Ingredients --> Ui : printMessage()
+activate Ui
+Ui --> Ingredients: output
+deactivate Ui
+end
+
+deactivate Ui
+Ingredients --> EditIngredientCommand
+deactivate Ingredients
+
+
+[<--EditIngredientCommand
+deactivate EditIngredientCommand
+@enduml
diff --git a/docs/diagrams/EditRecipeSequenceDiagram.puml b/docs/diagrams/EditRecipeSequenceDiagram.puml
new file mode 100644
index 0000000000..5de825746f
--- /dev/null
+++ b/docs/diagrams/EditRecipeSequenceDiagram.puml
@@ -0,0 +1,52 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":EditRecipeCommand" as EditRecipeCommand COMMAND_COLOR
+participant "recipes:RecipeList" as Recipes RECIPE_LIST_COLOR
+participant "<>\nUi" as Ui UI_COLOR
+
+[-> EditRecipeCommand : executeCommand()
+activate EditRecipeCommand
+
+EditRecipeCommand -> EditRecipeCommand : getRecipeTitle()
+activate EditRecipeCommand
+deactivate EditRecipeCommand
+
+EditRecipeCommand -> Recipes : getRecipe(recipeTitle)
+activate Recipes
+Recipes --> EditRecipeCommand : Recipe object
+
+EditRecipeCommand -> EditRecipeCommand : getAttributesToEdit()
+activate EditRecipeCommand
+deactivate EditRecipeCommand
+
+
+EditRecipeCommand -[hidden]> Recipes
+EditRecipeCommand -> Recipes : editRecipe(recipeToEdit, editDetails)
+
+loop for each editDetail
+ Recipes -> Recipes : editAttribute()
+
+note right
+ editAttribute() calls reuses and calls methods from other classes.
+ For example, editIngredient method that is in the IngredientList class,
+ but for brevity purposes, these details are omitted
+end note
+
+activate Recipes
+deactivate Recipes
+Recipes --> Ui : printMessage()
+activate Ui
+Ui --> Recipes: output
+deactivate Ui
+end
+
+deactivate Ui
+Recipes --> EditRecipeCommand
+deactivate Recipes
+
+
+[<--EditRecipeCommand
+deactivate EditRecipeCommand
+@enduml
diff --git a/docs/diagrams/EditShortcutSequenceDiagram.puml b/docs/diagrams/EditShortcutSequenceDiagram.puml
new file mode 100644
index 0000000000..23ed1c9103
--- /dev/null
+++ b/docs/diagrams/EditShortcutSequenceDiagram.puml
@@ -0,0 +1,80 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":EditShortcutCommand" as EditShortcutCommand COMMAND_COLOR
+participant "<>\nShortcutParser" as ShortcutParser SHORTCUT_PARSER_COLOR
+participant "shortcut:Shortcut" as Shortcut SHORTCUT_COLOR
+participant "shortcuts:ShortcutList" as Shortcuts SHORTCUT_LIST_COLOR
+participant "<>\nUi" as Ui UI_COLOR
+
+[-> EditShortcutCommand : executeCommand()
+activate EditShortcutCommand
+
+EditShortcutCommand -> ShortcutParser : getShortcutIndex(shortcut, shortcutDetail)
+activate ShortcutParser
+
+ShortcutParser --> EditShortcutCommand
+deactivate ShortcutParser
+
+EditShortcutCommand -> Shortcuts : getShortcut(index)
+activate Shortcuts
+
+Shortcuts --> EditShortcutCommand
+deactivate Shortcuts
+
+EditShortcutCommand -> ShortcutParser : editShortcut(shortcut, ingredients, shortcutDetail)
+activate ShortcutParser
+
+loop number of flags remaining
+alt name
+ShortcutParser -> ShortcutParser : editShortcutName()
+activate ShortcutParser
+
+ShortcutParser -> Ui : printEditShortcutName(oldName, newName);
+activate Ui
+
+Ui --> ShortcutParser
+deactivate Ui
+
+ShortcutParser -> Shortcut : setIngredientName(newName)
+activate Shortcut
+
+Shortcut --> ShortcutParser
+deactivate Shortcut
+
+ShortcutParser --> ShortcutParser
+deactivate ShortcutParser
+
+ShortcutParser -[hidden]-> ShortcutParser
+
+else quantity
+ShortcutParser -> ShortcutParser : editShortcutQuantity()
+activate ShortcutParser
+
+ShortcutParser -> Ui : printEditShortcutQuantity(oldQuantity, newQuantity);
+activate Ui
+
+Ui --> ShortcutParser
+deactivate Ui
+
+ShortcutParser -> Shortcut : setQuantity(newQuantity)
+activate Shortcut
+
+Shortcut --> ShortcutParser
+deactivate Shortcut
+
+ShortcutParser --> ShortcutParser
+deactivate ShortcutParser
+
+ShortcutParser -[hidden]-> ShortcutParser
+
+end
+end
+
+ShortcutParser --> EditShortcutCommand
+deactivate ShortcutParser
+
+[<-- EditShortcutCommand
+deactivate EditShortcutCommand
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/ExitSequenceDiagram.puml b/docs/diagrams/ExitSequenceDiagram.puml
new file mode 100644
index 0000000000..9b145aa586
--- /dev/null
+++ b/docs/diagrams/ExitSequenceDiagram.puml
@@ -0,0 +1,42 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+actor User
+
+participant ":EssenMakanan" as EssenMakanan MAIN_COLOR
+participant ":Parser" as Parser PARSER_COLOR
+participant "command:ExitCommand" as ExitCommand COMMAND_COLOR
+participant "<>\nUi" as Ui INTERFACE_COLOR
+
+User-> EssenMakanan : input: exit
+activate EssenMakanan
+
+EssenMakanan -> Parser : parseCommand()
+activate Parser
+
+create ExitCommand
+Parser -> ExitCommand
+activate ExitCommand
+
+
+ExitCommand --> Parser : command
+
+Parser --> EssenMakanan: command
+deactivate Parser
+
+EssenMakanan -> ExitCommand : executeCommand()
+
+ExitCommand -> Ui : bye()
+activate Ui
+
+Ui --> ExitCommand : exit message
+deactivate Ui
+
+ExitCommand --> EssenMakanan : exit message
+EssenMakanan --> User
+deactivate ExitCommand
+
+deactivate EssenMakanan
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/FilterRecipesSequenceDiagram.puml b/docs/diagrams/FilterRecipesSequenceDiagram.puml
new file mode 100644
index 0000000000..99fba46639
--- /dev/null
+++ b/docs/diagrams/FilterRecipesSequenceDiagram.puml
@@ -0,0 +1,48 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+actor User
+
+participant ":EssenMakanan" as EssenMakanan MAIN_COLOR
+participant ":Parser" as Parser PARSER_COLOR
+participant "command:FilterRecipeCommand" as Command COMMAND_COLOR
+participant "<>\nUi" as Ui INTERFACE_COLOR
+
+User-> EssenMakanan : input: view recipe i/rice i/...
+activate EssenMakanan
+
+EssenMakanan -> Parser : parseCommand()
+activate Parser
+
+create Command
+Parser -> Command
+activate Command
+
+Command --> Parser : command
+Parser --> EssenMakanan : command
+deactivate Parser
+
+EssenMakanan -> Command : executeCommand()
+
+loop#FFAEAE #FFE6E6 ingredient:ingredients
+
+Command -> Command : filterRecipes()
+activate Command
+Command --> Command : filtered recipes
+deactivate Command
+
+Command -> Ui : printFilteredRecipes()
+activate Ui
+
+Ui --> Command
+deactivate Ui
+
+end
+
+Command --> EssenMakanan
+EssenMakanan --> User
+
+
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/HelpFunctionSequenceDiagram.puml b/docs/diagrams/HelpFunctionSequenceDiagram.puml
new file mode 100644
index 0000000000..deefaf4338
--- /dev/null
+++ b/docs/diagrams/HelpFunctionSequenceDiagram.puml
@@ -0,0 +1,44 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+actor User
+
+participant ":EssenMakanan" as EssenMakanan MAIN_COLOR
+participant ":Parser" as Parser PARSER_COLOR
+participant "command:HelpCommand" as HelpCommand COMMAND_COLOR
+participant "<>\nUi" as Ui INTERFACE_COLOR
+
+User-> EssenMakanan : input: help
+activate EssenMakanan
+
+EssenMakanan -> Parser : parseCommand("help", recipes, ingredients)
+activate Parser
+
+create HelpCommand
+Parser -> HelpCommand
+activate HelpCommand
+
+
+HelpCommand --> Parser : command
+
+Parser --> EssenMakanan: command
+deactivate Parser
+
+EssenMakanan -> HelpCommand : executeCommand()
+
+HelpCommand -> Ui : showCommands()
+activate Ui
+
+Ui --> HelpCommand
+deactivate Ui
+
+HelpCommand --> EssenMakanan
+
+EssenMakanan --> User
+
+deactivate HelpCommand
+
+deactivate EssenMakanan
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/IngredientListClassDiagram.puml b/docs/diagrams/IngredientListClassDiagram.puml
new file mode 100644
index 0000000000..b5f59f57f3
--- /dev/null
+++ b/docs/diagrams/IngredientListClassDiagram.puml
@@ -0,0 +1,58 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor CLASS_ARROW_COLOR
+skinparam classBackgroundColor CLASS_DIAGRAM_COLOR
+
+Class IngredientList {
+ - ingredients: ArrayList
+
+ + getIngredients(): ArrayList
+ + getIngredient(index: Integer): Ingredient
+ + getSize(): Integer
+ + isEmpty(): Boolean
+ + exist(name: String): Boolean
+ + exist(index: Integer): Boolean
+ + getIndex(ingredient: Ingredient): Integer
+ + getIndex(name: String): Integer
+ + addIngredient(ingredient: Ingredient): Void
+ + updateIngredient(ingredient: Ingredient): Void
+ + {static}editIngredient(ingredient: Ingredient, details: String[]): Void
+ + deleteIngredient(index: Integer): Void
+ + listIngredients(): Void
+ + equals(list: IngredientList): Boolean
+
+}
+
+
+Class Ingredient {
+ - name: String
+ - quantity: Double
+
+ + getName(): String
+ + setName(name: String): Void
+ + getQuantity(): Double
+ + setQuantity(quantity: Double): Void
+ + getUnit(): IngredientUnit
+ + setUnit(unit: IngredientUnit): Void
+ + toString(): String
+ + equals(ingredient: Ingredient): Boolean
+}
+
+Class "<>\nIngredientUnit" as IngredientUnit {
+ GRAM
+ KILOGRAM
+ MILLILITER
+ LITER
+ TEASPOON
+ TABLESPOON
+ CUP
+ PIECE
+
+ + getValue(): String
+}
+
+IngredientList -down-> "*" Ingredient
+
+Ingredient "*" *-down-> "1" IngredientUnit
+@enduml
diff --git a/docs/diagrams/PlanRecipeSequenceDiagram.puml b/docs/diagrams/PlanRecipeSequenceDiagram.puml
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/docs/diagrams/RecipeListAdd.puml b/docs/diagrams/RecipeListAdd.puml
new file mode 100644
index 0000000000..9480c4c9fa
--- /dev/null
+++ b/docs/diagrams/RecipeListAdd.puml
@@ -0,0 +1,20 @@
+@startuml
+!include style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+skinparam ClassBackgroundColor #FFFFAA
+hide footbox
+hide members
+
+title 'add r/dumplings s/... i/...'
+
+class State1 as "meatball noodles:Recipe"
+class State2 as "chicken pizza:Recipe"
+class State3 as "dumplings:Recipe"
+
+class RecipeList as "recipes:RecipeList" #FFFFFF
+RecipeList -up-> State1
+RecipeList -up-> State2
+RecipeList -up-> State3
+
+@end
diff --git a/docs/diagrams/RecipeListClassDiagram.puml b/docs/diagrams/RecipeListClassDiagram.puml
new file mode 100644
index 0000000000..6d1d5c5a20
--- /dev/null
+++ b/docs/diagrams/RecipeListClassDiagram.puml
@@ -0,0 +1,116 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor CLASS_ARROW_COLOR
+skinparam classBackgroundColor CLASS_DIAGRAM_COLOR
+
+Class RecipeList {
+ - recipes: ArrayList
+
+ - listRecipeSteps(recipe: ArrayList): Void
+ - listRecipeIngredients(recipe: Recipe): Void
+ - noDescriptionExists(stepDetails: String[]): boolean
+ + getRecipes(): ArrayList
+ + addRecipe(recipe: Recipe): Void
+ + addRecipe(title: String, steps: String[]): Void
+ + deleteRecipe(index: Integer): Void
+ + getRecipe(index: Integer): Recipe
+ + getRecipe(name: String): Recipe
+ + getIndexOfRecipe(recipeTitle: String): Integer
+ + recipeExist(index: Integer): Boolean
+ + listRecipeTitles(): Void
+ + viewRecipe(index: Integer): Void
+ + editRecipe(existingRecipe: Recipe, editDetails:String[]): Void
+ + getIngredientEditDetails(ingredientEditString: String): String[]
+}
+Class Recipe {
+ - title: String
+ - recipeSteps: RecipeStepList
+ - recipeIngredients: RecipeIngredientList
+
+ + getRecipeSteps(): RecipeStepList
+ + getRecipeIngredients(): RecipeIngredientList
+ + getRecipeStepByIndex(index: Integer): Step
+ + getTitle(): String
+ + setTitle(title: String): Void
+ + toString(): String
+ + viewTimeLine(): Void
+ + createRecipeStub(title: String): Recipe
+}
+
+Class RecipeIngredientList {
+ - ingredients: ArrayList
+
+ + addIngredient(input: String): Void
+ + addIngredient(ingredient: Ingredient): Void
+ + getIngredients(): ArrayList
+ + getIngredientByIndex(index: Integer): Ingredient
+ + ingredientExist(ingredientName: String) Boolean
+}
+
+Class "<>\nTag" as Tag {
+ NIGHT_BEFORE
+ MORNING_OF_COOKING
+ MORE_THAN_ONE_DAY
+ ACTUAL_COOKING
+
+ + getPriority(): Integer
+ + hasHigherPriorityThan(otherTag: Tag): Integer
+ + tagExist(tagString: String): Boolean
+ + mapStringToTag(input: String): Tag
+}
+
+Class RecipeStepList {
+ - steps: ArrayList
+
+ + createStepWithTag(stepString: String): Step
+ + createStepWithDuration(stepString: String): Step
+ + createStepWithTagAndDuration(stepString: String): Step
+ + obtainTag(tagValue: String): Tag
+ + addStep(stepString: String): Void
+ + addStep(step Step): Void
+ + getSteps(): ArrayList
+ + getStepByIndex(index: int): Step
+}
+
+
+Class Ingredient {
+ - name: String
+ - quantity: Double
+ - unit: IngredientUnit
+
+ + getName(): String
+ + setName(name: String): Void
+ + getQuantity(): Void
+ + setQuantity(quantity: Double): Double
+ + getUnit(): IngredientUnit
+ + setUnit(): Void
+ + toString(): String
+ + equals(ingredient: Ingredient): Boolean
+
+}
+
+Class Step {
+ - description: String
+ - tag: Tag
+ - estimatedDuration: Integer
+
+ + getDescription(): String
+ + setDescription(description: String)
+ + getTag(): Tag
+ + setTag(tag Tag): Void
+ + getEstimatedDuration(): Integer
+ + setEstimatedDuration(estimatedDuration: Integer): Void;
+ + convertToStepIdTemplate(stepDescription: String, id: Integer): String
+ + toString(): String
+}
+
+RecipeList -down-> "*" Recipe
+
+Recipe -down-> "1" RecipeIngredientList
+Recipe *-down-> "0...4" Tag
+Recipe -down-> "1" RecipeStepList
+
+RecipeIngredientList -down-> "1...*" Ingredient
+RecipeStepList -down-> "1...*" Step
+@enduml
diff --git a/docs/diagrams/RecipeListDelete.puml b/docs/diagrams/RecipeListDelete.puml
new file mode 100644
index 0000000000..79d5a4f79b
--- /dev/null
+++ b/docs/diagrams/RecipeListDelete.puml
@@ -0,0 +1,22 @@
+@startuml
+!include style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+skinparam ClassBackgroundColor #FFFFAA
+hide footbox
+hide members
+
+title 'delete r/2' or 'delete r/chicken pizza'
+
+class State1 as "meatball noodles:Recipe"
+class State2 as "chicken pizza:Recipe"
+class State3 as "dumplings:Recipe"
+
+class RecipeList as "recipes:RecipeList" #FFFFFF
+RecipeList -up-> State1
+RecipeList -up-> State2
+RecipeList -up-> State3
+
+hide State2
+
+@end
diff --git a/docs/diagrams/RecipeListInitialState.puml b/docs/diagrams/RecipeListInitialState.puml
new file mode 100644
index 0000000000..b3eba6b9ff
--- /dev/null
+++ b/docs/diagrams/RecipeListInitialState.puml
@@ -0,0 +1,22 @@
+@startuml
+!include style.puml
+skinparam ClassFontColor #000000
+skinparam ClassBorderColor #000000
+skinparam ClassBackgroundColor #FFFFAA
+hide footbox
+hide members
+
+title Initial State
+
+class State1 as "meatball noodles:Recipe"
+class State2 as "chicken pizza:Recipe"
+class State3 as "dumplings:Recipe"
+
+class RecipeList as "recipes:RecipeList" #FFFFFF
+RecipeList -up-> State1
+RecipeList -up-> State2
+RecipeList -up-> State3
+
+hide State3
+
+@end
diff --git a/docs/diagrams/RestoreIngredientStorageSequenceDiagram.puml b/docs/diagrams/RestoreIngredientStorageSequenceDiagram.puml
new file mode 100644
index 0000000000..afb2de509a
--- /dev/null
+++ b/docs/diagrams/RestoreIngredientStorageSequenceDiagram.puml
@@ -0,0 +1,28 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":IngredientStorage" as IngredientStorage STORAGE_COLOR
+participant "scan:Scanner" as Scanner SCANNER_COLOR
+
+[-> IngredientStorage : restoreSavedData()
+activate IngredientStorage
+
+create Scanner
+IngredientStorage -> Scanner
+activate Scanner
+
+Scanner --> IngredientStorage : new Scanner(file:File)
+deactivate Scanner
+
+loop#FFAEAE #FFE6E6 scan.hasNext()
+IngredientStorage -> IngredientStorage : createNewData(scan)
+activate IngredientStorage
+
+IngredientStorage --> IngredientStorage
+deactivate IngredientStorage
+end
+
+[<-- IngredientStorage
+deactivate IngredientStorage
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/RestoreRecipeStorageSequenceDiagram.puml b/docs/diagrams/RestoreRecipeStorageSequenceDiagram.puml
new file mode 100644
index 0000000000..26cdfb3ac5
--- /dev/null
+++ b/docs/diagrams/RestoreRecipeStorageSequenceDiagram.puml
@@ -0,0 +1,29 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":RecipeStorage" as RecipeStorage STORAGE_COLOR
+participant "scan:Scanner" as Scanner SCANNER_COLOR
+
+
+[-> RecipeStorage : restoreSavedData()
+activate RecipeStorage
+
+create Scanner
+RecipeStorage -> Scanner
+activate Scanner
+
+Scanner --> RecipeStorage : new Scanner(file:File)
+deactivate Scanner
+
+loop#FFAEAE #FFE6E6 scan.hasNext()
+RecipeStorage -> RecipeStorage : createNewData(scan)
+activate RecipeStorage
+
+RecipeStorage --> RecipeStorage
+deactivate RecipeStorage
+end
+
+[<-- RecipeStorage
+deactivate RecipeStorage
+@enduml
diff --git a/docs/diagrams/RestoreShortcutStorageSequenceDiagram.puml b/docs/diagrams/RestoreShortcutStorageSequenceDiagram.puml
new file mode 100644
index 0000000000..6cc9221452
--- /dev/null
+++ b/docs/diagrams/RestoreShortcutStorageSequenceDiagram.puml
@@ -0,0 +1,28 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":ShortcutStorage" as ShortcutStorage STORAGE_COLOR
+participant "scan:Scanner" as Scanner SCANNER_COLOR
+
+[-> ShortcutStorage : restoreSavedData()
+activate ShortcutStorage
+
+create Scanner
+ShortcutStorage -> Scanner
+activate Scanner
+
+Scanner --> ShortcutStorage : new Scanner(file:File)
+deactivate Scanner
+
+loop#FFAEAE #FFE6E6 scan.hasNext()
+ShortcutStorage -> ShortcutStorage : createNewData(scan)
+activate ShortcutStorage
+
+ShortcutStorage --> ShortcutStorage
+deactivate ShortcutStorage
+end
+
+[<-- ShortcutStorage
+deactivate ShortcutStorage
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/ShortcutListClassDiagram.puml b/docs/diagrams/ShortcutListClassDiagram.puml
new file mode 100644
index 0000000000..bb195d58aa
--- /dev/null
+++ b/docs/diagrams/ShortcutListClassDiagram.puml
@@ -0,0 +1,31 @@
+@startuml
+!include style.puml
+skinparam arrowThickness 1.1
+skinparam arrowColor CLASS_ARROW_COLOR
+skinparam classBackgroundColor CLASS_DIAGRAM_COLOR
+
+Class ShortcutList {
+ + getShortcuts(): Shortcut[]
+ + addShortcut(shortcut: Shortcut)
+ + getShortcut(index: int): Shortcut
+ + listShortcuts()
+ + getIndex(ingredientName: String): int
+ + deleteShortcut(index: int)
+ + exist(ingredientName: String)
+}
+
+
+Class Shortcut {
+ - ingredientName: String
+ - quantity: Double
+
+ + getIngredientName(): String
+ + getQuantity(): double
+ + setIngredientName()
+ + setQuantity()
+ + toString(): String
+}
+
+ShortcutList -down-> "*" Shortcut
+
+@enduml
diff --git a/docs/diagrams/ShortcutListObjectDiagram.puml b/docs/diagrams/ShortcutListObjectDiagram.puml
new file mode 100644
index 0000000000..0aeffc725d
--- /dev/null
+++ b/docs/diagrams/ShortcutListObjectDiagram.puml
@@ -0,0 +1,12 @@
+@startuml
+!include style.puml
+skinparam objectBackgroundColor OBJECT_DIAGRAM_COLOR
+
+object "shortcuts:ShortcutList" as ShortcutList
+object ":Shortcut" as Shortcut
+
+hide ShortcutList members
+hide Shortcut members
+
+ShortcutList-[#000000]-Shortcut
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/StoreIngredientStorageSequenceDiagram.puml b/docs/diagrams/StoreIngredientStorageSequenceDiagram.puml
new file mode 100644
index 0000000000..2a474d625c
--- /dev/null
+++ b/docs/diagrams/StoreIngredientStorageSequenceDiagram.puml
@@ -0,0 +1,37 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":IngredientStorage" as IngredientStorage STORAGE_COLOR
+participant "<>\n:IngredientParser" as IngredientParser INGREDIENT_PARSER_COLOR
+participant ":FileWriter" as FileWriter FILE_WRITER_COLOR
+
+[-> IngredientStorage : saveData(ingredients)
+activate IngredientStorage
+
+create FileWriter
+IngredientStorage -> FileWriter : new FileWriter(dataPath, false)
+activate FileWriter
+
+FileWriter --> IngredientStorage
+deactivate FileWriter
+
+loop#FFAEAE #FFE6E6 ingredients
+IngredientStorage -> IngredientParser : convertToString(ingredient)
+activate IngredientParser
+
+IngredientParser --> IngredientStorage : return string
+deactivate IngredientParser
+
+IngredientStorage -[hidden]> FileWriter
+IngredientStorage -> FileWriter : write(dataString)
+activate FileWriter
+
+FileWriter --> IngredientStorage
+deactivate FileWriter
+end
+
+[<---IngredientStorage
+deactivate IngredientStorage
+
+@enduml
diff --git a/docs/diagrams/StoreRecipeStorageSequenceDiagram.puml b/docs/diagrams/StoreRecipeStorageSequenceDiagram.puml
new file mode 100644
index 0000000000..3073e3d9e1
--- /dev/null
+++ b/docs/diagrams/StoreRecipeStorageSequenceDiagram.puml
@@ -0,0 +1,58 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":RecipeStorage" as RecipeStorage STORAGE_COLOR
+participant "<>\n:RecipeParser" as RecipeParser RECIPE_PARSER_COLOR
+participant "<>\n:IngredientParser" as IngredientParser INGREDIENT_PARSER_COLOR
+participant ":FileWriter" as FileWriter FILE_WRITER_COLOR
+
+[-> RecipeStorage : saveData(recipes)
+activate RecipeStorage
+
+create FileWriter
+RecipeStorage -> FileWriter : new FileWriter(dataPath, false)
+activate FileWriter
+
+FileWriter --> RecipeStorage
+deactivate FileWriter
+
+loop#FFAEAE #FFE6E6 recipes
+RecipeStorage -> RecipeStorage : convertToString(recipe)
+activate RecipeStorage
+
+RecipeStorage -> RecipeParser : convertSteps(steps)
+activate RecipeParser
+
+RecipeParser --> RecipeStorage : return string
+deactivate RecipeParser
+
+RecipeStorage -> RecipeParser : convertIngredient(ingredients)
+activate RecipeParser
+
+loop ingredients
+RecipeParser -> IngredientParser : convertToString(ingredient)
+activate IngredientParser
+
+IngredientParser --> RecipeParser : return string
+deactivate IngredientParser
+end
+
+RecipeParser --> RecipeStorage : return string
+deactivate RecipeParser
+
+RecipeStorage --> RecipeStorage : return string
+deactivate RecipeStorage
+
+RecipeStorage -[hidden]> FileWriter
+RecipeStorage -> FileWriter : write(dataString)
+activate FileWriter
+
+FileWriter --> RecipeStorage
+deactivate FileWriter
+end
+
+[<---RecipeStorage
+deactivate RecipeStorage
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/StoreShortcutStorageSequenceDiagram.puml b/docs/diagrams/StoreShortcutStorageSequenceDiagram.puml
new file mode 100644
index 0000000000..d809db3f87
--- /dev/null
+++ b/docs/diagrams/StoreShortcutStorageSequenceDiagram.puml
@@ -0,0 +1,37 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":ShortcutStorage" as ShortcutStorage STORAGE_COLOR
+participant "<>\n:ShortcutParser" as ShortcutParser SHORTCUT_PARSER_COLOR
+participant ":FileWriter" as FileWriter FILE_WRITER_COLOR
+
+[-> ShortcutStorage : saveData(shortcuts)
+activate ShortcutStorage
+
+create FileWriter
+ShortcutStorage -> FileWriter : new FileWriter(dataPath, false)
+activate FileWriter
+
+FileWriter --> ShortcutStorage
+deactivate FileWriter
+
+loop#FFAEAE #FFE6E6 shortcuts
+ShortcutStorage -> ShortcutParser : convertToString(shortcut)
+activate ShortcutParser
+
+ShortcutParser --> ShortcutStorage : return string
+deactivate ShortcutParser
+
+ShortcutStorage -[hidden]> FileWriter
+ShortcutStorage -> FileWriter : write(dataString)
+activate FileWriter
+
+FileWriter --> ShortcutStorage
+deactivate FileWriter
+end
+
+[<---ShortcutStorage
+deactivate ShortcutStorage
+
+@enduml
diff --git a/docs/diagrams/Style.puml b/docs/diagrams/Style.puml
new file mode 100644
index 0000000000..90df06fdec
--- /dev/null
+++ b/docs/diagrams/Style.puml
@@ -0,0 +1,119 @@
+/'
+ 'Commonly used styles and colors across diagrams.
+ 'Refer to https://plantuml-documentation.readthedocs.io/en/latest for a more
+ 'comprehensive list of skinparams.
+ '/
+
+
+'T1 through T4 are shades of the original color from lightest to darkest
+
+!define MAIN_COLOR #DBA800
+
+!define PERSON_COLOR #ffb6c1
+
+!define COMMAND_COLOR #728FCE
+
+!define PARSER_COLOR #00A36C
+!define RECIPE_PARSER_COLOR #34A56F
+!define INGREDIENT_PARSER_COLOR #1AA260
+!define SHORTCUT_PARSER_COLOR #A43A7C
+
+!define RECIPE_COLOR #C6DEFF
+!define INGREDIENT_COLOR #5D3FD3
+!define SHORTCUT_COLOR #58699A
+
+!define RECIPE_LIST_COLOR #B0CFDE
+!define RECIPE_LIST_COLOR_T1 #C9DFEC
+!define INGREDIENT_LIST_COLOR #C9DFEC
+!define SHORTCUT_LIST_COLOR #3A7CA4
+
+!define STORAGE_COLOR #E69598
+
+!define FILE_WRITER_COLOR #CBE57C
+
+!define FILE_COLOR #8B1EC4
+
+!define SCANNER_COLOR #3E732E
+
+!define LOGGER_COLOR #FCBE85
+
+!define UI_COLOR #C24641
+!define UI_COLOR_T1 #C11B17
+
+
+!define UI_COLOR_T2 #3FC71B
+!define UI_COLOR_T3 #166800
+!define UI_COLOR_T4 #0E4100
+
+!define CLASS_DIAGRAM_COLOR #E5CFFB
+!define CLASS_ARROW_COLOR #3E4255
+
+!define OBJECT_DIAGRAM_COLOR #D0D7FE
+!define OBJECT_ARROW_COLOR #
+
+!define INTERFACE_COLOR #C24641
+!define INTERFACE_COLOR_T1 #C11B17
+
+
+!define INTERFACE_COLOR_T2 #3FC71B
+!define INTERFACE_COLOR_T3 #166800
+!define INTERFACE_COLOR_T4 #0E4100
+
+!define LOGIC_COLOR #3333C4
+!define LOGIC_COLOR_T1 #C8C8FA
+!define LOGIC_COLOR_T2 #6A6ADC
+!define LOGIC_COLOR_T3 #1616B0
+!define LOGIC_COLOR_T4 #101086
+
+!define MODEL_COLOR #9D0012
+!define MODEL_COLOR_T1 #F97181
+!define MODEL_COLOR_T2 #E41F36
+!define MODEL_COLOR_T3 #7B000E
+!define MODEL_COLOR_T4 #51000A
+
+!define USER_COLOR #000000
+
+skinparam Package {
+ BackgroundColor #FFFFFF
+ BorderThickness 1
+ FontSize 16
+}
+
+skinparam Class {
+ FontColor #000000
+ FontSize 15
+ BorderThickness 1
+ BorderColor #000000
+ StereotypeFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Actor {
+ BorderColor USER_COLOR
+ Color USER_COLOR
+ FontName Arial
+}
+
+skinparam Sequence {
+ MessageAlign center
+ BoxFontSize 15
+ BoxPadding 0
+ BoxFontColor #FFFFFF
+ FontName Arial
+}
+
+skinparam Participant {
+ FontColor #FFFFFFF
+ Padding 20
+}
+
+skinparam ArrowFontStyle bold
+skinparam MinClassWidth 50
+skinparam ParticipantPadding 10
+skinparam Shadowing false
+skinparam DefaultTextAlignment center
+skinparam packageStyle Rectangle
+
+'hide footbox
+'hide members
+hide circle
diff --git a/docs/diagrams/UseShortcutSequenceDiagram.puml b/docs/diagrams/UseShortcutSequenceDiagram.puml
new file mode 100644
index 0000000000..8d46618fa3
--- /dev/null
+++ b/docs/diagrams/UseShortcutSequenceDiagram.puml
@@ -0,0 +1,74 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+participant ":UseShortcutCommand" as UseShortcutCommand COMMAND_COLOR
+participant "<>\nShortcutParser" as ShortcutParser SHORTCUT_PARSER_COLOR
+participant "shortcut:Shortcut" as Shortcut SHORTCUT_COLOR
+participant "shortcuts:ShortcutList" as Shortcuts SHORTCUT_LIST_COLOR
+participant "<\nIngredientParser" as IngredientParser INGREDIENT_PARSER_COLOR
+participant "ingredients:IngredientList" as Ingredients INGREDIENT_LIST_COLOR
+participant ":Ingredient" as Ingredient INGREDIENT_COLOR
+participant "<>\nUi" as Ui UI_COLOR
+
+[-> UseShortcutCommand : executeCommand()
+activate UseShortcutCommand
+
+UseShortcutCommand -> ShortcutParser : getShortcutIndex(shortcut, shortcutDetail)
+activate ShortcutParser
+
+ShortcutParser --> UseShortcutCommand
+deactivate ShortcutParser
+
+UseShortcutCommand -> Shortcuts : getShortcut(index)
+activate Shortcuts
+
+Shortcuts --> UseShortcutCommand
+deactivate Shortcuts
+
+UseShortcutCommand -> Shortcut : getIngredientName();
+activate Shortcut
+
+Shortcut --> UseShortcutCommand
+deactivate Shortcut
+
+UseShortcutCommand -> Shortcut : getQuantity();
+activate Shortcut
+
+Shortcut --> UseShortcutCommand
+deactivate Shortcut
+
+UseShortcutCommand -> IngredientParser : getIngredientIndex(ingredients, ingredientName)
+activate IngredientParser
+
+IngredientParser --> UseShortcutCommand
+deactivate IngredientParser
+
+UseShortcutCommand -> Ingredients : getIngredient(index)
+activate Ingredients
+
+Ingredients --> UseShortcutCommand
+deactivate Ingredients
+
+UseShortcutCommand -> Ingredient : getUnit()
+activate Ingredient
+
+Ingredient --> UseShortcutCommand
+deactivate Ingredient
+
+UseShortcutCommand -> Ingredients : updateIngredient(new Ingredient())
+activate Ingredients
+
+Ingredients -> Ui : printUpdateIngredientsSuccess();
+activate Ui
+
+Ui --> Ingredients
+deactivate Ui
+
+Ingredients --> UseShortcutCommand
+deactivate Ingredients
+
+[<-- UseShortcutCommand
+deactivate UseShortcutCommand
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/VIewAllShortcutSequenceDiagram.puml b/docs/diagrams/VIewAllShortcutSequenceDiagram.puml
new file mode 100644
index 0000000000..be1fa25c3a
--- /dev/null
+++ b/docs/diagrams/VIewAllShortcutSequenceDiagram.puml
@@ -0,0 +1,48 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+actor User
+participant ":Essenmakanan" as EssenMakanan MAIN_COLOR
+participant ":Parser" as Parser PARSER_COLOR
+participant ":ViewShortcutCommand" as ViewShortcutCommand COMMAND_COLOR
+participant "<>\nUi" as Ui INTERFACE_COLOR
+participant "shortcuts:ShortcutList" as Shortcuts SHORTCUT_LIST_COLOR
+
+User -> EssenMakanan : input: view sc
+activate EssenMakanan
+
+EssenMakanan -> Parser : parseCommand()
+activate Parser
+
+create ViewShortcutCommand
+Parser -> ViewShortcutCommand: new ViewShortcutCommand()
+activate ViewShortcutCommand
+
+ViewShortcutCommand --> Parser : command object
+deactivate ViewShortcutCommand
+
+Parser -> EssenMakanan: command object
+deactivate Parser
+
+EssenMakanan -> ViewShortcutCommand: executeCommand()
+activate ViewShortcutCommand
+
+ViewShortcutCommand -> Ui: printAllShortcuts(shortcuts)
+activate Ui
+
+Ui -> Shortcuts: listShortcuts()
+activate Shortcuts
+
+Shortcuts --> Ui
+deactivate Shortcuts
+
+Ui --> ViewShortcutCommand
+deactivate Ui
+
+ViewShortcutCommand --> EssenMakanan
+deactivate ViewShortcutCommand
+
+EssenMakanan --> User
+deactivate EssenMakanan
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/ViewAllIngredientSequenceDiagram.puml b/docs/diagrams/ViewAllIngredientSequenceDiagram.puml
new file mode 100644
index 0000000000..2e7825b007
--- /dev/null
+++ b/docs/diagrams/ViewAllIngredientSequenceDiagram.puml
@@ -0,0 +1,49 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+actor User
+participant ":EssenMakanan" as EssenMakanan MAIN_COLOR
+participant ":Parser" as Parser PARSER_COLOR
+participant ":ViewIngredientCommand" as ViewIngredientCommand COMMAND_COLOR
+participant "<>\nUi" as Ui INTERFACE_COLOR
+participant "ingredients:IngredientList" as Ingredients INGREDIENT_COLOR
+
+User -> EssenMakanan: input: view i
+activate EssenMakanan
+
+EssenMakanan -> Parser : parseCommand()
+activate Parser
+
+create ViewIngredientCommand
+Parser -> ViewIngredientCommand: ViewIngredientCommand()
+activate ViewIngredientCommand
+
+ViewIngredientCommand --> Parser : command object
+
+Parser -> EssenMakanan: command object
+deactivate Parser
+
+EssenMakanan -> ViewIngredientCommand: executeCommand()
+
+ViewIngredientCommand -> Ui: printAllIngredients(ingredients)
+activate Ui
+
+Ui -> Ingredients: listIngredients()
+activate Ingredients
+
+Ingredients --> Ui
+deactivate Ingredients
+
+Ui --> ViewIngredientCommand
+
+deactivate Ui
+
+ViewIngredientCommand --> EssenMakanan
+deactivate ViewIngredientCommand
+
+EssenMakanan --> User: output: list of ingredients
+
+deactivate EssenMakanan
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/ViewAllRecipeSequenceDiagram.puml b/docs/diagrams/ViewAllRecipeSequenceDiagram.puml
new file mode 100644
index 0000000000..1ce159272e
--- /dev/null
+++ b/docs/diagrams/ViewAllRecipeSequenceDiagram.puml
@@ -0,0 +1,49 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+
+actor User
+participant ":EssenMakanan" as EssenMakanan MAIN_COLOR
+participant ":Parser" as Parser PARSER_COLOR
+participant ":ViewRecipeCommand" as ViewRecipeCommand COMMAND_COLOR
+participant "<>\nUi" as Ui UI_COLOR
+participant "recipes:RecipeList" as Recipes RECIPE_LIST_COLOR
+
+User -> EssenMakanan: input: view r
+activate EssenMakanan
+
+EssenMakanan -> Parser : parseCommand()
+activate Parser
+
+create ViewRecipeCommand
+Parser -> ViewRecipeCommand: ViewRecipeCommand()
+activate ViewRecipeCommand
+
+ViewRecipeCommand --> Parser : command object
+
+Parser -> EssenMakanan: command object
+deactivate Parser
+
+EssenMakanan -> ViewRecipeCommand: executeCommand()
+
+ViewRecipeCommand -> Ui: printAllRecipes(recipes)
+activate Ui
+
+Ui -> Recipes: listRecipeTitles()
+activate Recipes
+
+Recipes --> Ui
+deactivate Recipes
+
+Ui --> ViewRecipeCommand
+
+deactivate Ui
+
+ViewRecipeCommand --> EssenMakanan
+deactivate ViewRecipeCommand
+
+EssenMakanan --> User: output: list of recipes
+deactivate EssenMakanan
+
+@enduml
\ No newline at end of file
diff --git a/docs/diagrams/ViewSpecificIngredient.puml b/docs/diagrams/ViewSpecificIngredient.puml
new file mode 100644
index 0000000000..32abd1a5b6
--- /dev/null
+++ b/docs/diagrams/ViewSpecificIngredient.puml
@@ -0,0 +1,47 @@
+@startuml
+!include style.puml
+skinparam ArrowFontStyle plain
+
+actor User
+participant ":EssenMakanan" as EssenMakanan MAIN_COLOR
+participant ":Parser" as Parser PARSER_COLOR
+participant "command:ViewSpecificIngredientCommand" as Command COMMAND_COLOR
+participant "<>\n:IngredientParser" as IngredientParser INGREDIENT_PARSER_COLOR
+participant "ingredients:IngredientList" as Ingredients INGREDIENT_COLOR
+
+User -> EssenMakanan: input: view i/bread
+activate EssenMakanan
+
+EssenMakanan -> Parser : parseCommand()
+activate Parser
+
+create Command
+Parser -> Command
+activate Command
+
+Command --> Parser : command
+
+Parser -> EssenMakanan: command
+deactivate Parser
+
+EssenMakanan -> Command: executeCommand()
+
+Command -> IngredientParser : getIngredientIndex()
+activate IngredientParser
+
+IngredientParser --> Command
+deactivate IngredientParser
+
+Command -> Ingredients: getIngredient()
+activate Ingredients
+
+Ingredients --> Command : ingredient
+deactivate Ingredients
+
+Command --> EssenMakanan
+deactivate Command
+
+EssenMakanan --> User
+deactivate EssenMakanan
+
+@enduml
\ No newline at end of file
diff --git a/docs/images/AddIngredientWrongUnit.png b/docs/images/AddIngredientWrongUnit.png
new file mode 100644
index 0000000000..a8edd56367
Binary files /dev/null and b/docs/images/AddIngredientWrongUnit.png differ
diff --git a/docs/images/AddNewIngredientSequenceDiagram.png b/docs/images/AddNewIngredientSequenceDiagram.png
new file mode 100644
index 0000000000..b7800c48ec
Binary files /dev/null and b/docs/images/AddNewIngredientSequenceDiagram.png differ
diff --git a/docs/images/AddNewRecipeSequenceDiagram.png b/docs/images/AddNewRecipeSequenceDiagram.png
new file mode 100644
index 0000000000..7ea59f6c68
Binary files /dev/null and b/docs/images/AddNewRecipeSequenceDiagram.png differ
diff --git a/docs/images/AddNewShortcutSequenceDiagram.png b/docs/images/AddNewShortcutSequenceDiagram.png
new file mode 100644
index 0000000000..ff3872324a
Binary files /dev/null and b/docs/images/AddNewShortcutSequenceDiagram.png differ
diff --git a/docs/images/AddRecipe.png b/docs/images/AddRecipe.png
new file mode 100644
index 0000000000..5cb27c2cae
Binary files /dev/null and b/docs/images/AddRecipe.png differ
diff --git a/docs/images/AddRecipeFullEg2.png b/docs/images/AddRecipeFullEg2.png
new file mode 100644
index 0000000000..a0afeed09c
Binary files /dev/null and b/docs/images/AddRecipeFullEg2.png differ
diff --git a/docs/images/AddRecipeTagAndStepEg.png b/docs/images/AddRecipeTagAndStepEg.png
new file mode 100644
index 0000000000..49ea004ea8
Binary files /dev/null and b/docs/images/AddRecipeTagAndStepEg.png differ
diff --git a/docs/images/AddRecipeTagExample.png b/docs/images/AddRecipeTagExample.png
new file mode 100644
index 0000000000..5e84743a1f
Binary files /dev/null and b/docs/images/AddRecipeTagExample.png differ
diff --git a/docs/images/AddRecipeValidStepDuration.png b/docs/images/AddRecipeValidStepDuration.png
new file mode 100644
index 0000000000..a51cb90fcf
Binary files /dev/null and b/docs/images/AddRecipeValidStepDuration.png differ
diff --git a/docs/images/AddShortcutExample.png b/docs/images/AddShortcutExample.png
new file mode 100644
index 0000000000..99cc1cb6c4
Binary files /dev/null and b/docs/images/AddShortcutExample.png differ
diff --git a/docs/images/AddShortcutInvalid.png b/docs/images/AddShortcutInvalid.png
new file mode 100644
index 0000000000..2e618c0943
Binary files /dev/null and b/docs/images/AddShortcutInvalid.png differ
diff --git a/docs/images/AddToIngredientQuantityExample.png b/docs/images/AddToIngredientQuantityExample.png
new file mode 100644
index 0000000000..363527742a
Binary files /dev/null and b/docs/images/AddToIngredientQuantityExample.png differ
diff --git a/docs/images/ArchitectureDiagram.png b/docs/images/ArchitectureDiagram.png
new file mode 100644
index 0000000000..8de384b461
Binary files /dev/null and b/docs/images/ArchitectureDiagram.png differ
diff --git a/docs/images/BackToTopImage.png b/docs/images/BackToTopImage.png
new file mode 100644
index 0000000000..720cfa2d8e
Binary files /dev/null and b/docs/images/BackToTopImage.png differ
diff --git a/docs/images/CheckBeforeExecute.png b/docs/images/CheckBeforeExecute.png
new file mode 100644
index 0000000000..6f63b9e32c
Binary files /dev/null and b/docs/images/CheckBeforeExecute.png differ
diff --git a/docs/images/CheckRecipeCommand1.png b/docs/images/CheckRecipeCommand1.png
new file mode 100644
index 0000000000..0d3d3ef18e
Binary files /dev/null and b/docs/images/CheckRecipeCommand1.png differ
diff --git a/docs/images/CheckRecipeCommand2.png b/docs/images/CheckRecipeCommand2.png
new file mode 100644
index 0000000000..caf4688c9c
Binary files /dev/null and b/docs/images/CheckRecipeCommand2.png differ
diff --git a/docs/images/CheckRecipeSequenceDiagram.png b/docs/images/CheckRecipeSequenceDiagram.png
new file mode 100644
index 0000000000..d9484ca234
Binary files /dev/null and b/docs/images/CheckRecipeSequenceDiagram.png differ
diff --git a/docs/images/DeleteShortcutExample.png b/docs/images/DeleteShortcutExample.png
new file mode 100644
index 0000000000..4cf1f0e313
Binary files /dev/null and b/docs/images/DeleteShortcutExample.png differ
diff --git a/docs/images/DeleteShortcutSequenceDiagram.png b/docs/images/DeleteShortcutSequenceDiagram.png
new file mode 100644
index 0000000000..368ab83a49
Binary files /dev/null and b/docs/images/DeleteShortcutSequenceDiagram.png differ
diff --git a/docs/images/DuplicateRecipe.png b/docs/images/DuplicateRecipe.png
new file mode 100644
index 0000000000..95a5db827a
Binary files /dev/null and b/docs/images/DuplicateRecipe.png differ
diff --git a/docs/images/DuplicateRecipeEg.png b/docs/images/DuplicateRecipeEg.png
new file mode 100644
index 0000000000..49ea004ea8
Binary files /dev/null and b/docs/images/DuplicateRecipeEg.png differ
diff --git a/docs/images/DuplicateRecipeSequenceDiagram.png b/docs/images/DuplicateRecipeSequenceDiagram.png
new file mode 100644
index 0000000000..76375b9d80
Binary files /dev/null and b/docs/images/DuplicateRecipeSequenceDiagram.png differ
diff --git a/docs/images/EditIngredientEg.png b/docs/images/EditIngredientEg.png
new file mode 100644
index 0000000000..e15c6c2d92
Binary files /dev/null and b/docs/images/EditIngredientEg.png differ
diff --git a/docs/images/EditIngredientSequenceDiagram.png b/docs/images/EditIngredientSequenceDiagram.png
new file mode 100644
index 0000000000..3ae7aecbf5
Binary files /dev/null and b/docs/images/EditIngredientSequenceDiagram.png differ
diff --git a/docs/images/EditRecipeFullExample.png b/docs/images/EditRecipeFullExample.png
new file mode 100644
index 0000000000..cae26c4ed3
Binary files /dev/null and b/docs/images/EditRecipeFullExample.png differ
diff --git a/docs/images/EditRecipeSequenceDiagram.png b/docs/images/EditRecipeSequenceDiagram.png
new file mode 100644
index 0000000000..2dfc28f0b4
Binary files /dev/null and b/docs/images/EditRecipeSequenceDiagram.png differ
diff --git a/docs/images/EditShortcutExample.png b/docs/images/EditShortcutExample.png
new file mode 100644
index 0000000000..4e7bac099c
Binary files /dev/null and b/docs/images/EditShortcutExample.png differ
diff --git a/docs/images/EditShortcutMoreThanOneFlag.png b/docs/images/EditShortcutMoreThanOneFlag.png
new file mode 100644
index 0000000000..c76f56fd24
Binary files /dev/null and b/docs/images/EditShortcutMoreThanOneFlag.png differ
diff --git a/docs/images/EditShortcutSequenceDiagram.png b/docs/images/EditShortcutSequenceDiagram.png
new file mode 100644
index 0000000000..d42330bd1a
Binary files /dev/null and b/docs/images/EditShortcutSequenceDiagram.png differ
diff --git a/docs/images/ExecuteRecipeExample.png b/docs/images/ExecuteRecipeExample.png
new file mode 100644
index 0000000000..bb51317b4d
Binary files /dev/null and b/docs/images/ExecuteRecipeExample.png differ
diff --git a/docs/images/ExitSD.png b/docs/images/ExitSD.png
new file mode 100644
index 0000000000..ec5126238b
Binary files /dev/null and b/docs/images/ExitSD.png differ
diff --git a/docs/images/ExitSequenceDiagram.png b/docs/images/ExitSequenceDiagram.png
new file mode 100644
index 0000000000..d6e2b94d9b
Binary files /dev/null and b/docs/images/ExitSequenceDiagram.png differ
diff --git a/docs/images/FilterRecipesSequenceDiagram.png b/docs/images/FilterRecipesSequenceDiagram.png
new file mode 100644
index 0000000000..0331bdf1e2
Binary files /dev/null and b/docs/images/FilterRecipesSequenceDiagram.png differ
diff --git a/docs/images/FilterRecipseByIngredientCommand.png b/docs/images/FilterRecipseByIngredientCommand.png
new file mode 100644
index 0000000000..a50ad66987
Binary files /dev/null and b/docs/images/FilterRecipseByIngredientCommand.png differ
diff --git a/docs/images/HelpFunctionSequenceDiagram.png b/docs/images/HelpFunctionSequenceDiagram.png
new file mode 100644
index 0000000000..bd70fa9cf4
Binary files /dev/null and b/docs/images/HelpFunctionSequenceDiagram.png differ
diff --git a/docs/images/IngredientListClassDiagram.png b/docs/images/IngredientListClassDiagram.png
new file mode 100644
index 0000000000..1b616e8b13
Binary files /dev/null and b/docs/images/IngredientListClassDiagram.png differ
diff --git a/docs/images/PlanCommandImage.png b/docs/images/PlanCommandImage.png
new file mode 100644
index 0000000000..625e218c49
Binary files /dev/null and b/docs/images/PlanCommandImage.png differ
diff --git a/docs/images/RecipeListAdd.png b/docs/images/RecipeListAdd.png
new file mode 100644
index 0000000000..ce87dd9630
Binary files /dev/null and b/docs/images/RecipeListAdd.png differ
diff --git a/docs/images/RecipeListClassDiagram.png b/docs/images/RecipeListClassDiagram.png
new file mode 100644
index 0000000000..29df61f783
Binary files /dev/null and b/docs/images/RecipeListClassDiagram.png differ
diff --git a/docs/images/RecipeListDelete.png b/docs/images/RecipeListDelete.png
new file mode 100644
index 0000000000..cafdbf9de8
Binary files /dev/null and b/docs/images/RecipeListDelete.png differ
diff --git a/docs/images/RecipeListInitialState.png b/docs/images/RecipeListInitialState.png
new file mode 100644
index 0000000000..bb4a05ab11
Binary files /dev/null and b/docs/images/RecipeListInitialState.png differ
diff --git a/docs/images/RestoreIngredientStorageSequenceDiagram.png b/docs/images/RestoreIngredientStorageSequenceDiagram.png
new file mode 100644
index 0000000000..d86be0642e
Binary files /dev/null and b/docs/images/RestoreIngredientStorageSequenceDiagram.png differ
diff --git a/docs/images/RestoreRecipeStorageSequenceDiagram.png b/docs/images/RestoreRecipeStorageSequenceDiagram.png
new file mode 100644
index 0000000000..710fb1acdc
Binary files /dev/null and b/docs/images/RestoreRecipeStorageSequenceDiagram.png differ
diff --git a/docs/images/RestoreShortcutStorageSequenceDiagram.png b/docs/images/RestoreShortcutStorageSequenceDiagram.png
new file mode 100644
index 0000000000..e23d08ec12
Binary files /dev/null and b/docs/images/RestoreShortcutStorageSequenceDiagram.png differ
diff --git a/docs/images/ShortcutCommand.png b/docs/images/ShortcutCommand.png
new file mode 100644
index 0000000000..2c8b297684
Binary files /dev/null and b/docs/images/ShortcutCommand.png differ
diff --git a/docs/images/ShortcutListClassDiagram.png b/docs/images/ShortcutListClassDiagram.png
new file mode 100644
index 0000000000..6d2e153b74
Binary files /dev/null and b/docs/images/ShortcutListClassDiagram.png differ
diff --git a/docs/images/ShortcutListObjectDiagram.png b/docs/images/ShortcutListObjectDiagram.png
new file mode 100644
index 0000000000..de41cd68a6
Binary files /dev/null and b/docs/images/ShortcutListObjectDiagram.png differ
diff --git a/docs/images/StoreIngredientStorageSequenceDiagram.png b/docs/images/StoreIngredientStorageSequenceDiagram.png
new file mode 100644
index 0000000000..86518eb830
Binary files /dev/null and b/docs/images/StoreIngredientStorageSequenceDiagram.png differ
diff --git a/docs/images/StoreRecipeStorageSequenceDiagram.png b/docs/images/StoreRecipeStorageSequenceDiagram.png
new file mode 100644
index 0000000000..b852613740
Binary files /dev/null and b/docs/images/StoreRecipeStorageSequenceDiagram.png differ
diff --git a/docs/images/StoreShortcutStorageSequenceDiagram.png b/docs/images/StoreShortcutStorageSequenceDiagram.png
new file mode 100644
index 0000000000..55eab38332
Binary files /dev/null and b/docs/images/StoreShortcutStorageSequenceDiagram.png differ
diff --git a/docs/images/UseIngredientInvalidUnit.png b/docs/images/UseIngredientInvalidUnit.png
new file mode 100644
index 0000000000..9d9ada1280
Binary files /dev/null and b/docs/images/UseIngredientInvalidUnit.png differ
diff --git a/docs/images/UseIngredientValid.png b/docs/images/UseIngredientValid.png
new file mode 100644
index 0000000000..e62fde08dc
Binary files /dev/null and b/docs/images/UseIngredientValid.png differ
diff --git a/docs/images/UseShortcutExample.png b/docs/images/UseShortcutExample.png
new file mode 100644
index 0000000000..2c8b297684
Binary files /dev/null and b/docs/images/UseShortcutExample.png differ
diff --git a/docs/images/UseShortcutSequenceDiagram.png b/docs/images/UseShortcutSequenceDiagram.png
new file mode 100644
index 0000000000..52c124630d
Binary files /dev/null and b/docs/images/UseShortcutSequenceDiagram.png differ
diff --git a/docs/images/VIewAllShortcutSequenceDiagram.png b/docs/images/VIewAllShortcutSequenceDiagram.png
new file mode 100644
index 0000000000..f0d8f19f91
Binary files /dev/null and b/docs/images/VIewAllShortcutSequenceDiagram.png differ
diff --git a/docs/images/ViewARCommand1.png b/docs/images/ViewARCommand1.png
new file mode 100644
index 0000000000..e86aa33b29
Binary files /dev/null and b/docs/images/ViewARCommand1.png differ
diff --git a/docs/images/ViewARCommand2.png b/docs/images/ViewARCommand2.png
new file mode 100644
index 0000000000..76fb9eb8a5
Binary files /dev/null and b/docs/images/ViewARCommand2.png differ
diff --git a/docs/images/ViewAllIngredientSequenceDiagram.png b/docs/images/ViewAllIngredientSequenceDiagram.png
new file mode 100644
index 0000000000..a735fd78c3
Binary files /dev/null and b/docs/images/ViewAllIngredientSequenceDiagram.png differ
diff --git a/docs/images/ViewAllRecipeSequenceDiagram.png b/docs/images/ViewAllRecipeSequenceDiagram.png
new file mode 100644
index 0000000000..6c910122d3
Binary files /dev/null and b/docs/images/ViewAllRecipeSequenceDiagram.png differ
diff --git a/docs/images/ViewSpecificIngredientCommand.png b/docs/images/ViewSpecificIngredientCommand.png
new file mode 100644
index 0000000000..a1c0a2db43
Binary files /dev/null and b/docs/images/ViewSpecificIngredientCommand.png differ
diff --git a/docs/images/ViewSpecificIngredientSD.png b/docs/images/ViewSpecificIngredientSD.png
new file mode 100644
index 0000000000..349e5482e0
Binary files /dev/null and b/docs/images/ViewSpecificIngredientSD.png differ
diff --git a/docs/images/charkty.png b/docs/images/charkty.png
new file mode 100644
index 0000000000..ff3c975daf
Binary files /dev/null and b/docs/images/charkty.png differ
diff --git a/docs/images/edit_recipe_eg_1.png b/docs/images/edit_recipe_eg_1.png
new file mode 100644
index 0000000000..12f7edc0de
Binary files /dev/null and b/docs/images/edit_recipe_eg_1.png differ
diff --git a/docs/images/edit_recipe_eg_2.png.png b/docs/images/edit_recipe_eg_2.png.png
new file mode 100644
index 0000000000..a27ba73733
Binary files /dev/null and b/docs/images/edit_recipe_eg_2.png.png differ
diff --git a/docs/images/golden_retriever.jpg b/docs/images/golden_retriever.jpg
new file mode 100644
index 0000000000..16ade4dad6
Binary files /dev/null and b/docs/images/golden_retriever.jpg differ
diff --git a/docs/images/kjComments.png b/docs/images/kjComments.png
new file mode 100644
index 0000000000..d1c0ec2c76
Binary files /dev/null and b/docs/images/kjComments.png differ
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000000..b7add3d53b
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,9 @@
+# Duke
+
+EssenMakanan is an app that keeps track of ingredients that a user has in the kitchen, stores recipes and provides
+steps on how to cook a specific recipe. This app will include a CLI to use the available commands in the app.
+
+Useful links:
+* [User Guide](UserGuide.md)
+* [Developer Guide](DeveloperGuide.md)
+* [About Us](AboutUs.md)
diff --git a/docs/team/charkty.md b/docs/team/charkty.md
new file mode 100644
index 0000000000..80e7a1adb5
--- /dev/null
+++ b/docs/team/charkty.md
@@ -0,0 +1,96 @@
+# EssenMakanan
+EssenMakanan is a **desktop app for managing recipes and ingredients in your inventory, 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, EssenMakanan can get your recipes and ingredients management tasks done faster than traditional GUI apps.
+
+
+The following are some relevant links
+
+- [EssenMakanan](https://github.com/nus-cs2113-AY2324S1/tp/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+CS2113-F11-2)
+Github Repository
+
+- Charkty's [Pull Requests](https://github.com/AY2324S1-CS2113-F11-2/tp/pulls?q=is%3Apr+author%3Acharkty)
+
+- Charkty's
+[Code Contribution](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=charkty&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos&tabOpen=true&tabType=authorship&tabAuthor=charkty&tabRepo=AY2324S1-CS2113-F11-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false)
+
+---
+
+## Enhancements Implemented
+
+### Ingredients
+
+- Implemented Ingredient, IngredientList and IngredientParser classes
+
+| Command | User Story |
+|----------------------------|---------------------------------------------------------------------------------------------|
+| View a specific ingredient | For user to check how much ingredient is available in their ingredient inventory |
+| Delete an ingredient | For user to remove an ingredient when added wrongly or when it has been used up completely |
+
+
+### Recipes
+
+- Contributed to Recipe, RecipeList and RecipeParser classes
+
+| Command | User Story |
+|-------------------------------|--------------------------------------------------------------------------------------------------------------------------------|
+| Delete a Recipe |User wants to delete a recipe added|
+| Filter Recipes by Ingredients | User has cravings for a ingredient item (such as rice or fish) and wants to see which recipes contain that specific ingredient |
+| Check Recipe |User wants to check if they have all the ingredients needed to start a recipe|
+| Plan Recipe |User wants to plan their groccery shopping for the week and wants to know ingredients needed for the week|
+| View All Available Recipes| User wants to see what which recipes they can execute based on availability of ingredients in the ingredient inventory|
+
+---
+
+## Contributions to UG and DG
+
+### Standardisation work that I did:
+- Set up the skeleton of both UG and DG
+ - Identified and added all sections needed to the skeleton
+ - Ensured the consistent flow of UG and DG
+ - Created the skeleton of summary tables for commands
+- Distribute work for UG and DG
+- Settle bugs related to UG and DG
+- Standardise format for DG and UG
+ - Color scheme in `style.puml`
+ - Class diagram format standardisation of arrows, boxes and words
+ - Sequence diagram format standardiation of formatting by referencing AddressBook3
+
+### Documentation Guide implementation work:
+
+- Sequence diagram for my commands
+- RecipeList Class Diagram
+- RecipeList Object Diagram
+- Architecture Diagrams for my commands
+- User Story Table
+
+
+### User Guide implementation work:
+
+- CheckRecipe, PlanRecipe, FilterRecipe, ViewSpecificIngredient, DeleteIngredient, DeleteRecipe Commands (including screenshots)
+- Summary table
+
+---
+
+## Contributions to team based tasks
+
+- Set up Github Team Org and Repo together with the team
+- Assisted in maintaining the issue tracker
+- Assisted in planning for meetings and distributing work
+- Assisted in checking PRs frequently
+
+---
+
+## Review/Mentoring Contributions
+
+In general, I review PRs as soon as possible to allow for a fast and smooth flow of the project, ensuring that our team repo is up to date and bugs and solved as soon as possible.
+
+
+Here are some links to PRs I have reviewed:
+1. A [pull request](https://github.com/AY2324S1-CS2113-F11-2/tp/pull/228) by Kai Jie
+2. A [pull request](https://github.com/AY2324S1-CS2113-F11-2/tp/pull/241) by Haoyu
+3. A [pull request](https://github.com/AY2324S1-CS2113-F11-2/tp/pull/104) by Stanley
+
+To view more of my comments on PRs, do take a look at this [tp comments dashboard](https://nus-cs2113-ay2324s1.github.io/dashboards/contents/tp-comments.html) and search for `charkty`.
+
+---
+
diff --git a/docs/team/haoyuli2002.md b/docs/team/haoyuli2002.md
new file mode 100644
index 0000000000..c804ea1947
--- /dev/null
+++ b/docs/team/haoyuli2002.md
@@ -0,0 +1,40 @@
+# EssenMakanan
+EssenMakanan is an application based on Command Line Interface(CLI)
+that provides an easy and intuitive way to keep track of ingredients you have in your kitchen.
+This helps avoid buying duplicated ingredients,
+reminds you of the ingredients you need,
+and gives a visualisation of the recipe timeline to ensure
+that advance preparation is done in time.
+
+Given below are my contributions to the project.
+
+## Contributions
+New Feature:
+- Create the class Step and Tag for Recipe
+ - Create the Step class so that the user can specify each step when using a recipe
+ - Create the enumeration class Tag so that user can sort their step according to the Tag
+
+New Feature:
+- Create the method: viewRecipe
+ - Sort the steps according to their tag
+ - Check if there are at least one step under this tag,
+ if yes, then print them out, if not, then skip this tag
+ - Print them out
+
+
+SequenceDiagram:
+- [AddNewRecipeSequenceDiagram.puml](docs/diagrams/AddNewRecipeSequenceDiagram.puml)
+
+
+The following are some relevant links
+
+- [EssenMakanan]
+(https://github.com/nus-cs2113-AY2324S1/tp/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+CS2113-F11-2)
+Github Repository)
+
+- Haoyu's [Pull Requests]
+(https://github.com/AY2324S1-CS2113-F11-2/tp/pulls?q=is%3Apr+author%3AHaoyuli2002)
+
+- Haoyu's [Code Contribution]
+(https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=haoyuli2002&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos)
+
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
deleted file mode 100644
index ab75b391b8..0000000000
--- a/docs/team/johndoe.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# John Doe - Project Portfolio Page
-
-## Overview
-
-
-### Summary of Contributions
diff --git a/docs/team/kaijie0102.md b/docs/team/kaijie0102.md
new file mode 100644
index 0000000000..55ba3c25c0
--- /dev/null
+++ b/docs/team/kaijie0102.md
@@ -0,0 +1,68 @@
+# Leow Kai Jie - Project Portfolio Page
+### EssenMakanan
+EssenMakanan is a **desktop app for managing recipes and ingredients in your inventory, 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, EssenMakanan can get your recipes and ingredients management tasks done faster than traditional GUI apps.
+
+### Summary of Contributions
+The following are some relevant links of my contributions to the project:
+- Our [Github Repository](https://github.com/nus-cs2113-AY2324S1/tp/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+CS2113-F11-2)
+
+- My [Pull Requests](https://github.com/AY2324S1-CS2113-F11-2/tp/pulls?q=is%3Apr+author%3Akaijie0102)
+
+- My
+ [Code Contribution](https://nus-cs2113-ay2324s1.github.io/tp-dashboard/?search=kaijie0102&breakdown=false&sort=groupTitle%20dsc&sortWithin=title&since=2023-09-22&timeframe=commit&mergegroup=&groupSelect=groupByRepos&tabOpen=true&tabType=authorship&tabAuthor=kaijie0102&tabRepo=AY2324S1-CS2113-F11-2%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false)
+ (Repo Sense)
+
+#### Enhancements Implemented
+##### Ingredients
+- Implemented and ensured the consistencies of ingredients
+- Implemented that ingredients quantities is not in String format
+- Implemented the ability to edit ingredients
+- Made sure that names and unit of ingredients are unique and
+quantities will add up instead of being ingredient being duplicated
+##### Recipes
+- Implemented the ability for recipes to have ingredients
+- Implemented the ability to edit recipes' ingredients and steps
+- Implemented ability for recipe to parse input regardless of order of flag
+- Implemented execution of recipe to update ingredient inventory after ingredient use
+- Made sure recipes' names are unique by checking with user to overwrite existing recipe
+- Made sure that tags for steps and duration are integrated into the recipe
+- Made sure that all steps have a step id for easy user referencing when editing
+
+#### Contributions to UG
+- Elaborated the following:
+ - Add recipe
+ - Edit recipe
+ - Execute Recipe
+ - Add ingredient
+ - Edit Ingredient
+ - Use Ingredient
+- Formatting
+ - Created links from content page to section and vice versa for easy navigation
+ - Summary Tables for different types of commands
+
+#### Contributions to DG
+- Elaborated the following:
+ - IngredientList class diagram
+ - View Recipe explanation and sequence diagram
+ - View Ingredient explanation and sequence diagram
+ - Edit recipe explanation and sequence diagram
+ - Edit ingredient explanation and sequence diagram
+
+- Formatting
+ - Figured out how to make puml to look the same as what we have learnt in module
+
+#### Contributions to team-based tasks
+- Led and facilitated discussions during team meetings, delegating and splitting work to make sure we are on schedule
+- Tried my best to make my teammates and my code elegant and clean. Some code may follow the checkstyle but it is inefficient and ugly.
+- Note taking during discussions
+- Created a [Workflow and Standard Protocols](https://docs.google.com/document/d/11t6wNnOsvHWzN7rnr147CM5kjIGrAWzUtY4v-1XRwt0/edit#heading=h.5qwu1w5b5xrt)
+to ensure that the team is on the same page and everyone follows a similar process.
+- Standardised issue trackers and pull requests format. Issues now look similar and PR will close the relevant issues using keywords.
+
+#### Review/mentoring contributions
+- Focused on having a proper PR practices (screenshot from dashboard).
+
+
+#### Contributions beyond the project team
+- Helped other groups during tutorial to troubleshoot Intellij issues
diff --git a/docs/team/stanleyw00.md b/docs/team/stanleyw00.md
new file mode 100644
index 0000000000..d4b54a35c7
--- /dev/null
+++ b/docs/team/stanleyw00.md
@@ -0,0 +1,64 @@
+# Stanley Wijaya - Project Portfolio Page
+
+### Project: EssenMakanan
+
+EssenMakanan is an app that keeps track of ingredients that a user has in the kitchen, stores recipes and provides
+steps on how to cook a specific recipe. This app will include a CLI to use the available commands in the app. This app
+is created in Java and has around 10kLoC.
+
+### Code Contributed
+[Link to Code Contribution](https://tinyurl.com/5n88n476)
+
+### Enhancements Implemented
+* Implemented the ability for users to add shortcuts
+* Implemented the ability for users to edit shortcuts
+* Implemented the ability for users to delete shortcuts
+* Implemented the ability for users to use shortcuts
+* Implemented the ability for users to view shortcuts
+* Created Storage Handler for storing and loading ingredients, recipes and shortcuts
+* Contributed in created JUnit tests for the following functions:
+ * Add shortcuts
+ * Edit shortcuts
+ * Delete shortcuts
+ * Use shortcuts
+ * View shortcuts
+ * Save data with storage
+ * Load data with storage
+
+### Contributions to the UG
+* Added and explained the following section in the UG:
+ * Adding a shortcut
+ * Editing a shortcut
+ * Deleting a shortcut
+ * Using a shortcut
+ * Viewing shortcuts
+* Created table of contents for shortcut commands
+* Added hyperlinks for shortcut commands
+
+### Contributions to the DG
+* Added and explained the following features in the DG:
+ * Adding a shortcut
+ * Editing a shortcut
+ * Deleting a shortcut
+ * Using a shortcut
+ * Viewing shortcuts
+ * Storage
+ * Logging
+* Created and added UML diagrams for the following:
+ * Shortcut classes
+ * Adding a shortcut
+ * Editing a shortcut
+ * Deleting a shortcut
+ * Using a shortcut
+ * Viewing shortcuts
+ * Storage
+
+### Contributions to Team-Based Tasks
+* Set up GitHub Team Organisation and Repository
+* Set up issue tracker and milestones
+* Set up releases for v1.0, v1.5, v2.0 and v2.1
+* Created and managed issues in the issue tracker
+* Instances of help other team members in online and offline meetings.
+
+### Review/Mentoring Contributions
+[Link to PRs Reviewed](https://tinyurl.com/yc6c3sr2)
diff --git a/src/main/java/essenmakanan/EssenMakanan.java b/src/main/java/essenmakanan/EssenMakanan.java
new file mode 100644
index 0000000000..d0b56f625d
--- /dev/null
+++ b/src/main/java/essenmakanan/EssenMakanan.java
@@ -0,0 +1,111 @@
+package essenmakanan;
+
+import essenmakanan.command.Command;
+import essenmakanan.command.ExitCommand;
+import essenmakanan.exception.EssenCommandException;
+import essenmakanan.exception.EssenFileNotFoundException;
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.shortcut.ShortcutList;
+import essenmakanan.logger.EssenLogger;
+import essenmakanan.parser.Parser;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.storage.IngredientStorage;
+import essenmakanan.storage.RecipeStorage;
+import essenmakanan.storage.ShortcutStorage;
+import essenmakanan.ui.Ui;
+
+import java.util.Scanner;
+
+/**
+ * The entry point for EssenMakanan application.
+ */
+public class EssenMakanan {
+
+ private final String DATA_INGREDIENT_PATH = "data/ingredients.txt";
+ private final String DATA_RECIPE_PATH = "data/recipes.txt";
+ private final String DATA_SHORTCUT_PATH = "data/shortcuts.txt";
+ private final String DATA_DIRECTORY = "data";
+
+ private RecipeList recipes;
+ private IngredientList ingredients;
+ private ShortcutList shortcuts;
+ private Parser parser;
+ private IngredientStorage ingredientStorage;
+ private RecipeStorage recipeStorage;
+ private ShortcutStorage shortcutStorage;
+
+ /**
+ * Runs the application until termination.
+ */
+ public void run() {
+ Ui.start();
+
+ Scanner in = new Scanner(System.in);
+ String input;
+
+ Command command = null;
+ do {
+ input = in.nextLine();
+ try {
+ command = parser.parseCommand(input, recipes, ingredients, shortcuts);
+ command.executeCommand();
+ ingredientStorage.saveData(ingredients.getIngredients());
+ recipeStorage.saveData(recipes.getRecipes());
+ shortcutStorage.saveData(shortcuts.getShortcuts());
+ } catch (EssenCommandException exception) {
+ exception.handleException();
+ } catch (EssenFormatException exception) {
+ exception.handleException();
+ } catch (EssenOutOfRangeException exception) {
+ exception.handleException();
+ }
+ } while (!ExitCommand.isExitCommand(command));
+ }
+
+ /**
+ * Initializes all class members in EssenMakanan.
+ */
+ public void setup() {
+ EssenLogger.setup();
+ recipes = new RecipeList();
+ parser = new Parser();
+ ingredientStorage = new IngredientStorage(DATA_INGREDIENT_PATH);
+ recipeStorage = new RecipeStorage(DATA_RECIPE_PATH);
+
+ try {
+ ingredients = new IngredientList(ingredientStorage.restoreSavedData());
+ } catch (EssenFileNotFoundException exception) {
+ exception.handleFileNotFoundException(DATA_DIRECTORY, DATA_INGREDIENT_PATH);
+ ingredients = new IngredientList();
+ }
+
+ try {
+ recipes = new RecipeList(recipeStorage.restoreSavedData());
+ } catch (EssenFileNotFoundException exception) {
+ exception.handleFileNotFoundException(DATA_DIRECTORY, DATA_RECIPE_PATH);
+ recipes = new RecipeList();
+ }
+
+ shortcutStorage = new ShortcutStorage(DATA_SHORTCUT_PATH, ingredients);
+ try {
+ shortcuts = new ShortcutList(shortcutStorage.restoreSavedData());
+ } catch (EssenFileNotFoundException exception) {
+ exception.handleFileNotFoundException(DATA_DIRECTORY, DATA_SHORTCUT_PATH);
+ shortcuts = new ShortcutList();
+ }
+ }
+
+ /**
+ * Starts the application.
+ */
+ public void start() {
+ setup();
+ run();
+ }
+
+ public static void main(String[] args) {
+ new EssenMakanan().start();
+ }
+}
diff --git a/src/main/java/essenmakanan/command/AddIngredientCommand.java b/src/main/java/essenmakanan/command/AddIngredientCommand.java
new file mode 100644
index 0000000000..bf3d91e967
--- /dev/null
+++ b/src/main/java/essenmakanan/command/AddIngredientCommand.java
@@ -0,0 +1,56 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.parser.IngredientParser;
+import essenmakanan.ui.Ui;
+
+public class AddIngredientCommand extends Command {
+ private String toAdd;
+ private IngredientList ingredients;
+
+ public AddIngredientCommand(String toAdd, IngredientList ingredients) {
+ super();
+ this.toAdd = toAdd;
+ this.ingredients = ingredients;
+ }
+
+ /**
+ * Check if command and its contents are valid. Parse a valid Ingredient and add to list
+ */
+ @Override
+ public void executeCommand() {
+ String[] allIngredients = toAdd.split("i/");
+
+ for (String ingredient : allIngredients) {
+ if (ingredient.isEmpty()) {
+ continue;
+ }
+
+ Ingredient newIngredient;
+ try {
+ newIngredient = IngredientParser.parseIngredient(ingredient);
+
+ if (newIngredient.getQuantity() < 0) {
+ Ui.printNegativeIngredientQuantity();
+ return;
+ }
+
+ if (this.ingredients.exist(newIngredient.getName())) {
+ // if ingredient already exists, update the quantity
+ this.ingredients.updateIngredient(newIngredient);
+ } else {
+ // add new ingredient
+ this.ingredients.addIngredient(newIngredient);
+ Ui.printAddIngredientsSuccess(newIngredient.getName());
+ }
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+ }
+
+ }
+
+}
+
diff --git a/src/main/java/essenmakanan/command/AddRecipeCommand.java b/src/main/java/essenmakanan/command/AddRecipeCommand.java
new file mode 100644
index 0000000000..f0ee58dffd
--- /dev/null
+++ b/src/main/java/essenmakanan/command/AddRecipeCommand.java
@@ -0,0 +1,264 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.parser.IngredientParser;
+import essenmakanan.parser.RecipeParser;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.recipe.Step;
+import essenmakanan.ui.Ui;
+import essenmakanan.recipe.Tag;
+
+public class AddRecipeCommand extends Command {
+ private String toAdd;
+ private RecipeList recipes;
+
+ public AddRecipeCommand(String toAdd, RecipeList recipes) {
+ super();
+ this.toAdd = toAdd;
+ this.recipes = recipes;
+ }
+
+ /**
+ * Check if structure of command is valid
+ */
+ @Override
+ public void executeCommand() {
+ // if toAdd does not contain the necessary flag, it should have been flagged in Parser class
+ assert toAdd.contains("r/") && toAdd.contains("s/") && toAdd.contains("i/")
+ : "Parser did not catch incomplete input";
+
+ if ((toAdd.contains("r/") && toAdd.contains("s/") && toAdd.contains("i/"))) {
+ // title, steps and ingredients are available
+ try {
+ this.addValidRecipe();
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+ }
+ }
+
+ /**
+ * Add a valid recipe to the list
+ *
+ * @throws EssenFormatException if input is invalid
+ */
+ public void addValidRecipe() throws EssenFormatException {
+
+ // initialisation
+ int numberOfSteps = countOccurrences(toAdd, "s/");
+ int numberOfIngredients = countOccurrences(toAdd, "i/");
+
+ String[] stepsInString = new String[numberOfSteps];
+ int stepsCounter = 0;
+
+ String[] ingredientsInString = new String[numberOfIngredients];
+ int ingredientsCounter = 0;
+
+ String recipeTitle = "";
+ String tag = null;
+ int recipeIndexToOverwrite = -1;
+
+ int flagIndex;
+ String typeFlag;
+ String content;
+ int nextSlashIndex;
+ int slashIndex = toAdd.indexOf("/");
+
+ // getting contents from input
+ while (slashIndex != -1) {
+ flagIndex = slashIndex - 1;
+ typeFlag = toAdd.substring(flagIndex, flagIndex+1);
+ nextSlashIndex = toAdd.indexOf("/",slashIndex+1);
+
+ if ((flagIndex + 2 > nextSlashIndex - 2) && nextSlashIndex!=-1){
+ System.out.println("Please enter valid input! Make sure flags are spaced out. Examples on user guide.");
+ throw new EssenFormatException();
+ }
+
+ if (nextSlashIndex != -1) {
+ // obtain content after each flag until the next flag
+ content = toAdd.substring(flagIndex + 2, nextSlashIndex-2);
+ } else {
+ // from flag to end of string
+ content = toAdd.substring(flagIndex + 2);
+ }
+
+ switch (typeFlag) {
+ case "r":
+ int recipeIndex = this.obtainRecipeIndex(content, recipeTitle);
+
+ // check if recipe already exist
+ if (recipes.recipeExist(recipeIndex)) {
+ if (overwriteExistingRecipe()) {
+ recipeIndexToOverwrite = recipeIndex;
+ } else {
+ System.out.println("Operation cancelled!");
+ return;
+ }
+ }
+
+ recipeTitle = RecipeParser.parseRecipeTitle(content);
+
+ break;
+ case "s":
+ content = this.getStepsContent(content, tag, stepsCounter);
+ stepsInString[stepsCounter] = content;
+ stepsCounter++;
+ break;
+ case "i":
+ content = this.getIngredientContent(content);
+ ingredientsInString[ingredientsCounter] = content;
+ ingredientsCounter++;
+ break;
+ case "t":
+ if (content.isEmpty()) {
+ System.out.println("Tag is empty! Please enter valid tag number \"t/\"");
+ throw new EssenFormatException();
+ }
+
+ if (!Tag.tagExist(content)) {
+ System.out.println("Tag does not exist! Please enter valid tag number \"t/\"");
+ throw new EssenFormatException();
+ }
+
+ // steps after this tag (and before the next tag) will belong to this tag
+ tag = content;
+ break;
+ case "d":
+ // add duration to the latest step
+ int duration = RecipeParser.parseStepsDuration(content);
+
+ // if step has 2 specified duration, throw error
+ if (stepsInString[stepsCounter-1].contains("d/")) {
+ System.out.println("Please only enter one duration per step!");
+ throw new EssenFormatException();
+ }
+
+ stepsInString[stepsCounter-1] = stepsInString[stepsCounter-1] + " d/" + duration;
+ break;
+ default:
+ System.out.println("Please enter a valid recipe!");
+ }
+ slashIndex = nextSlashIndex;
+ }
+ if (recipeTitle.isEmpty()) {
+ System.out.println("The title of the recipe shouldn't be empty! Please give a valid title!");
+ return;
+ }
+ Recipe newRecipe = new Recipe(recipeTitle, stepsInString, ingredientsInString);
+ if (recipeIndexToOverwrite != -1) {
+ recipes.deleteRecipe(recipeIndexToOverwrite);
+ }
+ recipes.addRecipe(newRecipe);
+ Ui.printAddRecipeSuccess(recipeTitle);
+ }
+
+ /**
+ * Obtain recipe index from user input
+ *
+ * @param content user input
+ * @param recipeTitle recipe title
+ * @return recipe index
+ * @throws EssenFormatException if input is invalid
+ */
+ public int obtainRecipeIndex(String content, String recipeTitle) throws EssenFormatException {
+ if (content.isBlank()) {
+ System.out.println("Recipe title is empty! Please enter valid title after \"r/\"");
+ throw new EssenFormatException();
+ }
+
+ if (!recipeTitle.isEmpty()) {
+ // user input more than one recipe title
+ System.out.println("Please only enter one recipe title!");
+ throw new EssenFormatException();
+ }
+
+ // return recipe index
+ return recipes.getIndexOfRecipe(content.trim());
+ }
+
+ /**
+ * Obtain steps content from user input
+ *
+ * @param content user input
+ * @param tag tag of the step
+ * @param stepsCounter number of steps
+ * @return steps content
+ * @throws EssenFormatException if input is invalid
+ */
+ public String getStepsContent(String content, String tag, int stepsCounter) throws EssenFormatException {
+ if (content.isBlank()) {
+ System.out.println("Step is empty! Please enter valid step after \"s/\"");
+ throw new EssenFormatException();
+ }
+
+ content = content.trim();
+ content = Step.convertToStepIdTemplate(content, stepsCounter+1);
+
+ if (tag != null) {
+ // this step belongs to a tag
+ content = content + " t/" + tag;
+ }
+
+ return content;
+ }
+
+ /**
+ * Obtain ingredient content from user input
+ *
+ * @param content user input
+ * @return ingredient content
+ * @throws EssenFormatException if input is invalid
+ */
+ public String getIngredientContent(String content) throws EssenFormatException {
+ if (content.isBlank()) {
+ System.out.println("Ingredient is empty! Please enter valid ingredient after \"i/\"");
+ throw new EssenFormatException();
+ }
+
+ if (!IngredientParser.isValidIngredient(content)) {
+ System.out.println("Ingredient is not valid! Please enter valid ingredient after \"i/\"");
+ throw new EssenFormatException();
+ }
+
+ return content;
+ }
+
+ /**
+ * Count the number of occurrences of a substring in a string
+ *
+ * @param mainStr main string to count from
+ * @param subStr substring to count
+ * @return number of occurrences
+ */
+ public static int countOccurrences(String mainStr, String subStr) {
+ int count = 0;
+ int index = 0;
+ while ((index = mainStr.indexOf(subStr, index)) != -1) {
+ count++;
+ index += subStr.length(); // Move to the end of the found substring and continue
+ }
+ return count;
+ }
+
+ /**
+ * Ask user if they want to overwrite existing recipe. If yes, delete existing recipe
+ *
+ * @return true if user wants to overwrite existing recipe, false otherwise
+ */
+ public boolean overwriteExistingRecipe() {
+ System.out.println("Recipe already exist! Do you want to overwrite it? (Y/N)");
+ String input = Ui.readUserInput();
+ if (input.equalsIgnoreCase("Y")) {
+ return true;
+ } else if (input.equalsIgnoreCase("N")) {
+ return false;
+ } else {
+ System.out.println("Invalid input! Please enter Y or N");
+ return overwriteExistingRecipe();
+ }
+ }
+
+
+}
diff --git a/src/main/java/essenmakanan/command/AddShortcutCommand.java b/src/main/java/essenmakanan/command/AddShortcutCommand.java
new file mode 100644
index 0000000000..69a4cea8ad
--- /dev/null
+++ b/src/main/java/essenmakanan/command/AddShortcutCommand.java
@@ -0,0 +1,66 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenInvalidQuantityException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.exception.EssenShortcutException;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import essenmakanan.parser.IngredientParser;
+import essenmakanan.shortcut.Shortcut;
+import essenmakanan.shortcut.ShortcutList;
+import essenmakanan.parser.ShortcutParser;
+import essenmakanan.ui.Ui;
+
+/**
+ * Represents an add shortcut command.
+ */
+public class AddShortcutCommand extends Command {
+
+ private ShortcutList shortcuts;
+ private IngredientList ingredients;
+ private String input;
+
+ /**
+ * Creates an add shortcut command.
+ *
+ * @param shortcuts The shortcut list.
+ * @param ingredients The ingredient list.
+ * @param input The given input.
+ */
+ public AddShortcutCommand(ShortcutList shortcuts, IngredientList ingredients, String input) {
+ this.shortcuts = shortcuts;
+ this.ingredients = ingredients;
+ this.input = input;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void executeCommand() {
+ try {
+ Shortcut shortcut = ShortcutParser.parseShortcut(ingredients, input);
+
+ if (shortcuts.exist(shortcut.getIngredientName())) {
+ throw new EssenShortcutException();
+ }
+
+ int ingredientIndex = IngredientParser.getIngredientIndex(ingredients, shortcut.getIngredientName());
+ IngredientUnit unit = ingredients.getIngredient(ingredientIndex).getUnit();
+
+ shortcuts.addShortcut(shortcut);
+ Ui.printAddShortcutSuccess(shortcut, unit);
+ } catch (EssenFormatException exception) {
+ Ui.drawDivider();
+ exception.handleException();
+ Ui.drawDivider();
+ } catch (EssenShortcutException exception) {
+ exception.handleException();
+ } catch (NumberFormatException exception) {
+ EssenInvalidQuantityException.handleException();
+ } catch (EssenOutOfRangeException exception) {
+ exception.handleException();
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/command/CheckRecipeCommand.java b/src/main/java/essenmakanan/command/CheckRecipeCommand.java
new file mode 100644
index 0000000000..bece901207
--- /dev/null
+++ b/src/main/java/essenmakanan/command/CheckRecipeCommand.java
@@ -0,0 +1,136 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import essenmakanan.parser.IngredientParser;
+import essenmakanan.parser.RecipeParser;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeIngredientList;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.ui.Ui;
+
+public class CheckRecipeCommand extends Command {
+
+ public IngredientList missingIngredients;
+ public IngredientList insufficientIngredients;
+ public IngredientList diffUnitIngredients;
+ private String input; //either id or name of recipe
+ private IngredientList ingredients;
+ private RecipeList recipes;
+ private RecipeIngredientList recipeIngredients;
+
+ public CheckRecipeCommand(String input, RecipeList recipes, IngredientList ingredients) {
+ this.input = input;
+ this.ingredients = ingredients;
+ this.recipes = recipes;
+
+ this.missingIngredients = new IngredientList();
+ this.insufficientIngredients = new IngredientList();
+ this.diffUnitIngredients = new IngredientList();
+ }
+
+ /**
+ * To get the missing ingredient list (ingredients that are not in the inventory)
+ *
+ * @return IngredientList of missing ingredients
+ */
+ public IngredientList getMissingIngredients() {
+ return this.missingIngredients;
+ }
+
+ /**
+ * To get the list of insufficient ingredients (ingredients in the inventory that have insufficient quantity)
+ *
+ * @return IngredientList of insufficient ingredients
+ */
+ public IngredientList getInsufficientIngredients() {
+ return this.insufficientIngredients;
+ }
+
+
+ /**
+ * To check if two ingredients have the same unit
+ *
+ * @param ingredient1 first ingredient to compare
+ * @param ingredient2 second ingredient to compare
+ * @return boolean of if the units of both ingredients are the same
+ */
+ public static boolean sameUnit(Ingredient ingredient1, Ingredient ingredient2) {
+ return ingredient1.getUnit().equals(ingredient2.getUnit());
+ }
+
+ /**
+ * Compare ingredients in a recipe and ingredients in the inventory.
+ * Update missingIngredients, diffUnitIngredients and insufficientIngredients accordingly.
+ */
+ private void getIngredientsStillNeeded() {
+ String recipeIngredientName;
+ IngredientUnit recipeIngredientUnit;
+
+ for (Ingredient recipeIngredient : recipeIngredients.getIngredients()) {
+ recipeIngredientName = recipeIngredient.getName();
+ recipeIngredientUnit = recipeIngredient.getUnit();
+ if (!ingredients.exist(recipeIngredientName)) {
+ missingIngredients.addIngredient(recipeIngredient);
+ } else {
+ Ingredient inventoryIngredient = ingredients.getIngredient(recipeIngredientName);
+ boolean isSameUnit = sameUnit(inventoryIngredient, recipeIngredient);
+
+ if (!isSameUnit) {
+ diffUnitIngredients.addIngredient(recipeIngredient);
+ }
+
+ Double missingQuantity = 0.0;
+ if (isSameUnit) {
+ missingQuantity = IngredientParser.getInsufficientQuantity(recipeIngredient, inventoryIngredient);
+ }
+ if (isSameUnit && !missingQuantity.equals(0.0)) {
+ Ingredient lackingIngredient = new Ingredient(
+ recipeIngredientName, missingQuantity, recipeIngredientUnit);
+ insufficientIngredients.addIngredient(lackingIngredient);
+ }
+ }
+
+ }
+ }
+
+ /**
+ * To check if all ingredients needed for a recipe are in the ingredient inventory
+ *
+ * @param recipeIngredients that we want to check if it is available in our ingredient inventory
+ * @return boolean of whether user has all ingredients
+ */
+ public boolean allIngredientsReady(RecipeIngredientList recipeIngredients) {
+ this.recipeIngredients = recipeIngredients;
+ this.getIngredientsStillNeeded();
+ boolean allEmpty = this.missingIngredients.isEmpty()
+ && this.insufficientIngredients.isEmpty()
+ && this.diffUnitIngredients.isEmpty();
+ if (allEmpty) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void executeCommand() {
+ try {
+ int recipeIndex = RecipeParser.getRecipeIndex(recipes, input);
+ Recipe recipe = recipes.getRecipe(recipeIndex);
+
+ String recipeTitle = recipe.getTitle();
+ recipeIngredients = recipe.getRecipeIngredients();
+
+ getIngredientsStillNeeded();
+
+ Ui.printStartRecipeMessage(missingIngredients, insufficientIngredients, diffUnitIngredients, recipeTitle);
+ } catch (EssenOutOfRangeException | EssenFormatException e) {
+ e.handleException();
+ }
+ }
+
+}
diff --git a/src/main/java/essenmakanan/command/Command.java b/src/main/java/essenmakanan/command/Command.java
new file mode 100644
index 0000000000..eb520e86cc
--- /dev/null
+++ b/src/main/java/essenmakanan/command/Command.java
@@ -0,0 +1,22 @@
+package essenmakanan.command;
+
+import essenmakanan.ui.Ui;
+
+/**
+ * Represent a command in the application.
+ */
+public abstract class Command {
+ protected Ui ui;
+
+ /**
+ * Creates a new command.
+ */
+ public Command() {
+ ui = new Ui();
+ }
+
+ /**
+ * Executes the command's functions.
+ */
+ public abstract void executeCommand();
+}
diff --git a/src/main/java/essenmakanan/command/DeleteIngredientCommand.java b/src/main/java/essenmakanan/command/DeleteIngredientCommand.java
new file mode 100644
index 0000000000..5c4cd2ffdb
--- /dev/null
+++ b/src/main/java/essenmakanan/command/DeleteIngredientCommand.java
@@ -0,0 +1,25 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.parser.IngredientParser;
+
+public class DeleteIngredientCommand extends Command {
+ private IngredientList ingredients;
+ private String ingredientInput;
+
+ public DeleteIngredientCommand(IngredientList ingredients, String ingredientInput) {
+ this.ingredients = ingredients;
+ this.ingredientInput = ingredientInput;
+ }
+
+ @Override
+ public void executeCommand() {
+ try {
+ int ingredientIndex = IngredientParser.getIngredientIndex(ingredients, ingredientInput);
+ ingredients.deleteIngredient(ingredientIndex);
+ } catch (EssenOutOfRangeException e) {
+ e.handleException();
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/command/DeleteRecipeCommand.java b/src/main/java/essenmakanan/command/DeleteRecipeCommand.java
new file mode 100644
index 0000000000..e42bed8c0f
--- /dev/null
+++ b/src/main/java/essenmakanan/command/DeleteRecipeCommand.java
@@ -0,0 +1,26 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.parser.RecipeParser;
+import essenmakanan.recipe.RecipeList;
+
+public class DeleteRecipeCommand extends Command {
+ private RecipeList recipes;
+ private String recipeInput;
+
+ public DeleteRecipeCommand(RecipeList recipes, String recipeInput) {
+ this.recipes = recipes;
+ this.recipeInput = recipeInput;
+ }
+
+ @Override
+ public void executeCommand() {
+ try {
+ int recipeIndex = RecipeParser.getRecipeIndex(recipes, recipeInput);
+ recipes.deleteRecipe(recipeIndex);
+ } catch (EssenOutOfRangeException | EssenFormatException e) {
+ e.handleException();
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/command/DeleteShortcutCommand.java b/src/main/java/essenmakanan/command/DeleteShortcutCommand.java
new file mode 100644
index 0000000000..9134abf11d
--- /dev/null
+++ b/src/main/java/essenmakanan/command/DeleteShortcutCommand.java
@@ -0,0 +1,42 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.parser.ShortcutParser;
+import essenmakanan.shortcut.ShortcutList;
+import essenmakanan.ui.Ui;
+
+/**
+ * Represents a delete shortcut command.
+ */
+public class DeleteShortcutCommand extends Command {
+
+ private ShortcutList shortcuts;
+ private String input;
+
+ /**
+ * Creates a delete shortcut command.
+ *
+ * @param shortcuts The shortcut list.
+ * @param input The given input.
+ */
+ public DeleteShortcutCommand(ShortcutList shortcuts, String input) {
+ this.shortcuts = shortcuts;
+ this.input = input;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void executeCommand() {
+ try {
+ input = input.replace("sc/", "");
+ int shortcutIndex = ShortcutParser.getShortcutIndex(shortcuts, input);
+ shortcuts.deleteShortcut(shortcutIndex);
+ } catch (EssenOutOfRangeException exception) {
+ Ui.drawDivider();
+ exception.handleException();
+ Ui.drawDivider();
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/command/DuplicateRecipeCommand.java b/src/main/java/essenmakanan/command/DuplicateRecipeCommand.java
new file mode 100644
index 0000000000..3d159f8c92
--- /dev/null
+++ b/src/main/java/essenmakanan/command/DuplicateRecipeCommand.java
@@ -0,0 +1,46 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.parser.RecipeParser;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.ui.Ui;
+
+/**
+ * Represents a duplicate recipe command.
+ */
+public class DuplicateRecipeCommand extends Command {
+
+ private RecipeList recipes;
+ private String toDuplicate;
+
+ /**
+ * Creates a duplicate recipe command.
+ *
+ * @param recipes The recipe list.
+ * @param toDuplicate The given input.
+ */
+ public DuplicateRecipeCommand(RecipeList recipes, String toDuplicate) {
+ super();
+ this.recipes = recipes;
+ this.toDuplicate = toDuplicate;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void executeCommand() {
+ try {
+ int index = RecipeParser.getRecipeIndex(recipes, toDuplicate);
+ Recipe recipe = recipes.getRecipe(index);
+ Recipe copiedRecipe = new Recipe(recipe.getTitle() + " (copy)", recipe.getRecipeSteps()
+ , recipe.getRecipeIngredients());
+ recipes.addRecipe(copiedRecipe);
+ Ui.printDuplicatedRecipe(recipe.getTitle());
+ } catch(EssenOutOfRangeException | EssenFormatException exception) {
+ exception.handleException();
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/command/EditIngredientCommand.java b/src/main/java/essenmakanan/command/EditIngredientCommand.java
new file mode 100644
index 0000000000..87b693852d
--- /dev/null
+++ b/src/main/java/essenmakanan/command/EditIngredientCommand.java
@@ -0,0 +1,48 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+
+import java.util.Arrays;
+
+public class EditIngredientCommand extends Command {
+ private String editDetails;
+ private IngredientList ingredients;
+
+ public EditIngredientCommand(String input, IngredientList ingredients) {
+ super();
+ this.editDetails = input;
+ this.ingredients = ingredients;
+ }
+
+ /**
+ * Check if command and its contents are valid. Parse a valid Ingredient and edit the ingredient in the list
+ */
+ @Override
+ public void executeCommand() {
+ Ingredient existingIngredient;
+
+ this.editDetails = this.editDetails.replace("i/", "");
+
+ String[] splitDetails = this.editDetails.split(" ");
+ String ingredientName = splitDetails[0];
+ String[] ingredientEditDetails = Arrays.copyOfRange(splitDetails, 1, splitDetails.length);
+
+ existingIngredient = ingredients.getIngredient(ingredientName);
+
+ assert existingIngredient.getName().equals(ingredientName)
+ : "Selected ingredient does not have matching name.";
+
+ if (existingIngredient == null) {
+ System.out.println("Ingredient not found!");
+ } else {
+ try {
+ ingredients.editIngredient(existingIngredient, ingredientEditDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+ }
+
+ }
+}
diff --git a/src/main/java/essenmakanan/command/EditRecipeCommand.java b/src/main/java/essenmakanan/command/EditRecipeCommand.java
new file mode 100644
index 0000000000..d3a9ca7a65
--- /dev/null
+++ b/src/main/java/essenmakanan/command/EditRecipeCommand.java
@@ -0,0 +1,147 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenDoesNotExistException;
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenInvalidEditException;
+import essenmakanan.exception.EssenNullInputException;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeList;
+
+public class EditRecipeCommand extends Command {
+
+ private String editDetails;
+ private RecipeList recipes;
+
+ public EditRecipeCommand(String editDetails, RecipeList recipes) {
+ super();
+ this.editDetails = editDetails;
+ this.recipes = recipes;
+ }
+
+ /**
+ * Check if command and its contents are valid. Parse a valid Recipe and edit the recipe in the list
+ */
+ @Override
+ public void executeCommand() {
+
+ assert editDetails.startsWith("r/") : "Parser did not catch incomplete input";
+ this.editDetails = this.editDetails.replace("r/", "");
+
+ // getting recipe title
+ String recipeTitle = null;
+ try {
+ recipeTitle = getRecipeTitle();
+ } catch (EssenNullInputException e) {
+ return;
+ }
+
+ // getting recipe from recipe list based on title
+ Recipe existingRecipe = null;
+ try {
+ existingRecipe = recipes.getRecipe(recipeTitle);
+ if (existingRecipe == null) {
+ System.out.println("Recipe not found!");
+ throw new EssenDoesNotExistException();
+ }
+ } catch (EssenDoesNotExistException e){
+ e.handleException();
+ return;
+ }
+
+ // getting the attributes to edit
+ String[] attributesToEdit = null;
+ try {
+ attributesToEdit = getAttributesToEdit();
+ } catch (EssenInvalidEditException e) {
+ e.handleException();
+ }
+
+ assert !(attributesToEdit==null) : "Attributes is null";
+
+ try {
+ recipes.editRecipe(existingRecipe, attributesToEdit);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ }
+
+ /**
+ * Returns the recipe title from the editDetails string.
+ * @return recipe title
+ */
+ public String getRecipeTitle() throws EssenNullInputException {
+ int firstFlag = editDetails.indexOf("/");
+
+ if ( firstFlag < 4 ){
+ // missing other flags or missing title
+ throw new EssenNullInputException();
+ }
+
+ String recipeTitle = editDetails.substring(0, firstFlag-2).trim();
+
+ return recipeTitle;
+
+ }
+
+ /**
+ * Returns the attributes to edit from the editDetails string.
+ * @return attributes to edit
+ */
+ public String[] getAttributesToEdit() throws EssenInvalidEditException {
+ int firstFlag = editDetails.indexOf("/");
+ int totalFlagsMinusTitle = editDetails.split("/").length - 1;
+
+ String[] attributesToEdit = new String[totalFlagsMinusTitle];
+ int attributeCounter = 0;
+
+ while (firstFlag != -1) {
+ if ((firstFlag + 1) >= editDetails.length()) {
+ System.out.println("Please provide details to edit");
+ throw new EssenInvalidEditException();
+ }
+
+ int nextFlag = editDetails.indexOf("/", firstFlag+1);
+
+ if (nextFlag != -1) {
+ attributesToEdit[attributeCounter] = editDetails.substring(firstFlag-1, nextFlag-2).trim();
+ } else {
+ attributesToEdit[attributeCounter] = editDetails.substring(firstFlag-1).trim();
+ }
+ attributeCounter++;
+ firstFlag = nextFlag;
+ }
+
+ if (attributesToEdit.length == 0) {
+ System.out.println("Please provide details to edit");
+ throw new EssenInvalidEditException();
+ }
+
+ for (String attribute : attributesToEdit) {
+ if (!attribute.contains("n/") && !attribute.contains("s/") && !attribute.contains("i/")) {
+ // must have either one of these flags
+ System.out.println("Flag is invalid or format is incorrect.");
+ throw new EssenInvalidEditException();
+ } else if (this.detailsIsNull(attribute)){
+ throw new EssenInvalidEditException();
+ }
+ }
+
+ return attributesToEdit;
+ }
+
+ /**
+ * Given user input for editing, return true if details not provided.
+ * @param details string in the format: FLAG/CONTENT
+ * @return true if details is null
+ */
+ public boolean detailsIsNull(String details) {
+ int flag = details.indexOf("/");
+ String content = details.substring(flag+1);
+ if (content.isEmpty()) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/essenmakanan/command/EditShortcutCommand.java b/src/main/java/essenmakanan/command/EditShortcutCommand.java
new file mode 100644
index 0000000000..0b6f7f57d8
--- /dev/null
+++ b/src/main/java/essenmakanan/command/EditShortcutCommand.java
@@ -0,0 +1,56 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.parser.ShortcutParser;
+import essenmakanan.shortcut.Shortcut;
+import essenmakanan.shortcut.ShortcutList;
+
+/**
+ * Represents an edit shortcut command.
+ */
+public class EditShortcutCommand extends Command {
+
+ private ShortcutList shortcuts;
+ private IngredientList ingredients;
+ private String input;
+
+ /**
+ * Creates an edit shortcut command.
+ *
+ * @param shortcuts The shortcut list.
+ * @param ingredients The ingredient list.
+ * @param input The given input.
+ */
+ public EditShortcutCommand(ShortcutList shortcuts, IngredientList ingredients, String input) {
+ this.shortcuts = shortcuts;
+ this.ingredients = ingredients;
+ this.input = input;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void executeCommand() {
+ input = input.replace("sc/", "");
+
+ String[] splitDetails = input.split(" ");
+
+ try {
+ if (splitDetails.length == 0) {
+ throw new EssenFormatException();
+ }
+
+ int shortcutIndex = ShortcutParser.getShortcutIndex(shortcuts, splitDetails[0]);
+ Shortcut shortcut = shortcuts.getShortcut(shortcutIndex);
+
+ ShortcutParser.editShortcut(shortcuts, shortcut, ingredients, splitDetails);
+ } catch (EssenOutOfRangeException exception) {
+ exception.handleException();
+ } catch (EssenFormatException exception) {
+ exception.handleException();
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/command/ExecuteRecipeCommand.java b/src/main/java/essenmakanan/command/ExecuteRecipeCommand.java
new file mode 100644
index 0000000000..c835a9e6b7
--- /dev/null
+++ b/src/main/java/essenmakanan/command/ExecuteRecipeCommand.java
@@ -0,0 +1,98 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenNullInputException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.parser.RecipeParser;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeIngredientList;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.ui.Ui;
+
+public class ExecuteRecipeCommand extends Command {
+ private String recipeTitleToStart;
+ private RecipeList recipes;
+ private IngredientList allIngredientsList;
+ private CheckRecipeCommand checkRecipeCommand;
+ private IngredientList recipeIngredients;
+
+
+ public ExecuteRecipeCommand(IngredientList allIngredientsList, RecipeList recipes, String recipeTitleToStart) {
+ super();
+ this.recipeTitleToStart = recipeTitleToStart;
+ this.recipes = recipes;
+ this.allIngredientsList = allIngredientsList;
+ this.checkRecipeCommand = new CheckRecipeCommand(recipeTitleToStart, recipes, allIngredientsList);
+ }
+
+ /**
+ * Check if structure of command is valid and execute recipe by decreasing quantity of ingredients used for recipe
+ */
+ @Override
+ public void executeCommand() {
+ Recipe recipe = null;
+ try {
+ recipe = this.getRecipe();
+ if (recipe == null) {
+ return;
+ }
+ } catch (EssenNullInputException e) {
+ e.handleException();
+ }
+
+ RecipeIngredientList recipeIngredients = recipe.getRecipeIngredients();
+ if (checkRecipeCommand.allIngredientsReady(recipeIngredients)){
+ // if recipe is null, program should have terminated before this
+ assert recipe != null : "Recipe should not be null";
+ updateAllIngredientQuantity(recipeIngredients);
+ Ui.printExecuteRecipeSuccess(recipe.getTitle());
+ } else {
+ Ui.printExecuteRecipeFail(recipe.getTitle());
+ }
+
+ }
+
+ /**
+ * Get recipe from recipe list based on title
+ *
+ * @return Recipe object
+ * @throws EssenNullInputException if recipe title is empty
+ */
+ public Recipe getRecipe() throws EssenNullInputException {
+ try {
+ if (recipeTitleToStart.isEmpty()) {
+ System.out.println("Recipe Title is empty! Please enter valid title after \"execute\"");
+ throw new EssenNullInputException();
+ }
+
+ int recipeIndex = RecipeParser.getRecipeIndex(recipes, recipeTitleToStart);
+ Recipe recipe = recipes.getRecipe(recipeIndex);
+ return recipe;
+ } catch (EssenOutOfRangeException | EssenFormatException e) {
+ e.handleException();
+ }
+ return null;
+ }
+
+ /**
+ * Update quantity of ingredients used for recipe by decreasing the quantity used.
+ *
+ * @param recipeIngredients
+ */
+ public void updateAllIngredientQuantity(RecipeIngredientList recipeIngredients) {
+ for (Ingredient ingredient: recipeIngredients.getIngredients()) {
+ try {
+ // Decrease ingredient quantity in allIngredientsList
+ Ingredient tempIngredient = new Ingredient(ingredient.getName(), ingredient.getQuantity(),
+ ingredient.getUnit());
+ tempIngredient.setQuantity(-tempIngredient.getQuantity());
+ allIngredientsList.updateIngredient(tempIngredient);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/essenmakanan/command/ExitCommand.java b/src/main/java/essenmakanan/command/ExitCommand.java
new file mode 100644
index 0000000000..8f4f78e56f
--- /dev/null
+++ b/src/main/java/essenmakanan/command/ExitCommand.java
@@ -0,0 +1,32 @@
+package essenmakanan.command;
+
+/**
+ * Represents an exit command.
+ */
+public class ExitCommand extends Command {
+
+ /**
+ * Creates an exit command.
+ */
+ public ExitCommand() {
+ super();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void executeCommand() {
+ ui.bye();
+ }
+
+ /**
+ * Checks if it is an exit command.
+ *
+ * @param command The given command
+ * @return Confirmation if the command is an exit command.
+ */
+ public static boolean isExitCommand(Command command) {
+ return (command instanceof ExitCommand);
+ }
+}
diff --git a/src/main/java/essenmakanan/command/FilterRecipesCommand.java b/src/main/java/essenmakanan/command/FilterRecipesCommand.java
new file mode 100644
index 0000000000..e53dbb56d6
--- /dev/null
+++ b/src/main/java/essenmakanan/command/FilterRecipesCommand.java
@@ -0,0 +1,49 @@
+package essenmakanan.command;
+
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeIngredientList;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.ui.Ui;
+
+public class FilterRecipesCommand extends Command {
+ private String input;
+ private RecipeList recipes;
+ private RecipeIngredientList recipeingredientList;
+
+ public FilterRecipesCommand(String input, RecipeList recipes) {
+ this.input = input;
+ this.recipes = recipes;
+ }
+
+ /**
+ * To filter out all recipes containing a specific ingredient
+ *
+ * @param ingredientName the name of ingredient we want to find in recipes
+ * @return recipes with the specific ingredient
+ */
+ private RecipeList filterRecipes(String ingredientName) {
+ RecipeList filteredRecipes = new RecipeList();
+
+ for (Recipe recipe : recipes.getRecipes()) {
+ recipeingredientList = recipe.getRecipeIngredients();
+ if (recipeingredientList.ingredientExist(ingredientName)) {
+ filteredRecipes.addRecipe(recipe);
+ }
+ }
+ return filteredRecipes;
+ }
+
+
+ @Override
+ public void executeCommand() {
+ boolean isFirst = true;
+ for (String ingredientName : input.split("i/")) {
+ if (!isFirst) {
+ ingredientName = ingredientName.trim();
+ RecipeList filteredRecipes = filterRecipes(ingredientName);
+ Ui.printFilteredRecipes(filteredRecipes, ingredientName);
+ }
+ isFirst = false;
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/command/HelpCommand.java b/src/main/java/essenmakanan/command/HelpCommand.java
new file mode 100644
index 0000000000..e00699089e
--- /dev/null
+++ b/src/main/java/essenmakanan/command/HelpCommand.java
@@ -0,0 +1,22 @@
+package essenmakanan.command;
+
+/**
+ * Represents a help command.
+ */
+public class HelpCommand extends Command {
+
+ /**
+ * Creates a help command.
+ */
+ public HelpCommand() {
+ super();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void executeCommand() {
+ ui.showCommands();
+ }
+}
diff --git a/src/main/java/essenmakanan/command/PlanRecipesCommand.java b/src/main/java/essenmakanan/command/PlanRecipesCommand.java
new file mode 100644
index 0000000000..5bd62c6956
--- /dev/null
+++ b/src/main/java/essenmakanan/command/PlanRecipesCommand.java
@@ -0,0 +1,155 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import essenmakanan.parser.IngredientParser;
+import essenmakanan.parser.RecipeParser;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeIngredientList;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.ui.Ui;
+
+public class PlanRecipesCommand extends Command {
+ public IngredientList missingIngredients;
+ public IngredientList allIngredientsNeeded;
+
+ private String input; //either id or name of recipe
+ private IngredientList ingredients;
+ private RecipeList recipes;
+ private RecipeList allRecipes;
+
+ public PlanRecipesCommand(IngredientList ingredients, RecipeList recipes, String input) {
+ this.ingredients = ingredients;
+ this.recipes = recipes;
+ this.input = input;
+
+ this.allRecipes = new RecipeList();
+ this.allIngredientsNeeded = new IngredientList();
+ this.missingIngredients = new IngredientList();
+ }
+
+ /**
+ * Get an IngredientList of missing ingredients from the inventory that are needed
+ * to plan for recipes
+ *
+ * @return IngredientList of missing ingredients
+ */
+ public IngredientList getMissingIngredients() {
+ return this.missingIngredients;
+ }
+
+ /**
+ * Get an IngredientList of all ingredients needed for all the recipes indicated
+ *
+ * @return IngredientList of all ingredients needed
+ */
+ public IngredientList getAllIngredientsNeeded() {
+ return this.allIngredientsNeeded;
+ }
+
+ /**
+ * Compare ingredients in inventory and ingredients needed for all recipes indicated.
+ * Then, method updates IngredientList of allIngredientsNeeded and missingIngredients respectively.
+ */
+ public void setMissingIngredients() {
+ String ingredientName;
+ Ingredient ingredientAvailable;
+ Double missingQuantity = 0.0;
+ IngredientUnit ingredientUnit;
+
+ for (Ingredient ingredientNeeded : allIngredientsNeeded.getIngredients()) {
+ ingredientName = ingredientNeeded.getName();
+ ingredientUnit = ingredientNeeded.getUnit();
+ if (ingredients.exist(ingredientName)) {
+ ingredientAvailable = ingredients.getIngredient(ingredientName);
+ assert ingredientUnit == ingredientAvailable.getUnit() :
+ "Unit must be standardised for the same ingredient";
+ missingQuantity = IngredientParser.getInsufficientQuantity(ingredientNeeded, ingredientAvailable);
+ if (missingQuantity != 0.0) {
+ Ingredient lackingIngredient = new Ingredient(ingredientName, missingQuantity, ingredientUnit);
+ missingIngredients.addIngredient(lackingIngredient);
+ }
+ } else {
+ missingIngredients.addIngredient(ingredientNeeded);
+ }
+ }
+ }
+
+ /**
+ * To get an Ingredient List of all ingredients needed for all recipes in the recipe list
+ *
+ * @param recipes is a RecipeList of all recipes the user wants to process
+ * @return all ingredients in the list of recipes
+ */
+ public static IngredientList getIngredientsFromRecipes(RecipeList recipes) {
+ IngredientList allIngredients = new IngredientList();
+ RecipeIngredientList recipeIngredients;
+
+ for (Recipe recipe : recipes.getRecipes()) {
+ recipeIngredients = recipe.getRecipeIngredients();
+ for (Ingredient ingredient : recipeIngredients.getIngredients()) {
+ allIngredients.addIngredient(ingredient);
+ }
+ }
+ return allIngredients;
+ }
+
+ /**
+ * To transform a string of input "r/... r/..." to an integer array such as [1, 4, ...]
+ *
+ * @param input is a String from the user
+ * @return an integer array consisting of index of recipes
+ */
+ public static int[] getRecipeIdList(String input) {
+ String[] recipeInputList = RecipeParser.getPlannedRecipesString(input);
+ int[] recipeIdList = new int[recipeInputList.length];
+
+ String recipeString;
+ for (int i = 0; i < recipeInputList.length; i++) {
+ recipeString = recipeInputList[i].trim();
+ recipeIdList[i] = Integer.parseInt(recipeString) - 1;
+ }
+ return recipeIdList;
+ }
+
+ /**
+ * Get a RecipeList with recipes from a recipe ID list
+ *
+ * @param recipeIdList a list of ids of recipes
+ * @param recipes all recipes the user has
+ * @return RecipeList containing of all recipes that correspond to the index in the recipe ID list
+ * @throws EssenOutOfRangeException when the ID in recipe ID list is invalid
+ */
+ public static RecipeList getRecipes(int[] recipeIdList, RecipeList recipes) throws EssenOutOfRangeException {
+ RecipeList allRecipes = new RecipeList();
+ for (int id : recipeIdList) {
+ if (!recipes.recipeExist(id)) {
+ System.out.println("Your recipe Id is wrong");
+ throw new EssenOutOfRangeException();
+ }
+ allRecipes.addRecipe(recipes.getRecipe(id));
+ }
+ return allRecipes;
+ }
+
+ @Override
+ public void executeCommand() {
+ try {
+ RecipeParser.parsePlanCommandInput(input);
+
+ String[] inputList = input.split(" ", 2);
+ int[] recipeIdList = getRecipeIdList(inputList[1]);
+
+ this.allRecipes = getRecipes(recipeIdList, recipes);
+ this.allIngredientsNeeded = getIngredientsFromRecipes(allRecipes);
+ setMissingIngredients();
+
+ Ui.printPlanCommandIngredients(allIngredientsNeeded, missingIngredients, allRecipes);
+ } catch (EssenFormatException | EssenOutOfRangeException e) {
+ e.handleException();
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/command/UseIngredientCommand.java b/src/main/java/essenmakanan/command/UseIngredientCommand.java
new file mode 100644
index 0000000000..ef23cb1126
--- /dev/null
+++ b/src/main/java/essenmakanan/command/UseIngredientCommand.java
@@ -0,0 +1,63 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.parser.IngredientParser;
+import essenmakanan.ui.Ui;
+
+public class UseIngredientCommand extends Command {
+ private String toUse;
+ private IngredientList ingredients;
+
+ public UseIngredientCommand(IngredientList ingredients, String toUse) {
+ super();
+ this.toUse = toUse;
+ this.ingredients = ingredients;
+ }
+
+ /**
+ * Iterate through the ingredients to use and update the quantity of the ingredients in the list
+ */
+ @Override
+ public void executeCommand() {
+ String[] allIngredients = toUse.split("i/");
+
+ for (String ingredient : allIngredients) {
+ if (ingredient.isEmpty()) {
+ continue;
+ }
+
+ updateIngredientQuantity(ingredient);
+
+ }
+ }
+
+ /**
+ * Update the quantity of the ingredient in the list
+ *
+ * @param ingredientString String containing ingredient name, quantity and unit
+ */
+ public void updateIngredientQuantity(String ingredientString) {
+ try {
+ Ingredient newIngredient = IngredientParser.parseIngredient(ingredientString);
+
+ // you cannot use negative quantity of ingredients
+ if (newIngredient.getQuantity() < 0) {
+ Ui.printNegativeIngredientQuantity();
+ return;
+ }
+
+ if (this.ingredients.exist(newIngredient.getName())) {
+ // if ingredient already exists, update the quantity by changing quantity to negative
+ newIngredient.setQuantity(-newIngredient.getQuantity());
+ this.ingredients.updateIngredient(newIngredient);
+ } else {
+ // do nothing
+ Ui.printIngredientDoesNotExist(newIngredient.getName());
+ }
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/command/UseShortcutCommand.java b/src/main/java/essenmakanan/command/UseShortcutCommand.java
new file mode 100644
index 0000000000..d1b87dc61d
--- /dev/null
+++ b/src/main/java/essenmakanan/command/UseShortcutCommand.java
@@ -0,0 +1,61 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import essenmakanan.parser.IngredientParser;
+import essenmakanan.parser.ShortcutParser;
+import essenmakanan.shortcut.Shortcut;
+import essenmakanan.shortcut.ShortcutList;
+import essenmakanan.ui.Ui;
+
+/**
+ * Creates a use shortcut command.
+ */
+public class UseShortcutCommand extends Command {
+
+ private ShortcutList shortcuts;
+ private IngredientList ingredients;
+ private String input;
+
+ /**
+ * Creates a use shortcut command.
+ *
+ * @param shortcuts The shortcut list.
+ * @param ingredients The ingredient list.
+ * @param input The given input.
+ */
+ public UseShortcutCommand(ShortcutList shortcuts, IngredientList ingredients, String input) {
+ this.shortcuts = shortcuts;
+ this.ingredients = ingredients;
+ this.input = input;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void executeCommand() {
+ Ui.drawDivider();
+
+ try {
+ int shortcutIndex = ShortcutParser.getShortcutIndex(shortcuts, input);
+ Shortcut shortcut = shortcuts.getShortcut(shortcutIndex);
+ String ingredientName = shortcut.getIngredientName();
+ double quantity = shortcut.getQuantity();
+
+ int ingredientIndex = IngredientParser.getIngredientIndex(ingredients, ingredientName);
+ IngredientUnit unit = ingredients.getIngredient(ingredientIndex).getUnit();
+
+ ingredients.updateIngredient(new Ingredient(ingredientName, quantity, unit));
+ } catch (EssenOutOfRangeException exception) {
+ exception.handleException();
+ Ui.drawDivider();
+ } catch (EssenFormatException exception) {
+ exception.handleException();
+ Ui.drawDivider();
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/command/ViewAllAvailableRecipesCommand.java b/src/main/java/essenmakanan/command/ViewAllAvailableRecipesCommand.java
new file mode 100644
index 0000000000..73acd9f982
--- /dev/null
+++ b/src/main/java/essenmakanan/command/ViewAllAvailableRecipesCommand.java
@@ -0,0 +1,38 @@
+package essenmakanan.command;
+
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeIngredientList;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.ui.Ui;
+
+public class ViewAllAvailableRecipesCommand extends Command {
+ private IngredientList ingredients;
+ private RecipeList recipes;
+ private String input;
+ private CheckRecipeCommand checkRecipeCommand;
+ private RecipeList allAvailableRecipes;
+
+ public ViewAllAvailableRecipesCommand(RecipeList recipes, IngredientList ingredients, String input) {
+ this.ingredients = ingredients;
+ this.recipes = recipes;
+ this.input = input;
+ this.allAvailableRecipes = new RecipeList();
+ }
+
+
+ @Override
+ public void executeCommand() {
+ for (Recipe recipe : recipes.getRecipes()) {
+ String recipeTitleToStart = recipe.getTitle();
+ checkRecipeCommand = new CheckRecipeCommand(recipeTitleToStart, recipes, ingredients);
+
+ RecipeIngredientList recipeIngredients = recipe.getRecipeIngredients();
+
+ if (checkRecipeCommand.allIngredientsReady(recipeIngredients)) {
+ allAvailableRecipes.addRecipe(recipe);
+ }
+ }
+ Ui.printAllAvailableRecipes(allAvailableRecipes);
+ }
+}
diff --git a/src/main/java/essenmakanan/command/ViewIngredientsCommand.java b/src/main/java/essenmakanan/command/ViewIngredientsCommand.java
new file mode 100644
index 0000000000..8f898b29cd
--- /dev/null
+++ b/src/main/java/essenmakanan/command/ViewIngredientsCommand.java
@@ -0,0 +1,19 @@
+package essenmakanan.command;
+
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ui.Ui;
+
+public class ViewIngredientsCommand extends Command {
+
+ private IngredientList ingredients;
+
+ public ViewIngredientsCommand(IngredientList ingredients) {
+ super();
+ this.ingredients = ingredients;
+ }
+
+ @Override
+ public void executeCommand() {
+ Ui.printAllIngredients(ingredients);
+ }
+}
diff --git a/src/main/java/essenmakanan/command/ViewRecipesCommand.java b/src/main/java/essenmakanan/command/ViewRecipesCommand.java
new file mode 100644
index 0000000000..24dc5463a2
--- /dev/null
+++ b/src/main/java/essenmakanan/command/ViewRecipesCommand.java
@@ -0,0 +1,20 @@
+package essenmakanan.command;
+
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.ui.Ui;
+
+public class ViewRecipesCommand extends Command {
+
+ private RecipeList recipes;
+
+ public ViewRecipesCommand(RecipeList recipes) {
+ super();
+ this.recipes = recipes;
+ }
+
+ @Override
+ public void executeCommand() {
+ Ui.printAllRecipes(recipes);
+ }
+
+}
diff --git a/src/main/java/essenmakanan/command/ViewShortcutsCommand.java b/src/main/java/essenmakanan/command/ViewShortcutsCommand.java
new file mode 100644
index 0000000000..993f7d73c5
--- /dev/null
+++ b/src/main/java/essenmakanan/command/ViewShortcutsCommand.java
@@ -0,0 +1,29 @@
+package essenmakanan.command;
+
+import essenmakanan.shortcut.ShortcutList;
+import essenmakanan.ui.Ui;
+
+/**
+ * Represents a view shortcuts command.
+ */
+public class ViewShortcutsCommand extends Command {
+
+ private ShortcutList shortcuts;
+
+ /**
+ * Creates a view shortcuts command.
+ *
+ * @param shortcuts The shortcut list.
+ */
+ public ViewShortcutsCommand(ShortcutList shortcuts) {
+ this.shortcuts = shortcuts;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void executeCommand() {
+ Ui.printAllShortcuts(shortcuts);
+ }
+}
diff --git a/src/main/java/essenmakanan/command/ViewSpecificIngredientCommand.java b/src/main/java/essenmakanan/command/ViewSpecificIngredientCommand.java
new file mode 100644
index 0000000000..983b15d715
--- /dev/null
+++ b/src/main/java/essenmakanan/command/ViewSpecificIngredientCommand.java
@@ -0,0 +1,26 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.parser.IngredientParser;
+
+public class ViewSpecificIngredientCommand extends Command {
+ private IngredientList ingredients;
+ private String input;
+ public ViewSpecificIngredientCommand(IngredientList ingredients, String input) {
+ this.ingredients = ingredients;
+ this.input = input;
+ }
+
+ @Override
+ public void executeCommand() {
+ try {
+ int ingredientIndex = IngredientParser.getIngredientIndex(ingredients, input);
+ Ingredient ingredient = ingredients.getIngredient(ingredientIndex);
+ System.out.println(ingredient);
+ } catch (EssenOutOfRangeException e) {
+ e.handleException();
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/command/ViewSpecificRecipeCommand.java b/src/main/java/essenmakanan/command/ViewSpecificRecipeCommand.java
new file mode 100644
index 0000000000..5b59133736
--- /dev/null
+++ b/src/main/java/essenmakanan/command/ViewSpecificRecipeCommand.java
@@ -0,0 +1,36 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.parser.RecipeParser;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.ui.Ui;
+
+public class ViewSpecificRecipeCommand extends Command {
+
+ private String input;
+ private RecipeList recipes;
+
+ public ViewSpecificRecipeCommand(RecipeList recipes, String input) {
+ super();
+ this.recipes = recipes;
+ this.input = input;
+ }
+
+ /**
+ * Check if structure of command is valid and execute command by printing the recipe
+ */
+ @Override
+ public void executeCommand() {
+ try {
+ if (recipes.getRecipes().size() == 0) {
+ System.out.println("You haven't added any recipes yet, please add some recipes first!");
+ return;
+ }
+ int recipeIndex = RecipeParser.getRecipeIndex(recipes, input);
+ Ui.printSpecificRecipe(this.recipes, recipeIndex);
+ } catch (EssenOutOfRangeException | EssenFormatException e) {
+ e.getMessage();
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenCommandException.java b/src/main/java/essenmakanan/exception/EssenCommandException.java
new file mode 100644
index 0000000000..a2202d965d
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenCommandException.java
@@ -0,0 +1,14 @@
+package essenmakanan.exception;
+
+/**
+ * Indicates an error caused by invalid command being inputted.
+ */
+public class EssenCommandException extends EssenException {
+
+ /**
+ * Sends out a message that the command type is invalid.
+ */
+ public void handleException() {
+ System.out.println("Invalid command type. Please refer to user guide for full list of commands.");
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenDoesNotExistException.java b/src/main/java/essenmakanan/exception/EssenDoesNotExistException.java
new file mode 100644
index 0000000000..b04d739081
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenDoesNotExistException.java
@@ -0,0 +1,14 @@
+package essenmakanan.exception;
+
+/**
+ * Indicates an error caused by non-existing recipe/ingredient .
+ */
+public class EssenDoesNotExistException extends EssenException {
+
+ /**
+ * Sends out a message that the recipe/ingredient does not exist.
+ */
+ public void handleException() {
+ System.out.println("Recipe/Ingredient does not exist.");
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenEditShortcutException.java b/src/main/java/essenmakanan/exception/EssenEditShortcutException.java
new file mode 100644
index 0000000000..42a5bf9257
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenEditShortcutException.java
@@ -0,0 +1,43 @@
+package essenmakanan.exception;
+
+import essenmakanan.ui.Ui;
+
+/**
+ * Indicates an error during editing a shortcut.
+ */
+public class EssenEditShortcutException extends Exception {
+
+ private String scenario;
+
+ /**
+ * Creates the exception.
+ *
+ * @param scenario The type of scenario.
+ */
+ public EssenEditShortcutException(String scenario) {
+ this.scenario = scenario;
+ }
+
+ /**
+ * Sends out the message based on the scenario.
+ */
+ public void handleException() {
+ Ui.drawDivider();
+
+ switch (scenario) {
+ case "same name":
+ System.out.println("You can't edit shortcut's name with the same name.");
+ break;
+ case "same quantity":
+ System.out.println("You can't edit shortcut's quantity with the same quantity.");
+ break;
+ case "usage":
+ System.out.println("You can't edit a shortcut's property more than once in a single line.");
+ break;
+ default:
+ System.out.println("Invalid scenario for exception!");
+ }
+
+ Ui.drawDivider();
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenException.java b/src/main/java/essenmakanan/exception/EssenException.java
new file mode 100644
index 0000000000..fb109385ea
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenException.java
@@ -0,0 +1,20 @@
+package essenmakanan.exception;
+
+public class EssenException extends Exception {
+
+ public EssenException() {
+ getMessage();
+ }
+ public EssenException(String message) {
+ System.out.println("EssenMakanan Exception! " + message);
+ }
+
+ @Override
+ public String getMessage() {
+ return ("EssenMakanan Exception!");
+ }
+
+ public void handleException() {
+ System.out.println("EssenMakanan Exception!");
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenFileNotFoundException.java b/src/main/java/essenmakanan/exception/EssenFileNotFoundException.java
new file mode 100644
index 0000000000..7d9a5d6df4
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenFileNotFoundException.java
@@ -0,0 +1,60 @@
+package essenmakanan.exception;
+
+import essenmakanan.ui.Ui;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Indicates an error that a file is not found.
+ */
+public class EssenFileNotFoundException extends Exception {
+
+ /**
+ * Creates a directory based on the path.
+ *
+ * @param newDirectory The directory.
+ */
+ private void createDirectory(File newDirectory) {
+ if (!newDirectory.isDirectory() && newDirectory.mkdir()) {
+ System.out.println("Directory successfully created");
+ } else {
+ System.out.println("Directory located");
+ }
+ }
+
+ /**
+ * Creates a text file based on the path.
+ *
+ * @param newDatabase The text file.
+ * @throws IOException If there is an error related to creating the file.
+ */
+ private void createFile(File newDatabase) throws IOException {
+ if (!newDatabase.isFile() && newDatabase.createNewFile()) {
+ System.out.println("Data text file successfully created");
+ } else {
+ System.out.println("Text file located");
+ }
+ }
+
+ /**
+ * Handles missing text file for the application's database.
+ *
+ * @param dataDirectory The name of data directory.
+ * @param dataPath The name of data path.
+ */
+ public void handleFileNotFoundException(String dataDirectory, String dataPath) {
+ System.out.println("Creating database");
+
+ File newDirectory = new File(dataDirectory);
+ File newDatabase = new File(dataPath);
+
+ try {
+ createDirectory(newDirectory);
+ createFile(newDatabase);
+ } catch (IOException exception){
+ Ui.handleIOException(exception);
+ }
+
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenFormatException.java b/src/main/java/essenmakanan/exception/EssenFormatException.java
new file mode 100644
index 0000000000..e0f2b4fab8
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenFormatException.java
@@ -0,0 +1,14 @@
+package essenmakanan.exception;
+
+/**
+ * Indicates an error that a input's format is incorrect.
+ */
+public class EssenFormatException extends EssenException {
+
+ /**
+ * Sends out a message that the format is incorrect.
+ */
+ public void handleException() {
+ System.out.println("Format is incorrect.");
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenInvalidEditException.java b/src/main/java/essenmakanan/exception/EssenInvalidEditException.java
new file mode 100644
index 0000000000..67add393a7
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenInvalidEditException.java
@@ -0,0 +1,10 @@
+package essenmakanan.exception;
+
+public class EssenInvalidEditException extends EssenException {
+ /**
+ * Handles the exception when the edit command is invalid
+ */
+ public void handleException() {
+ System.out.println("Details to edit is invalid.");
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenInvalidEnumException.java b/src/main/java/essenmakanan/exception/EssenInvalidEnumException.java
new file mode 100644
index 0000000000..39ff9660ce
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenInvalidEnumException.java
@@ -0,0 +1,16 @@
+package essenmakanan.exception;
+
+/**
+ * Indicates an error caused by invalid enum.
+ */
+public class EssenInvalidEnumException extends Exception {
+
+ /**
+ * Sends out a message that the data detail cannot be made into an enum.
+ *
+ * @param dataString The data with invalid enum.
+ */
+ public static void handleException(String dataString) {
+ System.out.println("Invalid enum at: " + dataString);
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenInvalidQuantityException.java b/src/main/java/essenmakanan/exception/EssenInvalidQuantityException.java
new file mode 100644
index 0000000000..aeb52d8314
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenInvalidQuantityException.java
@@ -0,0 +1,18 @@
+package essenmakanan.exception;
+
+import essenmakanan.ui.Ui;
+
+/**
+ * Indicates an error caused by invalid quantity.
+ */
+public class EssenInvalidQuantityException extends Exception {
+
+ /**
+ * Sends out a message that the quantity is invalid.
+ */
+ public static void handleException() {
+ Ui.drawDivider();
+ System.out.println("Please put a non-zero positive number as the quantity");
+ Ui.drawDivider();
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenNullInputException.java b/src/main/java/essenmakanan/exception/EssenNullInputException.java
new file mode 100644
index 0000000000..8f5241c21e
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenNullInputException.java
@@ -0,0 +1,15 @@
+package essenmakanan.exception;
+
+public class EssenNullInputException extends EssenException {
+
+ private final String errorMessage = "Something is missing, please check your input"
+ + System.lineSeparator();
+
+ public EssenNullInputException() {
+ System.out.println(getMessage() + errorMessage);
+ }
+
+ public EssenNullInputException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenOutOfRangeException.java b/src/main/java/essenmakanan/exception/EssenOutOfRangeException.java
new file mode 100644
index 0000000000..d3d9495749
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenOutOfRangeException.java
@@ -0,0 +1,7 @@
+package essenmakanan.exception;
+
+public class EssenOutOfRangeException extends EssenException {
+ public void handleException() {
+ System.out.println("Please enter a valid input that exist in our database!");
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenShortcutException.java b/src/main/java/essenmakanan/exception/EssenShortcutException.java
new file mode 100644
index 0000000000..d982421f93
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenShortcutException.java
@@ -0,0 +1,13 @@
+package essenmakanan.exception;
+
+import essenmakanan.ui.Ui;
+
+public class EssenShortcutException extends EssenException {
+
+ public void handleException() {
+ Ui.drawDivider();
+ System.out.println("Shortcut cannot be created for a non-existing ingredient or an ingredient that has"
+ + " a shortcut assigned to it.");
+ Ui.drawDivider();
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenStorageDuplicateException.java b/src/main/java/essenmakanan/exception/EssenStorageDuplicateException.java
new file mode 100644
index 0000000000..dbeba6736e
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenStorageDuplicateException.java
@@ -0,0 +1,8 @@
+package essenmakanan.exception;
+
+public class EssenStorageDuplicateException extends EssenException {
+
+ public void handleException(String data) {
+ System.out.println("Duplicated data founded: " + data + ". Skipping data.");
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenStorageFormatException.java b/src/main/java/essenmakanan/exception/EssenStorageFormatException.java
new file mode 100644
index 0000000000..22552fb53a
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenStorageFormatException.java
@@ -0,0 +1,8 @@
+package essenmakanan.exception;
+
+public class EssenStorageFormatException extends EssenException {
+
+ public void handleException(String dataString) {
+ System.out.println("Invalid data format found in text file. This data will be skipped: '" + dataString + "'");
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenStorageInvalidQuantityException.java b/src/main/java/essenmakanan/exception/EssenStorageInvalidQuantityException.java
new file mode 100644
index 0000000000..b00eb0d6c6
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenStorageInvalidQuantityException.java
@@ -0,0 +1,8 @@
+package essenmakanan.exception;
+
+public class EssenStorageInvalidQuantityException extends EssenException {
+
+ public void handleException(String data) {
+ System.out.println("Invalid quantity detected on data : " + data);
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenStorageInvalidShortcutException.java b/src/main/java/essenmakanan/exception/EssenStorageInvalidShortcutException.java
new file mode 100644
index 0000000000..702b218479
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenStorageInvalidShortcutException.java
@@ -0,0 +1,8 @@
+package essenmakanan.exception;
+
+public class EssenStorageInvalidShortcutException extends EssenException {
+
+ public void handleException(String data) {
+ System.out.println("Invalid shortcut due to no matching ingredient in data: " + data);
+ }
+}
diff --git a/src/main/java/essenmakanan/exception/EssenStorageNumberException.java b/src/main/java/essenmakanan/exception/EssenStorageNumberException.java
new file mode 100644
index 0000000000..22ad08ad3b
--- /dev/null
+++ b/src/main/java/essenmakanan/exception/EssenStorageNumberException.java
@@ -0,0 +1,8 @@
+package essenmakanan.exception;
+
+public class EssenStorageNumberException extends EssenException {
+
+ public static void handleException(String data) {
+ System.out.println("Quantity cannot be converted in: " + data);
+ }
+}
diff --git a/src/main/java/essenmakanan/ingredient/Ingredient.java b/src/main/java/essenmakanan/ingredient/Ingredient.java
new file mode 100644
index 0000000000..ae1afa3556
--- /dev/null
+++ b/src/main/java/essenmakanan/ingredient/Ingredient.java
@@ -0,0 +1,68 @@
+package essenmakanan.ingredient;
+
+public class Ingredient {
+
+ private String name;
+ private Double quantity;
+ private IngredientUnit unit;
+
+ public Ingredient(String name, Double qty, IngredientUnit unit) {
+ this.name = name;
+ this.quantity = qty;
+ this.unit = unit;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Double getQuantity() {
+ return this.quantity;
+ }
+
+ public void setQuantity(Double quantity) {
+ this.quantity = quantity;
+ }
+
+ public IngredientUnit getUnit() {
+ return this.unit;
+ }
+
+ public void setUnit(IngredientUnit unit) {
+ this.unit = unit;
+ }
+
+ /**
+ * To print the ingredient in the format of "name: quantity unit"
+ *
+ * @return formatted string of the ingredient
+ */
+ @Override
+ public String toString() {
+ if (String.valueOf(this.quantity).endsWith(".0")) {
+ // if quantity is a whole number, remove the decimal point
+ String qtyString = String.valueOf(this.quantity);
+ qtyString = qtyString.substring(0, qtyString.length() - 2);
+ return this.name + ": " + Integer.parseInt(qtyString) + this.unit.getValue();
+ }
+ return this.name + ": " + this.quantity + this.unit.getValue();
+ }
+
+ /**
+ * To check if 2 ingredients are the same
+ *
+ * @param ingredient the other ingredient to check against
+ * @return a boolean of whether the ingredients have the same name, quantity and unit
+ */
+ public boolean equals(Ingredient ingredient) {
+ boolean nameEqual = this.getName().equals(ingredient.getName());
+ boolean quantityEqual = this.getQuantity().equals(ingredient.getQuantity());
+ boolean unitEqual = this.getUnit().equals(ingredient.getUnit());
+
+ return (nameEqual && quantityEqual && unitEqual);
+ }
+}
diff --git a/src/main/java/essenmakanan/ingredient/IngredientList.java b/src/main/java/essenmakanan/ingredient/IngredientList.java
new file mode 100644
index 0000000000..f41c67cb9e
--- /dev/null
+++ b/src/main/java/essenmakanan/ingredient/IngredientList.java
@@ -0,0 +1,251 @@
+package essenmakanan.ingredient;
+
+import java.util.ArrayList;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.parser.IngredientParser;
+import essenmakanan.ui.Ui;
+
+public class IngredientList {
+ private ArrayList ingredients;
+
+ public IngredientList() {
+ ingredients = new ArrayList<>();
+ }
+
+ public IngredientList(ArrayList ingredients) {
+ this.ingredients = ingredients;
+ }
+
+ public IngredientList(String[] inputIngredients) {
+ ingredients = new ArrayList<>();
+ for (String ingredientString : inputIngredients) {
+ assert !ingredientString.isEmpty() : "Ingredient must be valid and present";
+ try {
+ Ingredient ingredient = IngredientParser.parseIngredient(ingredientString);
+ ingredients.add(ingredient);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+ }
+ }
+
+ public ArrayList getIngredients() {
+ return ingredients;
+ }
+
+ public Ingredient getIngredient(int index) {
+ return ingredients.get(index);
+ }
+
+ /**
+ * Get the ingredient object from the ingredient name
+ *
+ * @param name of the ingredient
+ * @return the ingredient object
+ */
+ public Ingredient getIngredient(String name) {
+ for (Ingredient ingredient : ingredients) {
+ if (ingredient.getName().equals(name)) {
+ return ingredient;
+ }
+ }
+ return null;
+ }
+
+ public int getSize() {
+ return this.ingredients.size();
+ }
+
+ public boolean isEmpty() {
+ return ingredients.isEmpty();
+ }
+
+ /**
+ * To check if an ingredient name exists in the ingredient list
+ * @param ingredientName is a string
+ * @return a boolean of whether the ingredient name is valid
+ */
+ public boolean exist(String ingredientName) {
+ for (Ingredient ingredient : ingredients) {
+ if (ingredient.getName().equals(ingredientName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * To check if an id is valid
+ * @param id of the ingredient
+ * @return a boolean of whether the id exists
+ */
+ public boolean exist(int id) {
+ if (id >= 0 && id < ingredients.size()) {
+ return true;
+ }
+ return false;
+ }
+
+ public int getIndex(Ingredient ingredient) {
+ return ingredients.indexOf(ingredient);
+ }
+
+ /**
+ * Get the index of the ingredient from the ingredient name
+ *
+ * @param ingredientName is a string
+ * @return the index of the ingredient
+ */
+ public int getIndex(String ingredientName) {
+ int i = 0;
+ for (Ingredient ingredient : ingredients) {
+ if (ingredient.getName().equals(ingredientName)) {
+ return i;
+ }
+ i++;
+ }
+ return -1;
+ }
+
+ /**
+ * To add an ingredient object into the ingredient list if the ingredient does not already exist
+ *
+ * @param ingredient to be added
+ */
+ public void addIngredient(Ingredient ingredient) {
+ assert ingredient.getName() != null : "Ingredient name should not be null";
+
+ ingredients.add(ingredient);
+ }
+
+ /**
+ * To update the quantity of an ingredient in the ingredient list. If ingredient quantity is negative,
+ * subtract from existing quantity, else, add to the existing quantity
+ *
+ * @param ingredientToUpdate is an ingredient object
+ * @throws EssenFormatException if the unit of the ingredient to update does not match the existing ingredient
+ */
+ public void updateIngredient(Ingredient ingredientToUpdate) throws EssenFormatException {
+ Ingredient existingIngredient = this.getIngredient(ingredientToUpdate.getName());
+ // check if unit matches
+ if (!existingIngredient.getUnit().equals(ingredientToUpdate.getUnit())) {
+ System.out.println("Existing ingredient unit is " + existingIngredient.getUnit().getValue()
+ + " but new ingredient unit is " + ingredientToUpdate.getUnit().getValue());
+ throw new EssenFormatException();
+ }
+
+ double oldQuantity = existingIngredient.getQuantity();
+ double deltaQuantity = ingredientToUpdate.getQuantity();
+ double newQuantity = oldQuantity + deltaQuantity;
+
+ if (newQuantity < 0) {
+ // if new quantity is negative, throw exception
+ System.out.println("You do not have enough ingredients to use.");
+ return;
+ }
+
+ existingIngredient.setQuantity(newQuantity);
+ Ui.printUpdateIngredientsSuccess(existingIngredient.getName(),
+ oldQuantity,
+ newQuantity);
+
+ }
+
+ /**
+ * To edit the ingredient in the ingredient list. Details of what to edit can be found in editDetails
+ *
+ * @param existingIngredient is an ingredient object
+ * @param editDetails is a string array of the details to edit
+ * @throws EssenFormatException if the edit details are not in the correct format
+ */
+ public static void editIngredient(Ingredient existingIngredient, String[] editDetails) throws EssenFormatException {
+ for (int i = 0; i < editDetails.length; i++) {
+ if (editDetails[i].isEmpty()) {
+ continue;
+ }
+
+ // get flag of input to know which field to edit
+ String flag = editDetails[i].substring(0, 2);
+ String content = editDetails[i].substring(2);
+
+ switch (flag) {
+ case "n/":
+ Ui.printEditIngredientNameSuccess(existingIngredient.getName(), content);
+ existingIngredient.setName(content);
+ break;
+ case "q/":
+ Double newQuantity = null;
+ try {
+ newQuantity = Double.parseDouble(content);
+ } catch (NumberFormatException e) {
+ System.out.println("Quantity must be a double!");
+ throw new EssenFormatException();
+ }
+ Ui.printEditIngredientQuantitySuccess(existingIngredient.getQuantity(), newQuantity);
+ existingIngredient.setQuantity(newQuantity);
+ break;
+ case "u/":
+ try {
+ IngredientUnit newUnit = IngredientParser.mapIngredientUnit(content);
+ Ui.printEditIngredientUnitSuccess(existingIngredient.getUnit(), newUnit);
+ existingIngredient.setUnit(newUnit);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+ break;
+ default:
+ System.out.println("See user guide for correct edit format and example: " +
+ "edit i/INGREDIENT_NAME [n/NEW_NAME] [q/NEW_QUANTITY] [u/NEW_UNIT]");
+ throw new EssenFormatException();
+ }
+ }
+
+ }
+
+ /**
+ * Delete ingredient by index
+ *
+ * @param index
+ */
+ public void deleteIngredient(int index) {
+ Ui.printDeleteIngredientsSuccess(ingredients.get(index).getName());
+ ingredients.remove(index);
+ }
+
+ /**
+ * Print all ingredients in the ingredient list
+ */
+ public void listIngredients() {
+ Ui.drawDivider();
+ int count = 1;
+
+ for (Ingredient ingredient : ingredients) {
+ assert ingredients.get(count - 1).getName().equals(ingredient.getName())
+ : "Name is not matching with the current index";
+
+ System.out.println(count + ". " + ingredient);
+ count++;
+ }
+ }
+
+ /**
+ * To check if ingredient lists are the same
+ *
+ * @param list is an IngredientList ingredients
+ * @return boolean value of whether the ingredient lists are the same
+ */
+ public boolean equals(IngredientList list) {
+ if (this.getSize() != list.getSize()) {
+ return false;
+ }
+
+ for (int i=0; i 0) || quantity > 0;
+ }
+
+ /**
+ * Validates the input of ingredient ID or ingredient name.
+ *
+ * @param ingredients is the ingredient inventory
+ * @param input is the input of ingredient ID or ingredient name
+ * @return the index of the ingredient
+ * @throws EssenOutOfRangeException when the ingredient name or ingredient ID does not exist
+ */
+ public static int getIngredientIndex(IngredientList ingredients, String input)
+ throws EssenOutOfRangeException {
+ int index;
+ input = input.replace("i/", "");
+
+ if (input.matches("\\d+")) { //if input only contains numbers
+ index = Integer.parseInt(input) - 1;
+ } else {
+ index = ingredients.getIndex(input);
+ }
+
+ if (!ingredients.exist(index)) {
+ System.out.println("Your ingredient name or id does not exist or it is invalid.");
+ throw new EssenOutOfRangeException();
+ }
+
+ return index;
+ }
+
+ /**
+ * Get the difference between the needed ingredients and the available ingredients
+ *
+ * @param ingredientNeeded is the quantity needed
+ * @param ingredientAvailable is the quantity available
+ * @return quantity needed
+ */
+ public static Double getInsufficientQuantity(Ingredient ingredientNeeded, Ingredient ingredientAvailable) {
+ final Double zeroQuantity = 0.0;
+
+ Double quantityNeeded = ingredientNeeded.getQuantity();
+ Double quantityAvailable = ingredientAvailable.getQuantity();
+
+ if (quantityNeeded > quantityAvailable) {
+ return (quantityNeeded - quantityAvailable);
+ }
+
+ return zeroQuantity;
+ }
+
+ /**
+ * Parses the ingredient input. Name, quantity and unit should be valid
+ *
+ * @param inputDetail The given input.
+ * @return A valid ingredient.
+ * @throws EssenFormatException If the input is invalid.
+ */
+ public static Ingredient parseIngredient(String inputDetail)
+ throws EssenFormatException {
+
+ IngredientUnit ingredientUnit;
+
+ if (!isValidIngredient(inputDetail)) {
+ Ui.printValidIngredientExample();
+ throw new EssenFormatException();
+ }
+
+ inputDetail = inputDetail.replace("i/", "");
+
+ String[] ingredientDetails = inputDetail.split(",");
+
+ assert (ingredientDetails.length == 3) : "Ingredient details should have 3 parts";
+
+ String ingredientName = ingredientDetails[0].trim();
+ if (ingredientName.isEmpty()) {
+ System.out.println("Ingredient name should not be empty!");
+ throw new EssenFormatException();
+ }
+
+ if (ingredientDetails[1].isBlank()) {
+ // check if quantity is a null
+ System.out.println("Ingredient quantity should not be empty!");
+ throw new EssenFormatException();
+ }
+
+ Double ingredientQuantity = Double.parseDouble(ingredientDetails[1].trim());
+
+ String ingredientUnitString = ingredientDetails[2].trim().toLowerCase();
+ ingredientUnit = mapIngredientUnit(ingredientUnitString);
+
+ Ingredient newIngredient = new Ingredient(ingredientName, ingredientQuantity, ingredientUnit);
+
+ return newIngredient;
+ }
+
+ /**
+ * Checks if the ingredient is valid. True of ingredient has valid structure.
+ *
+ * @param inputDetail The given input.
+ * @return Confirmation if the ingredient is valid.
+ */
+ public static boolean isValidIngredient(String inputDetail) {
+ inputDetail = inputDetail.replace("i/", "");
+
+ String[] ingredientDetails = inputDetail.split(",");
+
+ if (ingredientDetails.length != 3) {
+ return false;
+ }
+
+ String ingredientUnitString = ingredientDetails[2].trim().toLowerCase();
+ try {
+ mapIngredientUnit(ingredientUnitString);
+ } catch (EssenFormatException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Maps the ingredient unit to the enum. For users to use the app more easily
+ *
+ * @param ingredientUnitString The given ingredient unit in natural language
+ * @return The mapped ingredient unit based on our enum
+ * @throws EssenFormatException If the ingredient unit is invalid or missing
+ */
+ public static IngredientUnit mapIngredientUnit(String ingredientUnitString) throws EssenFormatException {
+ IngredientUnit ingredientUnit;
+ // return("Valid ingredient units are: g, kg, ml, l, tsp, tbsp, cup, pcs");
+ switch(ingredientUnitString) {
+ case "g":
+ ingredientUnit = IngredientUnit.GRAM;
+ break;
+ case "kg":
+ ingredientUnit = IngredientUnit.KILOGRAM;
+ break;
+ case "ml":
+ ingredientUnit = IngredientUnit.MILLILITER;
+ break;
+ case "l":
+ ingredientUnit = IngredientUnit.LITER;
+ break;
+ case "tsp":
+ ingredientUnit = IngredientUnit.TEASPOON;
+ break;
+ case "tbsp":
+ ingredientUnit = IngredientUnit.TABLESPOON;
+ break;
+ case "cup":
+ ingredientUnit = IngredientUnit.CUP;
+ break;
+ case "pc":
+ ingredientUnit = IngredientUnit.PIECE;
+ break;
+ default:
+ System.out.println(Ui.validIngredientUnits());
+ throw new EssenFormatException();
+ }
+
+ return ingredientUnit;
+ }
+
+ /**
+ * Converts an ingredient into string form.
+ *
+ * @param ingredient The ingredient.
+ * @return An ingredient that has been converted into string.
+ */
+ public static String convertToString(Ingredient ingredient) {
+ return ingredient.getName() + " | " + ingredient.getQuantity() + " | " + ingredient.getUnit();
+ }
+}
diff --git a/src/main/java/essenmakanan/parser/Parser.java b/src/main/java/essenmakanan/parser/Parser.java
new file mode 100644
index 0000000000..4accc68928
--- /dev/null
+++ b/src/main/java/essenmakanan/parser/Parser.java
@@ -0,0 +1,159 @@
+package essenmakanan.parser;
+
+import essenmakanan.command.AddIngredientCommand;
+import essenmakanan.command.AddRecipeCommand;
+import essenmakanan.command.AddShortcutCommand;
+import essenmakanan.command.CheckRecipeCommand;
+import essenmakanan.command.Command;
+import essenmakanan.command.DeleteIngredientCommand;
+import essenmakanan.command.DeleteRecipeCommand;
+import essenmakanan.command.DeleteShortcutCommand;
+import essenmakanan.command.DuplicateRecipeCommand;
+import essenmakanan.command.EditIngredientCommand;
+import essenmakanan.command.EditRecipeCommand;
+import essenmakanan.command.EditShortcutCommand;
+import essenmakanan.command.ExecuteRecipeCommand;
+import essenmakanan.command.ExitCommand;
+import essenmakanan.command.FilterRecipesCommand;
+import essenmakanan.command.HelpCommand;
+import essenmakanan.command.PlanRecipesCommand;
+import essenmakanan.command.UseIngredientCommand;
+import essenmakanan.command.UseShortcutCommand;
+import essenmakanan.command.ViewAllAvailableRecipesCommand;
+import essenmakanan.command.ViewIngredientsCommand;
+import essenmakanan.command.ViewRecipesCommand;
+import essenmakanan.command.ViewShortcutsCommand;
+import essenmakanan.command.ViewSpecificIngredientCommand;
+import essenmakanan.command.ViewSpecificRecipeCommand;
+import essenmakanan.exception.EssenCommandException;
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.shortcut.ShortcutList;
+import essenmakanan.recipe.RecipeList;
+
+/**
+ * Represents parser to parse user's input.
+ */
+public class Parser {
+
+ /**
+ * Parses a command based on the user's input.
+ *
+ * @param input The user input.
+ * @param recipes The recipe list.
+ * @param ingredients The ingredient list.
+ * @param shortcuts The shortcut list.
+ * @return A command.
+ * @throws EssenCommandException If the command type is invalid.
+ * @throws EssenFormatException If the format is incorrect.
+ * @throws EssenOutOfRangeException If the application access is out of range.
+ */
+ public Command parseCommand(String input, RecipeList recipes, IngredientList ingredients, ShortcutList shortcuts)
+ throws EssenCommandException, EssenFormatException, EssenOutOfRangeException {
+ Command command;
+
+ String[] parsedInput = input.split(" ", 2);
+ String commandType = parsedInput[0];
+ String inputDetail = parsedInput.length == 1 ? "" : parsedInput[1].trim();
+
+ switch (commandType) {
+ case "check":
+ command = new CheckRecipeCommand(inputDetail, recipes, ingredients);
+ break;
+ case "add":
+ if (inputDetail.startsWith("r/")) {
+ // check that all fields needed for recipe is present
+ if (!(inputDetail.contains("r/") && inputDetail.contains("s/") && inputDetail.contains("i/"))) {
+ System.out.println("Recipe have to include title, steps and ingredients!");
+ throw new EssenFormatException();
+ }
+ command = new AddRecipeCommand(inputDetail, recipes);
+ } else if (inputDetail.startsWith("i/")) {
+ command = new AddIngredientCommand(inputDetail, ingredients);
+ } else if (inputDetail.startsWith("sc/")) {
+ command = new AddShortcutCommand(shortcuts, ingredients, inputDetail);
+ } else {
+ throw new EssenFormatException();
+ }
+ break;
+ case "delete":
+ if (inputDetail.startsWith("r/")) {
+ command = new DeleteRecipeCommand(recipes, inputDetail);
+ } else if (inputDetail.startsWith("i/")) {
+ command = new DeleteIngredientCommand(ingredients, inputDetail);
+ } else if (inputDetail.startsWith("sc/")) {
+ command = new DeleteShortcutCommand(shortcuts, inputDetail);
+ } else {
+ throw new EssenFormatException();
+ }
+ break;
+ case "view":
+ if (inputDetail.equals("r")) {
+ command = new ViewRecipesCommand(recipes);
+ } else if (inputDetail.equals("i")) {
+ command = new ViewIngredientsCommand(ingredients);
+ } else if (inputDetail.equals("sc")) {
+ command = new ViewShortcutsCommand(shortcuts);
+ } else if (inputDetail.startsWith("r/")) {
+ assert (!inputDetail.equals("")) : "To view a recipe, make sure title is not empty";
+ command = new ViewSpecificRecipeCommand(recipes, inputDetail);
+ } else if (inputDetail.startsWith("i/")) {
+ command = new ViewSpecificIngredientCommand(ingredients, inputDetail);
+ } else if (inputDetail.startsWith("ar")) {
+ command = new ViewAllAvailableRecipesCommand(recipes, ingredients, inputDetail);
+ } else {
+ throw new EssenFormatException();
+ }
+ break;
+ case "filter":
+ if (inputDetail.startsWith("recipe")) {
+ inputDetail = RecipeParser.parseFilterRecipeInput(inputDetail);
+ command = new FilterRecipesCommand(inputDetail, recipes);
+ } else {
+ throw new EssenFormatException();
+ }
+ break;
+ case "edit":
+ if (inputDetail.startsWith("i/")) {
+ command = new EditIngredientCommand(inputDetail, ingredients);
+ } else if (inputDetail.startsWith("r/")) {
+ command = new EditRecipeCommand(inputDetail, recipes);
+ } else if (inputDetail.startsWith("sc/")) {
+ command = new EditShortcutCommand(shortcuts, ingredients, inputDetail);
+ } else {
+ throw new EssenFormatException();
+ }
+ break;
+ case "duplicate":
+ command = new DuplicateRecipeCommand(recipes, inputDetail);
+ break;
+ case "plan":
+ command = new PlanRecipesCommand(ingredients, recipes, inputDetail);
+ break;
+ case "execute":
+ command = new ExecuteRecipeCommand(ingredients, recipes, inputDetail);
+ break;
+ case "sc":
+ command = new UseShortcutCommand(shortcuts, ingredients, inputDetail);
+ break;
+ case "use":
+ if (inputDetail.startsWith("i/")) {
+ command = new UseIngredientCommand(ingredients, inputDetail);
+ } else {
+ throw new EssenCommandException();
+ }
+ break;
+ case "help":
+ command = new HelpCommand();
+ break;
+ case "exit":
+ command = new ExitCommand();
+ break;
+ default:
+ throw new EssenCommandException();
+ }
+
+ return command;
+ }
+}
diff --git a/src/main/java/essenmakanan/parser/RecipeParser.java b/src/main/java/essenmakanan/parser/RecipeParser.java
new file mode 100644
index 0000000000..e675aa37e3
--- /dev/null
+++ b/src/main/java/essenmakanan/parser/RecipeParser.java
@@ -0,0 +1,287 @@
+package essenmakanan.parser;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.exception.EssenStorageFormatException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientUnit;
+import essenmakanan.recipe.RecipeIngredientList;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.recipe.RecipeStepList;
+import essenmakanan.recipe.Step;
+import essenmakanan.recipe.Tag;
+
+import java.util.ArrayList;
+import java.util.StringJoiner;
+
+public class RecipeParser {
+
+ /**
+ * Validate the recipe id or the recipe title.
+ *
+ * @param recipes is a RecipeList of all recipes the user has
+ * @param input is the recipe title or recipe id
+ * @return the index of the recipe
+ * @throws EssenOutOfRangeException when the recipe id or recipe title does not exist
+ * @throws EssenFormatException when there is no input of recipe id or recipe title
+ */
+ public static int getRecipeIndex(RecipeList recipes, String input)
+ throws EssenOutOfRangeException, EssenFormatException {
+ if (input.isEmpty()) {
+ throw new EssenFormatException();
+ }
+
+ int index;
+ input = input.replace("r/", "");
+
+ if (input.matches("\\d+")) { //if input only contains numbers
+ index = Integer.parseInt(input) - 1;
+ } else {
+ index = recipes.getIndexOfRecipe(input);
+ }
+
+ if (!recipes.recipeExist(index) || index < 0) {
+ System.out.println("Your recipe name or id does not exist or it is invalid.");
+ throw new EssenOutOfRangeException();
+ }
+
+ return index;
+ }
+
+ /**
+ * To remove the unused item at position zero after splitting the string.
+ * For example, ["", "fish", "rice"] as recipeInputString, will return ["fish", "rice"] after passing through method
+ *
+ * @param recipeInputString is the userInput in the format "r/RECIPE_ID [r/...]"
+ * @return recipeInputList which is a String[] that excludes the first unused variable
+ */
+ public static String[] getPlannedRecipesString(String recipeInputString) {
+ String[] recipeInputListTemp = recipeInputString.split("r/");
+ String[] recipeInputList = new String[recipeInputListTemp.length - 1];
+ System.arraycopy(recipeInputListTemp, 1, recipeInputList, 0, recipeInputList.length); //remove first space item
+ return recipeInputList;
+ }
+
+ /**
+ * Check for errors within the plan command input by user
+ * If no error, returns an integer list
+ *
+ * @param userInput is the input by user
+ * @throws EssenFormatException when there is an error in user input
+ */
+ public static void parsePlanCommandInput(String userInput) throws EssenFormatException {
+ // to check if compulsory fields are filled in
+ if (userInput.split(" ").length < 2 || !userInput.contains("r/")) {
+ System.out.println("Note the format `plan NUMBER_OF_RECIPES r/RECIPE_ID [r/...]`\n" +
+ "NUMBER_OF_RECIPES must be larger than or equal to 1 and it is a compulsory field\n" +
+ "r/RECIPE_ID is a compulsory field");
+ throw new EssenFormatException();
+ }
+
+ String[] input = userInput.split(" ", 2);
+ String numberOfRecipesString = input[0];
+ String recipeInputString = input[1];
+
+ try {
+ // to check if at least 1 recipe is added
+ int numberOfRecipes = Integer.parseInt(numberOfRecipesString);
+ if (numberOfRecipes <= 0) {
+ System.out.println("NUMBER_OF_RECIPES must be larger than or equal to 1");
+ throw new EssenFormatException();
+ }
+
+ String[] recipeInputList = getPlannedRecipesString(recipeInputString);
+ for (int i = 0; i < recipeInputList.length; i++) {
+ recipeInputList[i] = recipeInputList[i].trim();
+ }
+
+ // to check if NUMBER_OF_RECIPES corresponds to the total number of recipes input
+ int numberOfRecipesInInput = recipeInputList.length;
+
+ if (numberOfRecipesInInput != numberOfRecipes) {
+ System.out.println("Number of recipes in your input marked " +
+ "by 'r/' does not correspond to your NUMBER_OF_RECIPES");
+ throw new EssenFormatException();
+ }
+
+ // to check if each RECIPE_INDEX is an integer
+ for (String recipe : recipeInputList) {
+ Integer.parseInt(recipe); //to check if can be converted to integer, else, will throw an exception
+ }
+ } catch (NumberFormatException e) {
+ System.out.println("NUMBER_OF_RECIPES and RECIPE_INDEX should be integers!");
+ throw new EssenFormatException();
+ }
+ }
+
+ /**
+ * Parse a recipe title.
+ *
+ * @param toAdd The given input.
+ * @return A title for the recipe.
+ */
+ public static String parseRecipeTitle(String toAdd) {
+ return toAdd.replace("r/", "");
+ }
+
+ /**
+ * Converts a step into string form.
+ *
+ * @param step A step.
+ * @return A step that has been converted into string.
+ */
+ private static String convertStep(Step step) {
+ return step.getDescription() + " | " + step.getTag() + " | " + step.getEstimatedDuration();
+ }
+
+ /**
+ * Joins all the steps into string form.
+ *
+ * @param steps The step list.
+ * @return Steps that has been converted into string.
+ */
+ public static String convertSteps(ArrayList steps) {
+ StringJoiner joiner = new StringJoiner(" , ");
+
+ for (Step step: steps) {
+ joiner.add(convertStep(step));
+ }
+
+ return joiner.toString();
+ }
+
+ /**
+ * Joins all the ingredients into string form.
+ *
+ * @param ingredients The ingredient list.
+ * @return Ingredients that has been converted into string.
+ */
+ public static String convertIngredient(ArrayList ingredients) {
+ StringJoiner joiner = new StringJoiner(" , ");
+
+ for (Ingredient ingredient: ingredients) {
+ joiner.add(IngredientParser.convertToString(ingredient));
+ }
+
+ return joiner.toString();
+ }
+
+ /**
+ * Parse steps from a string.
+ *
+ * @param stepsString The string containing steps.
+ * @return The list of recipe steps.
+ * @throws EssenStorageFormatException If the storage format is invalid.
+ * @throws IllegalArgumentException If the data cannot be converted into enum.
+ */
+ public static RecipeStepList parseDataSteps(String stepsString) throws EssenStorageFormatException
+ , IllegalArgumentException {
+ String[] parsedSteps = stepsString.split(" , ");
+ ArrayList stepList = new ArrayList<>();
+
+ for (String step : parsedSteps) {
+ String[] parsedStep = step.split(" \\| ");
+
+ if (parsedStep.length != 3) {
+ throw new EssenStorageFormatException();
+ }
+
+ String stepDescription = parsedStep[0].trim();
+ Tag stepTag = Tag.valueOf(parsedStep[1].trim());
+ int stepDuration = Integer.parseInt(parsedStep[2].trim());
+
+ if (stepDuration < 0) {
+ throw new EssenStorageFormatException();
+ }
+
+ stepList.add(new Step(stepDescription, stepTag, stepDuration));
+ }
+
+ return new RecipeStepList(stepList);
+ }
+
+ /**
+ * Parse ingredient list from string.
+ *
+ * @param ingredientsString The string containing ingredients.
+ * @return The list of ingredients of a recipe.
+ * @throws EssenStorageFormatException If the storage format is invalid.
+ * @throws NumberFormatException If the data cannot be converted into enum.
+ */
+ public static RecipeIngredientList parseDataRecipeIngredients(String ingredientsString)
+ throws EssenStorageFormatException, NumberFormatException {
+ String[] parsedIngredients = ingredientsString.split(" , ");
+ ArrayList ingredientList = new ArrayList<>();
+
+ for (String ingredientData : parsedIngredients) {
+ String[] parsedIngredient = ingredientData.split(" \\| ");
+
+ if (parsedIngredient.length != 3 || parsedIngredient[1].isBlank()) {
+ throw new EssenStorageFormatException();
+ }
+
+ String ingredientName = parsedIngredient[0].trim();
+
+ for (Ingredient ingredient : ingredientList) {
+ if (ingredient.getName().equals(ingredientName)) {
+ throw new EssenStorageFormatException();
+ }
+ }
+
+ double ingredientQuantity = Double.parseDouble(parsedIngredient[1].trim());
+
+ if (!IngredientParser.checkForValidQuantity(ingredientQuantity)) {
+ throw new NumberFormatException();
+ }
+
+ IngredientUnit ingredientUnit = IngredientUnit.valueOf(parsedIngredient[2].trim());
+ ingredientList.add(new Ingredient(ingredientName, ingredientQuantity, ingredientUnit));
+ }
+
+ return new RecipeIngredientList(ingredientList);
+ }
+
+
+ /**
+ * To check if filter recipes contains "i/"
+ *
+ * @param input the filter recipe command input
+ * @return a String of the input stripped of whitespaces
+ * @throws EssenFormatException when "i/" is not found in input
+ */
+ public static String parseFilterRecipeInput(String input) throws EssenFormatException {
+ input = input.replace("recipe ", "");
+ if (!input.contains("i/")) {
+ throw new EssenFormatException();
+ }
+ return input.strip();
+ }
+
+ /**
+ * Parse duration of a step from user input to minutes
+ *
+ * @param time duration of a step, in min/h
+ * @return int duration in minutes
+ * @throws EssenFormatException if unit of duration is not specified
+ */
+ public static int parseStepsDuration(String time) throws EssenFormatException{
+
+ if (time.contains("minutes") || time.contains("mins") || time.contains("min")) {
+ time = time.replace("minutes", "")
+ .replace("mins", "")
+ .replace("min", "")
+ .trim();
+ return Integer.parseInt(time);
+ } else if (time.contains("hours") || time.contains("h") || time.contains("hour")) {
+ time = time.replace("hours", "")
+ .replace("h", "")
+ .replace("hour", "")
+ .trim();
+ return (int) (Double.parseDouble(time)*60);
+ } else {
+ System.out.println("Please specify unit of duration (min/h)");
+ throw new EssenFormatException();
+ }
+
+ }
+}
diff --git a/src/main/java/essenmakanan/parser/ShortcutParser.java b/src/main/java/essenmakanan/parser/ShortcutParser.java
new file mode 100644
index 0000000000..5a271d9f02
--- /dev/null
+++ b/src/main/java/essenmakanan/parser/ShortcutParser.java
@@ -0,0 +1,182 @@
+package essenmakanan.parser;
+
+import essenmakanan.exception.EssenEditShortcutException;
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenInvalidQuantityException;
+import essenmakanan.exception.EssenShortcutException;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.shortcut.Shortcut;
+import essenmakanan.shortcut.ShortcutList;
+import essenmakanan.ui.Ui;
+
+/**
+ * Represents a parser related to shortcuts
+ */
+public class ShortcutParser {
+
+ /**
+ * Parse a shortcut based on the input and the ingredient list.
+ *
+ * @param ingredients The ingredient list.
+ * @param input The given input.
+ * @return A shortcut based on the input.
+ * @throws EssenFormatException If the format is incorrect.
+ * @throws EssenShortcutException If the shortcut refers to a non-existing ingredient.
+ * @throws NumberFormatException If the quantity is invalid.
+ */
+ public static Shortcut parseShortcut(IngredientList ingredients, String input) throws EssenFormatException
+ , EssenShortcutException, NumberFormatException {
+ input = input.replace("sc/", "");
+ String[] shortcutDetails = input.split(",");
+
+ if (shortcutDetails.length != 2 || shortcutDetails[0].isBlank()) {
+ throw new EssenFormatException();
+ }
+
+ String ingredientName = shortcutDetails[0].strip();
+
+ if (!ingredients.exist(ingredientName)) {
+ throw new EssenShortcutException();
+ }
+
+ double quantity = Double.parseDouble(shortcutDetails[1].strip());
+
+ if (!IngredientParser.checkForValidQuantity(quantity)) {
+ throw new NumberFormatException();
+ }
+
+ return new Shortcut(ingredientName, quantity);
+ }
+
+ /**
+ * Gets shortcut index based on the input.
+ *
+ * @param shortcuts The shortcut list.
+ * @param input The given input.
+ * @return The index of the shortcut in the list.
+ */
+ public static int getShortcutIndex(ShortcutList shortcuts, String input) {
+ int index;
+
+ if (input.matches("\\d+")) { //if input only contains numbers
+ index = Integer.parseInt(input) - 1;
+ } else {
+ index = shortcuts.getIndex(input);
+ }
+
+ return index;
+ }
+
+ /**
+ * Changes the shortcut's name into a new name based on the ingredient list.
+ *
+ * @param shortcuts The shortcut list.
+ * @param shortcut The shortcut that is going to be edited.
+ * @param ingredients The ingredient list.
+ * @param editDetail The new name.
+ * @param hasEditName The status if the user has edited the name once in one line.
+ */
+ private static void editShortcutName(ShortcutList shortcuts, Shortcut shortcut, IngredientList ingredients
+ , String editDetail , boolean hasEditName) {
+ String newName = editDetail.substring(2).strip();
+ String oldName = shortcut.getIngredientName();
+
+ try {
+ if (hasEditName) {
+ throw new EssenEditShortcutException("usage");
+ }
+
+ if (newName.equals(oldName)) {
+ throw new EssenEditShortcutException("same name");
+ }
+
+ if (!ingredients.exist(newName) || shortcuts.exist(newName)) {
+ throw new EssenShortcutException();
+ }
+
+ Ui.printEditShortcutName(shortcut.getIngredientName(), newName);
+ shortcut.setIngredientName(newName);
+ } catch (EssenShortcutException exception) {
+ exception.handleException();
+ } catch (EssenEditShortcutException exception) {
+ exception.handleException();
+ }
+ }
+
+ /**
+ * Changes the shortcut's quantity into a new quantity.
+ *
+ * @param shortcut The shortcut that is going to be edited.
+ * @param editDetail The new quantity.
+ * @param hasEditQuantity The status if the user has edited the quantity once in one line.
+ */
+ private static void editShortcutQuantity(Shortcut shortcut, String editDetail, boolean hasEditQuantity) {
+ try {
+ double newQuantity = Double.parseDouble(editDetail.substring(2).strip());
+ if (hasEditQuantity) {
+ throw new EssenEditShortcutException("usage");
+ }
+
+ if (!IngredientParser.checkForValidQuantity(newQuantity)) {
+ throw new NumberFormatException();
+ }
+
+ if (newQuantity == shortcut.getQuantity()) {
+ throw new EssenEditShortcutException("same quantity");
+ }
+
+ Ui.printEditShortcutQuantity(shortcut.getQuantity(), newQuantity);
+ shortcut.setQuantity(newQuantity);
+ } catch (NumberFormatException exception) {
+ EssenInvalidQuantityException.handleException();
+ } catch (EssenEditShortcutException exception) {
+ exception.handleException();
+ }
+ }
+
+ /**
+ * Edits the shortcut's properties based on the flags.
+ *
+ * @param shortcuts The shortcut list.
+ * @param shortcut The shortcut that is going to be edited.
+ * @param ingredients The ingredient list.
+ * @param editDetails A string filled with changes to be made.
+ * @throws EssenFormatException If the format is incorrect.
+ */
+ public static void editShortcut(ShortcutList shortcuts, Shortcut shortcut, IngredientList ingredients
+ , String[] editDetails) throws EssenFormatException {
+ boolean hasEditName = false;
+ boolean hasEditQuantity = false;
+
+ for (int i = 1; i < editDetails.length; i++) {
+
+ if (editDetails[i].isBlank()) {
+ continue;
+ }
+
+ String flag = editDetails[i].substring(0, 2).strip();
+ switch (flag) {
+ case "n/":
+ editShortcutName(shortcuts, shortcut, ingredients, editDetails[i], hasEditName);
+ hasEditName = true;
+ break;
+ case "q/":
+ editShortcutQuantity(shortcut, editDetails[i], hasEditQuantity);
+ hasEditQuantity = true;
+ break;
+ default:
+ throw new EssenFormatException();
+ }
+ }
+ }
+
+ /**
+ * Converts a shortcut into string form.
+ *
+ * @param shortcut A shortcut
+ * @return A shortcut that has been converted into a string.
+ */
+ public static String convertToString(Shortcut shortcut) {
+ return shortcut.getIngredientName() + " | " + shortcut.getQuantity();
+ }
+}
diff --git a/src/main/java/essenmakanan/recipe/Recipe.java b/src/main/java/essenmakanan/recipe/Recipe.java
new file mode 100644
index 0000000000..5a3ae0759b
--- /dev/null
+++ b/src/main/java/essenmakanan/recipe/Recipe.java
@@ -0,0 +1,107 @@
+package essenmakanan.recipe;
+
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ui.Ui;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class Recipe {
+ private String title;
+ private RecipeStepList recipeSteps;
+ private RecipeIngredientList recipeIngredients;
+
+
+ public Recipe(String title, RecipeStepList recipeSteps, RecipeIngredientList recipeIngredients) {
+ this.title = title;
+ this.recipeSteps = recipeSteps;
+ this.recipeIngredients = recipeIngredients;
+ }
+
+ public Recipe(String title, String[] steps, String[] ingredients) {
+ this.title = title;
+ this.recipeSteps = new RecipeStepList(steps);
+ this.recipeIngredients = new RecipeIngredientList(ingredients);
+ }
+
+ public Recipe(String title, RecipeStepList recipeSteps) {
+ this.title = title;
+ this.recipeSteps = recipeSteps;
+ this.recipeIngredients = new RecipeIngredientList(new String[]{});
+ }
+
+ public RecipeStepList getRecipeSteps() {
+ return recipeSteps;
+ }
+
+ public RecipeIngredientList getRecipeIngredients() {
+ return recipeIngredients;
+ }
+
+ public Step getRecipeStepByIndex(int index) {
+ return recipeSteps.getStepByIndex(index);
+ }
+
+ public Ingredient getRecipeIngredientByIndex(int index) {
+ return recipeIngredients.getIngredientByIndex(index);
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ @Override
+ public String toString() {
+ return getTitle();
+ }
+
+ public void addStep(Step step) {
+ recipeSteps.addStep(step);
+ }
+
+ public String getTotalDuration() {
+ int totalDuration = 0;
+ for (Step step : recipeSteps.getSteps()) {
+ totalDuration += step.getEstimatedDuration();
+ }
+ int hour = totalDuration / 60;
+ int minutes = totalDuration - hour * 60;
+ return "This Recipe will take you " + hour + " hours and " + minutes + " minutes.";
+ }
+
+
+ public void viewTimeLine() {
+
+ Map> categorizedSteps = recipeSteps.getSteps()
+ .stream()
+ .sorted((s1,s2) -> s1.getTag().hasHigherPriorityThan(s2.getTag()))
+ .collect(Collectors.groupingBy(Step::getTag));
+
+ for (Tag tag : Tag.values()) {
+ if (categorizedSteps.get(tag) != null && categorizedSteps.get(tag).size() > 0) {
+ System.out.println("Steps that you have to do: " + tag);
+ categorizedSteps.get(tag).forEach(step -> System.out.println(" " + step));
+ Ui.drawDivider();
+ }
+ }
+
+ }
+
+ /**
+ * Recipe stub for testing purposes
+ *
+ * @param title name of the stub
+ * @return a Recipe stub object for testing
+ */
+ public static Recipe createRecipeStub(String title) {
+ String[] recipeSteps = {"step1", "step2"};
+ String[] recipeIngredients = {"i/flour,200,g", "i/egg,2,pc"};
+ return new Recipe(title, recipeSteps, recipeIngredients);
+ }
+
+}
diff --git a/src/main/java/essenmakanan/recipe/RecipeIngredientList.java b/src/main/java/essenmakanan/recipe/RecipeIngredientList.java
new file mode 100644
index 0000000000..e76ae1665a
--- /dev/null
+++ b/src/main/java/essenmakanan/recipe/RecipeIngredientList.java
@@ -0,0 +1,80 @@
+package essenmakanan.recipe;
+
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.parser.IngredientParser;
+
+import essenmakanan.exception.EssenFormatException;
+
+import java.util.ArrayList;
+
+public class RecipeIngredientList {
+ private ArrayList ingredients = new ArrayList<>();
+
+
+ public RecipeIngredientList(ArrayList ingredients) {
+ this.ingredients = ingredients;
+ }
+
+ public RecipeIngredientList(String[] inputIngredients) {
+ for (String ingredientString : inputIngredients) {
+ try {
+ Ingredient ingredient = IngredientParser.parseIngredient(ingredientString);
+ this.addIngredient(ingredient);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+ }
+ }
+
+ /**
+ * To add ingredient by name into the recipe
+ *
+ * @param input is the input of the ingredient
+ */
+ public void addIngredient(String input) {
+ try{
+ Ingredient ingredient = IngredientParser.parseIngredient(input);
+ this.ingredients.add(ingredient);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+ }
+
+ /**
+ * To add ingredient object into the recipe
+ *
+ * @param ingredient is the name of the ingredient
+ */
+ public void addIngredient(Ingredient ingredient) {
+ this.ingredients.add(ingredient);
+ }
+
+ public ArrayList getIngredients() {
+ return ingredients;
+ }
+
+ /**
+ * To get ingredient by index
+ *
+ * @param index of the ingredient to be retrieved
+ */
+ public Ingredient getIngredientByIndex(int index) {
+ return ingredients.get(index);
+ }
+
+ /**
+ * To check if ingredient exist using its name
+ * @param ingredientName is the name of the ingredient to check
+ * @return true if ingredient exist
+ */
+ public boolean ingredientExist(String ingredientName) {
+ String recipeIngredientName;
+ for (Ingredient recipeIngredient : ingredients) {
+ recipeIngredientName = recipeIngredient.getName();
+ if (recipeIngredientName.equals(ingredientName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/essenmakanan/recipe/RecipeList.java b/src/main/java/essenmakanan/recipe/RecipeList.java
new file mode 100644
index 0000000000..053bd48554
--- /dev/null
+++ b/src/main/java/essenmakanan/recipe/RecipeList.java
@@ -0,0 +1,308 @@
+package essenmakanan.recipe;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenInvalidEditException;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ui.Ui;
+import essenmakanan.ingredient.Ingredient;
+
+import java.util.ArrayList;
+
+public class RecipeList {
+ private ArrayList recipes;
+
+ public RecipeList() {
+ recipes = new ArrayList<>();
+ }
+
+ public RecipeList(ArrayList recipes) {
+ this.recipes = recipes;
+ }
+
+ public ArrayList getRecipes() {
+ return recipes;
+ }
+
+ public void addRecipe(Recipe recipe) {
+ recipes.add(recipe);
+ assert getRecipe(recipes.size() - 1).getTitle().equals(recipe.getTitle())
+ : "Recipe is not successfully added into the list.";
+ }
+
+ /**
+ * To delete a recipe by index
+ *
+ * @param index of the recipe to be deleted
+ */
+ public void deleteRecipe(int index) {
+ Ui.printDeleteRecipeSuccess(recipes.get(index).getTitle());
+ recipes.remove(index);
+ }
+
+ /**
+ * Get recipe using index
+ *
+ * @param index of recipe
+ * @return Recipe
+ */
+ public Recipe getRecipe(int index) {
+ assert recipeExist(index) : "Index is out of bounds";
+ return recipes.get(index);
+ }
+
+ /**
+ * Get recipe using title of recipe
+ *
+ * @param name is the title o fthe recipe
+ * @return recipe if exists, otherwise, null
+ */
+ public Recipe getRecipe(String name) {
+ for (Recipe recipe : recipes) {
+ if (recipe.getTitle().equals(name)) {
+ return recipe;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the index of the recipeu sing recipeTitle
+ *
+ * @param recipeTitle the title of the recipe
+ * @return index of recipe if recipe is found, otherwise, returns -1
+ */
+ public int getIndexOfRecipe(String recipeTitle) {
+ int i = 0;
+ for (Recipe recipe : recipes) {
+ if (recipe.getTitle().equalsIgnoreCase(recipeTitle)) {
+ return i;
+ }
+ i++;
+ }
+ return -1;
+ }
+
+ /**
+ * To check if the recipe exists using recipe index
+ *
+ * @param index of recipe
+ * @return true if recipe index is within range
+ */
+ public boolean recipeExist(int index) {
+ if (index >= 0 && index < recipes.size()) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Print all recipe titles in the recipe list
+ */
+ public void listRecipeTitles() {
+ int count = 1;
+
+ for (Recipe recipe : recipes) {
+ assert recipes.get(count - 1).getTitle().equals(recipe.getTitle())
+ : "Title is not matching with the current index";
+
+ System.out.println(count + ". " + recipe);
+ count++;
+ }
+ }
+
+ private static void listRecipeSteps(Recipe recipe) {
+ recipe.viewTimeLine();
+ }
+
+ /**
+ * Print all recipe ingredients in the recipe list
+ *
+ * @param recipe
+ */
+ private static void listRecipeIngredients(Recipe recipe) {
+ RecipeIngredientList ingredients = recipe.getRecipeIngredients();
+
+ // if ingredient list is empty
+ if (ingredients == null) {
+ System.out.println("\tNo ingredients needed!");
+ return;
+ }
+
+ int count = 1;
+ for (Ingredient ingredient : ingredients.getIngredients()) {
+ assert ingredients.getIngredientByIndex(count - 1).equals(ingredient)
+ : "Ingredient is not matching with the current index";
+
+ System.out.println("\t" + count + ") " + ingredient);
+ count++;
+ }
+ }
+
+ /**
+ * To view a recipe by index
+ *
+ * @param index of the recipe
+ */
+ public void viewRecipe(int index) {
+ Ui.drawDivider();
+
+ assert recipeExist(index) : "Index is out of bounds";
+ Recipe recipe = recipes.get(index);
+ System.out.println("<>");
+ Ui.drawDivider();
+ System.out.println("<>");
+ listRecipeIngredients(recipe);
+ Ui.drawDivider();
+ System.out.println("<>");
+ listRecipeSteps(recipe);
+ }
+
+ /**
+ * To view a recipe by title
+ *
+ * @param title of the recipe
+ */
+ public void viewRecipe(String title) {
+ Ui.drawDivider();
+ Recipe recipe = recipes.stream()
+ .filter(recipe1 -> recipe1.getTitle().equals(title))
+ .findFirst()
+ .orElse(null);
+ assert getRecipe(title) == recipe : "Recipe does not exist";
+ listRecipeSteps(recipe);
+ }
+
+ /**
+ * To edit a recipe
+ *
+ * @param existingRecipe is the recipe to be edited
+ * @param editDetails is the details to be edited
+ * @throws EssenFormatException if the format is invalid
+ */
+ public void editRecipe(Recipe existingRecipe, String[] editDetails) throws EssenFormatException {
+ for (int i = 0; i < editDetails.length; i++) {
+ // get flag of input to know which field to edit
+ String flag = editDetails[i].substring(0, 2);
+
+ assert (flag != null) : "Invalid flag";
+ switch (flag) {
+ case "n/":
+ String newName = editDetails[i].substring(2);
+ Ui.printEditRecipeNameSuccess(existingRecipe.getTitle(), newName);
+ existingRecipe.setTitle(newName);
+ break;
+ case "s/":
+ String[] stepDetails = editDetails[i].substring(2).split(",");
+
+ int stepIndex = -1;
+
+ try {
+ stepIndex = Integer.parseInt(stepDetails[0])-1;
+ } catch (NumberFormatException e) {
+ System.out.println("Step index must be a number!");
+ throw new EssenFormatException();
+ }
+
+ if (!noDescriptionExists(stepDetails)) {
+ stepIndex = Integer.parseInt(stepDetails[0])-1;
+ Step existingStep = existingRecipe.getRecipeStepByIndex(stepIndex);
+ String newStep = stepDetails[1];
+ newStep = Step.convertToStepIdTemplate(newStep, stepIndex+1);
+ Ui.printEditRecipeStepSuccess(existingStep.getDescription(), newStep);
+ existingStep.setDescription(newStep);
+ }
+ break;
+ case "i/":
+ int firstSlash = editDetails[i].indexOf("/");
+ int firstComma = editDetails[i].indexOf(",");
+ int ingredientIndex = -1;
+
+ try {
+ ingredientIndex = Integer.parseInt(editDetails[i].substring(firstSlash+1,firstComma))-1;
+ } catch (NumberFormatException e) {
+ System.out.println("Ingredient index must be a number!");
+ throw new EssenFormatException();
+ }
+
+ assert (ingredientIndex >= 0) : "Ingredient index must be positive";
+
+ Ingredient existingIngredient = null;
+ try {
+ existingIngredient = existingRecipe.getRecipeIngredientByIndex(ingredientIndex);
+ } catch (IndexOutOfBoundsException e) {
+ System.out.println("Make sure ingredient index is valid!");
+ throw new EssenFormatException();
+ }
+
+ String ingredientEditDetailsString = editDetails[i].substring(firstComma+1);
+ String[] ingredientDetails = null;
+ try {
+ ingredientDetails = getIngredientEditDetails(ingredientEditDetailsString);
+ } catch (EssenInvalidEditException e) {
+ e.handleException();
+ }
+
+ assert ingredientDetails != null : "Ingredient details is null";
+
+ try {
+ IngredientList.editIngredient(existingIngredient, ingredientDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ // Ui.printEditRecipeIngredientSuccess(existingIngredient.getName(), newIngredient);
+ break;
+ default:
+ throw new EssenFormatException();
+ }
+ }
+
+ }
+
+ /**
+ * To get the details of the ingredient to be edited
+ *
+ * @param ingredientEditString is the string of the ingredient to be edited
+ * @return String array of the ingredient details
+ * @throws EssenInvalidEditException if the format is invalid
+ */
+ public String[] getIngredientEditDetails(String ingredientEditString) throws EssenInvalidEditException{
+ int totalDashes = ingredientEditString.split("-").length-1;
+ String[] ingredientEditDetails = new String[totalDashes];
+ int counter = 0;
+
+ int firstDash = ingredientEditString.indexOf("-");
+
+ while (firstDash != -1) {
+ if ((firstDash + 1) >= ingredientEditString.length()) {
+ System.out.println("Please provide details to edit");
+ throw new EssenInvalidEditException();
+ }
+
+ int nextDash = ingredientEditString.indexOf("-", firstDash+1);
+
+ if (nextDash != -1) {
+ String stringToReplaceDash = ingredientEditString.substring(firstDash - 1, nextDash - 2).trim();
+ ingredientEditDetails[counter] = stringToReplaceDash.replace("-", "/");
+ } else {
+ String stringToReplaceDash = ingredientEditString.substring(firstDash-1).trim();
+ ingredientEditDetails[counter] = stringToReplaceDash.replace("-", "/");
+ }
+ counter++;
+ firstDash = nextDash;
+ }
+
+ return ingredientEditDetails;
+ }
+
+ private static boolean noDescriptionExists(String[] stepDetails) {
+ if (stepDetails.length <= 1) {
+ System.out.println("The description is empty! You have to provide details to edit this step!");
+ return true;
+ }
+ return false;
+ }
+ public boolean isEmpty() {
+ return recipes.isEmpty();
+ }
+}
diff --git a/src/main/java/essenmakanan/recipe/RecipeStepList.java b/src/main/java/essenmakanan/recipe/RecipeStepList.java
new file mode 100644
index 0000000000..07d37ff953
--- /dev/null
+++ b/src/main/java/essenmakanan/recipe/RecipeStepList.java
@@ -0,0 +1,193 @@
+package essenmakanan.recipe;
+
+import essenmakanan.exception.EssenInvalidEnumException;
+
+import java.util.ArrayList;
+import java.util.Scanner;
+
+public class RecipeStepList {
+ private ArrayList steps = new ArrayList<>();
+
+ public RecipeStepList() {
+ Scanner in = new Scanner(System.in);
+ String input;
+ boolean isAddingSteps = true;
+
+ do {
+ System.out.println("Add steps of your recipe, type \"end\" to finish");
+ input = in.nextLine();
+
+ if (input.equals("end")) {
+ isAddingSteps = false;
+ } else {
+ assert (input != null) : "Input is null";
+ this.addStep(input);
+ }
+ } while (isAddingSteps);
+ System.out.println("Finished adding steps!");
+ }
+
+ public RecipeStepList(ArrayList steps) {
+ this.steps = steps;
+ }
+
+ /**
+ * To add step by as a string of array into the recipe steps
+ *
+ * @param inputSteps total steps for a recipe
+ */
+ public RecipeStepList(String[] inputSteps) {
+ Step step = null;
+ for (String stepString : inputSteps) {
+ if (stepString.contains("t/") && stepString.contains("d/")) {
+ step = this.createStepWithTagAndDuration(stepString);
+ assert step != null : "Step is not initialised";
+ } else if (stepString.contains("t/")) {
+ // step with tag only
+ step = this.createStepWithTag(stepString);
+ assert step != null : "Step is not initialised";
+ } else if (stepString.contains("d/")) {
+ // step with duration only
+ step = this.createStepWithDuration(stepString);
+ assert step != null : "Step is not initialised";
+ }else {
+ // only step description
+ step = new Step(stepString);
+ }
+
+ this.addStep(step);
+
+ }
+ }
+
+ /**
+ * To create step that only has a tag
+ *
+ * @param stepString is the step with tag
+ * @return step with tag
+ */
+ public Step createStepWithTag(String stepString) {
+ String tagValue;
+
+ String[] stepStringSplit = stepString.split("t/");
+
+ // step description
+ String stepDescription = stepStringSplit[0].trim();
+
+ // get tag
+ tagValue = stepStringSplit[1];
+ Tag tag = this.obtainTag(tagValue);
+ return new Step(stepDescription, tag);
+ }
+
+ /**
+ * To create step that only has a duration
+ *
+ * @param stepString is the step with duration
+ * @return step with duration
+ */
+ public Step createStepWithDuration(String stepString) {
+
+ String[] stepStringSplit = stepString.split("d/");
+
+ // step description
+ String stepDescription = stepStringSplit[0].trim();
+
+ // get duration (in minutes)
+ String durationString = stepStringSplit[1];
+ int duration = Integer.parseInt(durationString);
+ return new Step(stepDescription, duration);
+ }
+
+ /**
+ * To create step that has a tag and duration
+ *
+ * @param stepString is the step with tag and duration
+ * @return step with tag and duration
+ */
+ public Step createStepWithTagAndDuration(String stepString) {
+
+ // by implementation, if tag exists, it will be before duration
+ int tagFlag = stepString.indexOf("t/");
+ int durationFlag = stepString.indexOf("d/");
+
+ // step description
+ String stepDescription = stepString.substring(0, tagFlag).trim();
+
+ // get tag
+ String tagValue = stepString.substring(tagFlag + 2, durationFlag).trim();
+ Tag tag = this.obtainTag(tagValue);
+
+ // get duration (in minutes)
+ String durationString = stepString.substring(durationFlag + 2).trim();
+ int duration = Integer.parseInt(durationString);
+
+ return new Step(stepDescription, tag, duration);
+ }
+
+ /**
+ * To obtain tag from string
+ *
+ * @param tagValue is the tag value
+ * @return tag
+ */
+ public Tag obtainTag(String tagValue) {
+ try {
+ return Tag.mapStringToTag(tagValue);
+ } catch (EssenInvalidEnumException e) {
+ System.out.println("No such tag");
+ }
+ return null;
+ }
+
+ /**
+ * To add step by string into the recipe steps
+ *
+ * @param stepString is the step
+ */
+ public void addStep(String stepString) {
+ Step step = new Step(stepString);
+ this.steps.add(step);
+ }
+
+ /**
+ * To add step object into the recipe steps
+ *
+ * @param step is the step object
+ */
+ public void addStep(Step step) {
+ this.steps.add(step);
+ }
+
+ public ArrayList getSteps() {
+ return steps;
+ }
+
+ /**
+ * To get step by index
+ *
+ * @param index of the step to be retrieved
+ */
+ public Step getStepByIndex(int index) {
+ return steps.get(index);
+ }
+
+ /**
+ * To get step by name
+ *
+ * @param name of the step to be retrieved
+ */
+ public int getStepIndexByName(String name) {
+ for (int i = 0; i < steps.size(); i++) {
+ if (steps.get(i).getDescription().equals(name)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public void deleteStep(Step step) {
+ steps.remove(step);
+ }
+
+}
diff --git a/src/main/java/essenmakanan/recipe/Step.java b/src/main/java/essenmakanan/recipe/Step.java
new file mode 100644
index 0000000000..80c12dc970
--- /dev/null
+++ b/src/main/java/essenmakanan/recipe/Step.java
@@ -0,0 +1,79 @@
+package essenmakanan.recipe;
+
+public class Step {
+
+ private String description;
+
+ private Tag tag;
+
+ private int estimatedDuration;
+
+ public Step(String description, Tag tag) {
+ this.description = description;
+ this.tag = tag;
+ this.estimatedDuration = 0;
+ }
+
+ public Step(String description) {
+ this.description = description;
+ this.estimatedDuration = 0;
+ this.tag = Tag.ACTUAL_COOKING;
+ }
+
+ public Step(String description, Tag tag, int estimatedDuration) {
+ this.description = description;
+ this.tag = tag;
+ this.estimatedDuration = estimatedDuration;
+ }
+
+ public Step(String description, int estimatedDuration) {
+ this.description = description;
+ this.estimatedDuration = estimatedDuration;
+ this.tag = Tag.ACTUAL_COOKING;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Tag getTag() {
+ return tag;
+ }
+
+ public void setTag(Tag tag) {
+ this.tag = tag;
+ }
+
+ public int getEstimatedDuration() {
+ return estimatedDuration;
+ }
+
+ public void setEstimatedDuration(int estimatedDuration) {
+ this.estimatedDuration = estimatedDuration;
+ }
+
+ /**
+ * Converts step description to step id template. Eg: "Wash vegetables" -> "Wash vegetables (step id = 1)"
+ *
+ * @param stepDescription step description
+ * @param id step id
+ * @return step id template
+ */
+ public static String convertToStepIdTemplate(String stepDescription, int id) {
+ return stepDescription + (" (step id = " + id + ")");
+ }
+
+ @Override
+ public String toString() {
+ String detailedStep = "You need to " + getDescription();
+ if (estimatedDuration == 0) {
+ return detailedStep;
+ } else {
+ return detailedStep + " for " + estimatedDuration + " minutes.";
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/recipe/Tag.java b/src/main/java/essenmakanan/recipe/Tag.java
new file mode 100644
index 0000000000..f307fe2bb3
--- /dev/null
+++ b/src/main/java/essenmakanan/recipe/Tag.java
@@ -0,0 +1,64 @@
+package essenmakanan.recipe;
+
+import essenmakanan.exception.EssenInvalidEnumException;
+
+public enum Tag {
+ NIGHT_BEFORE(1),
+ MORNING_OF_COOKING(2),
+ MORE_THAN_ONE_DAY(3),
+ ACTUAL_COOKING(4);
+
+ private int priority;
+
+ Tag(int priority) {
+ this.priority = priority;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public static Tag getByPriority(int priority) {
+ for (Tag tag : values()) {
+ if (tag.getPriority() == priority) {
+ return tag;
+ }
+ }
+ throw new IllegalArgumentException("No enum constant with priority " + priority);
+ }
+
+ public int hasHigherPriorityThan(Tag otherTag) {
+ if (this.priority > otherTag.priority) {
+ return 1;
+ } else if (this.priority < otherTag.priority) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ public static boolean tagExist(String tagString) {
+ int tagValue = Integer.parseInt(tagString);
+ for (Tag tag : values()) {
+ if (tag.getPriority() == tagValue) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static Tag mapStringToTag(String input) throws EssenInvalidEnumException {
+ switch(input) {
+ case "1":
+ return Tag.NIGHT_BEFORE;
+ case "2":
+ return Tag.MORNING_OF_COOKING;
+ case "3":
+ return Tag.MORE_THAN_ONE_DAY;
+ case "4":
+ return Tag.ACTUAL_COOKING;
+ default:
+ throw new EssenInvalidEnumException();
+ }
+ }
+}
diff --git a/src/main/java/essenmakanan/shortcut/Shortcut.java b/src/main/java/essenmakanan/shortcut/Shortcut.java
new file mode 100644
index 0000000000..e14994dbb0
--- /dev/null
+++ b/src/main/java/essenmakanan/shortcut/Shortcut.java
@@ -0,0 +1,33 @@
+package essenmakanan.shortcut;
+
+public class Shortcut {
+
+ private String ingredientName;
+ private double quantity;
+
+ public Shortcut(String ingredientName, double quantity) {
+ this.ingredientName = ingredientName;
+ this.quantity = quantity;
+ }
+
+ public String getIngredientName() {
+ return ingredientName;
+ }
+
+ public double getQuantity() {
+ return quantity;
+ }
+
+ public void setIngredientName(String ingredientName) {
+ this.ingredientName = ingredientName;
+ }
+
+ public void setQuantity(double quantity) {
+ this.quantity = quantity;
+ }
+
+ @Override
+ public String toString() {
+ return ingredientName + ": add " + quantity;
+ }
+}
diff --git a/src/main/java/essenmakanan/shortcut/ShortcutList.java b/src/main/java/essenmakanan/shortcut/ShortcutList.java
new file mode 100644
index 0000000000..3f7fd8625c
--- /dev/null
+++ b/src/main/java/essenmakanan/shortcut/ShortcutList.java
@@ -0,0 +1,87 @@
+package essenmakanan.shortcut;
+
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ui.Ui;
+
+import java.util.ArrayList;
+
+public class ShortcutList {
+
+ private ArrayList shortcuts;
+
+ public ShortcutList() {
+ shortcuts = new ArrayList<>();
+ }
+
+ public ShortcutList(ArrayList shortcuts) {
+ this.shortcuts = shortcuts;
+ }
+
+ public ArrayList getShortcuts() {
+ return shortcuts;
+ }
+
+ public void addShortcut(Shortcut shortcut) {
+ shortcuts.add(shortcut);
+ }
+
+ public Shortcut getShortcut(int index) throws EssenOutOfRangeException {
+ Shortcut shortcut;
+
+ try {
+ shortcut = shortcuts.get(index);
+ } catch (IndexOutOfBoundsException exception) {
+ throw new EssenOutOfRangeException();
+ }
+
+ return shortcut;
+ }
+
+ public void listShortcuts() {
+ Ui.drawDivider();
+
+ if (shortcuts.isEmpty()) {
+ System.out.println("No shortcuts found in session.");
+ }
+
+ int count = 1;
+
+ for (Shortcut shortcut : shortcuts) {
+ System.out.print(count + ". ");
+ System.out.println(shortcut);
+ count++;
+ }
+ }
+
+ public int getIndex(String ingredientName) {
+ int index = 0;
+
+ for (Shortcut shortcut : shortcuts) {
+ if (shortcut.getIngredientName().equals(ingredientName)) {
+ return index;
+ }
+ index++;
+ }
+
+ return -1;
+ }
+
+ public void deleteShortcut(int index) throws EssenOutOfRangeException {
+ try {
+ Ui.printDeletedShortcut(getShortcut(index).getIngredientName());
+ shortcuts.remove(index);
+ } catch (IndexOutOfBoundsException exception) {
+ throw new EssenOutOfRangeException();
+ }
+ }
+
+ public boolean exist(String ingredientName) {
+ for (Shortcut shortcut : shortcuts) {
+ if (shortcut.getIngredientName().equals(ingredientName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/essenmakanan/storage/IngredientStorage.java b/src/main/java/essenmakanan/storage/IngredientStorage.java
new file mode 100644
index 0000000000..c2a6a4e2d9
--- /dev/null
+++ b/src/main/java/essenmakanan/storage/IngredientStorage.java
@@ -0,0 +1,150 @@
+package essenmakanan.storage;
+
+import essenmakanan.exception.EssenFileNotFoundException;
+import essenmakanan.exception.EssenInvalidEnumException;
+import essenmakanan.exception.EssenStorageDuplicateException;
+import essenmakanan.exception.EssenStorageFormatException;
+import essenmakanan.exception.EssenStorageNumberException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientUnit;
+import essenmakanan.logger.EssenLogger;
+import essenmakanan.parser.IngredientParser;
+import essenmakanan.ui.Ui;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Scanner;
+
+/**
+ * A handler for storing ingredients.
+ */
+public class IngredientStorage {
+
+ private String dataPath;
+
+ private ArrayList ingredientListPlaceholder;
+
+ /**
+ * Creates an ingredient storage handler.
+ *
+ * @param path The path for storing ingredient data.
+ */
+ public IngredientStorage(String path) {
+ ingredientListPlaceholder = new ArrayList<>();
+ dataPath = path;
+ }
+
+ /**
+ * Saves ingredient data into a text file.
+ *
+ * @param ingredients The ingredient list.
+ */
+ public void saveData(ArrayList ingredients) {
+ try {
+ FileWriter writer = new FileWriter(dataPath, false);
+ String dataString;
+
+ EssenLogger.logInfo("Transferring ingredient data");
+ for (Ingredient ingredient : ingredients) {
+ dataString = IngredientParser.convertToString(ingredient);
+ writer.write(dataString);
+ writer.write(System.lineSeparator());
+ }
+
+ writer.close();
+ EssenLogger.logInfo("Ingredient data has been successfully saved");
+ } catch (IOException exception) {
+ Ui.handleIOException(exception);
+ EssenLogger.logSevere("Unable to save ingredient data", exception);
+ }
+ }
+
+ /**
+ * Searches duplicates in the data.
+ *
+ * @param ingredientName The ingredient name
+ * @return Confirmation if there is a duplicate in the list of data.
+ */
+ private boolean searchDuplicate(String ingredientName) {
+ for (Ingredient ingredient : ingredientListPlaceholder) {
+ if (ingredient.getName().equals(ingredientName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates a new data based on the current line of data.
+ *
+ * @param scan The scanner that refers to the text file.
+ */
+ private void createNewData(Scanner scan) {
+ String dataString = scan.nextLine();
+ String[] parsedIngredient = dataString.trim().split(" \\| ");
+
+ EssenLogger.logInfo("Retrieving ingredient data");
+ try {
+ if (parsedIngredient.length != 3 || parsedIngredient[1].isBlank()) {
+ throw new EssenStorageFormatException();
+ }
+
+ String ingredientName = parsedIngredient[0];
+ if (searchDuplicate(ingredientName)) {
+ throw new EssenStorageDuplicateException();
+ }
+
+
+ double ingredientQuantity = Double.parseDouble(parsedIngredient[1]);
+ IngredientUnit ingredientUnit = IngredientUnit.valueOf(parsedIngredient[2]);
+
+ if (!IngredientParser.checkForValidQuantity(ingredientQuantity)) {
+ throw new NumberFormatException();
+ }
+
+ ingredientListPlaceholder.add(new Ingredient(ingredientName, ingredientQuantity, ingredientUnit));
+ } catch (EssenStorageFormatException exception) {
+ exception.handleException(dataString);
+ String message = "Data: " + dataString + " has an invalid format";
+ EssenLogger.logWarning(message, exception);
+ } catch (NumberFormatException exception) {
+ EssenStorageNumberException.handleException(dataString);
+ String message = "Data: " + dataString + " has an invalid quantity";
+ EssenLogger.logWarning(message, exception);
+ } catch (IllegalArgumentException exception) {
+ EssenInvalidEnumException.handleException(dataString);
+ String message = "Data: " + dataString + " has an invalid enum";
+ EssenLogger.logWarning(message, exception);
+ } catch (EssenStorageDuplicateException exception) {
+ exception.handleException(dataString);
+ String message = "Data: " + dataString + " cannot be created due to duplicates";
+ EssenLogger.logWarning(message, exception);
+ }
+ EssenLogger.logInfo("Saved ingredient data has been received");
+ }
+
+ /**
+ * Restores saved data from the previous session.
+ *
+ * @return The ingredient list.
+ * @throws EssenFileNotFoundException If the text file is not found.
+ */
+ public ArrayList restoreSavedData() throws EssenFileNotFoundException {
+ try {
+ File file = new File(dataPath);
+ Scanner scan = new Scanner(file);
+ while (scan.hasNext()) {
+ createNewData(scan);
+ }
+ } catch (FileNotFoundException exception) {
+ EssenLogger.logWarning("Text file not found", exception);
+ throw new EssenFileNotFoundException();
+ }
+
+ return ingredientListPlaceholder;
+ }
+}
diff --git a/src/main/java/essenmakanan/storage/RecipeStorage.java b/src/main/java/essenmakanan/storage/RecipeStorage.java
new file mode 100644
index 0000000000..20e8cf7dbd
--- /dev/null
+++ b/src/main/java/essenmakanan/storage/RecipeStorage.java
@@ -0,0 +1,165 @@
+package essenmakanan.storage;
+
+import essenmakanan.exception.EssenFileNotFoundException;
+import essenmakanan.exception.EssenInvalidEnumException;
+import essenmakanan.exception.EssenStorageDuplicateException;
+import essenmakanan.exception.EssenStorageFormatException;
+import essenmakanan.exception.EssenStorageNumberException;
+import essenmakanan.logger.EssenLogger;
+import essenmakanan.parser.RecipeParser;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeIngredientList;
+import essenmakanan.recipe.RecipeStepList;
+import essenmakanan.ui.Ui;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Scanner;
+
+/**
+ * A handler for storing recipes.
+ */
+public class RecipeStorage {
+
+ private String dataPath;
+
+ private ArrayList recipeListPlaceholder;
+
+ /**
+ * Creates a recipe storage handler.
+ *
+ * @param path The path for storing recipe data.
+ */
+ public RecipeStorage(String path) {
+ recipeListPlaceholder = new ArrayList<>();
+ dataPath = path;
+ }
+
+ /**
+ * Converts a recipe into string form.
+ *
+ * @param recipe A recipe.
+ * @return A recipe that has been converted into a string.
+ */
+ public String convertToString(Recipe recipe) {
+ String recipeStepString;
+ recipeStepString = RecipeParser.convertSteps(recipe.getRecipeSteps().getSteps());
+
+ String ingredientString;
+ ingredientString = RecipeParser.convertIngredient(recipe.getRecipeIngredients().getIngredients());
+
+ return recipe.getTitle() + " || " + recipeStepString + " || " + ingredientString;
+ }
+
+ /**
+ * Saves recipe data into a text file.
+ *
+ * @param recipes The recipe list.
+ */
+ public void saveData(ArrayList recipes) {
+ try {
+ FileWriter writer = new FileWriter(dataPath, false);
+ String dataString;
+
+ EssenLogger.logInfo("Transferring recipe data");
+ for (Recipe recipe : recipes) {
+ dataString = convertToString(recipe);
+ writer.write(dataString);
+ writer.write(System.lineSeparator());
+ }
+
+ writer.close();
+ EssenLogger.logInfo("Recipe data has been successfully saved");
+ } catch (IOException exception) {
+ Ui.handleIOException(exception);
+ EssenLogger.logSevere("Unable to save recipe data", exception);
+ }
+ }
+
+ /**
+ * Searches duplicates in the data.
+ *
+ * @param recipeName The recipe name.
+ * @return Confirmation if there is a duplicate in the list of data.
+ */
+ private boolean searchDuplicate(String recipeName) {
+ for (Recipe recipe : recipeListPlaceholder) {
+ if (recipe.getTitle().equals(recipeName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates a new data based on the current line of data.
+ *
+ * @param scan The scanner that refers to the text file.
+ */
+ private void createNewData(Scanner scan) {
+ String dataString = scan.nextLine();
+ String[] parsedRecipe = dataString.trim().split(" \\|\\| ");
+
+ EssenLogger.logInfo("Retrieving recipe data");
+ try {
+ if (parsedRecipe.length != 3 || parsedRecipe[1].isEmpty()) {
+ throw new EssenStorageFormatException();
+ }
+
+ String recipeDescription = parsedRecipe[0];
+ if (searchDuplicate(recipeDescription)) {
+ throw new EssenStorageDuplicateException();
+ }
+
+ RecipeStepList steps;
+ steps = RecipeParser.parseDataSteps(parsedRecipe[1].trim());
+
+ RecipeIngredientList ingredientList;
+ ingredientList = RecipeParser.parseDataRecipeIngredients(parsedRecipe[2].trim());
+
+ recipeListPlaceholder.add(new Recipe(recipeDescription, steps, ingredientList));
+ } catch (EssenStorageFormatException exception) {
+ exception.handleException(dataString);
+ String message = "Data: " + dataString + " has an invalid format";
+ EssenLogger.logWarning(message, exception);
+ } catch (NumberFormatException exception) {
+ EssenStorageNumberException.handleException(dataString);
+ String message = "Data: " + dataString + " has an invalid quantity";
+ EssenLogger.logWarning(message, exception);
+ } catch (IllegalArgumentException exception) {
+ EssenInvalidEnumException.handleException(dataString);
+ String message = "Data: " + dataString + " has an invalid enum";
+ EssenLogger.logWarning(message, exception);
+ } catch (EssenStorageDuplicateException exception) {
+ exception.handleException(dataString);
+ String message = "Data: " + dataString + " cannot be created due to duplicates";
+ EssenLogger.logWarning(message, exception);
+ }
+ EssenLogger.logInfo("Saved recipe data has been received");
+ }
+
+ /**
+ * Restores saved data from the previous session.
+ *
+ * @return The recipe list.
+ * @throws EssenFileNotFoundException If the text file is not found.
+ */
+ public ArrayList restoreSavedData() throws EssenFileNotFoundException {
+ try {
+ File file = new File(dataPath);
+ Scanner scan = new Scanner(file);
+ while (scan.hasNext()) {
+ createNewData(scan);
+ }
+ } catch (FileNotFoundException exception) {
+ EssenLogger.logWarning("Text file not found", exception);
+ throw new EssenFileNotFoundException();
+ }
+
+ return recipeListPlaceholder;
+ }
+}
diff --git a/src/main/java/essenmakanan/storage/ShortcutStorage.java b/src/main/java/essenmakanan/storage/ShortcutStorage.java
new file mode 100644
index 0000000000..38c8bdf953
--- /dev/null
+++ b/src/main/java/essenmakanan/storage/ShortcutStorage.java
@@ -0,0 +1,162 @@
+package essenmakanan.storage;
+
+import essenmakanan.exception.EssenFileNotFoundException;
+import essenmakanan.exception.EssenStorageDuplicateException;
+import essenmakanan.exception.EssenStorageFormatException;
+import essenmakanan.exception.EssenStorageInvalidQuantityException;
+import essenmakanan.exception.EssenStorageInvalidShortcutException;
+import essenmakanan.exception.EssenStorageNumberException;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.logger.EssenLogger;
+import essenmakanan.parser.IngredientParser;
+import essenmakanan.parser.ShortcutParser;
+import essenmakanan.shortcut.Shortcut;
+import essenmakanan.ui.Ui;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Scanner;
+
+/**
+ * A handler for storing shortcuts.
+ */
+public class ShortcutStorage {
+ private String dataPath;
+
+ private ArrayList shortcutListPlaceholder;
+ private IngredientList ingredients;
+
+ /**
+ * Creates a shortcut storage handler.
+ *
+ * @param path The path for storing shortcut data.
+ * @param ingredients The ingredient list.
+ */
+ public ShortcutStorage(String path, IngredientList ingredients) {
+ shortcutListPlaceholder = new ArrayList<>();
+ dataPath = path;
+ this.ingredients = ingredients;
+ }
+
+ /**
+ * Saves shortcut data into a text file.
+ *
+ * @param shortcuts The shortcut list.
+ */
+ public void saveData(ArrayList shortcuts) {
+ try {
+ FileWriter writer = new FileWriter(dataPath, false);
+ String dataString;
+
+ EssenLogger.logInfo("Transferring shortcut data");
+ for (Shortcut shortcut : shortcuts) {
+ dataString = ShortcutParser.convertToString(shortcut);
+ writer.write(dataString);
+ writer.write(System.lineSeparator());
+ }
+
+ writer.close();
+ EssenLogger.logInfo("Shortcut data has been successfully saved");
+ } catch (IOException exception) {
+ Ui.handleIOException(exception);
+ EssenLogger.logSevere("Unable to save shortcut data", exception);
+ }
+ }
+
+ /**
+ * Searches duplicates in the data.
+ *
+ * @param shortcutName The shortcut name.
+ * @return Confirmation if there is a duplicate in the list of data.
+ */
+ private boolean searchDuplicate(String shortcutName) {
+ for (Shortcut shortcut : shortcutListPlaceholder) {
+ if (shortcut.getIngredientName().equals(shortcutName)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates a new data based on the current line of data.
+ *
+ * @param scan The scanner that refers to the text file.
+ */
+ private void createNewData(Scanner scan) {
+ String dataString = scan.nextLine();
+ String[] parsedShortcut = dataString.trim().split(" \\| ");
+
+ EssenLogger.logInfo("Retrieving shortcut data");
+ try {
+ if (parsedShortcut.length != 2) {
+ throw new EssenStorageFormatException();
+ }
+
+ String shortcutName = parsedShortcut[0];
+ if (searchDuplicate(shortcutName)) {
+ throw new EssenStorageDuplicateException();
+ }
+
+ if (!ingredients.exist(shortcutName)) {
+ throw new EssenStorageInvalidShortcutException();
+ }
+
+ double shortcutQuantity = Double.parseDouble(parsedShortcut[1]);
+ if (!IngredientParser.checkForValidQuantity(shortcutQuantity)) {
+ throw new EssenStorageInvalidQuantityException();
+ }
+
+ shortcutListPlaceholder.add(new Shortcut(shortcutName, shortcutQuantity));
+ } catch (EssenStorageFormatException exception) {
+ exception.handleException(dataString);
+ String message = "Data: " + dataString + " has an invalid format";
+ EssenLogger.logWarning(message, exception);
+ } catch (EssenStorageDuplicateException exception) {
+ exception.handleException(dataString);
+ String message = "Data: " + dataString + " cannot be created due to duplicates";
+ EssenLogger.logWarning(message, exception);
+ } catch (EssenStorageInvalidQuantityException exception) {
+ exception.handleException(dataString);
+ String message = "Data: " + dataString + " cannot be created due to invalid quantity";
+ EssenLogger.logWarning(message, exception);
+ } catch (NumberFormatException exception) {
+ EssenStorageNumberException.handleException(dataString);
+ String message = "Data: " + dataString + " cannot be created due to non-numerical quantity";
+ EssenLogger.logWarning(message, exception);
+ } catch (EssenStorageInvalidShortcutException exception) {
+ exception.handleException(dataString);
+ String message = "Data: " + dataString + " cannot be created due to shortcut not attached to "
+ + "any ingredient in the list";
+ EssenLogger.logWarning(message, exception);
+ }
+
+ EssenLogger.logInfo("Saved shortcut data has been received");
+ }
+
+ /**
+ * Restores saved data from the previous session.
+ *
+ * @return The shortcut list.
+ * @throws EssenFileNotFoundException If the test file is not found.
+ */
+ public ArrayList restoreSavedData() throws EssenFileNotFoundException {
+ try {
+ File file = new File(dataPath);
+ Scanner scan = new Scanner(file);
+ while (scan.hasNext()) {
+ createNewData(scan);
+ }
+ } catch (FileNotFoundException exception) {
+ EssenLogger.logWarning("Text file not found", exception);
+ throw new EssenFileNotFoundException();
+ }
+
+ return shortcutListPlaceholder;
+ }
+}
+
diff --git a/src/main/java/essenmakanan/ui/Ui.java b/src/main/java/essenmakanan/ui/Ui.java
new file mode 100644
index 0000000000..b0cd76709e
--- /dev/null
+++ b/src/main/java/essenmakanan/ui/Ui.java
@@ -0,0 +1,418 @@
+package essenmakanan.ui;
+
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.shortcut.Shortcut;
+import essenmakanan.shortcut.ShortcutList;
+
+import java.io.IOException;
+import java.util.Scanner;
+
+public class Ui {
+
+ /**
+ * Prints out a welcome message.
+ */
+ public static void start() {
+ drawDivider();
+ System.out.println("Welcome to Essen Makanan!!! A one-stop place " +
+ "to track the\ningredients in your kitchen and store " +
+ "your favourite recipes");
+ System.out.println("To get started, type [help] for list of commands");
+ drawDivider();
+ }
+
+ /**
+ * Prints out a goodbye message.
+ */
+ public static void bye() {
+ drawDivider();
+ System.out.println("Hope you had fun! See you again!");
+ drawDivider();
+ }
+
+ /**
+ * Prints out a divider.
+ */
+ public static void drawDivider() {
+ String divider = "------------------------------------------------";
+ System.out.println(divider);
+ }
+
+ public static void printNewLine() {
+ System.out.println("\n");
+ }
+
+ public static void showRecipeCommands() {
+ System.out.println("RECIPE");
+ System.out.println("\t- View all recipes. [view r]\n"
+ + "\t- Start a recipe to see if you are missing any ingredients.\n"
+ + "\t\t [start RECIPE_TITLE] or [start RECIPE_ID]\n"
+ + "\t- Add recipe. [add r/RECIPE_TITLE t/TAG_ID s/STEP1 s/STEP2 t/TAG_ID s/STEP3 [s/...] "
+ + "i/INGREDIENT_NAME [i/...]]\n"
+ + "\t\t Tags:\n"
+ + "\t\t\t1 - NIGHT_BEFORE\n\t\t\t2 - MORNING_OF_COOKING\n\t\t\t"
+ + "3 - MORE_THAN_ONE_DAY\n\t\t\t4 - ACTUAL_COOKING\n"
+ + "\t- View a recipe. [view r/RECIPE_TITLE]\n"
+ + "\t- Edit a recipe. [edit r/RECIPE_TITLE n/NEW_TITLE s/STEP_TO_EDIT,NEW_STEP]\n"
+ + "\t- Delete a recipe. [delete r/RECIPE_INDEX] OR [delete r/RECIPE_TITLE]\n"
+ + "\t- Filter recipes based on ingredients. [filter recipe i/INGREDIENTNAME [i/...] ]\n"
+ + "\t- View all available recipes. [view ar]\n"
+ );
+ }
+
+ public static void showIngredientCommands() {
+ System.out.println("INGREDIENT");
+ System.out.println("\t- View all ingredients. [view i]\n"
+ + "\t- Add an ingredient. [add i/INGREDIENT_NAME,QUANTITY,UNIT [i/...] ]\n"
+ + "\t\t" + validIngredientUnits() + "\n"
+ + "\t- View an Ingredient. [view i/INGREDIENT_NAME] or [view i/INGREDIENT_ID]\n"
+ + "\t- Edit an ingredient. [edit i/INGREDIENT_NAME [n/NEW_NAME]"
+ + " [q/NEW_QUANTITY] [u/NEW_UNIT]\n"
+ + "\t- Delete an ingredient. [delete i/INGREDIENT_INDEX] OR [delete i/INGREDIENT_NAME]\n");
+ }
+
+ /**
+ * Prints out the list of shortcut commands.
+ */
+ public static void showShortcutCommands() {
+ System.out.println("SHORTCUT");
+ System.out.println("\t- View all shortcuts. [view sc]\n"
+ + "\t- Add a shortcut. [add i/INGREDIENT_NAME,QUANTITY]\n"
+ + "\t- Edit a shortcut. [edit i/INGREDIENT_NAME [n/NEW_NAME]"
+ + " [q/NEW_QUANTITY]\n"
+ + "\t- Delete a shortcut. [delete i/SHORTCUT_INDEX] OR [delete i/INGREDIENT_NAME]\n");
+ }
+
+ public static void showOtherCommands() {
+ System.out.println("OTHERS");
+ System.out.println("\t- View all commands [help]\n"
+ + "\t- Exit application [exit]");
+ }
+
+ public static void showCommands() {
+ System.out.println("Here are the commands currently available:\n");
+ showRecipeCommands();
+ showIngredientCommands();
+ showShortcutCommands();
+ showOtherCommands();
+ drawDivider();
+ }
+
+ /**
+ * Prints out recent added recipe title.
+ *
+ * @param recipeTitle The recipe title.
+ */
+ public static void printAddRecipeSuccess(String recipeTitle) {
+ System.out.println("Recipe: " + recipeTitle + " has been successfully created!");
+ drawDivider();
+ }
+
+ /**
+ * Prints out recent added ingredient title.
+ *
+ * @param ingredientTitle The ingredient title.
+ */
+ public static void printAddIngredientsSuccess(String ingredientTitle) {
+ System.out.println("Ingredient: " + ingredientTitle + " has been successfully created!");
+ drawDivider();
+ }
+
+ /**
+ * Prints out the updated ingredient, and its quantity.
+ *
+ * @param name name of ingredient
+ * @param existingQuantity quantity of ingredient before update
+ * @param newQuantity quantity of ingredient after update
+ */
+ public static void printUpdateIngredientsSuccess(String name, Double existingQuantity, Double newQuantity) {
+ System.out.println("Ingredient: " + name + " has been successfully updated from: " + existingQuantity
+ + " to: " + newQuantity);
+ drawDivider();
+ }
+
+ public static void printAllIngredients(IngredientList ingredients) {
+ if (ingredients.getIngredients().size() == 0) {
+ System.out.println("The Inventory of Ingredients is empty now, please add something first!");
+ return;
+ }
+ System.out.println("Here's a list of your ingredients!");
+ ingredients.listIngredients();
+ drawDivider();
+ }
+
+ public static void printStartRecipeMessage(IngredientList missingIngredients,
+ IngredientList insufficientIngredients,
+ IngredientList diffUnitIngredients,
+ String recipeTitle) {
+ System.out.println("Starting Recipe: " + recipeTitle + "\n");
+ boolean allEmpty = missingIngredients.isEmpty()
+ && insufficientIngredients.isEmpty()
+ && diffUnitIngredients.isEmpty();
+ if (allEmpty) {
+ System.out.println("You have all the ingredients you need! You are ready to go!");
+ System.out.println("(Use the execute command after you've executed your recipe " +
+ "- this is to update your ingredients inventory)");
+ } else {
+ if (!missingIngredients.isEmpty()) {
+ System.out.println("You are missing these ingredient(s): ");
+ missingIngredients.listIngredients();
+ printNewLine();
+ }
+ if (!insufficientIngredients.isEmpty()) {
+ System.out.println("You need to get more of these ingredient(s)\n" +
+ "(the stated quantity is the additional amount you need)");
+ insufficientIngredients.listIngredients();
+ printNewLine();
+ }
+ if (!diffUnitIngredients.isEmpty()) {
+ System.out.println("You may or may not need these ingredients!!!\n" +
+ "They are of different units so we couldn't tell :(");
+ diffUnitIngredients.listIngredients();
+ printNewLine();
+ }
+ System.out.println("Start your recipe again after getting the above ingredients!");
+ }
+ drawDivider();
+ }
+
+ public static void printAllRecipes(RecipeList recipes) {
+ if (recipes.getRecipes().size() == 0) {
+ System.out.println("Your Recipe List is empty right now, please create your own recipe first :D!");
+ drawDivider();
+ } else {
+ System.out.println("Here's a list of your recipes!");
+ recipes.listRecipeTitles();
+ drawDivider();
+ }
+ }
+
+ public static void printAllAvailableRecipes(RecipeList recipes) {
+ if (recipes.getRecipes().size() == 0) {
+ System.out.println("You don't have sufficient ingredients for any recipes at the moment :(");
+ } else {
+ System.out.println("Here are the recipes you can execute with your current ingredients!");
+ recipes.listRecipeTitles();
+ System.out.println("Use the execute command after executing any of these recipes!");
+ }
+ drawDivider();
+ }
+
+ /**
+ * Message to say ingredient was deleted successfully.
+ *
+ * @param ingredientName Ingredient name that was deleted.
+ */
+ public static void printDeleteIngredientsSuccess(String ingredientName) {
+ System.out.println("You have deleted the following ingredient: " + ingredientName);
+ drawDivider();
+ }
+
+ public static void printDeleteRecipeSuccess(String recipeTitle) {
+ System.out.println("You have deleted the following recipe: " + recipeTitle);
+ drawDivider();
+ }
+
+ /**
+ * String which contains valid ingredient units which maps to our IngredientUnit enum.
+ *
+ * @return String containing valid ingredient units.
+ */
+ public static String validIngredientUnits() {
+ return("Valid ingredient units are: g, kg, ml, l, tsp, tbsp, cup, pc");
+ }
+
+ /**
+ * Message that shows successful edit of ingredient name
+ *
+ * @param oldName The name of ingredient before edit.
+ * @param newName The name of ingredient after edit.
+ */
+ public static void printEditIngredientNameSuccess(String oldName, String newName) {
+ System.out.println("You have successfully edited the ingredient name from: " + oldName +
+ " to: " + newName);
+ drawDivider();
+ }
+
+ /**
+ * Message that shows successful edit of ingredient quantity
+ *
+ * @param oldQuantity The quantity of ingredient before edit.
+ * @param newQuantity The quantity of ingredient after edit.
+ */
+ public static void printEditIngredientQuantitySuccess(Double oldQuantity, Double newQuantity) {
+ System.out.println("You have successfully edited the ingredient quantity from: " + oldQuantity +
+ " to: " + newQuantity);
+ drawDivider();
+ }
+
+ /**
+ * Message that shows successful edit of ingredient unit
+ *
+ * @param oldUnit The unit of ingredient before edit.
+ * @param newUnit The unit of ingredient after edit.
+ */
+ public static void printEditIngredientUnitSuccess(IngredientUnit oldUnit, IngredientUnit newUnit) {
+ System.out.println("You have successfully edited the ingredient unit from: " + oldUnit +
+ " to: " + newUnit);
+ drawDivider();
+ }
+
+ public static void printSpecificRecipe(RecipeList recipes, int recipeIndex) {
+ recipes.viewRecipe(recipeIndex);
+ }
+
+ public static void printFilteredRecipes(RecipeList filteredRecipes, String ingredientName) {
+ System.out.println("Here are the recipes containing ingredient " + ingredientName + ":");
+ if (filteredRecipes.isEmpty()) {
+ System.out.println("---NO RECIPES contain the ingredient---");
+ } else {
+ filteredRecipes.listRecipeTitles();
+ }
+ drawDivider();
+ }
+
+ /**
+ * Message that shows successful edit of recipe name
+ *
+ * @param oldName The name of recipe before edit.
+ * @param newName The name of recipe after edit.
+ */
+ public static void printEditRecipeNameSuccess (String oldName, String newName) {
+ System.out.println("You have successfully edited the recipe name from: " + oldName +
+ " to: " + newName);
+ drawDivider();
+ }
+
+ /**
+ * Message that shows successful edit of recipe steps
+ *
+ * @param oldSteps The step of recipe before edit.
+ * @param newSteps The step of recipe after edit.
+ */
+ public static void printEditRecipeStepSuccess (String oldSteps, String newSteps) {
+ System.out.println("You have successfully edited the recipe steps\nfrom: " + oldSteps +
+ "\nto: " + newSteps);
+ drawDivider();
+ }
+
+ /**
+ * Prints out IO exception message.
+ *
+ * @param exception IO exception.
+ */
+ public static void handleIOException(IOException exception) {
+ System.out.println("Unable to save data");
+ System.out.println(exception.getMessage());
+ }
+
+ /**
+ * Prints out recent duplicated recipe.
+ *
+ * @param recipeTitle The recipe title.
+ */
+ public static void printDuplicatedRecipe(String recipeTitle) {
+ drawDivider();
+ System.out.println(recipeTitle + " has been duplicated.");
+ drawDivider();
+ }
+
+ public static String readUserInput() {
+ Scanner in = new Scanner(System.in);
+ return in.nextLine();
+ }
+
+ public static void printPlanCommandIngredients(
+ IngredientList allIngredientsNeeded, IngredientList missingIngredients, RecipeList recipes) {
+ printAllRecipes(recipes);
+ System.out.println("Here is a list of all ingredients you need: ");
+ allIngredientsNeeded.listIngredients();
+ drawDivider();
+ System.out.println("Here are the ingredients you need to buy because your inventory is running low: ");
+ missingIngredients.listIngredients();
+ }
+
+ public static void printValidIngredientExample() {
+ System.out.println("Invalid Ingredient! Example of valid ingredient: i/Chicken,1,kg");
+ }
+
+ public static void printNegativeIngredientQuantity() {
+ System.out.println("You cannot add an ingredient with negative quantity.");
+ }
+
+ public static void printIngredientDoesNotExist(String name) {
+ System.out.println("You do not have any " + name + " to use.");
+ }
+
+ public static void printExecuteRecipeFail(String title) {
+ System.out.println("You are missing some ingredients to execute " + title +
+ "\nPlease use the [check] command to check what you are missing: \"check " + title +"\"");
+ drawDivider();
+ }
+
+ public static void printExecuteRecipeSuccess(String title) {
+ System.out.println("You have successfully executed " + title);
+ drawDivider();
+ }
+
+ /**
+ * Prints out all shortcuts in the list.
+ *
+ * @param shortcuts The shortcut list.
+ */
+ public static void printAllShortcuts(ShortcutList shortcuts) {
+ Ui.drawDivider();
+ System.out.println("Here's a list of your shortcuts!");
+ shortcuts.listShortcuts();
+ drawDivider();
+ }
+
+ /**
+ * Prints out recent added shortcut.
+ *
+ * @param shortcut A shortcut.
+ */
+ public static void printAddShortcutSuccess(Shortcut shortcut, IngredientUnit unit) {
+ Ui.drawDivider();
+ System.out.println("Shortcut to add " + shortcut.getQuantity() + unit.getValue() + " of '"
+ + shortcut.getIngredientName() + "' has been created!");
+ Ui.drawDivider();
+ }
+
+ /**
+ * Prints out deleted shortcut.
+ *
+ * @param ingredientName The name that shortcut refers to.
+ */
+ public static void printDeletedShortcut(String ingredientName) {
+ Ui.drawDivider();
+ System.out.println("Shortcut to add '" + ingredientName + "' has been deleted!");
+ Ui.drawDivider();
+ }
+
+ /**
+ * Prints out successful name edit on a shortcut.
+ *
+ * @param oldName The old shortcut name.
+ * @param newName The new shortcut name.
+ */
+ public static void printEditShortcutName(String oldName, String newName) {
+ Ui.drawDivider();
+ System.out.println("Shortcut to ingredient '" + oldName + "' has changed to ingredient '" + newName + "'.");
+ Ui.drawDivider();
+ }
+
+ /**
+ * Prints out successful quantity edit on a shortcut.
+ *
+ * @param oldQuantity The old shortcut quantity.
+ * @param newQuantity The new shortcut quantity.
+ */
+ public static void printEditShortcutQuantity(double oldQuantity, double newQuantity) {
+ Ui.drawDivider();
+ System.out.println("Shortcut's quantity has beed changed from " + oldQuantity + " to " + newQuantity + ".");
+ Ui.drawDivider();
+ }
+}
diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java
deleted file mode 100644
index 5c74e68d59..0000000000
--- a/src/main/java/seedu/duke/Duke.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package seedu.duke;
-
-import java.util.Scanner;
-
-public class Duke {
- /**
- * Main entry-point for the java.duke.Duke application.
- */
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- System.out.println("What is your name?");
-
- Scanner in = new Scanner(System.in);
- System.out.println("Hello " + in.nextLine());
- }
-}
diff --git a/src/test/data/ingredients.txt b/src/test/data/ingredients.txt
new file mode 100644
index 0000000000..e400194c44
--- /dev/null
+++ b/src/test/data/ingredients.txt
@@ -0,0 +1,3 @@
+bread | 2 | GRAM
+cheese | 10 | PIECE
+carrot | 5 | KILOGRAM
\ No newline at end of file
diff --git a/src/test/data/invalid_ingredients.txt b/src/test/data/invalid_ingredients.txt
new file mode 100644
index 0000000000..109137c6e4
--- /dev/null
+++ b/src/test/data/invalid_ingredients.txt
@@ -0,0 +1,9 @@
+ | 2 | GRAM
+cheese | 10 |
+bread | | GRAM
+lettuce | -1 | PIECE
+lettuce | dasfasfsaf | PIECE
+lettuce | 1.0 | asfasgsadsd
+carrot | 5 KILOGRAM
+carrot 5 | KILOGRAM
+lettuce | 0 | PIECE
\ No newline at end of file
diff --git a/src/test/data/invalid_recipes.txt b/src/test/data/invalid_recipes.txt
new file mode 100644
index 0000000000..6e7b94dfe2
--- /dev/null
+++ b/src/test/data/invalid_recipes.txt
@@ -0,0 +1,16 @@
+ || step1 | NIGHT_BEFORE | 50 || bread | 5 | KILOGRAM
+bread || || bread | 5 | KILOGRAM
+bread || step1 | NIGHT_BEFORE | 50 ||
+bread || | NIGHT_BEFORE | 50 || bread | 5 | KILOGRAM
+bread || step1 | | 50 || bread | 5 | KILOGRAM
+bread || step1 | NIGHT_BEFORE | || bread | 5 | KILOGRAM
+bread || step1 | NIGHT_BEFORE | 50 || | 5 | KILOGRAM
+bread || step1 | NIGHT_BEFORE | 50 || bread | | KILOGRAM
+bread || step1 | NIGHT_BEFORE | 50 || bread | 5 |
+bread || step1 | sfasvc asvsa | 50 || bread | 5 | KILOGRAM
+bread || step1 | NIGHT_BEFORE | -1 || bread | 5 | KILOGRAM
+bread || step1 | NIGHT_BEFORE | sfasfsaf || bread | 5 | KILOGRAM
+bread || step1 | NIGHT_BEFORE | 0 || bread | -1 | KILOGRAM
+bread || step1 | NIGHT_BEFORE | 0 || bread | 0 | KILOGRAM
+bread || step1 | NIGHT_BEFORE | 0 || bread | sfasfsaf | KILOGRAM
+bread || step1 | NIGHT_BEFORE | 0 || bread | 5 | sfgasdfsafasf
\ No newline at end of file
diff --git a/src/test/data/invalid_shortcuts.txt b/src/test/data/invalid_shortcuts.txt
new file mode 100644
index 0000000000..b4389d228f
--- /dev/null
+++ b/src/test/data/invalid_shortcuts.txt
@@ -0,0 +1,5 @@
+bread 1.0
+ | 0.1
+bread |
+bread | -1
+egg | 0
diff --git a/src/test/data/recipes.txt b/src/test/data/recipes.txt
new file mode 100644
index 0000000000..5d7baed2f1
--- /dev/null
+++ b/src/test/data/recipes.txt
@@ -0,0 +1 @@
+bread || step1 | NIGHT_BEFORE | 50 || bread | 5 | KILOGRAM
diff --git a/src/test/data/shortcuts.txt b/src/test/data/shortcuts.txt
new file mode 100644
index 0000000000..e56374d248
--- /dev/null
+++ b/src/test/data/shortcuts.txt
@@ -0,0 +1,2 @@
+bread | 1.0
+egg | 0.1
\ No newline at end of file
diff --git a/src/test/java/essenmakanan/command/AddIngredientCommandTest.java b/src/test/java/essenmakanan/command/AddIngredientCommandTest.java
new file mode 100644
index 0000000000..f619d9b9e6
--- /dev/null
+++ b/src/test/java/essenmakanan/command/AddIngredientCommandTest.java
@@ -0,0 +1,59 @@
+package essenmakanan.command;
+
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class AddIngredientCommandTest {
+
+ private IngredientList ingredients;
+ private AddIngredientCommand addIngredientCommand;
+
+ @BeforeEach
+ public void setUp() {
+ ingredients = new IngredientList();
+ Ingredient tomato = new Ingredient("tomato", 1.0, IngredientUnit.PIECE);
+ ingredients.addIngredient(tomato);
+ }
+
+ @Test
+ public void addExistingIngredient_increaseQuantity_quantityIncreased(){
+
+ String userInput = "i/tomato,2,pc";
+ addIngredientCommand = new AddIngredientCommand(userInput, ingredients);
+ addIngredientCommand.executeCommand();
+
+ Ingredient ingredient = ingredients.getIngredient(0);
+ assertEquals("tomato", ingredient.getName());
+ assertEquals(3.0, ingredient.getQuantity());
+ assertEquals(IngredientUnit.PIECE, ingredient.getUnit());
+ }
+
+ @Test
+ public void addExistingIngredient_multipleIncreaseQuantity_quantityIncreased(){
+
+ String userInput = "i/tomato,2,pc i/tomato,3,pc";
+ addIngredientCommand = new AddIngredientCommand(userInput, ingredients);
+ addIngredientCommand.executeCommand();
+
+ Ingredient ingredient = ingredients.getIngredient(0);
+ assertEquals("tomato", ingredient.getName());
+ assertEquals(6.0, ingredient.getQuantity());
+ assertEquals(IngredientUnit.PIECE, ingredient.getUnit());
+ }
+
+ @Test
+ public void addIngredient_negativeQuantity_nothingCreated(){
+
+ String userInput = "i/cheese,-2,pc";
+ addIngredientCommand = new AddIngredientCommand(userInput, ingredients);
+ addIngredientCommand.executeCommand();
+
+ // nothing should happen
+ assertEquals(1, ingredients.getSize());
+ }
+}
diff --git a/src/test/java/essenmakanan/command/AddRecipeCommandTest.java b/src/test/java/essenmakanan/command/AddRecipeCommandTest.java
new file mode 100644
index 0000000000..e543137af1
--- /dev/null
+++ b/src/test/java/essenmakanan/command/AddRecipeCommandTest.java
@@ -0,0 +1,240 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.ingredient.IngredientUnit;
+import essenmakanan.recipe.RecipeIngredientList;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.recipe.RecipeStepList;
+import essenmakanan.recipe.Step;
+import essenmakanan.recipe.Tag;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class AddRecipeCommandTest {
+
+ private AddRecipeCommand addRecipeCommand;
+
+ private RecipeList recipeList;
+
+ private RecipeStepList recipeStepList;
+ @BeforeEach
+ public void setUp() {
+ recipeList = new RecipeList();
+ }
+
+ @Test
+ public void addRecipeCommand_stepAndIngredient_recipeCreated() {
+ String userInput = "r/bread s/step 1 instructions i/eggs,2,pc";
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+ addRecipeCommand.executeCommand();
+ assertEquals("bread", recipeList.getRecipe(0).getTitle());
+ String step1 = recipeList.getRecipe(0).getRecipeSteps().getStepByIndex(0).getDescription();
+ assertEquals(Step.convertToStepIdTemplate("step 1 instructions",1), step1);
+
+ // check ingredients
+ RecipeIngredientList recipeIngredients = recipeList.getRecipe(0).getRecipeIngredients();
+ assertEquals("eggs", recipeIngredients.getIngredients().get(0).getName());
+ assertEquals(2.0, recipeIngredients.getIngredients().get(0).getQuantity());
+ assertEquals(IngredientUnit.PIECE, recipeIngredients.getIngredients().get(0).getUnit());
+ }
+ @Test
+ public void addWithTitleStepsTags_stepIngredientAndTag_validInput() {
+ String userInput = "r/bread t/1 s/buy ingredients i/vegetable oil,100,ml";
+
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+ addRecipeCommand.executeCommand();
+ recipeStepList = recipeList.getRecipes().get(0).getRecipeSteps();
+ Step step1 = recipeStepList.getStepByIndex(0);
+
+ // check step is correct
+
+ assertEquals(Step.convertToStepIdTemplate("buy ingredients",1), step1.getDescription());
+
+ // check tag is correct
+ assertEquals(Tag.NIGHT_BEFORE, step1.getTag());
+
+ // check ingredient is correct
+ RecipeIngredientList recipeIngredients = recipeList.getRecipe(0).getRecipeIngredients();
+ assertEquals("vegetable oil", recipeIngredients.getIngredients().get(0).getName());
+ assertEquals(100,0, recipeIngredients.getIngredients().get(0).getQuantity());
+ assertEquals(IngredientUnit.MILLILITER, recipeIngredients.getIngredients().get(0).getUnit());
+
+ }
+ @Test
+ public void addRecipeCommand_multipleStepsIngredientsAndTags_validInput() {
+ String userInput = "r/bread t/1 s/buy ingredients s/store ingredients " +
+ "t/2 s/wash the ingredients s/cut the ingredients t/3 s/marinade t/4 s/cook " +
+ "i/vegetable oil,1,l i/eggs,2,pc";
+
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+ addRecipeCommand.executeCommand();
+
+ recipeStepList = recipeList.getRecipes().get(0).getRecipeSteps();
+ Step step1 = recipeStepList.getStepByIndex(0);
+ Step step2 = recipeStepList.getStepByIndex(1);
+ Step step3 = recipeStepList.getStepByIndex(2);
+ Step step4 = recipeStepList.getStepByIndex(3);
+ Step step5 = recipeStepList.getStepByIndex(4);
+ Step step6 = recipeStepList.getStepByIndex(5);
+
+ assertEquals(Step.convertToStepIdTemplate("buy ingredients",1), step1.getDescription());
+ assertEquals(Step.convertToStepIdTemplate("store ingredients",2), step2.getDescription());
+ assertEquals(Step.convertToStepIdTemplate("wash the ingredients",3), step3.getDescription());
+ assertEquals(Step.convertToStepIdTemplate("cut the ingredients",4), step4.getDescription());
+ assertEquals(Step.convertToStepIdTemplate("marinade",5), step5.getDescription());
+ assertEquals(Step.convertToStepIdTemplate("cook",6), step6.getDescription());
+
+ assertEquals(Tag.NIGHT_BEFORE, step1.getTag());
+ assertEquals(Tag.NIGHT_BEFORE, step2.getTag());
+ assertEquals(Tag.MORNING_OF_COOKING, step3.getTag());
+ assertEquals(Tag.MORNING_OF_COOKING, step4.getTag());
+ assertEquals(Tag.MORE_THAN_ONE_DAY, step5.getTag());
+ assertEquals(Tag.ACTUAL_COOKING, step6.getTag());
+
+ // check ingredients are correct
+ RecipeIngredientList recipeIngredients = recipeList.getRecipe(0).getRecipeIngredients();
+ assertEquals("vegetable oil", recipeIngredients.getIngredients().get(0).getName());
+ assertEquals(1,0, recipeIngredients.getIngredients().get(0).getQuantity());
+ assertEquals(IngredientUnit.LITER, recipeIngredients.getIngredients().get(0).getUnit());
+
+ assertEquals("eggs", recipeIngredients.getIngredients().get(1).getName());
+ assertEquals(2.0, recipeIngredients.getIngredients().get(1).getQuantity());
+ assertEquals(IngredientUnit.PIECE, recipeIngredients.getIngredients().get(1).getUnit());
+ }
+
+
+ @Test
+ public void addWithTitleStepsTags_validInput_duration() {
+ String userInput = "r/bread t/1 s/buy ingredients d/30mins " +
+ "t/2 s/wash the ingredients d/20mins t/4 s/cook d/1.6h i/egg,2,pc";
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+ addRecipeCommand.executeCommand();
+
+ recipeStepList = recipeList.getRecipes().get(0).getRecipeSteps();
+ Step step1 = recipeStepList.getStepByIndex(0);
+ Step step2 = recipeStepList.getStepByIndex(1);
+ Step step3 = recipeStepList.getStepByIndex(2);
+
+ assertEquals(Step.convertToStepIdTemplate("buy ingredients",1), step1.getDescription());
+ assertEquals(Step.convertToStepIdTemplate("wash the ingredients",2), step2.getDescription());
+ assertEquals(Step.convertToStepIdTemplate("cook",3), step3.getDescription());
+
+ assertEquals(30, step1.getEstimatedDuration());
+ assertEquals(20, step2.getEstimatedDuration());
+ assertEquals(96, step3.getEstimatedDuration());
+ }
+
+ @Test
+ public void addRecipeWithInvalidInput_invalidIngredient_errorThrown() {
+ String userInput = "r/bread s/step 1 i/invalidIngredient";
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+
+ assertThrows(EssenFormatException.class, () -> {
+ addRecipeCommand.addValidRecipe();
+ });
+ }
+
+ @Test
+ public void addRecipeWithInvalidInput_missingTitle_errorThrown() {
+ String userInput = "r/ s/step1 i/egg,2,pc";
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+
+ assertThrows(EssenFormatException.class, () -> {
+ addRecipeCommand.addValidRecipe();
+ });
+ }
+
+
+ @Test
+ public void addRecipe_emptySteps_exceptionThrown() {
+ String userInput = "r/bread s/ i/egg,2,pc";
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+
+ assertThrows(EssenFormatException.class, () -> {
+ addRecipeCommand.addValidRecipe();
+ });
+ }
+
+ @Test
+ public void addValidCommand_stepAndIngredientNotInOrder_recipeCreated() {
+ String userInput = "r/bread i/eggs,2,pc s/step 1 instructions s/step 2 instructions";
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+ addRecipeCommand.executeCommand();
+ assertEquals("bread", recipeList.getRecipe(0).getTitle());
+ String step1 = recipeList.getRecipe(0).getRecipeSteps().getStepByIndex(0).getDescription();
+ String step2 = recipeList.getRecipe(0).getRecipeSteps().getStepByIndex(1).getDescription();
+
+ assertEquals(Step.convertToStepIdTemplate("step 1 instructions",1), step1);
+ assertEquals(Step.convertToStepIdTemplate("step 2 instructions",2), step2);
+
+ // check ingredients
+ RecipeIngredientList recipeIngredients = recipeList.getRecipe(0).getRecipeIngredients();
+ assertEquals("eggs", recipeIngredients.getIngredients().get(0).getName());
+ assertEquals(2.0, recipeIngredients.getIngredients().get(0).getQuantity());
+ assertEquals(IngredientUnit.PIECE, recipeIngredients.getIngredients().get(0).getUnit());
+ }
+
+ @Test
+ public void addRecipeCommand_multipleTitle_formatException() {
+ String userInput = "r/bread r/toast s/step 1 i/egg,2,pc";
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+
+ assertThrows(EssenFormatException.class, () -> {
+ addRecipeCommand.addValidRecipe();
+ });
+ }
+
+ @Test
+ public void addRecipe_stepsWithMultipleDuration_exceptionThrown() {
+ String userInput = "r/bread s/wash eggs d/1min d/1min i/egg,2,pc";
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+
+ assertThrows(EssenFormatException.class, () -> {
+ addRecipeCommand.addValidRecipe();
+ });
+ }
+
+ @Test
+ public void addRecipe_flagsTooClose_exceptionThrown() {
+ String userInput = "r/t/1 s/STEP1 s/STEP2 d/30h t/2 s/STEP3 i/bread,2,kg";
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+
+ assertThrows(EssenFormatException.class, () -> {
+ addRecipeCommand.addValidRecipe();
+ });
+ }
+
+ @Test
+ public void addRecipe_titleBlankSpaces_exceptionThrown() {
+ String userInput = "r/ t/1 s/STEP1 s/STEP2 d/30h t/2 s/STEP3 i/bread,2,kg";
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+
+ assertThrows(EssenFormatException.class, () -> {
+ addRecipeCommand.addValidRecipe();
+ });
+ }
+
+
+ @Test
+ public void addRecipe_stepBlankSpaces_exceptionThrown() {
+ String userInput = "r/toast s/ i/bread,2,kg";
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+
+ assertThrows(EssenFormatException.class, () -> {
+ addRecipeCommand.addValidRecipe();
+ });
+ }
+
+ @Test
+ public void addRecipe_ingredientBlankSpaces_exceptionThrown() {
+ String userInput = "r/toast s/step1 i/ ";
+ addRecipeCommand = new AddRecipeCommand(userInput, recipeList);
+
+ assertThrows(EssenFormatException.class, () -> {
+ addRecipeCommand.addValidRecipe();
+ });
+ }
+}
diff --git a/src/test/java/essenmakanan/command/CheckRecipeCommandTest.java b/src/test/java/essenmakanan/command/CheckRecipeCommandTest.java
new file mode 100644
index 0000000000..da72694f5b
--- /dev/null
+++ b/src/test/java/essenmakanan/command/CheckRecipeCommandTest.java
@@ -0,0 +1,76 @@
+package essenmakanan.command;
+
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeIngredientList;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.recipe.RecipeStepList;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class CheckRecipeCommandTest {
+ private RecipeList recipes;
+ private Recipe recipe0;
+ private Recipe recipe1;
+ private IngredientList ingredients;
+
+ @BeforeEach
+ public void setup() {
+ // Recipe creation
+ String[] steps = {"step1", "step2"};
+ RecipeStepList recipeStepList = new RecipeStepList(steps);
+
+ recipes = new RecipeList();
+
+ // Recipe for Fluffy Bread
+ String ingredientString1 = "flour, 200, g";
+ String ingredientString2 = "egg, 2, pc";
+ String ingredientString3 = "yeast, 50, g";
+ String[] ingredientList1 = {ingredientString1, ingredientString2, ingredientString3};
+ RecipeIngredientList recipeIngredientList1 = new RecipeIngredientList(ingredientList1);
+ recipe0 = new Recipe("Fluffy Bread", recipeStepList, recipeIngredientList1);
+
+ // Recipe for Meatball Noodles
+ String ingredientString4 = "noodles, 100, g";
+ String ingredientString5 = "egg, 1, pc";
+ String ingredientString6 = "vegetable, 4, pc";
+ String[] ingredientList2 = {ingredientString4, ingredientString5, ingredientString6};
+ RecipeIngredientList recipeIngredientList2 = new RecipeIngredientList(ingredientList2);
+ recipe1 = new Recipe("Meatball Noodles", recipeStepList, recipeIngredientList2);
+
+ // Add both recipes to recipes
+ recipes.addRecipe(recipe0);
+ recipes.addRecipe(recipe1);
+
+ // For ingredients in our inventory
+ ingredients = new IngredientList();
+ Ingredient ingredient1 = new Ingredient("flour", 100.0, IngredientUnit.GRAM);
+ Ingredient ingredient2 = new Ingredient("egg", 1.0, IngredientUnit.PIECE);
+ ingredients.addIngredient(ingredient1);
+ ingredients.addIngredient(ingredient2);
+ }
+
+ @Test
+ public void startRecipe_validIngredients_comparesCorrectly() {
+ CheckRecipeCommand command = new CheckRecipeCommand("Fluffy Bread", recipes, ingredients);
+ command.executeCommand();
+
+ // Check if insufficientIngredients list is correct
+ IngredientList insufficientIngredients = new IngredientList();
+ double ingredientQty1 = 100;
+ double ingredientQty2 = 1;
+ insufficientIngredients.addIngredient(new Ingredient("flour",
+ ingredientQty1, IngredientUnit.GRAM));
+ insufficientIngredients.addIngredient(new Ingredient("egg",
+ ingredientQty2, IngredientUnit.PIECE));
+ assert command.getInsufficientIngredients().equals(insufficientIngredients)
+ : "The insufficient quantity was not detected";
+
+ // Check if missingIngredient list is correct
+ IngredientList missingIngredients = new IngredientList();
+ missingIngredients.addIngredient(new Ingredient("yeast", 50.0, IngredientUnit.GRAM));
+ assert command.getMissingIngredients().equals(missingIngredients) : "The missing quantity was not detected";
+ }
+}
diff --git a/src/test/java/essenmakanan/command/DeleteIngredientCommandTest.java b/src/test/java/essenmakanan/command/DeleteIngredientCommandTest.java
new file mode 100644
index 0000000000..a9a8cdf9d9
--- /dev/null
+++ b/src/test/java/essenmakanan/command/DeleteIngredientCommandTest.java
@@ -0,0 +1,72 @@
+package essenmakanan.command;
+
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class DeleteIngredientCommandTest {
+ private IngredientList ingredients;
+ private Ingredient ingredient0;
+ private Ingredient ingredient1;
+
+ @BeforeEach
+ public void setup() {
+ ingredients = new IngredientList();
+
+ ingredient0 = new Ingredient("banana", 1.0, IngredientUnit.PIECE);
+ ingredient1 = new Ingredient("apple", 2.0, IngredientUnit.PIECE);
+
+ ingredients.addIngredient(ingredient0);
+ ingredients.addIngredient(ingredient1);
+ }
+
+ @Test
+ public void deleteIngredient_validIngredientId_deleteCorrectly() {
+ Command deleteCommand = new DeleteIngredientCommand(ingredients, "2");
+ deleteCommand.executeCommand();
+ assert ingredients.getIngredient(0) == ingredient0 : "Wrong ingredient was removed";
+ assert !ingredients.exist(1) : "Ingredient was not removed";
+ }
+
+ @Test
+ public void deleteIngredient_validIngredientName_deleteCorrectly() {
+ Command deleteCommand = new DeleteIngredientCommand(ingredients, "apple");
+ deleteCommand.executeCommand();
+ assert ingredients.getIngredient(0) == ingredient0 : "Wrong ingredient was removed";
+ assert !ingredients.exist(1) : "Ingredient was not removed";
+ }
+
+ @Test
+ public void deleteIngredient_extraIngredient_noDeletion() {
+ Command deleteCommand = new DeleteIngredientCommand(ingredients, "1 3");
+ deleteCommand.executeCommand();
+ assert ingredients.getIngredient(0) == ingredient0 : "Ingredient was not supposed to be removed";
+ assert ingredients.getIngredient(1) == ingredient1 : "Ingredient was not supposed to be removed";
+ }
+
+ @Test
+ public void deleteIngredient_invalidIngredientName_noDeletion() {
+ Command deleteCommand = new DeleteIngredientCommand(ingredients, "strawberry");
+ deleteCommand.executeCommand();
+ assert ingredients.getIngredient(0) == ingredient0 : "Ingredient was not supposed to be removed";
+ assert ingredients.getIngredient(1) == ingredient1 : "Ingredient was not supposed to be removed";
+ }
+
+ @Test
+ public void deleteIngredient_ingredientIdIsZero_noDeletion() {
+ Command deleteCommand = new DeleteIngredientCommand(ingredients, "0");
+ deleteCommand.executeCommand();
+ assert ingredients.getIngredient(0) == ingredient0 : "Ingredient was not supposed to be removed";
+ assert ingredients.getIngredient(1) == ingredient1 : "Ingredient was not supposed to be removed";
+ }
+
+ @Test
+ public void deleteIngredient_invalidIngredientId_noDeletion() {
+ Command deleteCommand = new DeleteIngredientCommand(ingredients, "4");
+ deleteCommand.executeCommand();
+ assert ingredients.getIngredient(0) == ingredient0 : "Ingredient was not supposed to be removed";
+ assert ingredients.getIngredient(1) == ingredient1 : "Ingredient was not supposed to be removed";
+ }
+}
diff --git a/src/test/java/essenmakanan/command/DeleteRecipeCommandTest.java b/src/test/java/essenmakanan/command/DeleteRecipeCommandTest.java
new file mode 100644
index 0000000000..91a194f5bf
--- /dev/null
+++ b/src/test/java/essenmakanan/command/DeleteRecipeCommandTest.java
@@ -0,0 +1,76 @@
+package essenmakanan.command;
+
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeList;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+
+public class DeleteRecipeCommandTest {
+ private RecipeList recipes;
+ private Recipe recipe0;
+ private Recipe recipe1;
+
+ @BeforeEach
+ public void setup() {
+ recipes = new RecipeList();
+
+ String[] steps = {"step1", "step2"};
+ String[] ingredients = {"egg,2,pc"};
+
+ recipe0 = new Recipe("Bake a cake", steps, ingredients);
+ recipe1 = new Recipe("Fry an egg", steps, ingredients);
+
+ recipes.addRecipe(recipe0);
+ recipes.addRecipe(recipe1);
+ }
+
+ @Test
+ public void deleteRecipe_validRecipeId_deleteCorrectly() {
+ Command deleteCommand = new DeleteRecipeCommand(recipes, "r/2");
+ deleteCommand.executeCommand();
+ assert recipes.getRecipe(0) == recipe0 : "Wrong recipe was removed";
+ assert !recipes.recipeExist(1) : "Recipe was not removed";
+ }
+
+ @Test
+ public void deleteRecipe_validRecipeTitle_deleteCorrectly() {
+ Command deleteCommand = new DeleteRecipeCommand(recipes, "r/Fry an egg");
+ deleteCommand.executeCommand();
+ assert recipes.getRecipe(0) == recipe0 : "Wrong recipe was removed";
+ assert !recipes.recipeExist(1) : "Recipe was not removed";
+ }
+
+
+ @Test
+ public void deleteRecipe_extraRecipe_noDeletion() {
+ Command deleteCommand = new DeleteRecipeCommand(recipes, "r/1 2");
+ deleteCommand.executeCommand();
+ assert recipes.getRecipe(0) == recipe0 : "Recipe was not supposed to be removed";
+ assert recipes.getRecipe(1) == recipe1 : "Recipe was not supposed to be removed";
+ }
+
+ @Test
+ public void deleteRecipe_invalidRecipeName_noDeletion() {
+ Command deleteCommand = new DeleteRecipeCommand(recipes, "r/Make strawberry fondue");
+ deleteCommand.executeCommand();
+ assert recipes.getRecipe(0) == recipe0 : "Recipe was not supposed to be removed";
+ assert recipes.getRecipe(1) == recipe1 : "Recipe was not supposed to be removed";
+ }
+
+ @Test
+ public void deleteRecipe_recipeIdIsZero_noDeletion() {
+ Command deleteCommand = new DeleteRecipeCommand(recipes, "r/0");
+ deleteCommand.executeCommand();
+ assert recipes.getRecipe(0) == recipe0 : "Recipe was not supposed to be removed";
+ assert recipes.getRecipe(1) == recipe1 : "Recipe was not supposed to be removed";
+ }
+
+ @Test
+ public void deleteRecipe_invalidRecipeId_noDeletion() {
+ Command deleteCommand = new DeleteRecipeCommand(recipes, "r/4");
+ deleteCommand.executeCommand();
+ assert recipes.getRecipe(0) == recipe0 : "Recipe was not supposed to be removed";
+ assert recipes.getRecipe(1) == recipe1 : "Recipe was not supposed to be removed";
+ }
+}
diff --git a/src/test/java/essenmakanan/command/DuplicateRecipeCommandTest.java b/src/test/java/essenmakanan/command/DuplicateRecipeCommandTest.java
new file mode 100644
index 0000000000..9e6959d079
--- /dev/null
+++ b/src/test/java/essenmakanan/command/DuplicateRecipeCommandTest.java
@@ -0,0 +1,91 @@
+package essenmakanan.command;
+
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeList;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Executes tests related to duplicate recipe command.
+ */
+public class DuplicateRecipeCommandTest {
+ private RecipeList recipes;
+ private Recipe recipe;
+ private Recipe duplicatedRecipe;
+
+ /**
+ * Sets up attributes before each test.
+ */
+ @BeforeEach
+ public void setup() {
+ recipes = new RecipeList();
+ String recipeTitle = "sandwich";
+ String[] ingredients = {"i/bread,2,pc", "i/meat,1,pc"};
+ String[] steps = {"step1", "step2"};
+ recipe = new Recipe(recipeTitle, steps, ingredients);
+ recipes.addRecipe(recipe);
+ }
+
+ /**
+ * Execute test for duplicating a recipe with index.
+ */
+ @Test
+ public void duplicateRecipe_numericalIndex_expectSimilarRecipe() {
+ DuplicateRecipeCommand command = new DuplicateRecipeCommand(recipes, "1");
+ command.executeCommand();
+
+ duplicatedRecipe = recipes.getRecipe(1);
+
+ assertEquals(recipe.getTitle() + " (copy)", duplicatedRecipe.getTitle());
+
+ assertEquals(recipe.getRecipeStepByIndex(0).getDescription( )
+ , duplicatedRecipe.getRecipeStepByIndex(0).getDescription());
+ assertEquals(recipe.getRecipeStepByIndex(1).getDescription()
+ , duplicatedRecipe.getRecipeStepByIndex(1).getDescription());
+
+ ArrayList recipeIngredients = recipe.getRecipeIngredients().getIngredients();
+ ArrayList duplicatedIngredients = duplicatedRecipe.getRecipeIngredients().getIngredients();
+
+ assertEquals(recipeIngredients.get(0).getName(), duplicatedIngredients.get(0).getName());
+ assertEquals(recipeIngredients.get(0).getQuantity(), duplicatedIngredients.get(0).getQuantity());
+ assertEquals(recipeIngredients.get(0).getUnit(), duplicatedIngredients.get(0).getUnit());
+
+ assertEquals(recipeIngredients.get(1).getName(), duplicatedIngredients.get(1).getName());
+ assertEquals(recipeIngredients.get(1).getQuantity(), duplicatedIngredients.get(1).getQuantity());
+ assertEquals(recipeIngredients.get(1).getUnit(), duplicatedIngredients.get(1).getUnit());
+ }
+
+ /**
+ * Execute test for duplicating a recipe with recipe name.
+ */
+ @Test
+ public void duplicateRecipe_recipeName_expectSimilarRecipe() {
+ DuplicateRecipeCommand command = new DuplicateRecipeCommand(recipes, "sandwich");
+ command.executeCommand();
+
+ duplicatedRecipe = recipes.getRecipe(1);
+
+ assertEquals(recipe.getTitle() + " (copy)", duplicatedRecipe.getTitle());
+
+ assertEquals(recipe.getRecipeStepByIndex(0).getDescription()
+ , duplicatedRecipe.getRecipeStepByIndex(0).getDescription());
+ assertEquals(recipe.getRecipeStepByIndex(1).getDescription()
+ , duplicatedRecipe.getRecipeStepByIndex(1).getDescription());
+
+ ArrayList recipeIngredients = recipe.getRecipeIngredients().getIngredients();
+ ArrayList duplicatedIngredients = duplicatedRecipe.getRecipeIngredients().getIngredients();
+
+ assertEquals(recipeIngredients.get(0).getName(), duplicatedIngredients.get(0).getName());
+ assertEquals(recipeIngredients.get(0).getQuantity(), duplicatedIngredients.get(0).getQuantity());
+ assertEquals(recipeIngredients.get(0).getUnit(), duplicatedIngredients.get(0).getUnit());
+
+ assertEquals(recipeIngredients.get(1).getName(), duplicatedIngredients.get(1).getName());
+ assertEquals(recipeIngredients.get(1).getQuantity(), duplicatedIngredients.get(1).getQuantity());
+ assertEquals(recipeIngredients.get(1).getUnit(), duplicatedIngredients.get(1).getUnit());
+ }
+}
diff --git a/src/test/java/essenmakanan/command/EditRecipeCommandTest.java b/src/test/java/essenmakanan/command/EditRecipeCommandTest.java
new file mode 100644
index 0000000000..1b57640ac8
--- /dev/null
+++ b/src/test/java/essenmakanan/command/EditRecipeCommandTest.java
@@ -0,0 +1,126 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenInvalidEditException;
+import essenmakanan.exception.EssenNullInputException;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.recipe.Step;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class EditRecipeCommandTest {
+ private EditRecipeCommand editRecipeCommand;
+ private RecipeList recipes;
+ private Recipe recipeToEdit;
+
+ @BeforeEach
+ public void setUp() {
+ this.recipes = new RecipeList();
+
+ // create recipeStub s/{"step1", "step2"} i/{"i/flour,200,g", "i/egg,2,pc"};
+ recipeToEdit = Recipe.createRecipeStub("white bread"); // flour: 200g egg: 2pc
+ }
+
+ @Test
+ public void editRecipe_validNameEdit_namedEdited() {
+
+ recipes.addRecipe(recipeToEdit);
+
+ String userInput = "r/white bread n/bread";
+ editRecipeCommand = new EditRecipeCommand(userInput, recipes);
+ editRecipeCommand.executeCommand();
+
+ assertEquals("bread", recipes.getRecipe(0).getTitle());
+ }
+
+ @Test
+ public void editRecipe_validStepEdit_namedEdited() {
+ recipes.addRecipe(recipeToEdit);
+
+ String userInput = "r/white bread s/1,new step 1";
+ editRecipeCommand = new EditRecipeCommand(userInput, recipes);
+ editRecipeCommand.executeCommand();
+
+ assertEquals(Step.convertToStepIdTemplate("new step 1",1),
+ recipes.getRecipe(0).getRecipeStepByIndex(0).getDescription());
+ }
+
+ @Test
+ public void editRecipe_validNameStepEdit_namedEdited() {
+ recipes.addRecipe(recipeToEdit);
+
+ String userInput = "r/white bread n/bread s/1,new step 1 s/2,new step 2";
+ editRecipeCommand = new EditRecipeCommand(userInput, recipes);
+ editRecipeCommand.executeCommand();
+
+ assertEquals("bread", recipes.getRecipe(0).getTitle());
+ assertEquals(Step.convertToStepIdTemplate("new step 1",1),
+ recipes.getRecipe(0).getRecipeStepByIndex(0).getDescription());
+ assertEquals(Step.convertToStepIdTemplate("new step 2",2),
+ recipes.getRecipe(0).getRecipeStepByIndex(1).getDescription());
+ }
+
+ @Test
+ public void editRecipe_jumbledNameStepEdit_namedEdited() {
+ recipes.addRecipe(recipeToEdit);
+
+ String userInput = "r/white bread s/1,new step 1 n/bread s/2,new step 2";
+ editRecipeCommand = new EditRecipeCommand(userInput, recipes);
+ editRecipeCommand.executeCommand();
+
+ assertEquals("bread", recipes.getRecipe(0).getTitle());
+ assertEquals(Step.convertToStepIdTemplate("new step 1",1),
+ recipes.getRecipe(0).getRecipeStepByIndex(0).getDescription());
+ assertEquals(Step.convertToStepIdTemplate("new step 2",2),
+ recipes.getRecipe(0).getRecipeStepByIndex(1).getDescription());
+ }
+
+ @Test
+ public void editRecipe_missingTitle_exceptionThrown() {
+ Recipe recipeToEdit = Recipe.createRecipeStub("white bread"); // flour: 200g egg: 2pc
+ recipes.addRecipe(recipeToEdit);
+
+ String userInput = "r/ n/newName";
+ editRecipeCommand = new EditRecipeCommand(userInput, recipes);
+ assertThrows(EssenNullInputException.class, () -> {
+ editRecipeCommand.getRecipeTitle();
+ });
+
+ // case 2 of missing title
+ userInput = "r/n/newName";
+ editRecipeCommand = new EditRecipeCommand(userInput, recipes);
+ assertThrows(EssenNullInputException.class, () -> {
+ editRecipeCommand.getRecipeTitle();
+ });
+ }
+
+ @Test
+ public void editRecipe_missingEditDetails_exceptionThrown() {
+ Recipe recipeToEdit = Recipe.createRecipeStub("white bread"); // flour: 200g egg: 2pc
+ recipes.addRecipe(recipeToEdit);
+ String userInput = "r/white bread n/";
+ editRecipeCommand = new EditRecipeCommand(userInput, recipes);
+
+ assertThrows(EssenInvalidEditException.class, () -> {
+ editRecipeCommand.getAttributesToEdit();
+ });
+ }
+
+ @Test
+ public void editRecipe_invalidFlag_exceptionThrown() {
+ Recipe recipeToEdit = Recipe.createRecipeStub("white bread"); // flour: 200g egg: 2pc
+ recipes.addRecipe(recipeToEdit);
+ String userInput = "r/white bread j/new name";
+ editRecipeCommand = new EditRecipeCommand(userInput, recipes);
+
+ assertThrows(EssenInvalidEditException.class, () -> {
+ editRecipeCommand.getAttributesToEdit();
+ });
+ }
+
+
+
+}
diff --git a/src/test/java/essenmakanan/command/ExecuteRecipeCommandTest.java b/src/test/java/essenmakanan/command/ExecuteRecipeCommandTest.java
new file mode 100644
index 0000000000..093999c161
--- /dev/null
+++ b/src/test/java/essenmakanan/command/ExecuteRecipeCommandTest.java
@@ -0,0 +1,103 @@
+package essenmakanan.command;
+
+import essenmakanan.exception.EssenNullInputException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeList;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class ExecuteRecipeCommandTest {
+ private IngredientList ingredients;
+ private RecipeList recipes;
+ private ExecuteRecipeCommand executeRecipeCommand;
+
+ @BeforeEach
+ public void setUp() {
+ // setting up ingredient inventory
+ ingredients = new IngredientList();
+ Ingredient egg = new Ingredient("egg", 4.0, IngredientUnit.PIECE);
+ ingredients.addIngredient(egg);
+
+ // setting up recipe
+ recipes = new RecipeList();
+ String recipeTitle = "bread";
+ String[] recipeSteps = {"step1", "step2"};
+ String[] recipeIngredients = {"i/egg,2,pc"};
+ Recipe newRecipe = new Recipe(recipeTitle, recipeSteps, recipeIngredients);
+ recipes.addRecipe(newRecipe);
+ }
+
+ @Test
+ public void executeRecipeCommand_oneIngredient_recipeExecuted() {
+
+ String userInput = "bread";
+ executeRecipeCommand = new ExecuteRecipeCommand(ingredients, recipes, userInput);
+ executeRecipeCommand.executeCommand();
+
+ // check if ingredient decreased
+ assertEquals(2.0, ingredients.getIngredient(0).getQuantity());
+ }
+
+
+ @Test
+ public void executeRecipeCommand_noTitle_errorThrown() {
+ String userInput = "";
+ executeRecipeCommand = new ExecuteRecipeCommand(ingredients, recipes, userInput);
+ assertThrows(EssenNullInputException.class, () -> {
+ executeRecipeCommand.getRecipe();
+ });
+ }
+
+ @Test
+ public void executeRecipeCommand_multipleIngredient_recipeExecuted() {
+ // setting up recipe
+ recipes = new RecipeList();
+ String recipeTitle = "toast";
+ String[] recipeSteps = {"butter the bread", "bake"};
+ String[] recipeIngredients = {"i/egg,2,pc", "i/cheese,0.2,kg", "i/milk,400,ml" ,"i/sugar,100,g"};
+ Recipe newRecipe = new Recipe(recipeTitle, recipeSteps, recipeIngredients);
+ recipes.addRecipe(newRecipe);
+
+ // set up ingredient inventory
+ Ingredient cheese = new Ingredient("cheese", 2.0, IngredientUnit.KILOGRAM);
+ ingredients.addIngredient(cheese);
+ Ingredient milk = new Ingredient("milk", 400.0, IngredientUnit.MILLILITER);
+ ingredients.addIngredient(milk);
+ Ingredient sugar = new Ingredient("sugar", 150.0, IngredientUnit.GRAM);
+ ingredients.addIngredient(sugar);
+
+ String userInput = "toast";
+ executeRecipeCommand = new ExecuteRecipeCommand(ingredients, recipes, userInput);
+ executeRecipeCommand.executeCommand();
+
+ // check if ingredient decreased
+ assertEquals(2.0, ingredients.getIngredient(0).getQuantity());
+ assertEquals(1.8, ingredients.getIngredient(1).getQuantity());
+ assertEquals(0.0, ingredients.getIngredient(2).getQuantity());
+ assertEquals(50.0, ingredients.getIngredient(3).getQuantity());
+ }
+
+ @Test
+ public void executeRecipeCommand_insufficientIngredient_recipeExecuted() {
+ // setting up recipe
+ recipes = new RecipeList();
+ String recipeTitle = "bread";
+ String[] recipeSteps = {"step1", "step2"};
+ String[] recipeIngredients = {"i/egg,5,pc"};
+ Recipe newRecipe = new Recipe(recipeTitle, recipeSteps, recipeIngredients);
+ recipes.addRecipe(newRecipe);
+
+ String userInput = "bread";
+ executeRecipeCommand = new ExecuteRecipeCommand(ingredients, recipes, userInput);
+ executeRecipeCommand.executeCommand();
+
+ // ingredient qunatity should stay the same because insufficient
+ assertEquals(4.0, ingredients.getIngredient(0).getQuantity());
+ }
+}
diff --git a/src/test/java/essenmakanan/command/PlanRecipesCommandTest.java b/src/test/java/essenmakanan/command/PlanRecipesCommandTest.java
new file mode 100644
index 0000000000..692844fa7f
--- /dev/null
+++ b/src/test/java/essenmakanan/command/PlanRecipesCommandTest.java
@@ -0,0 +1,115 @@
+package essenmakanan.command;
+
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.ingredient.IngredientList;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class PlanRecipesCommandTest {
+ public RecipeList myRecipeList;
+ public IngredientList myIngredientInventory;
+ public Recipe myRecipe1;
+ public Recipe myRecipe2;
+
+ @BeforeEach
+ public void setup() {
+ // Add recipes to myRecipeList
+ String[] inputIngredients1 = {"flour,200,g", "egg,2,pc"};
+ String[] inputSteps1 = {"beat the egg", "add flour into bowl of egg", "mix them together"};
+ this.myRecipe1 = new Recipe("bread", inputSteps1, inputIngredients1);
+
+ String[] inputIngredients2 = {"noodles,100,g", "egg,1,pc", "vegetable,4,pc"};
+ String[] inputSteps2 = {"beat the egg", "add flour into bowl of egg", "mix them together"};
+ this.myRecipe2 = new Recipe("noodles", inputSteps2, inputIngredients2);
+
+ this.myRecipeList = new RecipeList();
+ this.myRecipeList.addRecipe(myRecipe1);
+ this.myRecipeList.addRecipe(myRecipe2);
+
+ // Add ingredients to myInventory
+ String[] ingredientsString = {"flour,100,g", "egg,1,pc", "Banana,10,pc", "noodles,100,g"};
+ this.myIngredientInventory = new IngredientList(ingredientsString);
+ }
+
+ @Test
+ public void planRecipeCommand_planRecipe1_correctOutput() {
+ PlanRecipesCommand command = new PlanRecipesCommand(myIngredientInventory, myRecipeList, "1 r/1");
+ command.executeCommand();
+
+ String[] allIngredientsNeededString = {"flour,200,g", "egg,2,pc"};
+ IngredientList allIngredientsNeeded = new IngredientList(allIngredientsNeededString);
+ assert command.getAllIngredientsNeeded().equals(allIngredientsNeeded) : "Failed to plan recipe 1";
+
+ String[] missingIngredientsString = {"flour,100,g", "egg,1,pc"};
+ IngredientList missingIngredients = new IngredientList(missingIngredientsString);
+ assert command.getMissingIngredients().equals(missingIngredients) : "Failed to plan recipe 1";
+ }
+
+ @Test
+ public void planRecipeCommand_planRecipe1and2_correctOutput() {
+ PlanRecipesCommand command = new PlanRecipesCommand(myIngredientInventory, myRecipeList, "2 r/1 r/2");
+ command.executeCommand();
+
+ String[] allIngredientsNeededString = {"flour,200,g",
+ "egg,2,pc", "noodles,100,g", "egg,1,pc", "vegetable,4,pc"};
+ IngredientList allIngredientsNeeded = new IngredientList(allIngredientsNeededString);
+ assert command.getAllIngredientsNeeded().equals(allIngredientsNeeded) : "Failed to plan recipe 1";
+
+ String[] missingIngredientsString = {"flour,100,g", "egg,1,pc", "vegetable,4,pc"};
+ IngredientList missingIngredients = new IngredientList(missingIngredientsString);
+ assert command.getMissingIngredients().equals(missingIngredients) : "Failed to plan recipe 1";
+ }
+
+ @Test
+ public void planRecipeCommand_extraWhiteSpaces_correctOutput() {
+ PlanRecipesCommand command = new PlanRecipesCommand(myIngredientInventory,
+ myRecipeList, "2 r/1 r/2");
+ command.executeCommand();
+
+ String[] allIngredientsNeededString = {"flour,200,g", "egg,2,pc",
+ "noodles,100,g", "egg,1,pc", "vegetable,4,pc"};
+ IngredientList allIngredientsNeeded = new IngredientList(allIngredientsNeededString);
+ assert command.getAllIngredientsNeeded().equals(allIngredientsNeeded) : "Failed to plan recipe 1";
+
+ String[] missingIngredientsString = {"flour,100,g", "egg,1,pc", "vegetable,4,pc"};
+ IngredientList missingIngredients = new IngredientList(missingIngredientsString);
+ assert command.getMissingIngredients().equals(missingIngredients) : "Failed to plan recipe 1";
+ }
+
+ @Test void planRecipeCommand_planInvalidRecipeId_noActionTaken() {
+ PlanRecipesCommand command = new PlanRecipesCommand(myIngredientInventory, myRecipeList, "2 r/1 r/3");
+ command.executeCommand();
+ IngredientList emptyIngredients = new IngredientList();
+
+ assert command.getAllIngredientsNeeded().equals(emptyIngredients) : "No action should be taken";
+ assert command.getMissingIngredients().equals(emptyIngredients) : "No action should be taken";
+ }
+
+ @Test void planRecipeCommand_planInvalidRecipeCount_noActionTaken() {
+ PlanRecipesCommand command = new PlanRecipesCommand(myIngredientInventory, myRecipeList, "5 r/1 r/3");
+ command.executeCommand();
+ IngredientList emptyIngredients = new IngredientList();
+
+ assert command.getAllIngredientsNeeded().equals(emptyIngredients) : "No action should be taken";
+ assert command.getMissingIngredients().equals(emptyIngredients) : "No action should be taken";
+ }
+
+ @Test void planRecipeCommand_stringFormattedRecipeCount_noActionTaken() {
+ PlanRecipesCommand command = new PlanRecipesCommand(myIngredientInventory, myRecipeList, "two r/1 r/3");
+ command.executeCommand();
+ IngredientList emptyIngredients = new IngredientList();
+
+ assert command.getAllIngredientsNeeded().equals(emptyIngredients) : "No action should be taken";
+ assert command.getMissingIngredients().equals(emptyIngredients) : "No action should be taken";
+ }
+
+ @Test void planRecipeCommand_stringFormattedRecipeId_noActionTaken() {
+ PlanRecipesCommand command = new PlanRecipesCommand(myIngredientInventory, myRecipeList, "1 r/noodles");
+ command.executeCommand();
+ IngredientList emptyIngredients = new IngredientList();
+
+ assert command.getAllIngredientsNeeded().equals(emptyIngredients) : "No action should be taken";
+ assert command.getMissingIngredients().equals(emptyIngredients) : "No action should be taken";
+ }
+}
diff --git a/src/test/java/essenmakanan/ingredient/EditIngredientTest.java b/src/test/java/essenmakanan/ingredient/EditIngredientTest.java
new file mode 100644
index 0000000000..a69e5dd538
--- /dev/null
+++ b/src/test/java/essenmakanan/ingredient/EditIngredientTest.java
@@ -0,0 +1,91 @@
+package essenmakanan.ingredient;
+
+import essenmakanan.exception.EssenFormatException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class EditIngredientTest {
+
+ private IngredientList ingredients;
+ private Ingredient ingredientToEdit;
+ @BeforeEach
+ public void setUp() {
+ ingredients = new IngredientList();
+ ingredientToEdit = new Ingredient("bread",2.0,IngredientUnit.PIECE);
+ }
+ @Test
+ public void editIngredientName_validInput_editSuccess() {
+
+ ingredients.addIngredient(ingredientToEdit);
+ String[] editDetails = {"n/breads"};
+
+ try {
+ ingredients.editIngredient(ingredientToEdit, editDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ assertEquals("breads", ingredientToEdit.getName());
+ }
+
+ @Test
+ public void editIngredientQuantity_validInput_editSuccess() {
+
+ ingredients.addIngredient(ingredientToEdit);
+ String[] editDetails = {"q/3"};
+
+ try {
+ ingredients.editIngredient(ingredientToEdit, editDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ assertEquals(3.0, ingredientToEdit.getQuantity());
+ }
+
+ @Test
+ public void editIngredientUnit_validInput_editSuccess() {
+
+ ingredients.addIngredient(ingredientToEdit);
+ String[] editDetails = {"u/g"};
+
+ try {
+ ingredients.editIngredient(ingredientToEdit, editDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ assertEquals(IngredientUnit.GRAM, ingredientToEdit.getUnit());
+ }
+
+ @Test
+ public void editIngredientAll_validInput_editSuccess() {
+
+ ingredients.addIngredient(ingredientToEdit);
+ String[] editDetails = {"n/breads", "q/3", "u/g"};
+
+ try {
+ ingredients.editIngredient(ingredientToEdit, editDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ assertEquals("breads", ingredientToEdit.getName());
+ assertEquals(3.0, ingredientToEdit.getQuantity());
+ assertEquals(IngredientUnit.GRAM, ingredientToEdit.getUnit());
+ }
+
+ @Test
+ public void editIngredientName_invalidInput_editError() {
+
+ ingredients.addIngredient(ingredientToEdit);
+ String[] editDetails = {"/nbreads"};
+
+ assertThrows(EssenFormatException.class, () -> {
+ ingredients.editIngredient(ingredientToEdit, editDetails);
+ });
+ }
+}
diff --git a/src/test/java/essenmakanan/ingredient/IngredientListTest.java b/src/test/java/essenmakanan/ingredient/IngredientListTest.java
new file mode 100644
index 0000000000..128aa1e64e
--- /dev/null
+++ b/src/test/java/essenmakanan/ingredient/IngredientListTest.java
@@ -0,0 +1,107 @@
+package essenmakanan.ingredient;
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.ui.Ui;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class IngredientListTest {
+
+ private IngredientList ingredients;
+ @BeforeEach
+ public void setUp() {
+ ingredients = new IngredientList();
+ }
+
+ @Test
+ public void addIngredient_validIngredient_addsNormally() {
+
+ Ingredient tomato = new Ingredient("tomato", 1.0, IngredientUnit.PIECE);
+ Ingredient cheese = new Ingredient("cheese", 20.0, IngredientUnit.GRAM);
+
+ ingredients.addIngredient(tomato);
+ ingredients.addIngredient(cheese);
+
+ Ui.printAllIngredients(ingredients);
+
+ Ingredient ingredient;
+ ingredient = ingredients.getIngredient(0);
+ assertEquals("tomato", ingredient.getName());
+ assertEquals(1.0, ingredient.getQuantity());
+ assertEquals(IngredientUnit.PIECE, ingredient.getUnit());
+
+ ingredient = ingredients.getIngredient(1);
+ assertEquals("cheese", ingredient.getName());
+ assertEquals(20.0, ingredient.getQuantity());
+ assertEquals(IngredientUnit.GRAM, ingredient.getUnit());
+ }
+
+ @Test
+ public void updateExistingIngredient_increaseQuantity_quantityIncreased(){
+ Ingredient tomato = new Ingredient("tomato", 1.0, IngredientUnit.PIECE);
+ Ingredient tomato2 = new Ingredient("tomato", 2.0, IngredientUnit.PIECE);
+
+ ingredients.addIngredient(tomato);
+
+ try {
+ ingredients.updateIngredient(tomato2);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ Ingredient ingredient = ingredients.getIngredient(0);
+ assertEquals("tomato", ingredient.getName());
+ assertEquals(3.0, ingredient.getQuantity());
+ assertEquals(IngredientUnit.PIECE, ingredient.getUnit());
+ }
+
+ @Test
+ public void updateIngredient_decreaseQuantity_quantityDecreased() {
+ Ingredient tomato = new Ingredient("tomato", 2.0, IngredientUnit.PIECE);
+ Ingredient tomato2 = new Ingredient("tomato", -1.0, IngredientUnit.PIECE);
+
+ ingredients.addIngredient(tomato);
+
+ try {
+ ingredients.updateIngredient(tomato2);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ Ingredient ingredient = ingredients.getIngredient(0);
+ assertEquals("tomato", ingredient.getName());
+ assertEquals(1.0, ingredient.getQuantity());
+ assertEquals(IngredientUnit.PIECE, ingredient.getUnit());
+ }
+
+ @Test
+ public void updateIngredient_decreaseMoreThanExisting_quantitySame() {
+ Ingredient tomato = new Ingredient("tomato", 10.0, IngredientUnit.PIECE);
+ Ingredient tomato2 = new Ingredient("tomato", -11.0, IngredientUnit.PIECE);
+
+ ingredients.addIngredient(tomato);
+
+ try {
+ ingredients.updateIngredient(tomato2);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ Ingredient ingredient = ingredients.getIngredient(0);
+ assertEquals("tomato", ingredient.getName());
+ assertEquals(10.0, ingredient.getQuantity());
+ assertEquals(IngredientUnit.PIECE, ingredient.getUnit());
+ }
+
+ @Test
+ public void editIngredient_quantityNotDouble_errorThrown() {
+ Ingredient tomato = new Ingredient("tomato", 10.0, IngredientUnit.PIECE);
+
+ ingredients.addIngredient(tomato);
+ assertThrows(EssenFormatException.class, () -> {
+ ingredients.editIngredient(tomato, new String[]{"i/tomato", "q/ten", "u/pc"});
+ });
+ }
+}
diff --git a/src/test/java/essenmakanan/ingredient/IngredientTest.java b/src/test/java/essenmakanan/ingredient/IngredientTest.java
new file mode 100644
index 0000000000..9f38bd3b7c
--- /dev/null
+++ b/src/test/java/essenmakanan/ingredient/IngredientTest.java
@@ -0,0 +1,47 @@
+package essenmakanan.ingredient;
+
+import essenmakanan.command.AddIngredientCommand;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class IngredientTest {
+
+ private IngredientList ingredients;
+
+ @BeforeEach
+ public void setUp() {
+ ingredients = new IngredientList();
+ }
+
+ @Test
+ public void createIngredient_validUnit_addSuccess() {
+ Ingredient ingredient = new Ingredient("bread", 2.0, IngredientUnit.PIECE);
+ assert ingredient.getUnit() == IngredientUnit.PIECE;
+ assertEquals(2.0, ingredient.getQuantity());
+ assertEquals(IngredientUnit.PIECE, ingredient.getUnit());
+ }
+
+ @Test void addMultipleIngredients_inOneLine_addSuccess() {
+ AddIngredientCommand addIngredientCommand = new AddIngredientCommand(
+ "i/bread,2,pc i/apple,3,kg i/milk,1,l",
+ ingredients
+ );
+
+ addIngredientCommand.executeCommand();
+
+ assertEquals(ingredients.getIngredient(0).getName(), "bread");
+ assertEquals(ingredients.getIngredient(0).getQuantity(), 2.0);
+ assertEquals(ingredients.getIngredient(0).getUnit(), IngredientUnit.PIECE);
+
+ assertEquals(ingredients.getIngredient(1).getName(), "apple");
+ assertEquals(ingredients.getIngredient(1).getQuantity(), 3.0);
+ assertEquals(ingredients.getIngredient(1).getUnit(), IngredientUnit.KILOGRAM);
+
+ assertEquals(ingredients.getIngredient(2).getName(), "milk");
+ assertEquals(ingredients.getIngredient(2).getQuantity(), 1.0);
+ assertEquals(ingredients.getIngredient(2).getUnit(), IngredientUnit.LITER);
+ }
+
+}
diff --git a/src/test/java/essenmakanan/ingredient/UseIngredientTest.java b/src/test/java/essenmakanan/ingredient/UseIngredientTest.java
new file mode 100644
index 0000000000..4826f523fa
--- /dev/null
+++ b/src/test/java/essenmakanan/ingredient/UseIngredientTest.java
@@ -0,0 +1,46 @@
+package essenmakanan.ingredient;
+
+import essenmakanan.command.UseIngredientCommand;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class UseIngredientTest {
+ private IngredientList ingredients;
+ private UseIngredientCommand useIngredientCommand;
+
+ @BeforeEach
+ public void setUp() {
+ ingredients = new IngredientList();
+ Ingredient tomato = new Ingredient("tomato", 5.0, IngredientUnit.PIECE);
+ ingredients.addIngredient(tomato);
+ }
+
+ @Test
+ public void useIngredient_enoughIngredient_quantityDecrease(){
+
+ String userInput = "i/tomato,2,pc";
+ useIngredientCommand = new UseIngredientCommand(ingredients,userInput);
+ useIngredientCommand.executeCommand();
+
+ Ingredient ingredient = ingredients.getIngredient(0);
+ assertEquals("tomato", ingredient.getName());
+ assertEquals(3.0, ingredient.getQuantity());
+ assertEquals(IngredientUnit.PIECE, ingredient.getUnit());
+ }
+
+ @Test
+ public void useIngredient_notEnoughIngredient_quantityDecrease(){
+
+ String userInput = "i/tomato,6,pc";
+ useIngredientCommand = new UseIngredientCommand(ingredients,userInput);
+ useIngredientCommand.executeCommand();
+
+ // nothing should happen
+ Ingredient ingredient = ingredients.getIngredient(0);
+ assertEquals("tomato", ingredient.getName());
+ assertEquals(5.0, ingredient.getQuantity());
+ assertEquals(IngredientUnit.PIECE, ingredient.getUnit());
+ }
+}
diff --git a/src/test/java/essenmakanan/parser/IngredientParserTest.java b/src/test/java/essenmakanan/parser/IngredientParserTest.java
new file mode 100644
index 0000000000..ed7c4c2c52
--- /dev/null
+++ b/src/test/java/essenmakanan/parser/IngredientParserTest.java
@@ -0,0 +1,95 @@
+package essenmakanan.parser;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class IngredientParserTest {
+ //private IngredientParser parser;
+ private IngredientList ingredients;
+ private Ingredient ingredient0;
+ private Ingredient ingredient1;
+
+ @BeforeEach
+ public void setUp() {
+ ingredients = new IngredientList();
+
+ ingredient0 = new Ingredient("banana", 1.0, IngredientUnit.PIECE);
+ ingredient1 = new Ingredient("apple", 2.0, IngredientUnit.PIECE);
+
+ ingredients.addIngredient(ingredient0);
+ ingredients.addIngredient(ingredient1);
+ }
+
+ @Test
+ public void parseIngredient_invalidInput_throwsEssenMakananFormatException() {
+ String invalidDetails = "plus tomato";
+ assertThrows(EssenFormatException.class, () -> {
+ IngredientParser.parseIngredient(invalidDetails);
+ });
+ }
+
+ @Test
+ public void getIngredientIndex_extraIndexInInput_throwsEssenOutOfRangeException() {
+ String input = "0 1";
+ assertThrows(EssenOutOfRangeException.class, () -> {
+ IngredientParser.getIngredientIndex(ingredients, input);
+ });
+ }
+
+ @Test void getIngredientIndex_invalidName_throwsEssenOutOfRangeException() {
+ String input = "strawberry";
+ assertThrows(EssenOutOfRangeException.class, () -> {
+ IngredientParser.getIngredientIndex(ingredients, input);
+ });
+ }
+
+ @Test void getIngredientIndex_invalidIndex_throwsEssenOutOfRangeException() {
+ String input = "3";
+ assertThrows(EssenOutOfRangeException.class, () -> {
+ IngredientParser.getIngredientIndex(ingredients, input);
+ });
+ }
+
+ @Test
+ public void addIngredient_emptyName_errorThrown(){
+
+ String input = "i/,2,pc";
+ assertThrows(EssenFormatException.class, () -> {
+ IngredientParser.parseIngredient(input);
+ });
+ }
+
+ @Test
+ public void addIngredient_emptyQuantity_errorThrown(){
+
+ String input = "i/cheese,,pc";
+ assertThrows(EssenFormatException.class, () -> {
+ IngredientParser.parseIngredient(input);
+ });
+ }
+
+ @Test
+ public void addIngredient_emptyUnit_errorThrown(){
+
+ String input = "i/cheese,2,";
+ assertThrows(EssenFormatException.class, () -> {
+ IngredientParser.parseIngredient(input);
+ });
+ }
+
+ @Test
+ public void addIngredient_incompleteIngredient_errorThrown(){
+
+ String input = "i/cheese,2";
+ assertThrows(EssenFormatException.class, () -> {
+ IngredientParser.parseIngredient(input);
+ });
+ }
+}
diff --git a/src/test/java/essenmakanan/parser/ParserTest.java b/src/test/java/essenmakanan/parser/ParserTest.java
new file mode 100644
index 0000000000..26025f1dc4
--- /dev/null
+++ b/src/test/java/essenmakanan/parser/ParserTest.java
@@ -0,0 +1,93 @@
+package essenmakanan.parser;
+
+import essenmakanan.exception.EssenCommandException;
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.shortcut.ShortcutList;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class ParserTest {
+ private IngredientList ingredients;
+ private RecipeList recipes;
+ private Ingredient ingredient1;
+ private Parser parser;
+ private ShortcutList shortcuts;
+
+ @BeforeEach
+ public void setUp() {
+ ingredients = new IngredientList();
+ recipes = new RecipeList();
+ shortcuts = new ShortcutList();
+ parser = new Parser();
+ }
+
+ @Test
+ public void parseCommand_invalidCommand_essenCommandException() {
+ String input1 = "invalid";
+ assertThrows(EssenCommandException.class, () -> {
+ parser.parseCommand(input1, recipes, ingredients, shortcuts);
+ });
+
+ String input2 = "";
+ assertThrows(EssenCommandException.class, () -> {
+ parser.parseCommand(input2, recipes, ingredients, shortcuts);
+ });
+
+ String input3 = " ";
+ assertThrows(EssenCommandException.class, () -> {
+ parser.parseCommand(input3, recipes, ingredients, shortcuts);
+ });
+
+ String input4 = "1";
+ assertThrows(EssenCommandException.class, () -> {
+ parser.parseCommand(input4, recipes, ingredients, shortcuts);
+ });
+ }
+
+ @Test
+ public void parseFilterCommand_invalidCommand_essenFormatException() {
+ String input1 = "filter";
+ assertThrows(EssenFormatException.class, () -> {
+ parser.parseCommand(input1, recipes, ingredients, shortcuts);
+ });
+
+ String input2 = "filter i/1";
+ assertThrows(EssenFormatException.class, () -> {
+ parser.parseCommand(input2, recipes, ingredients, shortcuts);
+ });
+
+ String input3 = "filter recipe";
+ assertThrows(EssenFormatException.class, () -> {
+ parser.parseCommand(input3, recipes, ingredients, shortcuts);
+ });
+
+ String input4 = "filter recipe i";
+ assertThrows(EssenFormatException.class, () -> {
+ parser.parseCommand(input4, recipes, ingredients, shortcuts);
+ });
+ }
+
+ @Test
+ public void parseViewCommand_invalidCommand_essenFormatException() {
+ String input1 = "view";
+ assertThrows(EssenFormatException.class, () -> {
+ parser.parseCommand(input1, recipes, ingredients, shortcuts);
+ });
+
+ String input2 = "view m";
+ assertThrows(EssenFormatException.class, () -> {
+ parser.parseCommand(input2, recipes, ingredients, shortcuts);
+ });
+
+ String input3 = "view ";
+ assertThrows(EssenFormatException.class, () -> {
+ parser.parseCommand(input3, recipes, ingredients, shortcuts);
+ });
+ }
+
+}
diff --git a/src/test/java/essenmakanan/parser/RecipeParserTest.java b/src/test/java/essenmakanan/parser/RecipeParserTest.java
new file mode 100644
index 0000000000..e736c5ba25
--- /dev/null
+++ b/src/test/java/essenmakanan/parser/RecipeParserTest.java
@@ -0,0 +1,116 @@
+package essenmakanan.parser;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeList;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class RecipeParserTest {
+
+ RecipeParser recipeParser;
+ RecipeList recipes;
+
+ @BeforeEach
+ public void setUp() {
+
+ recipeParser = new RecipeParser();
+ recipes = new RecipeList();
+
+ String[] recipeSteps = {"step1", "step2"};
+ String[] recipeIngredients = {"i/flour,200,g", "i/egg,2,pc"};
+ Recipe banana = new Recipe("banana", recipeSteps, recipeIngredients);
+
+ recipes.addRecipe(banana);
+ }
+
+
+ @Test
+ public void parsePlanCommandInput_invalidInput_essenFormatException() {
+ String userInput1 = "1 r/bread";
+ assertThrows(EssenFormatException.class, () -> {
+ RecipeParser.parsePlanCommandInput(userInput1);
+ });
+
+ String userInput2 = "2 r/1 r/2 r/3";
+ assertThrows(EssenFormatException.class, () -> {
+ RecipeParser.parsePlanCommandInput(userInput2);
+ });
+
+ String userInput3 = "two r/1 r/2";
+ assertThrows(EssenFormatException.class, () -> {
+ RecipeParser.parsePlanCommandInput(userInput3);
+ });
+
+ String userInput4 = "r/1 r/2 r/3";
+ assertThrows(EssenFormatException.class, () -> {
+ RecipeParser.parsePlanCommandInput(userInput4);
+ });
+
+ String userInput5 = "2";
+ assertThrows(EssenFormatException.class, () -> {
+ RecipeParser.parsePlanCommandInput(userInput5);
+ });
+
+ String userInput6 = "two";
+ assertThrows(EssenFormatException.class, () -> {
+ RecipeParser.parsePlanCommandInput(userInput6);
+ });
+ }
+
+ @Test
+ public void getRecipeIndex_stringAsIndex_essenOutOfRangeException() {
+ // Add recipes to myRecipeList
+ String[] inputIngredients1 = {"flour,200,g", "egg,2,pc"};
+ String[] inputSteps1 = {"beat the egg", "add flour into bowl of egg", "mix them together"};
+ Recipe myRecipe1 = new Recipe("bread", inputSteps1, inputIngredients1);
+
+ String[] inputIngredients2 = {"noodles,100,g", "egg,1,pc", "vegetable,4,pc"};
+ String[] inputSteps2 = {"beat the egg", "add flour into bowl of egg", "mix them together"};
+ Recipe myRecipe2 = new Recipe("noodles", inputSteps2, inputIngredients2);
+
+ RecipeList myRecipes = new RecipeList();
+ myRecipes.addRecipe(myRecipe1);
+ myRecipes.addRecipe(myRecipe2);
+
+ String userInput1 = "r/6";
+ assertThrows(EssenOutOfRangeException.class, () -> {
+ RecipeParser.getRecipeIndex(myRecipes, userInput1);
+ });
+
+ String userInput2 = "r/0";
+ assertThrows(EssenOutOfRangeException.class, () -> {
+ RecipeParser.getRecipeIndex(myRecipes, userInput2);
+ });
+
+ String userInput3 = "r/no bread";
+ assertThrows(EssenOutOfRangeException.class, () -> {
+ RecipeParser.getRecipeIndex(myRecipes, userInput3);
+ });
+
+ String userInput4 = "no bread";
+ assertThrows(EssenOutOfRangeException.class, () -> {
+ RecipeParser.getRecipeIndex(myRecipes, userInput4);
+ });
+ }
+
+ @Test
+ public void viewRecipe_invalidRecipe_errorThrown() {
+ String input = "r/apple";
+ assertThrows(EssenOutOfRangeException.class, () -> {
+ RecipeParser.getRecipeIndex(recipes, input);
+ });
+ }
+
+ @Test
+ public void executeRecipeCommand_titleDoNotExist_errorThrown() {
+ String userInput = "recipeThatDoesNotExist";
+ assertThrows(EssenOutOfRangeException.class, () -> {
+ RecipeParser.getRecipeIndex(recipes, userInput);
+ });
+ }
+
+}
diff --git a/src/test/java/essenmakanan/parser/ShortcutParserTest.java b/src/test/java/essenmakanan/parser/ShortcutParserTest.java
new file mode 100644
index 0000000000..a414c2b32f
--- /dev/null
+++ b/src/test/java/essenmakanan/parser/ShortcutParserTest.java
@@ -0,0 +1,70 @@
+package essenmakanan.parser;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Execute tests related to shortcut parser.
+ */
+public class ShortcutParserTest {
+
+ private IngredientList ingredients;
+
+ /**
+ * Sets up attributes before each test.
+ */
+ @BeforeEach
+ public void setup() {
+ ingredients = new IngredientList();
+ Ingredient ingredient = new Ingredient("bread", 2.0, IngredientUnit.PIECE);
+ ingredients.addIngredient(ingredient);
+ }
+
+ /**
+ * Execute tests for parsing a shortcut with invalid format.
+ */
+ @Test
+ public void parseShortcut_invalidFormat_expectEssenFormatException() {
+ String shortcutWithMissingQuantity = "bread,";
+ assertThrows(EssenFormatException.class, () -> {
+ ShortcutParser.parseShortcut(ingredients, shortcutWithMissingQuantity);
+ });
+
+ String shortcutWithMissingName = ",2";
+ assertThrows(EssenFormatException.class, () -> {
+ ShortcutParser.parseShortcut(ingredients, shortcutWithMissingName);
+ });
+
+ String blankString = " ";
+ assertThrows(EssenFormatException.class, () -> {
+ ShortcutParser.parseShortcut(ingredients, blankString);
+ });
+ }
+
+ /**
+ * Execute tests for parsing a shortcut with invalid quantity.
+ */
+ @Test
+ public void parseShortcut_invalidQuantity_expectNumberFormatException() {
+ String shortcutWithNegativeQuantity = "bread,-1";
+ assertThrows(NumberFormatException.class, () -> {
+ ShortcutParser.parseShortcut(ingredients, shortcutWithNegativeQuantity);
+ });
+
+ String shortcutWithZeroQuantity = "bread,0";
+ assertThrows(NumberFormatException.class, () -> {
+ ShortcutParser.parseShortcut(ingredients, shortcutWithZeroQuantity);
+ });
+
+ String shortcutWithStringQuantity = "bread,1abcd";
+ assertThrows(NumberFormatException.class, () -> {
+ ShortcutParser.parseShortcut(ingredients, shortcutWithStringQuantity);
+ });
+ }
+}
diff --git a/src/test/java/essenmakanan/recipe/AddRecipeTest.java b/src/test/java/essenmakanan/recipe/AddRecipeTest.java
new file mode 100644
index 0000000000..659ae67eb0
--- /dev/null
+++ b/src/test/java/essenmakanan/recipe/AddRecipeTest.java
@@ -0,0 +1,68 @@
+package essenmakanan.recipe;
+
+import essenmakanan.ingredient.IngredientUnit;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class AddRecipeTest {
+ private RecipeList recipes;
+ @BeforeEach
+ public void setUp() {
+ recipes = new RecipeList();
+ }
+
+ @Test
+ public void addRecipes_validRecipeWithNameAndStep_fullyConstructedRecipes() {
+ String[] recipe1Steps = {"step1", "step2", "step3"};
+ String[] recipe2Steps = {"step1", "step2"};
+ String[] ingredients = {"egg,2,pc", "flour,200,g"};
+ recipes.addRecipe(new Recipe("Recipe1", recipe1Steps, ingredients));
+ recipes.addRecipe(new Recipe("Recipe2", recipe2Steps, ingredients));
+
+ Recipe recipe = recipes.getRecipe(0);
+ assertEquals("Recipe1", recipe.getTitle());
+
+ RecipeStepList steps = recipe.getRecipeSteps();
+ assertEquals("step1", steps.getStepByIndex(0).getDescription());
+ assertEquals("step2", steps.getStepByIndex(1).getDescription());
+ assertEquals("step3", steps.getStepByIndex(2).getDescription());
+
+ recipe = recipes.getRecipe(1);
+ steps = recipe.getRecipeSteps();
+ assertEquals("step1", steps.getStepByIndex(0).getDescription());
+ assertEquals("step2", steps.getStepByIndex(1).getDescription());
+ }
+
+ @Test
+ public void addRecipe_titleAndStepsAndIngredients_recipeCreated() {
+ String recipeTitle = "bread";
+ String[] recipeSteps = {"step1", "step2"};
+ String[] recipeIngredients = {"i/flour,200,g", "i/egg,2,pc"};
+
+ Recipe newRecipe = new Recipe(recipeTitle, recipeSteps, recipeIngredients);
+ recipes.addRecipe(newRecipe);
+
+ Recipe addedRecipe = recipes.getRecipe(0);
+
+ // check title is added correctly
+ assertEquals("bread", addedRecipe.getTitle());
+
+ // check steps are added correctly
+ RecipeStepList steps = addedRecipe.getRecipeSteps();
+ assertEquals("step1", steps.getStepByIndex(0).getDescription());
+ assertEquals("step2", steps.getStepByIndex(1).getDescription());
+
+ // check ingredients are added correctly
+ RecipeIngredientList ingredients = addedRecipe.getRecipeIngredients();
+ assertEquals("flour", ingredients.getIngredientByIndex(0).getName());
+ assertEquals(200.0, ingredients.getIngredientByIndex(0).getQuantity());
+ assertEquals(IngredientUnit.GRAM, ingredients.getIngredientByIndex(0).getUnit());
+
+ assertEquals("egg", ingredients.getIngredientByIndex(1).getName());
+ assertEquals(2.0, ingredients.getIngredientByIndex(1).getQuantity());
+ assertEquals(IngredientUnit.PIECE, ingredients.getIngredientByIndex(1).getUnit());
+ }
+
+}
diff --git a/src/test/java/essenmakanan/recipe/EditRecipeTest.java b/src/test/java/essenmakanan/recipe/EditRecipeTest.java
new file mode 100644
index 0000000000..3b49774fa6
--- /dev/null
+++ b/src/test/java/essenmakanan/recipe/EditRecipeTest.java
@@ -0,0 +1,154 @@
+package essenmakanan.recipe;
+
+import essenmakanan.exception.EssenFormatException;
+import essenmakanan.ingredient.IngredientUnit;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class EditRecipeTest {
+
+ private RecipeList recipes;
+ private Recipe recipeToEdit;
+
+ @BeforeEach
+ public void setUp() {
+ recipes = new RecipeList();
+ recipeToEdit = new Recipe("Bread", new String[]{"Prepare", "Bake"},
+ new String[]{"i/Flour,200,g", "i/Egg,2,pc"});
+ recipes.addRecipe(recipeToEdit);
+ }
+
+ @Test
+ public void editRecipeName_validInput_editSuccess() {
+ String[] editDetails = {"n/Breads"};
+ try {
+ recipes.editRecipe(recipeToEdit, editDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ assertEquals("Breads", recipeToEdit.getTitle());
+ }
+
+ @Test
+ public void editRecipeStep_validInput_editSuccess() {
+ String[] editDetails = {"s/1,Prepare the dough"};
+ try {
+ recipes.editRecipe(recipeToEdit, editDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ String newStep = recipeToEdit.getRecipeSteps().getStepByIndex(0).getDescription();
+ assertEquals(Step.convertToStepIdTemplate("Prepare the dough",1), newStep);
+ }
+
+ @Test
+ public void editRecipeNameAndStep_validInput_editSuccess() {
+ // recipe title with one word
+ String[] editDetails = {"n/Breads", "s/1,Prepare the dough"};
+ try {
+ recipes.editRecipe(recipeToEdit, editDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ String newStep = recipeToEdit.getRecipeSteps().getStepByIndex(0).getDescription();
+ assertEquals(Step.convertToStepIdTemplate("Prepare the dough",1), newStep);
+ assertEquals("Breads", recipeToEdit.getTitle());
+ }
+
+ @Test
+ public void editRecipe_invalidInput_exceptionThrown() {
+ String[] editDetails = {"/nbreads"};
+
+ assertThrows(EssenFormatException.class, () -> {
+ recipes.editRecipe(recipeToEdit, editDetails);
+ });
+ }
+
+
+ @Test
+ public void editRecipeNameAndSteps_validInput_editSuccess() {
+ // recipe title with multiple word
+ String[] editDetails = {"n/white bread", "s/1,Prepare the dough"};
+ try {
+ recipes.editRecipe(recipeToEdit, editDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ String newStep = recipeToEdit.getRecipeSteps().getStepByIndex(0).getDescription();
+ assertEquals(Step.convertToStepIdTemplate("Prepare the dough",1), newStep);
+ assertEquals("white bread", recipeToEdit.getTitle());
+ }
+
+ @Test
+ public void editRecipeIngredient_nonIntegerIndex_editSuccess() {
+ String[] editDetails = {"i/f,n-newName"};
+
+ assertThrows(EssenFormatException.class, () -> {
+ recipes.editRecipe(recipeToEdit, editDetails);
+ });
+ }
+
+ @Test
+ public void editRecipeIngredient_indexOutOfRange_editSuccess() {
+ String[] editDetails = {"i/10,n-newName"};
+
+ assertThrows(EssenFormatException.class, () -> {
+ recipes.editRecipe(recipeToEdit, editDetails);
+ });
+ }
+
+ @Test
+ public void editRecipeIngredient_validName_editSuccess() {
+ // changing Flour to egg
+ String[] editDetails = {"i/1,n-egg"};
+
+ try {
+ recipes.editRecipe(recipeToEdit, editDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ String newIngredientName = recipeToEdit.getRecipeIngredients().getIngredientByIndex(0).getName();
+ assertEquals("egg", newIngredientName);
+ }
+ // edit r/bread i/1,n-Yeast1,q-1.0 i/2,n-Yeast2,q-2.0
+
+ // edit r/recipeName i/1,q-newqty
+ @Test
+ public void editRecipeIngredient_validQuantity_editSuccess() {
+ // changing Flour quantity from 200 to 400
+ String[] editDetails = {"i/1,q-400"};
+
+ try {
+ recipes.editRecipe(recipeToEdit, editDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ double newIngredientQuantity = recipeToEdit.getRecipeIngredients().getIngredientByIndex(0).getQuantity();
+ assertEquals(400.0, newIngredientQuantity);
+ }
+
+ @Test
+ public void editRecipeIngredient_validUnit_editSuccess() {
+ // changing Flour quantity from 200 to 400
+ String[] editDetails = {"i/1,u-kg"};
+
+ try {
+ recipes.editRecipe(recipeToEdit, editDetails);
+ } catch (EssenFormatException e) {
+ e.handleException();
+ }
+
+ IngredientUnit newIngredientUnit = recipeToEdit.getRecipeIngredients().getIngredientByIndex(0).getUnit();
+ assertEquals(IngredientUnit.KILOGRAM, newIngredientUnit);
+ }
+
+}
diff --git a/src/test/java/essenmakanan/shortcut/AddShortcutTest.java b/src/test/java/essenmakanan/shortcut/AddShortcutTest.java
new file mode 100644
index 0000000000..8e477d7baf
--- /dev/null
+++ b/src/test/java/essenmakanan/shortcut/AddShortcutTest.java
@@ -0,0 +1,48 @@
+package essenmakanan.shortcut;
+
+import essenmakanan.command.AddShortcutCommand;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+
+import essenmakanan.ingredient.IngredientUnit;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Executes tests related to adding shortcuts.
+ */
+public class AddShortcutTest {
+
+ private ShortcutList shortcuts;
+ private IngredientList ingredients;
+ private AddShortcutCommand command;
+
+ /**
+ * Sets up attributes before each test.
+ */
+ @BeforeEach
+ public void setup() {
+ shortcuts = new ShortcutList();
+ ingredients = new IngredientList();
+ Ingredient ingredient = new Ingredient("bread", 2.0, IngredientUnit.PIECE);
+ ingredients.addIngredient(ingredient);
+ }
+
+ /**
+ * Executes test related to adding shortcuts with valid input.
+ *
+ * @throws EssenOutOfRangeException If application tries to access out of bounds data.
+ */
+ @Test
+ public void addShortcut_validShortcut_expectShortcutInList() throws EssenOutOfRangeException {
+ String userInput = "sc/bread,2";
+ command = new AddShortcutCommand(shortcuts, ingredients, userInput);
+ command.executeCommand();
+
+ assertEquals("bread", shortcuts.getShortcut(0).getIngredientName());
+ assertEquals(2.0, shortcuts.getShortcut(0).getQuantity());
+ }
+}
diff --git a/src/test/java/essenmakanan/shortcut/DeleteShortcutTest.java b/src/test/java/essenmakanan/shortcut/DeleteShortcutTest.java
new file mode 100644
index 0000000000..c4061886bc
--- /dev/null
+++ b/src/test/java/essenmakanan/shortcut/DeleteShortcutTest.java
@@ -0,0 +1,57 @@
+package essenmakanan.shortcut;
+
+import essenmakanan.command.DeleteShortcutCommand;
+import essenmakanan.exception.EssenOutOfRangeException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Executes tests related to deleting shortcuts.
+ */
+public class DeleteShortcutTest {
+
+ private ShortcutList shortcuts;
+ private DeleteShortcutCommand command;
+
+ /**
+ * Sets up attributes before each test.
+ */
+ @BeforeEach
+ public void setup() {
+ shortcuts = new ShortcutList();
+ shortcuts.addShortcut(new Shortcut("bread", 2.0));
+ shortcuts.addShortcut(new Shortcut("egg", 1.0));
+ shortcuts.addShortcut(new Shortcut("cheese", 11.0));
+ }
+
+ /**
+ * Executes a test related to deleting a shortcut.
+ */
+ @Test
+ public void deleteShortcut_existingShortcut_expectLesserShortcutInList() {
+ String userInput = "sc/cheese";
+ command = new DeleteShortcutCommand(shortcuts, userInput);
+ command.executeCommand();
+
+ assertFalse(shortcuts.exist("cheese"));
+
+ userInput = "sc/2";
+ command = new DeleteShortcutCommand(shortcuts, userInput);
+ command.executeCommand();
+
+ assertFalse(shortcuts.exist("egg"));
+ }
+
+ /**
+ * Execute a test related to deleting shortcut that is out of bounds.
+ */
+ @Test
+ public void deleteShortcut_outOfBoundsIndex_expectEssenOutOfRangeException() {
+ assertThrows(EssenOutOfRangeException.class, () -> {
+ shortcuts.deleteShortcut(100);
+ });
+ }
+}
diff --git a/src/test/java/essenmakanan/shortcut/EditShortcutTest.java b/src/test/java/essenmakanan/shortcut/EditShortcutTest.java
new file mode 100644
index 0000000000..ff8066e854
--- /dev/null
+++ b/src/test/java/essenmakanan/shortcut/EditShortcutTest.java
@@ -0,0 +1,104 @@
+package essenmakanan.shortcut;
+
+import essenmakanan.command.EditShortcutCommand;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Executes tests related to editing shortcut.
+ */
+public class EditShortcutTest {
+
+ private ShortcutList shortcuts;
+ private IngredientList ingredients;
+ private EditShortcutCommand command;
+
+ /**
+ * Sets up attributes before each test.
+ */
+ @BeforeEach
+ public void setup() {
+ shortcuts = new ShortcutList();
+ ingredients = new IngredientList();
+ ingredients.addIngredient(new Ingredient("bread", 2.0, IngredientUnit.PIECE));
+ ingredients.addIngredient(new Ingredient("egg", 2.0, IngredientUnit.PIECE));
+ shortcuts.addShortcut(new Shortcut("bread", 2.0));
+ }
+
+ /**
+ * Executes a test related to editing a shortcut.
+ *
+ * @throws EssenOutOfRangeException If application tries to access out of bounds data.
+ */
+ @Test
+ public void editShortcut_validInput_expectEditedShortcut() throws EssenOutOfRangeException {
+ String userInput = "sc/bread n/egg q/3";
+ command = new EditShortcutCommand(shortcuts, ingredients, userInput);
+ command.executeCommand();
+
+ Shortcut shortcut = shortcuts.getShortcut(0);
+ assertEquals("egg", shortcut.getIngredientName());
+ assertEquals(3, shortcut.getQuantity());
+
+ userInput = "sc/1 q/0.1 n/bread";
+ command = new EditShortcutCommand(shortcuts, ingredients, userInput);
+ command.executeCommand();
+
+ shortcut = shortcuts.getShortcut(0);
+ assertEquals("bread", shortcut.getIngredientName());
+ assertEquals(0.1, shortcut.getQuantity());
+ }
+
+ /**
+ * Executes a test related to editing a shortcut with no changes.
+ *
+ * @throws EssenOutOfRangeException If application tries to access out of bounds data.
+ */
+ @Test
+ public void editShortcut_sameProperty_expectShortcutWithUnchangedProperty() throws EssenOutOfRangeException {
+ String userInput = "sc/bread n/bread q/3";
+ command = new EditShortcutCommand(shortcuts, ingredients, userInput);
+ command.executeCommand();
+
+ Shortcut shortcut = shortcuts.getShortcut(0);
+ assertEquals("bread", shortcut.getIngredientName());
+ assertEquals(3, shortcut.getQuantity());
+
+ userInput = "sc/bread n/egg q/3";
+ command = new EditShortcutCommand(shortcuts, ingredients, userInput);
+ command.executeCommand();
+
+ shortcut = shortcuts.getShortcut(0);
+ assertEquals("egg", shortcut.getIngredientName());
+ assertEquals(3, shortcut.getQuantity());
+ }
+
+ /**
+ * Executes a test related to editing a shortcut with multiple of the same flags.
+ *
+ * @throws EssenOutOfRangeException If application tries to access out of bounds data.
+ */
+ @Test
+ public void editShortcut_moreThanOneSameFlag_expectShortcutWithOneChange() throws EssenOutOfRangeException {
+ String userInput = "sc/bread n/egg n/bread";
+ command = new EditShortcutCommand(shortcuts, ingredients, userInput);
+ command.executeCommand();
+
+ Shortcut shortcut = shortcuts.getShortcut(0);
+ assertEquals("egg", shortcut.getIngredientName());
+
+ userInput = "sc/bread q/3 q/4";
+ command = new EditShortcutCommand(shortcuts, ingredients, userInput);
+ command.executeCommand();
+
+ shortcut = shortcuts.getShortcut(0);
+ assertEquals("egg", shortcut.getIngredientName());
+ assertEquals(2, shortcut.getQuantity());
+ }
+}
diff --git a/src/test/java/essenmakanan/shortcut/UseShortcutTest.java b/src/test/java/essenmakanan/shortcut/UseShortcutTest.java
new file mode 100644
index 0000000000..46ed63ec14
--- /dev/null
+++ b/src/test/java/essenmakanan/shortcut/UseShortcutTest.java
@@ -0,0 +1,46 @@
+package essenmakanan.shortcut;
+
+import essenmakanan.command.UseShortcutCommand;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Executes tests related to using a shortcut.
+ */
+public class UseShortcutTest {
+
+ private ShortcutList shortcuts;
+ private IngredientList ingredients;
+ private UseShortcutCommand command;
+
+ /**
+ * Sets up attributes before each test.
+ */
+ @BeforeEach
+ public void setup() {
+ shortcuts = new ShortcutList();
+ ingredients = new IngredientList();
+ Ingredient ingredient = new Ingredient("bread", 2.0, IngredientUnit.PIECE);
+ ingredients.addIngredient(ingredient);
+ }
+
+ /**
+ * Executes a test related to using a shortcut.
+ */
+ @Test
+ public void useShortcut_availableShortcut_expectUpdatedIngredient() {
+ shortcuts.addShortcut(new Shortcut("bread", 2.0));
+
+ String userInput = "1";
+ command = new UseShortcutCommand(shortcuts, ingredients, userInput);
+ command.executeCommand();
+
+ Ingredient ingredient = ingredients.getIngredient(0);
+ assertEquals(4, ingredient.getQuantity());
+ }
+}
diff --git a/src/test/java/essenmakanan/storage/StorageTest.java b/src/test/java/essenmakanan/storage/StorageTest.java
new file mode 100644
index 0000000000..32d7f1cb47
--- /dev/null
+++ b/src/test/java/essenmakanan/storage/StorageTest.java
@@ -0,0 +1,187 @@
+package essenmakanan.storage;
+
+import essenmakanan.exception.EssenFileNotFoundException;
+import essenmakanan.exception.EssenOutOfRangeException;
+import essenmakanan.ingredient.Ingredient;
+import essenmakanan.ingredient.IngredientList;
+import essenmakanan.ingredient.IngredientUnit;
+import essenmakanan.recipe.Recipe;
+import essenmakanan.recipe.RecipeList;
+import essenmakanan.recipe.Step;
+import essenmakanan.shortcut.Shortcut;
+import essenmakanan.shortcut.ShortcutList;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Executes tests related to storage.
+ */
+public class StorageTest {
+
+ private static String DATA_INVALID_PATH = "src/test/data/invalid.txt";
+ private static String DATA_RECIPE_TEST_PATH = "src/test/data/recipes.txt";
+ private static String DATA_INGREDIENT_TEST_PATH = "src/test/data/ingredients.txt";
+ private static String DATA_SHORTCUT_TEST_PATH = "src/test/data/shortcuts.txt";
+ private static String DATA_INVALID_RECIPE_PATH = "src/test/data/invalid_recipes.txt";
+ private static String DATA_INVALID_INGREDIENT_PATH = "src/test/data/invalid_ingredients.txt";
+ private static String DATA_INVALID_SHORTCUT_PATH = "src/test/data/invalid_shortcuts.txt";
+
+ /**
+ * Executes a test related to invalid path for ingredient storage.
+ */
+ @Test
+ public void accessIngredientDatabase_invalidPath_expectEssenFileNotFoundException() {
+ IngredientStorage ingredientStorage = new IngredientStorage(DATA_INVALID_PATH);
+ assertThrows(EssenFileNotFoundException.class, ingredientStorage::restoreSavedData);
+ }
+
+ /**
+ * Executes a test related to invalid path for recipe storage.
+ */
+ @Test
+ public void accessRecipeDatabase_invalidPath_expectEssenFileNotFoundException() {
+ RecipeStorage recipeStorage = new RecipeStorage(DATA_INVALID_PATH);
+ assertThrows(EssenFileNotFoundException.class, recipeStorage::restoreSavedData);
+ }
+
+ /**
+ * Executes a test related to invalid path for shortcut storage.
+ */
+ @Test
+ public void accessShortcutDatabase_invalidPath_expectEssenFileNotFoundException() {
+ ShortcutStorage shortcutStorage = new ShortcutStorage(DATA_INVALID_PATH, new IngredientList());
+ assertThrows(EssenFileNotFoundException.class, shortcutStorage::restoreSavedData);
+ }
+
+ /**
+ * Executes a test to restore saved ingredients.
+ *
+ * @throws EssenFileNotFoundException If the text file is missing.
+ */
+ @Test
+ public void restoreSavedIngredients_storeValidIngredients_expectFilledIngredientList()
+ throws EssenFileNotFoundException {
+ IngredientStorage ingredientStorage = new IngredientStorage(DATA_INGREDIENT_TEST_PATH);
+ IngredientList ingredients = new IngredientList(ingredientStorage.restoreSavedData());
+
+ assertEquals("bread", ingredients.getIngredients().get(0).getName());
+ assertEquals(2.0, ingredients.getIngredients().get(0).getQuantity());
+ assertEquals("g", ingredients.getIngredients().get(0).getUnit().getValue());
+
+ assertEquals("cheese", ingredients.getIngredients().get(1).getName());
+ assertEquals(10.0, ingredients.getIngredients().get(1).getQuantity());
+ assertEquals("pc", ingredients.getIngredients().get(1).getUnit().getValue());
+
+ assertEquals("carrot", ingredients.getIngredients().get(2).getName());
+ assertEquals(5.0, ingredients.getIngredients().get(2).getQuantity());
+ assertEquals("kg", ingredients.getIngredients().get(2).getUnit().getValue());
+ }
+
+ /**
+ * Executes a test to restore saved ingredients with invalid format.
+ *
+ * @throws EssenFileNotFoundException If the text file is missing.
+ */
+ @Test
+ public void restoreSavedIngredients_invalidDataFormat_expectEmptyList() throws EssenFileNotFoundException {
+ IngredientStorage ingredientStorage = new IngredientStorage(DATA_INVALID_INGREDIENT_PATH);
+ IngredientList ingredients = new IngredientList(ingredientStorage.restoreSavedData());
+
+ assertTrue(ingredients.getIngredients().isEmpty());
+ }
+
+ /**
+ * Executes a test to restore saved recipes.
+ *
+ * @throws EssenFileNotFoundException If the text file is missing.
+ */
+ @Test
+ public void restoreSavedRecipes_storedValidRecipes_expectFilledRecipeList() throws EssenFileNotFoundException {
+ RecipeStorage recipeStorage = new RecipeStorage(DATA_RECIPE_TEST_PATH);
+ RecipeList recipes = new RecipeList(recipeStorage.restoreSavedData());
+
+ Recipe recipe = recipes.getRecipe(0);
+
+ assertEquals("bread", recipe.getTitle());
+
+ Step step = recipe.getRecipeSteps().getStepByIndex(0);
+ assertEquals("step1", step.getDescription());
+ assertEquals(1, step.getTag().getPriority());
+
+ Ingredient ingredient = recipe.getRecipeIngredients().getIngredientByIndex(0);
+
+ assertEquals("bread", ingredient.getName());
+ assertEquals(5.0, ingredient.getQuantity());
+ assertEquals("kg", ingredient.getUnit().getValue());
+ }
+
+ /**
+ * Executes a test to restore saved recipes with invalid format.
+ *
+ * @throws EssenFileNotFoundException If the text file is missing.
+ */
+ @Test
+ public void restoreSavedRecipes_invalidDataFormat_expectEmptyList() throws EssenFileNotFoundException {
+ RecipeStorage recipeStorage = new RecipeStorage(DATA_INVALID_RECIPE_PATH);
+ RecipeList recipes = new RecipeList(recipeStorage.restoreSavedData());
+ System.out.println(recipes.getRecipes().size());
+ assertTrue(recipes.getRecipes().isEmpty());
+ }
+
+ /**
+ * Executes a test to restore saved shortcuts.
+ *
+ * @throws EssenFileNotFoundException If the text file is missing.
+ * @throws EssenOutOfRangeException If application tries to access out of bounds data.
+ */
+ @Test
+ public void restoreSavedShortcuts_storedValidShortcuts_expectFilledShortcutList()
+ throws EssenFileNotFoundException, EssenOutOfRangeException {
+ IngredientList ingredientList = new IngredientList();
+ ingredientList.addIngredient(new Ingredient("bread", 2.0, IngredientUnit.PIECE));
+ ingredientList.addIngredient(new Ingredient("egg", 2.0, IngredientUnit.PIECE));
+
+ ShortcutStorage shortcutStorage = new ShortcutStorage(DATA_SHORTCUT_TEST_PATH, ingredientList);
+ ShortcutList shortcutList = new ShortcutList(shortcutStorage.restoreSavedData());
+
+ Shortcut shortcut = shortcutList.getShortcut(0);
+ assertEquals("bread", shortcut.getIngredientName());
+ assertEquals(1, shortcut.getQuantity());
+
+ shortcut = shortcutList.getShortcut(1);
+ assertEquals("egg", shortcut.getIngredientName());
+ assertEquals(0.1, shortcut.getQuantity());
+ }
+
+ /**
+ * Executes a test to restore saved shortcuts that has no matching ingredients.
+ *
+ * @throws EssenFileNotFoundException If the text file is missing.
+ */
+ @Test
+ public void restoreSavedShortcuts_storedShortcutsWithNoMatchingIngredient_expectEmptyList()
+ throws EssenFileNotFoundException {
+ IngredientList ingredientList = new IngredientList();
+
+ ShortcutStorage shortcutStorage = new ShortcutStorage(DATA_SHORTCUT_TEST_PATH, ingredientList);
+ ShortcutList shortcutList = new ShortcutList(shortcutStorage.restoreSavedData());
+
+ assertTrue(shortcutList.getShortcuts().isEmpty());
+ }
+
+ /**
+ * Executes a test to restore saved shortcuts with invalid format.
+ *
+ * @throws EssenFileNotFoundException If the text file is missing.
+ */
+ @Test
+ public void restoreSavedShortcuts_invalidDataFormat_expectEmptyList() throws EssenFileNotFoundException {
+ ShortcutStorage shortcutStorage = new ShortcutStorage(DATA_INVALID_SHORTCUT_PATH, new IngredientList());
+ ShortcutList shortcuts = new ShortcutList(shortcutStorage.restoreSavedData());
+
+ assertTrue(shortcuts.getShortcuts().isEmpty());
+ }
+}
diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/duke/DukeTest.java
deleted file mode 100644
index 2dda5fd651..0000000000
--- a/src/test/java/seedu/duke/DukeTest.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package seedu.duke;
-
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import org.junit.jupiter.api.Test;
-
-class DukeTest {
- @Test
- public void sampleTest() {
- assertTrue(true);
- }
-}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 892cb6cae7..7d99ec8860 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,9 +1,19 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
-What is your name?
-Hello James Gosling
+Directory successfully created
+Log file successfully created
+Creating database
+Directory located
+Data text file successfully created
+Creating database
+Directory located
+Data text file successfully created
+Creating database
+Directory located
+Data text file successfully created
+------------------------------------------------
+Welcome to Essen Makanan!!! A one-stop place to track the
+ingredients in your kitchen and store your favourite recipes
+To get started, type [help] for list of commands
+------------------------------------------------
+------------------------------------------------
+Hope you had fun! See you again!
+------------------------------------------------
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index f6ec2e9f95..ae3bc0a936 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -1 +1 @@
-James Gosling
\ No newline at end of file
+exit
\ No newline at end of file