Skip to content

Commit

Permalink
Merge pull request #199
Browse files Browse the repository at this point in the history
Validate options
  • Loading branch information
NereusWB922 authored Oct 31, 2023
2 parents 8863fb4 + c2128fb commit 6571771
Show file tree
Hide file tree
Showing 19 changed files with 197 additions and 98 deletions.
1 change: 1 addition & 0 deletions src/main/java/seedu/address/logic/Messages.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class Messages {
public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s";
public static final String MESSAGE_DUPLICATE_FIELDS =
"Multiple values specified for the following single-valued field(s): ";
public static final String MESSAGE_NO_OPTIONS = "%1$s: does not take any options.";

/**
* Returns an error message indicating the duplicate prefixes.
Expand Down
14 changes: 8 additions & 6 deletions src/main/java/seedu/address/logic/commands/EditCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class EditCommand extends Command {

public static final String COMMAND_WORD = "edit";

public static final String ERROR_MESSAGE_INVALID_PATH = "This path is invalid.";
public static final String ERROR_MESSAGE_INVALID_PATH = "This path cannot be edited.";

public static final String ERROR_MESSAGE_UNSUPPORTED_PATH_OPERATION = "Path operation is not supported";

Expand Down Expand Up @@ -164,11 +164,13 @@ private CommandResult handleEditGroup(Model model) throws CommandException {
rootOperation.addChild(editedGroup.getId(), editedGroup);

// If edited group is current path, need to redirect with new Id.
try {
model.changeDirectory(model.getCurrPath().resolve(RelativePath.PARENT));
model.changeDirectory(model.getCurrPath().resolve(new RelativePath(editedGroup.getId().toString())));
} catch (InvalidPathException e) {
throw new IllegalArgumentException("Internal Error: " + e.getMessage());
if (target.equals(model.getCurrPath())) {
try {
model.changeDirectory(model.getCurrPath().resolve(RelativePath.PARENT));
model.changeDirectory(model.getCurrPath().resolve(new RelativePath(editedGroup.getId().toString())));
} catch (InvalidPathException e) {
throw new IllegalArgumentException("Internal Error: " + e.getMessage());
}
}

model.updateList();
Expand Down
56 changes: 45 additions & 11 deletions src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,49 @@

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

/**
* Tokenizes arguments string of the form: {@code preamble <option>value <option>value ...}<br>
* e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where options 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>
* Tokenizes arguments string of the form: {@code preamble <option> value <option> value ...}<br>
* e.g. {@code some preamble text -t 11.00 -t 12.00 -k -m July} where options 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/}
* 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 {

/**
* Extracts preamble from the argString.
* String before the first "-".
*/
public static String extractPreamble(String argString) {
String[] s = argString.split(" -", 2);
return s[0].trim();
}

/**
* Extracts all option names appear in argString.
*/
public static Set<String> extractAllOptionNames(String argString) {
Set<String> set = new HashSet<>();
String[] splitBySpace = argString.split(" ");
for (String s : splitBySpace) {
if (s.startsWith("-") || s.startsWith("--")) {
set.add(s);
}
}
return set;
}

/**
* Tokenizes an arguments string and returns an {@code ArgumentMultimap} object that maps options to their
* respective argument values. Only the given options will be recognized in the arguments string.
*
* @param argsString Arguments string of the form: {@code preamble <option>value <option>value ...}
* @param argsString Arguments string of the form: {@code preamble <option> value <option> value ...}
* @param options Options to tokenize the arguments string with
* @return ArgumentMultimap object that maps options to their arguments
*/
Expand All @@ -32,7 +57,7 @@ public static ArgumentMultimap tokenize(String argsString, Option... options) {
/**
* Finds all zero-based option positions in the given arguments string.
*
* @param argsString Arguments string of the form: {@code preamble <option>value <option>value ...}
* @param argsString Arguments string of the form: {@code preamble <option> value <option> value ...}
* @param options Options to find in the arguments string
* @return List of zero-based option positions in the given arguments string
*/
Expand Down Expand Up @@ -77,10 +102,10 @@ private static List<OptionPosition> findOptionPositions(String argsString, Optio
* is valid if there is a whitespace before {@code option}. Returns -1 if no
* such occurrence can be found.
*
* E.g if {@code argsString} = "e/hip/900", {@code option} = "p/" and
* E.g if {@code argsString} = "-e hi-p 900", {@code option} = "-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 option} = "p/" and
* occurrences of "-p" with whitespace before it. However, if
* {@code argsString} = "-e hi -p 900", {@code option} = "-p" and
* {@code fromIndex} = 0, this method returns 5.
*/
private static int findOptionPosition(String argsString, String option, int fromIndex) {
Expand All @@ -94,7 +119,7 @@ private static int findOptionPosition(String argsString, String option, int from
* extracted options to their respective arguments. Options are extracted based on their zero-based positions in
* {@code argsString}.
*
* @param argsString Arguments string of the form: {@code preamble <option>value <option>value ...}
* @param argsString Arguments string of the form: {@code preamble <option> value <option> value ...}
* @param optionPositions Zero-based positions of all options in {@code argsString}
* @return ArgumentMultimap object that maps options to their arguments
*/
Expand Down Expand Up @@ -139,7 +164,16 @@ private static String extractArgumentValue(String argsString,
int valueStartPos = currentOptionPosition.getStartPosition() + optionLength;
String value = argsString.substring(valueStartPos, nextOptionPosition.getStartPosition());

return value.trim();
return unescape(value.trim());
}

/**
* Unescape special characters e.g. '/' and '-'.
*/
private static String unescape(String input) {
// Use regular expression to unescape backslash and hyphen
String unescapedInput = input.replaceAll("\\\\(.)", "$1");
return unescapedInput;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package seedu.address.logic.parser;

import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;
import static seedu.address.logic.parser.CliSyntax.OPTION_ALL;
import static seedu.address.logic.parser.CliSyntax.OPTION_DATETIME;
Expand Down Expand Up @@ -28,6 +29,11 @@ public class CreateDeadlineCommandParser implements Parser<CreateDeadlineCommand
* @throws ParseException if the user input does not conform the expected format
*/
public CreateDeadlineCommand parse(String args, AbsolutePath currPath) throws ParseException {
requireAllNonNull(args, currPath);

ParserUtil.verifyAllOptionsValid(args,
OPTION_DESC, OPTION_DATETIME, OPTION_ALL);

ArgumentMultimap argMultimap =
ArgumentTokenizer.tokenize(args, OPTION_DESC, OPTION_DATETIME, OPTION_ALL);

Expand All @@ -36,8 +42,6 @@ public CreateDeadlineCommand parse(String args, AbsolutePath currPath) throws Pa
MESSAGE_INVALID_COMMAND_FORMAT, CreateDeadlineCommand.MESSAGE_USAGE));
}

argMultimap.verifyNoDuplicateOptionsFor(OPTION_DESC, OPTION_DATETIME, OPTION_ALL);

// If no path given, default to current path.
AbsolutePath fullTargetPath = null;
if (argMultimap.getPreamble().isEmpty()) {
Expand All @@ -47,6 +51,8 @@ public CreateDeadlineCommand parse(String args, AbsolutePath currPath) throws Pa
fullTargetPath = ParserUtil.resolvePath(currPath, target);
}

argMultimap.verifyNoDuplicateOptionsFor(OPTION_DESC, OPTION_DATETIME, OPTION_ALL);

LocalDateTime by = ParserUtil.parseDateTime(argMultimap.getValue(OPTION_DATETIME).get());
Deadline deadline = new Deadline(argMultimap.getValue(OPTION_DESC).get(), by);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public class CreateGroupCommandParser implements Parser<CreateGroupCommand> {
* @throws ParseException if the user input does not conform the expected format
*/
public CreateGroupCommand parse(String args, AbsolutePath currPath) throws ParseException {
ParserUtil.verifyAllOptionsValid(args, OPTION_NAME);

ArgumentMultimap argMultimap =
ArgumentTokenizer.tokenize(args, OPTION_NAME);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ public class CreateStudentCommandParser implements Parser<CreateStudentCommand>
* @throws ParseException if the user input does not conform the expected format
*/
public CreateStudentCommand parse(String args, AbsolutePath currPath) throws ParseException {
ParserUtil.verifyAllOptionsValid(args, OPTION_NAME, OPTION_PHONE, OPTION_EMAIL, OPTION_ADDRESS);

ArgumentMultimap argMultimap =
ArgumentTokenizer.tokenize(args, OPTION_NAME, OPTION_PHONE, OPTION_EMAIL, OPTION_ADDRESS);

//todo: need usage format from command class
if (!ParserUtil.areOptionsPresent(argMultimap, OPTION_NAME)
|| argMultimap.getPreamble().isEmpty()) {
throw new ParseException(String.format(
Expand All @@ -56,7 +57,6 @@ public CreateStudentCommand parse(String args, AbsolutePath currPath) throws Par
throw new ParseException(e.getMessage());
}

//todo: is possible to create student without provide id -> will auto generate id
if (!targetPath.isStudentDirectory()) {
throw new ParseException(INVALID_PATH_MESSAGE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class CreateTodoCommandParser implements Parser<CreateTodoCommand> {
* @throws ParseException if the user input does not conform the expected format
*/
public CreateTodoCommand parse(String args, AbsolutePath currPath) throws ParseException {
ParserUtil.verifyAllOptionsValid(args, OPTION_DESC, OPTION_ALL);

ArgumentMultimap argMultimap =
ArgumentTokenizer.tokenize(args, OPTION_DESC, OPTION_ALL);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;

import seedu.address.logic.Messages;
import seedu.address.logic.commands.DeleteForStudentsAndGroupsCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.path.AbsolutePath;
Expand All @@ -22,6 +23,11 @@ public class DeleteForStudentsAndGroupsCommandParser implements Parser<DeleteFor
* @throws ParseException if the user input does not conform the expected format
*/
public DeleteForStudentsAndGroupsCommand parse(String args, AbsolutePath currPath) throws ParseException {
if (!ArgumentTokenizer.extractAllOptionNames(args).isEmpty()) {
throw new ParseException(String.format(Messages.MESSAGE_NO_OPTIONS,
DeleteForStudentsAndGroupsCommand.COMMAND_WORD));
}

ArgumentMultimap argMultimap =
ArgumentTokenizer.tokenize(args);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package seedu.address.logic.parser;

import static seedu.address.logic.Messages.MESSAGE_INVALID_COMMAND_FORMAT;

import java.util.logging.Logger;

import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.core.index.Index;
import seedu.address.logic.Messages;
import seedu.address.logic.commands.DeleteTaskCommand;
import seedu.address.logic.parser.exceptions.ParseException;
import seedu.address.model.path.AbsolutePath;
Expand All @@ -25,14 +24,12 @@ public class DeleteTaskCommandParser implements Parser<DeleteTaskCommand> {
*/
public DeleteTaskCommand parse(String args, AbsolutePath currPath) throws ParseException {
logger.fine("Parsing delete task command with arguments: " + args);
try {
Index index = ParserUtil.parseIndex(args);
logger.fine("Index parsed (One Based): " + index.getOneBased());
return new DeleteTaskCommand(index);
} catch (ParseException pe) {
logger.warning("Error parsing delete task command: " + pe.getMessage());
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteTaskCommand.MESSAGE_USAGE), pe);
if (!ArgumentTokenizer.extractAllOptionNames(args).isEmpty()) {
throw new ParseException(String.format(Messages.MESSAGE_NO_OPTIONS,
DeleteTaskCommand.COMMAND_WORD));
}
Index index = ParserUtil.parseIndex(args);
logger.fine("Index parsed (One Based): " + index.getOneBased());
return new DeleteTaskCommand(index);
}
}
69 changes: 45 additions & 24 deletions src/main/java/seedu/address/logic/parser/EditCommandParser.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package seedu.address.logic.parser;

import static java.util.Objects.requireNonNull;
import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
import static seedu.address.logic.parser.CliSyntax.OPTION_ADDRESS;
import static seedu.address.logic.parser.CliSyntax.OPTION_EMAIL;
import static seedu.address.logic.parser.CliSyntax.OPTION_ID;
Expand Down Expand Up @@ -29,39 +29,60 @@ public class EditCommandParser implements Parser<EditCommand> {
* @throws ParseException If the command arguments are invalid or if parsing fails.
*/
public EditCommand parse(String args, AbsolutePath currPath) throws ParseException {
requireNonNull(args);
requireAllNonNull(args, currPath);

String preamble = ArgumentTokenizer.extractPreamble(args);

RelativePath path = ParserUtil.parseRelativePath(preamble);
AbsolutePath targetPath = null;
if (preamble.isEmpty()) {
targetPath = currPath;
} else {
targetPath = ParserUtil.resolvePath(currPath, path);
}

if (targetPath.isStudentDirectory()) {
return parseEditStudent(args, targetPath);
}

if (targetPath.isGroupDirectory()) {
return parseEditGroup(args, targetPath);
}

throw new ParseException(EditCommand.ERROR_MESSAGE_INVALID_PATH);
}

private EditCommand parseEditStudent(String args, AbsolutePath target) throws ParseException {
ParserUtil.verifyAllOptionsValid(args,
OPTION_NAME, OPTION_PHONE, OPTION_EMAIL, OPTION_ADDRESS, OPTION_ID);

ArgumentMultimap argMultimap =
ArgumentTokenizer.tokenize(args, OPTION_NAME, OPTION_PHONE, OPTION_EMAIL, OPTION_ADDRESS, OPTION_ID);

argMultimap.verifyNoDuplicateOptionsFor(OPTION_NAME, OPTION_PHONE, OPTION_EMAIL, OPTION_ADDRESS, OPTION_ID);

// If no path given, default to current path.
AbsolutePath fullTargetPath = null;
if (argMultimap.getPreamble().isEmpty()) {
fullTargetPath = currPath;
} else {
RelativePath target = ParserUtil.parseRelativePath(argMultimap.getPreamble());
fullTargetPath = ParserUtil.resolvePath(currPath, target);
EditStudentDescriptor editStudentDescriptor = getEditStudentDescriptor(argMultimap);
if (!editStudentDescriptor.isAnyFieldEdited()) {
throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
}

if (fullTargetPath.isStudentDirectory()) {
EditStudentDescriptor editStudentDescriptor = getEditStudentDescriptor(argMultimap);
if (!editStudentDescriptor.isAnyFieldEdited()) {
throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
}
return new EditCommand(fullTargetPath, editStudentDescriptor);
}
return new EditCommand(target, editStudentDescriptor);
}

private EditCommand parseEditGroup(String args, AbsolutePath target) throws ParseException {
ParserUtil.verifyAllOptionsValid(args, OPTION_NAME, OPTION_ID);

ArgumentMultimap argMultimap =
ArgumentTokenizer.tokenize(args, OPTION_NAME, OPTION_ID);

argMultimap.verifyNoDuplicateOptionsFor(OPTION_NAME, OPTION_ID);

// bug: what if user pass phone option?
if (fullTargetPath.isGroupDirectory()) {
EditGroupDescriptor editGroupDescriptor = getEditGrouDescriptor(argMultimap);
if (!editGroupDescriptor.isAnyFieldEdited()) {
throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
}
return new EditCommand(fullTargetPath, editGroupDescriptor);
EditGroupDescriptor editGroupDescriptor = getEditGrouDescriptor(argMultimap);
if (!editGroupDescriptor.isAnyFieldEdited()) {
throw new ParseException(EditCommand.MESSAGE_NOT_EDITED);
}

throw new ParseException(EditCommand.MESSAGE_INCORRECT_DIRECTORY_ERROR);
return new EditCommand(target, editGroupDescriptor);
}

private EditStudentDescriptor getEditStudentDescriptor(ArgumentMultimap argMultimap) throws ParseException {
Expand Down
Loading

0 comments on commit 6571771

Please sign in to comment.