Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add deadline feature #38

Merged
merged 15 commits into from Oct 21, 2019
8 changes: 8 additions & 0 deletions docs/UserGuide.adoc
Expand Up @@ -38,6 +38,7 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window.
* *`list`* : lists all modules
* **`add`**`cs2103t` : adds a module with module code `cs2103t` to be tracked by ModuleBook.
* **`delete`**`cs2103t` : deletes module with code `cs2103t`
* **`deadline`**`1`**`d/`**`complete homework` : adds deadline to module
* *`exit`* : exits the app

. Refer to <<Features>> for details of each command.
Expand Down Expand Up @@ -80,6 +81,13 @@ Format: `view MODULE_CODE`

Example: `view cs1231`

=== Add deadline to the module: `deadline`

Adds deadline to tracked module

Format: `deadline MODULE_LIST_NUMBER d/ deadline description`

Example: `deadline 1 d/ finish homework`

=== Listing all modules : `list`

Expand Down
81 changes: 81 additions & 0 deletions src/main/java/seedu/module/logic/commands/AddDeadlineCommand.java
@@ -0,0 +1,81 @@
package seedu.module.logic.commands;

import static seedu.module.logic.parser.CliSyntax.PREFIX_DEADLINE;

import java.util.List;

import seedu.module.commons.core.Messages;

import seedu.module.commons.core.index.Index;
import seedu.module.logic.commands.exceptions.CommandException;
import seedu.module.model.Model;
import seedu.module.model.module.Deadline;
import seedu.module.model.module.TrackedModule;


/**
* Adds deadline to be module.
*/
public class AddDeadlineCommand extends Command {

public static final String COMMAND_WORD = "deadline";

public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a deadline to a specific module. "
+ "Parameters: "
+ "INDEX (must be a positive integer) "
+ PREFIX_DEADLINE + "DESCRIPTION\n"
+ "Example: " + COMMAND_WORD + " 1 "
+ PREFIX_DEADLINE + " quiz submission /by 2/2/2019 2359";

public static final String MESSAGE_ADD_DEADLINE_SUCCESS = "Added deadline to Module: %1$s";
public static final String MESSAGE_DELETE_DEADLINE_SUCCESS = "Removed deadline from module: %1$s";

private final Index index;
private final Deadline deadline;

public AddDeadlineCommand(Index index, Deadline deadline) {
this.index = index;
this.deadline = deadline;
}

@Override
public CommandResult execute(Model model) throws CommandException {
List<TrackedModule> lastShownList = model.getFilteredModuleList();

if (index.getZeroBased() >= lastShownList.size()) {
throw new CommandException(Messages.MESSAGE_INVALID_MODULE_DISPLAYED_INDEX);
}

TrackedModule moduleToEdit = lastShownList.get(index.getZeroBased());
TrackedModule editedModule = lastShownList.get(index.getZeroBased());
editedModule.setDeadline(deadline);

model.setModule(moduleToEdit, editedModule);
model.updateFilteredModuleList(Model.PREDICATE_SHOW_ALL_MODULES);
model.displayTrackedList();

return new CommandResult(generateSuccessMessage(editedModule));
}

/**
* Generates a command execution success message based on whether the remark is added to or removed from
* {@code moduleToEdit}.
*/
private String generateSuccessMessage(TrackedModule moduleToEdit) {
String message = !deadline.getValue().isEmpty() ? MESSAGE_ADD_DEADLINE_SUCCESS
geshuming marked this conversation as resolved.
Show resolved Hide resolved
: MESSAGE_DELETE_DEADLINE_SUCCESS;
return String.format(message, moduleToEdit);
}

@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof AddDeadlineCommand)) {
return false;
}
AddDeadlineCommand e = (AddDeadlineCommand) other;
return index.equals(e.deadline);
}
}
@@ -0,0 +1,35 @@
package seedu.module.logic.parser;

import static seedu.module.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.module.logic.parser.CliSyntax.PREFIX_DEADLINE;

import seedu.module.commons.core.index.Index;
import seedu.module.logic.commands.AddDeadlineCommand;
import seedu.module.logic.parser.exceptions.ParseException;
import seedu.module.model.module.Deadline;

/**
* Parses input arguments and creates a new AddDeadlineCommand object.
*/
public class AddDeadlineCommandParser implements Parser<AddDeadlineCommand> {

/**
* Parses the given {@code String} of arguments in the context of the AddDeadlineCommand
* and returns an AddDeadlineCommand object for execution.
* @param args
* @return AddDeadlineCommand
* @throws ParseException if the user input does not conform the expected format.
*/
public AddDeadlineCommand parse(String args) throws ParseException {
String trimmedArgs = args.trim();
if (trimmedArgs.isEmpty()) {
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddDeadlineCommand.MESSAGE_USAGE));
}

ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_DEADLINE);
Index index = ParserUtil.parseIndex(argMultimap.getPreamble());
String deadline = argMultimap.getValue(PREFIX_DEADLINE).orElse("");
return new AddDeadlineCommand(index, new Deadline(deadline));
}
}
62 changes: 62 additions & 0 deletions src/main/java/seedu/module/logic/parser/ArgumentMultimap.java
@@ -0,0 +1,62 @@
package seedu.module.logic.parser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* Stores mapping of prefixes to their respective arguments.
* Each key may be associated with multiple argument values.
* Values for a given key are stored in a list, and the insertion ordering is maintained.
* Keys are unique, but the list of argument values may contain duplicate argument values, i.e. the same argument value
* can be inserted multiple times for the same prefix.
*/
public class ArgumentMultimap {

/** Prefixes mapped to their respective arguments**/
private final Map<Prefix, List<String>> argMultimap = new HashMap<>();

/**
* Associates the specified argument value with {@code prefix} key in this map.
* If the map previously contained a mapping for the key, the new value is appended to the list of existing values.
*
* @param prefix Prefix key with which the specified argument value is to be associated
* @param argValue Argument value to be associated with the specified prefix key
*/
public void put(Prefix prefix, String argValue) {
List<String> argValues = getAllValues(prefix);
argValues.add(argValue);
argMultimap.put(prefix, argValues);
}

/**
* Returns the last value of {@code prefix}.
*/
public Optional<String> getValue(Prefix prefix) {
List<String> values = getAllValues(prefix);
return values.isEmpty() ? Optional.empty() : Optional.of(values.get(values.size() - 1));
}

/**
* Returns all values of {@code prefix}.
* If the prefix does not exist or has no values, this will return an empty list.
* Modifying the returned list will not affect the underlying data structure of the ArgumentMultimap.
*/
public List<String> getAllValues(Prefix prefix) {
if (!argMultimap.containsKey(prefix)) {
return new ArrayList<>();
}
return new ArrayList<>(argMultimap.get(prefix));
}

/**
* Returns the preamble (text before the first valid prefix). Trims any leading/trailing spaces.
*/
public String getPreamble() {
return getValue(new Prefix("")).orElse("");
}
}


150 changes: 150 additions & 0 deletions src/main/java/seedu/module/logic/parser/ArgumentTokenizer.java
@@ -0,0 +1,150 @@
package seedu.module.logic.parser;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
* Tokenizes arguments string of the form: {@code preamble <prefix>value <prefix>value ...}<br>
* e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.<br>
* 1. An argument's value can be an empty string e.g. the value of {@code k/} in the above example.<br>
* 2. Leading and trailing whitespaces of an argument value will be discarded.<br>
* 3. An argument may be repeated and all its values will be accumulated e.g. the value of {@code t/}
* in the above example.<br>
*/
public class ArgumentTokenizer {

/**
* Tokenizes an arguments string and returns an {@code ArgumentMultimap} object that maps prefixes to their
* respective argument values. Only the given prefixes will be recognized in the arguments string.
*
* @param argsString Arguments string of the form: {@code preamble <prefix>value <prefix>value ...}
* @param prefixes Prefixes to tokenize the arguments string with
* @return ArgumentMultimap object that maps prefixes to their arguments
*/
public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) {
List<PrefixPosition> positions = findAllPrefixPositions(argsString, prefixes);
return extractArguments(argsString, positions);
}

/**
* Finds all zero-based prefix positions in the given arguments string.
*
* @param argsString Arguments string of the form: {@code preamble <prefix>value <prefix>value ...}
* @param prefixes Prefixes to find in the arguments string
* @return List of zero-based prefix positions in the given arguments string
*/
private static List<PrefixPosition> findAllPrefixPositions(String argsString, Prefix... prefixes) {
return Arrays.stream(prefixes)
.flatMap(prefix -> findPrefixPositions(argsString, prefix).stream())
.collect(Collectors.toList());
}

/**
* {@see findAllPrefixPositions}
*/
private static List<PrefixPosition> findPrefixPositions(String argsString, Prefix prefix) {
List<PrefixPosition> positions = new ArrayList<>();

int prefixPosition = findPrefixPosition(argsString, prefix.getPrefix(), 0);
while (prefixPosition != -1) {
PrefixPosition extendedPrefix = new PrefixPosition(prefix, prefixPosition);
positions.add(extendedPrefix);
prefixPosition = findPrefixPosition(argsString, prefix.getPrefix(), prefixPosition);
}

return positions;
}

/**
* Returns the index of the first occurrence of {@code prefix} in
* {@code argsString} starting from index {@code fromIndex}. An occurrence
* is valid if there is a whitespace before {@code prefix}. Returns -1 if no
* such occurrence can be found.
*
* E.g if {@code argsString} = "e/hip/900", {@code prefix} = "p/" and
* {@code fromIndex} = 0, this method returns -1 as there are no valid
* occurrences of "p/" with whitespace before it. However, if
* {@code argsString} = "e/hi p/900", {@code prefix} = "p/" and
* {@code fromIndex} = 0, this method returns 5.
*/
private static int findPrefixPosition(String argsString, String prefix, int fromIndex) {
int prefixIndex = argsString.indexOf(" " + prefix, fromIndex);
return prefixIndex == -1 ? -1
: prefixIndex + 1; // +1 as offset for whitespace
}

/**
* Extracts prefixes and their argument values, and returns an {@code ArgumentMultimap} object that maps the
* extracted prefixes to their respective arguments. Prefixes are extracted based on their zero-based positions in
* {@code argsString}.
*
* @param argsString Arguments string of the form: {@code preamble <prefix>value <prefix>value ...}
* @param prefixPositions Zero-based positions of all prefixes in {@code argsString}
* @return ArgumentMultimap object that maps prefixes to their arguments
*/
private static ArgumentMultimap extractArguments(String argsString, List<PrefixPosition> prefixPositions) {

// Sort by start position
prefixPositions.sort((prefix1, prefix2) -> prefix1.getStartPosition() - prefix2.getStartPosition());

// Insert a PrefixPosition to represent the preamble
PrefixPosition preambleMarker = new PrefixPosition(new Prefix(""), 0);
prefixPositions.add(0, preambleMarker);

// Add a dummy PrefixPosition to represent the end of the string
PrefixPosition endPositionMarker = new PrefixPosition(new Prefix(""), argsString.length());
prefixPositions.add(endPositionMarker);

// Map prefixes to their argument values (if any)
ArgumentMultimap argMultimap = new ArgumentMultimap();
for (int i = 0; i < prefixPositions.size() - 1; i++) {
// Extract and store prefixes and their arguments
Prefix argPrefix = prefixPositions.get(i).getPrefix();
String argValue = extractArgumentValue(argsString, prefixPositions.get(i), prefixPositions.get(i + 1));
argMultimap.put(argPrefix, argValue);
}

return argMultimap;
}

/**
* Returns the trimmed value of the argument in the arguments string specified by {@code currentPrefixPosition}.
* The end position of the value is determined by {@code nextPrefixPosition}.
*/
private static String extractArgumentValue(String argsString,
PrefixPosition currentPrefixPosition,
PrefixPosition nextPrefixPosition) {
Prefix prefix = currentPrefixPosition.getPrefix();

int valueStartPos = currentPrefixPosition.getStartPosition() + prefix.getPrefix().length();
String value = argsString.substring(valueStartPos, nextPrefixPosition.getStartPosition());

return value.trim();
}

/**
* Represents a prefix's position in an arguments string.
*/
private static class PrefixPosition {
private int startPosition;
private final Prefix prefix;

PrefixPosition(Prefix prefix, int startPosition) {
this.prefix = prefix;
this.startPosition = startPosition;
}

int getStartPosition() {
return startPosition;
}

Prefix getPrefix() {
return prefix;
}
}

}


12 changes: 12 additions & 0 deletions src/main/java/seedu/module/logic/parser/CliSyntax.java
@@ -0,0 +1,12 @@
package seedu.module.logic.parser;

/**
* Contains Command Line Interface (CLI) syntax definitions common to multiple commands
*/
public class CliSyntax {

/* Prefix definitions */
public static final Prefix PREFIX_DEADLINE = new Prefix("d/");

}

4 changes: 4 additions & 0 deletions src/main/java/seedu/module/logic/parser/ModuleBookParser.java
Expand Up @@ -7,6 +7,7 @@
import java.util.regex.Pattern;

import seedu.module.logic.commands.AddCommand;
import seedu.module.logic.commands.AddDeadlineCommand;
import seedu.module.logic.commands.Command;
import seedu.module.logic.commands.DeleteCommand;
import seedu.module.logic.commands.ExitCommand;
Expand Down Expand Up @@ -42,6 +43,9 @@ public Command parseCommand(String userInput) throws ParseException {
final String arguments = matcher.group("arguments");
switch (commandWord) {

case AddDeadlineCommand.COMMAND_WORD:
return new AddDeadlineCommandParser().parse(arguments);

case AddCommand.COMMAND_WORD:
return new AddCommandParser().parse(arguments);

Expand Down