diff --git a/README.adoc b/README.adoc index 450054624f48..09131806e2f4 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,12 @@ -= Address Book (Level 4) += ResuMaker + ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] -https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +https://travis-ci.org/CS2103-AY1819S1-W17-1/main[image:https://travis-ci.org/CS2103-AY1819S1-W17-1/main.svg?branch=master["Build Status", link="https://travis-ci.org/CS2103-AY1819S1-W17-1/main"]] +https://ci.appveyor.com/project/anubh-v/main-6mx97[image:https://ci.appveyor.com/api/projects/status/7ge0ty51hokyqkpy/branch/master?svg=true[Build status]] +https://coveralls.io/github/CS2103-AY1819S1-W17-1/main?branch=master[image:https://coveralls.io/repos/github/CS2103-AY1819S1-W17-1/main/badge.svg?branch=master[Coverage Status]] +https://www.codacy.com/app/anubh-v/main?utm_source=github.com&utm_medium=referral&utm_content=CS2103-AY1819S1-W17-1/main&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/a03ee3703b9b464da2449893052e8532[Codacy Badge]] +https://github.com/CS2103-AY1819S1-W17-1/main/blob/master/LICENSE[image:https://img.shields.io/badge/License-MIT-yellow.svg[Licence]] ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,26 +16,23 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* Welcome to ResuMaker - a fast and flexible resume generator! It has a GUI, but most of the user interactions happen using a CLI (Command Line Interface) +* ResuMaker is optimised for computer science professionals, and can help you customise your resumes for specific jobs or requirements. +* Jump to our <> to get started! == Site Map * <> * <> -* <> * <> * <> == Acknowledgements -* Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by +* Some parts of this application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by _Marco Jakob_. -* Libraries used: https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit5[JUnit5] +* Libraries used: https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit5[JUnit5], https://github.com/Steppschuh/Java-Markdown-Generator[Java Markdown Generator] +* Adapted from: https://github.com/se-edu/addressbook-level4[An excellent Address Book application] created by the https://se-edu.github.io/[SE-EDU] initiative +* Application icon created by https://icons8.com/[Icons8]: https://icons8.com/icon/46438/profile[Link to icon] == Licence : link:LICENSE[MIT] diff --git a/_reposense/config.json b/_reposense/config.json new file mode 100644 index 000000000000..9237030e66e6 --- /dev/null +++ b/_reposense/config.json @@ -0,0 +1,30 @@ +{ + "authors": + [ + { + "githubId": "anubh-v", + "displayName": "Anubhav", + "authorNames": ["anubh-v"] + }, + { + "githubId": "jhengy", + "displayName": "Jiang Hengyuan", + "authorNames": ["jhengy"] + }, + { + "githubId": "marvintxd", + "displayName": "Marvin Tan Xu Dong", + "authorNames": ["Marvin Tan", "marvintxd"] + }, + { + "githubId": "ongspxm", + "displayName": "Ong Shu Peng", + "authorNames": ["Metta Ong"] + }, + { + "githubId": "scalarmotion", + "displayName": "Gu Wangfan", + "authorNames": ["scalarmotion", "Gu Wangfan"] + } + ] +} diff --git a/build.gradle b/build.gradle index f8e614f8b49b..777f71412b94 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,12 @@ targetCompatibility = JavaVersion.VERSION_1_9 repositories { mavenCentral() - maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + maven { + url 'https://oss.sonatype.org/content/repositories/snapshots/' + } + maven { + url 'http://dl.bintray.com/steppschuh/Markdown-Generator' + } } checkstyle { @@ -66,6 +71,7 @@ dependencies { implementation group: 'com.sun.xml.bind', name: 'jaxb-impl', version: '2.3.0' implementation group: 'com.sun.xml.bind', name: 'jaxb-core', version: '2.3.0' implementation group: 'javax.activation', name: 'activation', version: '1.1.1' + implementation group: 'net.steppschuh.markdowngenerator', name: 'markdowngenerator', version: '1.3.1.1' testImplementation group: 'junit', name: 'junit', version: '4.12' testImplementation group: 'org.testfx', name: 'testfx-core', version: testFxVersion, { @@ -82,7 +88,7 @@ dependencies { } shadowJar { - archiveName = 'addressbook.jar' + archiveName = 'resumaker.jar' destinationDir = file("${buildDir}/jar/") } @@ -207,9 +213,11 @@ asciidoctor { idprefix: '', // for compatibility with GitHub preview idseparator: '-', 'site-root': "${sourceDir}", // must be the same as sourceDir, do not modify - 'site-name': 'AddressBook-Level4', - 'site-githuburl': 'https://github.com/se-edu/addressbook-level4', - 'site-seedu': true, // delete this line if your project is not a fork (not a SE-EDU project) + + // removes SE-EDU navbar + // Anubhav learnt about this from this group: https://github.com/nus-cs2103-AY1819S1/addressbook-level4/pull/18 + 'site-name': 'ResuMaker', + 'site-githuburl': 'https://github.com/CS2103-AY1819S1-W17-1/main', ] options['template_dirs'].each { diff --git a/copyright.txt b/copyright.txt index 93aa2a39ce25..e806dbc3c789 100644 --- a/copyright.txt +++ b/copyright.txt @@ -7,3 +7,6 @@ Copyright by Susumu Yoshida - http://www.mcdodesign.com/ Copyright by Jan Jan Kovařík - http://glyphicons.com/ - calendar.png - edit.png + +Copyright by Icons8 - https://icons8.com/ +- resume.png: https://icons8.com/icon/46438/profile diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index e647ed1e715a..4e665a4b81c7 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -4,53 +4,54 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + -{empty} + -We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. +ResuMaker was born when a group of students decided that resumes should not require endless fiddling with formats and layouts. +We hope that ResuMaker provides a simpler, and yet more powerful approach to resumes. + + + +We are, https://github.com/CS2103-AY1819S1-W17-1[CS2103-W-17-1], a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<>] +=== Anubhav +image::anubh-v.png[width="150", align="left"] +{empty}[https://github.com/anubh-v[github]] [<>] -Role: Project Advisor +Role: Contextual Awareness + +Responsibility: User Interface, project deliverables, documentation ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Jiang Hengyuan +image::jhengy.png[width="150", align="left"] +{empty}[https://github.com/jhengy[github]] [<>] -Role: Team Lead + -Responsibilities: UI +Role: Data Management + +Responsibility: Logic, system testing, code quality ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Marvin Tan Xu Dong +image::marvintxd.png[width="150", align="left"] +{empty}[http://github.com/marvintxd[github]] [<>] -Role: Developer + -Responsibilities: Data +Role: Resume Template Management + +Responsibility: Storage, documentation, code quality ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Ong Shu Peng +image::ongspxm.png[width="150", align="left"] +{empty}[http://github.com/ongspxm[github]] [<>] -Role: Developer + -Responsibilities: Dev Ops + Threading +Role: Data Filtering + +Responsibility: Commons, integration, git expert ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Gu Wangfan +image::scalarmotion.png[width="150", align="left"] +{empty}[http://github.com/scalarmotion[github]] [<>] -Role: Developer + -Responsibilities: UI +Role: Resume Generation + +Responsibility: Model, integration, testing, markdown expert ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index 5de5363abffd..29bdd2dfba87 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -2,6 +2,7 @@ :site-section: ContactUs :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level4/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. -* *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +We hope you enjoy using ResuMaker. We're always open to new ideas - feel free to connect with us, on the following platforms: + +* *Bug reports, Suggestions*: Noticed a bug or have a great idea for a feature? Post in our https://github.com/CS2103-AY1819S1-W17-1/main/issues[issue tracker] to let us know. +* *Contributing*: Want to get involved? We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index ea58481e4740..7b51c5a49abe 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += ResuMaker - Developer Guide :site-section: DeveloperGuide :toc: :toc-title: @@ -12,10 +12,18 @@ ifdef::env-github[] :note-caption: :information_source: :warning-caption: :warning: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103-AY1819S1-W17-1/main/tree/master -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `CS2103-W-17-1`      Since: `August 2018`      Licence: `MIT` +== Introduction +Welcome to the Developer Guide for ResuMaker - a fast and flexible resume generator! +This Guide contains essential information for those seeking to maintain, or enhance ResuMaker. + +Developers new to ResuMaker can refer to <> to get started. + +Experienced developers can find advanced details in <> and <>. + +[[setting-up]] == Setting up === Prerequisites @@ -34,6 +42,7 @@ Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plu === Setting up the project in your computer +Follow these steps to set up the project in your computer for development: . Fork this repo, and clone the fork to your computer . Open IntelliJ (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project dialog first) @@ -48,6 +57,7 @@ Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plu This will generate all resources required by the application and tests. === Verifying the setup +To verify that you have setup the project correctly: . Run the `seedu.address.MainApp` and try a few commands . <> to ensure they all pass. @@ -93,10 +103,7 @@ Having both Travis and AppVeyor ensures your App works on both Unix-based platfo ==== Getting started with coding -When you are ready to start coding, - -1. Get some sense of the overall design by reading <>. -2. Take a look at <>. +When you are ready to start coding, get some sense of the overall design by reading <>. == Design @@ -123,12 +130,12 @@ The `.pptx` files used to create diagrams in this document can be found in the l The rest of the App consists of four components. -* <>: The UI of the App. -* <>: The command executor. +* <>: Displays the App's User Interface. +* <>: Executes Commands. * <>: Holds the data of the App in-memory. * <>: Reads data from, and writes data to, the hard disk. -Each of the four components +Each of these four components: * Defines its _API_ in an `interface` with the same name as the Component. * Exposes its functionality using a `{Component Name}Manager` class. @@ -147,7 +154,7 @@ The _Sequence Diagram_ below shows how the components interact for the scenario image::SDforDeletePerson.png[width="800"] [NOTE] -Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. +Note how the `Model` component simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. @@ -212,11 +219,6 @@ The `Model`, * exposes an unmodifiable `ObservableList` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. * does not depend on any of the other three components. -[NOTE] -As a more OOP model, we can store a `Tag` list in `Address Book`, which `Person` can reference. This would allow `Address Book` to only require one `Tag` object per unique `Tag`, instead of each `Person` needing their own `Tag` object. An example of how such a model may look like is given below. + - + -image:ModelClassBetterOopDiagram.png[width="800"] - [[Design-Storage]] === Storage component @@ -235,97 +237,550 @@ The `Storage` component, Classes used by multiple components are in the `seedu.addressbook.commons` package. +[[Implementation]] == Implementation This section describes some noteworthy details on how certain features are implemented. -// tag::undoredo[] -=== Undo/Redo feature -==== Current Implementation +// tag::tags[] +=== Tags and Categories +Tags and categories are single-word keywords tied to individual entries. Each `ResumeEntry` can be classified under one `Category`, but can be associated with multiple `Tag`. -The undo/redo mechanism is facilitated by `VersionedAddressBook`. -It extends `AddressBook` with an undo/redo history, stored internally as an `addressBookStateList` and `currentStatePointer`. -Additionally, it implements the following operations: +==== Categories +Category related functions are mainly contained in the `seedu.address.model.category` package, which includes the `Category` class and its relevant `CategoryManager`. `CategoryManager` is used by the `ModelManager` (`seedu.address.model.ModelManager`) to filter the list of entries by categories. -* `VersionedAddressBook#commit()` -- Saves the current address book state in its history. -* `VersionedAddressBook#undo()` -- Restores the previous address book state from its history. -* `VersionedAddressBook#redo()` -- Restores a previously undone address book state from its history. +Filtering can be done by passing the relevant `Predicate` into `CategoryManager` through `CategoryManger.setPredicate()`. The relevant filtered list can be obtained by subsequently calling `CategoryManager.getList()`. -These operations are exposed in the `Model` interface as `Model#commitAddressBook()`, `Model#undoAddressBook()` and `Model#redoAddressBook()` respectively. +==== CategoryManager +To use `CategoryManager` to filter out relevant entries, there are a few main functions to keep in mind: -Given below is an example usage scenario and how the undo/redo mechanism behaves at each step. +* `setList(List entries)`: sets the source list of entries to filter +* `getList()`: returns `unmodifiableObservableList` of relevant entries +* `setPredicate(Predicate predicate)`: sets the filtering criteria for the list of entries +* `mkPredicate(Predicate predicate, String category)`: returns a predicate that builds onto the given predicate to filter by given category as well +* `mkPredicate(String category)`: return a predicate that filters entries by the category -Step 1. The user launches the application for the first time. The `VersionedAddressBook` will be initialized with the initial address book state, and the `currentStatePointer` pointing to that single address book state. +===== Example: Listing entries from a certain category `tag ls ~work` +`CategoryManager` can be used to filter out certain entries for display in the UI. -image::UndoRedoStartingStateListDiagram.png[width="800"] +---- +CategoryManager categoryManager = new CategoryManager(); +Predicate predicate = categoryManager.mkPredicate("work"); +modelManager.updateFilteredEntryList(predicate); +---- -Step 2. The user executes `delete 5` command to delete the 5th person in the address book. The `delete` command calls `Model#commitAddressBook()`, causing the modified state of the address book after the `delete 5` command executes to be saved in the `addressBookStateList`, and the `currentStatePointer` is shifted to the newly inserted address book state. +* A `Predicate` will be generated by the command using `CategoryManager.mkPredicate()` +* This predicate is then passed along to `Model.filteredEntryList` for the that the display can be updated +* The diagram below illustrate the flow of the program if one were to implement use it in a `Command` -image::UndoRedoNewCommand1StateListDiagram.png[width="800"] +// TODO: update img to show mkPredicate +.Program flow for listing entries +image::categoryManager_example_listEntry.png[] -Step 3. The user executes `add n/David ...` to add a new person. The `add` command also calls `Model#commitAddressBook()`, causing another modified address book state to be saved into the `addressBookStateList`. +===== Example: Filtering entries to be written to resume +`CategoryManager` can be used to extract out the relevant resume entries to be included in the specific sections of the resume. -image::UndoRedoNewCommand2StateListDiagram.png[width="800"] +---- +TagManager tagManager = new TagManager(); +CategoryManager categoryManager = new CategoryManager(); -[NOTE] -If a command fails its execution, it will not call `Model#commitAddressBook()`, so the address book state will not be saved into the `addressBookStateList`. +categoryManager.setList(modelManager.getFullList()); +categoryManager.setPredicate(categoryManager.mkPredicate(category)); -Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the `undo` command. The `undo` command will call `Model#undoAddressBook()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous address book state, and restores the address book to that state. +tagManager.setList(categoryManager.getList()); +tagManager.setPredicate(tagManager.mkPredicate(tags)); +List filtered = tagManager.getList(); +---- -image::UndoRedoExecuteUndoStateListDiagram.png[width="800"] +* The full list obtained from `Model.filteredList` will be passed into the `CategoryManger` through `CategoryManager.setList()` +* Based on the filters on different sections of the template, a specific `Predicate` will be created for that section +* The `Predicate` created will be passed into the `CategoryManager` through `CategoryManager.setPredicate()` +* The list of entries to be printed will be retrieved through `CategoryManager.getList()` +* If there is further filtering to be done on tags, the same set of steps will be done on `TagManager` +* The diagram below illustrate the flow of the program if one were to implement use it in a `Command` -[NOTE] -If the `currentStatePointer` is at index 0, pointing to the initial address book state, then there are no previous address book states to restore. The `undo` command uses `Model#canUndoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the undo. +.Program flow for filtering out entries for resume generation +image::categoryManager_example_template.png[] -The following sequence diagram shows how the undo operation works: +==== Predicates +The `CategoryManager` was written to help developers filter out desired predicates easily. As such, the `CategoryManager.mkPredicate()` is written to return a `Predicate` which can be passed into other functions for the filtering process, be it for display or resume generation process. -image::UndoRedoSequenceDiagram.png[width="800"] +There are two general forms of the function, `mkPredicate` and `mkPredicate(Predicate entries, String category)`. -The `redo` command does the opposite -- it calls `Model#redoAddressBook()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the address book to that state. +The first form of the function returns a predicate which returns true if the `ResumeEntry.getCategory().cateName == category`. In short, it will filter out entries of a particular category. -[NOTE] -If the `currentStatePointer` is at index `addressBookStateList.size() - 1`, pointing to the latest address book state, then there are no undone address book states to restore. The `redo` command uses `Model#canRedoAddressBook()` to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo. +The second form of the function (`mkPredicate(Predicate predicate, String category)`) extends the existing predicate and implement the category checking process on top of it. For the new predicate to return true, the `ResumeEntry` must fulfill the first `Predicate` and also be of a particular specified category. + +===== Example: Filtering entries using both tags and category `tag ls ~work #java #python` +This function is used to implement more complex filters, for example, when entries needs to be filtered by both tags and categories in `tag ls`. + +---- +TagManager tagManager = new TagManager(); +CategoryManager categoryManager = new CategoryManager(); + +Predicate predicate = categoryManager.mkPredicate(category); +predicate = tagManager.mkPredicate(predicate, tags); +modelManager.updateFilteredEntryList(predicate); +---- + +* A `Predicate` will be generated by the command using `CategoryManager.mkPredicate()` +* This predicate is then passed along to `TagManager.mkPredicate()` to be extended to include tag filtering +* The combined predicate is passed to `Model.filteredList` for the display to be updated +* The diagram below illustrate the flow of the program if one were to implement use it in a `Command` + +.Program flow for filtering out entries for both tag and category +image::categoryManager_example_tagls.png[] + +==== Design considerations +There are 2 main ways to implement entries filtering: within `CategoryManager` itself or using `CategoryManager` to generate `Predicate` to be used for filtering. Below are some evaluation as to why and when each of the methods may be relevant. + +===== Alternative 1: Handles all the entries filtering within CategoryManager +This is implemented through `setList()`, `setPredicate()` and `getList()`. The full list of entries is passed in, and the filtered list of entries is returned. This will typically be used in the filtering of the entries in the resume generation process. + +This method is much cleaner, encapsulating all the filtering process within `CategoryManger`. But if we are sticking to the current implementation of displaying the UI from a `FilteredList`, this approach may not be appropriate, hence, the second alternative implementation, which gracefully handles this case. + +**Example:** + +- `setList(List)` to set the full list of entries to filter from +- `setPredicate(mkPredicate(category))` to filter list based on category +- `getList()` to return list of filtered entries + +===== Alternative 2: Using CategoryManager to build the desired Predicate +This is implemented through `mkPredicate()`. The function is used to build upon a given `Predicate`. which can be passed into `ModelManager.updateFilteredEntryBook()` to filter the displayed list of entries in the UI. + +This method allow us to utilize the original UI mechanism for updating the displaying using a predicate, instead of having to alter the list of entries over and over again. + +**Example:** + +- `Predicate` obtained that does some preliminary filtering (e.g. filtering based on tags) +- `mkPredicate(predicate, category)` extends the original predicate to further filter by category +- `ModelManager.updateFilteredEntryList(predicate)` to update view of displayed entries + +===== Current Implementation +Currently, a mixture of these functions are implemented. This allows the developers to use `CategoryManager` in both manner, whichever method they deem more appropriate. + +// end::tags[] + +// tag::template[] +=== Templates + +A template specifies the format of the generated resume. It specifies the sections in the resume, and which entries should be included under each section based on a set of tags. + +==== Template file +This section describes the implementation of the Template feature. +Templates files are user-written text files, with each line in the following format: +---- +[Category Heading]:~[Category Tag]:[Tag Groups] +---- + +The list of tags in `[Tag Groups]` can be treated as a sum of products form, where a `&` represents AND and space represents OR. +If no tags are specified, all entries with the `[Category Tag]` will be included. + + +Template files are written by the user and loaded into the application using the `loadtemplate` command. + +==== Template object structure + +The diagram below shows the structure of a `Template` object. A `Template` contains an `ArrayList` of `TemplateSection`, +where each contains a title to be displayed and two predicates for filtering entries based on their category and tags. + +.Template object UML class diagram +image::Template_UML.png[width="600"] + + +The following code snippet shows how the `[Tag Groups]` in the template file are parsed into a `Predicate`. + +---------- +private Predicate createTagPredicate(String tags) { + if (tags.equals("")) { // no filters + return entry -> true; + } + + String[] tagGroups = tags.split(OR_DELIMITER); -Step 5. The user then decides to execute the command `list`. Commands that do not modify the address book, such as `list`, will usually not call `Model#commitAddressBook()`, `Model#undoAddressBook()` or `Model#redoAddressBook()`. Thus, the `addressBookStateList` remains unchanged. + ArrayList> expressions = new ArrayList<>(); -image::UndoRedoNewCommand3StateListDiagram.png[width="800"] + for (int i = 0; i < tagGroups.length; i++) { + ArrayList expression = new ArrayList<>();] + for (String tag : tagGroups[i].split(AND_DELIMITER)) { + expression.add(new Tag(tag)); + } + expressions.add(expression); + } -Step 6. The user executes `clear`, which calls `Model#commitAddressBook()`. Since the `currentStatePointer` is not pointing at the end of the `addressBookStateList`, all address book states after the `currentStatePointer` will be purged. We designed it this way because it no longer makes sense to redo the `add n/David ...` command. This is the behavior that most modern desktop applications follow. + return entry -> { + Set entryTags = entry.getTags(); + boolean fitsOneExpression = false; + for (ArrayList expression : expressions) { -image::UndoRedoNewCommand4StateListDiagram.png[width="800"] + boolean fitsAllInExpression = true; + for (Tag tag : expression) { -The following activity diagram summarizes what happens when a user executes a new command: + if (!entryTags.contains(tag)) { + fitsAllInExpression = false; + break; + } + } -image::UndoRedoActivityDiagram.png[width="650"] + if (fitsAllInExpression) { + fitsOneExpression = true; + break; + } + } + return fitsOneExpression; + }; +} + +private Predicate createCategoryPredicate(String category) { + return entry -> entry.getCategory().equals(new Category(category)); +} +---------- + +==== Template loading sequence + +The diagram below shows how the components interact when the user attempts to load a template using the `loadtemplate template1.txt` command. + +.Component interactions for loadtemplate template1.txt command (part 1) +image::Template_SD1.png[width="600"] + +The diagram below shows how `EventsCenter` reacts, raising a `TemplateLoadRequestedEvent` which prompts `Storage` to attempt to load the template from file. +If the loading was successful, it the text file will be parsed into a `Template` object, and passed into the `TemplateLoadedEvent`. This event will be handled in the `Model`, which will store the `Template` retrieved from the event. +Otherwise, a `TemplateLoadingExceptionEvent` will be raised and handled by `Model` as well. + +.Component interactions for loadtemplate template1.txt command (part 2) +image::Template_SD2.png[width="600"] + +The diagram below provides a detailed view of the sequence of events taking place within the Storage component. +When `loadTemplate()` is called, `StorageManager` calls `loadTemplate()` on its `TxtTemplateStorage`, +which then creates a new `Template` object. +`loadTemplate()` will the repeatedly call `addSection()` on the created `Template` and parse the text file until it has been fully read. +The returned `Template` will then be passed into a `TemplateLoadedEvent`. +If an exception occurs while reading, such as if the file format is invalid, a `TemplateLoadingExceptionEvent` is raised instead. + +.Detailed sequence for template loading in Storage component +image::TemplateStorage Sequence Diagram.png[width="800"] ==== Design Considerations +===== Aspect: When to load the template + +* **Alternative 1 (current choice):** Have a dedicated `loadtemplate` command and only load it when the command is called +** Pros: +*** Application can store an active Template which can be displayed in the UI +*** Clearer and more logical separation of functionality between `make` and `loadtemplate` +** Cons: More work to implement +* **Alternative 2:** The Template filepath is specified as a parameter to the `make` command and loaded when `make` is called, right before the resume is generated +** Pros: One less command has to be implemented +** Cons: There is no active Template; every `make` command will load the Template again even if the same filepath is given, making the application do redundant work + + +// end::template[] + +// tag::entryManagement[] +=== Entry Management +This section describes the implementation of features related to managing entries in ResuMaker, and explains the underlying classes and supporting data structures. + +==== Construct Entry Related Classes + +Below is the class diagram of classes under the package `seedu.address.model.entry`, which lays the foundation for the implementation of <> + +.Class diagram for entry related classes +image::classDiagramForEntry.png[width="800"] + +* Current Implementation +.. As shown in the diagram, all data of an added entry in ResuMaker is encapsulated as a class `ResumeEntry`, which is composed of four other classes: namely one `Category`, one `EntryInfo`, one `EntryDescription` and multiple instances of `Tag`. +.. ResuMaker extends a `Taggable` interface which allows manipulation of tags associated with itself. + + +* Design Consideration +.. `Taggable` Interface + +Provides an additional abstraction layer for any class that needs to access methods that `ResumeEntry` overrides to implement `Taggable`. This enforces Interface Segregation Principle in which unrelated classes have limited knowledge about `ResumeEntry`. + +.. Encapsulation of `EntryInfo` + +... Instead of lumping `title`, `subtitle` and `duration` in `ResumeEntry`, encapsulating the three into `EntryInfo` provides another layer of abstraction. +... Not all entries have the three information, a <> does not contain title, subtitle or duration. `EntryInfo` helps to differentiate <> from <> using `isMinorEntry()`. + +.. Encapsulation using `EntryDescription` + +... As opposed to putting an entry description as a `String` field in `ResumeEntry`, encapsulating it in `EntryDescription` provides another layer of abstraction. +... `EntryDescription` contains `List` that allows for easy modification of a specific segment of the description of a particular entry, i.e. allows users to edit a particular line of description in that entry. + +==== Responsive Display of Expanded Entry +This enhancement enables the Graphic User Interface to display the description of an entry responsively to any modification of an entry. + +* Current Implementation + +.. The high level interaction between different components follows the same workflow as any other commands. + +Diagram(part a) below illustrates the high level interaction between different components when an `addBullet` or `editBullet` command is executed. +.. A noteworthy point is that it makes use of event driven design to allow UI to respond to Logic. +`EventCenter` acts as the receiver of the three events raised by Logic and sends them to the respective handlers of these events. +For `addBullet` command, the handlers of events raised are UI and Storage. Please refer to diagram(part b) below for more detail. + +.Sequence diagram for adding a bullet description to an entry (part a) +image::SequenceDiagramAddBullet_1.png[width="800"] +.Sequence diagram for adding a bullet description to an entry (part b) +image::SequenceDiagramAddBullet_2.png[width="800"] + + + +* Design Consideration + +.. Minimizing the amount of code to be added by tapping on the existing utility + +Given how well-established event driven approach is, it will be more convenient to adopt it to minimize the addition of lines of code. +Taking the alternative would mean extra code to be added to establish some form of reference of UI in Logic. + +.. Decoupling between Logic and UI + +Rather than asking Logic to interact directly with UI to request for changes in UI, which increases coupling between the two, `EventCenter` acts as the +"middle man" to minimize coupling. + + + + +//end::entryManagement[] + + + +// tag::resume[] +=== Resume Generation +This section describes the implementation of the resume generation feature, explains the underlying classes and +supporting data structures, justifies a key design decision and highlights areas that are open to extension. + +==== Resume Structure + +The `Resume` contains a list of `ResumeSections`, +each comprising a title and a list of `ResumeEntries` associated with it +(as stipulated by the `Template` used to generate the `Resume`). +The class structure is shown by the following class diagram: -===== Aspect: How undo & redo executes +.Class diagram for Resume related classes +image::resumeClassDiagram.png[width="800"] +<<< +The creation of a `Resume` by a `make` command is shown by this sequence diagram: -* **Alternative 1 (current choice):** Saves the entire address book. -** Pros: Easy to implement. -** Cons: May have performance issues in terms of memory usage. -* **Alternative 2:** Individual command knows how to undo/redo by itself. -** Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). -** Cons: We must ensure that the implementation of each individual command are correct. +.Sequence diagram for Resume creation +image::generateResumeSequenceDiagram.png[width="800"] -===== Aspect: Data structure to support the undo/redo commands -* **Alternative 1 (current choice):** Use a list to store the history of address book states. -** Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both `HistoryManager` and `VersionedAddressBook`. -* **Alternative 2:** Use `HistoryManager` for undo/redo -** Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase. -** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two different things. -// end::undoredo[] +==== Storage Management -// tag::dataencryption[] -=== [Proposed] Data Encryption +When a resume is to be saved to a file, the `ModelManager` raises a `ResumeSaveEvent`, which encapsulates +the complete `Resume` as well as the specified file `Path`. This event is captured by the `StorageManager`, +which passes the data to the `MarkdownResumeStorage` class. The `Resume` object is then converted to a markdown `String` +by the `MarkdownConverter` utility class, after which it is written to a file (with the specified name) by the `MdUtil` class. + -_{Explain here how the data encryption feature will be implemented}_ +<<< -// end::dataencryption[] +==== Markdown Conversion +To transform this abstract object representation of the `Resume` into concrete text, +the `MarkdownConverter` utility class progressively converts each level of the `Resume` +(from the titles, information and descriptions of each individual `ResumeEntry`, +to a `ResumeSection` of `ResumeEntries` and finally the full `Resume`) +into a `String` containing its formatted markdown representation. + +An external link:https://github.com/Steppschuh/Java-Markdown-Generator[Java Markdown Generator] library +was used to handle the generation of the markdown `String` in `MarkdownConverter`, +as it was built on a `Builder` pattern which made it easier and more organised to progressively +generate markdown text for each part of the resume and combine it all together in the end. + +===== Design Consideration + +There were some considerations regarding how and where the conversion of +`Resume` objects to markdown `Strings` would be done. We had to decide between these alternative approaches: + +====== Approach 1: + +All markdown conversion is done in a class (`MarkdownConverter`) separate from the `Resume` and `ResumeEntry` packages. +This ensures that the "abstract" data classes in the `Model` are completely separated from the conversion to "concrete" +markdown text in accordance to the Single Responsibility Principle and also allows for extension by adding a different +class to format the markdown differently. However, this causes heavy coupling in `MarkdownConverter`, as any change to +the internal structure of `Resume` and `ResumeEntry` classes would require extensive changes across multiple methods in +`MarkdownConverter`. + +====== Approach 2: + +Every class that could be converted to markdown would contain a `toMarkdown` method, possibly implementing a +`Markdownable` interface. This would ensure that any change to the structure or formatting of a particular class would +only require changes to the `toMarkdown` method of that class. However, this exposes the "abstract" data classes in the +`Model` to the implementation of using markdown text in `Storage` and does not allow for the extension of adding a +different markdown layout without replacing the existing markdown formatting. + +====== Decision + +Ultimately, we decided that it was more important to enforce the separation between the `Model` and `Storage` modules +as well as allow for potential further extension, so we chose Approach 1. + +<<< + +==== Possible Extensions + +===== Changing Markdown Layouts + +If you wish to modify the markdown layout produced by ResuMaker, look into the overloaded `toMarkdown` method +within `MarkdownConverter`. The various versions of this method are implemented using the +link:https://github.com/Steppschuh/Java-Markdown-Generator[Java Markdown Generator] external library and designed to be +easy to understand and modify. + +You are recommended to create a new subclass of `MarkdownConverter` and `@Override` its `toMarkdown` method +with your own versions, so as to preserve the original functionality of the application for reference. + +===== Adding File Formats + +While we have chosen to implement conversion into markdown, you may prefer a different file format and want to avoid +the hassle of converting markdown to other formats outside the application. To this end, you can extend ResuMaker by +adding support for saving `Resumes` to a new format. + +To achieve this, you would have to design your own class implementing the `ResumeStorage` interface. This new class would +also need new utility methods to handle conversion of the `Resume` into another text format. +The diagram below illustrates the key additions necessary to implement saving resumes in a new file format: + +.Class diagram for Resume Storage extension +image::resumeStorageExtensionClassDiagram.png[width="800"] + + +// end::resume[] + +// tag::contextualAwareness[] +=== Contextual Awareness + +==== Key Terms +Below are some terms that are used often when discussing Contexual Awareness. + +Please review their definitions in the Glossary before reading further. + + +* <> +* <> +* <> +* <> +* <> + +==== Overview +Contextual Awareness enables ResuMaker to: + + +* Create pre-filled ResumeEntries for standard <> (university modules, well known competitions, etc). +* Understand slang and partial phrases in the user's input. + +The flowchart below shows the overall flow taken by an user when working with the Contextual Awareness feature. + + +.Workflow for a user using the Awareness features +image::awarenessFlow.png[width=250, height=250] + +The diagram shows that there are 2 functions provided as part of the Contextual Awareness feature. +One is a real-time function that updates the User Interface **as the user types (without pressing `Enter`) ** + +The other functions adds a ResumeEntry to the internal EntryBook, and is triggered only when the user presses `Enter`. + +Both functions require the following steps to be executed. + +1. Guess the name of the Event that the user was referring to, when he / she typed ``. +2. Matching the guessed Event name, with a pre-filled ResumeEntry. + +In Step 1 (`Guess` step), the `` is called a `expression`. +In Step 2 (`Match` step), the guessed Event name is called a `possibleEventName`. + +The following sections examine these 2 steps, in the context of the **second function**. +The real-time function is similar, and is discussed later. + +==== Guessing an Event name, Matching it with a ResumeEntry, and Adding the Matched ResumeEntry +The Awareness class exposes a `getPossibleEventName` method that accepts a `expression` and returns a `possibleEventName`. The `getPossibleEventName` corresponds to Step 1 mentioned in <>. + + +Awareness also exposes a `getContextualEntry` method that accepts a `possibleEventName` and returns a matching `ResumeEntry` (if it exists). +Hence, the `possibleEventName` serves as a unique `key` to identify a `ResumeEntry`. +The `getContextualEntry` method corresponds to Step 2 mentioned in <>. + + +The information above is illustrated in the following sequence diagram. + + +.Sequence diagram showing how Awareness finds a Matching ResumeEntry +image::awarenessHighLevelSeq.png[width=400, height=350] + +As seen, `ContextCommand` is responsible for (indirectly) calling Awareness. Note that `ContextCommand` leverages on the existing `AddEntryCommand` to add the Matching ResumeEntry to the EntryBook. + + +==== Guessing an Event name, and Matching it with a ResumeEntry (Internal implementation) +The previous section considered Awareness as a black box. This section describes how the `Guess` and `Match` steps are implemented internally in Awareness. + +The following diagram shows the internal structure of Awareness. + +.Class structure for Awareness +image::awarenessStructure.png[width=350, height=200] + +As seen, an Awareness object will hold a reference to a Dictionary and a TreeMap. + +In fact, Awareness implements `getPossibleEventName` by calling `getPossibleEventName` on the Dictionary. To find a matching resume entry, this `possibleEventName` is used as a key to locate the corresponding ResumeEntry in the `nameToEntryMappings` TreeMap. + +==== Internal implementation of Dictionary + +As seen in the diagram, the Dictionary supports the following operations: + +* Register a mapping between a particular piece of slang, and a full phrase (e.g. "cs" maps to "computer science"). +* Register a full phrase to be tracked by the Dictionary. +* Given an expression, guess the name of the event that the expression refers to, and return this `possibleEventName`. + +When a client attempts to register mappings / full phrases, defensive checks are done and invalid mappings / full phrases are rejected. + +The User Guide provides details on the rules that determine the validity of a mapping / full phrase. + +* To form the `possibleEventName`, the Dictionary breaks the given `expression` into a stream of space delimited tokens. + +* Then, all empty string tokens are filtered out. +* Next, each token is treated as a slang, and the internal `mappings` HashMap is searched to determine if the token should be expanded into a full phrase. +* Next, each token is treated as a partial phrase and internal `allFullPhrases` TreeSet is searched to determine if the token should be expanded into a full phrase. +* Finally, all tokens are joined into a single string. +* This gives us a string of `full phrases`, which is our `possibleEventName`. + +This algorithm is described in the following code snippet: +``` + public String getPossibleEventName(String expression) { + requireNonNull(expression); + + return Arrays.stream(tokenize(expression)) + .filter(token -> !isEmptyString(token)) + .map(slang -> getFullPhraseFromSlang(slang)) + .map(partialPhrase -> getFullPhraseFromPartialPhrase(partialPhrase)) + .collect(Collectors.joining(SPACE)); + } +``` +==== Design Choices + +===== ContextCommand +The ContextCommand adds a ResumeEntry to the Entrybook by creating and calling `execute` on an AddEntryCommand object. + +Pros: Re-uses existing code + +Cons: Couples ContextCommand with AddEntryCommand. The writer of ContextCommand would need to be aware about any future changes done to AddEntryCommand (e.g. changes in API). + +Alternatives: + +ContextCommand should take advantage of polymorphism. Instead of creating and executing a AddEntryCommand object, it should obtain an instance of a Command and execute that. This would mean ContextCommand only needs to know about Command. + +To implement the alternative, we could create a CommandSupplier class, which exposes a `supply` method that takes an instance of the Model, and returns a Command. +ContextCommand would be constructed by passing a CommandSupplier to its constructor. ContextCommandParser would assume the responsibility of creating the correct ContextCommand by passing in an appropriate CommandSupplier. + +When it is time to `execute`, ContextCommand would call the `supply` method of CommandSupplier, and call `execute` on the supplied Command. + +Decision made: + +The alternative may be valid in another project, **where ContextCommand is required to provide many types of functionality**. + +For this project, creating the CommandSupplier class is a violation of the You-Ain't-Gonna-Need-It (YAGNI) principle. + +===== Passing Dictionary into Awareness for construction +The Awareness class is constructed by passing it a Dictionary. + +Alternatives: + +* Create the Dictionary instance within the constructor of Awareness, rather than accepting it as an argument. + +Pros: + +* The Awareness features become __more extensibile__. Future projects can pass in different types of Dictionaries without having to rewrite the Awareness code. +* Achieves better separation of concerns, where the Dictionary effectively acts as a blackbox that Awareness need not know about. +* Increased testability as the dependancy (Dictionary object) _can be better controlled_ since it is passed into the constructor. + +===== Lack of copying in Awareness constructor. +The Dictionary and TreeMap passed into the Awareness constructor are stored directly as members of the Awareness object. + +Alternatives: + +* __**Defensively**__ copy the Dictionary and TreeMap. This ensures that future modifications to the Dictionary / TreeMap does not change the behaviour of Awareness. + +Justification for my approach: + +In summary, defensive copying was not appropriate for the intended vision and possible use cases for Awareness. + +* Original vision for Awareness - It is true that Awareness can be indirectly modified by the callee, by modifying the Dictionary and TreeMap. However, Awareness was not envisioned to be used directly by the callee. After all, the callee already has access to both the Dictionary and TreeMap (eliminating the need for an Awareness class). It is expected that the callee will, rather than using the Awareness object, pass the Awareness object to another component of the application - which then uses Awareness as a black box. In our application, for example, the XML serialisation classes create the Awareness object and pass it to the Storage component. +* Increased flexiblity for callee - The callee can keep a reference to the Dictionary and TreeMap, and keep updating them over time. This will easily update the Awareness class. Such a situation may be needed if a `WebDictionary` was implemented, where a `WebDictionary` would dynamically add mappings via a web service. +In this case, the user of the Awareness object could continue to use the same instance, while enjoying the new updates made to its internal components. +* The Dictionary class has sufficient defensive checks to prevent a callee from creating invalid conditions within the Awareness class. + +// end::contextualAwareness[] === Logging We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. @@ -532,361 +987,337 @@ A project often depends on third-party libraries. For example, Address Book depe a. Include those libraries in the repo (this bloats the repo size) + b. Require developers to download those libraries manually (this creates extra work for developers) -[[GetStartedProgramming]] + [appendix] -== Suggested Programming Tasks to Get Started +== Product Scope -Suggested path for new programmers: +*Target user profile*: -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <>. +* SoC students with work/project experience/CS skills who are applying for programmes/ internships/jobs/etc -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <> explains how to go about adding such a feature. +*Value proposition*: -[[GetStartedProgramming-EachComponent]] -=== Improving each component +* Easy to use: CLI makes things fast and simple +* Flexible: Able to customise resume for specific job requirements +* SOC-aware: Save time with built in support for School of Computing programmes -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). -[discrete] -==== `Logic` component +[appendix] +== User Stories -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -[TIP] -Do take a look at <> before attempting to modify the `Logic` component. +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... +|`* * *` |Student |Save pieces of information about my relevant experience |Generate resumes without having to type the same things every time -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* Hints -** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. -* Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. -** Add new tests for each of the aliases that you have added. -** Update the user guide to document the new aliases. -** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. -**** +|`* * *` |Student |Generate a resume with only information relevant to a particular field of CS | Conveniently customise my resume for different applications. -[discrete] -==== `Model` component +|`* * *` |Student |Have my Work Experiences and Projects sorted by relevance to job requirements, on my resume |Ensure my resume is relevant to the employer -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. +|`* * *` |Student |Search for entries using filters |Check my saved entries conveniently -[TIP] -Do take a look at <> before attempting to modify the `Model` component. +|`* * *` |Student |Generate resumes in common file formats like PDF |My resumes are accepted by everyone -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. -** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. -* Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. -** Add new tests for each of the new public methods that you have added. -** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. -**** +|`* * *` |Student |Update or delete my personal information conveniently |Ensure that the resumes the tool generates are up to date -[discrete] -==== `Ui` component +|`* * *` |Student |Have my contact information and other “standard” information automatically added to my resumes | Focus on crafting the more valuable information in my resume -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. +|`* *` |Student |Save custom combinations of tags |Filter the exact entries I want to be put in my resume -[TIP] -Do take a look at <> before attempting to modify the `UI` component. +|`* *` |Student |Receive feedback from my CLI commands |Be sure of the results of my commands -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. -+ -**Before** -+ -image::getting-started-ui-tag-before.png[width="300"] -+ -**After** -+ -image::getting-started-ui-tag-after.png[width="300"] -+ -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** - -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). -+ -**Before** -+ -image::getting-started-ui-result-before.png[width="200"] -+ -**After** -+ -image::getting-started-ui-result-after.png[width="200"] -+ -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. -** You can write two different kinds of tests to ensure that the functionality works: -*** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. -** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. -*** Do read the commits one at a time if you feel overwhelmed. -**** - -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. -+ -**Before** -+ -image::getting-started-ui-status-before.png[width="500"] -+ -**After** -+ -image::getting-started-ui-status-after.png[width="500"] -+ -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* Solution -** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. -** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** +|`* *` |SoC Student |My resume to automatically contain descriptions of common SOC programmes (Orbital, NOC, etc) |Save time instead of having to input standard information manually +|`* *` |SoC Student |Pick standard SOC awards (Honour Roll, etc) from a list rather than type them out manually |Save time instead of having to input standard information manually -[discrete] -==== `Storage` component +|`* *` |Student |Have common NUS acronyms auto-translated into their full forms|So that I may type information using acronyms conveniently -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. +|`* *` |Student with existing work|Import my existing projects from Github |Save time typing them out manually -[TIP] -Do take a look at <> before attempting to modify the `Storage` component. +|`*` |Student with multiple computers |Export my saved information and import it on another computer |Generate resumes wherever I go -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. -+ -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** +|`*` |Student |Undo commands and revert any changes made |Quickly recover from making a typo -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` +|`*` |Student |Easily learn how to use the tool from a built-in tutorial |Get started and work more efficiently -By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. +|`*` |Student |Auto-fill information about special SoC programs | Saves the effect to input it by myself -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. +|`*` |Student |Conveniently contact people I have worked with in the past | Request testimonials or keep in touch for networking purposes -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` -Examples: +|======================================================================= -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. +_{More to be added}_ -==== Step-by-step Instructions +[appendix] +== Use Cases -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. +(For all use cases below, the *System* is `ResuMaker` and the *Actor* is the `Student`, unless specified otherwise) -**Main:** +[discrete] +=== Add Personal Information of the user -. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. +*MSS* -**Tests:** +1. User enters command to set his contact details +2. System prompts user for his mobile number, email address and GitHub username +3. User enters in his contact information +4. System prompts user for a confirmation +5. User confirms his data +6. System saves contact information -. Add `RemarkCommandTest` that tests that `execute()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. ++ +Use case ends. -===== [Step 2] Logic: Teach the app to accept 'remark' arguments -Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` +*Extensions* -**Main:** +* 3a. User enters invalid personal information -. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. -. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. ++ +[none] +** 3a1. System prompts user to enter valid contact information ++ +Step 3 repeats as many times as necessary -**Tests:** +[discrete] -. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. -. Add `RemarkCommandParserTest` that tests different boundary values -for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. +=== Add a generic entry or major entry in the resuMaker -===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` -Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. +*MSS* -**Main:** +1. User enters command to create a genric entry ( Skills / Awards), or major entry (Experience / Education) +2. System saves the Entry to the disk -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. ++ +Use case ends. -**Tests:** -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +[discrete] -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +=== Delete an Entry under a specific category -**Main:** +*MSS* -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +1. User filter the entries using specific tags, +returning an indexed list of entries (UC04) +2. User delete the corresponding entries in the list by indicating associated index for each entry to be deleted +3. System saves the Entry to the disk ++ +Use case ends. -**Tests:** +*Extensions* -. Add test for `Remark`, to test the `Remark#equals()` method. +* *a. User input command using the wrong syntax +** *a1. System prompts the user to re-enter the command +** *a2. User re-enters the command +* 3a. User inputs an index out of range +** 3a.1 System prompts for invalid input and asks user to re-enter the command +** 3a.2 return to step 4 for the user to re-enter the command -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -**Main:** +[discrete] -. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `addressBook.xml` so that the application will load the sample data when you launch it.) +=== edit an Entry under a specific index -===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class -We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. +*MSS* -**Main:** +1. User searches for a set of Entries, using some tags (UC04) +2. System displays an indexed list of Entries matching the search tags (UC04) +3. User enters command to edit Entries, and specifies an index and also the updated information +4. System displays the updated Entry for reference +5. System saves the Entry to the disk -. Add a new Xml field for `Remark`. ++ +Use case ends. -**Tests:** +*Extensions* -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +* *a. User input command using the wrong syntax +** *a1. System prompts the user to re-enter the command +** *a2. User re-enters the command +* 3a. User inputs an index out of range +** 3a.1 System prompts for invalid input and asks user to re-enter the command +** 3a.2 return to step 4 for the user to re-enter the command -===== [Step 6b] Test: Add withRemark() for `PersonBuilder` -Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -**Tests:** +[discrete] +=== Add information about a SoC / NUS Event (e.g. Hack N Roll, Student Exchange, Independent Work module) -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +*MSS* -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +1. User enters command to create new project, together with the “nus” keyword and the name of his Event +2. System recognises the name of the Event, as well as its type (Project, Work Experience, Skill, etc) +3. System prompts user to fill in further details of his Event, but also pre-fills some fields (such as duration, nature of Event) +4. User finalises the Event details +5. System prompts user to tag the Event, but also pre-selects some tags +6. User finalises the tags applicable to the Event +7. System saves the Event as an Entry ++ +Use case ends. -**Main:** +*Extensions* -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +* 1a. User enters slang or an acronym instead of the full name of his Event (e.g. "ug research opps" instead of Undergraduate Research Opportunites Programme) ++ +[none] +** 1a1. System matches the slang / acronym to the full Event name in the database, if possible. ++ +Use case continues from Step 2. -**Tests:** +* 2a. System does not recognises the Project as a SOC project ++ +[none] +** 2a1. System informs the User that no default information is available, and all information must be entered manually (UC09) ++ +Use case ends. -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +[discrete] +=== Add an SoC Award entry -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. +*MSS* -**Main:** +1. User enters command to view list of SOC Awards +2. System displays an indexed list of SOC Awards +3. User selects a particular SOC Award by specifying its index. +4. System prompts user to enter further details about the SOC Award (e.g. year) +5. User completes data entry +6. System saves the Award entry ++ +Use case ends. -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +*Extensions* -**Tests:** +* 3a. User does not find his award in the list ++ +[none] +** 3a1. User enters command to manually create an Award entry. ++ +Use case ends. -. Update `RemarkCommandTest` to test that the `execute()` logic works. +[discrete] +=== View a template -==== Full Solution +*MSS* -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +1. User enters command to view desired template using its filename +2. System displays contents of the config +3. User continues using the system ++ +Use case ends. -[appendix] -== Product Scope +*Extensions* +* 2a. Entered config name does not match any existing config. ++ +[none] +** 2a1. System displays a warning. ++ +Use case returns to Step 1. -*Target user profile*: +[discrete] +=== Adding tag to an entry -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +*MSS* -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +1. User enters command to list entries +2. User enters command to add tag to entry +3. System displays the entry with updated tags. -[appendix] -== User Stories +*Extensions* -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +* 1a. User wants to check existing tags first to ensure new tag is not duplicate +** 1a1. User enters commands to view list of tags and its corresponding entries +* 3a. User decides not to create new tag +** 3a1. User enters command to remove tag from entry +** 3a2. System display entry with updated tags. +Use case ends. +* 4a. New tag is a duplicate of an existing tag +** 4a1. System ignores duplicated tag +** 4a2. System displays entry with updated tags ++ +Use case ends. -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|Priority |As a ... |I want to ... |So that I can... -|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App +[discrete] +=== Viewing all active tags in resume generation + +*MSS* + +1. User enters command to list all tags +2. System displays all active tags and their entries' placement in the resume. -|`* * *` |user |add a new person | +[discrete] +=== Filtering out entries of a specific tag -|`* * *` |user |delete a person |remove entries that I no longer need +*MSS* -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +1. User enters command to list all entries containing a specific tag. +2. System displays all selected tags and their corresponding entries. -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident +*Extensions* -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +* 1a. No entries found for tag specified by user +** 1a1. System outputs an empty list -_{More to be added}_ +[discrete] +=== Retagging tags of a specific entry. -[appendix] -== Use Cases +*MSS* -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +1. User enters command to retag a specific entry. +2. System displays all selected tags and their corresponding entries. + +*Extensions* + +* 1a. No entries with the specified index +** 1a1. System displays an error message to alert user erroneous input. + +[discrete] +=== Removing tags from a specific entry. + +*MSS* + +1. User enters command to remove all tags from specific entry. +2. System displays selected entry void of tags. + +*Extensions* + +* 1a. No entries with the specified index +** 1a1. System displays an error message to alert user erroneous input. [discrete] -=== Use case: Delete person +=== Generate a resume *MSS* -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. Student enters command to create resume and specifies a template file, by providing the file path to the template file +2. System saves a markdown file containing a resume based on the template and the entries specified by the template + Use case ends. *Extensions* +* 1a. User does not specify a template file ++ [none] -* 2a. The list is empty. +** 1a1. System uses a pre-defined default template file instead + -Use case ends. - -* 3a. The given index is invalid. +Use case resumes from Step 2. +* 1b. User specifies a template file using an alias instead of a filepath + [none] -** 3a1. AddressBook shows an error message. +** 1b1. System reads application settings to match the file’s alias with its filepath + -Use case resumes at step 2. - -_{More to be added}_ +Use case resumes from Step 2. [appendix] == Non Functional Requirements . Should work on any <> as long as it has Java `9` or higher installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. -. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. - -_{More to be added}_ +. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. +. The primary mode of input should be a Command Line Interface. +. All application data must be stored locally, and in a human editable file +. There should be no installer for the application. +. Resume generation should be fast (within 2 minutes). [appendix] == Glossary @@ -894,25 +1325,70 @@ _{More to be added}_ [[mainstream-os]] Mainstream OS:: Windows, Linux, Unix, OS-X -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others +[[entries]] Entry:: +Contact, Education, Work Experience, Project, Skill or Award -[appendix] -== Product Survey +[[event]] Event:: +Work Experience, Project, or Award -*Product Name* +[[work-experience]] Work Experience:: +Any professional work (internship, freelance, job) -Author: ... +[[project]] Project:: +Any work done by student outside school/work requirements -Pros: +[[skill]] Skill:: +Proficiency in any language / framework / tool relevant to Computer Science professionals + +[[award]] Award:: +Any award / recognition + +[[contact]] Contact:: +Student’s email address, mobile phone number and GitHub Profile + +[[education]] Education:: +University name, degree programme name, Year of Study + +[[standard-information]] Standard Information:: +Student’s Contact and Education Details + +[[template]] Template:: +Sets of tags for each Section of a resume - to be used to custom generate a resume + +[[template-file]] Template File:: +A plaintext file containing a Template + +[[category]] Category:: +A single-word keyword starting with `~` (e.g. `~work`, `~project`) -* ... -* ... +[[tag]] Tag:: +A single-word keyword starting with `#` -Cons: +[[slang]] Slang:: +A single word that is an alias for a <>. (e.g. `cs` is slang for `computer science`) -* ... -* ... +[[partial-phrase]] Partial phrase:: +An incomplete word. (e.g. `comp`) + +[[full-phrase]] Full phrase:: +Single or multiple complete words. (e.g. `computer`, `computer science`) + +[[expression]] Expression:: +A combination of slang, partial phrases or full phrases. +(e.g. 'computer sci ug research proj') + +In Backus-Naur form, an Expression is defined: + +` ::= | | | ` + +[[entryManagement]] Entry Management:: +A set of features related to managing entries. Namely: +`addEntry`, `deleteEntry`, `addBullet`, `deleteBullet`. + +[[majorEntry]] major entry:: +An entry that contains information such as title, subtitle and duration. It is usually used for education, professional experience, projects, etc. +For example, to add a particular major entry, execute `addEntry ~work #java t/The Source Enterprise s/Java Programmer intern d/ May 2010 - Aug 2010`. + +[[minorEntry]] minor entry:: +An entry that does not contain entry information such as title, subtitle and duration. It is usually created for content like awards or certification. For example, to add a particular minor entry, one can execute `addEntry ~awards #java` [appendix] == Instructions for Manual Testing @@ -920,7 +1396,7 @@ Cons: Given below are instructions to test the app manually. [NOTE] -These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. +These instructions only provide a starting point for testers to work on; testers are requested to perform _more exploratory testing_. === Launch and Shutdown @@ -928,7 +1404,7 @@ These instructions only provide a starting point for testers to work on; testers .. Download the jar file and copy into an empty folder .. Double-click the jar file + - Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum. + Expected: Shows the GUI with _no resume entries_. The window size may not be optimum. . Saving window preferences @@ -936,26 +1412,51 @@ These instructions only provide a starting point for testers to work on; testers .. Re-launch the app by double-clicking the jar file. + Expected: The most recent window size and location is retained. -_{ more test cases ... }_ +=== Add a resume entry + +. Adding a resume entry via `addEntry` + +.. Test case: + +`addEntry ~work #python #data t/DataKinetics Corp s/Dashboard visualisation expert d/May 2010 - August 2015` + +Expected: First resume entry is added to the list of entries. Details of the added entry shown in the status message. Timestamp in the status bar is updated. + +.. Test case: + +`addEntry ~work #python #data t/DataKinetics Corp s/Dashboard visualisation expert d/May 2010 - August 2015` + +`addEntry ~work #python #data t/DataKinetics Corp s/Dashboard visualisation expert d/May 2010 - August 2015` + +Expected: No entry is created. The status message indicates that this entry already exists. + +.. Other `addEntry` commands to try: `addEntry`, `addEntry t/Google s/Intern d/Jan 2017 - Apr 2019` + + Expected: No entry created, with the status message indicating that the command format is invalid. -=== Deleting a person +. Adding a resume entry via `nus` -. Deleting a person while all persons are listed +.. Test case: + +`nus ta ma1101r` + +Expected: Similar to the `addEntry` positive test case. +.. Test case: + +`nus ta ma1101r` + +`nus teaching asst ma1101r` + +Expected: Similar to the `addEntry` duplicate entry test case. -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. -.. Test case: `delete 1` + - Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. -.. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -.. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + - Expected: Similar to previous. +=== Manipulating tags -_{ more test cases ... }_ +.. Test case: + +`nus ta ma1101r` + +`tag add [index of created entry] #nus` + +Expected: Adds the tag `nus` to the entry. -=== Saving data +.. Test case: + +`nus cs2103t` + +`tag retag [index of created entry] ~education #software_eng #java #module` + +Expected: Retags the entry with `software_eng`, `java`, and `module` -. Dealing with missing/corrupted data files +=== Using templates -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +.. Test case: + +Create a template.txt file with the correct format +`loadtemplate template.txt` + +Expected: The template should be displayed in the application. -_{ more test cases ... }_ +.. Test case: + +`loadtemplate invalidpath.txt` + +Expected: No template is loaded. The status message indicates that the file cannot be found. diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc deleted file mode 100644 index 83cda0927226..000000000000 --- a/docs/LearningOutcomes.adoc +++ /dev/null @@ -1,266 +0,0 @@ -= Learning Outcomes -:site-section: LearningOutcomes -:toc: macro -:toc-title: -:toclevels: 1 -:sectnums: -:sectnumlevels: 1 -:imagesDir: images -:stylesDir: stylesheets -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master - -After studying this code and completing the corresponding exercises, you should be able to, - -toc::[] - -''' - -== Use High-Level Designs `[LO-HighLevelDesign]` - -Note how the <> describes the high-level design using an _Architecture Diagrams_ and high-level sequence diagrams. - -*Resources* - -* https://se-edu.github.io/se-book/architecture/[se-edu/se-book: Design: Architecture] -* https://se-edu.github.io/se-book/design/introduction/multilevelDesign/[se-edu/se-book: Design: Introduction: Multi-Level Design] - -''' - -== Use Event-Driven Programming `[LO-EventDriven]` - -Note how the <> uses events to communicate with components without needing a direct coupling. Also note how the link:{repoURL}/src/main/java/seedu/address/commons/core/index/EventsCenter.java[`EventsCenter.java`] acts as an event dispatcher to facilitate communication between event creators and event consumers. - -*Resources* - -* https://se-edu.github.io/se-book/architecture/architecturalStyles/eventDriven/[se-edu/se-book: Design: Architecture: Architecture Styles: Event-Driven Architectural Style] - -''' - -== Use API Design `[LO-ApiDesign]` - -Note how components of AddressBook have well-defined APIs. For example, the API of the `Logic` component is given in the link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -image:LogicClassDiagram.png[width="800"] - -*Resources* - -* https://se-edu.github.io/se-book/reuse/apis/[se-edu/se-book: Implementation: Reuse: APIs] - -''' - -== Use Assertions `[LO-Assertions]` - -Note how the AddressBook app uses Java ``assert``s to verify assumptions. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/assertions/[se-edu/se-book: Implementation: Error Handling: Assertions] - -=== Exercise: Add more assertions - -* Make sure assertions are enabled in your IDE by forcing an assertion failure (e.g. add `assert false;` somewhere in the code and run the code to ensure the runtime reports an assertion failure). -* Add more assertions to AddressBook as you see fit. - - -''' - -== Use Logging `[LO-Logging]` - -Note <>. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/logging/[se-edu/se-book: Implementation: Error Handling: Logging] - -=== Exercise: Add more logging - -Add more logging to AddressBook as you see fit. - - -''' - -== Use Defensive Coding `[LO-DefensiveCoding]` - -Note how AddressBook uses the `ReadOnly*` interfaces to prevent objects being modified by clients who are not supposed to modify them. - -*Resources* - -* https://se-edu.github.io/se-book/errorHandling/defensiveProgramming/[se-edu/se-book: Implementation: Error Handling: Defensive Programming] - -=== Exercise: identify more places for defensive coding - -Analyze the AddressBook code/design to identify, - -* where defensive coding is used -* where the code can be more defensive - -''' - -== Use Build Automation `[LO-BuildAutomation]` - -Note <>. - -*Resources* - -* https://se-edu.github.io/se-book/integration/buildAutomation/what/[se-edu/se-book: Implementation: Integration: Build Automation: What] - -=== Exercise: Use gradle to run tasks - -* Use gradle to do these tasks: Run all tests in headless mode, build the jar file. - -=== Exercise: Use gradle to manage dependencies - -* Note how the build script `build.gradle` file manages third party dependencies such as ControlsFx. Update that file to manage a third-party library dependency. - - -''' - -== Use Continuous Integration `[LO-ContinuousIntegration]` - -Note <>. (https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]]) - -*Resources* - -* https://se-edu.github.io/se-book/integration/buildAutomation/continuousIntegrationDeployment/[se-edu/se-book: Implementation: Integration: Build Automation: CI & CD] - -=== Exercise: Use Travis in your own project - -* Set up Travis to perform CI on your own fork. - - -''' - -== Use Code Coverage `[LO-CodeCoverage]` - -Note how our CI server <>. (https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]]) After <> for your project, you can visit Coveralls website to find details about the coverage of code pushed to your repo. https://coveralls.io/github/se-edu/addressbook-level4?branch=master[Here] is an example. - -*Resources* - -* https://se-edu.github.io/se-book/testing/testCoverage/[se-edu/se-book: QA: Testing: Test Coverage] - -=== Exercise: Use the IDE to measure coverage locally - -* Use the IDE to measure code coverage of your tests. - -''' - -== Apply Test Case Design Heuristics `[LO-TestCaseDesignHeuristics]` - -The link:{repoURL}/src/test/java/seedu/address/commons/util/StringUtilTest.java[`StringUtilTest.java`] -class gives some examples of how to use _Equivalence Partitions_, _Boundary Value Analysis_, and _Test Input Combination Heuristics_ to improve the efficiency and effectiveness of test cases testing the link:../src/main/java/seedu/address/commons/util/StringUtil.java[`StringUtil.java`] class. - -*Resources* - -* https://se-edu.github.io/se-book/testCaseDesign/[se-edu/se-book: QA: Test Case Design] - -=== Exercise: Apply Test Case Design Heuristics to other places - -* Use the test case design heuristics mentioned above to improve test cases in other places. - -''' - -== Write Integration Tests `[LO-IntegrationTests]` - -Consider the link:{repoURL}/src/test/java/seedu/address/storage/StorageManagerTest.java[`StorageManagerTest.java`] class. - -* Test methods `prefsReadSave()` and `addressBookReadSave()` are integration tests. Note how they simply test if The `StorageManager` class is correctly wired to its dependencies. -* Test method `handleAddressBookChangedEvent_exceptionThrown_eventRaised()` is a unit test because it uses _dependency injection_ to isolate the SUT `StorageManager#handleAddressBookChangedEvent(...)` from its dependencies. - -Compare the above with link:{repoURL}/src/test/java/seedu/address/logic/LogicManagerTest.java[`LogicManagerTest`]. Some of the tests in that class (e.g. `execute_*` methods) are neither integration nor unit tests. They are _integration + unit_ tests because they not only check if the LogicManager is correctly wired to its dependencies, but also checks the working of its dependencies. For example, the following two lines test the `LogicManager` but also the `Parser`. - -[source,java] ----- -@Test -public void execute_invalidCommandFormat_throwsParseException() { - ... - assertParseException(invalidCommand, MESSAGE_UNKNOWN_COMMAND); - assertHistoryCorrect(invalidCommand); -} ----- - -*Resources* - -* https://se-edu.github.io/se-book/testing/testingTypes/[se-edu/se-book: QA: Testing: Testing Types] - -=== Exercise: Write unit and integration tests for the same method. - -* Write a unit test for a high-level method somewhere in the code base (or a new method you wrote). -* Write an integration test for the same method. - -''' - -== Write System Tests `[LO-SystemTesting]` - -Note how tests below `src/test/java/systemtests` package (e.g link:{repoURL}/src/test/java/systemtests/AddCommandSystemTest.java[`AddCommandSystemTest.java`]) are system tests because they test the entire system end-to-end. - -*Resources* - -* https://se-edu.github.io/se-book/testing/testingTypes/[se-edu/se-book: QA: Testing: Testing Types] - -=== Exercise: Write more system tests - -* Write system tests for the new features you add. - -''' - -== Automate GUI Testing `[LO-AutomateGuiTesting]` - -Note how this project uses TextFX library to automate GUI testing, including <>. - -=== Exercise: Write more automated GUI tests - -* Covered by `[LO-SystemTesting]` - -''' - -== Apply Design Patterns `[LO-DesignPatterns]` - -Here are some example design patterns used in the code base. - -* *Singleton Pattern* : link:{repoURL}/src/main/java/seedu/address/commons/core/EventsCenter.java[`EventsCenter.java`] is Singleton class. Its single instance can be accessed using the `EventsCenter.getInstance()` method. -* *Facade Pattern* : link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager.java`] is not only shielding the internals of the Storage component from outsiders, it is mostly redirecting method calls to its internal components (i.e. minimal logic in the class itself). Therefore, `StorageManager` can be considered a Facade class. -* *Command Pattern* : The link:{repoURL}/src/main/java/seedu/address/logic/commands/Command.java[`Command.java`] and its sub classes implement the Command Pattern. -* *Observer Pattern* : The <> used by this code base employs the Observer pattern. For example, objects that are interested in events need to have the `@Subscribe` annotation in the class (this is similar to implementing an `\<>` interface) and register with the `EventsCenter`. When something noteworthy happens, an event is raised and the `EventsCenter` notifies all relevant subscribers. Unlike in the Observer pattern in which the `\<>` class is notifying all `\<>` objects, here the `\<>` classes simply raises an event and the `EventsCenter` takes care of the notifications. -* *MVC Pattern* : -** The 'View' part of the application is mostly in the `.fxml` files in the `src/main/resources/view` folder. -** `Model` component contains the 'Model'. However, note that it is possible to view the `Logic` as the model because it hides the `Model` behind it and the view has to go through the `Logic` to access the `Model`. -** Sub classes of link:{repoURL}/src/main/java/seedu/address/ui/UiPart.java[`UiPart`] (e.g. `PersonListPanel` ) act as 'Controllers', each controlling some part of the UI and communicating with the 'Model' (via the `Logic` component which sits between the 'Controller' and the 'Model'). -* *Abstraction Occurrence Pattern* : Not currently used in the app. - -*Resources* - -* https://se-edu.github.io/se-book/designPatterns/[se-edu/se-book: Design: Design Patterns] - -=== Exercise: Discover other possible applications of the patterns - -* Find other possible applications of the patterns to improve the current design. e.g. where else in the design can you apply the Singleton pattern? -* Discuss pros and cons of applying the pattern in each of the situations you found in the previous step. - -=== Exercise: Find more applicable patterns - -* Learn other _Gang of Four_ Design patterns to see if they are applicable to the app. - -''' - -== Use Static Analysis `[LO-StaticAnalysis]` - -Note how this project uses the http://checkstyle.sourceforge.net/[CheckStyle] static analysis tool to confirm compliance with the coding standard. - -*Resources* - -* https://se-edu.github.io/se-book/qualityAssurance/staticAnalysis/[se-edu/se-book: QA: Static Analysis] - -=== Exercise: Use CheckStyle locally to check style compliance - -* Install the CheckStyle plugin for your IDE and use it to check compliance of your code with our style rules (given in `/config/checkstyle/checkstyle.xml`). - -''' - -== Do Code Reviews `[LO-CodeReview]` - -* Note how some PRs in this project have been reviewed by other developers. Here is an https://github.com/se-edu/addressbook-level4/pull/147[example]. -* Also note how we have used https://www.codacy.com[Codacy] to do automate some part of the code review workload (https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]]) - - -=== Exercise: Review a PR - -* Review PRs created by team members. diff --git a/docs/UiMockup.pptx b/docs/UiMockup.pptx new file mode 100644 index 000000000000..563346885fb7 Binary files /dev/null and b/docs/UiMockup.pptx differ diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 7e0070e12f49..428ecce979bd 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += ResuMaker - User Guide :site-section: UserGuide :toc: :toc-title: @@ -12,147 +12,353 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2103-AY1819S1-W17-1/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `CS2103-W-17-1` Since: `August 2018` Licence: `MIT` +// tag::intro[] == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <> to get started. Enjoy! +Do you find the need to maintain multiple versions of your resume? + +Perhaps you find yourself reorganising your resumes before making a job application? + +If so - welcome to ResuMaker: a *fast and flexible resume generator aimed at computer science students*. + +Unlike most resume generation webapps, ResuMaker tailors your resume to fit the specific requirements of a job. + +It can also pick up contextual awareness about your world - with out-of-the-box support for the National University of Singapore's School of Computing. + +Interested? Jump to the <> to get started. + + +With ResuMaker, __let your skills document themselves.__ +//end::intro[] == Quick Start +To get started with using ResuMaker: . Ensure you have Java version `9` or later installed in your Computer. -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. -. Double-click the file to start the app. The GUI should appear in a few seconds. +. Download the latest `resumaker.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for ResuMaker. +. Double-click the file to start the app. The graphical user interface should appear in a few seconds. + + +.A typical user's screen, immediately after application startup. image::Ui.png[width="790"] -+ -. Type the command in the command box and press kbd:[Enter] to execute it. + + +[start=5] +. Type your command in the command box and press kbd:[Enter] to execute it. + e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. -. Some example commands you can try: +. Try out the following examples: -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list +* `addEntry `~work` `#java t/The Source Enterprise s/Data Science Intern d/ May 2010 - Dec 2010` Adds a new entry, classified under `work`, with the given title, subheader and duration. +* `addEntry` `~project` `#java #entrepreneurship t/InnovFest Unbound Hackathon s/Best Fintech Hack d/ Jan 2011 - Feb 2011` +* `tag ls` `#java` * *`exit`* : exits the app -. Refer to <> for details of each command. +. Edit your User Preferences file as shown in <>. + +. Download the `awareness.xml` file from link:{repoURL}/releases[here]. + +. Refer to <> for the full details of all commands. + +// tag::wangfanUGDiag[] +Here is an illustration of the usual workflow of using ResuMaker. + +[[workflowfig]] +.Typical workflow of ResuMaker +image::ugTypicalFlowDiagram.png[width="250"] +// end::wangfanUGDiag[] + +[[Setup]] +== Setup + +Here are some important steps to set up ResuMaker to work better for you. +// tag::wangfanUG[] +=== Adding user particulars +You need to add your personal particulars to the user preferences file in order for ResuMaker to display +them at the top of your resume (see <> for an example of how it will look). Here's how you do it: + +. Open the `preferences.json` file which should have been created in the same folder as the ResuMaker application. +You can do this using Notepad or any other text editor you prefer. + +. Look for the section that starts with `"userParticulars" : {`. It should look like this: + +``` +"userParticulars" : { + "name" : "John Doe", + "mobile" : "+65 91234567", + "email" : "johndoe@example.com", + "address" : "412 Kent Ridge Road, #05-03" + } +``` +[start=3] +. Edit the name, phone number, email address and home address within the quotes. +For instance, if your name is Mary Jane, edit the line `"name" : "John Doe"` to `"name" : "Mary Jane"`. +Take note that you cannot leave any of the particulars fields empty. + +. Save the `preferences.json` file and restart ResuMaker. Try generating a resume to check that the changes +have been reflected. + +// end::wangfanUG[] [[Features]] == Features +Here are the features available in ResuMaker: ==== *Command Format* -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `addEntry ~CATEGORY, [t/TITLE], [s/SUBHEADER], [d/DURATION], [#TAGNAME]...`, `CATEGORY`, `TITLE` , `SUBHEADER`, `DURATION` and `TAGNAME` are parameters which can be used as `addEntry ~work #INTERNSHIP t/The Source Enterprise s/Software Engineering Intern d/Jan 2012 - June 2012`. +* Items in square brackets are optional e.g `~CATEGORY [t/TITLE] [s/SUBHEADER] [d/DURATION] [#TAG]...` can be used as `~work t/The Source Enterprise s/Java programmer d/ Jan 2012 - Dec 2014 #JAVA` or `~awards`. +* Items with `…`​ after them can be used multiple times including zero times e.g. `[#TAG]...` can be used as `{nbsp}` (i.e. 0 times), `#JAVA`, `#JAVA #Software Engineering`, `#JAVA #Software Engineering #AI` etc. +* Parameters can be in any order e.g. if the command specifies `[t/TITLE] [s/SUBHEADER]`, `[s/SUBHEADER] [t/TITLE]` is also acceptable. +* All commands are case sensitive, e.g. `deleteEntry 1` is valid but `deleteentry 1` would not be. ==== === Viewing help : `help` +Displays a list of all commands available. + Format: `help` -=== Adding a person: `add` +// tag::hengyuanUG[] +=== Adding an entry: `addEntry` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Adds an entry to ResuMaker. + +Format: `addEntry ~CATEGORY , [[t/TITLE], [s/SUBHEADER], [d/DURATION]], [#TAGNAME]...` [TIP] -A person can have any number of tags (including 0) +An entry need not have associated title, subheader and duration. +An entry can have any number of tags (including 0). +An entry contains description, which can be added using addBullet as a separate command. +All parameter input from the user must be alphanumeric and can be separated by the following characters: space, `-`, or `()`. Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `addEntry ~work #java t/The Source Enterprise s/Java Programmer intern d/ May 2010 - Aug 2010` +* `addEntry ~work t/Carousell #work #web #mobile #backend s/intern d/Nov 2017 - Jan 2018` +* `addEntry ~education #uni t/National University of Singapore s/Bachelor of Computing in Computer Science(Honours) d/2017 - 2021` +* `addEntry ~awards #java` -=== Listing all persons : `list` -Shows a list of all persons in the address book. + -Format: `list` -=== Editing a person : `edit` +=== Adding a bullet point to an entry: `addBullet` -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Adds a bullet point to the end of the description of an entry at index ENTRYINDEX in ResuMaker. +The user needs to execute `tag ls` command to display a filtered list of entries and select a particular entry +to add a bullet description to. + +Format: `addBullet ENTRYINDEX CONTENTTOADD` **** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the displayed person list. The index *must be a positive integer* 1, 2, 3, ... -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. +* `ENTRYINDEX` refers to the index of the displayed entry list from executing `tag ls` +* `CONTENTTOADD` refers to a line of description that the user wants to add to an entry **** Examples: -* `edit 1 p/91234567 e/johndoe@example.com` + -Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` + -Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +* `tag ls` + +`addBullet 1 attained Best Financial Hack Award` + +Adds the bullet point "attained Best Financial Hack Award" to the description of the 1st entry. +* `tag ls ~education` + +`addBullet 1 maintained a CAP of 4.95 on average throughout the four years` + +Adds the bullet point "maintained a CAP of 4.95 on average throughout the four years" to the description of the 1st entry under the education category. + -=== Locating persons by name: `find` +=== Viewing a particular entry : `selectEntry` -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +Displays detailed description of the entry in ResuMaker at the specified index on the panel display. + +Format: `selectEntry INDEX` **** -* The search is case insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +* `INDEX` refers to the index of the displayed entry list from executing `tag ls` +* Detailed description will be displayed as an indexed list of all bullet description in that entry **** Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `selectEntry 2` +* `selectEntry 0` + + +=== Editing an entry : `editEntryInfo` + +Edits entry info fields of an existing entry in ResuMaker, i.e title, sub-header and duration. + +Format: `editEntryInfo INDEX [t/TITLE] [s/SUBHEADER] [d/DURATION]` + +**** +* Edits the entry at the specified `INDEX`. The index refers to the index number shown in the displayed entry list. The index *must be a positive integer* 1, 2, 3, ... +* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +* When editing tags, the existing tags of the entry will be removed i.e adding of tags is not cumulative. +* You can remove all the entry's tags by typing `#` without specifying any tags after it. +* This command does not allow editing description of the entry; to do so, use the `editBullet` command. +**** + +Example: + +* `editEntryInfo 1 t/R Company s/Data Science Intern` + +Edits the entry at index 1 by replacing its title as R Company and subtitle as +Data Science Intern. -=== Deleting a person : `delete` -Deletes the specified person from the address book. + -Format: `delete INDEX` +=== Deleting an entry: `deleteEntry` + +Deletes the entry at the specified index. + +Format: `deleteEntry INDEX` **** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index *must be a positive integer* 1, 2, 3, ... +* `INDEX` refers to the index number shown in the displayed entry list. +* `INDEX` *must be a positive integer* 1, 2, 3, ... **** + Examples: -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +* `tag ls` + +`deleteEntry 2` + +Deletes the 2nd entry in ResuMaker. +* `tag ls ~education` + +`deleteEntry 1` + +Deletes the 1st entry displayed under education category. + +//end::hengyuanUG[] + +=== Editing a bullet point: `editBullet` [Coming in v2.0] + +Edits a bullet description of an entry in ResuMaker. INDEX refers to the index of a particular bullet description. + +Format: `editBullet ENTRYINDEX BULLETINDEX EDITEDCONTENT` + +**** +* Before executing this command, the user needs to execute `selectEntry` +* `ENTRYINDEX` refers to the index number shown in the displayed entry list +* `BULLETINDEX` refers to the index number shown of a particular bulleted description the displayed Entry +* `EDITEDCONTENT` refers to new content that the user wants to replace the old one with +* Both parameters *must be a positive integer* 1, 2, 3, ... +**** + +Example: -=== Selecting a person : `select` +* `selectEntry` + +`editBullet 1 1 implement scalable application for data visualization using java` + +Edits the 1st entry by replacing its 1st bullet description with "implement scalable application for data visualization using java". + + +=== Deleting a bullet: `deleteBullet` [Coming in v2.0] + +Deletes the bullet of a particular entry at the specified index. + +Format: `deleteBullet ENTRYINDEX BULLETINDEX` -Selects the person identified by the index number used in the displayed person list. + -Format: `select INDEX` **** -* Selects the person and loads the Google search page the person at the specified `INDEX`. -* The index refers to the index number shown in the displayed person list. -* The index *must be a positive integer* `1, 2, 3, ...` +* Before executing this command, the user needs to execute `selectEntry` +* `ENTRYINDEX` refers to the index number shown in the displayed entry list +* `BULLETINDEX` refers to the index number shown of a particular bulleted description the displayed Entry +* Both parameters *must be a positive integer* 1, 2, 3, ... **** + +Examples: + +* `tag ls` + +`selectEntry` + +`deleteBullet 2 1` + +Deletes the 1st bullet of the 2nd entry in ResuMaker. +* `tag ls ~education` + +`selectEntry` + +`deleteBullet 1 1` + +Deletes the 1st bullet of the 1st entry displayed under education category of ResuMaker. + + +// tag::anubhavUG[] +=== Adding pre-filled Resume Entries (Contextual Events): `nus` +There are times when we just don't want to manually enter every piece of required information into a computer program. +After all, shouldn't some things __just be common knowledge?__ + +With ResuMaker, you can auto-populate resume Entries if ResuMaker already knows about them! +We call such Entries __Contextual Events__. + +Format: `nus EVENT_NAME` + +[TIP] +The `EVENT_NAME` can be a combination of an Event's **full name** (mathematics and computer science double degree programme), **acronyms** (math - cs ddp), or even +**partially matching phrases** (math - comp sci double deg prog) + Examples: -* `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +* `nus cs2103t` + +Creates a Resume Entry for CS2103T - A rigorous software engineering class at NUS. +* `nus ta ma1101r` OR `nus teaching asst ma1101r` + +Creates a Resume Entry for a MA1101R teaching assistant position. (MA1101R is an undergraduate linear algebra class at NUS) +* `nus computing cl exco` + +Creates a Resume Entry for an executive commitee position in the Computing Club at SoC. + +[NOTE] +Slang and acronyms must be correctly configured in application data. ResuMaker ships with NUS / SOC specific slang and acronyms - available link:{repoURL}/releases[here]. + +//end::anubhavUG[] + +[[tags]] + +//tag::tags[] +=== Managing Tags +These are functions to help you manage your tags; namely to view and edit the relevant tags and entries. + +[NOTE] +All tags and categories are case-sensitive, only exact match in the casing will result in a successful match. + +==== Listing entries under specific tags: `tag list` or `tag ls` + +List all entries under specific tags (space separated). + +By default, if a tag is not given, all entries will be displayed. + +All entries displayed will be accompanied with their relevant entry id, to be used when editing. + +Examples: + +* `tag ls` + +Displays all entries in ResuMaker. +* `tag ls ~work #java` + +Lists all the `~work` entries tagged with `#java`. + +[NOTE] +Each entry can only be tagged with one category, therefore calls like `tag ls ~work ~project` will only pick one of the categories to display + +==== Add particular tag to entry: `tag add` + +Add tags (space separated) to particular entry (identified by index). + +If a category tag is given, it will replace the current category of the specific entry. + +Duplicate tags will be ignored. + +Examples: + +* `tag add 10 ~work #java` + +Adds tag `#java` and category `~work` to entry 10. + +==== Remove particular tag from entry: `tag remove` or `tag rm` + +Remove tags (space separated) from a particular entry (identified by index). + +By default, if no tags given, all tags will be removed from the entry. + +Examples: + +* `tag rm 1 ~work #java` + +Removes category `~work` and tag `#java` from entry 1. +* `tag rm 10` + +Removes all tags and categories from entry 10. + +==== Retagging a particular entry: `tag retag` or `tag rt` + +Remove all current tags and replace them with given tags (space separated). + +Each entry must be tied to a specific category, hence, any retagging must include a valid category. + +Examples: + +* `tag rt 1 ~work #java` + +Removes all tags and categories from entry 1, and then adds category `~work` and tag `#java` to entry 1. + +//end::tags[] === Listing entered commands : `history` @@ -164,59 +370,109 @@ Format: `history` Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box. ==== -// tag::undoredo[] -=== Undoing previous command : `undo` - -Restores the address book to the state before the previous _undoable_ command was executed. + -Format: `undo` - -[NOTE] -==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). -==== +// tag::template[] +=== Using templates to specify resume formats +A template specifies the format of the generated resume, in terms of the ordering and title of sections, +and which entries to include. +Templates are written by the user in a specific format and stored as text files. +They should be saved in the same directory as the `resumaker.jar` file, and are parsed and loaded into the application using the `loadtemplate` command. + +==== Writing template files +//TODO: how do we have parallel structure for this? +Templates are text files consisting solely of lines of the following format: +----- +[Category Heading]:~[Category Tag]:[Tag Groups] +----- + +Each line specifies a category, starting with the title to be displayed, its corresponding category tag, and tags used to filter entries. +Each `Tag Group` contains one `Tag`, or several separated by ampersands (&). Tag groups are separated by spaces. +For example, the following formats would all be valid as `[Tag Groups]`: +---- +* [Tag] [Tag] [Tag] +* [Tag]&[Tag] +* [Tag] [Tag]&[Tag]&[Tag] +* (no tags) +---- +An entry is included if it is tagged with that category, and fulfills any of the groups of tags. +It must contain all tags in a group to fulfil the group. + +For example, the following category, + + Work Experience:~work:java&recent python&recent&significant datascience + +means to include any entry categorized as `work`, as long as it fulfills any of the following: + +* tagged with `java` and `recent` +* tagged with `python` and `recent` and `significant` +* tagged with `datascience` + +As an example, the following template file: +[literal] +-- +Work Experience:~work: +Education:~education:uni training&cs +Projects:~projects:software&java&recent +-- +Will result in the resume being generated as follows: +[sidebar] +-- +*Work Experience* + +(all work entries regardless of tags) + +*Education* + +(education entries tagged with `uni`, or both `training` and `cs`) + +*Projects* + +(project entries tagged with `software`, `java` and `recent`) +-- + +==== Loading template : `loadtemplate` + +Loads a template from a text file into the application. + +Format: `loadtemplate FILEPATH` Examples: -* `delete 1` + -`list` + -`undo` (reverses the `delete 1` command) + +* `loadtemplate google.txt` + +Loads the template specified in `google.txt` +* `loadtemplate templates\facebook.txt` + +Loads the template specified in `facebook.txt` in the `templates` subfolder + +[TIP] +If the format of the text file looks to be correct but the application says that it is invalid, try checking for and removing any extra newlines or spaces. -* `select 1` + -`list` + -`undo` + -The `undo` command fails as there are no undoable commands executed previously. +// end::template[] -* `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + +// tag::resume[] +=== Generating Resume : `make` -=== Redoing the previously undone command : `redo` +Generates a Resume file with the given name, using the _template_ currently loaded in the application. + -Reverses the most recent `undo` command. + -Format: `redo` +[NOTE] +==== +By default, the file will be saved in the same folder as the application. +You can also specify a more complicated filepath if you want the file to be saved to a specific folder. +==== Examples: -* `delete 1` + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +* `make sep.md` + +Generates a file named sep.md in the same folder as the application, +containing a Resume which lists entries as designated by the currently loaded template. + +ResuMaker generates your resume files in the _markdown_ format, which is commonly used around the web. +Since you will likely need your resume in a different format such as a Word document or a PDF file, +here are some of the many tools out there that can help you convert your resume: -* `delete 1` + -`redo` + -The `redo` command fails as there are no `undo` commands executed previously. +* link:http://www.writage.com/[Writage], which allows you to edit and convert markdown files in Microsoft Word. +* link:https://pandoc.org/[Pandoc], which converts markdown files to a variety of formats like PDF. +* link:https://dillinger.io/[Dillinger], which lets you edit markdown files and convert them to HTML. -* `delete 1` + -`clear` + -`undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + -`redo` (reapplies the `clear` command) + -// end::undoredo[] +// end::resume[] -=== Clearing all entries : `clear` +=== Clearing all entries : `clear` [Coming in v2.0] -Clears all entries from the address book. + +Clears all entries from ResuMaker. + Format: `clear` === Exiting the program : `exit` @@ -226,7 +482,7 @@ Format: `exit` === Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data. + +ResuMaker data are saved in the hard disk automatically after any command that changes the data. + There is no need to save manually. // tag::dataencryption[] @@ -235,26 +491,119 @@ There is no need to save manually. _{explain how the user can enable/disable data encryption}_ // end::dataencryption[] +== Configuring Resume data + +All your resume entries are saved in the XML format. + +The filepath to the XML file can be specified in the `preferences.json` file. + +If the file cannot be found, ResuMaker will start with sample resume data. + +If the file cannot be read or has errors in its XML, ResuMaker will start with no resume data. + +// tag::dataConfig[] +== Configuring Awareness data + +Awareness data can be provided via XML. + +Create a XML file (with `.xml` extension) called `awareness` and place it in the same folder as `resumaker.jar`. + +If the file cannot be found, ResuMaker will start with sample awareness data. + +If the file cannot be read or has errors in its XML, ResuMaker will start with no awareness data. + +If you know some XML, you can easily configure your own slang and contextual events! +The Awareness XML file consists of a series of `mapping` elements and a series of `context-entry` elements. +Each `mapping` element defines a mapping between some slang (e.g. cs) and a full phrase (e.g computer science). + +These are the main rules to follow when writing the `mapping` elements. + +* You cannot use the same slang in multiple mapping elements. +* Each slang can only be one word. +* Each `slang` and `full phrase` element cannot be purely whitespace. + +These are the rules to follow when writing the `context-entry` elements. + +* Give each `context-entry` a unique name, and provide it in the `eventName` attribute. +* For best results, do not have unnecessary spaces in your `eventName`. +* The `eventName` cannot be purely whitespace. +* For the resume entry's XML, follow the same format as used by the resume entries' XML file. +// end::dataConfig[] + +The following can be used as a template for your awareness data. + +``` + + + Computer Science + cs + computing + + + financial technology + fintech + ft + + + machine learning + ml + + + singapore + sg + + + financial technology + fintech + + + hackathon + + + + + education + + + + + + Leadership + Entrepreneurship + + + +``` + == FAQ *Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous ResuMaker. == Command Summary - -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` +For reference, here is a brief summary of the commands available and their syntax: + +* *Add Bullet* `addBullet INDEX CONTENTTOADD` + +e.g. `addBullet 0 attain Best Financial Hack Award` +* *Add Entry* `addEntry ~CATEGORY , [t/TITLE], [s/SUBHEADER], [d/DURATION] [#TAGNAME]…` + +e.g. `addEntry ~work #java t/The Source Enterprise s/Java Programmer intern d/ May 2010 - Aug 2010` +* *Add Nus Entry* : `nus EVENT_NAME` + +e.g. `nus hack n roll` * *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` +* *Delete Bullet* : `deleteBullet ENTRYINDEX BULLETINDEX` + +e.g. `deleteBullet 2 2` +* *Delete Entry* : `deleteEntry INDEX` + +e.g. `deleteEntry 2` +* *Edit Bullet* : `editBullet ENTRYINDEX BULLETINDEX EDITTEDCONTENT` + +e.g. `editBullet 0 0 implement scalable application for data visualization using java` +* *Edit Entry* : `editEntry INDEX [t/TITLE ] [s/SUBHEADER] [d/DURATION] [#TAG]…` + +e.g. `editEntry 1 t/R company #JAVA` +* *Expand Entry* : `selectEntry INDEX` + +e.g. `selectEntry 2` +* *Generate Resume* : `make FILENAME` * *Help* : `help` +* *History* : `history` +* *Load Template*: `loadtemplate FILEPATH` + +e.g. `loadtemplate google.txt` * *Select* : `select INDEX` + e.g.`select 2` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` +* *Tag List*: `tag ls TAG [MORE_TAGS]` + +e.g. `tag ls ~work #java` +* *Tag Remove*: `tag rm INDEX TAG [MORE_TAGS]` + +e.g. `tag rm 10 ~work #python` +* *Tag Retag*: `tag tg INDEX TAG [MORE_TAGS]` + +e.g. `tag rt 10 ~project #web` diff --git a/docs/diagrams/ArchitectureDiagram.pptx b/docs/diagrams/ArchitectureDiagram.pptx index b0e5a9d0ff55..0ae217cb2674 100644 Binary files a/docs/diagrams/ArchitectureDiagram.pptx and b/docs/diagrams/ArchitectureDiagram.pptx differ diff --git a/docs/diagrams/HighLevelSequenceDiagrams.pptx b/docs/diagrams/HighLevelSequenceDiagrams.pptx index 38332090a79a..a74b30f300ba 100644 Binary files a/docs/diagrams/HighLevelSequenceDiagrams.pptx and b/docs/diagrams/HighLevelSequenceDiagrams.pptx differ diff --git a/docs/diagrams/LogicComponentClassDiagram.pptx b/docs/diagrams/LogicComponentClassDiagram.pptx index 6fcc1136a5bb..64463b47ce65 100644 Binary files a/docs/diagrams/LogicComponentClassDiagram.pptx and b/docs/diagrams/LogicComponentClassDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index 3c976908eaa7..27ca9a377f21 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index be29a9de7ca6..f830f12783fa 100644 Binary files a/docs/diagrams/StorageComponentClassDiagram.pptx and b/docs/diagrams/StorageComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UiComponentClassDiagram.pptx b/docs/diagrams/UiComponentClassDiagram.pptx index 384d0a00e6ea..e8a1b1886d71 100644 Binary files a/docs/diagrams/UiComponentClassDiagram.pptx and b/docs/diagrams/UiComponentClassDiagram.pptx differ diff --git a/docs/diagrams/awarenessFlow.xml b/docs/diagrams/awarenessFlow.xml new file mode 100644 index 000000000000..58e9abc8b94f --- /dev/null +++ b/docs/diagrams/awarenessFlow.xml @@ -0,0 +1,2 @@ +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 + diff --git a/docs/diagrams/awarenessHighLevelSeq.xml b/docs/diagrams/awarenessHighLevelSeq.xml new file mode 100644 index 000000000000..9ef6c63a0857 --- /dev/null +++ b/docs/diagrams/awarenessHighLevelSeq.xml @@ -0,0 +1,3 @@ +7V1bk6M2Fv41XZV9iAvd4bFvM/uQ7E5ltiqbR9pmutlg4wB9mfz6FRhhdCS7ZSywxzOuVHossIBz/c5F4orcLt8+FvH66dd8kWRXOFi8XZG7K4wRJUj+qUe+bkZYwDYDj0W6aE/aDnxO/07awaAdfU4XSamdWOV5VqVrfXCer1bJvNLG4qLIX/XTvuSZftV1/NheMdgOfJ7HmbqPGduO/54uqqfNeIj5dvyfSfr4pK6NeLQ58hDP/3ws8udVe8UrTL40n83hZazmaq9cPsWL/LU3RO6vyG2R59XmX8u32ySrqasIt/ndhx1HuwcsklXl8gN13y9x9pyoW25urPqqyPElzbLbPMuL5iu5u78PP8j5bsqqyP9M1JFVvkrMq7c39JIUVfLWG2rv5mOSL5Oq+CpPaY/+TGi4+U0rPLj9+rrlA2KiJd9TjwkhaQfjlv2P3eRbAsh/tDSw0yNE79MjWUhpab8m2UP+er8duGkG5IGnvEj/zldVnNWDq8V1LZeKTORmEZdPSX1NC8faofoyLhRDlAcaxSgRszAiDIWb/7hJwMBCv27QRr/26p/yVN7l9tKEUaUr7cU5ArOU+XMxT9of9iXx/bmEAHNVcfGYVMZcDVM7wjjxGR8s9re3FGFmiL08wnkQBMFOPrrLPg50RiIWWFhnco77kHxymOC3clw/XCot5y/xQ5J9ysu0SvOVPFzl697R6yx9rEcf8qrKl/JABk7Pki/yWW7i9ryiebajFQMBvYiUfI2hCuBiaHu1QzXBEANzKn+KQE22k+tf8sd0/mu8km6uMKSgc1lIcgwoyTW+pbfRWL4BMQytjU1BmMlQ5kFB+DmaDEIhSVDoZjRCDyQRNuG5zZfLeLX4FBflWUkPjk4nO+Eg4zoINmysjMafjbXQpNjVhmKBZ4SwgEe0+QBZ4+MZVELFDLMwFIQyLqIA6VceCjPqaZngKGJRKDij1P5A/g2tA9D25V+PdZxddNJSJcKjcRlDtykGu01C353KHzfRgXHCcXDJNASlfJRKjc2zuCzTud+wAgoBkpZA00cymVBgFs2EaQqOFZB3pvUoLA7RxsTY+jQyBUGypLrNxk8Az4X9ysdC9Xem9ShSzBCpeYO9VhINycmlmydv15J7LRoz5O10OIxEMMollrjMluGhYjfbXZEYsmDW08N4CowtDqhFFUyKEH48RbCDK5ueIgykAQlyC2t8oHVFAD2uafPcZ6NHDNieKeOZgdmiowMadZl+RHOoE+KIzmgoQipw8wERgKCzqP8Ro/kkLi/FmfQZ7UcX+MERzv5pOzEZIadqzSVdv9YsTcryjFSnS5gq1bEFOCOpDjlQdUasMPQ1i1oU6zC9wiSYBf0PcTDgngKG/Zemg5MFQE4InMif8rBhMcLRfD8wH0QEnzGBUBiw5hPp9CHRjEcCURZwGS+GNhDjh+OUoZkgHKkP1o1cSG33MShXpEsSpzPEtx8BZvYoDybAl6g+qyM39fd/8Utczot0XeP9x/aI+qvwv7TAn4r87esZWV8UgAiAcjwLQrL92HzuSOaYOaT1x9A7xd++wWXiMGVEMkyYBbu1MZRWMeTqIGPjqSPCdeDb00dg+zGz3smAGjGQHLp/Yo/6eJZxoyQ0NFGMuAVKkQ/lcahrnIAoUEoYdSMJbDcYVCU0/fg6L6s6DSOfqPzpH3uty06wp7J9pyoJIElFXaWDcavphutl20rn0WbDMtcOSyGNfPy1d9q6PqE80pZwWwgAfHspeWp49SMlp+eidotRl0eGklTL19FiFAggRtGo3UkwQ8IHi5ApkJbZJhUiS7QNhKhMipdUAo696PBjuvzrszrxfDAiCU7a62FD31NEY5YwXDlZ9yKL2IcK8YhxuPQSRjsKGqpx5lxd+8EI1fnAYHjylsyfq+QnqS5/Pdf9uDerZ6m08kQeL2tX2+hY8rYukrJMG3O9GVdK1v7K9PhNiYbcPFXLrNUtiw7pCtcOqSLeXApRUliM9DJdLBrz//qUVsnnddxI1msR1zfW9CU30hh07sRQSYuM7knSA15bAjdr2cKDikZm2WJdtxy1Ba8fbNtX2tfDM0Sn5JvNb71vWscuhsun+G9L4ebLH/WXmWAdxYH5tZryvtne2L3diTRglqPxrDKEQQiGfq4mmdJo/0Qe69uBqd6mmOxzt5qibvk7Y30ON8fe0qp3SH77Q1M1F8b3fTgKsEUahF0aeuxmFm6rsSLJ4ip90e/jgGwpKBUSWARylgAgSiMmxDsyek8uHGU7YaWeBLYAxsJJRHdzzbl5IRiWrZyw7WyrT6ivTYdaUqNEsduQBgBw1pWBifrUQEexjGa17p8A9Ks4axk25h1Py2x9qZvwcZG+qJBxvg0tP/SGDdm7HIjEIAtMiEQtUoQ9IKQOejn6vq0e2j0e1lzeYY5N82Ebi3wqJ4aBohu+x7lpj0MTPp4Ts/QJq7jykgMM2HRiqQSPpz0uuZv3tEf5sBnWvFggI6fN909Jkcp7q4l9PFY8GS4EYGZwowSbEBciGwYyPFZzNcnYhyy5f5Es+Fe8THY5MePHveSBlijYpga+L28IA7+uUtYT09AipsQH5nXp2D00F6v5R+HLP/ZW8NuoKDCaRWfReAgc4PBWQzCRUVT2qfbD1tBNG/rsSTMMk6n3FJPAHDlmeKrwB4Ypg7MKzFjX2y3VGUOQzIhnbXqKizXlAnhqYu6vMRo0ww6BzfQpHb4LvEyw1AC5rL44Ray3y5dNA0wZDNHQwFQK5wSalmC0IiJSvPsBTSezZ4yDUNMCTbtUsraYzAc2tS15OGNQgncWP7wla4cAY2psYoE5ti3NHQHEGJZmMIjhoOd1vM2LELalOAxToVCNs2151xpdrBFRzQRdvzJ2AkUE+7AhY20scxxJGFxD47YiVXhYkYpsG8scAYq8YaIdlaipknXMzpGDLZVq9+jWCYy484FTeUlerV25/hxnzubqt6R8Xib3q4ZqrvDJCPW+O8RE1ZKXzlGZxm60ZJ6ymN7bOsSevg7hqbFDWWqXBM00NoGDxg4GAZBznAQX1sDg1qNNcFkBe/osAHVs7IBLAIephUsc4bnbyZdWKHaeUfpAgJ1+KMxeuaqFACsVxwT1xAnUH+MqZ7PZ9+bszKhsSmfn0q11BIzdVaWuayVeq9Q2x9fq/ckyhMKTinMQ+1ESjLhtHLFFNoa6/ntdJ4QO0G+9uV0Hxnp3+/el/aFet6bUrIaNFtcTW+gDtf+b2fXtfZuxxwaDDLyw7rs3SnIPhMxw01t3eAwmAo8AOz09Wgwlnz8sxinKo5S6lUe9WAzqUgv8FixGH5poUcaMCM/QZFcDXcdPsI1cONWOk/DKNBy8sy2scdFwf5+5T+tj5mvielOvxaIxFqPsLnmcy0ccrhtWyLDPZKWv3reXpGZyI84qG4HW9T+fl9mHoq642O1bz6B6oA2hejDEVCtMjzLd61e05g+YIR5GGofG3CxtTUwtGurVO1CIgOtoDddOx1FKmqarx//U5vDuZ7Qd+aX54R3ZjvzWPjNpfEsVV/FDc2eBxV4qA9mu5pe3z26u2F0zUtRBunyIOG04k8Rl9ZqU1Ths5BYrRpQwa1v++2CiJaaVz42DVS7/p+GI4EvzHiSpfuzughED0asptv13bBkGLw1VdFhv7HkjBj2ZIRD1ihj24wWyjf53QP3xEEO3qbOSpKHRCmzr5yPuQ03NttxCGgGLy7sYjccgRhAWjR+thZI6ZBVO0C0A0qyc27rXTZKo2sFRJGHDwqb3FgjYCyWHtXJrocqGdXuaUPiebVtEhCfaWpNx3aEJBJjkHraAiUasKbpsn3p6tegoMIlanN8b3Xxjg0jsWo7Xb3rUyiaziHVlFY+AwnGlbIcyQhik2t9FMgbKAOBADN4LAsOHEJO9UMW2Oy67sUUfFx16gDI3R2ayUu0o5h2IWDbO/Q5WNsN3rQpsOmIr9vPRFumyB+151pNPVSWmWIcgHS49GMsgMNGYWOZSCofHyIdiYKjrG4um8pOw3VYMba2DnQrc/ggjCJKyVVry/nJNM9ztVdjeKzVaYM79LuRzNs5REH2rxlkVYZSGqdzGsfUxwUD85FOnHALNb8Q4710Y5t1yT/euPwre2iLEYLnSO+VFOKJcmR3Al55Hhb0WgjkuRfeBpblLb+aQHZ4PXlXEd2wN2bk1sictGIaTvXEH7FcSDt00xJhoxL0euEPE1CvF3yW1Kn3doQCGxqn6NPGkDwHgLTbhC7Ew08euvIg7BB1nQynGgMO3yD0eK/EhTKB36ZYaE53eTEwIrIWZx750eiNjWx/ +BTLzkieLya5HXm0dsjax8rKfN607J/f8B + diff --git a/docs/diagrams/awarenessStructure.xml b/docs/diagrams/awarenessStructure.xml new file mode 100644 index 000000000000..eb2c3ca724e4 --- /dev/null +++ b/docs/diagrams/awarenessStructure.xml @@ -0,0 +1,2 @@ +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 + diff --git a/docs/diagrams/categoryManager.xml b/docs/diagrams/categoryManager.xml new file mode 100644 index 000000000000..5e58dd34bff4 --- /dev/null +++ b/docs/diagrams/categoryManager.xml @@ -0,0 +1,2 @@ +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 + diff --git a/docs/images/Architecture.png b/docs/images/Architecture.png index bdc789000f77..51faaa56f90f 100644 Binary files a/docs/images/Architecture.png and b/docs/images/Architecture.png differ diff --git a/docs/images/AwarenessFlow.png b/docs/images/AwarenessFlow.png new file mode 100644 index 000000000000..defeb1ea00bd Binary files /dev/null and b/docs/images/AwarenessFlow.png differ diff --git a/docs/images/FlowDiagramForEntryManagement.png b/docs/images/FlowDiagramForEntryManagement.png new file mode 100644 index 000000000000..4ba9b02703d2 Binary files /dev/null and b/docs/images/FlowDiagramForEntryManagement.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index f4ecf65b3193..7ec37735b64e 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 9fb19078b859..3d2778f03e78 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/SequenceDiagramAddBullet_1.png b/docs/images/SequenceDiagramAddBullet_1.png new file mode 100644 index 000000000000..2d94155633b0 Binary files /dev/null and b/docs/images/SequenceDiagramAddBullet_1.png differ diff --git a/docs/images/SequenceDiagramAddBullet_2.png b/docs/images/SequenceDiagramAddBullet_2.png new file mode 100644 index 000000000000..fe7c80546e64 Binary files /dev/null and b/docs/images/SequenceDiagramAddBullet_2.png differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 7a4cd2700cbf..a3caeb439d7f 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/TemplateStorage Sequence Diagram.png b/docs/images/TemplateStorage Sequence Diagram.png new file mode 100644 index 000000000000..e83d3d784918 Binary files /dev/null and b/docs/images/TemplateStorage Sequence Diagram.png differ diff --git a/docs/images/Template_SD1.png b/docs/images/Template_SD1.png new file mode 100644 index 000000000000..b9dedcf8b549 Binary files /dev/null and b/docs/images/Template_SD1.png differ diff --git a/docs/images/Template_SD2.png b/docs/images/Template_SD2.png new file mode 100644 index 000000000000..7d12dc6ffef2 Binary files /dev/null and b/docs/images/Template_SD2.png differ diff --git a/docs/images/Template_UML.png b/docs/images/Template_UML.png new file mode 100644 index 000000000000..fa60d550a952 Binary files /dev/null and b/docs/images/Template_UML.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..fbf4b8290f0f 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/UiClassDiagram.png b/docs/images/UiClassDiagram.png index 369469ef176e..6efa82673e52 100644 Binary files a/docs/images/UiClassDiagram.png and b/docs/images/UiClassDiagram.png differ diff --git a/docs/images/anubh-v.png b/docs/images/anubh-v.png new file mode 100644 index 000000000000..f1edd2f80027 Binary files /dev/null and b/docs/images/anubh-v.png differ diff --git a/docs/images/awarenessFlow.png b/docs/images/awarenessFlow.png new file mode 100644 index 000000000000..ab777517de84 Binary files /dev/null and b/docs/images/awarenessFlow.png differ diff --git a/docs/images/awarenessHighLevelSeq.png b/docs/images/awarenessHighLevelSeq.png new file mode 100644 index 000000000000..9f79bf48a793 Binary files /dev/null and b/docs/images/awarenessHighLevelSeq.png differ diff --git a/docs/images/awarenessStructure.png b/docs/images/awarenessStructure.png new file mode 100644 index 000000000000..c699a37993db Binary files /dev/null and b/docs/images/awarenessStructure.png differ diff --git a/docs/images/categoryManager_example_listEntry.png b/docs/images/categoryManager_example_listEntry.png new file mode 100644 index 000000000000..25669db9a1b6 Binary files /dev/null and b/docs/images/categoryManager_example_listEntry.png differ diff --git a/docs/images/categoryManager_example_tagls.png b/docs/images/categoryManager_example_tagls.png new file mode 100644 index 000000000000..cf616cf1a5ef Binary files /dev/null and b/docs/images/categoryManager_example_tagls.png differ diff --git a/docs/images/categoryManager_example_template.png b/docs/images/categoryManager_example_template.png new file mode 100644 index 000000000000..97a60e57a817 Binary files /dev/null and b/docs/images/categoryManager_example_template.png differ diff --git a/docs/images/classDiagramForEntry.png b/docs/images/classDiagramForEntry.png new file mode 100644 index 000000000000..fcb0da85632f Binary files /dev/null and b/docs/images/classDiagramForEntry.png differ diff --git a/docs/images/damithc.jpg b/docs/images/damithc.jpg deleted file mode 100644 index 127543883893..000000000000 Binary files a/docs/images/damithc.jpg and /dev/null differ diff --git a/docs/images/generateResumeSequenceDiagram.png b/docs/images/generateResumeSequenceDiagram.png new file mode 100644 index 000000000000..7f03cfaead43 Binary files /dev/null and b/docs/images/generateResumeSequenceDiagram.png differ diff --git a/docs/images/jhengy.png b/docs/images/jhengy.png new file mode 100644 index 000000000000..2f5223549833 Binary files /dev/null and b/docs/images/jhengy.png differ diff --git a/docs/images/lejolly.jpg b/docs/images/lejolly.jpg deleted file mode 100644 index 2d1d94e0cf5d..000000000000 Binary files a/docs/images/lejolly.jpg and /dev/null differ diff --git a/docs/images/m133225.jpg b/docs/images/m133225.jpg deleted file mode 100644 index fd14fb94593a..000000000000 Binary files a/docs/images/m133225.jpg and /dev/null differ diff --git a/docs/images/marvintxd.png b/docs/images/marvintxd.png new file mode 100644 index 000000000000..c9668b830900 Binary files /dev/null and b/docs/images/marvintxd.png differ diff --git a/docs/images/ongspxm.png b/docs/images/ongspxm.png new file mode 100644 index 000000000000..3d99c7f1c014 Binary files /dev/null and b/docs/images/ongspxm.png differ diff --git a/docs/images/resumeClassDiagram.png b/docs/images/resumeClassDiagram.png new file mode 100644 index 000000000000..dfb0ff65ffda Binary files /dev/null and b/docs/images/resumeClassDiagram.png differ diff --git a/docs/images/resumeStorageExtensionClassDiagram.png b/docs/images/resumeStorageExtensionClassDiagram.png new file mode 100644 index 000000000000..04eb37335c3d Binary files /dev/null and b/docs/images/resumeStorageExtensionClassDiagram.png differ diff --git a/docs/images/scalarmotion.png b/docs/images/scalarmotion.png new file mode 100644 index 000000000000..f1f94e06099c Binary files /dev/null and b/docs/images/scalarmotion.png differ diff --git a/docs/images/ugTypicalFlowDiagram.png b/docs/images/ugTypicalFlowDiagram.png new file mode 100644 index 000000000000..8232597c530f Binary files /dev/null and b/docs/images/ugTypicalFlowDiagram.png differ diff --git a/docs/images/yijinl.jpg b/docs/images/yijinl.jpg deleted file mode 100644 index adbf62ad9406..000000000000 Binary files a/docs/images/yijinl.jpg and /dev/null differ diff --git a/docs/images/yl_coder.jpg b/docs/images/yl_coder.jpg deleted file mode 100644 index 17b48a732272..000000000000 Binary files a/docs/images/yl_coder.jpg and /dev/null differ diff --git a/docs/team/anubh-v.adoc b/docs/team/anubh-v.adoc new file mode 100644 index 000000000000..41b97674c522 --- /dev/null +++ b/docs/team/anubh-v.adoc @@ -0,0 +1,56 @@ += Anubhav - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ResuMaker + +--- + +== Overview + +This document details my contribution to ResuMaker - a fast and flexible resume generator. ResuMaker was built by five students, including myself. It is optimised for students in the School of Computing. + +== Summary of contributions + +* *Major enhancement*: Added support for contextual awareness +** What it does: allows the user to *communicate with the application via slang*. The application's ability to understand slang is quite configurable. Moreover, the user can choose to *auto-populate resume entries for events that have standard information*, such as university modules or programmes. +** Justification: This feature improves the product significantly because it allows users to control the application using language that is more convenient for them. +** Highlights: This enhancement is built to be configurable - the user can add on to the set of slang that is understood by the application. +** Code contributed: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=anubh-v&sort=displayName&since=2018-09-12&until=2018-11-12&timeframe=day&reverse=false&repoSort=true[link to RepoSense profile] +** Credits: ResuMaker was a team effort. The project would not be possible without the great effort displayed by the <<../AboutUs#, group>>. + +* *Other contributions*: + +** Project management: +*** Managed releases v1.1 - v1.3 (3 releases) +*** Added third party tools to GitHub repository (Travis, AppVeyor, Coveralls) +** Documentation: +*** Wrote the introductory blurb for User Guide and Developer Guide +*** Reviewed documentation related PRs +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/CS2103-AY1819S1-W17-1/main/pull/97[1], https://github.com/CS2103-AY1819S1-W17-1/main/pull/49[2], https://github.com/CS2103-AY1819S1-W17-1/main/pull/54[3]) +*** Contributed to forum discussions (examples: https://github.com/nus-cs2103-AY1819S1/forum/issues/75[1], https://github.com/nus-cs2103-AY1819S1/forum/issues/84[2], https://github.com/nus-cs2103-AY1819S1/forum/issues/85[3]) + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide._ +|=== + +include::../UserGuide.adoc[tag=intro] + +include::../UserGuide.adoc[tag=anubhavUG] + +include::../UserGuide.adoc[tag=dataConfig] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide._ +|=== + +include::../DeveloperGuide.adoc[tag=contextualAwareness] + diff --git a/docs/team/jhengy.adoc b/docs/team/jhengy.adoc new file mode 100644 index 000000000000..fd0e077fd06d --- /dev/null +++ b/docs/team/jhengy.adoc @@ -0,0 +1,70 @@ += Jiang Hengyuan- Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets +:sectnums: + += PROJECT: ResuMaker + +--- + +== Overview +This document showcases my contributions to this team project which consists of five team members. It is done in a semester long (13 weeks) NUS module CS2103T. Thanks to the help from my other four team members. Please refer to https://github.com/CS2103-AY1819S1-W17-1/main/blob/master/docs/AboutUs.adoc[AboutUs] page for more details of the team. + +ResuMaker is a fast, flexible and customizable resume generator aimed at computer science professionals. On top of the Command Line Interface where most of the user interaction takes place, it also provides users with an intuitive and user friendly Graphic User Interface. + + + +== Summary of Contributions + +* *Major enhancement*: Responsive Display of Expanded Entry +** What it does: It allows for the responsive display of the bullet description of an entry whenever a user edits the description of that particular entry. +** Justification: This is a useful feature as it provides a timely feedback to the user about the latest description of an entry that has been modified. Without this responsive feature, the user may mistakenly feed in the wrong description to an entry without knowing it or he would have to manually select the edited entry to disclose its description. +** Highlights: This feature involves implementation of multiple components, i.e. UI, Logic, Model and Commons +** Credits: Let me acknowledge the se-edu/addressbook-level4 team for their overall architecture design that lays the foundation for the implementation of this enhancement and also for their idea on event driven design which is central to making the expanded entry display responsive. +** Relevant PRs: https://github.com/CS2103-AY1819S1-W17-1/main/pull/191[#PR191], https://github.com/CS2103-AY1819S1-W17-1/main/pull/206[PR#206] + +* *Code contributed*: [https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=jhengy&sort=displayName&since=2018-09-12&until=2018-11-11&timeframe=day&reverse=false&repoSort=true[Functional code]] + +* *Other contributions*: +** Morphing of AddressBook 4 to ResuMaker: +*** Set up of Model Component Class Structure for ResumeEntry. See https://github.com/CS2103-AY1819S1-W17-1/main/pull/48/files[PR#48] https://github.com/CS2103-AY1819S1-W17-1/main/pull/114/files[PR#114]. +*** Morph existing commands to align with new Model. See https://github.com/CS2103-AY1819S1-W17-1/main/pull/165[PR#165], +https://github.com/CS2103-AY1819S1-W17-1/main/pull/178[PR#178], +https://github.com/CS2103-AY1819S1-W17-1/main/pull/200[PR#200] +*** Set up display of entries in UI. See https://github.com/CS2103-AY1819S1-W17-1/main/pull/178[PR#178] + +** Test Handling: +*** Addition of utility class such as EntryBuilder to facilitate unit testing of Model related classes + +** Documentation: +*** Modified Command Format section under the feature section of the User Guide + +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/CS2103-AY1819S1-W17-1/main/pull/179[PR#179], +https://github.com/CS2103-AY1819S1-W17-1/main/pull/205[PR#205] + +== Contributions to the User Guide +|=== +|_ Given below is the flow chart of the appearance of the Graphic User Interface +when a set of commands is executed by an user._ +|=== +.Flow diagram of a typical scenario for the list of commands executed. +image::FlowDiagramForEntryManagement.png[width="800"] + +{nbsp} + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=hengyuanUG] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=entryManagement] + + diff --git a/docs/team/marvintxd.adoc b/docs/team/marvintxd.adoc new file mode 100644 index 000000000000..dcff74919544 --- /dev/null +++ b/docs/team/marvintxd.adoc @@ -0,0 +1,70 @@ += Marvin Tan Xu Dong - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ResuMaker + +--- + +== Overview + +This project portfolio documents my contributions for a software engineering group project undertaken during my study of Computer Science +at the National University of Singapore. +This project was done in the context of our Software Engineering module, CS2103T. +In less than a semester, our group of five was tasked with adapting an existing codebase in Java to build a functioning product aimed at a specific target demographic. + + +Our product, ResuMaker, is a fast and flexible resume generator targeted at students and gradutes from the School of Computing. +The application supports both a command-line interface (accepting text inputs), as well as a graphical user interface. +ResuMaker simplifies the resume writing process, by storing your past experiences and achievements as taggable entries. +When generating the resume, the user can specify which tags to include, allowing users to easily tailor resumes for each job application without manual editing. +The application is also optimised for our target demographic, by recognizing common terminology used by them, and auto-generating pre-filled entries. + +== Summary of contributions + +* *Major enhancement*: added *the ability to use templates to specify resume formats* +** What it does: This feature allows the user to use template text files to specify how the resume will be generated, in terms of the ordering and title of sections, and which entries will appear under each section based on its tags. +** Justification: This feature enables one of the core value propositions of our product, which is to be able to easily generate resumes for different contexts. +While the application will hold all of the user's experiences as entries, +templates allow the user to customize the generated resume for specific purposes or job applications by specifying the types of entries to include, +without having to manually edit the resume. +** Highlights: The template feature accommodates a basic boolean logic system. In the template text files, users can specify +the tags to include using "&" and the space character as logical operators. For example, `java python&datascience swift` means "entries should be tagged with (java) OR (python AND datascience) OR (swift)". + + +* *Minor enhancement*: added the ability for users to specify their particulars and contact details in the preferences file, +for them to be displayed in the generated resume. + +* *Minor enhancement*: added the functionality of saving entries into an XML file automatically. + +* *Code contributed*: +https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=marvintxd[Project Code Dashboard] + +* *Other contributions*: + +** Documentation: +*** Made overall edits for User Guide and Developer Guide +*** Updated component diagrams: https://github.com/CS2103-AY1819S1-W17-1/main/pull/338[#338] +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com/CS2103-AY1819S1-W17-1/main/pull/150[#150], https://github.com/CS2103-AY1819S1-W17-1/main/pull/267[#267] +*** Bugs reported on teammates' features: https://github.com/CS2103-AY1819S1-W17-1/main/issues/290[#290] +** Tools: +*** Set up Travis CI and autopublishing of docs for the project + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=template] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=template] diff --git a/docs/team/ongspxm.adoc b/docs/team/ongspxm.adoc new file mode 100644 index 000000000000..e60079f9d3bd --- /dev/null +++ b/docs/team/ongspxm.adoc @@ -0,0 +1,62 @@ += Ong Shu Peng - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ResuMaker + +--- + +== Overview + +This portfolio showcases my contributions for a software engineering team project for our software engineering module. In a period of 6 weeks, we were tasked to implement a command line interface (CLI) based application targeted at a specific group of users. + +Our project, ResuMaker, is a fast and flexible resume generator, optimised for students in the School of Computing. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLo. + +== Summary of contributions + +* *Major enhancement*: added *tag management system* +** What it does: This enhancement allows the user to use tags to identify resume entries to include in the display or resume output. +** Justification: This enhancement allows ResuMaker to achieve one of its highlights: ability to selectively include specific entries to include in the resume creation process. +** Highlights: This enhancement will be needed for many of the other features, therefore many implementation details have to be considered to cater for the various different usages of the tags in the application. + +* *Minor enhancement*: added commands to alter existing tags of entries +Instead of having to reinsert entries, addition tag management functions have been implement to add, remove and replace tags. This will streamline the process of managing existing resume entries giving users more control over +** What it does: This enhancement allows the user to alter tags of existing entries +** Justification: This enhancement streamlines the tag management process, instead of having to reinsert entries, users can make use of the functions to alter the tags instead. + +* *Code contributed*: [https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=ongspxm&sort=displayName&timeframe=day&reverse=false&repoSort=true[CS2103 Dashboard]] + +* *Other contributions*: + +** Project management: +*** Managed merging of PR and handling of repository cleanups +** Enhancements to existing features: +*** Extended model functions (PR https://github.com/CS2103-AY1819S1-W17-1/main/pull/187[#187]) +*** Refactored existing address book classes (PR https://github.com/CS2103-AY1819S1-W17-1/main/pull/171[#171]) +*** Created EntryBook and corresponding testing utilities (PR https://github.com/CS2103-AY1819S1-W17-1/main/pull/87[#87]) +*** Developed command sub parser pattern (PR https://github.com/CS2103-AY1819S1-W17-1/main/pull/174[#174]) +** Documentation: +*** Did cosmetic tweaks to existing contents of the User Guide (PR https://github.com/CS2103-AY1819S1-W17-1/main/pull/262[#262]) +*** Included details on tags and category implementation (PR https://github.com/CS2103-AY1819S1-W17-1/main/pull/170[#170], https://github.com/CS2103-AY1819S1-W17-1/main/pull/54[#54]) +** Community: +*** PRs reviewed (with non-trivial review comments): (PR https://github.com/CS2103-AY1819S1-W17-1/main/pull/45[#45], https://github.com/CS2103-AY1819S1-W17-1/main/pull/253[#253]) +*** Reported bugs and suggestions F10-4 in PE Dry Run: (PR https://github.com/CS2103-AY1819S1-F10-4/main/issues?q=is%3Aissue+PE+Dry+Run+ongspxm[#219, #225, #232, #237, #239, #243, #245, #249]) +** Tools: +*** Integrated a third party tool (RepoSense) to the project (PR https://github.com/CS2103-AY1819S1-W17-1/main/pull/101[#101]) + +== Contributions to the User Guide + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=tags] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=tags] diff --git a/docs/team/scalarmotion.adoc b/docs/team/scalarmotion.adoc new file mode 100644 index 000000000000..80fd35eb6f6b --- /dev/null +++ b/docs/team/scalarmotion.adoc @@ -0,0 +1,168 @@ += Gu Wangfan - Project Portfolio +:site-section: AboutUs +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ResuMaker + +== Overview + +This portfolio aims to introduce a software engineering project I participated in and +highlight my contributions to it, both technical and otherwise. +This project was done as part of a Software Engineering module during my Computer Science studies +at the National University of Singapore's School of Computing (NUS SoC), +Over a period of 6 weeks, my team of 5 students developed a +Resume Generation app targeted specifically at students of NUS SoC. + +The project entailed morphing an existing medium scale software product +(about 6000 lines of code) into a totally new product +while learning and applying software engineering best practices. +It also imposed various constraints on the final product, such as +having to focus on only taking input from a Command Line Interface (CLI) +and requiring an incremental approach to development. + +== Summary of contributions + +* *Major enhancement*: Added *the ability to generate a resume and save it to a file*. +** This enhancement uses the specifications of a given template to filter and organise the entries stored in ResuMaker +into a visually appealing resume file, saved in the markdown format for easy conversion to other commonly used document formats. +** This enhancement is a core feature of the project and brings together the work of the team to generate a resume. +** This enhancement was designed to be modular enough to allow for easy extension. +For instance, the use of markdown to format the text of the resume was designed such that it would be easy to +change the layout of the resume (such as changing the font size of its section headers +or adding other content to the resume) or even switch to a different file format altogether. +** This enhancement made use of an link:https://github.com/Steppschuh/Java-Markdown-Generator[external library] to handle the +generation of markdown text in a more organised way rather than manipulating the text directly. +While this functionality was important, it is not essential to the feature - generating the markdown text of the resume +could have been done without it, although it would have either taken more work to develop the functionality separately +or been much messier. + +* *Code contributed*: https://nus-cs2103-ay1819s1.github.io/cs2103-dashboard/#=undefined&search=scalarmotion&sort=displayName&since=2018-09-12&until=2018-11-10&timeframe=day&reverse=false&repoSort=true[Reposense Overview] + +<<< + +Functional code snippet: +``` + public Resume(Model model) { + requireAllNonNull(model); + this.model = model; + + template = model.getLoadedTemplate() + .orElseThrow(() -> new IllegalArgumentException("Template cannot be blank.")); + requireAllNonNull(template); + + resumeHeader = new ResumeHeader(model.getUserParticulars()); + requireAllNonNull(resumeHeader); + + resumeSectionList = new ArrayList<>(); + populateSectionList(); + } + + /** + * Populates the section list by using each TemplateSection to fetch a set of entries belonging to that section. + */ + private void populateSectionList() { + ArrayList templateSections = template.getSections(); + for (TemplateSection templateSection : templateSections) { + resumeSectionList.add(fetchSectionEntries(templateSection)); + } + } + + /** + * Fetches a ResumeSection containing entries which match the tags specified in the + * @param templateSection and + * @return the ResumeSection containing the entries specified. + */ + private ResumeSection fetchSectionEntries(TemplateSection templateSection) { + Predicate sectionPredicate = templateSection.getCategoryPredicate() + .and(templateSection.getTagPredicate()); + return new ResumeSection(templateSection.getTitle(), + model.getFilteredEntryList(sectionPredicate)); + } +``` + +<<< + +Test code snippet: +``` + @Test + public void resumeConstructorTest() { + // no template loaded + assertThrows(IllegalArgumentException.class, "Template cannot be blank.", () -> new Resume(new ModelManager())); + + Model testModel = TypicalResumeModel.getDefaultTemplateModel(); + + // default template, no entries + Resume blankResume = new Resume(testModel); + List expectedBlankSectionList = new ArrayList<>(); + Template defaultTemplate = Template.getDefaultTemplate(); + for (TemplateSection templateSection : defaultTemplate.getSections()) { + expectedBlankSectionList.add(new ResumeSection(templateSection.getTitle(), new ArrayList())); + } + assertEquals(expectedBlankSectionList, blankResume.getSectionList()); + + // default template, typical entries + List typicalEntries = TypicalEntrys.getTypicalEntries(); + for (ResumeEntry entry : typicalEntries) { + testModel.addEntry(entry); + } + Resume typicalResume = new Resume(testModel); + // filtering entries manually to get expected section list + List expectedTypicalSectionList = new ArrayList<>(); + for (TemplateSection templateSection : defaultTemplate.getSections()) { + List testEntryList = new ArrayList<>(); + for (ResumeEntry entry : typicalEntries) { + if (templateSection.getCategoryPredicate().test(entry) + && templateSection.getTagPredicate().test(entry)) { + testEntryList.add(entry); + } + } + expectedTypicalSectionList.add(new ResumeSection(templateSection.getTitle(), testEntryList)); + } + assertEquals(expectedTypicalSectionList, typicalResume.getSectionList()); + } +``` + +<<< +* *Other contributions*: +** Project management: +*** Designed a link:https://github.com/CS2103-AY1819S1-W17-1/main/issues/62[neat, organised format] of +managing development issues for use by the project team. +*** Coordinated API design between team members across components and features to ensure smooth integration. +*** Reviewed test coverage and quality. +** Documentation: +*** Fixed writing style issues in the +link:https://github.com/CS2103-AY1819S1-W17-1/main/commit/f59e486c11887eb49c214738fb989161d591ba49[User Guide] and +link:https://github.com/CS2103-AY1819S1-W17-1/main/commit/e3509a0af98fce9277896e5d03338ff1c20f72e1[Developer Guide]. +** Community: +*** Reviewed pull requests (with non-trivial review comments): +link:https://github.com/CS2103-AY1819S1-W17-1/main/pull/119[#119], +link:https://github.com/CS2103-AY1819S1-W17-1/main/pull/152[#152], +link:https://github.com/CS2103-AY1819S1-W17-1/main/pull/163[#163], +link:https://github.com/CS2103-AY1819S1-W17-1/main/pull/220[#220], +link:https://github.com/CS2103-AY1819S1-W17-1/main/pull/231[#231], +link:https://github.com/CS2103-AY1819S1-W17-1/main/pull/280[#280] +** Tools: +*** Integrated a third party library to the project to generate markdown text: +link:https://github.com/CS2103-AY1819S1-W17-1/main/pull/33[#33], link:https://github.com/CS2103-AY1819S1-W17-1/main/pull/112[#112]. + +<<< +== Contributions to the User Guide + + +|=== +|_Here are some sections I contributed to the User Guide which showcase my ability to write documentation for end-users._ +|=== + +include::../UserGuide.adoc[tag=wangfanUGDiag] +include::../UserGuide.adoc[tag=wangfanUG] +include::../UserGuide.adoc[tag=resume] +<<< +== Contributions to the Developer Guide + +|=== +|_Here are some sections I contributed to the Developer Guide which showcase the +technical depth of my contributions to the project, as well as my ability to document them._ +|=== + +include::../DeveloperGuide.adoc[tag=resume] diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index ecdd043a4f81..b50b41c797a3 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -1,5 +1,7 @@ package seedu.address; +import static seedu.address.storage.AwarenessStorage.AWARENESS_FILEPATH; + import java.io.IOException; import java.nio.file.Path; import java.util.Optional; @@ -20,18 +22,22 @@ import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; +import seedu.address.model.EntryBook; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyEntryBook; import seedu.address.model.UserPrefs; +import seedu.address.model.awareness.Awareness; import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; import seedu.address.storage.JsonUserPrefsStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; +import seedu.address.storage.TemplateStorage; +import seedu.address.storage.TxtTemplateStorage; import seedu.address.storage.UserPrefsStorage; -import seedu.address.storage.XmlAddressBookStorage; +import seedu.address.storage.XmlAwarenessStorage; +import seedu.address.storage.entry.EntryBookStorage; +import seedu.address.storage.entry.XmlEntryBookStorage; import seedu.address.ui.Ui; import seedu.address.ui.UiManager; @@ -40,7 +46,7 @@ */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 3, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -54,7 +60,7 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing ResuMaker ]==========================="); super.init(); AppParameters appParameters = AppParameters.parse(getParameters()); @@ -62,18 +68,20 @@ public void init() throws Exception { UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new XmlAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); - initLogging(config); + EntryBookStorage entryBookStorage = new XmlEntryBookStorage(userPrefs.getEntryBookFilePath()); + TemplateStorage templateStorage = new TxtTemplateStorage(); + storage = new StorageManager(entryBookStorage, templateStorage, userPrefsStorage); + + initLogging(config); model = initModelManager(storage, userPrefs); logic = new LogicManager(model); - ui = new UiManager(logic, config, userPrefs); initEventsCenter(); + } /** @@ -82,23 +90,47 @@ public void init() throws Exception { * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. */ private Model initModelManager(Storage storage, UserPrefs userPrefs) { - Optional addressBookOptional; - ReadOnlyAddressBook initialData; + final String messageFileNotFound = "Data file not found. Will be starting with a sample %s."; + final String messageFormatProblem = "Data file not in the correct format. Will be starting with an empty %s."; + final String messageIoProblem = "Problem while reading from the file. Will be starting with an empty %s."; + + Optional entryBookOptional; + ReadOnlyEntryBook initialDataForEntryBook; + try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + entryBookOptional = storage.readEntryBook(); + initialDataForEntryBook = entryBookOptional.orElseGet(() -> { + logger.info(String.format(messageFileNotFound, "entrybook")); + return SampleDataUtil.getSampleEntryBook(); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + ); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning(String.format(messageFormatProblem, "entrybook")); + initialDataForEntryBook = new EntryBook(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning(String.format(messageIoProblem, "entrybook")); + initialDataForEntryBook = new EntryBook(); + } + + Optional awarenessOptional; + Awareness awareness; + + try { + awarenessOptional = new XmlAwarenessStorage(AWARENESS_FILEPATH).readAwarenessData(); + awareness = awarenessOptional.orElseGet(() -> { + logger.info(String.format(messageFileNotFound, "awareness")); + return SampleDataUtil.getSampleAwareness(); + }); + + } catch (DataConversionException e) { + logger.warning(String.format(messageFormatProblem, "awareness")); + awareness = new Awareness(); + } catch (IOException e) { + logger.warning(String.format(messageIoProblem, "awareness")); + awareness = new Awareness(); } - return new ModelManager(initialData, userPrefs); + return new ModelManager(initialDataForEntryBook, userPrefs, awareness); } private void initLogging(Config config) { @@ -179,13 +211,13 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting ResuMaker " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping ResuMaker ] ============================="); ui.stop(); try { storage.saveUserPrefs(userPrefs); diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index e978d621e086..3e15c1491a4f 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -13,7 +13,7 @@ public class Config { public static final Path DEFAULT_CONFIG_FILE = Paths.get("config.json"); // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "ResuMaker"; private Level logLevel = Level.INFO; private Path userPrefsFilePath = Paths.get("preferences.json"); diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..1234c65d4cb8 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -9,5 +9,8 @@ public class Messages { public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_ENTRIES_LISTED_OVERVIEW = "%1$d entries listed!"; + + public static final String MESSAGE_INVALID_ENTRY_DISPLAYED_INDEX = "The entry index provided is invalid"; } diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java deleted file mode 100644 index b72ad4740e5a..000000000000 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.commons.events.model; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.ReadOnlyAddressBook; - -/** Indicates the AddressBook in the model has changed*/ -public class AddressBookChangedEvent extends BaseEvent { - - public final ReadOnlyAddressBook data; - - public AddressBookChangedEvent(ReadOnlyAddressBook data) { - this.data = data; - } - - @Override - public String toString() { - return "number of persons " + data.getPersonList().size(); - } -} diff --git a/src/main/java/seedu/address/commons/events/model/EntryBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/EntryBookChangedEvent.java new file mode 100644 index 000000000000..c36be2e6d7cf --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/EntryBookChangedEvent.java @@ -0,0 +1,18 @@ +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.ReadOnlyEntryBook; + +/** Indicates the EntryBook in the model has changed*/ +public class EntryBookChangedEvent extends BaseEvent { + public final ReadOnlyEntryBook data; + + public EntryBookChangedEvent(ReadOnlyEntryBook data) { + this.data = data; + } + + @Override + public String toString() { + return "number of entries " + data.getEntryList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/model/ResumeSaveEvent.java b/src/main/java/seedu/address/commons/events/model/ResumeSaveEvent.java new file mode 100644 index 000000000000..38d6aec9c269 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/ResumeSaveEvent.java @@ -0,0 +1,24 @@ +package seedu.address.commons.events.model; + +import java.nio.file.Path; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.resume.Resume; + +/** Indicates that a resume is being saved. */ +public class ResumeSaveEvent extends BaseEvent { + + public final Resume data; + + public final Path filePath; + + public ResumeSaveEvent(Resume data, Path filePath) { + this.data = data; + this.filePath = filePath; + } + + @Override + public String toString() { + return "save resume to " + filePath.toString(); + } +} diff --git a/src/main/java/seedu/address/commons/events/model/TemplateLoadRequestedEvent.java b/src/main/java/seedu/address/commons/events/model/TemplateLoadRequestedEvent.java new file mode 100644 index 000000000000..406d5d7c4403 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/TemplateLoadRequestedEvent.java @@ -0,0 +1,20 @@ +package seedu.address.commons.events.model; + +import java.nio.file.Path; + +import seedu.address.commons.events.BaseEvent; + +/** Indicates there has been a call to load a Template from a file*/ +public class TemplateLoadRequestedEvent extends BaseEvent { + + public final Path filePath; + + public TemplateLoadRequestedEvent(Path filePath) { + this.filePath = filePath; + } + + @Override + public String toString() { + return "Request to load template from " + filePath.toString(); + } +} diff --git a/src/main/java/seedu/address/commons/events/storage/TemplateLoadedEvent.java b/src/main/java/seedu/address/commons/events/storage/TemplateLoadedEvent.java new file mode 100644 index 000000000000..1ea4564eec10 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/storage/TemplateLoadedEvent.java @@ -0,0 +1,29 @@ +package seedu.address.commons.events.storage; + +import java.nio.file.Path; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.template.Template; + +/** + * Indicates that a template has been successfully loaded + */ +public class TemplateLoadedEvent extends BaseEvent { + + public final Template template; + public final Path filePath; + + public TemplateLoadedEvent(Template template, Path filePath) { + this.template = template; + this.filePath = filePath; + } + + public Template getTemplate() { + return template; + } + + @Override + public String toString() { + return "Successfully loaded template from " + filePath.toString(); + } +} diff --git a/src/main/java/seedu/address/commons/events/storage/TemplateLoadingExceptionEvent.java b/src/main/java/seedu/address/commons/events/storage/TemplateLoadingExceptionEvent.java new file mode 100644 index 000000000000..f33e59456aba --- /dev/null +++ b/src/main/java/seedu/address/commons/events/storage/TemplateLoadingExceptionEvent.java @@ -0,0 +1,24 @@ +package seedu.address.commons.events.storage; + +import java.nio.file.Path; + +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates an exception during loading of a template + */ +public class TemplateLoadingExceptionEvent extends BaseEvent { + + public final Exception exception; + public final Path filePath; + + public TemplateLoadingExceptionEvent(Exception exception, Path filePath) { + this.exception = exception; + this.filePath = filePath; + } + + @Override + public String toString() { + return "Exception while attempting to load " + filePath + ":\n" + exception.toString(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/ContextUpdateEvent.java b/src/main/java/seedu/address/commons/events/ui/ContextUpdateEvent.java new file mode 100644 index 000000000000..cb2b3b0dae25 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/ContextUpdateEvent.java @@ -0,0 +1,21 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates that a new context related update is available. + */ +public class ContextUpdateEvent extends BaseEvent { + + public final String message; + + public ContextUpdateEvent(String message) { + this.message = message; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToEntryListRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/JumpToEntryListRequestEvent.java new file mode 100644 index 000000000000..37a6eba16dd0 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/JumpToEntryListRequestEvent.java @@ -0,0 +1,21 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.BaseEvent; + +/** + * Indicates a request to jump to the list of entries + */ +public class JumpToEntryListRequestEvent extends BaseEvent { + + public final int targetIndex; + + public JumpToEntryListRequestEvent(Index targetIndex) { + this.targetIndex = targetIndex.getZeroBased(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java b/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java index a5e8b2e13883..10f05f8864fb 100644 --- a/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java +++ b/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java @@ -15,7 +15,7 @@ public NewResultAvailableEvent(String message) { @Override public String toString() { - return getClass().getSimpleName(); + return getClass().getSimpleName() + "|" + message; } } diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java deleted file mode 100644 index c5c8b9ce90ed..000000000000 --- a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.commons.events.ui; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.person.Person; - -/** - * Represents a selection change in the Person List Panel - */ -public class PersonPanelSelectionChangedEvent extends BaseEvent { - - - private final Person newSelection; - - public PersonPanelSelectionChangedEvent(Person newSelection) { - this.newSelection = newSelection; - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - - public Person getNewSelection() { - return newSelection; - } -} diff --git a/src/main/java/seedu/address/commons/events/ui/UpdateExpandedEntryRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/UpdateExpandedEntryRequestEvent.java new file mode 100644 index 000000000000..88d2dbc08148 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/UpdateExpandedEntryRequestEvent.java @@ -0,0 +1,25 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.entry.ResumeEntry; + +/** + * a event indicating a need to update the ExpandedEntryPanel. + */ +public class UpdateExpandedEntryRequestEvent extends BaseEvent { + private final ResumeEntry updatedEntry; + + public UpdateExpandedEntryRequestEvent(ResumeEntry updatedEntry) { + this.updatedEntry = updatedEntry; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + public ResumeEntry getUpdatedEntry() { + return updatedEntry; + } + +} diff --git a/src/main/java/seedu/address/commons/exceptions/InvalidTemplateFileException.java b/src/main/java/seedu/address/commons/exceptions/InvalidTemplateFileException.java new file mode 100644 index 000000000000..7a3729f940a7 --- /dev/null +++ b/src/main/java/seedu/address/commons/exceptions/InvalidTemplateFileException.java @@ -0,0 +1,21 @@ +package seedu.address.commons.exceptions; + +/** + * Signals that the specified file is not a valid template file. + */ +public class InvalidTemplateFileException extends Exception { + /** + * @param message should contain relevant information on the failed constraint(s) + */ + public InvalidTemplateFileException(String message) { + super(message); + } + + /** + * @param message should contain relevant information on the failed constraint(s) + * @param cause of the main exception + */ + public InvalidTemplateFileException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/seedu/address/commons/exceptions/MarkdownProcessingException.java b/src/main/java/seedu/address/commons/exceptions/MarkdownProcessingException.java new file mode 100644 index 000000000000..c361a1975d50 --- /dev/null +++ b/src/main/java/seedu/address/commons/exceptions/MarkdownProcessingException.java @@ -0,0 +1,15 @@ +package seedu.address.commons.exceptions; + +/** + * Represents an error during conversion of data to Markdown. + */ +public class MarkdownProcessingException extends java.io.IOException { + public MarkdownProcessingException(String msg) { + super(msg); + } + + @Override public String toString() { + return getClass().getName() + ": " + getMessage(); + } +} + diff --git a/src/main/java/seedu/address/commons/util/MdUtil.java b/src/main/java/seedu/address/commons/util/MdUtil.java new file mode 100644 index 000000000000..a26bc1625ecb --- /dev/null +++ b/src/main/java/seedu/address/commons/util/MdUtil.java @@ -0,0 +1,47 @@ +package seedu.address.commons.util; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.exceptions.MarkdownProcessingException; +import seedu.address.storage.util.MarkdownConverter; + +/** + * Converts a Java object instance to Markdown and vice versa + */ +public class MdUtil { + + private static final Logger logger = LogsCenter.getLogger(MdUtil.class); + + static void serializeObjectToMdFile(Path mdFile, T objectToSerialize) throws IOException { + FileUtil.writeToFile(mdFile, toMdString(objectToSerialize)); + } + + /** + * Saves the Md object to the specified file. + * Overwrites existing file if it exists, creates a new file if it doesn't. + * @param mdFile cannot be null + * @param filePath cannot be null + * @throws IOException if there was an error during writing to the file + */ + public static void saveMdFile(T mdFile, Path filePath) throws IOException { + requireNonNull(filePath); + requireNonNull(mdFile); + + serializeObjectToMdFile(filePath, mdFile); + } + + /** + * Converts a given instance of a class into its Markdown formatted string representation + * @param instance The T object to be converted into the Markdown string + * @param The generic type to create an instance of + * @return Markdown data representation of the given class instance, in string + */ + public static String toMdString(T instance) throws MarkdownProcessingException { + return MarkdownConverter.toMarkdown(instance); + } +} diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 61cc8c9a1cb8..c8a5ba747535 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -65,4 +65,39 @@ public static boolean isNonZeroUnsignedInteger(String s) { return false; } } + + /** + * Returns true iff {@code s} is an empty string. + */ + public static boolean isEmptyString(String s) { + requireNonNull(s); + return s.equals(""); + } + + /** + * Returns true iff {@code s} is only whitespace. + */ + public static boolean isOnlyWhiteSpace(String s) { + requireNonNull(s); + return isEmptyString(s.trim()); + } + + /** + * Returns true iff {@code s} has no trailing or leading whitespaces. + */ + public static boolean isNotPaddedByWhiteSpace(String s) { + requireNonNull(s); + return s.length() == s.trim().length(); + } + + /** + * Returns true iff the given string is a single word. + * + * @param s must not have any trailing or leading whitespaces. + * @return true iff {@code s} is a single word. + */ + public static boolean isOneWord(String s) { + checkArgument(isNotPaddedByWhiteSpace(s)); + return s.split(" ").length == 1; + } } diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..41059c3f28d2 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,7 +4,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Person; +import seedu.address.model.entry.ResumeEntry; /** * API of the Logic component @@ -19,9 +19,12 @@ public interface Logic { */ CommandResult execute(String commandText) throws CommandException, ParseException; - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList getFilteredPersonList(); - /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ ListElementPointer getHistorySnapshot(); + + /** Returns an unmodifiable view of the filtered list of entries */ + ObservableList getFilteredEntryList(); + + /** Triggers any Command Line Observers held in the Logic Component */ + void observe(String currentInput); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9aff86fc33dc..d7fa4663a11f 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -1,5 +1,7 @@ package seedu.address.logic; +import java.util.LinkedList; +import java.util.List; import java.util.logging.Logger; import javafx.collections.ObservableList; @@ -8,10 +10,12 @@ import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.observers.AwarenessService; +import seedu.address.logic.observers.CmdLineObserver; +import seedu.address.logic.parser.CommandParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.entry.ResumeEntry; /** * The main LogicManager of the app. @@ -21,19 +25,28 @@ public class LogicManager extends ComponentManager implements Logic { private final Model model; private final CommandHistory history; - private final AddressBookParser addressBookParser; + private final CommandParser commandParser; + private final List cmdLineObservers = new LinkedList<>(); public LogicManager(Model model) { this.model = model; history = new CommandHistory(); - addressBookParser = new AddressBookParser(); + commandParser = new CommandParser(); + cmdLineObservers.add(new AwarenessService(model)); + } + + @Override + public void observe(String currentInput) { + for (CmdLineObserver observer : cmdLineObservers) { + observer.observe(currentInput).ifPresent(event -> raise(event)); + } } @Override public CommandResult execute(String commandText) throws CommandException, ParseException { logger.info("----------------[USER COMMAND][" + commandText + "]"); try { - Command command = addressBookParser.parseCommand(commandText); + Command command = commandParser.parseCommand(commandText); return command.execute(model, history); } finally { history.add(commandText); @@ -41,8 +54,8 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredEntryList() { + return model.getFilteredEntryList(); } @Override diff --git a/src/main/java/seedu/address/logic/commands/AddBulletCommand.java b/src/main/java/seedu/address/logic/commands/AddBulletCommand.java new file mode 100644 index 000000000000..e461c4c9bf91 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddBulletCommand.java @@ -0,0 +1,107 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.util.List; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.events.ui.JumpToEntryListRequestEvent; +import seedu.address.commons.events.ui.UpdateExpandedEntryRequestEvent; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.entry.ResumeEntry; + +/** + * Adds a bullet description to a particular entry. + */ +public class AddBulletCommand extends Command { + public static final String COMMAND_WORD = "addBullet"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a bullet description identified " + + "by the index number used in the displayed entry list.\n" + + "Parameters: INDEX (must be a positive integer) " + + "CONTENT_TO_ADD\n" + + "Example: " + COMMAND_WORD + " 1 " + + "attained Best Financial Hack Award"; + + public static final String MESSAGE_ADDBULLET_SUCCESS = "Added Bullet : %1$s"; + public static final String MESSAGE_ADDBULLET_DUPLICATE_BULLET = "This bullet already exists"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + + private final Index index; + private final String bullet; + + /** + * @param index of the entry in the filtered entry list to edit + * @param bullet details to edit the person with + */ + public AddBulletCommand(Index index, String bullet) { + requireNonNull(index); + requireNonNull(bullet); + + this.index = index; + this.bullet = bullet; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredEntryList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ENTRY_DISPLAYED_INDEX); + } + + ResumeEntry entryToEdit = lastShownList.get(index.getZeroBased()); + + if (entryToEdit.getDescription().contains(bullet)) { + throw new CommandException(MESSAGE_ADDBULLET_DUPLICATE_BULLET); + } + + ResumeEntry editedEntry = createEntryWithAddedBullet(entryToEdit, bullet); + + model.updateEntry(entryToEdit, editedEntry); + model.commitEntryBook(); + + postAddBulletCommandEvents(index, editedEntry); + + return new CommandResult(String.format(MESSAGE_ADDBULLET_SUCCESS, bullet)); + } + + /** + * raise events to UI when a bullet is successfully added to an entry. + */ + private void postAddBulletCommandEvents(Index index, ResumeEntry editedEntry) { + EventsCenter.getInstance().post(new JumpToEntryListRequestEvent(index)); + EventsCenter.getInstance().post(new UpdateExpandedEntryRequestEvent(editedEntry)); + } + + /** + * Creates and returns a {@code entry} with added bullet. + */ + private static ResumeEntry createEntryWithAddedBullet(ResumeEntry entryToEdit, String bulletToAdd) { + assert entryToEdit != null; + return entryToEdit.getEntryWithAddedBullet(bulletToAdd); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AddBulletCommand)) { + return false; + } + + // state check + AddBulletCommand e = (AddBulletCommand) other; + return index.equals(e.index) + && bullet.equals(e.bullet); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java deleted file mode 100644 index d88e831ff1ce..000000000000 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Adds a person to the address book. - */ -public class AddCommand extends Command { - - public static final String COMMAND_WORD = "add"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; - - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; - - private final Person toAdd; - - /** - * Creates an AddCommand to add the specified {@code Person} - */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - - if (model.hasPerson(toAdd)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.addPerson(toAdd); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); - } -} diff --git a/src/main/java/seedu/address/logic/commands/AddEntryCommand.java b/src/main/java/seedu/address/logic/commands/AddEntryCommand.java new file mode 100644 index 000000000000..46286b334681 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddEntryCommand.java @@ -0,0 +1,68 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import static seedu.address.logic.parser.CliSyntax.PREFIX_CATEGORY; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBHEADER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; + +import seedu.address.model.Model; +import seedu.address.model.entry.ResumeEntry; + +/** + * Adds an entry to ResuMaker. + */ +public class AddEntryCommand extends Command { + public static final String COMMAND_WORD = "addEntry"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an entry to ResuMaker " + + "Parameters: " + + PREFIX_CATEGORY + "SECTION TYPE " + + "[" + PREFIX_TAG + "TAG]..." + + "[" + PREFIX_TITLE + "TITLE] " + + "[" + PREFIX_SUBHEADER + "SUBHEADER]" + + "[" + PREFIX_DURATION + "DURATION]\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_CATEGORY + "Experience " + + PREFIX_TAG + "Java " + + PREFIX_TITLE + "The Source Enterprise " + + PREFIX_SUBHEADER + "Java Programmer intern " + + PREFIX_DURATION + "May 2010 - Aug 2010 "; + + public static final String MESSAGE_SUCCESS = "New entry added: %1$s"; + public static final String MESSAGE_DUPLICATE_ENTRY = "This entry already exists"; + + private final ResumeEntry toAdd; + + /** + * Creates an AddCommand to add the specified {@code Person} + */ + public AddEntryCommand(ResumeEntry entry) { + requireNonNull(entry); // if the object is null throw nullpointer exception, else return the object + toAdd = entry; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + if (model.hasEntry(toAdd)) { + throw new CommandException(MESSAGE_DUPLICATE_ENTRY); + } + + model.addEntry(toAdd); + model.commitEntryBook(); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddEntryCommand // instanceof handles nulls + && toAdd.equals(((AddEntryCommand) other).toAdd)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java deleted file mode 100644 index 1f85bcfe85a8..000000000000 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.logic.CommandHistory; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; - -/** - * Clears the address book. - */ -public class ClearCommand extends Command { - - public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - - - @Override - public CommandResult execute(Model model, CommandHistory history) { - requireNonNull(model); - model.resetData(new AddressBook()); - model.commitAddressBook(); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/ContextCommand.java b/src/main/java/seedu/address/logic/commands/ContextCommand.java new file mode 100644 index 000000000000..45078f00f54c --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ContextCommand.java @@ -0,0 +1,58 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Adds a pre-filled ResumeEntry. + */ +public class ContextCommand extends Command { + + public static final String COMMAND_WORD = "nus"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates a pre-filled resume entry." + + "Parameters: EXPRESSION [MORE EXPRESSIONS]...\n" + + "Expressions can be slang, partial phrases or full phrases."; + + public static final String MESSAGE_SUCCESS = "Created a resume entry for %1s."; + + public static final String MESSAGE_NO_RESUME_ENTRY = "There is no pre-filled resume entry: %1s."; + + public static final String MESSAGE_SUGGESTION = "Please update the XML data so that your slang is recognised," + + "or provide a more specific search expression."; + + /** + * A combination of slang, partial phrases or full phrases entered by the user. + * Example of an expression: "compsci", "comp sci", "ug" and "undegrad" + */ + private final String expression; + + public ContextCommand(String expression) { + + requireNonNull(expression); + this.expression = expression; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + String possibleEventName = model.getPossibleEventName(expression); + + Command addEntryCommand = model.getContextualResumeEntry(possibleEventName) + .map(AddEntryCommand::new) + .orElseThrow(() -> new CommandException(String.format(MESSAGE_NO_RESUME_ENTRY, + possibleEventName))); + + return addEntryCommand.execute(model, history); + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ContextCommand // instanceof handles nulls + && expression.equals(((ContextCommand) other).expression)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java deleted file mode 100644 index a20e9d49eac7..000000000000 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import java.util.List; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * Deletes a person identified using it's displayed index from the address book. - */ -public class DeleteCommand extends Command { - - public static final String COMMAND_WORD = "delete"; - - public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the displayed person list.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; - - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; - - private final Index targetIndex; - - public DeleteCommand(Index targetIndex) { - this.targetIndex = targetIndex; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToDelete = lastShownList.get(targetIndex.getZeroBased()); - model.deletePerson(personToDelete); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && targetIndex.equals(((DeleteCommand) other).targetIndex)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java deleted file mode 100644 index dc782d8e230f..000000000000 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ /dev/null @@ -1,228 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Edits the details of an existing person in the address book. - */ -public class EditCommand extends Command { - - public static final String COMMAND_WORD = "edit"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the displayed person list. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; - public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; - - private final Index index; - private final EditPersonDescriptor editPersonDescriptor; - - /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with - */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { - requireNonNull(index); - requireNonNull(editPersonDescriptor); - - this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); - } - - @Override - public CommandResult execute(Model model, CommandHistory history) throws CommandException { - requireNonNull(model); - List lastShownList = model.getFilteredPersonList(); - - if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); - } - - Person personToEdit = lastShownList.get(index.getZeroBased()); - Person editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - - if (!personToEdit.isSamePerson(editedPerson) && model.hasPerson(editedPerson)) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } - - model.updatePerson(personToEdit, editedPerson); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - model.commitAddressBook(); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditCommand)) { - return false; - } - - // state check - EditCommand e = (EditCommand) other; - return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor); - } - - /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. - */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; - private Set tags; - - public EditPersonDescriptor() {} - - /** - * Copy constructor. - * A defensive copy of {@code tags} is used internally. - */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); - setTags(toCopy.tags); - } - - /** - * Returns true if at least one field is edited. - */ - public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(name, phone, email, address, tags); - } - - public void setName(Name name) { - this.name = name; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public void setPhone(Phone phone) { - this.phone = phone; - } - - public Optional getPhone() { - return Optional.ofNullable(phone); - } - - public void setEmail(Email email) { - this.email = email; - } - - public Optional getEmail() { - return Optional.ofNullable(email); - } - - public void setAddress(Address address) { - this.address = address; - } - - public Optional
getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; - } - - /** - * Returns an unmodifiable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - * Returns {@code Optional#empty()} if {@code tags} is null. - */ - public Optional> getTags() { - return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { - return false; - } - - // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; - - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); - } - } -} diff --git a/src/main/java/seedu/address/logic/commands/EditEntryInfoCommand.java b/src/main/java/seedu/address/logic/commands/EditEntryInfoCommand.java new file mode 100644 index 000000000000..cdea899e65d7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/EditEntryInfoCommand.java @@ -0,0 +1,223 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_DURATION; +import static seedu.address.logic.parser.CliSyntax.PREFIX_SUBHEADER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_ENTRIES; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.category.Category; +import seedu.address.model.entry.EntryDescription; +import seedu.address.model.entry.EntryInfo; +import seedu.address.model.entry.ResumeEntry; +import seedu.address.model.tag.Tag; + +/** + * Edits the entryInfo of a particular major entry. + */ +public class EditEntryInfoCommand extends Command { + public static final String COMMAND_WORD = "editEntryInfo"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Edits the title, subtitle or duration of the entry identified " + + "by the index number used in the displayed entry list. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_TITLE + "TITLE] " + + "[" + PREFIX_SUBHEADER + "SUBTITLE] " + + "[" + PREFIX_DURATION + "DURATION]\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_TITLE + "NUS Education" + + PREFIX_SUBHEADER + "Bachelor of Computing in Computer Science"; + + public static final String MESSAGE_EDIT_ENTRYINFO_SUCCESS = "Edited Entry: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_ENTRY = "This entry already exists in the entry book."; + public static final String MESSAGE_NON_MAJOR_ENTRY = "The entry is not " + + "a major entry, i.e. does not contain title,subtitle or duration."; + + private final Index index; + private final EditEntryInfoDescriptor editEntryInfoDescriptor; + + /** + * @param index of the entry in the filtered entry list to edit + * @param editEntryInfoDescriptor EditEntryInfoDescriptor instance to edit + */ + public EditEntryInfoCommand(Index index, EditEntryInfoDescriptor editEntryInfoDescriptor) { + requireNonNull(index); + requireNonNull(editEntryInfoDescriptor); + + this.index = index; + this.editEntryInfoDescriptor = editEntryInfoDescriptor; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + List lastShownList = model.getFilteredEntryList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_ENTRY_DISPLAYED_INDEX); + } + + ResumeEntry entryToEdit = lastShownList.get(index.getZeroBased()); + + if (entryToEdit.isMinorEntry()) { + throw new CommandException(MESSAGE_NON_MAJOR_ENTRY); + } + + ResumeEntry editedEntry = createEditedEntry(entryToEdit, editEntryInfoDescriptor); + if (!entryToEdit.isSameEntry(editedEntry) && model.hasEntry(editedEntry)) { + throw new CommandException(MESSAGE_DUPLICATE_ENTRY); + } + + model.updateEntry(entryToEdit, editedEntry); + model.updateFilteredEntryList(PREDICATE_SHOW_ALL_ENTRIES); + model.commitEntryBook(); + return new CommandResult(String.format(MESSAGE_EDIT_ENTRYINFO_SUCCESS, editedEntry)); + } + + + /** + * Creates and returns a {@code ResumeEntry} with the details of {@code entryToEdit} + * edited with {@code editEntryInfoDescriptor}. + */ + private static ResumeEntry createEditedEntry(ResumeEntry entryToEdit, + EditEntryInfoDescriptor editEntryInfoDescriptor) { + assert entryToEdit != null; + // non EntryInfo fields preserved + Category updatedCategory = entryToEdit.getCategory(); + Set updatedTags = entryToEdit.getTags(); + EntryDescription updatedEntryDescription = entryToEdit.getDescription(); + + + String title = editEntryInfoDescriptor.getTitle().orElse(entryToEdit.getEntryInfo().getTitle()); + String subtitle = editEntryInfoDescriptor.getSubtitle().orElse(entryToEdit.getEntryInfo().getSubHeader()); + String duration = editEntryInfoDescriptor.getDuration().orElse(entryToEdit.getEntryInfo().getDuration()); + EntryInfo updatedEntryInfo = new EntryInfo(title, subtitle, duration); + + return new ResumeEntry(updatedCategory, updatedEntryInfo, updatedTags, updatedEntryDescription); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditEntryInfoCommand)) { + return false; + } + + // state check + EditEntryInfoCommand e = (EditEntryInfoCommand) other; + return index.equals(e.index) + && editEntryInfoDescriptor.equals(e.editEntryInfoDescriptor); + } + + + /** + * Stores the details to edit the entry with. Each non-empty field value will replace the + * corresponding field value of the entry. + */ + public static class EditEntryInfoDescriptor { + private String title; + private String subtitle; + private String duration; + + public EditEntryInfoDescriptor() {} + + /** + * Constructing a duplicated copy of EditEntryInfoDescriptor + * @param toCopy EditEntryInfoDescriptor instance to be duplicated + */ + public EditEntryInfoDescriptor(EditEntryInfoDescriptor toCopy) { + setTitle(toCopy.title); + setSubtitle(toCopy.subtitle); + setDuration(toCopy.duration); + } + + /** + * Constructing a EditEntryInfoDescriptor by extracting the title, subtitle and duration of an entry. + * @param entry ResumeEntry instance to be extracted + */ + public EditEntryInfoDescriptor(ResumeEntry entry) { + setTitle(entry.getEntryInfo().getTitle()); + setSubtitle(entry.getEntryInfo().getSubHeader()); + setDuration(entry.getEntryInfo().getDuration()); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(title, subtitle, duration); + } + + public void setTitle(String title) { + this.title = title; + } + + public Optional getTitle() { + return Optional.ofNullable(title); + } + + public void setSubtitle(String subtitle) { + this.subtitle = subtitle; + } + + public Optional getSubtitle() { + return Optional.ofNullable(subtitle); + } + + public void setDuration(String duration) { + this.duration = duration; + } + + public Optional getDuration() { + return Optional.ofNullable(duration); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditEntryInfoDescriptor)) { + return false; + } + + // state check + EditEntryInfoDescriptor e = (EditEntryInfoDescriptor) other; + + + return getTitle().equals(e.getTitle()) + && getSubtitle().equals(e.getSubtitle()) + && getDuration().equals(e.getDuration()); + } + + @Override + public String toString() { + return title + " " + subtitle + " " + duration; + } + } + + + +} + diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java deleted file mode 100644 index beb178e3a3f5..000000000000 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; - -import seedu.address.commons.core.Messages; -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; -import seedu.address.model.person.NameContainsKeywordsPredicate; - -/** - * Finds and lists all persons in address book whose name contains any of the argument keywords. - * Keyword matching is case insensitive. - */ -public class FindCommand extends Command { - - public static final String COMMAND_WORD = "find"; - - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-insensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; - - private final NameContainsKeywordsPredicate predicate; - - public FindCommand(NameContainsKeywordsPredicate predicate) { - this.predicate = predicate; - } - - @Override - public CommandResult execute(Model model, CommandHistory history) { - requireNonNull(model); - model.updateFilteredPersonList(predicate); - return new CommandResult( - String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, model.getFilteredPersonList().size())); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && predicate.equals(((FindCommand) other).predicate)); // state check - } -} diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java deleted file mode 100644 index 6d44824c7d1b..000000000000 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package seedu.address.logic.commands; - -import static java.util.Objects.requireNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; - -import seedu.address.logic.CommandHistory; -import seedu.address.model.Model; - -/** - * Lists all persons in the address book to the user. - */ -public class ListCommand extends Command { - - public static final String COMMAND_WORD = "list"; - - public static final String MESSAGE_SUCCESS = "Listed all persons"; - - - @Override - public CommandResult execute(Model model, CommandHistory history) { - requireNonNull(model); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(MESSAGE_SUCCESS); - } -} diff --git a/src/main/java/seedu/address/logic/commands/LoadTemplateCommand.java b/src/main/java/seedu/address/logic/commands/LoadTemplateCommand.java new file mode 100644 index 000000000000..7ce2fbf16d17 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LoadTemplateCommand.java @@ -0,0 +1,113 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.nio.file.Path; +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import seedu.address.commons.core.EventsCenter; +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.events.storage.TemplateLoadedEvent; +import seedu.address.commons.events.storage.TemplateLoadingExceptionEvent; +import seedu.address.commons.exceptions.InvalidTemplateFileException; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; + +/** + * Loads a template from file. + */ +public class LoadTemplateCommand extends Command { + + public static final String COMMAND_WORD = "loadtemplate"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Loads a template file. " + + "Parameters: " + + "FILEPATH\n" + + "Example: " + COMMAND_WORD + " " + + "template1.txt"; + + public static final String MESSAGE_SUCCESS = "Successful load from %1$s."; + public static final String MESSAGE_FILE_NOT_FOUND = "File %1$s not found."; + public static final String MESSAGE_INVALID_FILE_FORMAT = "File %1$s has invalid format.\n" + + "Specified file should consist only of lines of the format: " + + "CATEGORY_TILE:~CATEGORY_TAG:[TAG_GROUP]... " + + "with no extra newlines or spaces,\n" + + "with each TAG_GROUP of the format: " + + "TAG[&TAG]..."; + + private static final Logger logger = LogsCenter.getLogger(LoadTemplateCommand.class); + + private final Path filePath; + private boolean isSuccessful; + private Exception exception; + + /** + * Creates a LoadTemplateCommand to load the specified {@code Template} + */ + public LoadTemplateCommand(Path filePath) { + requireNonNull(filePath); + this.filePath = filePath; + EventsCenter.getInstance().registerHandler(this); + } + + public boolean isSuccessful() { + return isSuccessful; + } + + public void setSuccessful(boolean successful) { + isSuccessful = successful; + } + + public Exception getException() { + return exception; + } + + public void setException(Exception exception) { + this.exception = exception; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) throws CommandException { + requireNonNull(model); + + model.loadTemplate(filePath); + + // as events are handled sequentially, the listeners will set isSuccessful before the function continues + // executing + if (!isSuccessful) { + if (exception instanceof InvalidTemplateFileException) { + throw new CommandException(String.format(MESSAGE_INVALID_FILE_FORMAT, filePath)); + } else { + throw new CommandException(String.format(MESSAGE_FILE_NOT_FOUND, filePath)); + } + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, filePath)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LoadTemplateCommand // instanceof handles nulls + && filePath.equals(((LoadTemplateCommand) other).filePath)); + } + + @Subscribe + public void handleTemplateLoadedEvent(TemplateLoadedEvent event) { + isSuccessful = true; + logger.info(LogsCenter.getEventHandlingLogMessage(event, "Template loading succeeded, " + + "loadtemplate result updated")); + } + + @Subscribe + public void handleTemplateLoadingExceptionEvent(TemplateLoadingExceptionEvent event) { + isSuccessful = false; + exception = event.exception; + logger.info(LogsCenter.getEventHandlingLogMessage(event, "Template loading failed, " + + "loadtemplate result updated")); + } +} + diff --git a/src/main/java/seedu/address/logic/commands/MakeCommand.java b/src/main/java/seedu/address/logic/commands/MakeCommand.java new file mode 100644 index 000000000000..4c44994717a7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/MakeCommand.java @@ -0,0 +1,55 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import java.nio.file.Path; +import java.util.Optional; + +import seedu.address.logic.CommandHistory; +import seedu.address.model.Model; +import seedu.address.model.template.Template; + +/** + * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Keyword matching is case insensitive. + */ +public class MakeCommand extends Command { + + public static final String COMMAND_WORD = "make"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Generates a resume using stored entries " + + "matching the currently loaded template and saves it in a file with the specified name.\n" + + "Will only work if there is a template currently loaded.\n" + + "Parameters: FILENAME\n" + + "Example: " + COMMAND_WORD + " sep.txt"; + + public static final String MESSAGE_SUCCESS = "Resume successfully generated at: %s"; + public static final String MESSAGE_NO_TEMPLATE_FAILURE = "There is no template loaded."; + + private final Path filename; + + public MakeCommand(Path filename) { + this.filename = filename; + } + + @Override + public CommandResult execute(Model model, CommandHistory history) { + requireNonNull(model); + Optional