From 0741bd29a0219d9b90f1603f782e1c9c266f6d50 Mon Sep 17 00:00:00 2001 From: Niklas Date: Thu, 24 Oct 2024 01:02:23 +0200 Subject: [PATCH 1/4] Add Command support to ArgsParser This commit introduces the concept of Commands to the ArgsParser, allowing users to define and parse both flags and commands. Updated UnknownFlagArgsException to handle unknown commands, adjusted help message generation to include commands, and added relevant tests to ensure correct behavior. --- .../UnknownFlagArgsException.java | 47 ++++++----- src/ArgsParser/ArgsParser.java | 84 +++++++++++++++---- src/ArgsParser/CalledForHelpNotification.java | 72 ++++++++++++++-- src/ArgsParser/Command.java | 70 ++++++++++++++++ src/test/TestArgsExceptions.java | 54 ++++++++++-- 5 files changed, 276 insertions(+), 51 deletions(-) create mode 100644 src/ArgsParser/Command.java diff --git a/src/ArgsParser/ArgsExceptions/UnknownFlagArgsException.java b/src/ArgsParser/ArgsExceptions/UnknownFlagArgsException.java index 5eb2c53..2d87436 100644 --- a/src/ArgsParser/ArgsExceptions/UnknownFlagArgsException.java +++ b/src/ArgsParser/ArgsExceptions/UnknownFlagArgsException.java @@ -1,54 +1,61 @@ package ArgsParser.ArgsExceptions; import ArgsParser.ArgsException; -import ArgsParser.ArgsParser; -import jdk.jshell.SourceCodeAnalysis; - -import java.util.LinkedList; import java.util.Set; /** * Exception to be thrown if an unknown flag is provided */ public class UnknownFlagArgsException extends ArgsException { - public UnknownFlagArgsException(String flagName, LinkedList fullFlags, LinkedList shortFlags) { - super("unknown flag: " + flagName + computeSuggestion(flagName, fullFlags, shortFlags), true); + public UnknownFlagArgsException(String flagName, Set parameterFlags, Set commandNames, boolean isFirstPosition) { + super("unknown flag or command: " + flagName + computeSuggestion(flagName, parameterFlags, commandNames, isFirstPosition), true); } /** * Compute a suggestion for the user based on the flag name * @param userInput the flag name provided by the user - * @param fullFlags the full flags available - * @param shortFlags the short flags available + * @param parameterFlags the full flags available + * @param commandNames the short flags available * @return a suggestion for the user */ - protected static String computeSuggestion(String userInput, LinkedList fullFlags, LinkedList shortFlags) { - fullFlags.add("help"); - shortFlags.add("h"); - userInput = stripFlagPrefix(userInput); + protected static String computeSuggestion(String userInput, Set parameterFlags, Set commandNames, boolean isFirstPosition) { + String strippedInput = stripFlagPrefix(userInput); double highestSimilarity = 0.0; String suggestion = ""; - for (String fullFlag : fullFlags) { - fullFlag = stripFlagPrefix(fullFlag); - double similarity = SimilarityScore(userInput, fullFlag); + for (String parameterFlag : parameterFlags) { + String strippedFlag = stripFlagPrefix(parameterFlag); + double similarity = SimilarityScore(strippedInput, strippedFlag); + if (similarity > highestSimilarity) { + highestSimilarity = similarity; + suggestion = parameterFlag; + } + } + + for (String help : new String[]{"--help", "-h"}) { + String strippedFlag = stripFlagPrefix(help); + double similarity = SimilarityScore(strippedInput, strippedFlag); if (similarity > highestSimilarity) { highestSimilarity = similarity; - suggestion = "--" + fullFlag; + suggestion = help; } } - for (String shortFlag : shortFlags) { - shortFlag = stripFlagPrefix(shortFlag); - double similarity = SimilarityScore(userInput, shortFlag); + for (String commandName : commandNames) { + commandName = stripFlagPrefix(commandName); + double similarity = SimilarityScore(strippedInput, commandName); if (similarity > highestSimilarity) { highestSimilarity = similarity; - suggestion = "-" + shortFlag; + suggestion = commandName; } } if (highestSimilarity < 0.1) { return ""; + } else if (isFirstPosition && !(userInput.charAt(0) == '-')) { + return "\n> did you mean: " + suggestion + " ?" + + "\n" + + "\n> flag or command expected in first position!"; } else { return "\n> did you mean: " + suggestion + " ?"; } diff --git a/src/ArgsParser/ArgsParser.java b/src/ArgsParser/ArgsParser.java index a7a9c94..7dffdcc 100644 --- a/src/ArgsParser/ArgsParser.java +++ b/src/ArgsParser/ArgsParser.java @@ -50,10 +50,11 @@ public class ArgsParser { private final String[] args; private final Map> parameterMap = new HashMap<>(); + private final Map commandMap = new HashMap<>(); private final Set> mandatoryParameters = new HashSet<>(); private final Set arrayParameters = new HashSet<>(); - private final LinkedList fullFlags = new LinkedList<>(); - private final LinkedList shortFlags = new LinkedList<>(); + private final LinkedList commandsInDefinitionOrder = new LinkedList<>(); + private final LinkedList flagsInDefinitionOrder = new LinkedList<>(); protected boolean parseArgsWasCalled = false; private int longestFlagSize = 0; private int longestShortFlag = 0; @@ -100,8 +101,8 @@ private Parameter createParameter(String fullFlag, shortFlag = makeFlag(shortFlag, true); // check if the flag names are already used - if (fullFlags.contains(fullFlag)) throw new IllegalArgumentException("Flag already exists: " + fullFlag); - if (shortFlags.contains(shortFlag)) throw new IllegalArgumentException("Flag already exists: " + shortFlag); + if (parameterMap.containsKey(fullFlag)) throw new IllegalArgumentException("Flag already exists: " + fullFlag); + if (parameterMap.containsKey(shortFlag)) throw new IllegalArgumentException("Flag already exists: " + shortFlag); // create new parameter instance Parameter parameter = new Parameter(fullFlag, shortFlag, description, type, isMandatory, this); @@ -114,6 +115,8 @@ private Parameter createParameter(String fullFlag, parameterMap.put(parameter.getFullFlag(), parameter); parameterMap.put(parameter.getShortFlag(), parameter); + flagsInDefinitionOrder.add(fullFlag); + // check for the lengths of the name int nameSize = parameter.getFullFlag().length(); if (longestFlagSize < nameSize) longestFlagSize = nameSize; @@ -121,10 +124,6 @@ private Parameter createParameter(String fullFlag, int shortSize = parameter.getShortFlag().length(); if (longestShortFlag < shortSize) longestShortFlag = shortSize; - // add names to the name sets - fullFlags.add(fullFlag); - shortFlags.add(shortFlag); - // add to mandatory parameters if parameter is mandatory if (parameter.isMandatory()) mandatoryParameters.add(parameter); @@ -493,7 +492,38 @@ public Parameter addDefaultCharacterArrayParameter(String fullFlag, } - // + // Command + + + /** + * Adds a new command with its full name, short name, and description to the existing command set. + * + * @param fullCommandName The full name of the command, used as the primary identifier. + * @param shortCommandName The abbreviated name of the command, used as a shorthand identifier. + * @param description A brief description of what the command does. + * @return The newly created and added Command object. + * @throws IllegalArgumentException If the fullCommandName or shortCommandName already exists in the command set. + */ + public Command addCommand(String fullCommandName, String shortCommandName, String description) { + + if (commandMap.containsKey(fullCommandName)) throw new IllegalArgumentException("Command name already exists: " + fullCommandName); + if (commandMap.containsKey(shortCommandName)) throw new IllegalArgumentException("Command name already exists: " + shortCommandName); + + Command command = new Command(fullCommandName, shortCommandName, description, this); + + int nameSize = fullCommandName.length(); + if (longestFlagSize < nameSize) longestFlagSize = nameSize; + + int shortSize = shortCommandName.length(); + if (longestShortFlag < shortSize) longestShortFlag = shortSize; + + commandMap.put(fullCommandName, command); + commandMap.put(shortCommandName, command); + + commandsInDefinitionOrder.add(fullCommandName); + + return command; + } /** @@ -579,17 +609,28 @@ private void checkForHelpCall() throws UnknownFlagArgsException, CalledForHelpNo boolean oneArgProvided = argsLength == 1; boolean twoArgsProvided = argsLength == 2; boolean firstArgumentIsParameter = parameterMap.get(args[0]) != null; + boolean firstArgumentIsCommand = commandMap.get(args[0]) != null; if (oneArgProvided && (args[0].equals("--help") || args[0].equals("-h"))) { // if --help or -h was called, the help is printed - throw new CalledForHelpNotification(parameterMap, fullFlags, longestFlagSize, longestShortFlag); + throw new CalledForHelpNotification(parameterMap, flagsInDefinitionOrder, + commandMap, commandsInDefinitionOrder, + longestFlagSize, longestShortFlag); } else if (twoArgsProvided && (args[1].equals("--help") || args[1].equals("-h"))) { if (firstArgumentIsParameter) { // if the first argument is a parameter and --help follows, - throw new CalledForHelpNotification(parameterMap, new LinkedList<>(Collections.singletonList(args[0])), longestFlagSize, longestShortFlag); - + throw new CalledForHelpNotification(parameterMap, Collections.singletonList(args[0]), + commandMap, new LinkedList<>(), + longestFlagSize, longestShortFlag); + + } else if (firstArgumentIsCommand) { // if the first argument is a command and --help follows + throw new CalledForHelpNotification(parameterMap, new LinkedList<>(), + commandMap, Collections.singletonList(args[0]), + longestFlagSize, longestShortFlag); + } else { // if the first argument is not a parameter but --help was called, - // the program notifies the user of an unknown parameter input - throw new UnknownFlagArgsException(args[0], fullFlags, shortFlags); + // the program notifies the user of an unknown parameter input + throw new UnknownFlagArgsException(args[0], parameterMap.keySet(), commandMap.keySet(), false); + } } } @@ -611,6 +652,11 @@ private Set> parseArguments() throws UnknownFlagArgsException, TooM MissingArgArgsException, InvalidArgTypeArgsException, FlagAlreadyProvidedArgsException, HelpAtWrongPositionArgsException { Set> givenParameters = new HashSet<>(); + if (argsLength > 0 && parameterMap.get(args[0]) == null && commandMap.get(args[0]) == null && + !(args[0].equals("--help") || args[0].equals("-h"))) { + throw new UnknownFlagArgsException(args[0], parameterMap.keySet(), commandMap.keySet(), true); + } + Parameter currentParameter = null; boolean longFlagUsed = false; for (int i = 0; i < argsLength; i++) { @@ -620,6 +666,7 @@ private Set> parseArguments() throws UnknownFlagArgsException, TooM currentParameter = parameterMap.get(args[i]); longFlagUsed = args[i].startsWith("--"); } + boolean currentPositionIsCommand = commandMap.get(args[i]) != null; boolean flagExists = parameterMap.get(args[i]) != null; boolean isLastEntry = i == argsLength - 1; boolean currentParameterNotNull = currentParameter != null; @@ -628,13 +675,13 @@ private Set> parseArguments() throws UnknownFlagArgsException, TooM boolean flagAlreadyProvided = false; if (flagExists) flagAlreadyProvided = givenParameters.contains(currentParameter); boolean isHelpCall = ("--help".equals(args[i]) || "-h".equals(args[i])); - boolean isHelpCallInWrongPosition = isHelpCall && (i > 1 || (i == 0 && argsLength == 2)); + boolean helpCallInWrongPosition = isHelpCall && (i > 1 || (i == 0 && argsLength == 2)); - if (isHelpCallInWrongPosition) { + if (helpCallInWrongPosition) { throw new HelpAtWrongPositionArgsException(); } else if (currentPositionIsFlag && !flagExists) { // if flag is unknown - throw new UnknownFlagArgsException(args[i], fullFlags, shortFlags); + throw new UnknownFlagArgsException(args[i], parameterMap.keySet(), commandMap.keySet(), false); } else if (currentPositionIsFlag && flagAlreadyProvided) { // if the flag already was set throw new FlagAlreadyProvidedArgsException(currentParameter.getFullFlag(), currentParameter.getShortFlag()); @@ -648,6 +695,9 @@ private Set> parseArguments() throws UnknownFlagArgsException, TooM } else if (isLastEntry && currentPositionIsFlag) { //if last Flag has no argument throw new MissingArgArgsException(args[i]); + } else if (currentPositionIsCommand) { // if current position is a command + commandMap.get(args[i]).setCommand(); // set the command to true + } else if (lastPositionWasFlag && currentParameterNotNull) { // if the current position is an argument boolean isArrayParam = arrayParameters.contains(args[i - 1]); diff --git a/src/ArgsParser/CalledForHelpNotification.java b/src/ArgsParser/CalledForHelpNotification.java index 9664318..1fd68fc 100644 --- a/src/ArgsParser/CalledForHelpNotification.java +++ b/src/ArgsParser/CalledForHelpNotification.java @@ -25,14 +25,20 @@ public class CalledForHelpNotification extends Exception { put("Character[]", "c+"); }}; - public CalledForHelpNotification(Map> parameters, LinkedList fullFlags, int longestFlagSize, int longestShortFlag) { - super(generateHelpMessage(parameters, fullFlags, longestFlagSize, longestShortFlag)); + public CalledForHelpNotification(Map> parameterMap, List flagsInDefinitionOrder, + Map commandMap, List commandsInDefinitionOrder, + int longestFlagSize, int longestShortFlag) { + super(generateHelpMessage(parameterMap, flagsInDefinitionOrder, + commandMap, commandsInDefinitionOrder, + longestFlagSize, longestShortFlag)); } /** * prints all available Parameters found in argumentsList to the console */ - private static String generateHelpMessage(Map> parameters, LinkedList fullFlags, int longestFlagSize, int longestShortFlag) { + private static String generateHelpMessage(Map> parameterMap, List flagsInDefinitionOrder, + Map commandMap, List commandsInDefinitionOrder, + int longestFlagSize, int longestShortFlag) { StringBuilder helpMessage = new StringBuilder(); helpMessage.append("\n"); @@ -46,13 +52,24 @@ private static String generateHelpMessage(Map> parameters, helpMessage.append(centerString("(!)=mandatory | (?)=optional")).append("\n"); helpMessage.append("#").append("\n"); - if (fullFlags.size() > 1) { + if (flagsInDefinitionOrder.size() > 1) { helpMessage.append(centerString("Available Parameters:")).append("\n"); helpMessage.append("#").append("\n"); } - for (String flag : fullFlags) { - String helpString = parameterHelpString(parameters.get(flag), longestFlagSize, longestShortFlag); + for (String flag : flagsInDefinitionOrder) { + String helpString = parameterHelpString(parameterMap.get(flag), longestFlagSize, longestShortFlag); + helpMessage.append(helpString).append("\n"); + helpMessage.append("#").append("\n"); + } + + if (commandsInDefinitionOrder.size() > 1) { + helpMessage.append(centerString("Available Commands:")).append("\n"); + helpMessage.append("#").append("\n"); + } + + for (String command : commandsInDefinitionOrder) { + String helpString = commandHelpString(commandMap.get(command), longestFlagSize, longestShortFlag); helpMessage.append(helpString).append("\n"); helpMessage.append("#").append("\n"); } @@ -69,6 +86,47 @@ private static String centerString(String stringToCenter) { int freeSpace = (consoleWidth - stringToCenter.length()) / 2 - 1; return "#" + " ".repeat(freeSpace) + stringToCenter; } + + private static String commandHelpString(Command command, int longestFlagSize, int longestShortFlag) { + String fullCommandName = command.getFullCommandName(); + String shortCommandName = command.getShortCommandName(); + String description = command.getDescription(); + String isMandatory = " "; + StringBuilder helpString = new StringBuilder("### "); + + // check if a description is available: + if (description == null || description.isEmpty()) { + description = "No description available!"; + } else { + description = description.trim(); + } + + // align the parameter names nicely + int nameWhiteSpaceSize = longestFlagSize - fullCommandName.length(); + fullCommandName = fullCommandName + " ".repeat(nameWhiteSpaceSize); + int shortWhiteSpaceSize = longestShortFlag == 0 ? 0 : longestShortFlag - shortCommandName.length(); + shortCommandName = shortCommandName + " ".repeat(shortWhiteSpaceSize); + + helpString.append(fullCommandName).append(" ").append(shortCommandName).append(" "); + + int whiteSpace = helpString.length(); + + // The description String gets checked if it fits inside the info box. + // If not, a new line will be added and the rest of the description will be aligned. + String[] descriptionRows = description.split(" \\n| \\n |\\n ", -1); + if (descriptionRows.length == 1) { + helpString.append(lineBreakAtWhitespace(description, whiteSpace)); + + } else { + for (int i = 0; i < descriptionRows.length; i++) { + String row = descriptionRows[i]; + helpString.append(lineBreakAtWhitespace(row, whiteSpace)); + if (i != descriptionRows.length - 1) helpString.append("\n#").append(" ".repeat(whiteSpace - 1)); + } + } + + return helpString.toString(); + } /** * generates the help String (fullFlag, shortFlag, description) for a single Parameter @@ -77,7 +135,7 @@ private static String centerString(String stringToCenter) { */ private static String parameterHelpString(Parameter parameter, int longestFlagSize, int longestShortFlag) { String name = parameter.getFullFlag(); - String shortFlag = parameter.getShortFlag() == null ? "/" : parameter.getShortFlag(); + String shortFlag = parameter.getShortFlag(); String description = parameter.getDescription(); String isMandatory = parameter.isMandatory() ? "(!)" : "(?)"; StringBuilder helpString = new StringBuilder("### "); diff --git a/src/ArgsParser/Command.java b/src/ArgsParser/Command.java new file mode 100644 index 0000000..03fee41 --- /dev/null +++ b/src/ArgsParser/Command.java @@ -0,0 +1,70 @@ +package ArgsParser; + +public class Command { + private final ArgsParser argsParser; + private final String fullCommandName; + private final String shortCommandName; + private final String description; + private boolean status = false; + + /** + * Constructs a Command object with specified full flag, short flag, description, and args parser. + * + * @param fullCommandName the detailed or long form flag for this command + * @param shortCommandName the abbreviated or short form flag for this command + * @param description a brief description of what this command does + * @param argsParser the parser responsible for handling command-line arguments + */ + public Command(String fullCommandName, String shortCommandName, String description, ArgsParser argsParser) { + this.fullCommandName = fullCommandName; + this.shortCommandName = shortCommandName; + this.description = description; + this.argsParser = argsParser; + } + + /** + * Returns the full flag associated with this command. + * + * @return the full flag of the command. + */ + protected String getFullCommandName() { + return fullCommandName; + } + + /** + * Returns the short flag associated with this command. + * + * @return the short flag of the command. + */ + protected String getShortCommandName() { + return shortCommandName; + } + + /** + * Returns the description of the command. + * + * @return the description of the command. + */ + protected String getDescription() { + return description; + } + + /** + * Sets the command status to true, indicating that the command has been activated. + */ + protected void setCommand() { + this.status = true; + } + + /** + * Checks if the command has been provided after the arguments were parsed. + * + * @return true if the command is provided, false otherwise. + * @throws IllegalArgumentException if the {@link ArgsParser#parse()} method was not called before checking the command. + */ + public boolean isProvided() throws IllegalArgumentException { + if (!argsParser.parseArgsWasCalled()) throw new IllegalStateException("parse() was not called before trying to check the command!"); + return status; + } + +} diff --git a/src/test/TestArgsExceptions.java b/src/test/TestArgsExceptions.java index fdde858..be91084 100644 --- a/src/test/TestArgsExceptions.java +++ b/src/test/TestArgsExceptions.java @@ -162,6 +162,27 @@ public void testHelpWithArrayParameters() { System.out.println(exception.getMessage()); } + @Test + public void testHelpWithCommand() { + String[] args = {"--help"}; + ArgsParser parser = new ArgsParser(args); + + Command command = parser.addCommand("command", "c", "this is a command"); + + Exception exception = assertThrows(CalledForHelpNotification.class, parser::parseUnchecked); + String expected = """ + + ############################################### HELP ############################################### + # [s]/[s+]=String | [i]/[i+]=Integer | [c]/[c+]=Character | [b]/[b+]=Boolean | [d]/[d+]=Double + # ('+' marks a flag that takes several arguments of the same type whitespace separated) + # (!)=mandatory | (?)=optional + # + ### command c this is a command + # + ####################################################################################################"""; + assertEquals(expected, exception.getMessage()); + } + @Test public void testHelpWithNewLineInDescription() { String[] args = {"--help"}; @@ -309,8 +330,17 @@ public void testNoFlags() { Parameter string = parser.addMandatoryStringParameter("file", "f", "descr"); - Exception exception = assertThrows(MandatoryArgNotProvidedArgsException.class, parser::parseUnchecked); - assertEquals(new MandatoryArgNotProvidedArgsException("Mandatory parameters are missing:\n--file").getMessage(), exception.getMessage()); + Exception exception = assertThrows(UnknownFlagArgsException.class, parser::parseUnchecked); + String expected = """ + + unknown flag or command: file.txt + > did you mean: --file ? + + > flag or command expected in first position! + + > Use --help for more information. + """; + assertEquals(expected, exception.getMessage()); } @Test @@ -407,7 +437,7 @@ public void testUnknownFlag() { Exception exception = assertThrows(UnknownFlagArgsException.class, parser::parseUnchecked); assertEquals(""" - unknown flag: -w + unknown flag or command: -w > Use --help for more information. """, exception.getMessage()); @@ -424,7 +454,7 @@ public void testUnknownFlagWithMultipleFlagsAndWrongInput() { Exception exception = assertThrows(UnknownFlagArgsException.class, parser::parseUnchecked); assertEquals(""" - unknown flag: -s + unknown flag or command: -s > did you mean: --save ? > Use --help for more information. @@ -442,7 +472,7 @@ public void testMissingShorts() { Exception exception = assertThrows(UnknownFlagArgsException.class, parser::parseUnchecked); assertEquals(""" - unknown flag: -f + unknown flag or command: -f > did you mean: --file ? > Use --help for more information. @@ -460,7 +490,7 @@ public void testMisspelledHelp() { Exception exception = assertThrows(UnknownFlagArgsException.class, parser::parseUnchecked); assertEquals(""" - unknown flag: --hp + unknown flag or command: --hp > did you mean: --help ? > Use --help for more information. @@ -478,7 +508,7 @@ public void testSuggestionForFullFlag() { Exception exception = assertThrows(UnknownFlagArgsException.class, parser::parseUnchecked); assertEquals(""" - unknown flag: --sve + unknown flag or command: --sve > did you mean: --save ? > Use --help for more information. @@ -520,4 +550,14 @@ public void testArgsIsNull() { Exception exception = assertThrows(IllegalArgumentException.class, () -> new ArgsParser(args)); assertEquals("Args cannot be null!", exception.getMessage()); } + + @Test + public void testCommand() { + String[] args = new String[]{"command"}; + ArgsParser parser = new ArgsParser(args); + Command command = parser.addCommand("command", "c", "test command"); + parser.parse(); + + assertTrue(command.isProvided()); + } } From eabda257564a2ad9b09a45b0dc1e1d7438d45693 Mon Sep 17 00:00:00 2001 From: Niklas Date: Thu, 24 Oct 2024 01:14:58 +0200 Subject: [PATCH 2/4] Add support for command indication in help messages Inserted command indication "(/)" in help messages to differentiate commands from regular parameters. Also, updated associated tests to validate this enhancement. --- src/ArgsParser/ArgsParser.java | 1 + src/ArgsParser/CalledForHelpNotification.java | 4 +- src/test/TestArgsExceptions.java | 45 ++++++++++++++++--- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/ArgsParser/ArgsParser.java b/src/ArgsParser/ArgsParser.java index 7dffdcc..84f460f 100644 --- a/src/ArgsParser/ArgsParser.java +++ b/src/ArgsParser/ArgsParser.java @@ -652,6 +652,7 @@ private Set> parseArguments() throws UnknownFlagArgsException, TooM MissingArgArgsException, InvalidArgTypeArgsException, FlagAlreadyProvidedArgsException, HelpAtWrongPositionArgsException { Set> givenParameters = new HashSet<>(); + //check if the first argument provided is actually a flag or command if (argsLength > 0 && parameterMap.get(args[0]) == null && commandMap.get(args[0]) == null && !(args[0].equals("--help") || args[0].equals("-h"))) { throw new UnknownFlagArgsException(args[0], parameterMap.keySet(), commandMap.keySet(), true); diff --git a/src/ArgsParser/CalledForHelpNotification.java b/src/ArgsParser/CalledForHelpNotification.java index 1fd68fc..67788a7 100644 --- a/src/ArgsParser/CalledForHelpNotification.java +++ b/src/ArgsParser/CalledForHelpNotification.java @@ -49,7 +49,7 @@ private static String generateHelpMessage(Map> parameterMap helpMessage.append(header).append("\n"); helpMessage.append(centerString("[s]/[s+]=String | [i]/[i+]=Integer | [c]/[c+]=Character | [b]/[b+]=Boolean | [d]/[d+]=Double")).append("\n"); helpMessage.append(centerString(" ('+' marks a flag that takes several arguments of the same type whitespace separated)")).append("\n"); - helpMessage.append(centerString("(!)=mandatory | (?)=optional")).append("\n"); + helpMessage.append(centerString("(!)=mandatory | (?)=optional | (/)=command")).append("\n"); helpMessage.append("#").append("\n"); if (flagsInDefinitionOrder.size() > 1) { @@ -107,7 +107,7 @@ private static String commandHelpString(Command command, int longestFlagSize, in int shortWhiteSpaceSize = longestShortFlag == 0 ? 0 : longestShortFlag - shortCommandName.length(); shortCommandName = shortCommandName + " ".repeat(shortWhiteSpaceSize); - helpString.append(fullCommandName).append(" ").append(shortCommandName).append(" "); + helpString.append(fullCommandName).append(" ").append(shortCommandName).append(" (/) "); int whiteSpace = helpString.length(); diff --git a/src/test/TestArgsExceptions.java b/src/test/TestArgsExceptions.java index be91084..72e1cba 100644 --- a/src/test/TestArgsExceptions.java +++ b/src/test/TestArgsExceptions.java @@ -21,7 +21,7 @@ public void testHelpForSingleFlag() { ############################################### HELP ############################################### # [s]/[s+]=String | [i]/[i+]=Integer | [c]/[c+]=Character | [b]/[b+]=Boolean | [d]/[d+]=Double # ('+' marks a flag that takes several arguments of the same type whitespace separated) - # (!)=mandatory | (?)=optional + # (!)=mandatory | (?)=optional | (/)=command # ### --parameterFlag4 -pf4 [d] (?) description # default: 5.6 @@ -62,7 +62,7 @@ public void testHelpWithEachParameter() { ############################################### HELP ############################################### # [s]/[s+]=String | [i]/[i+]=Integer | [c]/[c+]=Character | [b]/[b+]=Boolean | [d]/[d+]=Double # ('+' marks a flag that takes several arguments of the same type whitespace separated) - # (!)=mandatory | (?)=optional + # (!)=mandatory | (?)=optional | (/)=command # # Available Parameters: # @@ -128,7 +128,7 @@ public void testHelpWithArrayParameters() { ############################################### HELP ############################################### # [s]/[s+]=String | [i]/[i+]=Integer | [c]/[c+]=Character | [b]/[b+]=Boolean | [d]/[d+]=Double # ('+' marks a flag that takes several arguments of the same type whitespace separated) - # (!)=mandatory | (?)=optional + # (!)=mandatory | (?)=optional | (/)=command # # Available Parameters: # @@ -175,9 +175,42 @@ public void testHelpWithCommand() { ############################################### HELP ############################################### # [s]/[s+]=String | [i]/[i+]=Integer | [c]/[c+]=Character | [b]/[b+]=Boolean | [d]/[d+]=Double # ('+' marks a flag that takes several arguments of the same type whitespace separated) - # (!)=mandatory | (?)=optional + # (!)=mandatory | (?)=optional | (/)=command # - ### command c this is a command + ### command c (/) this is a command + # + ####################################################################################################"""; + assertEquals(expected, exception.getMessage()); + } + + @Test + public void testHelpWithCommandsAndParameters() { + String[] args = {"--help"}; + ArgsParser parser = new ArgsParser(args); + Command command = parser.addCommand("command", "c", "this is a command"); + Parameter newParam1 = parser.addMandatoryStringParameter("newParam1", "np1", "this is the first new parameter"); + Parameter newParam2 = parser.addOptionalIntegerParameter("newParam2", "np2", "this is the second new parameter"); + Command newCommand = parser.addCommand("newCommand", "nc", "this is another command"); + + Exception exception = assertThrows(CalledForHelpNotification.class, parser::parseUnchecked); + String expected = """ + + ############################################### HELP ############################################### + # [s]/[s+]=String | [i]/[i+]=Integer | [c]/[c+]=Character | [b]/[b+]=Boolean | [d]/[d+]=Double + # ('+' marks a flag that takes several arguments of the same type whitespace separated) + # (!)=mandatory | (?)=optional | (/)=command + # + # Available Parameters: + # + ### --newParam1 -np1 [s] (!) this is the first new parameter + # + ### --newParam2 -np2 [i] (?) this is the second new parameter + # + # Available Commands: + # + ### command c (/) this is a command + # + ### newCommand nc (/) this is another command # ####################################################################################################"""; assertEquals(expected, exception.getMessage()); @@ -201,7 +234,7 @@ public void testHelpWithNewLineInDescription() { ############################################### HELP ############################################### # [s]/[s+]=String | [i]/[i+]=Integer | [c]/[c+]=Character | [b]/[b+]=Boolean | [d]/[d+]=Double # ('+' marks a flag that takes several arguments of the same type whitespace separated) - # (!)=mandatory | (?)=optional + # (!)=mandatory | (?)=optional | (/)=command # ### --longString -ls [s] (?) This description is so long, it will force the automatic help # printout to introduce a new line and still have a nice look :) From bf538360c324e642c7a3a1f8bc442cad89f93b0b Mon Sep 17 00:00:00 2001 From: Niklas Date: Thu, 24 Oct 2024 01:46:31 +0200 Subject: [PATCH 3/4] Add indirect command check and update README accordingly Updated README to reflect these changes, including usage examples and instructions for commands. --- README.md | 74 +++++++++++++++++++++++++++------- src/ArgsParser/ArgsParser.java | 16 +++++++- 2 files changed, 75 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8673c9a..3f1a4d7 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ catering to diverse application needs. - **Easy Definition of Parameters:** Define parameters with flag names and shorthands, mandatory/optional status, as well as optional default values and descriptions. +- **Custom Command Support:** Define custom commands and easily check if they were provided. - **Robust Parsing-Error Handling:** Catch and handle custom exceptions for missing arguments, invalid types, and other common parsing errors. - **Automatic exception handling** Alternatively of handling parsing errors manually, you can use a parsing method that @@ -63,10 +64,11 @@ public static void main(String[] args) { } ``` -### 2. Define the Parameters +### 2. Define the Parameters or Commands This parser supports 5 **types**: - *String, Integer, Double, Boolean, Character* +#### addParameter methods: The type of a Parameter is defined with its respective method on the parser object. For each type there are 5 `addParameter` methods (example for String): - `addMandatoryStringParameter(String fullFlag, String shortFlag, String description)` @@ -77,6 +79,7 @@ For each type there are 5 `addParameter` methods (example for String): (see a list of all Methods at the end of this README) +#### Available Fields for each Parameter: You can specify several fields for each parameter: - **fullFlag**: The flag name of the parameter. @@ -84,6 +87,7 @@ You can specify several fields for each parameter: - **description**: A description of the parameter, insert `null` or an empty string`""` if not needed. - **defaultValue**: A default value that the parameter returns if no argument was provided but accessed in the program. +#### Multiple Arguments to one flag: A **special type** are the `addArray` methods that are introduced in Version 4.0.0 of the ArgsParser. They allow handing several arguments to a single flag: ``` @@ -91,14 +95,23 @@ They allow handing several arguments to a single flag: ``` Several of these Array Parameters can be defined without problems. +#### Commands: + +Another added feature in Version 4.0.0 is the "command" type. +The parser checks if any commands were provided in the arguments, enabling the developer to activate different +modes or functionalities based on the commands passed. +You simply define commands just like regular parameters, and the +parser will recognize them when parsing the input arguments. + ```java - // ... +// ... Parameter example = parser.addMandatoryStringParameter("parameterFlag", "pf", "short Description"); Parameter example2 = parser.addOptionalIntegerParameter("parameterFlag2", "pf2", null); Parameter argWithDefault = parser.addDefaultDoubleParameter("parameterFlag3", "pf3", "description", 5.6); Parameter booleanArrayParam = parser.addBooleanArrayParameter("boolArray", "bArr", "Array of several boolean values", false); Parameter integerArrayParam = parser.addDefaultIntegerArrayParameter("intArray", "iArr", "Array of several integer values", new Integer[]{1, 2, 3}); - // ... + Command command = parser.addCommand("commandName", "cN", "this is a description for the command"); +// ... ``` ### 3. Parse the Arguments @@ -150,6 +163,8 @@ or like the following example for manually handling the ArgsExceptions: ``` ### 4. Access the Arguments + +#### direct access to arguments via its parameter Access the console arguments given by the user by calling the `getArgument()` method on the parameter variable. The return type matches the type defined when adding the parameter. The **arguments can be used directly in your code**! @@ -158,8 +173,18 @@ The **arguments can be used directly in your code**! // ... String providedArgument = example.getArgument(); Double result = example2.getArgument() + argWithDefault.getArgument(); + //... ``` +#### check provision of a command +For the commands, a simple call of `isProvided` on the Command instance will return if the command was provided in args: +```java + //... + if (command.isProvided()) System.out.println("command provided"); + //... +``` + +#### access arguments indirectly via the ArgsParser instance they were defined on With version 3.0.0 the `getArgumentOf(String fullFlag)` method ia available, thus arguments provided to a specific parameter flag can be accessed by the parameter flugFlag name. But this comes with some restrictions: @@ -174,7 +199,13 @@ But this comes with some restrictions: Integer getInteger = parser.getArgumentOf("parameterFlag2"); Double getDouble = parser.getArgumentOf("parameterFlag3"); Double result = getInteger + getDouble; -} + //... +``` + +The same is possible with commands: +```java + //... + if (parser.checkIfCommandIsProvided("commandName")) System.out.println("command still provided"); ``` ## Integrated --help function: @@ -193,7 +224,7 @@ they were added** on the ArgsParser Instance: ############################################### HELP ############################################### # [s]/[s+]=String | [i]/[i+]=Integer | [c]/[c+]=Character | [b]/[b+]=Boolean | [d]/[d+]=Double # ('+' marks a flag that takes several arguments of the same type whitespace separated) -# (!)=mandatory | (?)=optional +# (!)=mandatory | (?)=optional | (/)=command # # Available Parameters: # @@ -209,18 +240,20 @@ they were added** on the ArgsParser Instance: ### --intArray -iArr [i+] (?) Array of several integer values # default: [1, 2, 3] # +### commandName cN (/) this is a description for the command +# #################################################################################################### ``` while calling `--help` or `-h` with a specific parameter will only print the help message for that parameter: -`> exampleProgramm -pf4 -h` +`> exampleProgramm -pf3 -h` ``` ############################################### HELP ############################################### # [s]/[s+]=String | [i]/[i+]=Integer | [c]/[c+]=Character | [b]/[b+]=Boolean | [d]/[d+]=Double # ('+' marks a flag that takes several arguments of the same type whitespace separated) -# (!)=mandatory | (?)=optional +# (!)=mandatory | (?)=optional | (/)=command # ### --parameterFlag3 -pf3 [d] (?) description # default: 5.6 @@ -232,10 +265,10 @@ while calling `--help` or `-h` with a specific parameter will only print the hel The ArgsParser will throw an `ArgsException` if the user provides invalid arguments. The printouts of these exceptions look like this: -`> exampleProgramm -pf4 5.6 5.6` +`> exampleProgramm -pf3 5.6 5.6` ``` - Too many arguments provided to flag: -pf4 + Too many arguments provided to flag: -pf3 > Use --help for more information. @@ -243,10 +276,10 @@ The printouts of these exceptions look like this: or -`> exampleProgramm -pf4` +`> exampleProgramm -pf3` ``` - Missing argument for flag: -pf4 + Missing argument for flag: -pf3 > Use --help for more information. @@ -254,11 +287,11 @@ or for misspelled flags, the Parser will even do a suggestion: -`> exampleProgramm --paraeterflg4, 5.6` +`> exampleProgramm --paraeterflg3, 5.6` ``` - unknown flag: --paraeterflg4 -> did you mean: --parameterFlag4 ? + unknown flag: --paraeterflg3 +> did you mean: --parameterFlag3 ? > Use --help for more information. @@ -300,3 +333,16 @@ for misspelled flags, the Parser will even do a suggestion: - `addDefaultCharacterParameter(String fullFlag, String shortFlag, String description, Character defaultValue)` - `addCharacterArrayParameter(String fullFlag, String shortFlag, String description, boolean isMandatory)` - `addDefaultCharacterArrayParameter(String fullFlag, String shortFlag, String description, Character[] defaultValue)` + +#### access an Argument of ANY of these Parameters: +- `parameter.getArgument()` + +#### Command type: +- `addCommand(String fullCommandName, String shortCommandName, String description)` + +#### check provision of a specific command: +- `command.isProvided()` + +#### indirect access of parameters / commands: +- `getArgumentOf(String fullFlag)` +- `checkIfCommandIsProvided(String fullCommandName)` diff --git a/src/ArgsParser/ArgsParser.java b/src/ArgsParser/ArgsParser.java index 84f460f..f5833cf 100644 --- a/src/ArgsParser/ArgsParser.java +++ b/src/ArgsParser/ArgsParser.java @@ -745,9 +745,23 @@ private void checkMandatoryArguments(Set> givenParameters) throws M * @param type of the parameter * @return the argument of the parameter * @throws ClassCastException if the argument is not of the correct type + * @throws IllegalArgumentException if the provided flag is not defined on this parser instance */ @SuppressWarnings("unchecked") - public T getArgumentOf(String fullFlag) throws ClassCastException { + public T getArgumentOf(String fullFlag) throws ClassCastException, IllegalArgumentException { + if (parameterMap.get(fullFlag) == null) throw new IllegalArgumentException("Parameter '" + fullFlag + "' not defined"); return (T) parameterMap.get(makeFlag(fullFlag, false)).getArgument(); } + + /** + * Checks if the specified command is provided. + * + * @param fullCommandName the full name of the command to be checked + * @return true if the command is provided, false otherwise + * @throws IllegalArgumentException if the command is not defined on this parser instance + */ + public boolean checkIfCommandIsProvided(String fullCommandName) { + if (commandMap.get(fullCommandName) == null) throw new IllegalArgumentException("Command '" + fullCommandName + "' not defined"); + return commandMap.get(fullCommandName).isProvided(); + } } From f0e34b7f16c5123752499bf4392d4ebe58305bde Mon Sep 17 00:00:00 2001 From: Niklas Date: Thu, 24 Oct 2024 01:50:04 +0200 Subject: [PATCH 4/4] Add indirect command check and update README accordingly Updated README to reflect these changes, including usage examples and instructions for commands. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 3f1a4d7..aa93303 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,6 @@ Instantiate an ArgsParser object, and hand over the String array `args` from the public static void main(String[] args) { ArgsParser parser = new ArgsParser(args); // ... -} ``` ### 2. Define the Parameters or Commands