diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 8e359a0145..ffaf0600f2 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -2,7 +2,7 @@ Display | Name | Github Profile | Homepage ---|:---:|:---:|:---: -![](https://avatars0.githubusercontent.com/u/22460123?s=100) | Jeffry Lum | [Github](https://github.com/j-lum/) | [Homepage](https://se.kasugano.moe) +![](https://avatars0.githubusercontent.com/u/22460123?s=100) | Jeffry Lum | [Github](https://github.com/j-lum/) | [Homepage](https://se.kasugano.moe/) ![](https://avatars0.githubusercontent.com/u/1673303?s=100) | Damith C. Rajapakse | [Github](https://github.com/damithc/) | [Homepage](https://www.comp.nus.edu.sg/~damithch/) # I would like to join this list. How can I help the project diff --git a/build.gradle b/build.gradle index b0c5528fb5..91fffd027d 100644 --- a/build.gradle +++ b/build.gradle @@ -43,4 +43,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..51e0e9b9de 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,11 @@ -# About us +[<== Back to Home](README.md) -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +# About Us + +Member | Github Profile | Portfolio +-----|:--------------:|:---------: +Hemrish Bundhoo | [Github](https://github.com/H-horizon) | [Portfolio](team/h-horizon.md) +Ivan Chong Zhi En | [Github](https://github.com/ivanchongzhien) | [Portfolio](team/ivanchongzhien.md) +Chong Wen Hao | [Github](https://github.com/8kdesign) | [Portfolio](team/8kdesign.md) +Isa Haron | [Github](https://github.com/isaharon) | [Portfolio](team/isaharon.md) +Alicia Tay | [Github](https://github.com/aliciatay-zls/) | [Portfolio](team/aliciatay-zls.md) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 0ec3db103d..c09abe677e 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,34 +1,702 @@ +[<== Back to Home](README.md) + + # Developer Guide -## Design & implementation +**GULIO (Get Ur Life In Order)** is a desktop app that provides a single consolidated and personalised workspace for NUS SoC students to organize their modules. It is optimized for use via a Command Line Interface (CLI) which SoC students will be familiar with typing in, instead of a Graphical User Interface (GUI). + +This guide is for developers looking to modify GULIO. For users of GULIO, please refer to the User Guide [here](UserGuide.md). + +  + +---- + +## Table of Contents + +* [How to Use This Guide](#how-to-use-this-guide) +* [Setting up GULIO on Your Computer](#setting-up-gulio-on-your-computer) +* [Introduction](#introduction) +* [Design](#design) + * [Architecture](#architecture) + * [UI Component](#ui-component) + * [Parser Component](#parser-component) + * [Command Component](#command-component) + * [Model Component](#model-component) + * [Storage Component](#storage-component) + * [Editor Component](#editor-component) + * [Common Classes](#common-classes) +* [Implementation](#implementation) + * [Adding of Lesson](#adding-of-lesson) + * [Adding of Cheat-Sheet](#adding-of-cheat-sheet) + * [Loading & Storing of Data](#loading--storing-of-data) + * [Design Considerations](#design-considerations) + * [Future Features](#future-features) +* [Appendix: Requirements](#appendix-requirements) + * [Product Scope](#product-scope) + * [User Stories](#user-stories) + * [Non-Functional Requirements](#non-functional-requirements) + * [Glossary](#glossary) +* [Appendix: Instruction for Manual Testing](#appendix-instruction-for-manual-testing) + * [Launching GULIO](#launching-gulio) + * [Exiting GULIO](#exiting-gulio) + * [Adding a Module](#adding-a-module) + * [Deleting a Module](#deleting-a-module) + * [Opening a Module](#opening-a-module) + * [Closing a Module](#closing-a-module) + * [Adding a Lesson](#adding-a-lesson) + * [Deleting a Lesson](#deleting-a-lesson) + * [Opening a Link](#opening-a-link) +* [Appendix: Command Summary](#command-summary) + +  + +---- + +## How to Use This Guide + +#### Icons & labels used in this guide: + +💡 - indicates a tip that may be useful to you.
+⚠ - indicates a warning that you should take note of.
+ +_Italic_ - Indicates that the content is a component.
+`Inline code` - Indicates that the content is a class, method or input by the user. + +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +## Setting up GULIO on Your Computer + +1. Fork [this repo](https://github.com/AY2021S2-CS2113T-W09-3/tp), and clone the fork into your computer. +1. Ensure you are using Java 11 or above. +1. Import the project in your IDE. + +> 💡 You are highly recommended to use Intellij IDEA. +> +> To set up Intellij: +> 1. Ensure Intellij is configured to use JDK 11. +> 1. Import the project as a Gradle project. +> 1. Verify the setup: + > 1. Run `seedu.duke.Duke` and try a few commands. +> 1. Run the tests to ensure they all pass. + +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +## Introduction + +### Background of GULIO + +GULIO is a command line application for NUS SoC students to organize their modules. It has a 2-layer system: dashboard layer and module layer. In both layers, the user has access to a different set of commands. + +On start up, the user will be on the dashboard layer and has an overview of all their modules. They will have access to module management commands such as adding, deleting or opening a particular module. Opening a module then puts them on the module layer where they can interact with the data within the module. + +

+ 2-Layer System
+ Figure 1 - Visualisation of GULIO’s 2-layer system +

+ +Currently, GULIO is a basic university module manager intended to provide students with an overview and consolidated workspace for all of their modules, lessons, tasks and cheat-sheets. Going forward, we feel that GULIO has the potential for many more features to be added, some of which are proposed in the [Implementation section](#implementation). + +### Scope + +This document describes the software architecture and software design decisions for the implementation of GULIO. The intended audience of this document is the developers, designers, and software testers of GULIO. + +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +
+ + + +## Design + +### Architecture + +

+ Architecture Diagram
+ Figure 2 - GULIO Architecture Diagram +

+ +_Duke_ contains the main method which is required by Java to run the application. It is responsible for instantiating and calling methods from the _UI_, _Parser_ and _Command_ components. + +Apart from _Duke_, the application consists of the following components: + +* _UI_ : Handles reading and printing +* _Parser_ : Validates and checks user input +* _Command_ : Executes commands +* _Model_ : Consists of data related to the application +* _Storage_ : Handles loading and storing of data into text files +* _Editor_ : Graphical interface for users to type +* _Common_ : collection of classes used by multiple components + + +GULIO is a local application that stores its application data using readable text files, allowing users the flexibility of viewing and editing data locally. + +The way GULIO runs and handles user input can be described as follows: + +

+ Sequence Diagram
+ Figure 3 - GULIO Sequence Diagram +

+ +Upon starting up GULIO, the `Duke` (main) class calls `run()` which enters a while loop and reads in user input. In the loop, the _Parser_ component processes the user input into various commands implemented in GULIO. The loop ends when the user enters exit. + +  + +
+ +### UI Component + +**API**: `UI.java` + +* Facilitates the CLI interface +* Methods to display general messages, prompt messages and error messages +* Reads in user’s input, and used by Command classes to react to user’s inputs +* The `UI` object created as an attribute in Duke is passed into each command to be executed +* Instances of `UI` are used by tests in general + +  + +### Parser Component + +**API**: `Parser.java` + +* Determines the command entered by the user + +* Parses the parameters needed by subclasses of the `Command` object (for commands which require additional details) + +* Checks the validity of parsed parameters, in some instances calling methods from other relevant classes, e.g. calling a method from the `Lessons` class to verify parsed lesson links. + +* May instruct `UI` to print warnings and prompts to users, e.g. when users enter invalid parameters. + +* Returns a new `Command` object with all the necessary attributes filled + +  + +
+ +### Command Component + +

+ Dual Layer Command System
+ Figure 4 - Visualisation of Dual Layer Command System +

+ +**API**: `Command.java` + +The _Command_ component consist of an abstract `Command` class, as well as its subclasses. Each subclass handles a specific command in GULIO. The abstract `Command` class contains methods that these commands inherit from. + +Steps for command execution: + +1. The `Parser` validates user input and returns a `Command` object to `Duke` +1. `Duke` calls the execute method in the `Command` object +1. Depending on the command, it may make changes to the objects within the _Model_ component +1. If there are changes, the _Model_ component then updates application data via the _Storage_ component +1. The `UI` prints user information related to the command executed + + +  + +
+ +### Model Component + +

+ Class Diagram of Model
+ Figure 5 - Class Diagram of Model +

+ +The Model component consists of classes that represent real-world objects related to the program. `ModuleList` represents the various modules that a typical SoC student may be taking within the semester. Each of these modules is encapsulated in the `Module` class which contains an ArrayList of `Lesson` and `Task` objects representing the lessons that would be conducted for the module and tasks that students have to complete for the modules they are taking. + +#### ModuleList: + +`ModuleList` is responsible for managing loaded modules in the program and keeping track if a user is at the dashboard or within a selected module. `ModuleList` interacts with instances of the `Loader` and `Writer` class to load and write data respectively to the storage files. It also contains methods to sort data. + +The `ModuleList` class contains the attributes: + +* ArrayList of module code strings +* Class-level member storing `Module` + +#### Module: + +The `Module` class contains the attributes: + +* Module code string +* ArrayList of `Lesson` +* ArrayList of `Task` + +#### Lesson: + +In SoC, lessons are conducted by a combination of lectures, tutorials or labs. To enforce this constraint, the Enum class `LessonType` contains a set of constants for lecture, tutorial and lab. + +The `Lesson` class contains attributes related to a typical course lesson: + +* Lesson Type, e.g. lab, tutorial or lecture +* Time and day of the lesson (stored as a String for flexibility) +* Link for online lessons +* Teaching Staff information encapsulated in `TeachingStaff` + +#### Teaching staff: + +Being enrolled in several modules, it would be useful for students to store names of the teaching staff the way they prefer to be addressed along with their email addresses in events that require the student to call upon or inquire a teaching staff for a module. + +The `TeachingStaff` class contains the attributes related to the teacher(s) of a particular lesson: + +* Name of the teacher +* Email address of the teacher + +#### Task: + +The `Task` class contains attributes related to an assignment, deadline or task in a university setting + +* Description of task +* Deadline of task +* Remarks +* Done status +* Graded status + +  + +
+ + + +### Storage Component + +

+ Storage Structure
+ Figure 6 - Illustration of Storage Structure +

+ +The _Storage_ component is responsible for creating and loading modules and their respective data, as well as saving the data each time a change is made. It consists of two classes: `Loader` and `Writer`. At every moment, `Loader` only loads up to 1 module at a time and data for each module is stored separately. This is done to ensure fast loading and writing of files. + +#### Loader: + +* Loads the list of modules from the “Data” directory +* Loads lesson and task data from the selected module’s “.txt” file + +#### Writer: + +* Creates all the directories required +* Deletes files and directories +* Creates the “.txt” file that saves the module’s lessons and tasks +* Writes changes to the “.txt” file that saves the module’s lessons and tasks + +  + +### Editor Component + +**API**: `TextEditor.java` + +The _Editor_ component is responsible for opening the text editor to add or edit cheat-sheets/notes. It consists of two classes: + +#### Text Editor + +* Sets up the text editor +* Loads existing file from Cheatsheet directory within a module for the edit cheat-sheet command +* Flushes out the text from the editor when a different or new file is opened +* Adjusts the font size of the text within the editor +* Detects mouse input to change font style and save the text +* Saves the text from the text editor into a file + +#### ShortcutListener + +* Detects keyboard input for shortcuts + +  + +### Common Classes + +Classes that are used by multiple components: +* `CommonMethods`: Stores methods that are used by multiple components +* `Constants`: Stores constants +* `Messages`: Stores strings that are printed by the `UI` +* `DashboardCommands`: Enum of commands that can be used outside a module +* `ModuleCommands`: Enum of commands that can be used inside a module +* `InputValidator`: Validates user input such as file name + +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +
+ +## Implementation + +In this section, we highlight a few of the key features whose implementations are reflective of most of the commands available, as well as those that are more unique to GULIO. + +### Adding of Lesson + +The `AddLessonCommand` class is responsible for the creation and addition of a new `Lesson` object to the lesson list of a given module. The following sequence diagrams shows how a new `Lesson` is created and added to the lesson list. + +

+ parse() Sequence Diagram
+ Figure 7 - parse() Sequence Diagram +

+ +The creation process is facilitated by the `Parser` class, which parses the appropriate arguments from the user input and initialises the `Lesson` object attributes with the parsed values. + +

+ AddLessonCommand Constructor Sequence Diagram
+ Figure 8 - AddLessonCommand Constructor Sequence Diagram +

+ +The newly created `Lesson` object is then passed to a new `AddLessonCommand` object as an argument. + +

+ execute() AddLessonCommand Sequence Diagram
+ Figure 9 - execute() AddLessonCommand Sequence Diagram +

+ +`AddLessonCommand` then adds the `Lesson` object to the lesson list of a module. The lessons in the list are sorted by their lesson types each time a new lesson is added. `AddLessonCommand` also calls the `writeLesson()` method of `ModuleList` to update the change locally. + +  + +### Adding of Cheat-Sheet + +The `AddCheatSheetCommand` class enables the creation, addition and saving of a ".txt" file to the current module’s “Cheatsheet” directory (see [Figure 6](#storage-component)). Upon creating a new instance of it and calling the `execute()` method on it, a text editor window will also be automatically opened if there is none opened yet. + +An invocation of the `add cheat-sheet` command involves the following interactions: + +

+ AddCheatSheetCommand Invocation Sequence Diagram A
+ Figure 10a - AddCheatSheetCommand Invocation Sequence Diagram +

+ +When `AddCheatSheetCommand` is executed, it gets the currently selected module by calling the `getSelectedModule()` method in `ModuleList`. It then checks if the file name given by the user is invalid. If no, `AddCheatSheetCommand` proceeds to call the `getDirectoryPath()` method in itself to obtain the directory where the cheat-sheet would be saved to. It then calls the `openTextEditor()` method itself, which will interact with the `TextEditor` class, a Singleton class, in the following way: + +

+ AddCheatSheetCommand Invocation Sequence Diagram B
+ Figure 10b - Opening the Text Editor +

+ +The `openTextEditor()` method will first check if the single instance of `TextEditor` is `null`. If it is, then there is no text editor window currently opened and `openTextEditor()` proceeds to call the `createNew()` method of `TextEditor`. This initialises the single instance of `TextEditor` by calling the class constructor, which sets up, loads from previous data (if any) and opens the text editor for the user. The user can now start typing into the text editor. + +  + +### Loading & Storing of Data + +This section covers how the _Storage_ component works, from the loading of all module codes to the loading of individual module and creation of data files. + +#### Saving of Data + +The `Writer` class is responsible for writing any changes to the module’s data file, as well as creating the file itself. Interaction with the `Writer` class is done through the `ModuleList` class, whose methods are called by the other components of the app. + +

+ writeModule() Sequence Diagram
+ Figure 11 - writeModule() Sequence Diagram +

+ +Whenever some data in a module changes, the command that made those changes would call the method `writeModule()` in `ModuleList` to update the change in the data file. This method would then call a method of the same name in the `Writer` class, which overwrites the existing data in the file with the new data. + +Due to how much data needs to be written each time, we decided to split the data file by module. That way, we only need to overwrite the module's data when changes are made. + + + +#### Loading of Data + +The `Loader` class is responsible for identifying all the modules currently added, as well as loading the data file of the selected class. Methods in the `Loader` class are accessed by the other components via the `ModuleList` class. + +

+ loadModuleCodes() Sequence Diagram
+ Figure 12 - loadModuleCodes() Sequence Diagram +

+ +To identify modules in the “Data” directory, Duke would call `loadModuleCodes()` method in the `ModuleList`. This method would then call the `getModules()` method in `Loader`, which returns a list of module codes. For each of the identified module code, `ModuleList` would call its own `insertModule()` method to add it to the module list. + +

+ setSelectedModule() Sequence Diagram
+ Figure 13 - setSelectedModule() Sequence Diagram +

+ +When a module is selected via the `setSelectedModule()` method, the specified module code would be searched for in the module list. If it is inside, `loadModule()` method in the `Loader` would be called. This method reads the module’s data file for data and adds them into a new instance of `Module` class. This `Module` is then returned to `ModuleList` and set as the selected module. + +If the `Loader` failed to load the file, null would be returned. If null is not returned, `ModuleList` would sort the data and then use `Writer` to override the existing file. This is done to remove invalid entries that were initially in the file. + +  + +### Design Considerations + +#### Implementation of `EditLessonCommand` and `EditTaskCommand` + +* **Initial:** used a while loop for fields to be updated that require input validation (email, link for `EditLessonCommand` and deadline for `EditTaskCommand`). It only breaks when the user enters a valid input for the field, otherwise keeps prompting for another input. +* **Current:** notifies the user that the field was unsuccessfully updated, goes on to the next field to be updated (if any). +* **Rationale for change:** we decided not to use a while loop because we anticipated that if the user changed his/her mind halfway and no longer wanted to edit the lesson or task, they should not be stuck endlessly being prompted for a valid input. In future implementations we could combine both methods to make these commands even more user-friendly e.g. a "cancel" command together with the while loop. + +### Future Features + +1. Add weightage for modules. +1. Integrate with Github. +1. Group project information for each module (e.g. group members' emails, off-limit days). +1. Search via a filter. + +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +## Appendix: Requirements + +### Product Scope + +#### Target user profile: + +1. Needs a consolidated and personalisable workspace to organize their university modules +1. Prefers desktop apps over other types +1. Can type fast +1. Is comfortable using CLI apps +1. Is familiar with command-line shell environment + +#### Value proposition: + +Efficiently view and update regularly-needed information on modules and deadlines using a single keyboard. + +  + +### User Stories + +> 💡 Priority levels:
+> `1`: High (Must have)
+> `2`: Medium (Good to have)
+> `3`: Low (Unlikely to have) + +| Priority | As a/an ... | I want to ... | So that I can ... | +| --- | --- | --- | --- | +| 1 | new user | see available commands | refer to the help page when I forget how to use the app | +| 1 | NUS student | add a module | store useful information by module that I can easily refer to | +| 1 | NUS student faced with e-learning | add a lesson | consolidate regularly-needed information such as Zoom links by tutorial/lecture, for quick access before the lesson | +| 1 | busy NUS student | add a task | keep track of assignments and deadlines for a module in an organised to-do list | +| 1 | NUS student | get an overview of the module / lesson / task list | filter out specific information with a single command | +| 2 | NUS student | delete a module | store the information only temporarily, e.g. for the semester/term | +| 2 | NUS SoC student | open a module’s cheat sheet(s) | I have a handy list of tools for the module, tests and exams at my disposal | +| 2 | NUS SoC student with many team projects | View a module’s project team information and contact details | keep track of the various teams I am in and communicate more efficiently with my teammates | +| 3 | busy NUS student | sort tasks by graded and done status | know which tasks are of highest priority | + +_Note: some are features to be implemented in future._ + +  + +### Non-Functional Requirements + +1. Text editor will only work on OS with GUI support. +1. All other features will work on any mainstream OS. +1. It should work for students taking up to 10 modules. +1. Each module should be able to store 100 tasks without issues. +1. Every command should respond within 10s of input on a typical modern computer. + +  + +### Glossary + +* Mainstream OS: Windows, Linux, Unix, OS-X +* CLI: Command-Line Interface +* GUI: Graphical User Interface +* Module: A university module + +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +
+ +## Appendix: Instruction for Manual Testing + +Due to the 2-layer command system, you will need to identify which layer you are on in order to run the tests correctly. To identify which layer you are on, simply check the tag beside your input, known as the **input label**. + +* `GULIO` indicates that you are at the dashboard layer. +* A module code (e.g. `CS2113T`) indicates that you are within that module. + + + + + + +
+

+ Dashboard Label
+ Figure 3a - Dashboard Layer +

+
+

+ Module Label
+ Figure 3b - Module Layer +

+
+ +### Launching GULIO + +1. Shift the file GULIO.jar to your desired directory. +1. Open command prompt and navigate to the directory. +1. Enter `java -jar GULIO.jar` into the command prompt.
+ >Expected outcome: Prints welcome message. + + +### Exiting GULIO + +1. Ensure that no module is selected. Input label should show `GULIO`. + 1. If you see a module code instead, enter `close` to close the module.
+ >Expected outcome: Input label changes to `GULIO`. +1. Enter `exit`.
+ >Expected outcome: Prints exit message and program closes. + + + +### Adding a Module + +1. Ensure that no module is selected. Input label should show `GULIO`. + 1. If you see a module code instead, enter `close` to close the module.
+ >Expected outcome: Input label changes to `GULIO`. +1. Enter `mods`.
+ >Expected outcome: Lists all existing modules. +1. Enter `add ` where \ is a module code that is not in the list.
+ >Expected outcome: Prints success message. +1. Enter `mods` again to list all existing modules.
+ >Expected outcome: New module is added to list. + + +### Deleting a Module + +1. Ensure that no module is selected. Input label should show `GULIO`. + 1. If you see a module code instead, enter `close` to close the module.
+ >Expected outcome: Input label changes to `GULIO`. +1. Enter `del`.
+ >Expected outcome: Lists all existing modules and asks for indices to delete. +1. Enter indices of modules to delete, separated by space.
+ >Expected outcome: Prints success message. +1. Enter `mods`.
+ >Expected outcome: Specified modules deleted. + +### Opening a Module + +1. Ensure that no module is selected. Input label should show `GULIO`. + 1. If you see a module code instead, enter `close` to close the module.
+ >Expected outcome: Input label changes to `GULIO`. +1. Enter `mods`.
+ >Expected outcome: Lists all existing modules. +1. Enter `open ` where \ is a module code in the list.
+ >Expected outcome: Prints overview of module and input label changes to module code. + +### Closing a Module + +1. Check if a module is selected via the input label. + 1. If the input label shows `GULIO`, enter `mods`.
+ >Expected outcome: Lists all existing modules. + 1. Add a new module if the list is empty. E.g. `add CS2113T` + 1. Open one of the modules. E.g. `open CS2113T`

+ >Expected outcome: Prints overview of module. +1. Enter `close`.
+ >Expected outcome: Input label changes back to `GULIO`. + +### Adding a Lesson + +1. Check if a module is selected via the input label. + 1. If the input label shows `GULIO`, enter `mods`.
+ >Expected outcome: Lists all existing modules. + 1. Add a new module if the list is empty. E.g. `add CS2113T` + 1. Open one of the modules. E.g. `open CS2113T`
+ >Expected outcome: Prints overview of module. +1. Enter `lsn`.
+ >Expected outcome: Lists all lessons for that module. +1. Add a new lesson. E.g. `add lsn lecture ;; Friday 6pm`.
+ >Expected outcome: Prints success message. +1. Enter `lsn`.
+ >Expected outcome: New lesson added to list. + +### Deleting a Lesson + +1. Check if a module is selected via the input label. + 1. If the input label shows `GULIO`, enter `mods`.
+ >Expected outcome: Lists all existing modules. + 1. Add a new module if the list is empty. E.g. `add CS2113T` + 1. Open one of the modules. E.g. `open CS2113T`
+ >Expected outcome: Prints overview of module. +1. Enter `del lsn`.
+ >Expected outcome: Lists all existing lessons and asks for indices to delete. +1. Enter indices of lessons to delete, separated by space.
+ >Expected outcome: Prints success message. +1. Enter `lsn`.
+ >Expected outcome: Specified lessons removed from list. + +### Opening a Link + +1. Check if a module is selected via the input label. +1. If the input label shows `GULIO`, enter `modules`.
+ >Expected outcome: Lists all existing modules. +1. Add a new module if the list is empty. E.g. `add CS2113T` +1. Open one of the modules. E.g. `open CS2113T`
+ >Expected outcome: Prints overview of module. +1. Add a new lesson with a link. E.g. `add lsn lecture ;; Friday 4pm ;; https://nus-sg.zoom.us/`.
+ >Expected outcome: Prints success message. +1. Enter `link`.
+ >Expected outcome: Lists all existing lessons. +1. Enter indices of lessons with links to open.
+ >Expected outcome: Opens link for lessons selected. -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +  -## Product scope -### Target user profile +[🡅 Back to Table of Contents](#table-of-contents) -{Describe the target user profile} +---- -### Value proposition +
-{Describe the value proposition: what problem does it solve?} +# Command Summary -## User Stories +### Dashboard Commands Summary -|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| +| Keyword | Format | +| --- | --- | +| help | `help` | +| exit | `exit` | +| open | `open `| +| add | `add ` | +| delete | `del` | +| modules | `mods` | -## Non-Functional Requirements +
-{Give non-functional requirements} +### Module Commands Summary -## Glossary +| Keyword | Format | +| --- | --- | +| help | `help` | +| close | `close` | +| info | `info` | +| add lesson | `add lsn ;; ;; ;; ;; ` | +| delete lesson | `del lsn` | +| edit lesson | `edit lsn` | +| link | `link` | +| teacher | `tch` | +| lessons | `lsn` | +| add task | `add task ;; ;; ` | +| delete task | `del task` | +| edit task | `edit task` | +| mark | `mark` | +| unmark | `unmark` | +| tasks | `task` | +| add cheat-sheet | `add cs ` | +| delete cheat-sheet | `del cs ` | +| edit cheat-sheet | `edit cs ` | +| cheat-sheets | `cs` | -* *glossary item* - Definition +  -## Instructions for manual testing +[🡅 Back to Table of Contents](#table-of-contents) -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} +---- diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..7dfcf7d122 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,10 @@ -# Duke +# Welcome -{Give product intro here} +GULIO (Get Ur Life In Order) is a desktop app that provides a single consolidated and personalised workspace for NUS SOC students to organize your modules. It is optimized for use via a Command Line Interface, which SOC students will be familiar with. + +If you are having difficulties managing your schedule, lesson links and notes, GULIO is the app for you. With the ability to store lesson details, tasks, notes and more, GULIO is a one-stop solution for all your university scheduling needs. Useful links: -* [User Guide](UserGuide.md) -* [Developer Guide](DeveloperGuide.md) -* [About Us](AboutUs.md) +* For users, see our [User Guide](UserGuide.md). +* For developers, refer to our [Developer Guide](DeveloperGuide.md). +* To know more about us, click [here](AboutUs.md). diff --git a/docs/UML Templates.pptx b/docs/UML Templates.pptx new file mode 100644 index 0000000000..957d52f1ce Binary files /dev/null and b/docs/UML Templates.pptx differ diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..449f630ede 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,928 @@ -# User Guide +[<== Back to Home](README.md) -## Introduction + +# User guide -{Give a product intro} +**GULIO (Get Ur Life In Order)** is a desktop app that provides a single consolidated and personalised workspace for NUS SoC students to organize their modules. GULIO is optimized for use via a Command Line Interface (CLI) which SoC students will be familiar with typing in, instead of a Graphical User Interface (GUI). -## Quick Start +If you are having difficulties managing your schedule, lesson links and notes, GULIO is the app for you. With the ability to store lesson details, tasks, notes and more, GULIO is a one-stop solution for all your university scheduling needs. -{Give steps to get started quickly} +This guide is written for users like you, for reference when there is a particular feature that you are unsure of how to use. It provides all the necessary information required for you to fully utilize GULIO's capabilities. -1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +  -## Features +---- -{Give detailed description of each feature} +# Table of Contents -### Adding a todo: `todo` -Adds a new item to the list of todo items. +* [How to Use This Guide](#how-to-use-this-guide) +* [Quick Start](#quick-start) +* [Features](#features) + * [Overview](#overview) + * [Dashboard Commands](#dashboard-commands) + * [Listing all dashboard commands : `help`](#listing-all-dashboard-commands--help) + * [Exiting the program : `exit`](#exiting-the-program--exit) + * [Opening a module : `open`](#opening-a-module--open) + * [Adding a module : `add`](#adding-a-module--add) + * [Deleting a module : `del`](#deleting-a-module--del) + * [Listing all modules : `mods`](#listing-all-modules--mods) + * [Module Commands](#module-commands) + * [Listing all module commands : `help`](#listing-all-module-commands--help) + * [Closing a module : `close`](#closing-a-module--close) + * [Showing module information : `info`](#showing-module-information--info) + * [Adding a lesson : `add lsn`](#adding-a-lesson--add-lsn) + * [Deleting a lesson : `del lsn`](#deleting-a-lesson--del-lsn) + * [Editing a lesson : `edit lsn`](#editing-a-lesson--edit-lsn) + * [Opening lesson link : `link`](#opening-lesson-link--link) + * [Listing all teaching staff : `tch`](#listing-all-teaching-staff--tch) + * [Listing all lessons : `lsn`](#listing-all-lessons--lsn) + * [Adding a task : `add task`](#adding-a-task--add-task) + * [Deleting a task : `del task`](#deleting-a-task--del-task) + * [Editing a task : `edit task`](#editing-a-task--edit-task) + * [Marking task as done : `mark`](#marking-task-as-done--mark) + * [Marking task as undone : `unmark`](#marking-task-as-undone--unmark) + * [Listing all tasks : `task`](#listing-all-tasks--task) + * [Adding a cheat-sheet : `add cs`](#adding-a-cheat-sheet--add-cs) + * [Deleting a cheat-sheet : `del cs`](#deleting-a-cheat-sheet--del-cs) + * [Editing a cheat-sheet : `edit cs`](#editing-a-cheat-sheet--edit-cs) + * [Listing all cheat-sheets : `cs`](#listing-all-cheat-sheets--cs) + * [Data & Storage](#data--storage) + * [Automatic Saving](#automatic-saving) + * [Manual Editing Outside GULIO](#manual-editing-outside-of-gulio) + * [Text Editor](#text-editor) +* [FAQ](#faq) +* [Command Summary](#command-summary) -Format: `todo n/TODO_NAME d/DEADLINE` +  -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +---- -Example of usage: +
-`todo n/Write the rest of the User Guide d/next week` +# How to Use This Guide -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +#### Icons used in this guide: -## FAQ +💡 - indicates a tip that may be useful to you.
+⚠ - indicates a warning that you should take note of. -**Q**: How do I transfer my data to another computer? +#### Commands will be presented in the following format: -**A**: {your answer here} +> ### Command function : *keyword* +> +> Summary of actions involved. +> +> **Format:**
+> `command format` +> +> **Example:** (if any)
+> +> +> +> +> +>
Step number
Label >> What you input
What you will get.
+> +> **Result** - _Outcome of command_ (if any) -## Command Summary +  -{Give a 'cheat sheet' of commands here} +---- -* Add todo `todo n/TODO_NAME d/DEADLINE` +
+ +# Quick Start + +Download the latest version of GULIO from [here](https://github.com/AY2021S2-CS2113T-W09-3/tp/releases). + +### Requirements: + +Java 11 and above
+ +> 💡 Verify this by running the command “java --version” in command prompt (for Windows users) or Terminal (for Mac and Linux users). + +### Steps: + +1. Move the `GULIO.jar` file to your preferred directory. +1. Open command prompt (for Windows users) or Terminal (for Mac and Linux users), +1. Navigate to the directory of your GULIO.jar file. +1. Run the command “java -jar gulio.jar” to start GULIO. + > 💡 The file name is not case-sensitive, so both `gulio.jar` and `GULIO.jar` works. + +  + +

+ Command Line GULIO
+ Figure 1 - Example of Opening GULIO in Command Prompt +

+ +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +
+ +# Features + +## Overview + +GULIO has a 2-layer system, consisting of the dashboard layer, and the module layer. In both layers, you have access to a different set of commands. + +

+ 2-Layer System
+ Figure 2 - Visualisation of GULIO’s 2-layer system +

+ +On start up, you will be on the dashboard layer where you have an overview of all your modules. You have access to module management commands like adding, deleting or opening a particular module. + +> 💡 Please refer to the section [Dashboard Commands](#dashboard-commands) for information regarding commands at the dashboard layer.
+ +Opening a module puts you on the module layer where you can interact with data within the module. + +> 💡 Please refer to the section [Module Commands](#module-commands) for information regarding commands at the module layer. + +To identify which layer you are on, simply check the tag beside your input. + +* “GULIO” indicates that you are at the dashboard layer. +* A module code (e.g. “CS2113T”) indicates that you are within that module. + + + + + + +
+

+ Dashboard Label
+ Figure 3a - Dashboard Layer +

+
+

+ Module Label
+ Figure 3b - Module Layer +

+
+ +Each module can store two types of data: lesson and task. Lessons refer to your lectures, labs and tutorials, which are all recurring events. Meanwhile, tasks are used to store one-time events, like your homework, quizzes and any other activities with a deadline. + + + +### Fields in a lesson: + +| Field | Description | +| --- | --- | +| Lesson type | Lecture, lab or tutorial. | +| Day & time | Information on when the lesson happens. | +| Link | Online meeting link for the lesson. | +| Teaching staff name | Name of the lesson's teacher. | +| Teaching staff email | Email of the lesson's teacher. | + +### Fields in a task: + +| Field | Description | +| --- | --- | +| Task name | A short title for the task. | +| Deadline | Deadline of task in DD-MM-YYYY format. | +| Remarks | Additional information on the task. | +| Graded status | True or false, whether the task is graded. | +| Done status | True or false, whether the task is done. | + +Additionally, you can store your lecture notes in GULIO using the cheat-sheet feature. Cheat-sheets are stored as text files and GULIO has a built-in text editor that can be used to edit them. Cheat-sheets are unformatted so that users can focus on writing the content.
+ +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +
+ +## Dashboard Commands +These are commands used on the dashboard layer, when no modules have been selected. Commands here deal with the creation of modules, as well as accessing modules. + +  + +### Listing all dashboard commands : _help_ + +Use this command to view all commands accessible from the dashboard layer. + +**Format:**
+`help` + +  + +### Exiting the program : _exit_ + +Use this command to exit GULIO. + +**Format:**
+`exit` + +  + +### Opening a module : _open_ + +Use this command to open an existing module. + +**Format:**
+`open ` + +**Example:** + + + + + + +
Step #1
GULIO >> open CS2113T

Opening CS2113T.

<Overview for CS2113T>
Lecture - Friday 4pm - 6pm
Tutorial - Wednesday 9am - 10am

Undone tasks:
1. iP increments
2. Weekly exercises
+ +**Result** - GULIO moves from dashboard to module layer, and the module CS2113T is loaded. + +> 💡 Module name is auto-converted to uppercase, hence is not case-sensitive. + +  + +### Adding a module : _add_ + +Use this command to add a new module with the specified name. + +**Format:**
+`add ` + +**Example:** + + + + + + +
Step #1
GULIO >> add CS2113T

Added CS2113T to the module list.
+ +**Result** - A new module called CS2113T is added. + +> 💡 Module name is auto-converted to uppercase, hence is not case-sensitive. + +  + +### Deleting a module : _del_ + +Use this command to delete existing modules. + +Upon entering the command, GULIO will show the list of current modules. Then, specify the indices corresponding to the modules you would like to delete. + +**Format:**
+`del` + +**Example:** + + + + + + + + + + + +
Step #1
GULIO >> del

Which modules would you like to delete?
1. CS2107
2. CS2113T
3. CS2101

Please enter the indices of the modules you would like to delete.
Separate indices with a blank space.
Step #2
1 3
Removed CS2107 from the module list.
Removed CS2101 from the module list.
+ +**Result** - Modules CS2107 and CS2101 are removed from the module list. + +> 💡 Separate indices with a space. Invalid indices will be ignored. + +  + +### Listing all modules : _mods_ + +Use this command to view all existing modules. + +**Format:**
+`mods` + +**Example:** + + + + + + +
Step #1
GULIO >> mods

Modules in your list:
1. CS2101
2. CS2113T
+ +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +
+ +## Module Commands + +These are commands used on the module layer, when a module has been selected. Commands here deal with modifying the data corresponding to the specified module. + +  + +### Listing all module commands : _help_ + +Use this command to view all commands accessible from the module layer. + +**Format:**
+`help` + +  + +### Closing a module : _close_ + +Use this command to close the current module and go back to the dashboard layer. + +**Format:**
+`close` + +  + + + +### Showing module information : _info_ + +Use this command to get a summary of the module, including lessons and undone tasks. + +**Format:**
+`info` + +**Example:** + + + + + + +
Step #1
CS2113T >> info

<Overview for CS2113T>
Lecture - Friday 4pm - 6pm
Tutorial - Wednesday 9am - 10am

Undone tasks:
1. iP increments - 22 Feb 2021 (Overdue by 32 days)
+ +**Result** - Prints overview of module, including lessons and undone tasks. + +  + +### Adding a lesson : _add lsn_ + +Use this command to add a new lesson to the module. + +Choose from one of the three lesson types: `lecture`,`lab` or `tutorial`. Follow one of the following formats to include additional information with the lesson. + +**Format:**
+`add lsn `
+`add lsn ;; `
+`add lsn ;; ;; `
+`add lsn ;; ;; ;; `
+`add lsn ;; ;; ;; ;; ` + +**Example:** + + + + + + +
Step #1
CS2113T >> add lsn tutorial ;; Wednesday 9am - 10am ;; https://zoom.us/

Added Tutorial to lesson list.
+ +**Result** - Adds “Tutorial” to the module's list of lessons, with specified details. + +> ⚠ Only accepts 3 lesson types: `Lecture`, `Lab` and `Tutorial`. Lesson type is auto-capitalised when displayed, hence input for the field is not case-sensitive. + +> 💡 To skip an input, leave a blank in between the field separators. For example, +> +> `add lsn tutorial ;; ;; ;; Prof Akshay ;; akshay@email.com` +> +> will add “Tutorial” to the module's list of lessons with only the given teaching staff name and email. The fields “day & time” and “link” were skipped. +> +> Note: “lesson type” cannot be skipped. + +  + +### Deleting a lesson : _del lsn_ + +Use this command to delete lessons from the module. + +Upon entering the command, GULIO will show the list of lessons in the module. Then, specify the indices corresponding to the lessons you would like to delete. + +**Format:**
+`del lsn` + +**Example:** + + + + + + + + + + +
Step #1
CS2113T >> del lsn

Which lessons would you like to delete?
1. Lecture (Teaching staff name: Prof Isa)
2. Tutorial (Wednesday 9am - 10am)

Please enter the indices of the lessons you would like to delete.
Separate indices with a blank space.
Step #2
1 2
Removed Lecture.
Removed Tutorial.
+ +**Result** - The lessons “Lecture” and “Tutorial” are removed from the list of lessons. + +> 💡 Separate indices with a space. Invalid indices will be ignored. + +  + +### Editing a lesson : _edit lsn_ + +Use this command to edit a lesson in the module. + +Upon entering the command, GULIO will show the list of lessons in the module. Select the lesson you would like to edit by entering its index. Then, you will see a list of fields you can edit. Specify the indices of the fields you would like to edit. Finally, for each field you have selected, enter a new value. + +**Format:**
+`edit lsn` + +**Example:** + + + + + + + + + + + + + + + + + + +
Step #1
CS2113T >> edit lsn

Which lessons would you like to edit?
1. Lecture - Wed 10am
    www.zoom.com
    Prof Isa
    isa@gmail.com
Step #2
1

Editing: LECTURE
Which fields would you like to edit?
1. Time and day
2. Lesson link
3. Teaching staff name
4. Teaching staff email

Separate indices with a blank space.
Step #3
1

Enter new time and day:
Step #4
Thursday 9am
Updated time and day.
+ +**Result** - Edits time and day of "Lecture". + +> 💡 While only one lesson can be edited at a time, you can edit multiple fields simultaneously. As such, separate multiple indices with a space. Invalid indices will be ignored. + +  + +### Opening lesson link : _link_ + +Use this command to open your lesson links. + +Upon entering the command, GULIO will show the list of lessons in the module. Then, specify the indices corresponding to the lessons whose links you would like to open. + +**Format:**
+`link` + +**Example:** + + + + + + + + + + +
Step #1
CS2113T >> link

Which lesson’s link would you like to open?
1. Lecture
2. Tutorial
Step #2
1
Opening lecture link in browser.
+ +**Result** - Opens the Zoom link used for lectures in a browser. + +> 💡 Multiple links can be opened at once. As such, separate indices with a space. Invalid indices will be ignored. + +> ⚠ Web protocol must be included in lesson link for security purposes. Links without protocol (e.g. no https nor http at the start) will be deemed invalid. + +  + +### Listing all teaching staff : _tch_ + +Use this command to view all teaching staff in the module. + +**Format:**
+`tch` + +**Example:** + + + + + + +
Step #1
CS2113T >> tch

Teaching staff for CS2113T:
1. Prof Akshay - profakshay@email.com
2. Cheng Xianhao - cxh@email.com
+ +  + +### Listing all lessons : _lsn_ + +Use this command to view all lessons in the module. + +**Format:**
+`lsn` + +**Example:** + + + + + + +
Step #1
CS2113T >> lsn

Lessons for CS2113T:
1. Lecture - Friday 4pm - 6pm
    https://nus-sg.zoom.us/j/def
    Prof Akshay
    profakshay@email.com
2. Tutorial - Wednesday 9am - 10am
    https://nus-sg.zoom.us/j/abc
    meeting - Wednesday 2pm - 4pm
+ +  + +### Adding a task : _add task_ + +Use this command to add a new task to the module. + +Follow one of the two formats below. + +**Format:**
+`add task ;; `
+`add task ;; ;; ` + +**Example:** + + + + + + + + + + +
Step #1
CS2113T >> add task iP submission ;; 3-3-2021 ;; Attach JAR

Is this task graded? (Y / N)
Step #2
Y
Added iP submission to task list.
+ +**Result** - Adds “iP submission” to the module’s list of tasks, with the specified details. + +> 💡 Deadline has to be in the DD-MM-YYYY format. +> +> Note: a task should minimally have a "task name" and "deadline", so only the "remarks" field can be skipped. + +> 💡 When asked if the task is graded, both "y" and "Y" will work. Response is not case-sensitive for the user's convenience. + +  + + + +### Deleting a task : _del task_ + +Use this command to delete tasks from the module. + +Upon entering the command, GULIO will show the list of tasks in the module. Then, specify the indices corresponding to the tasks you would like to delete. + +**Format:**
+`del task` + +**Example:** + + + + + + + + + + +
Step #1
CS2113T >> del task

Which tasks would you like to delete?
1. weekly exercise
2. watch video snippets
3. iP submission

Please enter the indices of the tasks you would like to delete.
Separate indices with a blank space.
Step #2
1 3
Removed weekly exercise.
Removed iP submission.
+ +**Result** - The tasks “weekly exercise” and “iP submission” are removed from the list of tasks. + +> 💡 Separate indices with a space. Invalid indices will be ignored. + +  + +### Editing a task : _edit task_ + +Use this command to edit a task in the module. + +Upon entering the command, GULIO will show the list of tasks in the module. Select the task you would like to edit by entering its index. Then, you will see a list of fields you can edit. Specify the indices of the fields you would like to edit. Finally, for each field you have selected, enter a new value. + +**Format:**
+`edit task` + +**Example:** + + + + + + + + + + + + + + + + + + +
Step #1
CS2113T >> edit task

Which task would you like to edit?
1. weekly exercise - 23 Feb 2021
    Do before 2359.
    Graded
2. lecture quiz - 26 Feb 2021
    Complete before next lecture.
    Not graded
Step #2
2

Editing: lecture quiz
Which fields?
1. Description
2. Deadline
3. Remarks
4. Graded/not graded

Separate indices with a blank space.
Step #3
2

New deadline:
Step #4
2-3-2021
Updated deadline.
+ +**Result** - Edits the deadline for the task “lecture quiz”. + +> 💡 While only one task can be edited at a time, you can edit multiple fields simultaneously. As such, separate multiple indices with a space. Invalid indices will be ignored. + +  + +### Marking task as done : _mark_ + +Use this command to mark tasks as done. + +Upon entering the command, GULIO will show the list of tasks in the module that are undone. Then, specify the indices corresponding to the tasks you would like to mark as done. + +**Format:**
+`mark` + +**Example:** + + + + + + + + + + +
Step #1
CS2113T >> mark

Which undone tasks have you completed?
1. weekly exercise
2. lecture quiz
3. read up notes

Please enter the indices of the tasks you would like to mark as done.
Separate indices with a blank space.
Step #2
1 2
Marked weekly exercise as done.
Marked lecture quiz as done.
+ +**Result** - The tasks “weekly exercise” and “lecture quiz” are marked as done. + +> 💡 Separate indices with a space. Invalid indices will be ignored. + +  + +### Marking task as undone : _unmark_ + +Use this command to mark tasks as undone. + +Upon entering the command, GULIO will show the list of tasks in the module that are done. Then, specify the indices corresponding to the tasks you would like to mark as undone. + +**Format:**
+`unmark` + +**Example:** + + + + + + + + + + +
Step #1
CS2113T >> unmark

Which done tasks have you completed?
1. watch video snippets
2. iP submission

Please enter the indices of the tasks you would like to mark as done.
Separate indices with a blank space.
Step #2
1
Marked watch video snippets as undone.
+ +**Result** - The task “watch video snippets” is marked as undone. + +> 💡 Separate indices with a space. Invalid indices will be ignored. + +  + +### Listing all tasks : _task_ + +Use this command to view all the tasks in the module. + +Done and undone tasks are separated. Undone tasks are sorted by earliest deadline. + +**Format:**
+`task` + +**Example:** + + + + + + +
Step #1
CS2113T >> task

Tasks for CS2113T:

[Undone]
You have completed all your tasks.

[Done]
1. iP increments - 22 Feb 2021
+ +  + +### Adding a cheat-sheet : _add cs_ + +Use this command to add a new cheat-sheet to the module and open it in the text editor. + +**Format:**
+`add cs ` + +**Example:** + + + + + + +
Step #1
CS2113T >> add cs lecture notes

lecture notes has been added to your Cheatsheet folder.
+ +**Result** - Adds new cheat-sheet “lecture notes” and opens it in the text editor. + +> ⚠ Please do not include any file extension in the cheat-sheet name. + +  + +### Deleting a cheat-sheet : _del cs_ + +Use this command to delete the specified cheat-sheet from the module. + +**Format:**
+`del cs ` + +**Example:** + + + + + + +
Step #1
CS2113T >> del cs lecture notes

lecture notes has been deleted!
+ +**Result** - Deletes cheat-sheet “lecture notes”. + +> ⚠ Please do not include any file extension in the cheat-sheet name. + +  + +### Editing a cheat-sheet : _edit cs_ + +Use this command to open and edit the specified cheat-sheet in the text editor. + +**Format:**
+`edit cs ` + +**Example:** + + + + + + +
Step #1
CS2113T >> edit cs lecture notes

Opened lecture notes.
+ +**Result** - Opens cheat-sheet “lecture notes” in text editor. + +> ⚠ Please do not include any file extension in the cheat-sheet name. + +  + + + +### Listing all cheat-sheets : _cs_ + +Use this command to view the list of cheat-sheets you have for the module. + +**Format:**
+`cs` + +**Example:** + + + + + + +
Step #1
CS2113T >> cs

Here is your list of cheat-sheets:

1. lecture notes
+ +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +
+ +# Data & Storage + +### Automatic Saving + +Data for each module is stored in their respective module’s text file, located in a folder called `Data` created in the same directory as the `GULIO.jar` file. When moving this folder, please ensure that it is placed in the same directory as your `GULIO.jar` file. After every modification, changes are automatically saved to the file. + +### Manual Editing Outside of GULIO + +Files can be modified outside of the program. Invalid inputs will not be loaded when the program is run and will be removed from the file. To ensure that your data loads properly, please follow the format stated in the data files strictly. + +#### Format for Lessons: + +1. `lesson | | ` +1. `lesson | | | ` +1. `lesson | | | | ` +1. `lesson | | | | | ` + +> ⚠ Only accepts 3 lesson types: “Lecture”, “Lab” and “Tutorial”. + +#### Format for Tasks: + +1. `task | | | | ` +1. `task | | | | | ` + +> ⚠ For `` and ``, use ‘T’ for true and ‘F’ for false. + +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +
+ +# Text Editor + +GULIO comes with a built-in text-editor that allows you to edit cheat sheets directly. This text editor can be accessed via the add and edit cheat sheet commands. Cheat-sheets are stored in the `Cheatsheet` directory within their respective module directories as `.txt` files. + +

+ Text Editor
+ Figure 4 - GULIO Text Editor +

+ +In the text editor, you can type in your notes in the text field. When done, remember to save any changes via the `ctrl-s` shortcut. To close the text editor, simply press the escape key on your keyboard. Using `ctrl-up` and `ctrl-down`, you can enlarge or shrink text respectively. + +| Shortcuts | Actions | +| --- | --- | +| `ctrl-s` | Save cheat-sheet. | +| `ctrl-up` | Enlarge text. | +| `ctrl-down` | Shrink text. | +| `esc` | Exit test editor | + +> ⚠ Do not include file extension (e.g. ”.txt”) when creating or editing the cheat-sheet. + +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +
+ +# FAQ + +Here are some frequently asked questions that you may have regarding GULIO. + +**Q:** How do I open GULIO?
+**A:** Please refer to the steps [here](#quick-start). + +**Q:** Why am I unable to open cheat-sheet in Windows Subsystem for Linux (WSL)?
+**A:** GULIO's cheat-sheet feature requires the usage of GUI, which is not available on WSL. + +**Q:** Where can I find the data files for GULIO?
+**A:** Data used by GULIO are stored in the `Data` directory, which is created in the same location that the `GULIO.jar` file is in. + +**Q:** Where can I submit any feedback or issues regarding GULIO?
+**A:** You can create an issue [here](https://github.com/AY2021S2-CS2113T-W09-3/tp/issues). Thank you very much. + +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- + +# Command Summary + +### Dashboard Commands Summary + +| Keyword | Format | +| --- | --- | +| help | `help` | +| exit | `exit` | +| open | `open `| +| add | `add ` | +| delete | `del` | +| modules | `mods` | + +
+ +### Module Commands Summary + +| Keyword | Format | +| --- | --- | +| help | `help` | +| close | `close` | +| info | `info` | +| add lesson | `add lsn ;; ;; ;; ;; ` | +| delete lesson | `del lsn` | +| edit lesson | `edit lsn` | +| link | `link` | +| teacher | `tch` | +| lessons | `lsn` | +| add task | `add task ;; ;; ` | +| delete task | `del task` | +| edit task | `edit task` | +| mark | `mark` | +| unmark | `unmark` | +| tasks | `task` | +| add cheat-sheet | `add cs ` | +| delete cheat-sheet | `del cs ` | +| edit cheat-sheet | `edit cs ` | +| cheat-sheets | `cs` | + +  + +[🡅 Back to Table of Contents](#table-of-contents) + +---- diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..cbfd0e6129 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,2 @@ +theme: jekyll-theme-cayman +title: GULIO \ No newline at end of file diff --git a/docs/developerGuideImages/addCheatSheetCommand-v2.1-part1.png b/docs/developerGuideImages/addCheatSheetCommand-v2.1-part1.png new file mode 100644 index 0000000000..2ddb5bcd9a Binary files /dev/null and b/docs/developerGuideImages/addCheatSheetCommand-v2.1-part1.png differ diff --git a/docs/developerGuideImages/addCheatSheetCommand-v2.1-part2.png b/docs/developerGuideImages/addCheatSheetCommand-v2.1-part2.png new file mode 100644 index 0000000000..7f1fe5a515 Binary files /dev/null and b/docs/developerGuideImages/addCheatSheetCommand-v2.1-part2.png differ diff --git a/docs/developerGuideImages/addLesson1.png b/docs/developerGuideImages/addLesson1.png new file mode 100644 index 0000000000..7bf64dd3a2 Binary files /dev/null and b/docs/developerGuideImages/addLesson1.png differ diff --git a/docs/developerGuideImages/addLessonCommand.png b/docs/developerGuideImages/addLessonCommand.png new file mode 100644 index 0000000000..48d93429ce Binary files /dev/null and b/docs/developerGuideImages/addLessonCommand.png differ diff --git a/docs/developerGuideImages/add_lesson_to_lesson_list.png b/docs/developerGuideImages/add_lesson_to_lesson_list.png new file mode 100644 index 0000000000..54b0825f84 Binary files /dev/null and b/docs/developerGuideImages/add_lesson_to_lesson_list.png differ diff --git a/docs/developerGuideImages/architecture.png b/docs/developerGuideImages/architecture.png new file mode 100644 index 0000000000..b7614305a4 Binary files /dev/null and b/docs/developerGuideImages/architecture.png differ diff --git a/docs/developerGuideImages/command-component-level.png b/docs/developerGuideImages/command-component-level.png new file mode 100644 index 0000000000..e3095b43a7 Binary files /dev/null and b/docs/developerGuideImages/command-component-level.png differ diff --git a/docs/developerGuideImages/designModel.png b/docs/developerGuideImages/designModel.png new file mode 100644 index 0000000000..4af6dee25e Binary files /dev/null and b/docs/developerGuideImages/designModel.png differ diff --git a/docs/developerGuideImages/loadModuleCode.png b/docs/developerGuideImages/loadModuleCode.png new file mode 100644 index 0000000000..3153620860 Binary files /dev/null and b/docs/developerGuideImages/loadModuleCode.png differ diff --git a/docs/developerGuideImages/main-loop-sequence-diagram.png b/docs/developerGuideImages/main-loop-sequence-diagram.png new file mode 100644 index 0000000000..74fb706f95 Binary files /dev/null and b/docs/developerGuideImages/main-loop-sequence-diagram.png differ diff --git a/docs/developerGuideImages/setSelectedModule.png b/docs/developerGuideImages/setSelectedModule.png new file mode 100644 index 0000000000..430fe3106e Binary files /dev/null and b/docs/developerGuideImages/setSelectedModule.png differ diff --git a/docs/developerGuideImages/storage.png b/docs/developerGuideImages/storage.png new file mode 100644 index 0000000000..4574bb655b Binary files /dev/null and b/docs/developerGuideImages/storage.png differ diff --git a/docs/developerGuideImages/writeModule.png b/docs/developerGuideImages/writeModule.png new file mode 100644 index 0000000000..cbb5cb437c Binary files /dev/null and b/docs/developerGuideImages/writeModule.png differ diff --git a/docs/team/8kdesign.md b/docs/team/8kdesign.md new file mode 100644 index 0000000000..539043f47a --- /dev/null +++ b/docs/team/8kdesign.md @@ -0,0 +1,132 @@ +[<== Back to About Us](../AboutUs.md) + +# Chong Wen Hao - Project Portfolio Page + +## Overview + +GULIO is a module planner designed for efficiency for people that can type fast. It is capable of storing lessons and tasks for individual modules, as well as lesson notes via cheat-sheets. + +## Summary of Contributions + +[Click here to view code contribution.](https://nus-cs2113-ay2021s2.github.io/tp-dashboard/?search=CS2113T-W09-3&sort=groupTitle&sortWithin=title&since=&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=8kdesign&tabRepo=AY2021S2-CS2113T-W09-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other) + +### Enchancements Contributed: + +1. Implemented ModuleList class and storage system, excluding cheat-sheets.
+1. Implemented shortcut listener for text editor. + > Using KeyListener, I implemented shortcuts such as "ctrl-s" to save. The shortcuts would call methods that Hemrish created in the TextEditor class. +1. Cleaned up code for most components.
+ > I cleaned up the code after we merged our parts for V1.0 as there were inconsistencies and duplicates. This includes shifting of constants and messages to their respective classes, removing of duplicate methods, etc... +1. Added days remaining for undone task and used it for sorting. + +### Contributions to Documentation: + +1. Added data & storage section. +1. Created skeleton for the command section. +1. Converted user guide from Google Docs into Markdown. + > The team originally did the user guide on Google Docs. I converted it to markdown before we split it up and pushed our respective portions. It was really tedious, but not difficult. + +### Contributions to Developer Guide: + +1. Created the architecture diagram and did the parts relating to storage. +1. Added instruction for manual testing. +1. Converted developer guide from Google Docs into Markdown. + > Like the user guide, we did the developer guide on Google Docs. I converted it into markdown before splitting it up with the others. + +### Contribution to Team-Based Tasks: + +1. Did the release for V1.0. +1. Fixed minor bugs for some components. + > Examples include [fixing the formatting of various list commands](https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/74/files), as well as [getting focus for the text editor](https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/130/files). +1. Created issues and added labels on the issue tracker. + +### Review/mentoring contributions: + +1. Reviewed pull requests.
+ > Examples: + > 1. https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/129 + > 1. https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/125 + > 1. https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/43 +1. Helped Hemrish resolve some issues regarding cheat-sheets. + +### Contributions Beyond the Team +1. Identified bugs during dry run PE. Click [here](https://github.com/8kdesign/ped/issues) to view. +1. Answered a few question on the forum. + > Examples: + > 1. https://github.com/nus-cs2113-AY2021S2/forum/issues/2 + > 1. https://github.com/nus-cs2113-AY2021S2/forum/issues/11 + +
+ +## [Optional] Contributions to User Guide + +Example of parts that I wrote: + +> # Data & Storage +> +> ### Automatic Saving +> +> Data for each module is stored in their respective module’s text file, located in a folder called “Data” created in the same directory as the GULIO.jar file. When moving this folder, please ensure that it is placed in the same directory as your GULIO.jar file. After every modification, changes are automatically saved to the file. +> +> ### Manual Editing Outside of GULIO +> +> Files can be modified outside of the program. Invalid inputs will not be loaded when the program is run and will be removed from the file. To ensure that your data loads properly, please follow the format stated in the data files strictly. +> +> #### Format for Lessons: +> +> 1. `lesson | | ` +> 1. `lesson | | | ` +> 1. `lesson | | | | ` +> 1. `lesson | | | | | ` +> +> > ⚠ Only accepts 3 lesson types: “lecture”, “lab” and “tutorial”. +> +> #### Format for Tasks: +> +> 1. `task | | | | ` +> 1. `task | | | | | ` +> +> > ⚠ For `` and ``, use ‘T’ for true and ‘F’ for false. + +
+ +## [Optional] Contributions to Developer Guide + +Example of parts that I wrote: + +> ### Loading & Storing of Data +> +> This section covers how the _Storage_ component works, from the loading of all module codes to the loading of individual module and creation of data files. +> +> #### Saving of Data +> +> The `Writer` class is responsible for writing any changes to the module’s data file, as well as creating the file itself. Interaction with the `Writer` class is done through the `ModuleList` class, whose methods are called by the other components of the app. +> +>

+> writeModule() Sequence Diagram
+> Figure 11 - writeModule() Sequence Diagram +>

+> +> Whenever some data in a module changes, the command that made those changes would call the method `writeModule()` in `ModuleList` to update the change in the data file. This method would then call a method of the same name in the `Writer` class, which overwrites the existing data in the file with the new data. +> +> Due to how much data needs to be written each time, we decided to split the data file by module. That way, we only need to overwrite the module's data when changes are made. +> +> #### Loading of Data +> +> The `Loader` class is responsible for identifying all the modules currently added, as well as loading the data file of the selected class. Methods in the `Loader` class are accessed by the other components via the `ModuleList` class. +> +>

+> loadModuleCodes() Sequence Diagram
+> Figure 12 - loadModuleCodes() Sequence Diagram +>

+> +> To identify modules in the “Data” directory, Duke would call `loadModuleCodes()` method in the `ModuleList`. This method would then call the `getModules()` method in `Loader`, which returns a list of module codes. For each of the identified module code, `ModuleList` would call its own `insertModule()` method to add it to the module list. +> +>

+> setSelectedModule() Sequence Diagram
+> Figure 13 - setSelectedModule() Sequence Diagram +>

+> +> When a module is selected via the `setSelectedModule()` method, the specified module code would be searched for in the module list. If it is inside, `loadModule()` method in the `Loader` would be called. This method reads the module’s data file for data and adds them into a new instance of `Module` class. This `Module` is then returned to `ModuleList` and set as the selected module. +> +> If the `Loader` failed to load the file, null would be returned. If null is not returned, `ModuleList` would sort the data and then use `Writer` to override the existing file. This is done to remove invalid entries that were initially in the file. diff --git a/docs/team/aliciatay-zls.md b/docs/team/aliciatay-zls.md new file mode 100644 index 0000000000..080bf01099 --- /dev/null +++ b/docs/team/aliciatay-zls.md @@ -0,0 +1,167 @@ +[<== Back to About Us](../AboutUs.md) + +# Alicia Tay - Project Portfolio Page + +## Overview + +GULIO is a university module manager designed for efficiency when used by fast typists. It is capable of storing lessons and tasks for individual modules and lesson notes via cheat-sheets. + +## Summary of Contributions + +Click **[here](https://nus-cs2113-ay2021s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&since=&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=aliciatay-zls&tabRepo=AY2021S2-CS2113T-W09-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code)** to view code contribution. + +### Enhancements Implemented + +1. Implemented and maintained the task commands. + * `AddTaskCommand`, `DeleteTaskCommand`, `ListTasksCommand`, `MarkAsDoneCommand`, `MarkAsUndoneCommand`: I implemented these classes very differently from the iP. Mainly, I kept looking for ways to do more SLAP, application of DRY principle, SoC, and less coupling, to all the code I wrote this time around. Figuring out what constitutes as a duplicate task also took me a while. + * `EditTaskCommand`: Invoking this command involves multiple rounds of prompting and checking input, which had to be handled carefully and displayed in a standardised way with the other commands. +1. Wrote basic JUnit tests for the 6 commands. + * Wrote `AddTaskCommandTest` with Isa's help. +1. Implemented the `Task` class with Wen Hao and some general methods in `UI`. +1. General code cleanup. + * Helped to standardise printing across commands. + * Improved printing for some commands (e.g. printing task list, differentiated same lesson types when printing for `DeleteLessonCommand`). + * Removed duplicate/similar constants across all classes. + * Refactored code. + +### Contributions to Documentation + +1. Visualisation of GULIO's 2-layer system (Figure 2 in UG, Figure 1 in DG). +1. Worked with Wen Hao on the "How to Use This Guide", "Overview" sections. +1. "Editing a task : `edit task`" section. +1. General editing, proof-reading, organisation of the UG. + * Both on Google Docs and multiple rounds in Markdown. + +### Contributions to Developer Guide + +1. Set up skeleton of the DG at the start. +1. "Adding of Cheat-Sheet" section. + * 2 sequence diagrams and their write-ups. + * Done twice due to changes in the code (bug fixes, enhancements) after V2.0/V2.1. +1. All sections under "Appendix: Requirements". +1. "Design Considerations", "UI Component" sections. +1. General editing, proof-reading of the DG. + * Both on Google Docs and multiple rounds in Markdown. + +### Contributions to Team-Based Tasks + +1. Fixed and identified minor bugs in some components. +1. Maintained issue tracker. +1. Wrote the README.md. +1. Tidied up AboutUs.md, added GULIO banner for Github Pages. + +### Review/Mentoring Contributions + +1. Code enhancements. + * [Suggestion to Ivan implemented, to remove loop for EditLessonCommand and EditTaskCommand. Worked together to standardise printing.](https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/161) +1. Provide checks for PRs with many LOC changed. + * [Wenhao's code cleanup.](https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/47) + * https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/45 +1. Gave suggestions to rename methods/classes to follow coding standards. + * https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/218 + * https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/22 +1. Gave feedback for teammates' class diagrams offline/in [Google Docs](https://docs.google.com/document/d/1fzG0QCIz7RxnNNVTO4eB7D0EsAKdZy7OJcZ0gHMRZ84/edit#heading=h.c6ibq4vl5nbr). + +### Contributions Beyond the Project Team +1. [Suggestions for improvements in PE-D implemented by the team](https://github.com/aliciatay-zls/ped/issues) + +
+ +## [Optional] Contributions to Developer Guide + +Some examples: + +> ### Adding of Cheat-Sheet +> +> The `AddCheatSheetCommand` class enables the creation, addition and saving of a ".txt" file to the current module’s “Cheatsheet” directory (see Figure 5). Upon creating a new instance of it and calling the `execute()` method on it, the GULIO Text Editor application will also be automatically started. +> +> An invocation of the `add cheat-sheet` command involves the following interactions: +> +>

+> AddCheatSheetCommand Invocation Sequence Diagram A
+> Figure 10a - AddCheatSheetCommand Invocation Sequence Diagram +>

+> +> When `AddCheatSheetCommand` is executed, it gets the currently selected module by calling the `getSelectedModule()` method in `ModuleList`. It then checks if the file name given by the user is invalid. If no, `AddCheatSheetCommand` proceeds to call the `getDirectoryPath()` method in itself to obtain the directory where the cheat-sheet would be saved to. It then calls the `openTextEditor()` method itself, which will interact with the `TextEditor` class, which is a Singleton class, in the following way: +> +>

+> AddCheatSheetCommand Invocation Sequence Diagram B
+> Figure 10b - Opening the Text Editor +>

+> +> The `openTextEditor()` method will first check if the single instance of `TextEditor` is `null`. If it is, then there is no GULIO Text Editor window currently opened and `openTextEditor()` proceeds to call the `createNew()` method of `TextEditor`. This initialises the single instance of `TextEditor` by calling the class constructor, which sets up, loads from previous data (if any) and opens the Text Editor for the user. The user can now start typing into the Text Editor. +> +> ## Appendix: Requirements +> +> ### Product Scope +> +> #### Target user profile: +> +> 1. Needs a consolidated and personalisable workspace to organize their university modules +> 1. Prefers desktop apps over other types +> 1. Can type fast +> 1. Is comfortable using CLI apps +> 1. Is familiar with command-line shell environment +> +> #### Value proposition: +> +> Efficiently view and update regularly-needed information on modules and deadlines using a single keyboard. +> +> ### User Stories +> +> > 💡 Priority levels:
+> > `1`: High (Must have)
+> > `2`: Medium (Good to have)
+> > `3`: Low (Unlikely to have) +> +> | Priority | As a/an ... | I want to ... | So that I can ... | +> | --- | --- | --- | --- | +> | 1 | new user | see available commands | refer to the help page when I forget how to use the app | +> | 1 | NUS student | add a module | store useful information by module that I can easily refer to | +> | 1 | NUS student faced with e-learning | add a lesson | consolidate regularly-needed information such as Zoom links by tutorial/lecture, for quick access before the lesson | +> | 1 | busy NUS student | add a task | keep track of assignments and deadlines for a module in an organised to-do list | +> | 1 | NUS student | get an overview of the module / lesson / task list | filter out specific information with a single command | +> | 2 | NUS student | delete a module | store the information only temporarily, e.g. for the semester/term | +> | 2 | NUS SoC student | open a module’s cheat sheet(s) | I have a handy list of tools for the module, tests and exams at my disposal | +> | 2 | NUS SoC student with many team projects | View a module’s project team information and contact details | keep track of the various teams I am in and communicate more efficiently with my teammates | +> | 3 | busy NUS student | sort tasks by graded and done status | know which tasks are of highest priority | +> +> _Note: some are features to be implemented in future._ +> +> ### Non-Functional Requirements +> +> 1. Text editor will only work on OS with GUI support. +> 1. All other features will work on any mainstream OS. +> 1. It should work for students taking up to 10 modules. +> 1. Each module should be able to store 100 tasks without issues. +> 1. Every command should respond within 10s of input on a typical modern computer. +> +> ### Glossary +> +> * Mainstream OS: Windows, Linux, Unix, OS-X +> * CLI: Command-Line Interface +> * GUI: Graphical User Interface +> * Module: A university module + +
+ +## [Optional] Contributions to User Guide + +Some examples: +> +>

+> 2-Layer System
+> Figure 2 - Visualisation of GULIO’s 2-layer system +>

+> +>   +> +> > ⚠ Only accepts 3 lesson types: `Lecture`, `Lab` and `Tutorial`. Lesson type is auto-capitalised when displayed, hence input for the field is not case-sensitive. +> +> > 💡 To skip an input, leave a blank in between the field separators. For example, +> > +> > `add lsn tutorial ;; ;; ;; Prof Akshay ;; akshay@email.com` +> > +> > will add “Tutorial” to the module's list of lessons with only the given teaching staff name and email. The fields “day & time” and “link” were skipped. +> > +> > Note: “lesson type” cannot be skipped. diff --git a/docs/team/h-horizon.md b/docs/team/h-horizon.md new file mode 100644 index 0000000000..f66ef19707 --- /dev/null +++ b/docs/team/h-horizon.md @@ -0,0 +1,134 @@ +[<== Back to About Us](../AboutUs.md) + +# Hemrish Bundhoo - Project Portfolio Page + +## Overview + +GULIO is a module planner designed for efficiency when used by someone that can type fast. It is capable of storing lessons and tasks for individual modules, as well as lesson notes via cheat-sheets. + +## Summary of Contributions + +[Click here to view code contribution.](https://nus-cs2113-ay2021s2.github.io/tp-dashboard/?search=CS2113T-W09-3&sort=groupTitle&sortWithin=title&since=&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=H-horizon&tabRepo=AY2021S2-CS2113T-W09-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code) + +### Enhancements Contributed: + +1. Implemented commands involved in manipulating Lesson objects, excluding edit lesson.
+ > I implemented AddLessonCommand, DeleteLessonCommand and ListLessonsCommand to add, remove and view lesson objects. +1. Implemented OpenLessonListCommand. + > I implemented this class that allows the user to open the link, inputted when creating a lesson, in a web browser. The issue I had to resolve was that the default command to open a link in a browser was platform dependent. +1. Implemented ViewTeachingStaffCommand + > I implemented the command needed to view the tutor's name and email address for a particular lesson. +1. Implemented ModuleInfoCommand + > I implemented this command to print a summary about a module. +1. Implemented commands involved in manipulating Cheat-sheet files.
+ > I implemented AddCheatSheetCommand, DeleteCheatSheetCommand, EditCheatSheetCommand and ListCheatSheetCommand to add, remove, edit and view Cheat-sheet files of a particular module. +1. Implemented TextEditor. + > I implemented the TextEditor class to allow the user to input and edit cheat-sheets. A problem I faced when implementing the editor was that if the editor was used multiple times for one Gulio Session, it didn't flush data from previous files after they were closed. I then had to append the text area to null each time the text editor was invoked. + +### Contributions to Documentation: + +1. Documented Lesson and Cheat-sheet commands + > For each command I implemented, I added a brief description, the command format, a sample input and the expected output. +1. Added text editor section. + > As the one responsible for the text editor, I did the part explaining to the user how GULIO's text editor works and what he/she can do with it. + +### Contributions to Developer Guide: +1. Text Editor component in Design section. + >I wrote the description for the text editor component under Design in the DG as I was the one who implemented it in our codebase. +1. Add lesson command class in Implementation section. + > I drew two of the sequence diagrams describing the implementation of the add lesson command under the Implementation section. + + +### Contribution to Team-Based Tasks: + +1. Fixed minor bugs for some components. +1. Maintained issue tracker. + +### Review/mentoring contributions: +1. Full list of PRs approved or commented on by me can be found [here](https://github.com/AY2021S2-CS2113T-W09-3/tp/pulls?q=is%3Apr+reviewed-by%3AH-horizon+is%3Aclosed+). + +### Contributions Beyond the Team +1. [Sharing useful information in the forum](https://github.com/nus-cs2113-AY2021S2/forum/issues/26#issuecomment-770166991) +1. [Bugs reported in other team's products during dry-run](https://github.com/H-horizon/ped) + +
+ +## Contributions to the User Guide (Extracts)[Optional] +Extract of contributions: + +### Adding a lesson : _add lsn_ + +Adds a new lesson with specified lesson type and information to the current module. + +**Format:**
+`add lsn `
+`add lsn ;; `
+`add lsn ;; ;; `
+`add lsn ;; ;; ;; `
+`add lsn ;; ;; ;; ;; ` + +**Example:** + +| Step | When You Enter This: | You Get This: | +| --- | --- | --- | +| 1 | add lsn tutorial ;; Wednesday 9 am - 10am ;; https://nus-sg.zoom.us/j/abc | Added tutorial to lesson list. | + +**Result** - Adds “tutorial” to the module's list of lessons, with specified details. + +> ⚠ Only accepts 3 lesson types: “lecture”, “lab” and “tutorial”. + +> 💡 To skip an input, leave a blank in between the field separators. For example, +> +> `add lesson tutorial ;; ;; ;; Prof Akshay ;; akshay@email.com` +> +> will add “tutorial” to the module's list of lessons with only the given teaching staff name and email. The fields “day & time” and “link” were skipped. +> +> Note: “lesson type” cannot be skipped. + +  + +
+ +### Contributions to the Developer Guide (Extracts)[Optional] + +Extract of contributions: + +### Editor component + +**API**: `TextEditor.java` + +The _Editor_ component is responsible for opening the text editor to add or edit cheat-sheets/notes. It consists of two classes: + +#### Text Editor + +* Sets up the text editor +* Loads existing file from Cheatsheet directory within a module for the edit cheat-sheet command +* Flushes out the text from the editor when a different or new file is opened. +* Adjusts the font size of the text within the editor +* Detects mouse input to change font style and save the text +* Saves the text from the text editor into a file + +#### ShortcutListener + +* Detects keyboard input for shortcuts + +  + +

+ AddLessonCommand Constructor Sequence Diagram
+ Figure 8 - AddLessonCommand Constructor Sequence Diagram +

+ +The newly created `Lesson` object is then passed to a new `AddLessonCommand` object as an argument. + +

+ execute() AddLessonCommand Sequence Diagram
+ Figure 9 - execute() AddLessonCommand Sequence Diagram +

+ +`AddLessonCommand` then adds the `Lesson` object to the lesson list of a module. The lessons in the list are sorted by their lesson types each time a new lesson is added. `AddLessonCommand` also calls the `writeLesson()` method of `ModuleList` to update the change locally. + +  + + + diff --git a/docs/team/isaharon.md b/docs/team/isaharon.md new file mode 100644 index 0000000000..b094e79ef6 --- /dev/null +++ b/docs/team/isaharon.md @@ -0,0 +1,52 @@ +[<== Back to About Us](../AboutUs.md) + +# Isa Bin Haron - Project Portfolio Page + +## Overview + +GULIO is a CLI-based module planner designed for efficiency when used by someone that can type fast. It is meant for NUS SoC students and capable of storing lessons and tasks for individual modules, as well as lesson notes via cheat-sheets. + +## Summary of Contributions + +[Click here to view code contribution.](https://nus-cs2113-ay2021s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&since=&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=isaharon&tabRepo=AY2021S2-CS2113T-W09-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code) + +### Enhancements Implemented: + +1. Implemented Commands used within the Dashboard. +1. Implemented `enum` class, `DashboardCommands` and `ModuleCommands` to allow easier adding of new commands. +1. General code cleanup +1. Organized `Parser` class +1. Added exceptions and standardized printing of error messages + +### Contributions to Documentation: + +1. Documented dashboard commands + +### Contributions to Developer Guide: + +1. Drafted & finalized content for the `Architecture` section +1. Drafted & finalized content for `Command` component +1. Drafted some content for `Module` component, subsequently finalized descriptions for the `Module` component +1. Sequence diagram for `main` loop +1. Class diagram for `Command` component + +### Contribution to Team-Based Tasks: + +1. Created milestones +1. Created initial labels +1. Fix CI before first PR +1. General code enhancements + +### Review/mentoring contributions + +1. Full list of PRs approved or commented on by me can be found [here](https://github.com/AY2021S2-CS2113T-W09-3/tp/pulls?q=is%3Apr+is%3Aclosed+reviewed-by%3Aisaharon+). + +1. Examples of review where I communicated with my teammates on Github: + 1. [Review on Ivan's PR and suggested improvements](https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/70) + 1. [Review on Alicia's PR and requested changes](https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/41) + 1. [Review on Hemrish's PR and requested changes](https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/40) + +### Contributions beyond the project team + +1. [Reviewed Developer Guide of other team's project](https://github.com/nus-cs2113-AY2021S2/tp/pull/3#pullrequestreview-624820704) +1. [Reported various bugs for other team's product during PE dry-run](https://github.com/isaharon/ped/issues) \ No newline at end of file diff --git a/docs/team/ivanchongzhien.md b/docs/team/ivanchongzhien.md new file mode 100644 index 0000000000..bec06c0ebe --- /dev/null +++ b/docs/team/ivanchongzhien.md @@ -0,0 +1,153 @@ +[<== Back to About Us](../AboutUs.md) + +# Ivan Chong Zhi En - Project Portfolio Page + +## Overview +We designed GULIO, a CLI-based module planner intended to help users keep track of tasks and assignment deadlines for the modules they are taking. GULIO also stores information like online lesson links and contact information of the module teaching staff, serving as a one stop location for essential module information. + + +## Summary of Contributions + +[View code contribution](https://nus-cs2113-ay2021s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&since=&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=ivanchongzhien&tabRepo=AY2021S2-CS2113T-W09-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code) + +### Enhancements contributed + +1. Implemented a parser to parse inputs to GULIO + > Since the Parser class had a rather large scope where many other classes relied on it, I only focused on the Parser for v1.0 to ensure that the Parser was working reliably before we proceeded with more features. + > As we made progress and the parser seemed stable, I also refactored the parser code with Isa to improve its flow and logic. + +1. Implemented the edit lesson functionality + > For version 2.0, I implemented the command which allowed users to edit the values of previously entered lessons. +1. Shortened GULIO commands + > For version 2.1, we decided that some commands were too long and slowed the process of navigating and using GULIO. + > I shortened several of the long commands words (e.g. "lesson" to "lsn") to improve the speed at which the user can enter inputs. + +### Contributions to Documentation +1. As the one who implemented the edit lesson command, I covered the explanations and sample inputs for the edit lesson instruction. +1. I also updated the shortened commands in the User Guide to ensure consistency with our program. + +### Contributions to the Developer Guide +1. Parser component in Design section. + >I wrote the description for the Parser component under Design in the DG as I was the one who implemented it in our codebase. +1. Description and class diagram for the Model component in Design section. + > I drew the class diagram for the Model component and contributed to its description. +1. Add lesson command class in Implementation section. + > I worked with Hemrish on describing the implementation of the add lesson command under the Implementation section. + > I drew one of the sequence diagrams for this implementation. + +### Contributions to team-based tasks +1. Did the release for v2.0. +1. Maintained issue tracker by adding issues found during discussion with the appropriate labels. + +### Review/mentoring contributions +1. Full list of PRs approved or commented on by me can be found [here](https://github.com/AY2021S2-CS2113T-W09-3/tp/pulls?q=is%3Apr+is%3Aclosed+reviewed-by%3A%40me). + +2. Example of reviews where I communicated with teammates on Github: + 1. [Communication with Alicia and Hemrish regarding my PR](https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/77) + 1. [Review on Wen Hao's PR to use singleton class](https://github.com/AY2021S2-CS2113T-W09-3/tp/pull/123) + +### Contributions beyond the project team +1. [Raised question on forum](https://github.com/nus-cs2113-AY2021S2/forum/issues/15) +1. [Reported bugs during PE Dry Run](https://github.com/ivanchongzhien/ped/issues) + +
+ +## [Optional] Contribution to User Guide +Extract of contributions: + +### Editing a lesson : _edit lsn_ + +Lists all lessons for the module and asks the user for the index of the lesson to edit. Then, lists all editable fields and asks the user for the indices of the fields to edit. Lastly, for each selected field, the user inputs a new value. + +**Format:**
+`edit lsn` + +**Example:** + +| Step | When You Enter This: | You Get This: | +| --- | --- | --- | +| 1 | edit lsn | Which lessons would you like to edit?
1. lecture - Wed 10am
    www.zoom.com
    Prof Isa
    isa@gmail.com | +| 2 | 1 | Editing: LECTURE
Which fields would you like to edit?
1. Time and day
2. Lesson link
3. Teaching staff name
4. Teaching staff email

Separate indices with a blank space. | +| 3 | 1 2 | Enter new time and day: | +| 4 | Thursday 9am | Updated time and day.
Enter new lesson link: | +| 5 | www.googleclassroom.com | Updated lesson link. | + +**Result** - Edits time and day, as well as lesson link of "lecture". + +> 💡 While only one lesson can be edited at a time, you can edit multiple fields simultaneously. As such, separate multiple indices with a space. Invalid indices will be ignored. + +
+ +## [Optional] Contributions to Developer Guide +Extract of contributions: + +### Model component + +

+ Class Diagram of Model
+ Figure 5 - Class Diagram of Model +

+ +The Model component consists of classes that represent real-world objects related to the program. `ModuleList` represents the various modules that a typical SOC student may be taking within the semester. Each of these modules is encapsulated in the `Module` class which contains an ArrayList of `Lesson` and `Task` objects representing the lessons that would be conducted for the module and tasks that students have to complete for the modules they are taking. + +#### ModuleList: + +`ModuleList` is responsible for managing loaded modules in the program and keeping track if a user is at the dashboard or within a selected module. `ModuleList` interacts with instances of the `Loader` and `Writer` class to load and write data respectively to the storage files. It also contains methods to sort data. + +The `ModuleList` class contains the attributes: + +* ArrayList of module code strings +* Class-level member storing `Module` + +#### Module: + +The `Module` class contains the attributes: + +* Module code string +* ArrayList of `Lesson` +* ArrayList of `Task` + +#### Lesson: + +In SOC, lessons are conducted by a combination of lectures, tutorials or labs. To enforce this constraint, the Enum class `LessonType` contains a set of constants for lecture, tutorial and lab. + +The `Lesson` class contains attributes related to a typical course lesson: + +* Lesson Type, e.g. lab, tutorial or lecture +* Time and day of the lesson (stored as a String for flexibility) +* Link for online lessons +* Teaching Staff information encapsulated in `TeachingStaff` + +#### Teaching staff: + +Being enrolled in several modules, it would be useful for students to store names of the teaching staff the way they prefer to be addressed along with their email addresses in events that require the student to call upon or inquire a teaching staff for a module. + +The `TeachingStaff` class contains the attributes related to the teacher(s) of a particular lesson: + +* Name of the teacher +* Email address of the teacher + +#### Task: + +The `Task` class contains attributes related to an assignment, deadline or task in a university setting + +* Description of task +* Deadline of task +* Remarks +* Done status +* Graded status + +  + +--- + +### Add Lesson + +The `AddLessonCommand` class is responsible for the creation and addition of a new `Lesson` object to the lesson list of a given module. The following sequence diagrams shows how a new `Lesson` is created and added to the lesson list. + +

+ parse() Sequence Diagram
+ Figure 7 - parse() Sequence Diagram +

+ +The creation process is facilitated by the `Parser` class, which parses the appropriate arguments from the user input and initialises the `Lesson` object attributes with the parsed values. \ No newline at end of file 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/userGuideImages/2-layer.png b/docs/userGuideImages/2-layer.png new file mode 100644 index 0000000000..73d0da5472 Binary files /dev/null and b/docs/userGuideImages/2-layer.png differ diff --git a/docs/userGuideImages/Dashboard.png b/docs/userGuideImages/Dashboard.png new file mode 100644 index 0000000000..49358aaf8a Binary files /dev/null and b/docs/userGuideImages/Dashboard.png differ diff --git a/docs/userGuideImages/Module.png b/docs/userGuideImages/Module.png new file mode 100644 index 0000000000..8f1b386ed4 Binary files /dev/null and b/docs/userGuideImages/Module.png differ diff --git a/docs/userGuideImages/StartGULIO.png b/docs/userGuideImages/StartGULIO.png new file mode 100644 index 0000000000..72f9296218 Binary files /dev/null and b/docs/userGuideImages/StartGULIO.png differ diff --git a/docs/userGuideImages/TextEditor.png b/docs/userGuideImages/TextEditor.png new file mode 100644 index 0000000000..5af13e3983 Binary files /dev/null and b/docs/userGuideImages/TextEditor.png differ diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java index 5c74e68d59..16d050d6fe 100644 --- a/src/main/java/seedu/duke/Duke.java +++ b/src/main/java/seedu/duke/Duke.java @@ -1,21 +1,46 @@ package seedu.duke; -import java.util.Scanner; +import seedu.duke.commands.Command; +import seedu.duke.exception.DukeException; +import seedu.duke.module.ModuleList; +import seedu.duke.parser.Parser; +import seedu.duke.ui.UI; + +import static seedu.duke.common.Constants.EMPTY_STRING; +import static seedu.duke.common.Messages.DIVIDER; +import static seedu.duke.common.Messages.MESSAGE_WELCOME; +import static seedu.duke.common.Messages.NEWLINE; public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ + + private static final UI ui = new UI(); + 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()); + start(); + run(); + } + + private static void start() { + ui.printMessage(DIVIDER + NEWLINE + MESSAGE_WELCOME); + ModuleList.loadModuleCodes(); + } + + private static void run() { + boolean isExit = false; + Parser parser = new Parser(); + + while (!isExit) { + ui.printMessage(DIVIDER); + ui.printModuleIndicator(); + String input = ui.readUserInput(); + ui.printMessage(EMPTY_STRING); + try { + Command command = parser.parse(input); + command.execute(ui); + isExit = command.isExit(); + } catch (DukeException e) { + ui.printError(e); + } + } } } diff --git a/src/main/java/seedu/duke/commands/AddCheatSheetCommand.java b/src/main/java/seedu/duke/commands/AddCheatSheetCommand.java new file mode 100644 index 0000000000..a835bf0e9f --- /dev/null +++ b/src/main/java/seedu/duke/commands/AddCheatSheetCommand.java @@ -0,0 +1,84 @@ +package seedu.duke.commands; + +import seedu.duke.editor.TextEditor; +import seedu.duke.exception.CommandException; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static seedu.duke.common.Constants.FOLDER_PATH; +import static seedu.duke.common.Constants.PATH_DELIMITER; +import static seedu.duke.common.Constants.STRING_CHEATSHEET; +import static seedu.duke.common.Constants.TXT_FORMAT; +import static seedu.duke.common.Messages.MESSAGE_CHEATSHEET_ADDED; +import static seedu.duke.common.Messages.MESSAGE_CHEAT_SHEET_ALREADY_EXISTS; +import static seedu.duke.common.Messages.MESSAGE_CLOSE_CHEATSHEET_FIRST; +import static seedu.duke.common.Messages.MESSAGE_INVALID_FILE_NAME; +import static seedu.duke.common.InputValidator.hasInvalidCharacter; + +public class AddCheatSheetCommand extends Command { + public static String fileName; + + //@@author H-horizon + public AddCheatSheetCommand(String nameOfFile) { + fileName = nameOfFile; + } + + @Override + public void execute(UI ui) throws CommandException { + Module module = ModuleList.getSelectedModule(); + boolean isInvalidFileName = hasInvalidCharacter(fileName); + if (fileName.isEmpty() || isInvalidFileName) { + throw new CommandException(MESSAGE_INVALID_FILE_NAME); + } + try { + String directoryPath = getDirectoryPath(module, ui); + String filePath = directoryPath + fileName + TXT_FORMAT; + Path path; + path = Paths.get(filePath); + openTextEditor(ui, path, filePath); + } catch (InvalidPathException e) { + throw new CommandException(MESSAGE_INVALID_FILE_NAME); + } + } + + public void openTextEditor(UI ui, Path path, String filePath) { + + if (Files.exists(path)) { + ui.printMessage(MESSAGE_CHEAT_SHEET_ALREADY_EXISTS); + } else { + if (!TextEditor.isNull()) { + //already open + ui.printMessage(MESSAGE_CLOSE_CHEATSHEET_FIRST); + return; + } + try { + File file = new File(filePath); + file.createNewFile(); + ui.printMessage(String.format(MESSAGE_CHEATSHEET_ADDED, fileName)); + TextEditor.createNew(filePath, fileName); + } catch (NullPointerException | IOException e) { + ui.printMessage(MESSAGE_INVALID_FILE_NAME); + } + } + } + + public String getDirectoryPath(Module module, UI ui) { + String directoryPath = FOLDER_PATH + PATH_DELIMITER + module.getModuleCode() + PATH_DELIMITER + + STRING_CHEATSHEET + PATH_DELIMITER; + try { + Path path = Paths.get(directoryPath); + assert Files.isDirectory(path) : "Directory missing"; + } catch (InvalidPathException e) { + ui.printMessage(MESSAGE_INVALID_FILE_NAME); + } + return directoryPath; + } +} diff --git a/src/main/java/seedu/duke/commands/AddLessonCommand.java b/src/main/java/seedu/duke/commands/AddLessonCommand.java new file mode 100644 index 0000000000..bcf42f947d --- /dev/null +++ b/src/main/java/seedu/duke/commands/AddLessonCommand.java @@ -0,0 +1,36 @@ +package seedu.duke.commands; + +import seedu.duke.lesson.Lesson; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import static seedu.duke.common.Messages.MESSAGE_ADDED_LESSON; + +/** + * Represents the command used to add a new lesson to the list of lessons. + */ +public class AddLessonCommand extends Command { + + private final Lesson lesson; + + //@@author H-horizon + public AddLessonCommand(Lesson lesson) { + this.lesson = lesson; + } + + /** + * Adds new lesson to selected module. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + Module module = ModuleList.getSelectedModule(); + module.addLesson(lesson); + String lessonTypeString = lesson.getLessonTypeString(); + ui.printMessage(String.format(MESSAGE_ADDED_LESSON, lessonTypeString)); + ModuleList.writeModule(); + ModuleList.sortLessons(); + } +} diff --git a/src/main/java/seedu/duke/commands/AddModuleCommand.java b/src/main/java/seedu/duke/commands/AddModuleCommand.java new file mode 100644 index 0000000000..82bd171bf7 --- /dev/null +++ b/src/main/java/seedu/duke/commands/AddModuleCommand.java @@ -0,0 +1,33 @@ +package seedu.duke.commands; + +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import static seedu.duke.common.Messages.MESSAGE_DUPLICATE_MODULE; +import static seedu.duke.common.Messages.MESSAGE_ADDED_MODULE; + +public class AddModuleCommand extends Command { + + private final String moduleCode; + + //@@author isaharon + public AddModuleCommand(String moduleCode) { + this.moduleCode = moduleCode; + } + + /** + * Creates new module. + * + * @param ui Instance of UI. + * @throws CommandException Specified module already exists. + */ + @Override + public void execute(UI ui) throws CommandException { + if (ModuleList.addModule(moduleCode)) { + ui.printMessage(String.format(MESSAGE_ADDED_MODULE, moduleCode)); + } else { + throw new CommandException(String.format(MESSAGE_DUPLICATE_MODULE, moduleCode)); + } + } +} diff --git a/src/main/java/seedu/duke/commands/AddTaskCommand.java b/src/main/java/seedu/duke/commands/AddTaskCommand.java new file mode 100644 index 0000000000..7a70d5c500 --- /dev/null +++ b/src/main/java/seedu/duke/commands/AddTaskCommand.java @@ -0,0 +1,41 @@ +package seedu.duke.commands; + +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import static seedu.duke.common.Messages.MESSAGE_ADDED_TASK; +import static seedu.duke.common.Messages.MESSAGE_TASK_CHECK_GRADED; +import static seedu.duke.common.CommonMethods.isTaskGraded; + +public class AddTaskCommand extends Command { + + private final Task task; + + //@@author aliciatay-zls + public AddTaskCommand(Task task) { + this.task = task; + } + + /** + * Adds new task to selected module. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + Module module = ModuleList.getSelectedModule(); + boolean isAddTaskAllowed = module.isAddTaskAllowed(ui, task); + if (!isAddTaskAllowed) { + return; + } + ui.printMessage(MESSAGE_TASK_CHECK_GRADED); + boolean isGraded = isTaskGraded(ui); + task.setGraded(isGraded); + module.addTask(task); + ui.printMessage(String.format(MESSAGE_ADDED_TASK, task.getDescription())); + ModuleList.writeModule(); + ModuleList.sortTasks(); + } +} diff --git a/src/main/java/seedu/duke/commands/Command.java b/src/main/java/seedu/duke/commands/Command.java new file mode 100644 index 0000000000..faea746016 --- /dev/null +++ b/src/main/java/seedu/duke/commands/Command.java @@ -0,0 +1,14 @@ +package seedu.duke.commands; + +import seedu.duke.exception.CommandException; +import seedu.duke.ui.UI; + +public abstract class Command { + + public abstract void execute(UI ui) throws CommandException; + + public boolean isExit() { + return false; + } + +} diff --git a/src/main/java/seedu/duke/commands/DeleteCheatSheetCommand.java b/src/main/java/seedu/duke/commands/DeleteCheatSheetCommand.java new file mode 100644 index 0000000000..8204e7336a --- /dev/null +++ b/src/main/java/seedu/duke/commands/DeleteCheatSheetCommand.java @@ -0,0 +1,48 @@ +package seedu.duke.commands; + +import seedu.duke.exception.CommandException; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static seedu.duke.common.Constants.TXT_FORMAT; +import static seedu.duke.common.Messages.MESSAGE_FILE_DOES_NOT_EXIST; +import static seedu.duke.common.Messages.MESSAGE_FILE_HAS_BEEN_DELETED; + +public class DeleteCheatSheetCommand extends AddCheatSheetCommand { + + public static String filePath; + + //@@author H-horizon + public DeleteCheatSheetCommand(String nameOfFile) { + super(nameOfFile); + } + + @Override + public void execute(UI ui) throws CommandException { + Module module = ModuleList.getSelectedModule(); + try { + String directoryPath = getDirectoryPath(module, ui); + + + filePath = directoryPath + fileName + TXT_FORMAT; + Path path = Paths.get(filePath); + performFunction(ui, path); + } catch (InvalidPathException | IOException e) { + ui.printMessage(String.format(MESSAGE_FILE_DOES_NOT_EXIST, fileName)); + Command command = new ListCheatSheetsCommand(); + command.execute(ui); + } + } + + public void performFunction(UI ui, Path path) throws IOException { + Files.delete(path); + ui.printMessage(String.format(MESSAGE_FILE_HAS_BEEN_DELETED, fileName)); + } +} diff --git a/src/main/java/seedu/duke/commands/DeleteLessonCommand.java b/src/main/java/seedu/duke/commands/DeleteLessonCommand.java new file mode 100644 index 0000000000..3fee1265f0 --- /dev/null +++ b/src/main/java/seedu/duke/commands/DeleteLessonCommand.java @@ -0,0 +1,101 @@ +package seedu.duke.commands; + +import seedu.duke.lesson.Lesson; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.parser.ParserUtil; +import seedu.duke.ui.UI; + +import java.util.ArrayList; + +import static seedu.duke.common.Constants.DELETE; +import static seedu.duke.common.Constants.TYPE_LESSON; +import static seedu.duke.common.Messages.FORMAT_INDEX_ITEM; +import static seedu.duke.common.Messages.FORMAT_INDEX_LESSON_DETAILS; +import static seedu.duke.common.Messages.MESSAGE_ENTER_INDICES; +import static seedu.duke.common.Messages.MESSAGE_LESSONS_LIST_EMPTY; +import static seedu.duke.common.Messages.MESSAGE_LESSONS_TO_DELETE; +import static seedu.duke.common.Messages.MESSAGE_REMOVED_LESSON; + +/** + * Represents the command used to delete a lesson from the list of lessons. + */ +public class DeleteLessonCommand extends Command { + + //@@author H-horizon + + /** + * Deletes all lessons corresponding to specified indices. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + Module module = ModuleList.getSelectedModule(); + ArrayList lessonList = module.getLessonList(); + verifyLessonsToDelete(lessonList, ui); + ModuleList.sortLessons(); + } + + /** + * Validates lessons to delete from list. + * + * @param ui Instance of UI. + * @param lessonList ArrayList of lessons in specified module. + */ + private void verifyLessonsToDelete(ArrayList lessonList, UI ui) { + if (lessonList.size() == 0) { + ui.printMessage(MESSAGE_LESSONS_LIST_EMPTY); + } else { + ui.printMessage(MESSAGE_LESSONS_TO_DELETE); + printLessons(lessonList, ui); + ui.printMessage(String.format(MESSAGE_ENTER_INDICES, TYPE_LESSON, DELETE)); + String line = ui.readUserInput(); + ArrayList indices = ParserUtil.checkIndices(line, lessonList.size()); + deleteLessonsFromList(lessonList, indices, ui); + ModuleList.writeModule(); + } + } + + /** + * Prints list of lessons in specified module. + * For each lesson, prints lesson type and exactly one detail (a field that was successfully added) + * to differentiate same lesson types (e.g. multiple tutorials). + * Does not print any detail for the lesson if none of its fields have been filled yet. + * + * @param lessonList ArrayList of lessons in specified module. + * @param ui Instance of UI. + */ + private static void printLessons(ArrayList lessonList, UI ui) { + int counter = 1; + for (Lesson lesson : lessonList) { + String lessonType = lesson.getLessonTypeString(); + if (lesson.getDetailsStringIfAny() != null) { + String appendString = lesson.getDetailsStringIfAny(); + ui.printMessage(String.format(FORMAT_INDEX_LESSON_DETAILS, counter, lessonType, appendString)); + } else { + ui.printMessage(String.format(FORMAT_INDEX_ITEM, counter, lessonType)); + } + counter++; + } + } + + /** + * Removes lessons corresponding to the indices from the specified module. + * + * @param lessonList ArrayList of lessons in specified module. + * @param indices Indices of lessons to delete. + * @param ui Instance of UI. + */ + public static void deleteLessonsFromList(ArrayList lessonList, ArrayList indices, UI ui) { + int pointer = 1; + for (int index : indices) { + int modifiedIndex = index - pointer; + Lesson lesson = lessonList.get(modifiedIndex); + String lessonType = lesson.getLessonTypeString(); + ui.printMessage(String.format(MESSAGE_REMOVED_LESSON, lessonType)); + ModuleList.getSelectedModule().removeLesson(modifiedIndex); + pointer++; + } + } +} diff --git a/src/main/java/seedu/duke/commands/DeleteModuleCommand.java b/src/main/java/seedu/duke/commands/DeleteModuleCommand.java new file mode 100644 index 0000000000..729544b4e7 --- /dev/null +++ b/src/main/java/seedu/duke/commands/DeleteModuleCommand.java @@ -0,0 +1,80 @@ +package seedu.duke.commands; + +import seedu.duke.common.Messages; +import seedu.duke.module.ModuleList; +import seedu.duke.parser.ParserUtil; +import seedu.duke.ui.UI; + +import java.util.ArrayList; + +import static seedu.duke.common.Constants.DELETE; +import static seedu.duke.common.Constants.INDEX_FIRST; +import static seedu.duke.common.Constants.TYPE_MODULE; +import static seedu.duke.common.Messages.MESSAGE_ENTER_INDICES; +import static seedu.duke.common.Messages.MESSAGE_MODULE_TO_DELETE; +import static seedu.duke.common.Messages.MESSAGE_NO_MODULES_TO_DELETE; +import static seedu.duke.common.Messages.MESSAGE_REMOVED_MODULE; +import static seedu.duke.common.Messages.NEWLINE; + +public class DeleteModuleCommand extends Command { + + //@@author isaharon + /** + * Requests for list of indices to delete. + * Deletes all modules corresponding to specified indices. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + if (ModuleList.getModules().isEmpty()) { + ui.printMessage(MESSAGE_NO_MODULES_TO_DELETE); + return; + } + ui.printMessage(getDeleteInfo()); + + String userInput = ui.readUserInput(); + ArrayList indices = ParserUtil.checkIndices(userInput, ModuleList.getSize()); + if (indices.isEmpty()) { + return; + } + + ArrayList deletedModulesCodes = ModuleList.deleteModules(indices); + String deletedMessage = getDeletedModuleCodes(deletedModulesCodes); + ui.printMessage(deletedMessage); + } + + /** + * Returns string containing instructions and module list. + * + * @return Message to print. + */ + private String getDeleteInfo() { + StringBuilder stringBuilder = new StringBuilder(MESSAGE_MODULE_TO_DELETE); + ArrayList modules = ModuleList.getModules(); + for (String moduleCode : modules) { + int counter = modules.indexOf(moduleCode) + 1; + stringBuilder.append(String.format(Messages.FORMAT_LIST_ITEMS, counter, moduleCode)); + stringBuilder.append(NEWLINE); + } + stringBuilder.append(String.format(MESSAGE_ENTER_INDICES, TYPE_MODULE, DELETE)); + return stringBuilder.toString(); + } + + /** + * Returns string containing list of deleted modules. + * + * @param deletedModuleCodes Arraylist of module codes. + * @return Message to print. + */ + private String getDeletedModuleCodes(ArrayList deletedModuleCodes) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < deletedModuleCodes.size(); i++) { + if (i != INDEX_FIRST) { + stringBuilder.append(NEWLINE); + } + stringBuilder.append(String.format(MESSAGE_REMOVED_MODULE, deletedModuleCodes.get(i))); + } + return stringBuilder.toString(); + } +} diff --git a/src/main/java/seedu/duke/commands/DeleteTaskCommand.java b/src/main/java/seedu/duke/commands/DeleteTaskCommand.java new file mode 100644 index 0000000000..6f286c8f10 --- /dev/null +++ b/src/main/java/seedu/duke/commands/DeleteTaskCommand.java @@ -0,0 +1,62 @@ +package seedu.duke.commands; + +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.util.ArrayList; + +import static seedu.duke.common.CommonMethods.getSpecifiedTasks; +import static seedu.duke.common.Constants.TYPE_TASK; +import static seedu.duke.common.Constants.DELETE; +import static seedu.duke.common.Messages.MESSAGE_ENTER_INDICES; +import static seedu.duke.common.Messages.MESSAGE_NO_TASK_MODIFIED; +import static seedu.duke.common.Messages.MESSAGE_REMOVED_TASK; +import static seedu.duke.common.Messages.MESSAGE_TASKS_TO_DELETE; +import static seedu.duke.common.Messages.MESSAGE_TASK_LIST_EMPTY; + +public class DeleteTaskCommand extends Command { + + //@@author aliciatay-zls + /** + * Displays list of all tasks, asks user for a string of space-separated + * indices and deletes all corresponding tasks. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + Module module = ModuleList.getSelectedModule(); + ArrayList taskList = module.getTaskList(); + if (taskList.isEmpty()) { + ui.printMessage(String.format(MESSAGE_TASK_LIST_EMPTY, DELETE)); + return; + } + printPrompt(ui, taskList); + ArrayList selectedTasks = getSpecifiedTasks(ui, taskList); + if (selectedTasks.isEmpty()) { + ui.printMessage(MESSAGE_NO_TASK_MODIFIED); + return; + } + for (Task task : selectedTasks) { + String description = task.getDescription(); + ui.printMessage(String.format(MESSAGE_REMOVED_TASK, description)); + module.removeTask(task); + } + ModuleList.writeModule(); + ModuleList.sortTasks(); + } + + /** + * Prints prompt for indices of tasks to remove from the list. + * + * @param ui Instance of UI. + * @param taskList Array list of tasks. + */ + private void printPrompt(UI ui, ArrayList taskList) { + ui.printMessage(MESSAGE_TASKS_TO_DELETE); + ui.printTasks(taskList, true); + ui.printMessage(String.format(MESSAGE_ENTER_INDICES, TYPE_TASK, DELETE)); + } +} diff --git a/src/main/java/seedu/duke/commands/EditCheatSheetCommand.java b/src/main/java/seedu/duke/commands/EditCheatSheetCommand.java new file mode 100644 index 0000000000..ef681ccce1 --- /dev/null +++ b/src/main/java/seedu/duke/commands/EditCheatSheetCommand.java @@ -0,0 +1,45 @@ +package seedu.duke.commands; + +import seedu.duke.editor.TextEditor; +import seedu.duke.exception.CommandException; +import seedu.duke.ui.UI; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static seedu.duke.common.Messages.MESSAGE_CLOSE_CHEATSHEET_FIRST; +import static seedu.duke.common.Messages.MESSAGE_FILE_DOES_NOT_EXIST; +import static seedu.duke.common.Messages.MESSAGE_OPEN_FILE; + +public class EditCheatSheetCommand extends DeleteCheatSheetCommand { + + //@@author H-horizon + public EditCheatSheetCommand(String nameOfFile) { + super(nameOfFile); + } + + @Override + public void performFunction(UI ui, Path path) throws IOException { + openTextEditor(ui, path, filePath); + } + + @Override + public void openTextEditor(UI ui, Path path, String filePath) { + if (Files.exists(path)) { + if (TextEditor.createNew(filePath, fileName)) { + ui.printMessage(String.format(MESSAGE_OPEN_FILE, fileName)); + } else { + ui.printMessage(String.format(MESSAGE_CLOSE_CHEATSHEET_FIRST, fileName)); + } + } else { + ui.printMessage(String.format(MESSAGE_FILE_DOES_NOT_EXIST, fileName)); + Command command = new ListCheatSheetsCommand(); + try { + command.execute(ui); + } catch (CommandException e) { + assert false : "Directory not found"; + } + } + } +} diff --git a/src/main/java/seedu/duke/commands/EditLessonCommand.java b/src/main/java/seedu/duke/commands/EditLessonCommand.java new file mode 100644 index 0000000000..9f12b80b3d --- /dev/null +++ b/src/main/java/seedu/duke/commands/EditLessonCommand.java @@ -0,0 +1,217 @@ +package seedu.duke.commands; + +import seedu.duke.exception.ParserException; +import seedu.duke.lesson.Lesson; +import seedu.duke.lesson.TeachingStaff; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.parser.ParserUtil; +import seedu.duke.ui.UI; + +import java.util.ArrayList; + +import static seedu.duke.common.Constants.EDIT_INDEX_DAY_TIME; +import static seedu.duke.common.Constants.EDIT_INDEX_LINK; +import static seedu.duke.common.Constants.EDIT_INDEX_TEACHER_NAME; +import static seedu.duke.common.Constants.LESSON_FIELD_1_TIME_DAY; +import static seedu.duke.common.Constants.LESSON_FIELD_2_LINK; +import static seedu.duke.common.Constants.LESSON_FIELD_3_T_NAME; +import static seedu.duke.common.Constants.LESSON_FIELD_4_T_EMAIL; +import static seedu.duke.common.Constants.MAX_EDITABLE_FIELDS; +import static seedu.duke.common.Messages.FORMAT_INDEX_ITEM; +import static seedu.duke.common.Messages.MESSAGE_EDITED_FIELD; +import static seedu.duke.common.Messages.MESSAGE_FIELDS_TO_EDIT; +import static seedu.duke.common.Messages.MESSAGE_FIELD_BEING_EDITED; +import static seedu.duke.common.Messages.MESSAGE_INVALID_LESSON_EMAIL; +import static seedu.duke.common.Messages.MESSAGE_INVALID_LESSON_LINK; +import static seedu.duke.common.Messages.MESSAGE_LESSONS_LIST_EMPTY; +import static seedu.duke.common.Messages.MESSAGE_LESSON_TO_EDIT; +import static seedu.duke.common.Messages.MESSAGE_NOT_UPDATED; +import static seedu.duke.common.Messages.MESSAGE_NO_CHANGES; +import static seedu.duke.common.Messages.MESSAGE_SEPARATE_INDICES; +import static seedu.duke.common.Messages.PROMPT_ENTER_FIELD_DETAILS; +import static seedu.duke.common.Messages.WARNING_NO_VALID_INPUT; + +//@@author ivanchongzhien +public class EditLessonCommand extends Command { + private final String[] fields = {LESSON_FIELD_1_TIME_DAY, LESSON_FIELD_2_LINK, + LESSON_FIELD_3_T_NAME, LESSON_FIELD_4_T_EMAIL}; + + /** + * Edits the fields of selected lesson. + * + * @param ui instance of UI + */ + @Override + public void execute(UI ui) { + Module module = ModuleList.getSelectedModule(); + ArrayList lessonsList = module.getLessonList(); + + Lesson lessonToEdit = getLessonToEdit(lessonsList, ui); + + if (lessonToEdit == null) { + return; + } + + ui.printMessage(String.format(MESSAGE_FIELD_BEING_EDITED, lessonToEdit.getLessonType().toString())); + editLessonFields(lessonToEdit, ui); + ModuleList.writeModule(); + ModuleList.sortLessons(); + } + + /** + * Obtains the fields to be edited from the user and makes the changes + * to the Lesson object. + * + * @param lesson the lesson to be edited + * @param ui instance of UI for printing prompts + */ + private void editLessonFields(Lesson lesson, UI ui) { + ArrayList indices = getFieldIndicesFromUser(ui); + String newFieldValue; + + for (int index : indices) { + newFieldValue = getNewFieldFromUser(index, ui); + setNewFieldValue(lesson, newFieldValue, index, ui); + } + } + + /** + * Determines the lesson the user wants to edit. + * + * @param lessonList list of lessons that can be edited + * @param ui instance of UI for printing prompts and warnings + * @return the lesson chosen by the user + */ + private Lesson getLessonToEdit(ArrayList lessonList, UI ui) { + Lesson chosenLesson; + + if (lessonList.size() == 0) { + ui.printMessage(MESSAGE_LESSONS_LIST_EMPTY); + return null; + } + + int index = getLessonIndexFromUser(lessonList, ui); + + if (index == 0) { + ui.printMessage(MESSAGE_NO_CHANGES); + return null; + } + chosenLesson = lessonList.get(index - 1); + return chosenLesson; + } + + /** + * Prompts user to choose the lesson to be edited. + * + * @param lessonList list of lessons that can be edited + * @param ui instance of UI for printing user prompts + * @return the index of the chosen lesson, 0 if user enters invalid index + */ + private int getLessonIndexFromUser(ArrayList lessonList, UI ui) { + int index = 0; + + ui.printMessage(MESSAGE_LESSON_TO_EDIT); + ListLessonsCommand.printLessons(lessonList, ui); + + String line = ui.readUserInput(); + + try { + index = ParserUtil.checkIndex(line, lessonList.size()); + } catch (ParserException e) { + ui.printError(e); + } + return index; + } + + /** + * Prompts user to choose the fields of the lesson to be edited. + * + * @param ui instance of UI for printing prompts and warnings + * @return an array list of indices chosen by the user + */ + private ArrayList getFieldIndicesFromUser(UI ui) { + + ui.printMessage(MESSAGE_FIELDS_TO_EDIT); + printFieldsAsList(ui); + ui.printMessage(MESSAGE_SEPARATE_INDICES); + + String input = ui.readUserInput(); + + ArrayList indices; + indices = ParserUtil.checkIndices(input, MAX_EDITABLE_FIELDS); + + if (indices.size() == 0) { + ui.printMessage(WARNING_NO_VALID_INPUT); + } + return indices; + } + + /** + * Reads in a new lesson field from the user. + * + * @param userIndex the index of the field being read (as entered by the user) + * @param ui instance of UI for printing prompts + * @return trimmed user input string + */ + private String getNewFieldFromUser(int userIndex, UI ui) { + int fieldIndex = userIndex - 1; + + ui.printMessage(String.format(PROMPT_ENTER_FIELD_DETAILS, fields[fieldIndex].toLowerCase())); + String input = ui.readUserInput(); + + return input.trim(); + } + + /** + * Sets the value of a lesson field to the new value entered by the user. + * + * @param lesson the lesson being edited + * @param newFieldValue new field entered obtained from the user + * @param userIndex the index of the field being edited + * @param ui instance of UI for printing messages and warnings + */ + private void setNewFieldValue(Lesson lesson, String newFieldValue, int userIndex, UI ui) { + int fieldIndex = userIndex - 1; + + switch (fieldIndex) { + case EDIT_INDEX_DAY_TIME: + lesson.setTime(newFieldValue); + ui.printMessage(String.format(MESSAGE_EDITED_FIELD, fields[fieldIndex].toLowerCase())); + break; + case EDIT_INDEX_LINK: + if (Lesson.isValidLink(newFieldValue)) { + lesson.setOnlineLink(newFieldValue); + ui.printMessage(String.format(MESSAGE_EDITED_FIELD, fields[fieldIndex].toLowerCase())); + } else { + ui.printMessage(MESSAGE_INVALID_LESSON_LINK + MESSAGE_NOT_UPDATED); + } + break; + case EDIT_INDEX_TEACHER_NAME: + lesson.setTeachingStaffName(newFieldValue); + ui.printMessage(String.format(MESSAGE_EDITED_FIELD, fields[fieldIndex].toLowerCase())); + break; + default: + if (TeachingStaff.isValidEmail(newFieldValue)) { + lesson.setTeachingStaffEmail(newFieldValue); + ui.printMessage(String.format(MESSAGE_EDITED_FIELD, fields[fieldIndex].toLowerCase())); + } else { + ui.printMessage(MESSAGE_INVALID_LESSON_EMAIL + MESSAGE_NOT_UPDATED); + } + break; + } + } + + /** + * Prints the fields of a lesson with numbering. + * + * @param ui instance of UI to print the fields + */ + private void printFieldsAsList(UI ui) { + int numbering = 1; + for (String field : this.fields) { + ui.printMessage(String.format(FORMAT_INDEX_ITEM, numbering, field)); + numbering++; + } + } +} diff --git a/src/main/java/seedu/duke/commands/EditTaskCommand.java b/src/main/java/seedu/duke/commands/EditTaskCommand.java new file mode 100644 index 0000000000..6b4e14caac --- /dev/null +++ b/src/main/java/seedu/duke/commands/EditTaskCommand.java @@ -0,0 +1,146 @@ +package seedu.duke.commands; + +import seedu.duke.exception.DukeException; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.parser.ParserUtil; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; + +import static seedu.duke.common.CommonMethods.isTaskGraded; +import static seedu.duke.common.Constants.EDIT; +import static seedu.duke.common.Constants.FORMAT_DATE_IO; +import static seedu.duke.common.Constants.TASK_FIELD_DEADLINE; +import static seedu.duke.common.Constants.TASK_FIELD_DESCRIPTION; +import static seedu.duke.common.Constants.TASK_FIELD_GRADED_STATUS; +import static seedu.duke.common.Constants.TASK_FIELD_REMARKS; +import static seedu.duke.common.Messages.FORMAT_INDEX_ITEM; +import static seedu.duke.common.Messages.MESSAGE_EDITED_FIELD; +import static seedu.duke.common.Messages.MESSAGE_FIELDS_TO_EDIT; +import static seedu.duke.common.Messages.MESSAGE_FIELD_BEING_EDITED; +import static seedu.duke.common.Messages.MESSAGE_INVALID_TASK_DEADLINE; +import static seedu.duke.common.Messages.MESSAGE_NOT_UPDATED; +import static seedu.duke.common.Messages.MESSAGE_NO_TASK_MODIFIED; +import static seedu.duke.common.Messages.MESSAGE_SEPARATE_INDICES; +import static seedu.duke.common.Messages.MESSAGE_TASK_LIST_EMPTY; +import static seedu.duke.common.Messages.MESSAGE_TASK_TO_EDIT; +import static seedu.duke.common.Messages.PROMPT_ENTER_FIELD_DETAILS; + +public class EditTaskCommand extends Command { + + private final String[] fields = {TASK_FIELD_DESCRIPTION, TASK_FIELD_DEADLINE, + TASK_FIELD_REMARKS, TASK_FIELD_GRADED_STATUS}; + private Task selectedTask; + + //@@author aliciatay-zls + /** + * Asks user for index of a task to edit, then index/indices of field(s) of + * that task to be edited. Finally, updates each field with the new value given. + * @param ui Instance of UI, used for displaying prompts and confirmation messages + */ + @Override + public void execute(UI ui) { + Module module = ModuleList.getSelectedModule(); + ArrayList taskList = module.getTaskList(); + if (taskList.isEmpty()) { + ui.printMessage(String.format(MESSAGE_TASK_LIST_EMPTY, EDIT)); + return; + } + + printPromptForTask(ui, taskList); + selectedTask = getTaskToEdit(ui, taskList); + // getTaskToEdit() will have displayed error message + if (selectedTask == null) { + return; + } + + printPromptForFields(ui); + ArrayList selectedIndices = ui.getIndicesFromUser(fields.length); + if (selectedIndices.isEmpty()) { + ui.printMessage(MESSAGE_NO_TASK_MODIFIED); + return; + } + + for (int index : selectedIndices) { + editTaskField(ui, index); + } + ModuleList.writeModule(); + ModuleList.sortTasks(); + } + + private void printPromptForTask(UI ui, ArrayList taskList) { + ui.printMessage(MESSAGE_TASK_TO_EDIT); + int tasksCount = 0; + for (Task task : taskList) { + tasksCount++; + ui.printMessage(String.format(FORMAT_INDEX_ITEM, tasksCount, task.getFullTaskString())); + } + } + + private Task getTaskToEdit(UI ui, ArrayList taskList) { + String line = ui.readUserInput(); + try { + int index = ParserUtil.checkIndex(line, taskList.size()); + return taskList.get(index - 1); + } catch (DukeException e) { + ui.printError(e); + return null; + } + } + + private void printPromptForFields(UI ui) { + ui.printMessage(String.format(MESSAGE_FIELD_BEING_EDITED, selectedTask.getDescription())); + ui.printMessage(MESSAGE_FIELDS_TO_EDIT); + printTaskFields(ui); + ui.printMessage(MESSAGE_SEPARATE_INDICES); + } + + private void printTaskFields(UI ui) { + for (int i = 0; i < fields.length; i++) { + ui.printMessage(String.format(FORMAT_INDEX_ITEM, i + 1, fields[i])); + } + } + + private void editTaskField(UI ui, int fieldIndex) { + //assumption: invalid indices should have been filtered out (1-based indexing) + assert fieldIndex >= 1 && fieldIndex <= 4 : fieldIndex; + + ui.printMessage(String.format(PROMPT_ENTER_FIELD_DETAILS, fields[fieldIndex - 1].toLowerCase())); + switch (fieldIndex) { + case 1: + selectedTask.editDescription(ui.readUserInput()); + break; + case 2: + LocalDate newDeadline = getNewTaskDeadline(ui, ui.readUserInput()); + if (newDeadline == null) { + return; + } + selectedTask.editDeadline(newDeadline); + break; + case 3: + selectedTask.editRemarks(ui.readUserInput()); + break; + case 4: + selectedTask.editGraded(isTaskGraded(ui)); + break; + default: + } + ui.printMessage(String.format(MESSAGE_EDITED_FIELD, fields[fieldIndex - 1].toLowerCase())); + } + + private LocalDate getNewTaskDeadline(UI ui, String newValue) { + DateTimeFormatter parseFormat = DateTimeFormatter.ofPattern(FORMAT_DATE_IO); + try { + return LocalDate.parse(newValue, parseFormat); + } catch (DateTimeParseException e) { + ui.printMessage(MESSAGE_INVALID_TASK_DEADLINE); + ui.printMessage(MESSAGE_NOT_UPDATED); + return null; + } + } +} diff --git a/src/main/java/seedu/duke/commands/EnterModuleCommand.java b/src/main/java/seedu/duke/commands/EnterModuleCommand.java new file mode 100644 index 0000000000..6b6abfc6a1 --- /dev/null +++ b/src/main/java/seedu/duke/commands/EnterModuleCommand.java @@ -0,0 +1,35 @@ +package seedu.duke.commands; + +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import static seedu.duke.common.Messages.MESSAGE_INVALID_MODULE; +import static seedu.duke.common.Messages.MESSAGE_MODULE_OPENED; + +public class EnterModuleCommand extends Command { + + private final String moduleCode; + + //@@author isaharon + public EnterModuleCommand(String moduleCode) { + this.moduleCode = moduleCode; + } + + /** + * Opens specified module. + * + * @param ui Instance of UI. + * @throws CommandException Invalid module code. + */ + @Override + public void execute(UI ui) throws CommandException { + if (ModuleList.setSelectedModule(moduleCode)) { + ui.printMessage(String.format(MESSAGE_MODULE_OPENED, moduleCode)); + Command command = new ModuleInfoCommand(); + command.execute(ui); + } else { + throw new CommandException(String.format(MESSAGE_INVALID_MODULE, moduleCode)); + } + } +} diff --git a/src/main/java/seedu/duke/commands/ExitModuleCommand.java b/src/main/java/seedu/duke/commands/ExitModuleCommand.java new file mode 100644 index 0000000000..1537e0f8ed --- /dev/null +++ b/src/main/java/seedu/duke/commands/ExitModuleCommand.java @@ -0,0 +1,22 @@ +package seedu.duke.commands; + +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import static seedu.duke.common.Messages.MESSAGE_CLOSED_MODULE; + +public class ExitModuleCommand extends Command { + + //@@author 8kdesign + /** + * Exits from selected module. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + String moduleCode = ModuleList.getSelectedModuleCode(); + ModuleList.reset(); + ui.printMessage(String.format(MESSAGE_CLOSED_MODULE, moduleCode)); + } +} diff --git a/src/main/java/seedu/duke/commands/ExitProgramCommand.java b/src/main/java/seedu/duke/commands/ExitProgramCommand.java new file mode 100644 index 0000000000..ef7bfff134 --- /dev/null +++ b/src/main/java/seedu/duke/commands/ExitProgramCommand.java @@ -0,0 +1,24 @@ +package seedu.duke.commands; + +import seedu.duke.ui.UI; + +import static seedu.duke.common.Messages.MESSAGE_EXIT; + +public class ExitProgramCommand extends Command { + + //@@author isaharon + /** + * Prints exit message. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + ui.printMessage(MESSAGE_EXIT); + } + + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/seedu/duke/commands/ListCheatSheetsCommand.java b/src/main/java/seedu/duke/commands/ListCheatSheetsCommand.java new file mode 100644 index 0000000000..953ea6fc96 --- /dev/null +++ b/src/main/java/seedu/duke/commands/ListCheatSheetsCommand.java @@ -0,0 +1,51 @@ +package seedu.duke.commands; + +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static seedu.duke.common.Constants.BEGIN_INDEX; +import static seedu.duke.common.Constants.STRING_CHEATSHEET; +import static seedu.duke.common.Constants.DOT; +import static seedu.duke.common.Constants.EMPTY; +import static seedu.duke.common.Constants.EXTENSION; +import static seedu.duke.common.Constants.FOLDER_PATH; +import static seedu.duke.common.Constants.PATH_DELIMITER; +import static seedu.duke.common.Messages.MESSAGE_EMPTY_CHEAT_SHEET_DIRECTORY; +import static seedu.duke.common.Messages.MESSAGE_LIST_OF_CHEAT_SHEETS; + +public class ListCheatSheetsCommand extends Command { + protected static String directoryPath; + protected static Path directoryAbsolutePath; + + //@@author H-horizon + public ListCheatSheetsCommand() { + directoryPath = FOLDER_PATH + PATH_DELIMITER + ModuleList.getSelectedModuleCode() + PATH_DELIMITER + + STRING_CHEATSHEET + PATH_DELIMITER; + directoryAbsolutePath = Paths.get(directoryPath); + assert Files.isDirectory(directoryAbsolutePath) : "Directory missing"; + } + + @Override + public void execute(UI ui) throws CommandException { + String[] filePaths; + File f = new File(directoryPath); + + filePaths = f.list(); + int i = 1; + if (filePaths != null && filePaths.length > EMPTY) { + ui.printMessage(MESSAGE_LIST_OF_CHEAT_SHEETS); + for (String filePath : filePaths) { + ui.printMessage(i + DOT + filePath.substring(BEGIN_INDEX, filePath.length() - EXTENSION)); + i++; + } + } else { + ui.printMessage(MESSAGE_EMPTY_CHEAT_SHEET_DIRECTORY); + } + } +} diff --git a/src/main/java/seedu/duke/commands/ListLessonsCommand.java b/src/main/java/seedu/duke/commands/ListLessonsCommand.java new file mode 100644 index 0000000000..66f131d552 --- /dev/null +++ b/src/main/java/seedu/duke/commands/ListLessonsCommand.java @@ -0,0 +1,67 @@ +package seedu.duke.commands; + +import seedu.duke.lesson.Lesson; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.util.ArrayList; + +import static seedu.duke.common.Messages.FORMAT_INDEX_ITEM_DETAILS; +import static seedu.duke.common.Messages.INDENTATION; +import static seedu.duke.common.Messages.MESSAGE_LESSONS_LIST_EMPTY; +import static seedu.duke.common.Messages.MESSAGE_LESSONS_TO_LIST; + +/** + * Represents the command used to print the list of lessons. + */ +public class ListLessonsCommand extends Command { + + //@@author H-horizon + + /** + * Prints list of lessons in selected module. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + Module module = ModuleList.getSelectedModule(); + String moduleCode = module.getModuleCode(); + + if (module.getLessonList().size() > 0) { + ui.printMessage(String.format(MESSAGE_LESSONS_TO_LIST, moduleCode)); + printLessons(module.getLessonList(), ui); + } else { + ui.printMessage(MESSAGE_LESSONS_LIST_EMPTY); + } + } + + /** + * Prints list of lessons. + * + * @param lessonList ArrayList of lessons. + * @param ui Instance of lessons. + */ + public static void printLessons(ArrayList lessonList, UI ui) { + int counter = 1; + for (Lesson lesson : lessonList) { + String lessonType = lesson.getLessonTypeString(); + String lessonTime = lesson.getTime(); + ui.printMessage(String.format(FORMAT_INDEX_ITEM_DETAILS, counter, lessonType, lessonTime)); + String lessonOnlineLink = lesson.getOnlineLink(); + if (!lessonOnlineLink.isEmpty()) { + ui.printMessage(INDENTATION + lessonOnlineLink); + } + String teacherName = lesson.getTeachingStaffName(); + if (!teacherName.isEmpty()) { + ui.printMessage(INDENTATION + teacherName); + } + String teacherEmail = lesson.getTeachingStaffEmail(); + if (!teacherEmail.isEmpty()) { + ui.printMessage(INDENTATION + teacherEmail); + } + counter++; + } + } +} diff --git a/src/main/java/seedu/duke/commands/ListModulesCommand.java b/src/main/java/seedu/duke/commands/ListModulesCommand.java new file mode 100644 index 0000000000..498d09deef --- /dev/null +++ b/src/main/java/seedu/duke/commands/ListModulesCommand.java @@ -0,0 +1,36 @@ +package seedu.duke.commands; + +import seedu.duke.common.Messages; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import static seedu.duke.common.Constants.INDEX_FIRST; +import static seedu.duke.common.Messages.MESSAGE_MODULE_TO_LIST; +import static seedu.duke.common.Messages.NEWLINE; + +public class ListModulesCommand extends Command { + + //@@author isaharon + /** + * Prints list of modules. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + ui.printMessage(MESSAGE_MODULE_TO_LIST); + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < ModuleList.getModules().size(); i++) { + if (i != INDEX_FIRST) { + stringBuilder.append(NEWLINE); + } + String moduleCode = ModuleList.getModuleByIndex(i); + int counter = ModuleList.getModuleIndex(moduleCode) + 1; + stringBuilder.append(String.format(Messages.FORMAT_LIST_ITEMS, counter, moduleCode)); + } + String listMessage = stringBuilder.toString(); + if (!listMessage.isEmpty()) { + ui.printMessage(listMessage); + } + } +} diff --git a/src/main/java/seedu/duke/commands/ListTasksCommand.java b/src/main/java/seedu/duke/commands/ListTasksCommand.java new file mode 100644 index 0000000000..bc2eda86ca --- /dev/null +++ b/src/main/java/seedu/duke/commands/ListTasksCommand.java @@ -0,0 +1,57 @@ +package seedu.duke.commands; + +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.util.ArrayList; + +import static seedu.duke.common.Constants.EMPTY_STRING; +import static seedu.duke.common.Messages.MESSAGE_TASKS_DONE; +import static seedu.duke.common.Messages.MESSAGE_TASKS_EMPTY; +import static seedu.duke.common.Messages.MESSAGE_TASKS_TO_LIST; +import static seedu.duke.common.Messages.NEWLINE; + +public class ListTasksCommand extends Command { + + //@@author aliciatay-zls + /** + * Prints all tasks in selected module's task list. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + Module module = ModuleList.getSelectedModule(); + ui.printMessage(String.format(MESSAGE_TASKS_TO_LIST, module.getModuleCode()) + NEWLINE); + printFilteredTasks(ui, module, false, false); + ui.printMessage(EMPTY_STRING); + printFilteredTasks(ui, module, true, false); + } + + /** + * Prints either all done or all undone tasks. + */ + public static void printFilteredTasks(UI ui, Module module, boolean isDone, boolean isOverview) { + ui.printTaskListHeader(isDone, isOverview); + ArrayList filteredTasks = module.getDoneOrUndoneTasks(isDone); + if (filteredTasks.isEmpty()) { + printEmpty(ui, isDone); + return; + } + ui.printTasks(filteredTasks, false); + } + + /** + * Prints message to indicate task list is empty. + * @param isDone Status of tasks in taskList. + */ + private static void printEmpty(UI ui, boolean isDone) { + if (isDone) { + ui.printMessage(MESSAGE_TASKS_EMPTY); + } else { + ui.printMessage(MESSAGE_TASKS_DONE); + } + } +} diff --git a/src/main/java/seedu/duke/commands/MarkAsDoneCommand.java b/src/main/java/seedu/duke/commands/MarkAsDoneCommand.java new file mode 100644 index 0000000000..4e23f0b8cb --- /dev/null +++ b/src/main/java/seedu/duke/commands/MarkAsDoneCommand.java @@ -0,0 +1,61 @@ +package seedu.duke.commands; + +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.util.ArrayList; + +import static seedu.duke.common.CommonMethods.getSpecifiedTasks; +import static seedu.duke.common.Constants.MARK; +import static seedu.duke.common.Constants.TYPE_TASK; +import static seedu.duke.common.Messages.MESSAGE_ENTER_INDICES; +import static seedu.duke.common.Messages.MESSAGE_MARKED_AS_DONE; +import static seedu.duke.common.Messages.MESSAGE_NO_TASK_MODIFIED; +import static seedu.duke.common.Messages.MESSAGE_TASKS_TO_MARK; +import static seedu.duke.common.Messages.MESSAGE_TASK_LIST_EMPTY; + +public class MarkAsDoneCommand extends Command { + + //@@author aliciatay-zls + /** + * Displays list of undone tasks, asks user for a string of space-separated + * indices and marks all corresponding tasks as done. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + Module module = ModuleList.getSelectedModule(); + ArrayList undoneTasks = module.getDoneOrUndoneTasks(false); + if (undoneTasks.isEmpty()) { + ui.printMessage(String.format(MESSAGE_TASK_LIST_EMPTY, MARK)); + return; + } + printPrompt(ui, undoneTasks); + ArrayList selectedTasks = getSpecifiedTasks(ui, undoneTasks); + if (selectedTasks.isEmpty()) { + ui.printMessage(MESSAGE_NO_TASK_MODIFIED); + return; + } + for (Task task : selectedTasks) { + String description = task.getDescription(); + ui.printMessage(String.format(MESSAGE_MARKED_AS_DONE, description)); + task.setDone(true); + } + ModuleList.writeModule(); + } + + /** + * Prints prompt for indices of tasks to mark as done. + * + * @param ui Instance of UI. + * @param undoneTasks Array list of undone tasks. + */ + private void printPrompt(UI ui, ArrayList undoneTasks) { + ui.printMessage(MESSAGE_TASKS_TO_MARK); + ui.printTasks(undoneTasks, true); + ui.printMessage(String.format(MESSAGE_ENTER_INDICES, TYPE_TASK, MARK)); + } +} diff --git a/src/main/java/seedu/duke/commands/MarkAsUndoneCommand.java b/src/main/java/seedu/duke/commands/MarkAsUndoneCommand.java new file mode 100644 index 0000000000..fde60c7367 --- /dev/null +++ b/src/main/java/seedu/duke/commands/MarkAsUndoneCommand.java @@ -0,0 +1,61 @@ +package seedu.duke.commands; + +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.util.ArrayList; + +import static seedu.duke.common.CommonMethods.getSpecifiedTasks; +import static seedu.duke.common.Constants.TYPE_TASK; +import static seedu.duke.common.Constants.UNMARK; +import static seedu.duke.common.Messages.MESSAGE_ENTER_INDICES; +import static seedu.duke.common.Messages.MESSAGE_MARKED_AS_UNDONE; +import static seedu.duke.common.Messages.MESSAGE_NO_TASK_MODIFIED; +import static seedu.duke.common.Messages.MESSAGE_TASKS_TO_UNMARK; +import static seedu.duke.common.Messages.MESSAGE_TASK_LIST_EMPTY; + +public class MarkAsUndoneCommand extends Command { + + //@@author aliciatay-zls + /** + * Displays list of done tasks, asks user for a string of space-separated + * indices and marks all corresponding tasks as undone. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + Module module = ModuleList.getSelectedModule(); + ArrayList doneTasks = module.getDoneOrUndoneTasks(true); + if (doneTasks.isEmpty()) { + ui.printMessage(String.format(MESSAGE_TASK_LIST_EMPTY, UNMARK)); + return; + } + printPrompt(ui, doneTasks); + ArrayList selectedTasks = getSpecifiedTasks(ui, doneTasks); + if (selectedTasks.isEmpty()) { + ui.printMessage(MESSAGE_NO_TASK_MODIFIED); + return; + } + for (Task task : selectedTasks) { + String description = task.getDescription(); + ui.printMessage(String.format(MESSAGE_MARKED_AS_UNDONE,description)); + task.setDone(false); + } + ModuleList.writeModule(); + } + + /** + * Prints prompt for indices of tasks to mark as undone. + * + * @param ui Instance of UI. + * @param doneTasks Array list of done tasks. + */ + private void printPrompt(UI ui, ArrayList doneTasks) { + ui.printMessage(MESSAGE_TASKS_TO_UNMARK); + ui.printTasks(doneTasks, true); + ui.printMessage(String.format(MESSAGE_ENTER_INDICES, TYPE_TASK, UNMARK)); + } +} diff --git a/src/main/java/seedu/duke/commands/ModuleInfoCommand.java b/src/main/java/seedu/duke/commands/ModuleInfoCommand.java new file mode 100644 index 0000000000..5eba45e64c --- /dev/null +++ b/src/main/java/seedu/duke/commands/ModuleInfoCommand.java @@ -0,0 +1,51 @@ +package seedu.duke.commands; + +import seedu.duke.exception.CommandException; +import seedu.duke.lesson.Lesson; +import seedu.duke.lesson.LessonType; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.util.ArrayList; + +import static seedu.duke.common.Constants.EMPTY_STRING; +import static seedu.duke.common.Messages.FORMAT_ITEM_TIME; +import static seedu.duke.common.Messages.FORMAT_MODULE_INFO; + +/** + * Represents the command used to print the module overview. + */ +public class ModuleInfoCommand extends Command { + + //@@author H-horizon + /** + * prints module overview. + * + * @param ui Instance of UI. + * @throws CommandException if command was not constructed properly. + */ + @Override + public void execute(UI ui) throws CommandException { + Module module = ModuleList.getSelectedModule(); + String moduleCode = module.getModuleCode(); + ui.printMessage(String.format(FORMAT_MODULE_INFO, moduleCode)); + ArrayList lessonList = module.getLessonList(); + printLessonsFromList(lessonList, ui); + ui.printMessage(EMPTY_STRING); + ListTasksCommand.printFilteredTasks(ui, module, false, true); + } + + /** + * prints lessons info for module overview. + * + * @param lessonList is the list of lessons. + */ + public static void printLessonsFromList(ArrayList lessonList, UI ui) { + for (Lesson lesson : lessonList) { + String lessonName = LessonType.getLessonTypeString(lesson.getLessonType()); + String lessonTime = lesson.getTime(); + ui.printMessage(String.format(FORMAT_ITEM_TIME, lessonName, lessonTime)); + } + } +} diff --git a/src/main/java/seedu/duke/commands/OpenLessonLinkCommand.java b/src/main/java/seedu/duke/commands/OpenLessonLinkCommand.java new file mode 100644 index 0000000000..6edcb6152c --- /dev/null +++ b/src/main/java/seedu/duke/commands/OpenLessonLinkCommand.java @@ -0,0 +1,158 @@ +package seedu.duke.commands; + +import seedu.duke.lesson.Lesson; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.parser.ParserUtil; +import seedu.duke.ui.UI; + +import java.awt.Desktop; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import static seedu.duke.common.Constants.HEAD; +import static seedu.duke.common.Constants.LINUX_OPEN_COMMAND; +import static seedu.duke.common.Constants.LOGGER_PATH; +import static seedu.duke.common.Messages.FORMAT_INDEX_ITEM; +import static seedu.duke.common.Messages.FORMAT_INDEX_LESSON_DETAILS; +import static seedu.duke.common.Messages.MESSAGE_INVALID_LESSON_LINK; +import static seedu.duke.common.Messages.MESSAGE_LESSONS_LIST_EMPTY; +import static seedu.duke.common.Messages.MESSAGE_LESSON_TO_OPEN_LINK; +import static seedu.duke.common.Messages.MESSAGE_OPENED_LESSON_LINK; +import static seedu.duke.common.Messages.MESSAGE_UNABLE_TO_OPEN_LINK; + +/** + * Represents the command used to open a lesson link. + */ +public class OpenLessonLinkCommand extends Command { + + public static final String FILE_LOGGER_NOT_WORKING = "File logger not working."; + private static Logger logger = Logger.getLogger(OpenLessonLinkCommand.class.getName()); + private static FileHandler fileHandler; + //@@author H-horizon + + /** + * Sets up logger when command is created. + */ + public OpenLessonLinkCommand() { + LogManager.getLogManager().reset(); + try { + fileHandler = new FileHandler(LOGGER_PATH); + fileHandler.setLevel(Level.FINE); + logger.addHandler(fileHandler); + } catch (IOException e) { + logger.log(Level.SEVERE, FILE_LOGGER_NOT_WORKING, e); + } + } + + /** + * Opens links corresponding to specified indices. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + Module module = ModuleList.getSelectedModule(); + ArrayList lessonList = module.getLessonList(); + + if (lessonList.size() > 0) { + ui.printMessage(MESSAGE_LESSON_TO_OPEN_LINK); + printLessons(lessonList, ui); + String line = ui.readUserInput(); + ArrayList indices = ParserUtil.checkIndices(line, lessonList.size()); + printLessonsLink(lessonList, indices, ui); + } else { + ui.printMessage(MESSAGE_LESSONS_LIST_EMPTY); + } + } + + /** + * Prints list of links opened. + * + * @param lessonList ArrayList of lessons in specified module. + * @param indices Indices of links to open. + * @param ui Instance of UI. + */ + public static void printLessonsLink(ArrayList lessonList, ArrayList indices, UI ui) { + for (int index : indices) { + Lesson lesson = lessonList.get(index - 1); + String lessonType = lesson.getLessonTypeString(); + ui.printMessage(String.format(MESSAGE_OPENED_LESSON_LINK, lessonType.toLowerCase())); + String lessonLink = lesson.getOnlineLink(); + validateLessonLink(ui, lessonLink); + } + } + + /** + * Validates link before trying to open it in a browser. + * + * @param ui Instance of UI. + * @param lessonLink link to open. + */ + private static void validateLessonLink(UI ui, String lessonLink) { + HttpURLConnection connection; + try { + URL lessonUrl = new URL(lessonLink); + connection = (HttpURLConnection) lessonUrl.openConnection(); + connection.setRequestMethod(HEAD); + int statusCode = connection.getResponseCode(); + assert statusCode >= -1 : MESSAGE_UNABLE_TO_OPEN_LINK; + openLessonLink(lessonLink, ui); + } catch (IOException e) { + ui.printMessage(MESSAGE_INVALID_LESSON_LINK); + logger.info(lessonLink); + } + } + + /** + * Opens the specified link in browser. + * + * @param onlineLink Link to open. + * @param ui Instance of UI. + */ + public static void openLessonLink(String onlineLink, UI ui) { + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + desktop.browse(new URI(onlineLink)); + } catch (IOException | URISyntaxException e) { + ui.printMessage(MESSAGE_UNABLE_TO_OPEN_LINK); + } + } else { + Runtime runtime = Runtime.getRuntime(); + try { + runtime.exec(LINUX_OPEN_COMMAND + onlineLink); + } catch (IOException e) { + ui.printMessage(MESSAGE_UNABLE_TO_OPEN_LINK); + } + } + } + + /** + * Prints list of lessons in specified module. + * + * @param lessonList ArrayList of lessons in specified module. + * @param ui Instance of UI. + */ + private static void printLessons(ArrayList lessonList, UI ui) { + int counter = 1; + for (Lesson lesson : lessonList) { + String lessonType = lesson.getLessonTypeString(); + if (lesson.getDetailsStringIfAny() != null) { + String appendString = lesson.getDetailsStringIfAny(); + ui.printMessage(String.format(FORMAT_INDEX_LESSON_DETAILS, counter, lessonType, appendString)); + } else { + ui.printMessage(String.format(FORMAT_INDEX_ITEM, counter, lessonType)); + } + counter++; + } + } +} diff --git a/src/main/java/seedu/duke/commands/PrintHelpCommand.java b/src/main/java/seedu/duke/commands/PrintHelpCommand.java new file mode 100644 index 0000000000..fc4205fb97 --- /dev/null +++ b/src/main/java/seedu/duke/commands/PrintHelpCommand.java @@ -0,0 +1,55 @@ +package seedu.duke.commands; + +import seedu.duke.common.CommandList; +import seedu.duke.common.DashboardCommands; +import seedu.duke.common.Messages; +import seedu.duke.common.ModuleCommands; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import static seedu.duke.common.Constants.INDEX_FIRST; +import static seedu.duke.common.Messages.MESSAGE_DASHBOARD_HELP; +import static seedu.duke.common.Messages.MESSAGE_MODULE_HELP; +import static seedu.duke.common.Messages.NEWLINE; + +public class PrintHelpCommand extends Command { + + //@@author isaharon + /** + * Prints list of commands. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + if (ModuleList.getSelectedModule() == null) { + ui.printMessage(MESSAGE_DASHBOARD_HELP); + DashboardCommands[] commands = DashboardCommands.values(); + ui.printMessage(getCommands(commands)); + } else { + ui.printMessage(MESSAGE_MODULE_HELP); + ModuleCommands[] commands = ModuleCommands.values(); + ui.printMessage(getCommands(commands)); + } + } + + /** + * Returns string containing commands and their descriptions. + * + * @param commands Array of commands to print. + * @return Message to print. + */ + private static String getCommands(CommandList[] commands) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < commands.length; i++) { + if (i != INDEX_FIRST) { + stringBuilder.append(NEWLINE).append(NEWLINE); + } + String commandWordAndArgs = commands[i].getWord() + " " + commands[i].getArgumentsFormat(); + String commandAndDescription = String.format(Messages.FORMAT_LIST_HELP, + commandWordAndArgs, commands[i].getDescription()); + stringBuilder.append(commandAndDescription); + } + return stringBuilder.toString(); + } +} diff --git a/src/main/java/seedu/duke/commands/ViewTeachingStaffCommand.java b/src/main/java/seedu/duke/commands/ViewTeachingStaffCommand.java new file mode 100644 index 0000000000..6eeba4e1ad --- /dev/null +++ b/src/main/java/seedu/duke/commands/ViewTeachingStaffCommand.java @@ -0,0 +1,41 @@ +package seedu.duke.commands; + +import seedu.duke.lesson.Lesson; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.util.ArrayList; + +import static seedu.duke.common.Messages.FORMAT_INDEX_ITEM_DETAILS; +import static seedu.duke.common.Messages.MESSAGE_TEACHING_STAFF_TO_LIST; + +/** + * Represents the command used to print the teaching staff for a selected lesson. + */ +public class ViewTeachingStaffCommand extends Command { + + //@@author H-horizon + /** + * Prints list of teaching staff for selected module. + * + * @param ui Instance of UI. + */ + @Override + public void execute(UI ui) { + Module module = ModuleList.getSelectedModule(); + String moduleCode = module.getModuleCode(); + ui.printMessage(String.format(MESSAGE_TEACHING_STAFF_TO_LIST, moduleCode)); + ArrayList lessonList = module.getLessonList(); + int counter = 1; + for (Lesson lesson : lessonList) { + String teacherName = lesson.getTeachingStaff().getName(); + if (teacherName.length() == 0) { + continue; + } + String teacherEmail = lesson.getTeachingStaff().getEmail(); + ui.printMessage(String.format(FORMAT_INDEX_ITEM_DETAILS, counter, teacherName, teacherEmail)); + counter++; + } + } +} diff --git a/src/main/java/seedu/duke/common/CommandList.java b/src/main/java/seedu/duke/common/CommandList.java new file mode 100644 index 0000000000..a883280350 --- /dev/null +++ b/src/main/java/seedu/duke/common/CommandList.java @@ -0,0 +1,10 @@ +package seedu.duke.common; + +public interface CommandList { + + String getWord(); + + String getDescription(); + + String getArgumentsFormat(); +} diff --git a/src/main/java/seedu/duke/common/CommonMethods.java b/src/main/java/seedu/duke/common/CommonMethods.java new file mode 100644 index 0000000000..642b823c5c --- /dev/null +++ b/src/main/java/seedu/duke/common/CommonMethods.java @@ -0,0 +1,92 @@ +package seedu.duke.common; + +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.logging.ConsoleHandler; +import java.util.logging.FileHandler; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static seedu.duke.common.Constants.LOGGER_NAME; +import static seedu.duke.common.Constants.LOGGER_PATH; +import static seedu.duke.common.Constants.NO_STRING; +import static seedu.duke.common.Constants.YES_STRING; +import static seedu.duke.common.Messages.MESSAGE_TASK_CHECK_GRADED_INFO; + +public class CommonMethods { + + + /** + * Writes specified message to log. + * + * @param message Message to save to log. + */ + public static void writeLog(String message) { + try { + Logger logger = Logger.getLogger(LOGGER_NAME); + logger.setLevel(Level.ALL); + FileHandler fileHandler = new FileHandler(LOGGER_PATH, true); + fileHandler.setLevel(Level.ALL); + logger.addHandler(fileHandler); + + ConsoleHandler consoleHandler = new ConsoleHandler(); + consoleHandler.setLevel(Level.SEVERE); + logger.addHandler(consoleHandler); + + logger.log(Level.FINE, message); + } catch (IOException e) { + //Failed to write to log + } + } + + /** + * Calculates difference in specified date with current date. + * + * @param dueDate LocalDate of task due date. + * @return Long of difference in days. + */ + public static long getDaysRemaining(LocalDate dueDate) { + LocalDate currentDate = LocalDate.now(); + return ChronoUnit.DAYS.between(currentDate, dueDate); + } + + //@@author aliciatay-zls + /** + * Requests for list of indices. + * Returns tasks corresponding to indices specified. + * + * @param ui Instance of UI. + * @param taskList Array list of undone tasks. + * @return Array list of selected tasks. + */ + public static ArrayList getSpecifiedTasks(UI ui, ArrayList taskList) { + ArrayList indices = ui.getIndicesFromUser(taskList.size()); + //Store the tasks chosen by user to new array list + ArrayList selectedTasks = new ArrayList<>(); + for (Integer index : indices) { + selectedTasks.add(taskList.get(index - 1)); + } + return selectedTasks; + } + + /** + * Asks user if the task to be added is a graded one. + * + * @param ui Instance of UI. + * @return Boolean of whether new task is graded. + */ + public static boolean isTaskGraded(UI ui) { + String userInput = ui.readUserInput(); + while (!userInput.equalsIgnoreCase(YES_STRING) + && !userInput.equalsIgnoreCase(NO_STRING)) { + ui.printMessage(MESSAGE_TASK_CHECK_GRADED_INFO); + userInput = ui.readUserInput(); + } + return userInput.equalsIgnoreCase(YES_STRING); + } +} diff --git a/src/main/java/seedu/duke/common/Constants.java b/src/main/java/seedu/duke/common/Constants.java new file mode 100644 index 0000000000..3891efc895 --- /dev/null +++ b/src/main/java/seedu/duke/common/Constants.java @@ -0,0 +1,142 @@ +package seedu.duke.common; + +import java.util.ArrayList; +import java.util.Arrays; + +public class Constants { + //@@author + //General + public static final String FORMAT_DATE_IO = "d-M-yyyy"; + public static final String FORMAT_DATE_NORMAL = "d MMM yyyy"; + + public static final String LINUX_OPEN_COMMAND = "xdg-open "; + public static final int INDEX_FIRST = 0; + + public static final String TYPE_MODULE = "module"; + public static final String TYPE_LESSON = "lesson"; + public static final String TYPE_TASK = "task"; + + public static final String DELIM = ";;"; + public static final String WHITESPACE = " "; + public static final String EMPTY_STRING = ""; + public static final String COLON = ": "; + + public static final String ADD = "add"; + public static final String DELETE = "delete"; + public static final String DELETE_COMMAND = "del"; + public static final String EDIT = "edit"; + + //Lecture type + public static final String LECTURE_STRING = "lecture"; + public static final String TUTORIAL_STRING = "tutorial"; + public static final String LAB_STRING = "lab"; + + //Lesson + public static final int INDEX_TYPE = 0; + public static final int INDEX_DAY_TIME = 1; + public static final int INDEX_LINK = 2; + public static final int INDEX_TEACHER_NAME = 3; + public static final int INDEX_TEACHER_EMAIL = 4; + + public static final int ENTRY_LESSON_MAX_PARSER = 5; + public static final int ENTRY_LESSON_SHORT = 2; + public static final int ENTRY_LESSON_MEDIUM = 3; + public static final int ENTRY_LESSON_LONG = 4; + public static final int ENTRY_LESSON_EXTRA_LONG = 5; + public static final ArrayList ENTRY_SIZE_LESSON + = new ArrayList<>(Arrays.asList(ENTRY_LESSON_SHORT, ENTRY_LESSON_MEDIUM, + ENTRY_LESSON_LONG, ENTRY_LESSON_EXTRA_LONG)); + + public static final int EDIT_INDEX_DAY_TIME = 0; + public static final int EDIT_INDEX_LINK = 1; + public static final int EDIT_INDEX_TEACHER_NAME = 2; + public static final int EDIT_INDEX_TEACHER_EMAIL = 3; + public static final int MAX_EDITABLE_FIELDS = 4; + + public static final String LESSON_FIELD_1_TIME_DAY = "Time and day"; + public static final String LESSON_FIELD_2_LINK = "Lesson link"; + public static final String LESSON_FIELD_3_T_NAME = "Teaching staff name"; + public static final String LESSON_FIELD_4_T_EMAIL = "Teaching staff email"; + + //Task + public static final int INDEX_DESCRIPTION = 0; + public static final int INDEX_DEADLINE = 1; + public static final int INDEX_REMARKS_PARSER = 2; + public static final int INDEX_IS_DONE = 2; + public static final int INDEX_IS_GRADED = 3; + public static final int INDEX_REMARKS_LOADER = 4; + + public static final int ENTRY_TASK_MAX_PARSER = 3; + public static final int ENTRY_TASK_SHORT = 4; + public static final int ENTRY_TASK_LONG = 5; + public static final ArrayList ENTRY_SIZE_TASK + = new ArrayList<>(Arrays.asList(ENTRY_TASK_SHORT, ENTRY_TASK_LONG)); + + public static final String TASK_FIELD_DESCRIPTION = "Description"; + public static final String TASK_FIELD_DEADLINE = "Deadline"; + public static final String TASK_FIELD_REMARKS = "Remarks"; + public static final String TASK_FIELD_GRADED_STATUS = "Graded/not graded"; + + public static final String YES_STRING = "Y"; + public static final String NO_STRING = "N"; + + public static final String MARK = "mark as done"; + public static final String UNMARK = "mark as undone"; + + //Parser + public static final String FORMAT_MODULE_CODE = "([A-z]{2,3}[\\d]{4}[A-z]{0,2})"; + // General Email Regex (RFC 5322 Official Standard) taken from https://emailregex.com/ + public static final String FORMAT_EMAIL = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\." + + "[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\" + + "[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]" + + "*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]" + + "?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\" + + "[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"; + public static final String FORMAT_LINK = "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\-\\+~#=]{2,256}\\.[a-z]{2,6}" + + "\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)"; + + public static final String FORMAT_COMMAND_WORD_AND_ARGS = "(^\\S+)(.*)"; + public static final String FORMAT_TWO_COMMAND_WORD_AND_ARGS = "(^\\S+\\s+\\S+)(.*)"; + + public static final int INDEX_COMMAND_WORD = 0; + public static final int INDEX_COMMAND_ARGS = 1; + + //Storage + public static final String FOLDER_PATH = "Data"; + public static final String TXT_FORMAT = ".txt"; + public static final String STOP_LINE = "------------"; + public static final String DIVIDER_READ = "\\|"; + public static final String DIVIDER_WRITE = " | "; + public static final String KEYWORD_LESSON = "lesson | "; + public static final String KEYWORD_TASK = "task | "; + public static final String TRUE_STRING = "T"; + public static final String FALSE_STRING = "F"; + + //Command + public static final String HEAD = "HEAD"; + + //Logging + public static final String LOGGER_PATH = "GULIOLog.log"; + public static final String LOGGER_NAME = "LOGGER"; + + //Editor + public static final int TEXT_EDITOR_WIDTH = 800; + public static final int TEXT_EDITOR_HEIGHT = 800; + public static final int TEXT_AREA_WIDTH = 700; + public static final int TEXT_AREA_HEIGHT = 700; + public static final String SAVE_ICON = "Save"; + public static final String TEXT_EDITOR_TITLE = "%s - Gulio Text Editor"; + + //Cheat sheet + public static final String STRING_CHEATSHEET = "Cheatsheet"; + public static final String PATH_DELIMITER = "/"; + public static final int KEYCODE_S = 83; + public static final int KEYCODE_UP = 38; + public static final int KEYCODE_DOWN = 40; + public static final int BEGIN_INDEX = 0; + public static final int EXTENSION = 4; + public static final String DOT = ". "; + public static final int EMPTY = 0; + public static final int FONT_SIZE_MIN = 10; + public static final int FONT_SIZE_MAX = 50; +} diff --git a/src/main/java/seedu/duke/common/DashboardCommands.java b/src/main/java/seedu/duke/common/DashboardCommands.java new file mode 100644 index 0000000000..59bdd9f655 --- /dev/null +++ b/src/main/java/seedu/duke/common/DashboardCommands.java @@ -0,0 +1,65 @@ +package seedu.duke.common; + +//@@author isaharon +public enum DashboardCommands implements CommandList { + + HELP("help", "Lists all commands available when no module is selected."), + EXIT("exit", "Exits the program."), + OPEN("open", "MODULE_CODE", "Opens the specified module."), + ADD("add", "MODULE_CODE", "Adds a new module with specified module code."), + DELETE("del", "Deletes the specified module."), + MODULES("mods", "Lists all modules."); + + private final String word; + private final String argumentsFormat; + private final String description; + + DashboardCommands(String word, String description) { + this.word = word; + this.argumentsFormat = Constants.EMPTY_STRING; + this.description = description; + } + + DashboardCommands(String word, String argumentsFormat, String description) { + this.word = word; + this.argumentsFormat = argumentsFormat; + this.description = description; + } + + public String getWord() { + return word; + } + + @Override + public String getArgumentsFormat() { + return argumentsFormat; + } + + public String getDescription() { + return description; + } + + //@@author ivanchongzhien + /** + * Converts given string to a dashboard command. + * + * @param commandWord word string + * @return the enum type of DashboardCommands representing the command specified + */ + public static DashboardCommands getDashboardCommandFromString(String commandWord) { + if (commandWord.equalsIgnoreCase(DashboardCommands.HELP.getWord())) { + return DashboardCommands.HELP; + } else if (commandWord.equalsIgnoreCase(EXIT.getWord())) { + return EXIT; + } else if (commandWord.equalsIgnoreCase(MODULES.getWord())) { + return MODULES; + } else if (commandWord.equalsIgnoreCase(ADD.getWord())) { + return ADD; + } else if (commandWord.equalsIgnoreCase(DELETE.getWord())) { + return DELETE; + } else if (commandWord.equalsIgnoreCase(OPEN.getWord())) { + return OPEN; + } + return null; + } +} diff --git a/src/main/java/seedu/duke/common/InputValidator.java b/src/main/java/seedu/duke/common/InputValidator.java new file mode 100644 index 0000000000..84f78f8565 --- /dev/null +++ b/src/main/java/seedu/duke/common/InputValidator.java @@ -0,0 +1,31 @@ +package seedu.duke.common; + +import java.util.ArrayList; + +public class InputValidator { + //@@author H-horizon + public static boolean hasInvalidCharacter(String input) { + + ArrayList invalidCharacters = new ArrayList<>(); + initialiseInvalidCharactersList(invalidCharacters); + + for (String invalidCharacter : invalidCharacters) { + if (input.contains(invalidCharacter)) { + return true; + } + } + return false; + } + + private static void initialiseInvalidCharactersList(ArrayList invalidCharacters) { + invalidCharacters.add("/"); + invalidCharacters.add("\\"); + invalidCharacters.add(":"); + invalidCharacters.add("*"); + invalidCharacters.add("?"); + invalidCharacters.add("\""); + invalidCharacters.add("<"); + invalidCharacters.add(">"); + invalidCharacters.add("|"); + } +} diff --git a/src/main/java/seedu/duke/common/Messages.java b/src/main/java/seedu/duke/common/Messages.java new file mode 100644 index 0000000000..e4923ee44b --- /dev/null +++ b/src/main/java/seedu/duke/common/Messages.java @@ -0,0 +1,191 @@ +package seedu.duke.common; + +public class Messages { + + //General messages + public static final String FORMAT_LIST_HELP = "%s \n\t- %s"; + public static final String FORMAT_LIST_ITEMS = "%2s. %s"; + public static final String FORMAT_INDEX_ITEM = "%d. %s"; + public static final String FORMAT_INDEX_ITEM_DETAILS = "%d. %s - %s"; + public static final String FORMAT_ITEM_TIME = "%s - %s"; + public static final String FORMAT_ITEM = "- %s"; + public static final String NEWLINE = System.lineSeparator(); + public static final String DIVIDER = "--------------------------------------------------------------------------"; + public static final String INDENTATION = "\t\t"; + + public static final String MESSAGE_EXIT = "Have a nice day! Bye bye!"; + public static final String MESSAGE_WELCOME = "What can I do for you today?"; + public static final String MESSAGE_CLOSED_MODULE = "%s closed."; + + public static final String TAG_GULIO = "GULIO >> "; + public static final String TAG_MODULE = "%s >> "; + + public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command."; + public static final String MESSAGE_INVALID_COMMAND = "Invalid command. Enter \"help\" for list of valid commands."; + + public static final String MESSAGE_SEPARATE_INDICES = NEWLINE + "Separate indices with a blank space."; + public static final String MESSAGE_ENTER_INDICES = + NEWLINE + "Please enter the indices of the %ss you would like to %s." + MESSAGE_SEPARATE_INDICES; + + public static final String PROMPT_ENTER_FIELD_DETAILS = NEWLINE + "Enter new %s:"; + public static final String MESSAGE_FIELD_BEING_EDITED = NEWLINE + "Editing: %s"; + public static final String MESSAGE_EDITED_FIELD = "Updated %s."; + public static final String MESSAGE_NOT_UPDATED = "Field not updated."; + public static final String MESSAGE_FIELDS_TO_EDIT = "Which fields would you like to edit?"; + + + //Parser messages + public static final String MESSAGE_MODULE_CODE_EMPTY = "Module not specified."; + public static final String MESSAGE_INVALID_MODULE_CODE = "Invalid module code. " + NEWLINE + + "Please use the format [Subject][Catalog Number][Variant]. (E.g. CS2113T)"; + + public static final String MESSAGE_LESSON_FIELDS_EMPTY = "Missing lesson details."; + public static final String MESSAGE_INVALID_LESSON_TYPE = "Invalid lesson type entered."; + public static final String MESSAGE_INVALID_LESSON_LINK = "Invalid link entered. "; + public static final String MESSAGE_INVALID_LESSON_EMAIL = "Invalid email entered. "; + + public static final String MESSAGE_TASK_FIELDS_EMPTY = "Missing task details."; + public static final String MESSAGE_TASK_DESCRIPTION_EMPTY = "Missing task description."; + public static final String MESSAGE_INVALID_TASK_DEADLINE = "Invalid/missing deadline."; + + public static final String MESSAGE_NON_INTEGER_INDICES = "Warning, non-integer values removed: %s"; + public static final String MESSAGE_OUT_OF_BOUNDS_INDICES = "Warning, out of bounds index removed: %s"; + public static final String MESSAGE_NON_INTEGER_INDEX = "Warning, value entered is not a valid integer!"; + public static final String MESSAGE_OUT_OF_BOUNDS_INDEX = "Warning, index given is out of bounds/invalid!"; + + + //Module messages + public static final String MESSAGE_ADDED_MODULE = "Added %s to the module list."; + public static final String MESSAGE_REMOVED_MODULE = "Removed %s from the module list."; + public static final String MESSAGE_DUPLICATE_MODULE = "Module code %s already exists in the module list."; + public static final String MESSAGE_INVALID_MODULE = "Unable to find %s."; + public static final String MESSAGE_NO_MODULES_TO_DELETE = "No modules to be deleted."; + public static final String MESSAGE_DASHBOARD_HELP = "Dashboard commands information" + + "\n------------------------------"; + public static final String MESSAGE_MODULE_HELP = "Module commands information" + + "\n---------------------------"; + + public static final String MESSAGE_MODULE_TO_DELETE = "Which modules would you like to delete?" + NEWLINE; + public static final String MESSAGE_MODULE_TO_LIST = "Modules in your list:"; + public static final String MESSAGE_MODULE_OPENED = "Opening %s." + NEWLINE; + + + //Lesson messages + public static final String FORMAT_INDEX_LESSON_DETAILS = "%d. %s (%s)"; + + public static final String MESSAGE_ADDED_LESSON = "Added %s to lesson list."; + public static final String MESSAGE_REMOVED_LESSON = "Removed %s."; + public static final String MESSAGE_OPENED_LESSON_LINK = "Opening %s link in browser."; + public static final String MESSAGE_UNABLE_TO_OPEN_LINK = "Cannot open lesson link"; + + public static final String MESSAGE_LESSONS_TO_DELETE = "Which lessons would you like to delete?"; + public static final String MESSAGE_LESSONS_TO_LIST = "Lessons for %s:"; + public static final String MESSAGE_LESSON_TO_OPEN_LINK = "Which lesson's link would you like to open?"; + public static final String MESSAGE_TEACHING_STAFF_TO_LIST = "Teaching staff for %s:"; + + public static final String MESSAGE_LESSON_TO_EDIT = "Which lesson would you like to edit?"; + + public static final String WARNING_NO_VALID_INPUT = "No valid inputs received, lesson unchanged."; + public static final String MESSAGE_NO_CHANGES = "No changes to lesson list."; + + public static final String MESSAGE_LESSONS_LIST_EMPTY = "Your list of lessons is empty."; + + + //Task messages + public static final String FORMAT_DAY_REMAINING = " (1 day remaining)"; + public static final String FORMAT_DAYS_REMAINING = " (%d days remaining)"; + public static final String FORMAT_OVERDUE = " (Overdue by %d days)"; + public static final String FORMAT_DUE_TODAY = " (Due today)"; + + public static final String HEADER_DONE = "[Done]"; + public static final String HEADER_UNDONE = "[Undone]"; + + public static final String MESSAGE_GRADED = " (graded) "; + public static final String MESSAGE_GRADED_STATUS = "Graded"; + public static final String MESSAGE_UNGRADED_STATUS = "Not graded"; + + public static final String MESSAGE_TASK_CHECK_GRADED = "Is this task graded? (Y / N)"; + public static final String MESSAGE_TASK_CHECK_GRADED_INFO = "Please enter \"Y\" or \"N\""; + + public static final String MESSAGE_ADDED_TASK = "Added %s to task list."; + public static final String MESSAGE_REMOVED_TASK = "Removed %s from the task list."; + public static final String MESSAGE_MARKED_AS_DONE = "Marked %s as done."; + public static final String MESSAGE_MARKED_AS_UNDONE = "Marked %s as undone."; + + public static final String MESSAGE_TASKS_TO_DELETE = "Which tasks would you like to delete?"; + public static final String MESSAGE_TASKS_TO_LIST = "Tasks for %s:"; + public static final String MESSAGE_TASKS_TO_MARK = "Which undone tasks have you completed?"; + public static final String MESSAGE_TASKS_TO_UNMARK = "Which done tasks would you like to undo?"; + public static final String MESSAGE_TASK_TO_EDIT = "Which task would you like to edit?"; + + public static final String MESSAGE_DUPLICATE_TASK = "This task is already in the list."; + public static final String MESSAGE_SAME_DESCRIPTION_TASK = "This task already exists but " + + "with different field(s)." + NEWLINE + + "You could edit this task instead."; + public static final String MESSAGE_TASK_LIST_EMPTY = "No tasks to %s."; + public static final String MESSAGE_NO_TASK_MODIFIED = "No changes to task list."; + + public static final String MESSAGE_TASKS_EMPTY = "No task here."; + public static final String MESSAGE_TASKS_DONE = "You have completed all your tasks."; + + + //Storage messages + public static final String MESSAGE_WRITER_FAILED_TO_SAVE = "Error: Failed to save data."; + public static final String FILE_INSTRUCTIONS = " Data File" + NEWLINE + + NEWLINE + + "Note for editing:" + NEWLINE + + "Please follow the format strictly when adding/editing/removing lessons or tasks." + NEWLINE + + "Do not edit anything above the line, as well as the line." + NEWLINE + + "Please do not use '\\n' in any of the entries." + NEWLINE + + "Please do not use '|' except for separating fields." + NEWLINE + + NEWLINE + + "Choose 1 of the 4 formats for lessons:" + NEWLINE + + "1) lesson | | " + NEWLINE + + "2) lesson | | | " + NEWLINE + + "3) lesson | | | | " + NEWLINE + + "4) lesson | | | | | " + NEWLINE + + NEWLINE + + "Type: \"lecture\", \"tutorial\" or \"lab\"." + NEWLINE + + "Day & time: When the lesson occurs." + NEWLINE + + "Link: Online meeting link for the lesson." + NEWLINE + + "Teaching staff name: Name of teaching staff for the lesson." + NEWLINE + + "Teaching staff email: Email of teaching staff for the lesson." + NEWLINE + + NEWLINE + + "Choose 1 of the 2 formats for tasks:" + NEWLINE + + "1) task | | | | " + NEWLINE + + "2) task | | | | | " + NEWLINE + + NEWLINE + + "Description: Name/description of the task." + NEWLINE + + "Deadline: In the format DD-MM-YYYY." + NEWLINE + + "Is done: 'T' for true and 'F' for false." + NEWLINE + + "Is graded: 'T' for true and 'F' for false." + NEWLINE + + "Remarks: Additional information for task." + NEWLINE + + NEWLINE + + "--------------------------------------------------------------------------------" + NEWLINE + + NEWLINE; + + + //Module info messages + public static final String FORMAT_MODULE_INFO = ""; + public static final String MESSAGE_TASKS_TO_LIST_UNDONE = "Undone tasks:"; + + + //Logging messages + public static final String MESSAGE_LOAD_FAILED = "LOADER: failed to load %s."; + + + //Cheat-sheet messages + public static final String MESSAGE_CHEAT_SHEET_ALREADY_EXISTS = "Cheat sheet already exists!"; + public static final String MESSAGE_CHEATSHEET_ADDED = "%s has been added to your Cheatsheet folder."; + public static final String MESSAGE_FILE_HAS_BEEN_DELETED = "%s has been deleted!"; + public static final String MESSAGE_FILE_DOES_NOT_EXIST = "%s does not exist!"; + public static final String MESSAGE_LIST_OF_CHEAT_SHEETS = "Here is your list of cheat-sheets:" + NEWLINE; + public static final String MESSAGE_EMPTY_CHEAT_SHEET_DIRECTORY = "You have no cheat-sheet."; + public static final String MESSAGE_OPEN_FILE = "Opened %s."; + public static final String MESSAGE_INVALID_FILE_NAME = "Invalid file name."; + public static final String MESSAGE_CLOSE_CHEATSHEET_FIRST = + "Please close the existing cheat-sheet first."; + public static final String TEXT_EDITOR_INSTRUCTION = + "Press esc to close, ctrl-s to save, ctrl-up to enlarge and ctrl-down to shrink text."; + +} diff --git a/src/main/java/seedu/duke/common/ModuleCommands.java b/src/main/java/seedu/duke/common/ModuleCommands.java new file mode 100644 index 0000000000..34035e320b --- /dev/null +++ b/src/main/java/seedu/duke/common/ModuleCommands.java @@ -0,0 +1,110 @@ +package seedu.duke.common; + +//@@author isaharon +public enum ModuleCommands implements CommandList { + + HELP("help", "Lists all commands available for the selected module."), + CLOSE("close", "Closes the current module."), + INFO("info", "Prints an overview of the current module."), + ADD_LESSON("add lsn", + "LESSON_NAME ;; [DAY_AND_TIME STRING] ;; [LESSON LINK] ;; [STAFF_NAME] ;; [EMAIL]", + "Adds a new lesson."), + DELETE_LESSON("del lsn", "Deletes lessons."), + EDIT_LESSON("edit lsn", "Edits specified fields of a chosen lesson."), + LINK("link", "Opens link to the lesson in a browser."), + TEACHER("tch", "Lists all teaching staff."), + LESSONS("lsn", "Lists all lessons."), + ADD_TASK("add task", "NAME ;; DD-MM-YYYY ;; [REMARKS]", "Adds new task."), + DELETE_TASK("del task", "Deletes specified tasks."), + EDIT_TASK("edit task", "Edits specified fields of a chosen task."), + MARK("mark", "Marks specified tasks as done."), + UNMARK("unmark", "Marks specified tasks as undone."), + TASKS("task", "Lists all tasks."), + ADD_CHEAT_SHEET("add cs", "[Cheat-Sheet name]", + "Add a new cheat-sheet for that module."), + DELETE_CHEAT_SHEET("del cs", "[Cheat-Sheet name]", + "Delete a cheat-sheet stored in the cheat-sheet directory of the module."), + EDIT_CHEAT_SHEET("edit cs", "[Cheat-Sheet name]", + "Edit a cheat-sheet stored in the cheat-sheet directory of the module."), + LIST_CHEAT_SHEET("cs", "Lists all cheat-sheets"); + + private final String word; + private final String argumentsFormat; + private final String description; + + ModuleCommands(String word, String description) { + this.word = word; + this.argumentsFormat = Constants.EMPTY_STRING; + this.description = description; + } + + ModuleCommands(String word, String argumentsFormat, String description) { + this.word = word; + this.argumentsFormat = argumentsFormat; + this.description = description; + } + + public String getWord() { + return word; + } + + @Override + public String getArgumentsFormat() { + return argumentsFormat; + } + + public String getDescription() { + return description; + } + + //@@author ivanchongzhien + + /** + * Converts given string to a module command. + * + * @param commandWord word string + * @return the enum type of ModuleCommands representing the command specified + */ + public static ModuleCommands getModuleCommandsFromString(String commandWord) { + if (commandWord.equalsIgnoreCase(ModuleCommands.HELP.getWord())) { + return ModuleCommands.HELP; + } else if (commandWord.equalsIgnoreCase(CLOSE.getWord())) { + return CLOSE; + } else if (commandWord.equalsIgnoreCase(INFO.getWord())) { + return INFO; + } else if (commandWord.equalsIgnoreCase(LESSONS.getWord())) { + return LESSONS; + } else if (commandWord.equalsIgnoreCase(LINK.getWord())) { + return LINK; + } else if (commandWord.equalsIgnoreCase(TASKS.getWord())) { + return TASKS; + } else if (commandWord.equalsIgnoreCase(MARK.getWord())) { + return MARK; + } else if (commandWord.equalsIgnoreCase(UNMARK.getWord())) { + return UNMARK; + } else if (commandWord.equalsIgnoreCase(TEACHER.getWord())) { + return TEACHER; + } else if (commandWord.equalsIgnoreCase(ADD_LESSON.getWord())) { + return ADD_LESSON; + } else if (commandWord.equalsIgnoreCase(DELETE_LESSON.getWord())) { + return DELETE_LESSON; + } else if (commandWord.equalsIgnoreCase(ADD_TASK.getWord())) { + return ADD_TASK; + } else if (commandWord.equalsIgnoreCase(DELETE_TASK.getWord())) { + return DELETE_TASK; + } else if (commandWord.equalsIgnoreCase(ADD_CHEAT_SHEET.getWord())) { + return ADD_CHEAT_SHEET; + } else if (commandWord.equalsIgnoreCase(DELETE_CHEAT_SHEET.getWord())) { + return DELETE_CHEAT_SHEET; + } else if (commandWord.equalsIgnoreCase(EDIT_CHEAT_SHEET.getWord())) { + return EDIT_CHEAT_SHEET; + } else if (commandWord.equalsIgnoreCase(LIST_CHEAT_SHEET.getWord())) { + return LIST_CHEAT_SHEET; + } else if (commandWord.equalsIgnoreCase(EDIT_TASK.getWord())) { + return EDIT_TASK; + } else if (commandWord.equalsIgnoreCase(EDIT_LESSON.getWord())) { + return EDIT_LESSON; + } + return null; + } +} diff --git a/src/main/java/seedu/duke/editor/ShortcutListener.java b/src/main/java/seedu/duke/editor/ShortcutListener.java new file mode 100644 index 0000000000..1f557640e8 --- /dev/null +++ b/src/main/java/seedu/duke/editor/ShortcutListener.java @@ -0,0 +1,119 @@ +package seedu.duke.editor; + +import javax.swing.JFrame; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +import static seedu.duke.common.Constants.KEYCODE_DOWN; +import static seedu.duke.common.Constants.KEYCODE_UP; +import static seedu.duke.common.Constants.KEYCODE_S; + +//@@author 8kdesign +public class ShortcutListener implements KeyListener { + + JFrame frame; + TextEditor textEditor; + boolean isCtrlPressed = false; + boolean isSPressed = false; + boolean isUpPressed = false; + boolean isDownPressed = false; + + public ShortcutListener(JFrame frame, TextEditor editor) { + this.frame = frame; + this.textEditor = editor; + } + + @Override + public void keyTyped(KeyEvent e) { + + } + + @Override + public void keyPressed(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_ESCAPE: { + //Close text editor + frame.dispose(); + break; + } + case KeyEvent.VK_CONTROL: { + isCtrlPressed = true; + checkSaveShortcut(); + break; + } + case KEYCODE_S: { + isSPressed = true; + checkSaveShortcut(); + break; + } + case KEYCODE_UP: { + isUpPressed = true; + checkZoomShortcut(); + break; + } + case KEYCODE_DOWN: { + isDownPressed = true; + checkZoomShortcut(); + break; + } + default: + } + } + + @Override + public void keyReleased(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_CONTROL: { + isCtrlPressed = false; + break; + } + case KEYCODE_S: { + isSPressed = false; + break; + } + case KEYCODE_UP: { + isUpPressed = false; + break; + } + case KEYCODE_DOWN: { + isDownPressed = false; + break; + } + default: + } + } + + /** + * Checks if keys for save shortcut are pressed. + * Calls save file method if true. + */ + void checkSaveShortcut() { + if (isSPressed && isCtrlPressed) { + isSPressed = false; + //Save + textEditor.saveTextToFile(); + } + } + + /** + * Check if keys for zoom shortcut are pressed. + * Calls save file method if true. + */ + void checkZoomShortcut() { + if (!isCtrlPressed) { + return; + } + if (isDownPressed && isUpPressed) { + return; + } + if (isUpPressed) { + isUpPressed = false; + //Increase font size + textEditor.increaseFontSize(); + } else if (isDownPressed) { + isDownPressed = false; + //Decrease font size + textEditor.decreaseFontSize(); + } + } +} diff --git a/src/main/java/seedu/duke/editor/TextEditor.java b/src/main/java/seedu/duke/editor/TextEditor.java new file mode 100644 index 0000000000..ddb41bdf57 --- /dev/null +++ b/src/main/java/seedu/duke/editor/TextEditor.java @@ -0,0 +1,203 @@ +package seedu.duke.editor; + +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.ScrollPaneConstants; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.GraphicsEnvironment; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Scanner; + + +import static seedu.duke.common.Constants.FONT_SIZE_MAX; +import static seedu.duke.common.Constants.FONT_SIZE_MIN; +import static seedu.duke.common.Constants.SAVE_ICON; +import static seedu.duke.common.Constants.TEXT_AREA_HEIGHT; +import static seedu.duke.common.Constants.TEXT_AREA_WIDTH; +import static seedu.duke.common.Constants.TEXT_EDITOR_HEIGHT; +import static seedu.duke.common.Constants.TEXT_EDITOR_TITLE; +import static seedu.duke.common.Constants.TEXT_EDITOR_WIDTH; +import static seedu.duke.common.Messages.NEWLINE; +import static seedu.duke.common.Messages.TEXT_EDITOR_INSTRUCTION; + +public class TextEditor extends JFrame implements ActionListener { + + public static String[] fontStyles = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + public static JTextArea textArea = new JTextArea(); + public static JScrollPane scrollPane = new JScrollPane(textArea); + public static JComboBox fontStyleBox = new JComboBox<>(fontStyles); + public static JButton saveButton = new JButton(SAVE_ICON); + public static String pathName; + + private static TextEditor textEditor; + + //@@author H-horizon + private TextEditor(String path, String fileName) { + setPathName(path); + setTextEditorTitle(fileName); + setCloseIcon(); + setTextEditorDimension(); + setSaveIcon(); + setTextArea(); + setTextAreaToVoid(); + setScrollPane(); + setInstructionText(); + setLayout(); + setShortcutListener(); + loadFile(path); + } + + //@@author 8kdesign + public static boolean createNew(String path, String fileName) { + if (textEditor == null) { + textEditor = new TextEditor(path, fileName); + textEditor.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + super.windowClosed(e); + textEditor = null; + } + }); + return true; + } else { + return false; + } + } + + private void setInstructionText() { + this.add(new JLabel(TEXT_EDITOR_INSTRUCTION)); + } + + public static boolean isNull() { + return textEditor == null; + } + + //@@author H-horizon + private void loadFile(String filePath) { + File file = new File(filePath); + Scanner fileReader = null; + try { + fileReader = new Scanner(file); + while (fileReader.hasNext()) { + String line = fileReader.nextLine() + NEWLINE; + textArea.append(line); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } finally { + assert fileReader != null; + fileReader.close(); + } + } + + private void setTextAreaToVoid() { + textArea.setText(null); + } + + private void setPathName(String path) { + pathName = path; + } + + private void setSaveIcon() { + saveButton.addActionListener(this); + this.add(saveButton); + } + + private void setLayout() { + this.setLocationRelativeTo(null); + this.setLayout(new FlowLayout()); + this.setVisible(true); + textArea.requestFocus(); + } + + private void setFontStyleIcon() { + String[] fontStyles = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + fontStyleBox = new JComboBox<>(fontStyles); + fontStyleBox.addActionListener(this); + fontStyleBox.setSelectedItem(textArea.getFont().getFontName()); + add(fontStyleBox); + } + + public void increaseFontSize() { + int size = textArea.getFont().getSize(); + if (size >= FONT_SIZE_MAX) { + return; + } + size++; + textArea.setFont(new Font(textArea.getFont().getFamily(), Font.PLAIN, size)); + } + + public void decreaseFontSize() { + int size = textArea.getFont().getSize(); + if (size <= FONT_SIZE_MIN) { + return; + } + size--; + textArea.setFont(new Font(textArea.getFont().getFamily(), Font.PLAIN, size)); + } + + private void setScrollPane() { + scrollPane.setPreferredSize(new Dimension(TEXT_AREA_WIDTH, TEXT_AREA_HEIGHT)); + scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + add(scrollPane); + } + + private void setTextArea() { + textArea.setLineWrap(true); + textArea.setWrapStyleWord(true); + textArea.setFont(new Font(textArea.getFont().getFontName(), Font.PLAIN, textArea.getFont().getSize())); + } + + private void setTextEditorDimension() { + this.setSize(TEXT_EDITOR_WIDTH, TEXT_EDITOR_HEIGHT); + } + + private void setCloseIcon() { + this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + } + + private void setTextEditorTitle(String fileName) { + this.setTitle(String.format(TEXT_EDITOR_TITLE, fileName)); + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == fontStyleBox) { + openFontStyleOptions(); + } else if (e.getSource() == saveButton) { + saveTextToFile(); + } + } + + public void saveTextToFile() { + try { + FileWriter fileWriter = new FileWriter(pathName); + fileWriter.write(textArea.getText()); + fileWriter.close(); + } catch (IOException fileNotFoundException) { + assert false : "File has not been created"; + } + } + + private void openFontStyleOptions() { + textArea.setFont(new Font((String) fontStyleBox.getSelectedItem(), Font.PLAIN, + textArea.getFont().getSize())); + } + + private void setShortcutListener() { + textArea.addKeyListener(new ShortcutListener(this, this)); + } +} diff --git a/src/main/java/seedu/duke/exception/CommandException.java b/src/main/java/seedu/duke/exception/CommandException.java new file mode 100644 index 0000000000..bcc5feb9d7 --- /dev/null +++ b/src/main/java/seedu/duke/exception/CommandException.java @@ -0,0 +1,11 @@ +package seedu.duke.exception; + +/** + * Exception class for Commands. + */ +public class CommandException extends DukeException { + + public CommandException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/duke/exception/DukeException.java b/src/main/java/seedu/duke/exception/DukeException.java new file mode 100644 index 0000000000..e83822c19c --- /dev/null +++ b/src/main/java/seedu/duke/exception/DukeException.java @@ -0,0 +1,15 @@ +package seedu.duke.exception; + +/** + * Main class for all exceptions within the program. + */ +public class DukeException extends Exception { + + /** + * Sole constructor for DukeException. Requires all exceptions to have an error message. + * @param message error message of the exception + */ + public DukeException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/duke/exception/ParserException.java b/src/main/java/seedu/duke/exception/ParserException.java new file mode 100644 index 0000000000..27706f2b33 --- /dev/null +++ b/src/main/java/seedu/duke/exception/ParserException.java @@ -0,0 +1,8 @@ +package seedu.duke.exception; + +public class ParserException extends DukeException { + + public ParserException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/duke/lesson/Lesson.java b/src/main/java/seedu/duke/lesson/Lesson.java new file mode 100644 index 0000000000..a54807a5e7 --- /dev/null +++ b/src/main/java/seedu/duke/lesson/Lesson.java @@ -0,0 +1,105 @@ +package seedu.duke.lesson; + +import static seedu.duke.common.Constants.COLON; +import static seedu.duke.common.Constants.FORMAT_LINK; +import static seedu.duke.common.Constants.LESSON_FIELD_2_LINK; +import static seedu.duke.common.Constants.LESSON_FIELD_3_T_NAME; +import static seedu.duke.common.Constants.LESSON_FIELD_4_T_EMAIL; + +public class Lesson { + + private final LessonType lessonType; + private String time; + private String onlineLink; + private TeachingStaff teachingStaff; + + public Lesson(LessonType lessonType, String time, String onlineLink, TeachingStaff teachingStaff) { + this.lessonType = lessonType; + this.time = time; + this.onlineLink = onlineLink; + this.teachingStaff = teachingStaff; + } + + public String getTime() { + return time; + } + + public LessonType getLessonType() { + return lessonType; + } + + public String getLessonTypeString() { + return LessonType.getLessonTypeString(lessonType); + } + + public TeachingStaff getTeachingStaff() { + return teachingStaff; + } + + public String getOnlineLink() { + return onlineLink; + } + + public void setTime(String time) { + this.time = time; + } + + public void setOnlineLink(String onlineLink) { + this.onlineLink = onlineLink; + } + + public void setTeachingStaff(TeachingStaff teachingStaff) { + this.teachingStaff = teachingStaff; + } + + public String getTeachingStaffName() { + return teachingStaff.getName(); + } + + public String getTeachingStaffEmail() { + return teachingStaff.getEmail(); + } + + public void setTeachingStaffName(String name) { + teachingStaff.setName(name); + } + + public void setTeachingStaffEmail(String email) { + teachingStaff.setEmail(email); + } + + //@@author ivanchongzhien + /** + * Check if given string is a valid link. + * + * @param link string to be checked + * @return true if string follows the format of a valid email + */ + public static boolean isValidLink(String link) { + return link.matches(FORMAT_LINK); + } + + //@@author aliciatay-zls + /** + * Returns the value of a filled field as a string, in this order of availability: + * day and time, link, teacher's name, teacher's email. + * Returns null if none of the lesson's fields have been filled by the user yet. + */ + public String getDetailsStringIfAny() { + if (!this.time.isEmpty()) { + return this.time; + } else if (!this.onlineLink.isEmpty()) { + return LESSON_FIELD_2_LINK + COLON + this.onlineLink; + } else if (this.teachingStaff != null) { + String nameField = this.teachingStaff.getName(); + String emailField = this.teachingStaff.getEmail(); + if (!nameField.isEmpty()) { + return LESSON_FIELD_3_T_NAME + COLON + nameField; + } + if (!emailField.isEmpty()) { + return LESSON_FIELD_4_T_EMAIL + COLON + emailField; + } + } + return null; + } +} diff --git a/src/main/java/seedu/duke/lesson/LessonType.java b/src/main/java/seedu/duke/lesson/LessonType.java new file mode 100644 index 0000000000..322663f001 --- /dev/null +++ b/src/main/java/seedu/duke/lesson/LessonType.java @@ -0,0 +1,46 @@ +package seedu.duke.lesson; + +import static seedu.duke.common.Constants.LAB_STRING; +import static seedu.duke.common.Constants.LECTURE_STRING; +import static seedu.duke.common.Constants.TUTORIAL_STRING; + +public enum LessonType { + LECTURE, LAB, TUTORIAL; + + //@@author 8kdesign + /** + * Returns string of lesson type. + * + * @param lessonType Lesson type to convert. + * @return String of lesson type. + */ + public static String getLessonTypeString(LessonType lessonType) { + switch (lessonType) { + case LECTURE: + return LECTURE_STRING.substring(0,1).toUpperCase() + LECTURE_STRING.substring(1); + case TUTORIAL: + return TUTORIAL_STRING.substring(0,1).toUpperCase() + TUTORIAL_STRING.substring(1); + default: + return LAB_STRING.substring(0,1).toUpperCase() + LAB_STRING.substring(1); + } + } + + /** + * Returns lesson type if valid, null if invalid. + * + * @param input String of type. + * @return Lesson type specified. + */ + public static LessonType getLessonTypeFromString(String input) { + switch (input.toLowerCase()) { + case LECTURE_STRING: + return LessonType.LECTURE; + case TUTORIAL_STRING: + return LessonType.TUTORIAL; + case LAB_STRING: + return LessonType.LAB; + default: + return null; + } + } +} diff --git a/src/main/java/seedu/duke/lesson/TeachingStaff.java b/src/main/java/seedu/duke/lesson/TeachingStaff.java new file mode 100644 index 0000000000..7ad7cf5673 --- /dev/null +++ b/src/main/java/seedu/duke/lesson/TeachingStaff.java @@ -0,0 +1,41 @@ +package seedu.duke.lesson; + +import static seedu.duke.common.Constants.FORMAT_EMAIL; + +public class TeachingStaff { + + private String name; + private String email; + + public TeachingStaff(String name, String email) { + this.name = name; + this.email = email; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public void setName(String name) { + this.name = name; + } + + public void setEmail(String email) { + this.email = email; + } + + //@@author ivanchongzhien + /** + * Check if given string is a valid email. + * + * @param email string to be checked + * @return true if string follows the format of a valid email + */ + public static boolean isValidEmail(String email) { + return email.trim().matches(FORMAT_EMAIL); + } +} diff --git a/src/main/java/seedu/duke/module/Module.java b/src/main/java/seedu/duke/module/Module.java new file mode 100644 index 0000000000..5efe8efe3a --- /dev/null +++ b/src/main/java/seedu/duke/module/Module.java @@ -0,0 +1,111 @@ +package seedu.duke.module; + +import seedu.duke.lesson.Lesson; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.time.LocalDate; +import java.util.ArrayList; + +import static seedu.duke.common.Messages.MESSAGE_DUPLICATE_TASK; +import static seedu.duke.common.Messages.MESSAGE_SAME_DESCRIPTION_TASK; +import static seedu.duke.common.Constants.FORMAT_MODULE_CODE; + +public class Module { + + private final String moduleCode; + private final ArrayList lessonList; + private final ArrayList taskList; + + public Module(String moduleCode) { + this.moduleCode = moduleCode; + this.lessonList = new ArrayList<>(); + this.taskList = new ArrayList<>(); + } + + public String getModuleCode() { + return moduleCode; + } + + //Lesson + public ArrayList getLessonList() { + return lessonList; + } + + public void addLesson(Lesson lesson) { + lessonList.add(lesson); + } + + public void removeLesson(int index) { + lessonList.remove(index); + } + + //Task + public ArrayList getTaskList() { + return taskList; + } + + public void addTask(Task task) { + taskList.add(task); + } + + public void removeTask(Task task) { + taskList.remove(task); + } + + //@@author aliciatay-zls + /** + * Returns array list of done/undone tasks. + * + * @param isDone Boolean to filter done or undone. + * @return Array list of filtered tasks. + */ + public ArrayList getDoneOrUndoneTasks(boolean isDone) { + ArrayList filteredTasks = new ArrayList<>(); + for (Task task : taskList) { + if (task.getDone() == isDone) { + filteredTasks.add(task); + } + } + return filteredTasks; + } + + /** + * Prevents task from being added once a duplicate (same task description and deadline) + * is found in the list. If same task description but different deadline, suggest + * editing task fields instead. + * + * @param ui Instance of UI + * @param targetTask Task the user is trying to add to the list + * @return true when no task with same description is found. + */ + public boolean isAddTaskAllowed(UI ui, Task targetTask) { + String targetDescription = targetTask.getDescription().toUpperCase(); + LocalDate targetDeadline = targetTask.getDeadline(); + for (Task task : taskList) { + if (targetDescription.equals(task.getDescription().toUpperCase())) { + if (targetDeadline.equals(task.getDeadline())) { + ui.printMessage(MESSAGE_DUPLICATE_TASK); + return false; + } + ui.printMessage(MESSAGE_SAME_DESCRIPTION_TASK); + return false; + } + } + return true; + } + + //@@author ivanchongzhien + /** + * Checks if given string is a valid module name. + * + * @param moduleCode string to be validated + * @return true if string is a valid module name + */ + public static boolean isValidModuleCode(String moduleCode) { + moduleCode = moduleCode.trim(); + + // check that input matches the convention of a standard NUS module code. + return (moduleCode.matches(FORMAT_MODULE_CODE)); + } +} diff --git a/src/main/java/seedu/duke/module/ModuleList.java b/src/main/java/seedu/duke/module/ModuleList.java new file mode 100644 index 0000000000..3714943e8c --- /dev/null +++ b/src/main/java/seedu/duke/module/ModuleList.java @@ -0,0 +1,193 @@ +package seedu.duke.module; + +import seedu.duke.lesson.Lesson; +import seedu.duke.storage.Loader; +import seedu.duke.storage.Writer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +import static seedu.duke.common.CommonMethods.getDaysRemaining; + +public class ModuleList { + + private static final ArrayList modules = new ArrayList<>(); + private static Module selectedModule; + + public static Module getSelectedModule() { + return selectedModule; + } + + public static ArrayList getModules() { + return modules; + } + + public static String getModuleByIndex(int index) { + return getModules().get(index); + } + + public static int getModuleIndex(String moduleCode) { + return getModules().indexOf(moduleCode); + } + + public static int getSize() { + return getModules().size(); + } + + public static String getSelectedModuleCode() { + return selectedModule.getModuleCode(); + } + + //@@author ivanchongzhien + public static boolean hasSelectedModule() { + return selectedModule != null; + } + + //@@author 8kdesign + + /** + * Searches directory for module files. + * Adds their name (excluding ".txt") to the module list. + */ + public static void loadModuleCodes() { + modules.clear(); + Loader loader = new Loader(); + for (String moduleCode : loader.getModules()) { + insertModule(moduleCode); + } + } + + /** + * Adds a new module to the module list and add create file for new module. + * + * @param moduleCode Module name, excluding ".txt". + */ + public static boolean addModule(String moduleCode) { + if (insertModule(moduleCode)) { + Writer writer = new Writer(); + writer.createFile(moduleCode); + return true; + } + return false; + } + + /** + * Adds a module to the module list. + * + * @param moduleCode Module code, excluding ".txt". + */ + private static boolean insertModule(String moduleCode) { + if (modules.contains(moduleCode)) { + //Error, module already exists + return false; + } + modules.add(moduleCode); + return true; + } + + //@@author isaharon + + /** + * Deletes modules specified. + * + * @param moduleNumbers Index of modules to delete. + * @return List of names of modules that are deleted. + */ + public static ArrayList deleteModules(ArrayList moduleNumbers) { + ArrayList deletedModules = new ArrayList<>(); + Collections.reverse(moduleNumbers); + for (Integer moduleNumber : moduleNumbers) { + int indexToRemove = moduleNumber - 1; + String name = removeModule(indexToRemove); + if (name != null) { + deletedModules.add(0, name); + } + } + return deletedModules; + } + + //@@author 8kdesign + + /** + * Removes selected module and deletes module file. + * + * @param index Index of module to remove. + */ + public static String removeModule(int index) { + if (index < 0 || index >= modules.size()) { + return null; + } + Writer writer = new Writer(); + if (writer.deleteDirectory(modules.get(index))) { + String moduleName = modules.get(index); + modules.remove(index); + return moduleName; + } else { + //Unable to remove + return null; + } + } + + /** + * Loads the current module from storage. + * + * @param moduleCode Module name, excluding ".txt". + * @return True if successful, false if unable to find file. + */ + public static boolean setSelectedModule(String moduleCode) { + Loader loader = new Loader(); + if (!modules.contains(moduleCode)) { + //Unable to find file + return false; + } + selectedModule = loader.loadModule(moduleCode); + if (selectedModule != null) { + //Sort data + sortLessons(); + sortTasks(); + //Remove invalid inputs + Writer writer = new Writer(); + writer.writeModule(); + } + return selectedModule != null; + } + + /** + * Resets selected module by setting it to null. + */ + public static void reset() { + selectedModule = null; + } + + /** + * Writes updated data to file of selected module. + */ + public static void writeModule() { + Writer writer = new Writer(); + writer.writeModule(); + } + + /** + * Sorts tasks by deadline. + */ + public static void sortTasks() { + selectedModule.getTaskList().sort((task1, task2) -> { + long daysRemaining1 = getDaysRemaining(task1.getDeadline()); + long daysRemaining2 = getDaysRemaining(task2.getDeadline()); + if (daysRemaining1 != daysRemaining2) { + return (int) (daysRemaining1 - daysRemaining2); + } + return task1.getDescription().compareTo(task2.getDescription()); + }); + } + + //@@author H-horizon + + /** + * Sorts lesson list by lesson type. + */ + public static void sortLessons() { + selectedModule.getLessonList().sort(Comparator.comparing(Lesson::getLessonType)); + } +} diff --git a/src/main/java/seedu/duke/parser/Parser.java b/src/main/java/seedu/duke/parser/Parser.java new file mode 100644 index 0000000000..3bc2f82539 --- /dev/null +++ b/src/main/java/seedu/duke/parser/Parser.java @@ -0,0 +1,186 @@ +package seedu.duke.parser; + +import seedu.duke.commands.AddCheatSheetCommand; +import seedu.duke.commands.AddLessonCommand; +import seedu.duke.commands.AddModuleCommand; +import seedu.duke.commands.AddTaskCommand; +import seedu.duke.commands.Command; +import seedu.duke.commands.DeleteCheatSheetCommand; +import seedu.duke.commands.DeleteLessonCommand; +import seedu.duke.commands.DeleteModuleCommand; +import seedu.duke.commands.DeleteTaskCommand; +import seedu.duke.commands.EditCheatSheetCommand; +import seedu.duke.commands.EditLessonCommand; +import seedu.duke.commands.EditTaskCommand; +import seedu.duke.commands.EnterModuleCommand; +import seedu.duke.commands.ExitModuleCommand; +import seedu.duke.commands.ExitProgramCommand; +import seedu.duke.commands.ListCheatSheetsCommand; +import seedu.duke.commands.ListLessonsCommand; +import seedu.duke.commands.ListModulesCommand; +import seedu.duke.commands.ListTasksCommand; +import seedu.duke.commands.MarkAsDoneCommand; +import seedu.duke.commands.MarkAsUndoneCommand; +import seedu.duke.commands.ModuleInfoCommand; +import seedu.duke.commands.OpenLessonLinkCommand; +import seedu.duke.commands.PrintHelpCommand; +import seedu.duke.commands.ViewTeachingStaffCommand; +import seedu.duke.common.DashboardCommands; +import seedu.duke.common.ModuleCommands; +import seedu.duke.exception.ParserException; +import seedu.duke.lesson.Lesson; +import seedu.duke.module.ModuleList; +import seedu.duke.task.Task; + +import static seedu.duke.common.Constants.INDEX_COMMAND_ARGS; +import static seedu.duke.common.Constants.INDEX_COMMAND_WORD; +import static seedu.duke.common.Messages.MESSAGE_INVALID_COMMAND; +import static seedu.duke.common.Messages.MESSAGE_UNKNOWN_COMMAND; + +public class Parser { + + //@@author ivanchongzhien + + /** + * Calls the appropriate parser method depending on whether user is at dashboard or has selected + * a module. + * + * @param input full user input string + * @return command object based on user input + * @throws ParserException if valid command cannot be parsed from user input + */ + public Command parse(String input) throws ParserException { + Command parsedCommand; + + if (ModuleList.hasSelectedModule()) { + parsedCommand = parseInModule(input); + } else { + parsedCommand = parseAtDashboard(input); + } + return parsedCommand; + } + + /** + * Parses dashboard commands from user input. + * User is yet to select a module. + * + * @param input full user input string + * @return dashboard command object based on user input + * @throws ParserException if valid command cannot be parsed from user input + */ + private Command parseAtDashboard(String input) throws ParserException { + String[] commandWordAndArgs = ParserUtil.getCommandWordAndArgs(input); + String commandWord = commandWordAndArgs[INDEX_COMMAND_WORD]; + String commandArgs = commandWordAndArgs[INDEX_COMMAND_ARGS].trim(); + + DashboardCommands command = DashboardCommands.getDashboardCommandFromString(commandWord); + if (command == null) { + throw new ParserException(MESSAGE_INVALID_COMMAND); + } + + // commands which require arguments + switch (command) { + case ADD: + String moduleCodeToAdd = ParserUtil.parseModuleCode(commandArgs); + return new AddModuleCommand(moduleCodeToAdd); + case OPEN: + String moduleCodeToOpen = ParserUtil.parseModuleCode(commandArgs); + return new EnterModuleCommand(moduleCodeToOpen); + default: + // Fallthrough + } + + // Commands which do not require arguments + if (!commandArgs.isEmpty()) { + throw new ParserException(MESSAGE_UNKNOWN_COMMAND); + } + + switch (command) { + case HELP: + return new PrintHelpCommand(); + case EXIT: + return new ExitProgramCommand(); + case DELETE: + return new DeleteModuleCommand(); + case MODULES: + return new ListModulesCommand(); + default: + throw new ParserException(MESSAGE_UNKNOWN_COMMAND); + } + + } + + /** + * Parses in-module commands from user input. + * User has selected a module and is currently in the module. + * + * @param input full user input string + * @return in-module command object based on user input + * @throws ParserException if valid command cannot be parsed from user input + */ + private Command parseInModule(String input) throws ParserException { + String[] commandWordAndArgs = ParserUtil.getModuleCommandWordAndArgs(input); + String commandWord = commandWordAndArgs[INDEX_COMMAND_WORD]; + String commandArgs = commandWordAndArgs[INDEX_COMMAND_ARGS].trim(); + + ModuleCommands command = ModuleCommands.getModuleCommandsFromString(commandWord); + if (command == null) { + throw new ParserException(MESSAGE_INVALID_COMMAND); + } + + switch (command) { + case ADD_LESSON: + Lesson lesson = ParserUtil.parseLesson(commandArgs); + return new AddLessonCommand(lesson); + case ADD_TASK: + Task task = ParserUtil.parseTask(commandArgs); + return new AddTaskCommand(task); + case ADD_CHEAT_SHEET: + return new AddCheatSheetCommand(commandArgs); + case DELETE_CHEAT_SHEET: + return new DeleteCheatSheetCommand(commandArgs); + case EDIT_CHEAT_SHEET: + return new EditCheatSheetCommand(commandArgs); + default: + // Fallthrough + } + + // Commands which do not require arguments + if (!commandArgs.isEmpty()) { + throw new ParserException(MESSAGE_UNKNOWN_COMMAND); + } + + switch (command) { + case HELP: + return new PrintHelpCommand(); + case CLOSE: + return new ExitModuleCommand(); + case INFO: + return new ModuleInfoCommand(); + case LESSONS: + return new ListLessonsCommand(); + case LINK: + return new OpenLessonLinkCommand(); + case TASKS: + return new ListTasksCommand(); + case MARK: + return new MarkAsDoneCommand(); + case UNMARK: + return new MarkAsUndoneCommand(); + case TEACHER: + return new ViewTeachingStaffCommand(); + case DELETE_LESSON: + return new DeleteLessonCommand(); + case DELETE_TASK: + return new DeleteTaskCommand(); + case LIST_CHEAT_SHEET: + return new ListCheatSheetsCommand(); + case EDIT_TASK: + return new EditTaskCommand(); + case EDIT_LESSON: + return new EditLessonCommand(); + default: + throw new ParserException(MESSAGE_UNKNOWN_COMMAND); + } + } +} diff --git a/src/main/java/seedu/duke/parser/ParserUtil.java b/src/main/java/seedu/duke/parser/ParserUtil.java new file mode 100644 index 0000000000..b6358f8245 --- /dev/null +++ b/src/main/java/seedu/duke/parser/ParserUtil.java @@ -0,0 +1,399 @@ +package seedu.duke.parser; + +import seedu.duke.exception.ParserException; +import seedu.duke.lesson.Lesson; +import seedu.duke.lesson.LessonType; +import seedu.duke.lesson.TeachingStaff; +import seedu.duke.module.Module; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static seedu.duke.common.Constants.ADD; +import static seedu.duke.common.Constants.DELETE_COMMAND; +import static seedu.duke.common.Constants.DELIM; +import static seedu.duke.common.Constants.EDIT; +import static seedu.duke.common.Constants.EMPTY_STRING; +import static seedu.duke.common.Constants.ENTRY_LESSON_MAX_PARSER; +import static seedu.duke.common.Constants.ENTRY_TASK_MAX_PARSER; +import static seedu.duke.common.Constants.FORMAT_COMMAND_WORD_AND_ARGS; +import static seedu.duke.common.Constants.FORMAT_DATE_IO; +import static seedu.duke.common.Constants.FORMAT_TWO_COMMAND_WORD_AND_ARGS; +import static seedu.duke.common.Constants.INDEX_COMMAND_WORD; +import static seedu.duke.common.Constants.INDEX_DAY_TIME; +import static seedu.duke.common.Constants.INDEX_DEADLINE; +import static seedu.duke.common.Constants.INDEX_DESCRIPTION; +import static seedu.duke.common.Constants.INDEX_LINK; +import static seedu.duke.common.Constants.INDEX_REMARKS_PARSER; +import static seedu.duke.common.Constants.INDEX_TEACHER_EMAIL; +import static seedu.duke.common.Constants.INDEX_TEACHER_NAME; +import static seedu.duke.common.Constants.INDEX_TYPE; +import static seedu.duke.common.Constants.WHITESPACE; +import static seedu.duke.common.Messages.MESSAGE_INVALID_COMMAND; +import static seedu.duke.common.Messages.MESSAGE_INVALID_LESSON_EMAIL; +import static seedu.duke.common.Messages.MESSAGE_INVALID_LESSON_LINK; +import static seedu.duke.common.Messages.MESSAGE_INVALID_LESSON_TYPE; +import static seedu.duke.common.Messages.MESSAGE_INVALID_MODULE_CODE; +import static seedu.duke.common.Messages.MESSAGE_INVALID_TASK_DEADLINE; +import static seedu.duke.common.Messages.MESSAGE_LESSON_FIELDS_EMPTY; +import static seedu.duke.common.Messages.MESSAGE_MODULE_CODE_EMPTY; +import static seedu.duke.common.Messages.MESSAGE_NON_INTEGER_INDEX; +import static seedu.duke.common.Messages.MESSAGE_NON_INTEGER_INDICES; +import static seedu.duke.common.Messages.MESSAGE_OUT_OF_BOUNDS_INDEX; +import static seedu.duke.common.Messages.MESSAGE_OUT_OF_BOUNDS_INDICES; +import static seedu.duke.common.Messages.MESSAGE_TASK_DESCRIPTION_EMPTY; +import static seedu.duke.common.Messages.MESSAGE_TASK_FIELDS_EMPTY; + +public class ParserUtil { + + //@@author ivanchongzhien + /** + * Parses details of lesson from user arguments. + * + * @param input command arguments from user input + * @return a Lesson object with the details entered by the user. Fields not found will be left as an empty string. + */ + public static Lesson parseLesson(String input) throws ParserException { + if (input.isEmpty()) { + throw new ParserException(MESSAGE_LESSON_FIELDS_EMPTY); + } + // initialize an array of empty strings to store lesson details + String[] lessonDetails = new String[ENTRY_LESSON_MAX_PARSER]; + Arrays.fill(lessonDetails, EMPTY_STRING); + fillLessonDetails(input, lessonDetails); + + LessonType lessonType = LessonType.getLessonTypeFromString(lessonDetails[INDEX_TYPE]); + String timeAndDay = lessonDetails[INDEX_DAY_TIME]; + String link = lessonDetails[INDEX_LINK]; + String teacherName = lessonDetails[INDEX_TEACHER_NAME]; + String email = lessonDetails[INDEX_TEACHER_EMAIL]; + + if (lessonType == null) { + throw new ParserException(MESSAGE_INVALID_LESSON_TYPE); + } + if (!Lesson.isValidLink(link) && !link.equals(EMPTY_STRING)) { + throw new ParserException(MESSAGE_INVALID_LESSON_LINK); + } + + TeachingStaff teacher = parseTeachingStaff(teacherName, email); + return new Lesson(lessonType, timeAndDay, link, teacher); + } + + /** + * Parses details of teaching staff from given arguments. + * + * @param teacherName name of teaching staff + * @param email email address of teaching staff + * @return a TeachingStaff object + * @throws ParserException if the email address is invalid + */ + private static TeachingStaff parseTeachingStaff(String teacherName, String email) throws ParserException { + if (!TeachingStaff.isValidEmail(email) && !email.equals(EMPTY_STRING)) { + throw new ParserException(MESSAGE_INVALID_LESSON_EMAIL); + } + return new TeachingStaff(teacherName, email); + } + + /** + * Splits input string into its respective fields and stores each substring in an array. + * @param inputString user input containing new lesson details + * @param lessonDetails array storing parsed details + */ + private static void fillLessonDetails(String inputString, String[] lessonDetails) { + // split the details field using delimiter to get the individual detail fields + String[] splitDetails = inputString.split(DELIM); + + // store detail fields that have been filled by the user into an array + // if user did not enter that field, it will remain as an empty string + for (int i = 0; i < splitDetails.length && i < ENTRY_LESSON_MAX_PARSER; i++) { + lessonDetails[i] = splitDetails[i].trim(); + } + } + + /** + * Parses details of new task from command arguments. + * + * @param input command arguments + * @return a Task object with the details entered by the user. Fields not found will be left as an empty string. + * @throws ParserException if user does not enter any details + */ + public static Task parseTask(String input) throws ParserException { + // user does not enter any parameters. + if (input.isEmpty()) { + throw new ParserException(MESSAGE_TASK_FIELDS_EMPTY); + } + // initialize an array of empty strings to store task details + String[] taskDetails = new String[ENTRY_TASK_MAX_PARSER]; + Arrays.fill(taskDetails, EMPTY_STRING); + fillTaskDetails(input, taskDetails); + + // user does not enter a task description + if (taskDetails[0].isEmpty()) { + throw new ParserException(MESSAGE_TASK_DESCRIPTION_EMPTY); + } + + String description = taskDetails[INDEX_DESCRIPTION]; + LocalDate deadline = convertToDate(taskDetails[INDEX_DEADLINE]); + String remarks = taskDetails[INDEX_REMARKS_PARSER]; + + return new Task(description, deadline, remarks); + } + + /** + * Splits input string into its respective fields and stores each substring in an array. + * + * @param inputString user input containing new task details + * @param allDetails array storing parsed details + */ + private static void fillTaskDetails(String inputString, String[] allDetails) { + // split the details field using DELIMITER to get the individual detail fields + String[] details = inputString.split(DELIM); + + // store detail fields that have been filled by the user into an array + // if user did not enter that field, it will remain as an empty string + for (int i = 0; i < details.length && i < allDetails.length; i++) { + allDetails[i] = details[i].trim(); + } + } + + /** + * Converts given string to LocalDate object. + * + * @param string string to be converted + * @return LocalDate object of the date represented by the string + * @throws DateTimeParseException if invalid input format / invalid date given + */ + private static LocalDate convertToDate(String string) throws ParserException { + DateTimeFormatter parseFormat = DateTimeFormatter.ofPattern(FORMAT_DATE_IO); + LocalDate date; + try { + date = LocalDate.parse(string, parseFormat); + } catch (DateTimeParseException e) { + throw new ParserException(MESSAGE_INVALID_TASK_DEADLINE); + } + return date; + } + + //@@author isaharon + /** + * Gets the single command word and arguments from user input. + * + * @param input full user input + * @return string array of command word and arguments + * @throws ParserException if invalid word is given + */ + public static String[] getCommandWordAndArgs(String input) throws ParserException { + Pattern commandWordAndArgsPattern = Pattern.compile(FORMAT_COMMAND_WORD_AND_ARGS); + Matcher matcher = commandWordAndArgsPattern.matcher(input.trim()); + if (!matcher.matches()) { + throw new ParserException(MESSAGE_INVALID_COMMAND); + } + String[] commandWordAndArgs = {matcher.group(1), matcher.group(2)}; + return commandWordAndArgs; + } + + /** + * Enhanced version of getCommandWordAndArgs for module commands. + * + * @param input full user input + * @return string array of command word and arguments + * @throws ParserException if invalid word given + */ + public static String[] getModuleCommandWordAndArgs(String input) throws ParserException { + String[] commandWordAndArgs = getCommandWordAndArgs(input); + // command is more than 1 word + if (commandWordAndArgs[INDEX_COMMAND_WORD].equalsIgnoreCase(ADD) + || commandWordAndArgs[INDEX_COMMAND_WORD].equalsIgnoreCase(DELETE_COMMAND) + || commandWordAndArgs[INDEX_COMMAND_WORD].equalsIgnoreCase(EDIT)) { + commandWordAndArgs = getTwoCommandWordAndArgs(input); + } + return commandWordAndArgs; + } + + /** + * Gets two command word and arguments from user input. + * Only called when first command word is "add" or "delete" or "edit". + * + * @param input full user input + * @return string array of two command words and arguments + * @throws ParserException if insufficient number of words given + */ + private static String[] getTwoCommandWordAndArgs(String input) throws ParserException { + Pattern twoCommandWordAndArgsPattern = Pattern.compile(FORMAT_TWO_COMMAND_WORD_AND_ARGS); + Matcher matcher = twoCommandWordAndArgsPattern.matcher(input.trim()); + if (!matcher.matches()) { + throw new ParserException(MESSAGE_INVALID_COMMAND); + } + String[] commandWordsAndArgs = {matcher.group(1), matcher.group(2)}; + return commandWordsAndArgs; + } + + //@@author ivanchongzhien + /** + * Parses module code from user input. + * + * @param input full user input string + * @return module code string + * @throws ParserException if empty/invalid module code given + */ + public static String parseModuleCode(String input) throws ParserException { + if (input.isEmpty()) { + throw new ParserException(MESSAGE_MODULE_CODE_EMPTY); + } + + String moduleCode = input.toUpperCase(); + if (!Module.isValidModuleCode(moduleCode)) { + throw new ParserException(MESSAGE_INVALID_MODULE_CODE); + } + + return moduleCode; + } + + /** + * Parses given input string to integer, ensuring that parsed index is not out of bounds. + * + * @param input user input string + * @return index parsed from input string + */ + public static int checkIndex(String input, int max) throws ParserException { + int index = 0; + try { + index = Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new ParserException(MESSAGE_NON_INTEGER_INDEX); + } + + if (index < 1 || index > max) { + throw new ParserException(MESSAGE_OUT_OF_BOUNDS_INDEX); + } + + return index; + } + + /** + * Converts given input string to an array list of integers. + * Removes duplicates and indices which are out of bounds. + * + * @param input full user input string + * @param max the maximum accepted index + * @return a sorted integer arraylist with valid indices + * @throws NumberFormatException if non-integer value is present in the input + */ + public static ArrayList checkIndices(String input, int max) { + UI ui = new UI(); + ArrayList rawIndices = new ArrayList<>(); + + // assumption that input is non-null + assert (input != null); + + ArrayList nonIntegers = parseIndicesFromString(rawIndices, input); + if (nonIntegers.size() != 0) { + printNonIntegerWarning(nonIntegers, ui); + } + // remove duplicates + ArrayList indices = removeDuplicateIndex(rawIndices); + + // remove out of bounds + ArrayList removed = removeOutOfBoundIndex(indices, max); + if (removed.size() != 0) { + printOutOfBoundsWarning(removed, ui); + } + + // Sorts array list + Collections.sort(indices); + return indices; + } + + /** + * Converts input string into integers and stores them in given array list. Non-integer inputs are stored in + * a different array list. + * + * @param indices integer array list used to store parsed integers + * @param input input string to be converted + * @return a new array list of non-integers that were removed + */ + private static ArrayList parseIndicesFromString(ArrayList indices, String input) { + int index; + ArrayList nonIntegers = new ArrayList<>(); + String[] words = input.trim().split(WHITESPACE); + + for (String word : words) { + try { + index = Integer.parseInt(word); + indices.add(index); + } catch (NumberFormatException e) { + // Non-integer inputs will not be added to the indices array list + nonIntegers.add(word); + } + } + return nonIntegers; + } + + /** + * Removes duplicates from an array list of indices. + * + * @param indexList array list of indices to be checked + * @return a copy of the original array list without duplicates + */ + private static ArrayList removeDuplicateIndex(ArrayList indexList) { + // Remove duplicates + ArrayList noDuplicates = new ArrayList<>(); + + for (int number : indexList) { + if (!noDuplicates.contains(number)) { + noDuplicates.add(number); + } + } + return noDuplicates; + } + + /** + * Removes indices that are out of bounds from given array list. + * + * @param indexList array list indices to be checked + * @param max the upper bound limit + * @return a new array list containing indices which were out of bounds + */ + private static ArrayList removeOutOfBoundIndex(ArrayList indexList, int max) { + ArrayList removed = new ArrayList<>(); + + for (int i = 0; i < indexList.size(); i++) { + if (indexList.get(i) > max || indexList.get(i) <= 0) { + int removedIndex = indexList.remove(i); + removed.add(removedIndex); + i--; + } + } + return removed; + } + + /** + * Prints warning to inform user that some inputs were out of bounds and removed. + * Prints the integers that were removed. + * + * @param removed array list of integers that were out of bounds and have been removed + * @param ui UI object for printing + */ + private static void printOutOfBoundsWarning(ArrayList removed, UI ui) { + ui.printMessage(String.format(MESSAGE_OUT_OF_BOUNDS_INDICES, removed)); + } + + /** + * Prints warning to inform user that some inputs were not integers. + * Prints the strings that were removed. + * + * @param removed array list of strings that were invalid and have been removed + * @param ui UI object for printing + */ + private static void printNonIntegerWarning(ArrayList removed, UI ui) { + ui.printMessage(String.format(MESSAGE_NON_INTEGER_INDICES, removed)); + } +} diff --git a/src/main/java/seedu/duke/storage/Loader.java b/src/main/java/seedu/duke/storage/Loader.java new file mode 100644 index 0000000000..61872dd7e8 --- /dev/null +++ b/src/main/java/seedu/duke/storage/Loader.java @@ -0,0 +1,244 @@ +package seedu.duke.storage; + +import seedu.duke.lesson.Lesson; +import seedu.duke.lesson.LessonType; +import seedu.duke.lesson.TeachingStaff; +import seedu.duke.module.Module; +import seedu.duke.task.Task; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.attribute.FileTime; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Scanner; + +import static seedu.duke.common.CommonMethods.writeLog; +import static seedu.duke.common.Constants.DIVIDER_READ; +import static seedu.duke.common.Constants.EMPTY_STRING; +import static seedu.duke.common.Constants.ENTRY_LESSON_EXTRA_LONG; +import static seedu.duke.common.Constants.ENTRY_LESSON_LONG; +import static seedu.duke.common.Constants.ENTRY_LESSON_MEDIUM; +import static seedu.duke.common.Constants.ENTRY_SIZE_LESSON; +import static seedu.duke.common.Constants.ENTRY_SIZE_TASK; +import static seedu.duke.common.Constants.ENTRY_TASK_LONG; +import static seedu.duke.common.Constants.FOLDER_PATH; +import static seedu.duke.common.Constants.FORMAT_DATE_IO; +import static seedu.duke.common.Constants.INDEX_DAY_TIME; +import static seedu.duke.common.Constants.INDEX_DEADLINE; +import static seedu.duke.common.Constants.INDEX_DESCRIPTION; +import static seedu.duke.common.Constants.INDEX_IS_DONE; +import static seedu.duke.common.Constants.INDEX_IS_GRADED; +import static seedu.duke.common.Constants.INDEX_LINK; +import static seedu.duke.common.Constants.INDEX_REMARKS_LOADER; +import static seedu.duke.common.Constants.INDEX_TEACHER_EMAIL; +import static seedu.duke.common.Constants.INDEX_TEACHER_NAME; +import static seedu.duke.common.Constants.INDEX_TYPE; +import static seedu.duke.common.Constants.KEYWORD_LESSON; +import static seedu.duke.common.Constants.KEYWORD_TASK; +import static seedu.duke.common.Constants.STOP_LINE; +import static seedu.duke.common.Constants.TRUE_STRING; +import static seedu.duke.common.Constants.TXT_FORMAT; +import static seedu.duke.common.Messages.MESSAGE_LOAD_FAILED; + +public class Loader { + + //@@author 8kdesign + /** + * Searches directory for module files. + * Returns ArrayList of names (excluding ".txt"). + */ + public ArrayList getModules() { + ArrayList moduleCodes = new ArrayList<>(); + File directory = new File(FOLDER_PATH); + File[] files = directory.listFiles(); + if (files == null) { + return moduleCodes; + } + sortFilesByCreationTime(files); + for (File file : files) { + if (file.isDirectory()) { + moduleCodes.add(file.getName()); + } + } + return moduleCodes; + } + + /** + * Loads data from the selected module file. + * + * @param moduleCode Module code, excluding ".txt". + * @return Loaded module. + */ + public Module loadModule(String moduleCode) { + String fileName = moduleCode + TXT_FORMAT; + Module module = new Module(moduleCode); + File path = new File(FOLDER_PATH + "/" + moduleCode + "/" + fileName); + try { + Scanner scanner = new Scanner(path); + readTillLine(scanner); + readData(scanner, module); + scanner.close(); + } catch (FileNotFoundException e) { + //Unable to find file, return null + writeLog(String.format(MESSAGE_LOAD_FAILED, moduleCode)); + return null; + } + return module; + } + + /** + * Skips to first line of data. + * + * @param scanner Scanner for module file. + */ + private void readTillLine(Scanner scanner) { + while (scanner.hasNext()) { + String input = scanner.nextLine(); + if (input.startsWith(STOP_LINE)) { + return; + } + } + } + + /** + * Identifies data type and calls methods to handle them. + * Runs through all data. + * + * @param scanner Scanner for module file. + * @param module Module to add data to. + */ + private void readData(Scanner scanner, Module module) { + while (scanner.hasNext()) { + String input = scanner.nextLine(); + if (input.startsWith(KEYWORD_LESSON)) { + readLessonData(input.substring(KEYWORD_LESSON.length()), module); + } else if (input.startsWith(KEYWORD_TASK)) { + readTaskData(input.substring(KEYWORD_TASK.length()), module); + } + } + } + + /** + * Reads data for lesson. + * Adds lesson to lesson list in module. + * + * @param module Module to add data to. + */ + private void readLessonData(String input, Module module) { + String[] fields = input.split(DIVIDER_READ); + if (!ENTRY_SIZE_LESSON.contains(fields.length)) { + //Invalid format + return; + } + LessonType lessonType = LessonType.getLessonTypeFromString(fields[INDEX_TYPE].trim()); + if (lessonType == null) { + //Invalid lesson type + return; + } + String time = fields[INDEX_DAY_TIME].trim(); + String link = ""; + TeachingStaff teachingStaff = new TeachingStaff(EMPTY_STRING,EMPTY_STRING); + + switch (fields.length) { + case ENTRY_LESSON_EXTRA_LONG: + teachingStaff.setEmail(fields[INDEX_TEACHER_EMAIL].trim()); + // fallthrough + case ENTRY_LESSON_LONG: + teachingStaff.setName(fields[INDEX_TEACHER_NAME].trim()); + // fallthrough + case ENTRY_LESSON_MEDIUM: + link = fields[INDEX_LINK].trim(); + // fallthrough + default: + } + Lesson lesson = new Lesson(lessonType, time, link, teachingStaff); + module.addLesson(lesson); + } + + /** + * Reads data for task. + * Adds task to task list in module. + * + * @param module Module to add data to. + */ + private void readTaskData(String input, Module module) { + String[] fields = input.split(DIVIDER_READ); + if (!ENTRY_SIZE_TASK.contains(fields.length)) { + //Invalid format + return; + } + String description = fields[INDEX_DESCRIPTION].trim(); + LocalDate deadline = getDeadline(fields[INDEX_DEADLINE].trim()); + if (deadline == null) { + //Invalid deadline + return; + } + boolean isDone = getTrueFalse(fields[INDEX_IS_DONE].trim()); + boolean isGraded = getTrueFalse(fields[INDEX_IS_GRADED].trim()); + String remarks = EMPTY_STRING; + if (fields.length == ENTRY_TASK_LONG) { + remarks = fields[INDEX_REMARKS_LOADER].trim(); + } + Task task = new Task(description, deadline, remarks, isDone, isGraded); + module.addTask(task); + } + + /** + * Returns deadline of task. + * + * @param input String of deadline. + * @return Deadline in LocalDateTime. + */ + private LocalDate getDeadline(String input) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(FORMAT_DATE_IO); + return LocalDate.parse(input, formatter); + } catch (DateTimeParseException e) { + //invalid deadline + return null; + } + } + + /** + * Returns true if "T" and false if "F". + * Returns false if invalid. + * + * @param input "T" for true or "F" for false. + * @return Boolean of input. + */ + private boolean getTrueFalse(String input) { + //Default is false + return input.equalsIgnoreCase(TRUE_STRING); + } + + //@@author isaharon + /** + * Sorts array of files according to creation time. + * + * @param files array of files + */ + private void sortFilesByCreationTime(File[] files) { + Arrays.sort(files, new Comparator() { + @Override + public int compare(File file1, File file2) { + FileTime fileTime1 = null; + FileTime fileTime2 = null; + try { + fileTime1 = (FileTime) Files.getAttribute(file1.toPath(), "creationTime"); + fileTime2 = (FileTime) Files.getAttribute(file2.toPath(), "creationTime"); + } catch (IOException e) { + e.printStackTrace(); + } + return fileTime1.compareTo(fileTime2); + } + }); + } + +} diff --git a/src/main/java/seedu/duke/storage/Writer.java b/src/main/java/seedu/duke/storage/Writer.java new file mode 100644 index 0000000000..00e0123680 --- /dev/null +++ b/src/main/java/seedu/duke/storage/Writer.java @@ -0,0 +1,218 @@ +package seedu.duke.storage; + +import seedu.duke.lesson.Lesson; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import static seedu.duke.common.Constants.DIVIDER_WRITE; +import static seedu.duke.common.Constants.FALSE_STRING; +import static seedu.duke.common.Constants.FOLDER_PATH; +import static seedu.duke.common.Constants.FORMAT_DATE_IO; +import static seedu.duke.common.Constants.KEYWORD_LESSON; +import static seedu.duke.common.Constants.KEYWORD_TASK; +import static seedu.duke.common.Constants.STRING_CHEATSHEET; +import static seedu.duke.common.Constants.TRUE_STRING; +import static seedu.duke.common.Constants.TXT_FORMAT; +import static seedu.duke.common.Messages.FILE_INSTRUCTIONS; +import static seedu.duke.common.Messages.MESSAGE_WRITER_FAILED_TO_SAVE; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public class Writer { + + //@@author 8kdesign + /** + * Creates file for new module. + * + * @param moduleCode Module code, excluding ".txt". + */ + public void createFile(String moduleCode) { + try { + checkForDirectories(moduleCode); + String fileName = moduleCode + TXT_FORMAT; + File path = new File(FOLDER_PATH + "/" + moduleCode + "/" + fileName); + path.createNewFile(); + FileWriter fileWriter = new FileWriter(path); + writeInstructions(fileWriter, moduleCode); + fileWriter.flush(); + fileWriter.close(); + } catch (IOException e) { + //Error creating file + } + } + + /** + * Deletes specified file. + * Returns true if deleted, or file does not exist, false if unable to delete. + * + * @param moduleCode Module code, excluding ".txt". + * @return True if file is gone, false if file is still around. + */ + public boolean deleteDirectory(String moduleCode) { + File directory = new File(FOLDER_PATH + "/" + moduleCode); + return recursivelyRemoveFiles(directory); + } + + /** + * Deletes all files in specified directory. + * Recursively calls itself if a directory is in the specified directory. + * + * @param directory Directory to delete. + * @return False if error deleting, true if successful. + */ + public static boolean recursivelyRemoveFiles(File directory) { + File[] files = directory.listFiles(); + if (files == null) { + return true; + } + for (File file : files) { + if (file.isDirectory()) { + if (!recursivelyRemoveFiles(file)) { + //Unable to remove directory + return false; + } + } else if (!file.delete()) { + //Unable to remove file + return false; + } + } + return directory.delete(); + } + + /** + * Creates directory if it does not exist. + * + * @throws IOException Unable to create directory. + */ + private void checkForDirectories(String moduleCode) throws IOException { + String directory = FOLDER_PATH; + File mainDirectory = new File(directory); + mainDirectory.mkdir(); + directory += "/" + moduleCode; + File moduleDirectory = new File(directory); + moduleDirectory.mkdir(); + directory += "/" + STRING_CHEATSHEET; + File cheatsheetDirectory = new File(directory); + cheatsheetDirectory.mkdir(); + } + + /** + * Updates changes to module in file. + * Writes module instructions and data to module file. + */ + public void writeModule() { + try { + Module module = ModuleList.getSelectedModule(); + File path = getFile(module); + FileWriter fileWriter = new FileWriter(path); + writeInstructions(fileWriter, module.getModuleCode()); + writeLessons(fileWriter, module); + writeTasks(fileWriter, module); + fileWriter.flush(); + fileWriter.close(); + } catch (IOException e) { + //Error editing file + new UI().printMessage(MESSAGE_WRITER_FAILED_TO_SAVE); + } + } + + /** + * Returns module file. + * + * @param module Selected module. + * @return Module file for selected module. + * @throws IOException Unable to create file. + */ + private File getFile(Module module) throws IOException { + String moduleCode = module.getModuleCode(); + String fileName = moduleCode + TXT_FORMAT; + File path = new File(FOLDER_PATH + "/" + moduleCode + "/" + fileName); + if (!path.exists()) { + //File does not exist + createFile(moduleCode); + assert path.exists(); + } + return path; + } + + /** + * Writes instructions to module file. + * + * @param fileWriter FileWriter for module file. + * @param moduleCode Module code, excluding ".txt". + * @throws IOException Unable to write to file. + */ + private void writeInstructions(FileWriter fileWriter, String moduleCode) throws IOException { + fileWriter.write(moduleCode + FILE_INSTRUCTIONS); + } + + /** + * Writes lessons to module file. + * + * @param fileWriter FileWriter for module file. + * @param module Selected module. + * @throws IOException Unable to write to file. + */ + private void writeLessons(FileWriter fileWriter, Module module) throws IOException { + for (Lesson lesson : module.getLessonList()) { + String entry = KEYWORD_LESSON; + entry += lesson.getLessonTypeString().toLowerCase() + DIVIDER_WRITE; + entry += lesson.getTime() + DIVIDER_WRITE; + entry += lesson.getOnlineLink() + DIVIDER_WRITE; + entry += lesson.getTeachingStaff().getName() + DIVIDER_WRITE; + entry += lesson.getTeachingStaff().getEmail(); + fileWriter.write(entry + '\n'); + } + } + + /** + * Writes tasks to module file. + * + * @param fileWriter FileWriter for module file. + * @param module Selected module. + * @throws IOException Unable to write to file. + */ + private void writeTasks(FileWriter fileWriter, Module module) throws IOException { + for (Task task : module.getTaskList()) { + String entry = KEYWORD_TASK; + entry += task.getDescription() + DIVIDER_WRITE; + entry += getDeadlineString(task.getDeadline()) + DIVIDER_WRITE; + entry += getTrueFalseString(task.getDone()) + DIVIDER_WRITE; + entry += getTrueFalseString(task.getGraded()); + if (task.getRemarks().length() > 0) { + entry += DIVIDER_WRITE + task.getRemarks(); + } + fileWriter.write(entry + '\n'); + } + } + + /** + * Returns string of deadline. + * + * @param deadline LocalDateTime to convert. + * @return String of deadline. + */ + private String getDeadlineString(LocalDate deadline) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(FORMAT_DATE_IO); + return deadline.format(formatter); + } + + /** + * Converts boolean to storage format. + * + * @param isTrue Boolean to convert. + * @return "T" if true, "F" if false. + */ + private String getTrueFalseString(Boolean isTrue) { + if (isTrue) { + return TRUE_STRING; + } + return FALSE_STRING; + } +} diff --git a/src/main/java/seedu/duke/task/Task.java b/src/main/java/seedu/duke/task/Task.java new file mode 100644 index 0000000000..6b7dec3e98 --- /dev/null +++ b/src/main/java/seedu/duke/task/Task.java @@ -0,0 +1,154 @@ +package seedu.duke.task; + +import seedu.duke.ui.UI; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import static seedu.duke.common.Constants.FORMAT_DATE_NORMAL; +import static seedu.duke.common.Constants.WHITESPACE; +import static seedu.duke.common.Messages.FORMAT_ITEM; +import static seedu.duke.common.Messages.FORMAT_ITEM_TIME; +import static seedu.duke.common.Messages.INDENTATION; +import static seedu.duke.common.Messages.MESSAGE_GRADED; +import static seedu.duke.common.Messages.MESSAGE_GRADED_STATUS; +import static seedu.duke.common.Messages.MESSAGE_UNGRADED_STATUS; +import static seedu.duke.common.Messages.NEWLINE; + +public class Task { + + //@@author 8kdesign + private String description; + private LocalDate deadline; + private String remarks; + private Boolean isDone; + private Boolean isGraded; + + /** + * Class constructor for a new task. + * This is the default. + */ + public Task(String description, LocalDate deadline, String remarks) { + this.description = description; + this.deadline = deadline; + this.remarks = remarks; + this.isDone = false; + this.isGraded = false; + } + + /** + * Class constructor for an existing task. + * Used by Loader and WriterTest. + */ + public Task(String description, LocalDate deadline, String remarks, + Boolean isDone, Boolean isGraded) { + this.description = description; + this.deadline = deadline; + this.remarks = remarks; + this.isDone = isDone; + this.isGraded = isGraded; + } + + public String getDescription() { + return description; + } + + public LocalDate getDeadline() { + return deadline; + } + + public String getRemarks() { + return remarks; + } + + public Boolean getDone() { + return isDone; + } + + public Boolean getGraded() { + return isGraded; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setDeadline(LocalDate deadline) { + this.deadline = deadline; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + public void setDone(Boolean done) { + isDone = done; + } + + public void setGraded(Boolean graded) { + isGraded = graded; + } + + public void editDescription(String newDescription) { + setDescription(newDescription); + } + + public void editDeadline(LocalDate newDeadine) { + setDeadline(newDeadine); + } + + public void editRemarks(String newRemarks) { + setRemarks(newRemarks); + } + + public void editGraded(boolean newGraded) { + setGraded(newGraded); + } + + //@@author aliciatay-zls + /** + * Concatenates details of the task into a single, two-line, formatted string. + * @return String giving an overview of the task + */ + public StringBuilder getTaskString() { + StringBuilder taskString = new StringBuilder(getDescription()); + taskString.append(getGraded() ? MESSAGE_GRADED : WHITESPACE); + taskString.append(String.format(FORMAT_ITEM, getFormattedDeadline())); + if (!isDone) { + taskString.append(new UI().getDaysRemainingMessage(getDeadline())); + } + if (!getRemarks().isEmpty()) { + taskString.append(NEWLINE).append(INDENTATION).append(getRemarks()); + } + return taskString; + } + + /** + * Concatenates all details of the task into a single, multi-line, indented string. + * @return String containing all non-empty fields of the task + */ + public StringBuilder getFullTaskString() { + StringBuilder taskString = new StringBuilder(String.format(FORMAT_ITEM_TIME, + getDescription(), getFormattedDeadline())); + taskString.append(NEWLINE); + if (!getRemarks().isEmpty()) { + taskString.append(INDENTATION).append(getRemarks()).append(NEWLINE); + } + taskString.append(INDENTATION).append(getGradedStatus()); + return taskString; + } + + /** + * Returns the task's deadline, for displaying to the user. + */ + public String getFormattedDeadline() { + return deadline.format(DateTimeFormatter.ofPattern(FORMAT_DATE_NORMAL)); + } + + /** + * Returns whether the task is graded or not, for displaying to the user. + */ + public String getGradedStatus() { + return getGraded() ? MESSAGE_GRADED_STATUS : MESSAGE_UNGRADED_STATUS; + } +} diff --git a/src/main/java/seedu/duke/ui/UI.java b/src/main/java/seedu/duke/ui/UI.java new file mode 100644 index 0000000000..5ac4edcd86 --- /dev/null +++ b/src/main/java/seedu/duke/ui/UI.java @@ -0,0 +1,133 @@ +package seedu.duke.ui; + +import seedu.duke.exception.DukeException; +import seedu.duke.module.ModuleList; +import seedu.duke.parser.ParserUtil; +import seedu.duke.task.Task; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Scanner; + +import static seedu.duke.common.CommonMethods.getDaysRemaining; +import static seedu.duke.common.Messages.FORMAT_DAYS_REMAINING; +import static seedu.duke.common.Messages.FORMAT_DAY_REMAINING; +import static seedu.duke.common.Messages.FORMAT_DUE_TODAY; +import static seedu.duke.common.Messages.FORMAT_INDEX_ITEM; +import static seedu.duke.common.Messages.FORMAT_OVERDUE; +import static seedu.duke.common.Messages.HEADER_DONE; +import static seedu.duke.common.Messages.HEADER_UNDONE; +import static seedu.duke.common.Messages.MESSAGE_TASKS_TO_LIST_UNDONE; +import static seedu.duke.common.Messages.TAG_GULIO; +import static seedu.duke.common.Messages.TAG_MODULE; + +public class UI { + + private final Scanner scanner; + + //@@author aliciatay-zls + public UI() { + scanner = new Scanner(System.in); + } + + /** + * Prints specified message. + * + * @param message String to print. + */ + public void printMessage(String message) { + System.out.println(message); + } + + /** + * Prints error message of an exception within the program. + * @param e Exception to be printed + */ + public void printError(DukeException e) { + System.out.println(e.getMessage()); + } + + /** + * Reads input from user. + * + * @return String of input. + */ + public String readUserInput() { + return scanner.nextLine(); + } + + //@@author 8kdesign + /** + * Prints module indicator for user input. + */ + public void printModuleIndicator() { + if (!ModuleList.hasSelectedModule()) { + System.out.print(TAG_GULIO); + } else { + String moduleCode = ModuleList.getSelectedModuleCode(); + System.out.printf(TAG_MODULE, moduleCode); + } + } + + //@@author aliciatay-zls + /** + * Prints the heading seen before a task list is displayed. + */ + public void printTaskListHeader(boolean isDone, boolean isOverview) { + if (isDone) { + printMessage(HEADER_DONE); + } else if (isOverview) { + printMessage(MESSAGE_TASKS_TO_LIST_UNDONE); + } else { + printMessage(HEADER_UNDONE); + } + } + + /** + * Prints either only the description or the overview of each task in the task list. + * + * @param taskList Array list of tasks to print. + */ + public void printTasks(ArrayList taskList, boolean isDescriptionOnly) { + int tasksCount = 0; + for (Task task : taskList) { + tasksCount++; + if (isDescriptionOnly) { + printMessage(String.format(FORMAT_INDEX_ITEM, tasksCount, task.getDescription())); + continue; + } + printMessage(String.format(FORMAT_INDEX_ITEM, tasksCount, task.getTaskString())); + } + } + + //@@author 8kdesign + /** + * Returns message for days remaining. + * + * @param dueDate LocalDate of task deadline. + * @return Message for days remaining. + */ + public String getDaysRemainingMessage(LocalDate dueDate) { + long daysRemaining = getDaysRemaining(dueDate); + if (daysRemaining < 0) { + return String.format(FORMAT_OVERDUE, -daysRemaining); + } else if (daysRemaining == 0) { + return FORMAT_DUE_TODAY; + } else if (daysRemaining == 1) { + return FORMAT_DAY_REMAINING; + } else { + return String.format(FORMAT_DAYS_REMAINING, daysRemaining); + } + } + + //@@author isaharon + /** + * Read input from user and returns list of indices. + * @param max the maximum accepted index + * @return an integer arraylist with valid indices + */ + public ArrayList getIndicesFromUser(int max) { + String userInput = readUserInput(); + return ParserUtil.checkIndices(userInput, max); + } +} diff --git a/src/main/unused/SortLessonsCommand.java b/src/main/unused/SortLessonsCommand.java new file mode 100644 index 0000000000..9e5c64bb2a --- /dev/null +++ b/src/main/unused/SortLessonsCommand.java @@ -0,0 +1,67 @@ +package seedu.duke.unused; + +import seedu.duke.commands.Command; +import seedu.duke.exception.CommandException; +import seedu.duke.lesson.Lesson; +import seedu.duke.lesson.LessonType; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.util.ArrayList; + +import static seedu.duke.commands.ListLessonsCommand.printLessons; +import static seedu.duke.common.Messages.MESSAGE_SORT_LESSON_LIST; + +//@@author H-horizon-unused +/** + * Replaced with method as command is not needed in this case. + * + * Represents the command used to sort the lessons on the list of lessons. + */ +public class SortLessonsCommand extends Command { + + @Override + public void execute(UI ui) throws CommandException { + ui.printMessage(MESSAGE_SORT_LESSON_LIST); + Module module = ModuleList.getSelectedModule(); + ArrayList lessonList = module.getLessonList(); + sortLessonList(ui, lessonList); + } + + /** + * Sorts lessons based on their lesson type. + * + * @param ui Instance of UI. + * @param lessonList ArrayList of lessons. + */ + private void sortLessonList(UI ui, ArrayList lessonList) { + ArrayList sortedLessonList = new ArrayList<>(); + filterByLessonType(lessonList, sortedLessonList, LessonType.LECTURE); + filterByLessonType(lessonList, sortedLessonList, LessonType.TUTORIAL); + filterByLessonType(lessonList, sortedLessonList, LessonType.LAB); + lessonList = sortedLessonList; + printLessons(lessonList, ui); + } + + /** + * Filters lessons of a particular type and add them to a new list. + * + * @param lessonList ArrayList of lessons. + * @param sortedLessonList Sorted arrayList of lessons. + * @param lecture Type of Lecture + */ + public void filterByLessonType(ArrayList lessonList, ArrayList sortedLessonList, + LessonType lecture) { + for (Lesson lesson : lessonList) { + if (lesson.getLessonType().equals(lecture)) { + sortedLessonList.add(lesson); + } + } + } + + @Override + public boolean isExit() { + return false; + } +} diff --git a/src/test/java/seedu/duke/TestUtilAndConstants.java b/src/test/java/seedu/duke/TestUtilAndConstants.java new file mode 100644 index 0000000000..6b87886f6e --- /dev/null +++ b/src/test/java/seedu/duke/TestUtilAndConstants.java @@ -0,0 +1,112 @@ +package seedu.duke; + +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.task.Task; + +import java.io.File; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; + +import static seedu.duke.common.Constants.FOLDER_PATH; +import static seedu.duke.common.Messages.INDENTATION; +import static seedu.duke.common.Messages.MESSAGE_TASKS_DONE; +import static seedu.duke.common.Messages.MESSAGE_TASKS_TO_LIST_UNDONE; +import static seedu.duke.common.Messages.NEWLINE; +import static seedu.duke.storage.Writer.recursivelyRemoveFiles; + +public class TestUtilAndConstants { + + //@@author H-horizon + public static final String INPUT_ADD_TASK_DESCRIPTION = "iP submission"; + public static final String INPUT_ADD_TASK_REMARKS = "Remember to attach the jar file."; + public static final String INPUT_INVALID_IS_GRADED = "no"; + + public static final String EXPECTED_ADD_LESSON = "Added Tutorial to lesson list." + NEWLINE; + public static final String EXPECTED_ADD_TASK = "Added iP submission to task list."; + public static final String EXPECTED_DELETE_LESSON = "Removed Lab." + NEWLINE + + "Removed Tutorial." + NEWLINE; + public static final String EXPECTED_OPEN_LINK = "Opening lab link in browser." + NEWLINE + + "Opening tutorial link in browser." + NEWLINE; + public static final String EXPECTED_MODULE_OVERVIEW = "" + NEWLINE + + "Lab - Wednesday 9 pm - 10 pm" + NEWLINE + "Tutorial - Wednesday 9 am - 10am" + + NEWLINE + NEWLINE + MESSAGE_TASKS_TO_LIST_UNDONE + NEWLINE + + "1. iP submission (graded) - 3 Mar 2021%s" + + NEWLINE + INDENTATION + "remember to attach JAR file" + NEWLINE; + public static final String EXPECTED_ENTER_MODULE = "Opening CS2106." + NEWLINE + NEWLINE + + "" + NEWLINE + NEWLINE + MESSAGE_TASKS_TO_LIST_UNDONE + NEWLINE + + MESSAGE_TASKS_DONE + NEWLINE; + + public static final String MESSAGE_MODULE_ERROR = "There was a problem with getting selected module." + NEWLINE; + + public static final String MODULE_CODE_1 = "CS2113T"; + public static final String MODULE_CODE_2 = "CS2106"; + public static final String MODULE_CODE_3 = "CS2105"; + public static final String MODULE_CODE_4 = "CS2101"; + + public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("d-M-yyyy"); + + + //@@author 8kdesign + public static void removeFiles() { + File directory = new File(FOLDER_PATH); + recursivelyRemoveFiles(directory); + } + + //@@author aliciatay-zls + /** + * Creates task list for use by all tests on task commands. + * @return pre-populated task list + */ + public static ArrayList initialiseTaskList() { + Module module = ModuleList.getSelectedModule(); + + LocalDate deadline1 = LocalDate.parse("23-2-2021", FORMATTER); + Task task1 = new Task("weekly exercise", deadline1, "Do before 2359."); + task1.setGraded(true); + module.addTask(task1); + + LocalDate deadline3 = LocalDate.parse("26-2-2021", FORMATTER); + Task task2 = new Task("lecture quiz", deadline3, "Complete before next lecture."); + module.addTask(task2); + + Task task3 = new Task("read up notes", deadline3, ""); + module.addTask(task3); + + LocalDate deadline4 = LocalDate.parse("3-3-2021", FORMATTER); + Task task4 = new Task("iP submission", deadline4, "Remember to attach the jar file."); + task4.setGraded(true); + task4.setDone(true); + module.addTask(task4); + + LocalDate deadline2 = LocalDate.parse("25-2-2021", FORMATTER); + Task task5 = new Task("watch video snippets", deadline2, ""); + task5.setDone(true); + module.addTask(task5); + + ModuleList.sortTasks(); + + return module.getTaskList(); + } + + public static void initialiseModuleList() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + ModuleList.setSelectedModule(MODULE_CODE_1); + } + + //@@author ivanchongzhien + + /** + * Empties the modules in the list that were used for testing. + */ + public static void emptyModuleList() { + ArrayList indices = new ArrayList<>(); + for (int i = 1; i <= ModuleList.getModules().size(); i++) { + indices.add(i); + } + ModuleList.deleteModules(indices); + } +} diff --git a/src/test/java/seedu/duke/commands/AddLessonCommandTest.java b/src/test/java/seedu/duke/commands/AddLessonCommandTest.java new file mode 100644 index 0000000000..c9932f7408 --- /dev/null +++ b/src/test/java/seedu/duke/commands/AddLessonCommandTest.java @@ -0,0 +1,39 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.exception.CommandException; +import seedu.duke.lesson.Lesson; +import seedu.duke.lesson.LessonType; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.OutputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.TestUtilAndConstants.EXPECTED_ADD_LESSON; + +class AddLessonCommandTest extends LessonCommandTest { + + //@@author H-horizon + @Test + void execute_ui_expectPrintsCorrectOutput() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + + UI ui = new UI(); + ModuleList.addModule(MODULE_CODE); + ModuleList.setSelectedModule(MODULE_CODE); + Lesson newLesson = initialiseLesson(TEACHER_NAME, TEACHER_EMAIL, LessonType.TUTORIAL, TIME, ONLINE_LINK); + Command command = new AddLessonCommand(newLesson); + + OutputStream os = getOutputStream(); + try { + command.execute(ui); + } catch (CommandException e) { + printFailedToExecuteCommand(); + } + assertEquals(EXPECTED_ADD_LESSON, os.toString()); + removeOutputStream(); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/AddModuleCommandTest.java b/src/test/java/seedu/duke/commands/AddModuleCommandTest.java new file mode 100644 index 0000000000..f1c84382c5 --- /dev/null +++ b/src/test/java/seedu/duke/commands/AddModuleCommandTest.java @@ -0,0 +1,58 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_1; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_2; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_3; +import static seedu.duke.common.Messages.MESSAGE_ADDED_MODULE; + +class AddModuleCommandTest { + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + //@@author isaharon + @Test + void execute_uniqueModuleCode_expectPrintSuccess() throws CommandException { + System.setOut(new PrintStream(outContent)); + + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + + ModuleList.addModule(MODULE_CODE_2); + ModuleList.addModule(MODULE_CODE_3); + + String moduleCode = MODULE_CODE_1; + Command command = new AddModuleCommand(moduleCode); + command.execute(new UI()); + + String output = String.format(MESSAGE_ADDED_MODULE, moduleCode); + assertEquals(output + System.lineSeparator(), outContent.toString()); + + System.setOut(originalOut); + } + + @Test + void execute_duplicateModuleCode_expectDuplicateModuleException() { + System.setOut(new PrintStream(outContent)); + + + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + + Command command = new AddModuleCommand(MODULE_CODE_1); + assertThrows(CommandException.class, () -> command.execute(new UI())); + + System.setOut(originalOut); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/AddTaskCommandTest.java b/src/test/java/seedu/duke/commands/AddTaskCommandTest.java new file mode 100644 index 0000000000..f762039389 --- /dev/null +++ b/src/test/java/seedu/duke/commands/AddTaskCommandTest.java @@ -0,0 +1,173 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.module.ModuleList; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.duke.TestUtilAndConstants.EXPECTED_ADD_TASK; +import static seedu.duke.TestUtilAndConstants.FORMATTER; +import static seedu.duke.TestUtilAndConstants.INPUT_ADD_TASK_DESCRIPTION; +import static seedu.duke.TestUtilAndConstants.INPUT_ADD_TASK_REMARKS; +import static seedu.duke.TestUtilAndConstants.INPUT_INVALID_IS_GRADED; +import static seedu.duke.TestUtilAndConstants.initialiseModuleList; +import static seedu.duke.common.Constants.EMPTY_STRING; +import static seedu.duke.common.Constants.NO_STRING; +import static seedu.duke.common.Constants.YES_STRING; +import static seedu.duke.common.Messages.MESSAGE_DUPLICATE_TASK; +import static seedu.duke.common.Messages.MESSAGE_SAME_DESCRIPTION_TASK; +import static seedu.duke.common.Messages.MESSAGE_TASK_CHECK_GRADED; +import static seedu.duke.common.Messages.MESSAGE_TASK_CHECK_GRADED_INFO; +import static seedu.duke.common.Messages.NEWLINE; + +public class AddTaskCommandTest { + private final InputStream originalIn = System.in; + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + private static final LocalDate FORMATTED_DEADLINE_1 = LocalDate.parse("3-3-2021", FORMATTER); + private static final LocalDate FORMATTED_DEADLINE_2 = LocalDate.parse("5-3-2021", FORMATTER); + + //@@author aliciatay-zls + @Test + void execute_fullTaskInput_expectSuccess() { + String isGradedInput = YES_STRING + NEWLINE; + ByteArrayInputStream bis = new ByteArrayInputStream(isGradedInput.getBytes()); + System.setIn(bis); + System.setOut(new PrintStream(bos)); + + initialiseModuleList(); + + Task task = new Task(INPUT_ADD_TASK_DESCRIPTION, FORMATTED_DEADLINE_1, INPUT_ADD_TASK_REMARKS); + AddTaskCommand addTaskCommand = new AddTaskCommand(task); + + addTaskCommand.execute(new UI()); + + String output = MESSAGE_TASK_CHECK_GRADED + NEWLINE + + EXPECTED_ADD_TASK + NEWLINE; + + // checks displayed output to user + assertEquals(output, bos.toString()); + + // checks if new task was really added to task list + assertTrue(ModuleList.getSelectedModule().getTaskList().contains(task)); + + // checks if new task's graded status and + // done status (default false) were set correctly + assertEquals(true, ModuleList.getSelectedModule().getTaskList().get(0).getGraded()); + assertEquals(false, ModuleList.getSelectedModule().getTaskList().get(0).getDone()); + + System.setIn(originalIn); + System.setOut(originalOut); + } + + @Test + void execute_taskInputWithNoRemarks_expectSuccess() { + String isGradedInput = YES_STRING + NEWLINE; + ByteArrayInputStream bis = new ByteArrayInputStream(isGradedInput.getBytes()); + System.setIn(bis); + System.setOut(new PrintStream(bos)); + + initialiseModuleList(); + + // remarks field is empty + Task task = new Task(INPUT_ADD_TASK_DESCRIPTION, FORMATTED_DEADLINE_1, EMPTY_STRING); + AddTaskCommand addTaskCommand = new AddTaskCommand(task); + + addTaskCommand.execute(new UI()); + + String output = MESSAGE_TASK_CHECK_GRADED + NEWLINE + + EXPECTED_ADD_TASK + NEWLINE; + + // checks displayed output to user + assertEquals(output, bos.toString()); + + // checks if new task was really added to task list + assertTrue(ModuleList.getSelectedModule().getTaskList().contains(task)); + + System.setIn(originalIn); + System.setOut(originalOut); + } + + @Test + void execute_taskInputAndInitiallyInvalidIsGradedInputs_expectSuccess() { + String isGradedInput = EMPTY_STRING + NEWLINE + + INPUT_INVALID_IS_GRADED + NEWLINE + + NO_STRING + NEWLINE; + ByteArrayInputStream bis = new ByteArrayInputStream(isGradedInput.getBytes()); + System.setIn(bis); + System.setOut(new PrintStream(bos)); + + initialiseModuleList(); + + Task task = new Task(INPUT_ADD_TASK_DESCRIPTION, FORMATTED_DEADLINE_1, INPUT_ADD_TASK_REMARKS); + AddTaskCommand addTaskCommand = new AddTaskCommand(task); + + addTaskCommand.execute(new UI()); + + String output = MESSAGE_TASK_CHECK_GRADED + NEWLINE + + MESSAGE_TASK_CHECK_GRADED_INFO + NEWLINE + + MESSAGE_TASK_CHECK_GRADED_INFO + NEWLINE + + EXPECTED_ADD_TASK + NEWLINE; + + // checks displayed output to user + assertEquals(output, bos.toString()); + + // checks if new task was really added to task list + assertTrue(ModuleList.getSelectedModule().getTaskList().contains(task)); + + // checks if new task's graded status was set correctly + assertEquals(false, ModuleList.getSelectedModule().getTaskList().get(0).getGraded()); + + System.setIn(originalIn); + System.setOut(originalOut); + } + + @Test + void execute_duplicateAndSimilarTasks_expectIgnore() { + String isGradedInput = YES_STRING + NEWLINE; + ByteArrayInputStream bis = new ByteArrayInputStream(isGradedInput.getBytes()); + System.setIn(bis); + System.setOut(new PrintStream(bos)); + + initialiseModuleList(); + + // New task + Task duplicateTask = new Task(INPUT_ADD_TASK_DESCRIPTION, FORMATTED_DEADLINE_1, INPUT_ADD_TASK_REMARKS); + AddTaskCommand addTaskCommand1 = new AddTaskCommand(duplicateTask); + addTaskCommand1.execute(new UI()); + + // Duplicate + AddTaskCommand addTaskCommand2 = new AddTaskCommand(duplicateTask); + addTaskCommand2.execute(new UI()); + + // Similar + Task similarTask = new Task(INPUT_ADD_TASK_DESCRIPTION, FORMATTED_DEADLINE_2, EMPTY_STRING); + AddTaskCommand addTaskCommand3 = new AddTaskCommand(similarTask); + addTaskCommand3.execute(new UI()); + + String output = MESSAGE_TASK_CHECK_GRADED + NEWLINE + + EXPECTED_ADD_TASK + NEWLINE + + MESSAGE_DUPLICATE_TASK + NEWLINE + + MESSAGE_SAME_DESCRIPTION_TASK + NEWLINE; + + // checks displayed output to user + assertEquals(output, bos.toString()); + + // checks if duplicate and similar tasks were really ignored + int count = (int)ModuleList.getSelectedModule().getTaskList().stream() + .filter((t) -> t.getDescription().equalsIgnoreCase(duplicateTask.getDescription())) + .count(); + assertEquals(1, count); + + System.setIn(originalIn); + System.setOut(originalOut); + } +} diff --git a/src/test/java/seedu/duke/commands/DeleteLessonCommandTest.java b/src/test/java/seedu/duke/commands/DeleteLessonCommandTest.java new file mode 100644 index 0000000000..09989ef371 --- /dev/null +++ b/src/test/java/seedu/duke/commands/DeleteLessonCommandTest.java @@ -0,0 +1,91 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.OutputStream; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.duke.TestUtilAndConstants.EXPECTED_DELETE_LESSON; + +class DeleteLessonCommandTest extends LessonCommandTest { + + //@@author H-horizon + @Test + void deleteLessonsFromList_moduleLessonListIndexes_expectPrintsCorrectOutput() { + + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + + UI ui = new UI(); + + ModuleList.addModule(MODULE_CODE); + ModuleList.setSelectedModule(MODULE_CODE); + + OutputStream os = getOutputStream(); + addLessonsToList(ui); + ArrayList indexes = new ArrayList<>(); + initialisedIndexes(indexes); + removeOutputStream(); + + OutputStream newOs = getOutputStream(); + DeleteLessonCommand.deleteLessonsFromList(ModuleList.getSelectedModule().getLessonList(), indexes, ui); + assertEquals(EXPECTED_DELETE_LESSON, newOs.toString()); + removeOutputStream(); + } + + @Test + void execute_ui_expectPrintsEmptyList() { + + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE); + ModuleList.setSelectedModule(MODULE_CODE); + + UI ui = new UI(); + OutputStream os = getOutputStream(); + + Command command = new DeleteLessonCommand(); + + try { + command.execute(ui); + } catch (CommandException e) { + printFailedToExecuteCommand(); + } + + String entireInput = "1 2"; + initialiseUserInput(entireInput); + boolean isEqual = false; + if (os.toString().equals("Your list of lessons is empty.\n") + || os.toString().equals("Your list of lessons is empty.\r\n")) { + isEqual = true; + } + removeOutputStream(); + assertTrue(isEqual); + } + + @Test + void execute_ui_expectPrintsCorrectOutput() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE); + ModuleList.setSelectedModule(MODULE_CODE); + + UI ui = new UI(); + addLessonsToList(ui); + Command command = new DeleteLessonCommand(); + try { + command.execute(ui); + } catch (CommandException e) { + printFailedToExecuteCommand(); + } + String entireInput = "1 2"; + initialiseUserInput(entireInput); + assertEquals(ModuleList.getSelectedModule().getLessonList().size(), 0); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/DeleteModuleCommandTest.java b/src/test/java/seedu/duke/commands/DeleteModuleCommandTest.java new file mode 100644 index 0000000000..0cfe775823 --- /dev/null +++ b/src/test/java/seedu/duke/commands/DeleteModuleCommandTest.java @@ -0,0 +1,64 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.common.Messages; +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_1; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_2; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_3; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_4; +import static seedu.duke.common.Constants.DELETE; +import static seedu.duke.common.Constants.TYPE_MODULE; +import static seedu.duke.common.Messages.MESSAGE_ENTER_INDICES; +import static seedu.duke.common.Messages.MESSAGE_MODULE_TO_DELETE; +import static seedu.duke.common.Messages.MESSAGE_REMOVED_MODULE; +import static seedu.duke.common.Messages.NEWLINE; + +class DeleteModuleCommandTest { + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + + //@@author isaharon + @Test + void execute_validModuleNumbersInput_expectSuccess() throws CommandException { + ByteArrayInputStream inContent = new ByteArrayInputStream("1 3".getBytes()); + System.setOut(new PrintStream(outContent)); + System.setIn(inContent); + + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + + ModuleList.addModule(MODULE_CODE_1); + ModuleList.addModule(MODULE_CODE_4); + ModuleList.addModule(MODULE_CODE_3); + ModuleList.addModule(MODULE_CODE_2); + + Command command = new DeleteModuleCommand(); + command.execute(new UI()); + + StringBuilder sb = new StringBuilder(); + sb.append(MESSAGE_MODULE_TO_DELETE); + sb.append(String.format(Messages.FORMAT_LIST_ITEMS, 1, MODULE_CODE_1)).append(NEWLINE); + sb.append(String.format(Messages.FORMAT_LIST_ITEMS, 2, MODULE_CODE_4)).append(NEWLINE); + sb.append(String.format(Messages.FORMAT_LIST_ITEMS, 3, MODULE_CODE_3)).append(NEWLINE); + sb.append(String.format(Messages.FORMAT_LIST_ITEMS, 4, MODULE_CODE_2)).append(NEWLINE); + sb.append(String.format(MESSAGE_ENTER_INDICES, TYPE_MODULE, DELETE)); + sb.append(System.lineSeparator()); + sb.append(String.format(MESSAGE_REMOVED_MODULE, MODULE_CODE_1)).append(NEWLINE); + sb.append(String.format(MESSAGE_REMOVED_MODULE, MODULE_CODE_3)).append(NEWLINE); + + assertEquals(sb.toString(), outContent.toString()); + + System.setOut(originalOut); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/DeleteTaskCommandTest.java b/src/test/java/seedu/duke/commands/DeleteTaskCommandTest.java new file mode 100644 index 0000000000..42bf33e459 --- /dev/null +++ b/src/test/java/seedu/duke/commands/DeleteTaskCommandTest.java @@ -0,0 +1,88 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.TestUtilAndConstants.initialiseTaskList; +import static seedu.duke.TestUtilAndConstants.initialiseModuleList; +import static seedu.duke.common.Constants.DELETE; +import static seedu.duke.common.Constants.TYPE_TASK; +import static seedu.duke.common.Messages.MESSAGE_ENTER_INDICES; +import static seedu.duke.common.Messages.MESSAGE_TASKS_TO_DELETE; +import static seedu.duke.common.Messages.MESSAGE_TASK_LIST_EMPTY; +import static seedu.duke.common.Messages.NEWLINE; + +class DeleteTaskCommandTest { + private final InputStream originalIn = System.in; + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + //@@author aliciatay-zls + @Test + void execute_twoValidTaskIndices_expectSuccess() { + String input = "1 5" + NEWLINE; + ByteArrayInputStream bis = new ByteArrayInputStream(input.getBytes()); + System.setIn(bis); + System.setOut(new PrintStream(bos)); + + initialiseModuleList(); + + ArrayList taskList = initialiseTaskList(); + + DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(); + deleteTaskCommand.execute(new UI()); + + String output = MESSAGE_TASKS_TO_DELETE + NEWLINE + + "1. weekly exercise" + NEWLINE + + "2. watch video snippets" + NEWLINE + + "3. lecture quiz" + NEWLINE + + "4. read up notes" + NEWLINE + + "5. iP submission" + NEWLINE + + String.format(MESSAGE_ENTER_INDICES, TYPE_TASK, DELETE) + NEWLINE + + "Removed weekly exercise from the task list." + NEWLINE + + "Removed iP submission from the task list." + NEWLINE; + + // checks displayed output to user + assertEquals(output, bos.toString()); + + String expectedRemaining = "watch video snippets" + NEWLINE + + "lecture quiz" + NEWLINE + + "read up notes" + NEWLINE; + + StringBuilder actualRemaining = new StringBuilder(); + for (Task task : taskList) { + actualRemaining.append(task.getDescription()).append(NEWLINE); + } + + //checks if tasks were really deleted from task list + assertEquals(expectedRemaining, actualRemaining.toString()); + + System.setIn(originalIn); + System.setOut(originalOut); + } + + @Test + void execute_noInputButEmptyTaskList_expectIgnore() { + System.setOut(new PrintStream(bos)); + + initialiseModuleList(); + + DeleteTaskCommand deleteTaskCommand = new DeleteTaskCommand(); + deleteTaskCommand.execute(new UI()); + + String output = String.format(MESSAGE_TASK_LIST_EMPTY, DELETE) + NEWLINE; + + // checks displayed output to user + assertEquals(output, bos.toString()); + + System.setOut(originalOut); + } +} diff --git a/src/test/java/seedu/duke/commands/EditLessonCommandTest.java b/src/test/java/seedu/duke/commands/EditLessonCommandTest.java new file mode 100644 index 0000000000..a34ec8eaa8 --- /dev/null +++ b/src/test/java/seedu/duke/commands/EditLessonCommandTest.java @@ -0,0 +1,105 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.exception.CommandException; +import seedu.duke.lesson.Lesson; +import seedu.duke.lesson.LessonType; +import seedu.duke.module.Module; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class EditLessonCommandTest extends LessonCommandTest { + + //@@author ivanchongzhien + @Test + void execute_validIndexAllFields_lessonFieldsEdited() throws CommandException { + editLessonTestSetup(); + Module module = ModuleList.getSelectedModule(); + + Lesson oriLesson = initialiseLesson(TEACHER_NAME, TEACHER_EMAIL, + LessonType.TUTORIAL, TIME, ONLINE_LINK); + module.addLesson(oriLesson); + + // Prepare user input + String lessonIndex = "1" + System.lineSeparator(); + String lessonFields = "1 2 3 4" + System.lineSeparator(); + String newTime = TIME1 + System.lineSeparator(); + String newLink = ONLINE_LINK1 + System.lineSeparator(); + String newTeacherName = TEACHER_NAME1 + System.lineSeparator(); + String newTeacherEmail = TEACHER_EMAIL1 + System.lineSeparator(); + + String entireInput = lessonIndex + lessonFields + newTime + newLink + newTeacherName + newTeacherEmail; + initialiseUserInput(entireInput); + + // Run command + UI ui = new UI(); + Command command = new EditLessonCommand(); + command.execute(ui); + + // Expected lesson after edit + Lesson editedLesson = initialiseLesson(TEACHER_NAME1, TEACHER_EMAIL1, + LessonType.TUTORIAL, TIME1, ONLINE_LINK1); + Lesson actualLessonAfterEdit = ModuleList.getSelectedModule().getLessonList().get(0); + + assertEquals(editedLesson.getTime(), actualLessonAfterEdit.getTime()); + assertEquals(editedLesson.getOnlineLink(), actualLessonAfterEdit.getOnlineLink()); + assertEquals(editedLesson.getTeachingStaff().getName(), actualLessonAfterEdit.getTeachingStaff().getName()); + assertEquals(editedLesson.getTeachingStaff().getEmail(), actualLessonAfterEdit.getTeachingStaff().getEmail()); + + editLessonTestCleanup(); + } + + @Test + void execute_validAndInvalidIndexPartialFields_lessonFieldsEdited() throws CommandException { + editLessonTestSetup(); + Module module = ModuleList.getSelectedModule(); + + Lesson oriLesson = initialiseLesson(TEACHER_NAME, TEACHER_EMAIL, + LessonType.TUTORIAL, TIME, ONLINE_LINK); + module.addLesson(oriLesson); + + // Prepare user input + String lessonIndex = "1" + System.lineSeparator(); + String lessonFields = "1 10 -1 3 0 abc" + System.lineSeparator(); // Only 1 and 3 are valid + String newTime = TIME1 + System.lineSeparator(); + String newTeacherName = TEACHER_NAME1 + System.lineSeparator(); + + String entireInput = lessonIndex + lessonFields + newTime + newTeacherName; + initialiseUserInput(entireInput); + + // Run command + UI ui = new UI(); + Command command = new EditLessonCommand(); + command.execute(ui); + + + // Expected lesson after edit + Lesson editedLesson = initialiseLesson(TEACHER_NAME1, TEACHER_EMAIL, + LessonType.TUTORIAL, TIME1, ONLINE_LINK); + Lesson actualLessonAfterEdit = ModuleList.getSelectedModule().getLessonList().get(0); + + assertEquals(editedLesson.getTime(), actualLessonAfterEdit.getTime()); + assertEquals(editedLesson.getOnlineLink(), actualLessonAfterEdit.getOnlineLink()); + assertEquals(editedLesson.getTeachingStaff().getName(), actualLessonAfterEdit.getTeachingStaff().getName()); + assertEquals(editedLesson.getTeachingStaff().getEmail(), actualLessonAfterEdit.getTeachingStaff().getEmail()); + + editLessonTestCleanup(); + } + + private void editLessonTestSetup() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + + ModuleList.addModule(MODULE_CODE); + ModuleList.setSelectedModule(MODULE_CODE); + } + + private void editLessonTestCleanup() { + ModuleList.reset(); + TestUtilAndConstants.emptyModuleList(); + removeOutputStream(); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/EditTaskCommandTest.java b/src/test/java/seedu/duke/commands/EditTaskCommandTest.java new file mode 100644 index 0000000000..0fead4cc43 --- /dev/null +++ b/src/test/java/seedu/duke/commands/EditTaskCommandTest.java @@ -0,0 +1,78 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.ui.UI; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.TestUtilAndConstants.initialiseModuleList; +import static seedu.duke.TestUtilAndConstants.initialiseTaskList; +import static seedu.duke.common.Messages.MESSAGE_EDITED_FIELD; +import static seedu.duke.common.Messages.MESSAGE_FIELDS_TO_EDIT; +import static seedu.duke.common.Messages.MESSAGE_FIELD_BEING_EDITED; +import static seedu.duke.common.Messages.MESSAGE_SEPARATE_INDICES; +import static seedu.duke.common.Messages.MESSAGE_TASK_TO_EDIT; +import static seedu.duke.common.Messages.NEWLINE; +import static seedu.duke.common.Messages.PROMPT_ENTER_FIELD_DETAILS; + +class EditTaskCommandTest { + private final InputStream originalIn = System.in; + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + @Test + void execute_validIndicesAndInput_expectSuccess() { + String input = "3" + NEWLINE + + "2 3" + NEWLINE + + "2-3-2021" + NEWLINE + + "Both quizzes 1 and 2." + NEWLINE; + ByteArrayInputStream bis = new ByteArrayInputStream(input.getBytes()); + System.setIn(bis); + System.setOut(new PrintStream(bos)); + + initialiseModuleList(); + + initialiseTaskList(); + + EditTaskCommand editTaskCommand = new EditTaskCommand(); + editTaskCommand.execute(new UI()); + + String before = "lecture quiz" + NEWLINE; + String fields = "1. Description" + NEWLINE + + "2. Deadline" + NEWLINE + + "3. Remarks" + NEWLINE + + "4. Graded/not graded"; + String output = MESSAGE_TASK_TO_EDIT + NEWLINE + + "1. weekly exercise - 23 Feb 2021" + NEWLINE + + "\t\tDo before 2359." + NEWLINE + + "\t\tGraded" + NEWLINE + + "2. watch video snippets - 25 Feb 2021" + NEWLINE + + "\t\tNot graded" + NEWLINE + + "3. lecture quiz - 26 Feb 2021" + NEWLINE + + "\t\tComplete before next lecture." + NEWLINE + + "\t\tNot graded" + NEWLINE + + "4. read up notes - 26 Feb 2021" + NEWLINE + + "\t\tNot graded" + NEWLINE + + "5. iP submission - 3 Mar 2021" + NEWLINE + + "\t\tRemember to attach the jar file." + NEWLINE + + "\t\tGraded" + NEWLINE + + String.format(MESSAGE_FIELD_BEING_EDITED, before) + + MESSAGE_FIELDS_TO_EDIT + NEWLINE + + fields + NEWLINE + + MESSAGE_SEPARATE_INDICES + NEWLINE + + String.format(PROMPT_ENTER_FIELD_DETAILS, "deadline") + NEWLINE + + String.format(MESSAGE_EDITED_FIELD, "deadline") + NEWLINE + + String.format(PROMPT_ENTER_FIELD_DETAILS, "remarks") + NEWLINE + + String.format(MESSAGE_EDITED_FIELD, "remarks") + NEWLINE; + + // checks displayed output to user + assertEquals(output, bos.toString()); + + System.setIn(originalIn); + System.setOut(originalOut); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/EnterModuleCommandTest.java b/src/test/java/seedu/duke/commands/EnterModuleCommandTest.java new file mode 100644 index 0000000000..9d267d7ca0 --- /dev/null +++ b/src/test/java/seedu/duke/commands/EnterModuleCommandTest.java @@ -0,0 +1,61 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static seedu.duke.TestUtilAndConstants.EXPECTED_ENTER_MODULE; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_1; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_2; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_3; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_4; + +class EnterModuleCommandTest { + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + //@@author isaharon + @Test + void execute_validModuleCodeInput_expectSuccess() throws CommandException { + System.setOut(new PrintStream(outContent)); + + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + ModuleList.addModule(MODULE_CODE_4); + ModuleList.addModule(MODULE_CODE_2); + ModuleList.addModule(MODULE_CODE_3); + + Command command = new EnterModuleCommand(MODULE_CODE_2); + command.execute(new UI()); + + assertEquals(EXPECTED_ENTER_MODULE, outContent.toString()); + + System.setOut(originalOut); + } + + @Test + void execute_invalidModuleCodeInput_expectException() { + System.setOut(new PrintStream(outContent)); + + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + ModuleList.addModule(MODULE_CODE_4); + ModuleList.addModule(MODULE_CODE_2); + ModuleList.addModule(MODULE_CODE_3); + + Command command = new EnterModuleCommand("CS3235"); + + assertThrows(CommandException.class, () -> command.execute(new UI())); + + System.setOut(originalOut); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/ExitModuleCommandTest.java b/src/test/java/seedu/duke/commands/ExitModuleCommandTest.java new file mode 100644 index 0000000000..bc04418b1e --- /dev/null +++ b/src/test/java/seedu/duke/commands/ExitModuleCommandTest.java @@ -0,0 +1,23 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_1; + +public class ExitModuleCommandTest { + + //@@author 8kdesign + @Test + void execute_selectCloseModule_selectedEqualsNull() { + ModuleList.addModule(MODULE_CODE_1); + ModuleList.setSelectedModule(MODULE_CODE_1); + ExitModuleCommand command = new ExitModuleCommand(); + command.execute(new UI()); + assertNull(ModuleList.getSelectedModule()); + ModuleList.reset(); + } + +} diff --git a/src/test/java/seedu/duke/commands/ExitProgramCommandTest.java b/src/test/java/seedu/duke/commands/ExitProgramCommandTest.java new file mode 100644 index 0000000000..c8cbb4e283 --- /dev/null +++ b/src/test/java/seedu/duke/commands/ExitProgramCommandTest.java @@ -0,0 +1,45 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.common.Messages; +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_2; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_3; + +class ExitProgramCommandTest { + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + //@@author isaharon + @Test + void execute_noInput_expectMessageExit() throws CommandException { + System.setOut(new PrintStream(outContent)); + + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_3); + ModuleList.addModule(MODULE_CODE_2); + + Command command = new ExitProgramCommand(); + command.execute(new UI()); + + String output = Messages.MESSAGE_EXIT; + assertEquals(output + System.lineSeparator(), outContent.toString()); + + System.setOut(originalOut); + } + + @Test + void isExit() { + assertTrue(new ExitProgramCommand().isExit()); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/LessonCommandTest.java b/src/test/java/seedu/duke/commands/LessonCommandTest.java new file mode 100644 index 0000000000..8f3c60aeb6 --- /dev/null +++ b/src/test/java/seedu/duke/commands/LessonCommandTest.java @@ -0,0 +1,82 @@ +package seedu.duke.commands; + +import seedu.duke.exception.CommandException; +import seedu.duke.lesson.Lesson; +import seedu.duke.lesson.LessonType; +import seedu.duke.lesson.TeachingStaff; +import seedu.duke.ui.UI; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; + +public class LessonCommandTest { + + public static final String MODULE_CODE = "CS3235"; + public static final String TEACHER_NAME = "Ivan Chong Zhi En"; + public static final String TEACHER_EMAIL = "ivanchong@zhi.en"; + public static final String TIME = "Wednesday 9 am - 10am"; + public static final String ONLINE_LINK = "https://youtu.be/dQw4w9WgXcQ"; + public static final String TEACHER_NAME1 = "Isa Bin Haron"; + public static final String TEACHER_EMAIL1 = "isa@bin.haron"; + public static final String TIME1 = "Wednesday 9 pm - 10 pm"; + public static final String ONLINE_LINK1 = "https://youtu.be/YnopHCL1Jk8"; + public static final int FIRST_INDEX = 1; + public static final int SECOND_INDEX = 2; + public static final String COMMAND_FAILED = "Command failed"; + + //@@author H-horizon + public static void removeOutputStream() { + PrintStream originalOut = System.out; + System.setOut(originalOut); + } + + public static OutputStream getOutputStream() { + OutputStream os = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(os); + System.setOut(ps); + return os; + } + + public static Lesson initialiseLesson(String teacherName, String teacherEmail, LessonType lessonType, String time, + String onlineLink) { + TeachingStaff teachingStaff = new TeachingStaff(teacherName, teacherEmail); + Lesson newLesson = new Lesson(lessonType, time, onlineLink, teachingStaff); + return newLesson; + } + + public static void addLessonsToList(UI ui) { + Lesson newLesson = initialiseLesson(TEACHER_NAME, TEACHER_EMAIL, LessonType.TUTORIAL, TIME, ONLINE_LINK); + Command command = new AddLessonCommand(newLesson); + try { + command.execute(ui); + } catch (CommandException e) { + printFailedToExecuteCommand(); + } + + newLesson = initialiseLesson(TEACHER_NAME1, TEACHER_EMAIL1, LessonType.LAB, TIME1, ONLINE_LINK1); + command = new AddLessonCommand(newLesson); + try { + command.execute(ui); + } catch (CommandException e) { + printFailedToExecuteCommand(); + } + } + + public static void printFailedToExecuteCommand() { + System.out.println(COMMAND_FAILED); + } + + protected void initialiseUserInput(String entireInput) { + ByteArrayInputStream inContent = new ByteArrayInputStream(entireInput.getBytes()); + System.setIn(inContent); + getOutputStream(); + } + + public static void initialisedIndexes(ArrayList indexes) { + indexes.add(FIRST_INDEX); + indexes.add(SECOND_INDEX); + } +} diff --git a/src/test/java/seedu/duke/commands/ListLessonsCommandTest.java b/src/test/java/seedu/duke/commands/ListLessonsCommandTest.java new file mode 100644 index 0000000000..db71016cbf --- /dev/null +++ b/src/test/java/seedu/duke/commands/ListLessonsCommandTest.java @@ -0,0 +1,68 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.OutputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.duke.common.Messages.INDENTATION; +import static seedu.duke.common.Messages.MESSAGE_LESSONS_LIST_EMPTY; +import static seedu.duke.common.Messages.MESSAGE_LESSONS_TO_LIST; +import static seedu.duke.common.Messages.NEWLINE; + +class ListLessonsCommandTest extends LessonCommandTest { + + public static final String EXPECTED_OUTPUT = String.format(MESSAGE_LESSONS_TO_LIST, MODULE_CODE) + NEWLINE + + "1. Lab - " + TIME1 + NEWLINE + INDENTATION + ONLINE_LINK1 + NEWLINE + INDENTATION + + TEACHER_NAME1 + NEWLINE + INDENTATION + TEACHER_EMAIL1 + NEWLINE + + "2. Tutorial - " + TIME + NEWLINE + INDENTATION + ONLINE_LINK + NEWLINE + INDENTATION + + TEACHER_NAME + NEWLINE + INDENTATION + TEACHER_EMAIL + NEWLINE; + + //@@author H-horizon + @Test + void printLessonsFromList_lessonList_expectPrintsCorrectOutput() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + UI ui = new UI(); + + OutputStream os = getOutputStream(); + ModuleList.addModule(MODULE_CODE); + ModuleList.setSelectedModule(MODULE_CODE); + addLessonsToList(ui); + removeOutputStream(); + + OutputStream newOs = getOutputStream(); + ListLessonsCommand listLessonsCommand = new ListLessonsCommand(); + listLessonsCommand.execute(ui); + assertEquals(EXPECTED_OUTPUT, newOs.toString()); + removeOutputStream(); + } + + @Test + void execute_ui_expectsPrintEmpty() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE); + ModuleList.setSelectedModule(MODULE_CODE); + + UI ui = new UI(); + OutputStream os = getOutputStream(); + Command command = new ListLessonsCommand(); + try { + command.execute(ui); + boolean isEqual = false; + if (os.toString().equals(MESSAGE_LESSONS_LIST_EMPTY + "\n") + || os.toString().equals(MESSAGE_LESSONS_LIST_EMPTY + "\r\n")) { + isEqual = true; + } + assertTrue(isEqual); + } catch (CommandException e) { + printFailedToExecuteCommand(); + } + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/ListModulesCommandTest.java b/src/test/java/seedu/duke/commands/ListModulesCommandTest.java new file mode 100644 index 0000000000..893ddd79a4 --- /dev/null +++ b/src/test/java/seedu/duke/commands/ListModulesCommandTest.java @@ -0,0 +1,48 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.common.Messages; +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_1; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_2; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_3; +import static seedu.duke.common.Messages.MESSAGE_MODULE_TO_LIST; +import static seedu.duke.common.Messages.NEWLINE; + +class ListModulesCommandTest { + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + //@@author isaharon + @Test + void execute_noInput_expectListAllModulesAdded() throws CommandException { + System.setOut(new PrintStream(outContent)); + + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + ModuleList.addModule(MODULE_CODE_3); + ModuleList.addModule(MODULE_CODE_2); + + Command command = new ListModulesCommand(); + command.execute(new UI()); + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(MESSAGE_MODULE_TO_LIST).append(NEWLINE); + stringBuilder.append(String.format(Messages.FORMAT_LIST_ITEMS, 1, MODULE_CODE_1)).append(NEWLINE); + stringBuilder.append(String.format(Messages.FORMAT_LIST_ITEMS, 2, MODULE_CODE_3)).append(NEWLINE); + stringBuilder.append(String.format(Messages.FORMAT_LIST_ITEMS, 3, MODULE_CODE_2)).append(NEWLINE); + + assertEquals(stringBuilder.toString(), outContent.toString()); + + System.setOut(originalOut); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/ListTasksCommandTest.java b/src/test/java/seedu/duke/commands/ListTasksCommandTest.java new file mode 100644 index 0000000000..5dc2297bc1 --- /dev/null +++ b/src/test/java/seedu/duke/commands/ListTasksCommandTest.java @@ -0,0 +1,58 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.ui.UI; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.TestUtilAndConstants.FORMATTER; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_1; +import static seedu.duke.TestUtilAndConstants.initialiseModuleList; +import static seedu.duke.TestUtilAndConstants.initialiseTaskList; +import static seedu.duke.common.Messages.HEADER_DONE; +import static seedu.duke.common.Messages.HEADER_UNDONE; +import static seedu.duke.common.Messages.MESSAGE_TASKS_TO_LIST; +import static seedu.duke.common.Messages.NEWLINE; + + +class ListTasksCommandTest { + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + //@@author aliciatay-zls + @Test + void execute_void_expectSuccess() { + System.setOut(new PrintStream(bos)); + + initialiseModuleList(); + + initialiseTaskList(); + + UI ui = new UI(); + LocalDate dueDate1 = LocalDate.parse("23-02-2021", FORMATTER); + LocalDate dueDate2 = LocalDate.parse("26-02-2021", FORMATTER); + + ListTasksCommand listTasksCommand = new ListTasksCommand(); + listTasksCommand.execute(ui); + + String output = String.format(MESSAGE_TASKS_TO_LIST, MODULE_CODE_1) + NEWLINE + NEWLINE + + HEADER_UNDONE + NEWLINE + + "1. weekly exercise (graded) - 23 Feb 2021" + ui.getDaysRemainingMessage(dueDate1) + NEWLINE + + "\t\tDo before 2359." + NEWLINE + + "2. lecture quiz - 26 Feb 2021" + ui.getDaysRemainingMessage(dueDate2) + NEWLINE + + "\t\tComplete before next lecture." + NEWLINE + + "3. read up notes - 26 Feb 2021" + ui.getDaysRemainingMessage(dueDate2) + NEWLINE + NEWLINE + + HEADER_DONE + NEWLINE + + "1. watch video snippets - 25 Feb 2021" + NEWLINE + + "2. iP submission (graded) - 3 Mar 2021" + NEWLINE + + "\t\tRemember to attach the jar file." + NEWLINE; + + // checks displayed output to user + assertEquals(output, bos.toString()); + + System.setOut(originalOut); + } +} diff --git a/src/test/java/seedu/duke/commands/MarkAsDoneCommandTest.java b/src/test/java/seedu/duke/commands/MarkAsDoneCommandTest.java new file mode 100644 index 0000000000..ab6a66fc7a --- /dev/null +++ b/src/test/java/seedu/duke/commands/MarkAsDoneCommandTest.java @@ -0,0 +1,70 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.TestUtilAndConstants.initialiseModuleList; +import static seedu.duke.TestUtilAndConstants.initialiseTaskList; +import static seedu.duke.common.Constants.MARK; +import static seedu.duke.common.Constants.TYPE_TASK; +import static seedu.duke.common.Messages.MESSAGE_ENTER_INDICES; +import static seedu.duke.common.Messages.MESSAGE_TASKS_TO_MARK; +import static seedu.duke.common.Messages.NEWLINE; + +class MarkAsDoneCommandTest { + private final InputStream originalIn = System.in; + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + //@@author aliciatay-zls + @Test + void execute_twoValidTaskIndices_expectSuccess() { + String input = "1 2" + NEWLINE; + ByteArrayInputStream bis = new ByteArrayInputStream(input.getBytes()); + System.setIn(bis); + System.setOut(new PrintStream(bos)); + + initialiseModuleList(); + + ArrayList taskList = initialiseTaskList(); + + MarkAsDoneCommand markAsDoneCommand = new MarkAsDoneCommand(); + markAsDoneCommand.execute(new UI()); + + String output = MESSAGE_TASKS_TO_MARK + NEWLINE + + "1. weekly exercise" + NEWLINE + + "2. lecture quiz" + NEWLINE + + "3. read up notes" + NEWLINE + + String.format(MESSAGE_ENTER_INDICES, TYPE_TASK, MARK) + NEWLINE + + "Marked weekly exercise as done." + NEWLINE + + "Marked lecture quiz as done." + NEWLINE; + + // checks displayed output to user + assertEquals(output, bos.toString()); + + String expectedDone = "true" + NEWLINE + + "true" + NEWLINE + + "true" + NEWLINE + + "false" + NEWLINE + + "true" + NEWLINE; + + StringBuilder actualDone = new StringBuilder(); + for (Task task : taskList) { + actualDone.append(task.getDone()).append(NEWLINE); + } + + // checks if tasks were correctly marked in task list + assertEquals(expectedDone, actualDone.toString()); + + System.setIn(originalIn); + System.setOut(originalOut); + } +} diff --git a/src/test/java/seedu/duke/commands/MarkAsUndoneCommandTest.java b/src/test/java/seedu/duke/commands/MarkAsUndoneCommandTest.java new file mode 100644 index 0000000000..7520ccf4ec --- /dev/null +++ b/src/test/java/seedu/duke/commands/MarkAsUndoneCommandTest.java @@ -0,0 +1,67 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.TestUtilAndConstants.initialiseModuleList; +import static seedu.duke.TestUtilAndConstants.initialiseTaskList; +import static seedu.duke.common.Constants.TYPE_TASK; +import static seedu.duke.common.Constants.UNMARK; +import static seedu.duke.common.Messages.MESSAGE_ENTER_INDICES; +import static seedu.duke.common.Messages.MESSAGE_TASKS_TO_UNMARK; +import static seedu.duke.common.Messages.NEWLINE; + +class MarkAsUndoneCommandTest { + private final InputStream originalIn = System.in; + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + //@@author aliciatay-zls + @Test + void execute_twoValidTaskIndices_expectSuccess() { + String input = "1" + NEWLINE; + ByteArrayInputStream bis = new ByteArrayInputStream(input.getBytes()); + System.setIn(bis); + System.setOut(new PrintStream(bos)); + + initialiseModuleList(); + + ArrayList taskList = initialiseTaskList(); + + MarkAsUndoneCommand markAsUndoneCommand = new MarkAsUndoneCommand(); + markAsUndoneCommand.execute(new UI()); + + String output = MESSAGE_TASKS_TO_UNMARK + NEWLINE + + "1. watch video snippets" + NEWLINE + + "2. iP submission" + NEWLINE + + String.format(MESSAGE_ENTER_INDICES, TYPE_TASK, UNMARK) + NEWLINE + + "Marked watch video snippets as undone." + NEWLINE; + + // checks displayed output to user + assertEquals(output, bos.toString()); + + StringBuilder actualDone = new StringBuilder(); + for (Task task : taskList) { + actualDone.append(task.getDone()).append(NEWLINE); + } + String expectedDone = "false" + NEWLINE + + "false" + NEWLINE + + "false" + NEWLINE + + "false" + NEWLINE + + "true" + NEWLINE; + + // checks if tasks were correctly unmarked in task list + assertEquals(expectedDone, actualDone.toString()); + + System.setIn(originalIn); + System.setOut(originalOut); + } +} diff --git a/src/test/java/seedu/duke/commands/ModuleInfoCommandTest.java b/src/test/java/seedu/duke/commands/ModuleInfoCommandTest.java new file mode 100644 index 0000000000..3b2a0a8678 --- /dev/null +++ b/src/test/java/seedu/duke/commands/ModuleInfoCommandTest.java @@ -0,0 +1,52 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.task.Task; +import seedu.duke.ui.UI; + +import java.io.OutputStream; +import java.time.LocalDate; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.TestUtilAndConstants.EXPECTED_MODULE_OVERVIEW; +import static seedu.duke.TestUtilAndConstants.FORMATTER; + +class ModuleInfoCommandTest extends LessonCommandTest { + + + //@@author H-horizon + @Test + void execute_ui_expectPrintsCorrectOutput() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + UI ui = new UI(); + + OutputStream os = getOutputStream(); + ModuleList.addModule(MODULE_CODE); + ModuleList.setSelectedModule(MODULE_CODE); + addLessonsToList(ui); + LocalDate deadline = LocalDate.parse("3-3-2021", FORMATTER); + Task task = new Task("iP submission", deadline, "remember to attach JAR file"); + task.setGraded(true); + ArrayList tasksList = ModuleList.getSelectedModule().getTaskList(); + tasksList.add(task); + removeOutputStream(); + + OutputStream newOs = getOutputStream(); + Command command = new ModuleInfoCommand(); + try { + command.execute(ui); + } catch (CommandException e) { + printFailedToExecuteCommand(); + } + + String deadlineMessage = ui.getDaysRemainingMessage(deadline); + String expectedMessage = String.format(EXPECTED_MODULE_OVERVIEW, deadlineMessage); + assertEquals(expectedMessage, newOs.toString()); + removeOutputStream(); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/OpenLessonLinkCommandTest.java b/src/test/java/seedu/duke/commands/OpenLessonLinkCommandTest.java new file mode 100644 index 0000000000..7d75eb53e9 --- /dev/null +++ b/src/test/java/seedu/duke/commands/OpenLessonLinkCommandTest.java @@ -0,0 +1,36 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.OutputStream; +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.TestUtilAndConstants.EXPECTED_OPEN_LINK; + +class OpenLessonLinkCommandTest extends LessonCommandTest { + + //@@author H-horizon + @Test + void printLessonsLink_lessonListIndexes_expectPrintsCorrectOutput() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + UI ui = new UI(); + + OutputStream os = getOutputStream(); + ModuleList.addModule(MODULE_CODE); + ModuleList.setSelectedModule(MODULE_CODE); + addLessonsToList(ui); + ArrayList indices = new ArrayList<>(); + initialisedIndexes(indices); + removeOutputStream(); + + OutputStream newOs = getOutputStream(); + OpenLessonLinkCommand.printLessonsLink(ModuleList.getSelectedModule().getLessonList(), indices, ui); + assertEquals(EXPECTED_OPEN_LINK, newOs.toString()); + removeOutputStream(); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/PrintHelpCommandTest.java b/src/test/java/seedu/duke/commands/PrintHelpCommandTest.java new file mode 100644 index 0000000000..ed3a6195da --- /dev/null +++ b/src/test/java/seedu/duke/commands/PrintHelpCommandTest.java @@ -0,0 +1,65 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.common.DashboardCommands; +import seedu.duke.common.Messages; +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_2; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_3; +import static seedu.duke.common.Messages.MESSAGE_DASHBOARD_HELP; +import static seedu.duke.common.Messages.MESSAGE_MODULE_HELP; +import static seedu.duke.common.Messages.NEWLINE; + +class PrintHelpCommandTest { + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + //@@author isaharon + @Test + void execute_noInput_expectAllCommandsWithDescription() throws CommandException { + System.setOut(new PrintStream(outContent)); + + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_3); + ModuleList.addModule(MODULE_CODE_2); + + // no module selected + ModuleList.reset(); + + // execute command + PrintHelpCommand printHelpCommand = new PrintHelpCommand(); + printHelpCommand.execute(new UI()); + + StringBuilder stringBuilder = new StringBuilder(); + + if (ModuleList.getSelectedModule() == null) { + stringBuilder.append(MESSAGE_DASHBOARD_HELP).append(NEWLINE); + } else { + stringBuilder.append(MESSAGE_MODULE_HELP).append(NEWLINE); + } + for (DashboardCommands command : DashboardCommands.values()) { + String commandWordAndArgs = command.getWord() + " " + command.getArgumentsFormat(); + String commandAndDescription = String.format(Messages.FORMAT_LIST_HELP, + commandWordAndArgs, command.getDescription()); + stringBuilder.append(commandAndDescription).append(NEWLINE).append(NEWLINE); + } + String output = stringBuilder.toString(); + assertEquals(output, outContent.toString() + NEWLINE); + + // module selected + ModuleList.setSelectedModule(MODULE_CODE_3); + printHelpCommand = new PrintHelpCommand(); + printHelpCommand.execute(new UI()); + + System.setOut(originalOut); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/commands/ViewTeachingStaffCommandTest.java b/src/test/java/seedu/duke/commands/ViewTeachingStaffCommandTest.java new file mode 100644 index 0000000000..17e5b4ff9a --- /dev/null +++ b/src/test/java/seedu/duke/commands/ViewTeachingStaffCommandTest.java @@ -0,0 +1,41 @@ +package seedu.duke.commands; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.exception.CommandException; +import seedu.duke.module.ModuleList; +import seedu.duke.ui.UI; + +import java.io.OutputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.common.Messages.MESSAGE_TEACHING_STAFF_TO_LIST; +import static seedu.duke.common.Messages.NEWLINE; + +class ViewTeachingStaffCommandTest extends LessonCommandTest { + + //@@author H-horizon + @Test + void execute_ui_expectPrintsCorrectOutput() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + UI ui = new UI(); + + ModuleList.addModule(MODULE_CODE); + ModuleList.setSelectedModule(MODULE_CODE); + addLessonsToList(ui); + Command command = new ViewTeachingStaffCommand(); + + OutputStream newOs = getOutputStream(); + try { + command.execute(ui); + } catch (CommandException e) { + printFailedToExecuteCommand(); + } + String expectedOutput = String.format(MESSAGE_TEACHING_STAFF_TO_LIST, MODULE_CODE) + NEWLINE + + "1. " + TEACHER_NAME1 + " - " + TEACHER_EMAIL1 + NEWLINE + + "2. " + TEACHER_NAME + " - " + TEACHER_EMAIL + NEWLINE; + assertEquals(expectedOutput, newOs.toString()); + removeOutputStream(); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/lesson/TeachingStaffTest.java b/src/test/java/seedu/duke/lesson/TeachingStaffTest.java new file mode 100644 index 0000000000..d71679bdcb --- /dev/null +++ b/src/test/java/seedu/duke/lesson/TeachingStaffTest.java @@ -0,0 +1,76 @@ +package seedu.duke.lesson; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TeachingStaffTest { + // Emails taken from https://gist.github.com/cjaoude/fd9910626629b53c4d25. + // Excluded strange emails and some others. + static final String VALID_EMAIL1 = "email@example.com"; + static final String VALID_EMAIL2 = "firstname.lastname@example.com"; + static final String VALID_EMAIL3 = "email@subdomain.example.com"; + static final String VALID_EMAIL4 = "firstname+lastname@example.com"; + static final String VALID_EMAIL5 = "email@123.123.123.123"; + static final String VALID_EMAIL6 = "email@[123.123.123.123]"; + static final String VALID_EMAIL7 = "\"email\"@example.com"; + static final String VALID_EMAIL8 = "1234567890@example.com"; + static final String VALID_EMAIL9 = "email@example-one.com"; + static final String VALID_EMAIL10 = "_______@example.com"; + static final String VALID_EMAIL11 = "email@example.name"; + static final String VALID_EMAIL12 = "email@example.museum"; + static final String VALID_EMAIL13 = "email@example.co.jp"; + static final String VALID_EMAIL14 = "firstname-lastname@example.com"; + + static final String INVALID_EMAIL1 = "plainaddress"; + static final String INVALID_EMAIL2 = "#@%^%#$@#$@#.com"; + static final String INVALID_EMAIL3 = "@example.com"; + static final String INVALID_EMAIL4 = "Joe Smith "; + static final String INVALID_EMAIL5 = "email.example.com"; + static final String INVALID_EMAIL6 = "email@example@example.com"; + static final String INVALID_EMAIL7 = ".email@example.com"; + static final String INVALID_EMAIL8 = "email.@example.com"; + static final String INVALID_EMAIL9 = "email..email@example.com"; + static final String INVALID_EMAIL10 = "email@example.com (Joe Smith)"; + static final String INVALID_EMAIL11 = "email@example"; + static final String INVALID_EMAIL12 = "email@-example.com"; + static final String INVALID_EMAIL13 = "email@example..com"; + static final String INVALID_EMAIL14 = "Abc..123@example.com"; + + @Test + void isValidEmail_validEmailInput_expectTrue() { + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL1)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL2)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL3)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL4)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL5)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL6)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL7)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL8)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL9)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL10)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL11)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL12)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL13)); + assertTrue(TeachingStaff.isValidEmail(VALID_EMAIL14)); + } + + @Test + void isValidEmail_invalidEmailInput_expectFalse() { + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL1)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL2)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL3)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL4)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL5)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL6)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL7)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL8)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL9)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL10)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL11)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL12)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL13)); + assertFalse(TeachingStaff.isValidEmail(INVALID_EMAIL14)); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/module/ModuleListTest.java b/src/test/java/seedu/duke/module/ModuleListTest.java new file mode 100644 index 0000000000..82623d38df --- /dev/null +++ b/src/test/java/seedu/duke/module/ModuleListTest.java @@ -0,0 +1,230 @@ +package seedu.duke.module; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.storage.Writer; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_1; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_4; +import static seedu.duke.common.Constants.FOLDER_PATH; +import static seedu.duke.common.Constants.TXT_FORMAT; + +class ModuleListTest { + + //@@author 8kdesign + @Test + void loadModuleCodes_noDirectory_sizeZero() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + assertEquals(0, ModuleList.getModules().size()); + } + + @Test + void loadModuleCodes_oneInvalidFile_sizeZero() throws IOException { + TestUtilAndConstants.removeFiles(); + File directory = new File(FOLDER_PATH); + directory.mkdir(); + File file1 = new File(FOLDER_PATH + "/" + "CS2113T.img"); + file1.createNewFile(); + ModuleList.loadModuleCodes(); + assertEquals(0, ModuleList.getModules().size()); + } + + @Test + void loadModuleCodes_twoDifferentModules_sizeTwo() throws IOException { + TestUtilAndConstants.removeFiles(); + File mainDirectory = new File(FOLDER_PATH); + mainDirectory.mkdir(); + File moduleDirectory1 = new File(FOLDER_PATH + "/" + MODULE_CODE_1); + moduleDirectory1.mkdir(); + File moduleDirectory2 = new File(FOLDER_PATH + "/" + MODULE_CODE_4); + moduleDirectory2.mkdir(); + File file1 = new File(FOLDER_PATH + "/" + MODULE_CODE_1 + "/" + MODULE_CODE_1 + TXT_FORMAT); + file1.createNewFile(); + File file2 = new File(FOLDER_PATH + "/" + MODULE_CODE_4 + "/" + MODULE_CODE_4 + TXT_FORMAT); + file2.createNewFile(); + ModuleList.loadModuleCodes(); + assertEquals(2, ModuleList.getModules().size()); + } + + @Test + void loadModuleCodes_twoSameModules_sizeTwo() throws IOException { + TestUtilAndConstants.removeFiles(); + File mainDirectory = new File(FOLDER_PATH); + mainDirectory.mkdir(); + File moduleDirectory = new File(FOLDER_PATH + "/" + MODULE_CODE_1); + moduleDirectory.mkdir(); + File file1 = new File(FOLDER_PATH + "/" + MODULE_CODE_1 + "/" + MODULE_CODE_1 + TXT_FORMAT); + file1.createNewFile(); + file1.createNewFile(); + ModuleList.loadModuleCodes(); + assertEquals(1, ModuleList.getModules().size()); + } + + @Test + void setSelectedModule_validCode_loadsModule() throws IOException { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.reset(); + File mainDirectory = new File(FOLDER_PATH); + mainDirectory.mkdir(); + File moduleDirectory = new File(FOLDER_PATH + "/CS2113T"); + moduleDirectory.mkdir(); + Path reference = Paths.get("src/test/java/seedu/duke/storage/reference/all_content_reference.txt"); + Path destination = Paths.get("Data/CS2113T/CS2113T.txt"); + if (Files.exists(destination)) { + Files.delete(destination); + } + Files.copy(reference, destination); + ModuleList.loadModuleCodes(); + ModuleList.setSelectedModule(MODULE_CODE_1); + assertEquals(2, ModuleList.getSelectedModule().getTaskList().size()); + } + + @Test + void setSelectedModule_invalidCode_remainNull() throws IOException { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.reset(); + File directory = new File(FOLDER_PATH); + directory.mkdir(); + Path reference = Paths.get("src/test/java/seedu/duke/storage/reference/all_content_reference.txt"); + Path destination = Paths.get("Data/CS2113T.txt"); + if (Files.exists(destination)) { + Files.delete(destination); + } + Files.copy(reference, destination); + ModuleList.loadModuleCodes(); + ModuleList.setSelectedModule("CS2100"); + assertThrows(NullPointerException.class, () -> { + ModuleList.getSelectedModule().getModuleCode(); + }); + } + + @Test + void setSelectedModule_invalidFile_noTaskAndLesson() throws IOException { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.reset(); + File mainDirectory = new File(FOLDER_PATH); + mainDirectory.mkdir(); + File moduleDirectory = new File(FOLDER_PATH + "/CS2113T"); + moduleDirectory.mkdir(); + Path reference = Paths.get("src/test/java/seedu/duke/storage/reference/invalid_file_reference.txt"); + Path destination = Paths.get("Data/CS2113T/CS2113T.txt"); + if (Files.exists(destination)) { + Files.delete(destination); + } + Files.copy(reference, destination); + ModuleList.loadModuleCodes(); + ModuleList.setSelectedModule(MODULE_CODE_1); + assertEquals(0,ModuleList.getSelectedModule().getTaskList().size()); + assertEquals(0,ModuleList.getSelectedModule().getLessonList().size()); + } + + @Test + void setSelectedModule_invalidContent_removeInvalid() throws IOException { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.reset(); + File mainDirectory = new File(FOLDER_PATH); + mainDirectory.mkdir(); + File moduleDirectory = new File(FOLDER_PATH + "/CS2113T"); + moduleDirectory.mkdir(); + Path reference = Paths.get("src/test/java/seedu/duke/storage/reference/invalid_content_reference.txt"); + Path destination = Paths.get("Data/CS2113T/CS2113T.txt"); + if (Files.exists(destination)) { + Files.delete(destination); + } + Files.copy(reference, destination); + ModuleList.loadModuleCodes(); + ModuleList.setSelectedModule(MODULE_CODE_1); + assertEquals(3,ModuleList.getSelectedModule().getLessonList().size()); + assertEquals(1,ModuleList.getSelectedModule().getTaskList().size()); + } + + @Test + void addModule_twoDifferentModules_sizeTwo() throws IOException { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_4); + ModuleList.addModule(MODULE_CODE_1); + Path reference = Paths.get("src/test/java/seedu/duke/storage/reference/empty_reference.txt"); + Path actual1 = Paths.get("Data/CS2113T/CS2113T.txt"); + Path actual2 = Paths.get("Data/CS2101/CS2101.txt"); + boolean isExist = Files.exists(actual1) && Files.exists(actual2); + boolean isIdentical = Files.readAllLines(reference).equals(Files.readAllLines(actual1)); + boolean isTwo = ModuleList.getModules().size() == 2; + assertTrue(isExist); + assertTrue(isIdentical); + assertTrue(isTwo); + } + + @Test + void addModule_twoSameModules_sizeOne() throws IOException { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + ModuleList.addModule(MODULE_CODE_1); + System.out.println(ModuleList.getModules().size()); + Path reference = Paths.get("src/test/java/seedu/duke/storage/reference/empty_reference.txt"); + Path actual1 = Paths.get("Data/CS2113T/CS2113T.txt"); + boolean isExist = Files.exists(actual1); + boolean isIdentical = Files.readAllLines(reference).equals(Files.readAllLines(reference)); + boolean isOne = ModuleList.getModules().size() == 1; + assertTrue(isExist); + assertTrue(isIdentical); + assertTrue(isOne); + } + + @Test + void removeModule_validIndex_removes() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + ModuleList.addModule(MODULE_CODE_4); + ModuleList.removeModule(1); + assertEquals(1, ModuleList.getModules().size()); + } + + @Test + void removeModule_negativeIndex_noChange() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + ModuleList.addModule(MODULE_CODE_4); + ModuleList.removeModule(-1); + assertEquals(2, ModuleList.getModules().size()); + } + + @Test + void removeModule_indexOutOfBounds_noChange() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + ModuleList.addModule(MODULE_CODE_4); + ModuleList.removeModule(2); + assertEquals(2, ModuleList.getModules().size()); + } + + @Test + void addRemoveAddModule_SameModule_sizeOne() { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_4); + assertEquals(1, ModuleList.getModules().size()); + ModuleList.removeModule(0); + assertEquals(0, ModuleList.getModules().size()); + ModuleList.addModule(MODULE_CODE_4); + assertEquals(1, ModuleList.getModules().size()); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/parser/ParserTest.java b/src/test/java/seedu/duke/parser/ParserTest.java new file mode 100644 index 0000000000..36367e3acc --- /dev/null +++ b/src/test/java/seedu/duke/parser/ParserTest.java @@ -0,0 +1,153 @@ +package seedu.duke.parser; + +import seedu.duke.TestUtilAndConstants; +import seedu.duke.commands.AddModuleCommand; +import seedu.duke.commands.Command; +import seedu.duke.commands.DeleteModuleCommand; +import seedu.duke.commands.EditCheatSheetCommand; +import seedu.duke.commands.EnterModuleCommand; +import seedu.duke.commands.ListLessonsCommand; +import seedu.duke.commands.ListTasksCommand; +import seedu.duke.commands.ModuleInfoCommand; +import seedu.duke.commands.OpenLessonLinkCommand; +import seedu.duke.commands.PrintHelpCommand; +import seedu.duke.exception.CommandException; +import seedu.duke.exception.ParserException; +import seedu.duke.module.ModuleList; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static seedu.duke.common.ModuleCommands.EDIT_CHEAT_SHEET; +import static seedu.duke.common.ModuleCommands.INFO; +import static seedu.duke.common.ModuleCommands.LESSONS; +import static seedu.duke.common.ModuleCommands.LINK; +import static seedu.duke.common.ModuleCommands.TASKS; + +import org.junit.jupiter.api.Test; + +class ParserTest { + + public static final String MODULE_CODE = "CS1234"; + public static final String ADD_MODULE = "add CS2113T"; + public static final String HELP = "help"; + public static final String OPEN_MODULE = "open cs1234"; + public static final String DELETE_MODULE = "del"; + public static final String ARBITRARY_STRING = "AakjhdLKLlkjlLJAAasldkj 12801 =123-=-';"; + public static final String INCORRECT_DASHBOARD1 = "help me!!"; + public static final String INCORRECT_DASHBOARD2 = "modules abc 123"; + public static final String INCORRECT_DASHBOARD3 = "del something"; + + + //@@author ivanchongzhien + + @Test + // DASHBOARD COMMANDS + void parse_dashboardCommands_correctCommandObject() throws CommandException, + ParserException { + ModuleList.reset(); + + Parser parser = new Parser(); + + Command command1 = parser.parse(ADD_MODULE); + assertTrue(command1 instanceof AddModuleCommand); + + Command command2 = parser.parse(HELP); + assertTrue(command2 instanceof PrintHelpCommand); + + Command command3 = parser.parse(OPEN_MODULE); + assertTrue(command3 instanceof EnterModuleCommand); + + Command command4 = parser.parse(DELETE_MODULE); + assertTrue(command4 instanceof DeleteModuleCommand); + } + + @Test + // IN MODULE COMMAND + void parse_inModuleCommands_correctCommandObject() throws CommandException, ParserException { + ModuleList.addModule(MODULE_CODE); + ModuleList.setSelectedModule(MODULE_CODE); + + Parser parser = new Parser(); + + Command actualCommand = parser.parse(TASKS.getWord()); + assertTrue(actualCommand instanceof ListTasksCommand); + + actualCommand = parser.parse(LINK.getWord()); + assertTrue(actualCommand instanceof OpenLessonLinkCommand); + + actualCommand = parser.parse(LESSONS.getWord()); + assertTrue(actualCommand instanceof ListLessonsCommand); + + actualCommand = parser.parse(INFO.getWord()); + assertTrue(actualCommand instanceof ModuleInfoCommand); + + actualCommand = parser.parse(EDIT_CHEAT_SHEET.getWord()); + assertTrue(actualCommand instanceof EditCheatSheetCommand); + + // Invalid command case - not so happy case + boolean isThrown = false; + try { + parser.parse(ARBITRARY_STRING); + } catch (ParserException e) { + isThrown = true; + } + assertTrue(isThrown); + + ModuleList.reset(); + TestUtilAndConstants.emptyModuleList(); + } + + @Test + // IN MODULE COMMAND + void parse_invalidCommands_exceptionIsThrown() throws CommandException, ParserException { + Parser parser = new Parser(); + boolean isThrown = false; + + // Dashboard + try { + parser.parse(ARBITRARY_STRING); + } catch (ParserException e) { + isThrown = true; + } + assertTrue(isThrown); + isThrown = false; + + // Command with unnecessary arguments + try { + parser.parse(INCORRECT_DASHBOARD1); + } catch (ParserException e) { + isThrown = true; + } + assertTrue(isThrown); + isThrown = false; + + try { + parser.parse(INCORRECT_DASHBOARD2); + } catch (ParserException e) { + isThrown = true; + } + assertTrue(isThrown); + isThrown = false; + + try { + parser.parse(INCORRECT_DASHBOARD3); + } catch (ParserException e) { + isThrown = true; + } + assertTrue(isThrown); + isThrown = false; + + // In module + ModuleList.addModule(MODULE_CODE); + ModuleList.setSelectedModule(MODULE_CODE); + + try { + parser.parse(ARBITRARY_STRING); + } catch (ParserException e) { + isThrown = true; + } + assertTrue(isThrown); + + ModuleList.reset(); + TestUtilAndConstants.emptyModuleList(); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/parser/ParserUtilTest.java b/src/test/java/seedu/duke/parser/ParserUtilTest.java new file mode 100644 index 0000000000..1d1af8141c --- /dev/null +++ b/src/test/java/seedu/duke/parser/ParserUtilTest.java @@ -0,0 +1,40 @@ +package seedu.duke.parser; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ParserUtilTest { + + //@@author ivanchongzhien + @Test + // Test check indices method - providing various valid and invalid inputs + // Invalid inputs : out of bounds, duplicate index, non-integer inputs + void checkIndices_variousInputs_processedArrayList() { + ArrayList expected = new ArrayList<>(); + expected.add(1); + expected.add(2); + expected.add(3); + + int max = 3; + String input1 = "1 2 3"; // happy + assertEquals(expected, ParserUtil.checkIndices(input1, max)); + + String input2 = "2 1 3"; // unsorted + assertEquals(expected, ParserUtil.checkIndices(input2, max)); + + String input3 = "3 2 3 3 2 2 1 2 1 1"; // duplicates, unsorted + assertEquals(expected, ParserUtil.checkIndices(input3, max)); + + String input4 = "10 3 99 2 10909 3 99 99 100 1 10"; // out of bounds, duplicates, unsorted + assertEquals(expected, ParserUtil.checkIndices(input4, max)); + + String input5 = "10 1 -1 -99 -2 10918 2 3 -99 990 990 10 0 10"; // out of bounds, duplicates, unsorted + assertEquals(expected, ParserUtil.checkIndices(input5, max)); + + String input6 = "10 2 3 3 -1 -99 -2 10918 2 3 1 abc"; // out of bounds, duplicates, unsorted, non-integer + assertEquals(expected, ParserUtil.checkIndices(input6, max)); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/storage/WriterTest.java b/src/test/java/seedu/duke/storage/WriterTest.java new file mode 100644 index 0000000000..b4d1324504 --- /dev/null +++ b/src/test/java/seedu/duke/storage/WriterTest.java @@ -0,0 +1,98 @@ +package seedu.duke.storage; + +import org.junit.jupiter.api.Test; +import seedu.duke.TestUtilAndConstants; +import seedu.duke.lesson.Lesson; +import seedu.duke.lesson.LessonType; +import seedu.duke.lesson.TeachingStaff; +import seedu.duke.module.ModuleList; +import seedu.duke.task.Task; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.TestUtilAndConstants.MODULE_CODE_1; + +class WriterTest { + + //@@author 8kdesign + @Test + void writeModule_noContentNoDirectory_instructionOnly() throws IOException { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + ModuleList.setSelectedModule(MODULE_CODE_1); + Writer writer = new Writer(); + writer.writeModule(); + Path reference = Paths.get("src/test/java/seedu/duke/storage/reference/empty_reference.txt"); + Path actual = Paths.get("Data/CS2113T/CS2113T.txt"); + assertEquals(Files.readAllLines(reference), Files.readAllLines(actual)); + } + + @Test + void writeModule_twoTask_instructionAndTask() throws IOException { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + ModuleList.setSelectedModule(MODULE_CODE_1); + Task task1 = new Task("Task1", LocalDate.of(2020,2,20), "Task1Remarks", + false,false); + Task task2 = new Task("Task2", LocalDate.of(2021,12,12), + "", true,true); + ModuleList.getSelectedModule().addTask(task1); + ModuleList.getSelectedModule().addTask(task2); + Writer writer = new Writer(); + writer.writeModule(); + Path reference = Paths.get("src/test/java/seedu/duke/storage/reference/task_only_reference.txt"); + Path actual = Paths.get("Data/CS2113T/CS2113T.txt"); + assertEquals(Files.readAllLines(reference), Files.readAllLines(actual)); + } + + @Test + void writeModule_twoLesson_instructionAndLesson() throws IOException { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + ModuleList.setSelectedModule(MODULE_CODE_1); + Lesson lesson1 = new Lesson(LessonType.LECTURE, "Friday 2pm", "test.com", + new TeachingStaff("","")); + Lesson lesson2 = new Lesson(LessonType.LECTURE, "Weekdays", "", + new TeachingStaff("Name2","Email2")); + ModuleList.getSelectedModule().addLesson(lesson1); + ModuleList.getSelectedModule().addLesson(lesson2); + Writer writer = new Writer(); + writer.writeModule(); + Path reference = Paths.get("src/test/java/seedu/duke/storage/reference/lesson_only_reference.txt"); + Path actual = Paths.get("Data/CS2113T/CS2113T.txt"); + assertEquals(Files.readAllLines(reference), Files.readAllLines(actual)); + } + + @Test + void writeModule_twoLessonTwoTask_allContent() throws IOException { + TestUtilAndConstants.removeFiles(); + ModuleList.loadModuleCodes(); + ModuleList.addModule(MODULE_CODE_1); + ModuleList.setSelectedModule(MODULE_CODE_1); + Lesson lesson1 = new Lesson(LessonType.LECTURE, "Friday 2pm", "test.com", + new TeachingStaff("","")); + Lesson lesson2 = new Lesson(LessonType.LECTURE, "Weekdays", "", + new TeachingStaff("Name2","Email2")); + ModuleList.getSelectedModule().addLesson(lesson1); + ModuleList.getSelectedModule().addLesson(lesson2); + Task task1 = new Task("Task1", LocalDate.of(2020,2,20), + "Task1Remarks", false,false); + Task task2 = new Task("Task2", LocalDate.of(2021,12,12), + "", true,true); + ModuleList.getSelectedModule().addTask(task1); + ModuleList.getSelectedModule().addTask(task2); + Writer writer = new Writer(); + writer.writeModule(); + Path reference = Paths.get("src/test/java/seedu/duke/storage/reference/all_content_reference.txt"); + Path actual = Paths.get("Data/CS2113T/CS2113T.txt"); + assertEquals(Files.readAllLines(reference), Files.readAllLines(actual)); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/storage/reference/all_content_reference.txt b/src/test/java/seedu/duke/storage/reference/all_content_reference.txt new file mode 100644 index 0000000000..0662c48450 --- /dev/null +++ b/src/test/java/seedu/duke/storage/reference/all_content_reference.txt @@ -0,0 +1,36 @@ +CS2113T Data File + +Note for editing: +Please follow the format strictly when adding/editing/removing lessons or tasks. +Do not edit anything above the line, as well as the line. +Please do not use '\n' in any of the entries. +Please do not use '|' except for separating fields. + +Choose 1 of the 4 formats for lessons: +1) lesson | | +2) lesson | | | +3) lesson | | | | +4) lesson | | | | | + +Type: "lecture", "tutorial" or "lab". +Day & time: When the lesson occurs. +Link: Online meeting link for the lesson. +Teaching staff name: Name of teaching staff for the lesson. +Teaching staff email: Email of teaching staff for the lesson. + +Choose 1 of the 2 formats for tasks: +1) task | | | | +2) task | | | | | + +Description: Name/description of the task. +Deadline: In the format DD-MM-YYYY. +Is done: 'T' for true and 'F' for false. +Is graded: 'T' for true and 'F' for false. +Remarks: Additional information for task. + +-------------------------------------------------------------------------------- + +lesson | lecture | Friday 2pm | test.com | | +lesson | lecture | Weekdays | | Name2 | Email2 +task | Task1 | 20-2-2020 | F | F | Task1Remarks +task | Task2 | 12-12-2021 | T | T \ No newline at end of file diff --git a/src/test/java/seedu/duke/storage/reference/empty_reference.txt b/src/test/java/seedu/duke/storage/reference/empty_reference.txt new file mode 100644 index 0000000000..41ac55853a --- /dev/null +++ b/src/test/java/seedu/duke/storage/reference/empty_reference.txt @@ -0,0 +1,32 @@ +CS2113T Data File + +Note for editing: +Please follow the format strictly when adding/editing/removing lessons or tasks. +Do not edit anything above the line, as well as the line. +Please do not use '\n' in any of the entries. +Please do not use '|' except for separating fields. + +Choose 1 of the 4 formats for lessons: +1) lesson | | +2) lesson | | | +3) lesson | | | | +4) lesson | | | | | + +Type: "lecture", "tutorial" or "lab". +Day & time: When the lesson occurs. +Link: Online meeting link for the lesson. +Teaching staff name: Name of teaching staff for the lesson. +Teaching staff email: Email of teaching staff for the lesson. + +Choose 1 of the 2 formats for tasks: +1) task | | | | +2) task | | | | | + +Description: Name/description of the task. +Deadline: In the format DD-MM-YYYY. +Is done: 'T' for true and 'F' for false. +Is graded: 'T' for true and 'F' for false. +Remarks: Additional information for task. + +-------------------------------------------------------------------------------- + diff --git a/src/test/java/seedu/duke/storage/reference/invalid_content_reference.txt b/src/test/java/seedu/duke/storage/reference/invalid_content_reference.txt new file mode 100644 index 0000000000..74e580ece8 --- /dev/null +++ b/src/test/java/seedu/duke/storage/reference/invalid_content_reference.txt @@ -0,0 +1,40 @@ +CS2113T Data File + +Note for editing: +Please follow the format strictly when adding/editing/removing lessons or tasks. +Do not edit anything above the line, as well as the line. +Please do not use '\n' in any of the entries. +Please do not use '|' except for separating fields. + +Choose 1 of the 4 formats for lessons: +1) lesson | | +2) lesson | | | +3) lesson | | | | +4) lesson | | | | | + +Type: "lecture", "tutorial" or "lab". +Day & time: When the lesson occurs. +Link: Online meeting link for the lesson. +Teaching staff name: Name of teaching staff for the lesson. +Teaching staff email: Email of teaching staff for the lesson. + +Choose 1 of the 2 formats for tasks: +1) task | | | | +2) task | | | | | + +Description: Name/description of the task. +Deadline: In the format DD-MM-YYYY. +Is done: 'T' for true and 'F' for false. +Is graded: 'T' for true and 'F' for false. +Remarks: Additional information for task. + +-------------------------------------------------------------------------------- + +lesson | lecture | +lesson | lunch | chicken rice +lesson | lecture | | +lesson | lecture || +lesson | lecture | Weekdays | | Name2 +lesson | lecture | Weekdays | | Name2 | Email2 +task | Task1 | 20-20-2020 | F | F | Task1Remarks +task | Task2 | 12-12-2021 | T | T \ No newline at end of file diff --git a/src/test/java/seedu/duke/storage/reference/invalid_file_reference.txt b/src/test/java/seedu/duke/storage/reference/invalid_file_reference.txt new file mode 100644 index 0000000000..ec4cc002b7 --- /dev/null +++ b/src/test/java/seedu/duke/storage/reference/invalid_file_reference.txt @@ -0,0 +1,24 @@ +CS2113T Data File + +Note for editing: +Please follow the format strictly when adding/editing/removing lessons or tasks. +Do not edit anything above the line, as well as the line. +Please do not use '\n' in any of the entries. +Please do not use '|' except for separating fields. + +Choose 1 of the 4 formats for lessons: +1) lesson | | +2) lesson | | | +3) lesson | | | | +4) lesson | | | | | + +Type: "lecture", "tutorial" or "lab". +Day & time: When the lesson occurs. +Link: Online meeting link for the lesson. +Teaching staff name: Name of teaching staff for the lesson. +Teaching staff email: Email of teaching staff for the lesson. + +Choose 1 of the 2 formats for tasks: +1) task | | | | +2) task | | | | | + diff --git a/src/test/java/seedu/duke/storage/reference/lesson_only_reference.txt b/src/test/java/seedu/duke/storage/reference/lesson_only_reference.txt new file mode 100644 index 0000000000..5673275f23 --- /dev/null +++ b/src/test/java/seedu/duke/storage/reference/lesson_only_reference.txt @@ -0,0 +1,34 @@ +CS2113T Data File + +Note for editing: +Please follow the format strictly when adding/editing/removing lessons or tasks. +Do not edit anything above the line, as well as the line. +Please do not use '\n' in any of the entries. +Please do not use '|' except for separating fields. + +Choose 1 of the 4 formats for lessons: +1) lesson | | +2) lesson | | | +3) lesson | | | | +4) lesson | | | | | + +Type: "lecture", "tutorial" or "lab". +Day & time: When the lesson occurs. +Link: Online meeting link for the lesson. +Teaching staff name: Name of teaching staff for the lesson. +Teaching staff email: Email of teaching staff for the lesson. + +Choose 1 of the 2 formats for tasks: +1) task | | | | +2) task | | | | | + +Description: Name/description of the task. +Deadline: In the format DD-MM-YYYY. +Is done: 'T' for true and 'F' for false. +Is graded: 'T' for true and 'F' for false. +Remarks: Additional information for task. + +-------------------------------------------------------------------------------- + +lesson | lecture | Friday 2pm | test.com | | +lesson | lecture | Weekdays | | Name2 | Email2 diff --git a/src/test/java/seedu/duke/storage/reference/task_only_reference.txt b/src/test/java/seedu/duke/storage/reference/task_only_reference.txt new file mode 100644 index 0000000000..1fc1b6f121 --- /dev/null +++ b/src/test/java/seedu/duke/storage/reference/task_only_reference.txt @@ -0,0 +1,34 @@ +CS2113T Data File + +Note for editing: +Please follow the format strictly when adding/editing/removing lessons or tasks. +Do not edit anything above the line, as well as the line. +Please do not use '\n' in any of the entries. +Please do not use '|' except for separating fields. + +Choose 1 of the 4 formats for lessons: +1) lesson | | +2) lesson | | | +3) lesson | | | | +4) lesson | | | | | + +Type: "lecture", "tutorial" or "lab". +Day & time: When the lesson occurs. +Link: Online meeting link for the lesson. +Teaching staff name: Name of teaching staff for the lesson. +Teaching staff email: Email of teaching staff for the lesson. + +Choose 1 of the 2 formats for tasks: +1) task | | | | +2) task | | | | | + +Description: Name/description of the task. +Deadline: In the format DD-MM-YYYY. +Is done: 'T' for true and 'F' for false. +Is graded: 'T' for true and 'F' for false. +Remarks: Additional information for task. + +-------------------------------------------------------------------------------- + +task | Task1 | 20-2-2020 | F | F | Task1Remarks +task | Task2 | 12-12-2021 | T | T diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..0a9796edde 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,41 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| +-------------------------------------------------------------------------- +What can I do for you today? +-------------------------------------------------------------------------- +GULIO >> +Modules in your list: +-------------------------------------------------------------------------- +GULIO >> +Added CS2113T to the module list. +-------------------------------------------------------------------------- +GULIO >> +Modules in your list: + 1. CS2113T +-------------------------------------------------------------------------- +GULIO >> +Opening CS2113T. -What is your name? -Hello James Gosling + + +Undone tasks: +You have completed all your tasks. +-------------------------------------------------------------------------- +CS2113T >> +Is this task graded? (Y / N) +Added tP Submission to task list. +-------------------------------------------------------------------------- +CS2113T >> +CS2113T closed. +-------------------------------------------------------------------------- +GULIO >> +Which modules would you like to delete? + 1. CS2113T + +Please enter the indices of the modules you would like to delete. +Separate indices with a blank space. +Removed CS2113T from the module list. +-------------------------------------------------------------------------- +GULIO >> +Modules in your list: +-------------------------------------------------------------------------- +GULIO >> +Have a nice day! Bye bye! diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..e0c30217d5 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1,11 @@ -James Gosling \ No newline at end of file +mods +add CS2113T +mods +open CS2113T +add task tP Submission ;; 16-3-2021 +Y +close +del +1 +mods +exit \ No newline at end of file diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh old mode 100644 new mode 100755