diff --git a/README.md b/README.md index 816b26bc..81cd81be 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,10 @@ public int age = 18; @InitDef - public static void beforeInit(@NotNull CommandBuildHelper cmdBuildHelper) { + public static void beforeInit(@NotNull CommandBuildHelper helper) { // configure the argument "age" to have an argument type of // number range and set the range to 1-100 - cmdBuildHelper., Integer>getArgument("age") + helper., Integer>getArgument("age") .withArgType(new NumberRangeArgumentType<>(1, 100)) .onOk(v -> System.out.println("The age is valid!")); } @@ -81,4 +81,28 @@ The package is currently available on Repsy and GitHub Packages. ``` > [!NOTE] > The `+` symbol is a wildcard that will automatically use the latest version of the package. -> You can also specify a specific version (e.g. `0.1.0`). \ No newline at end of file +> You can also specify a specific version (e.g. `0.1.0`). + + + +## FAQ + +* ### Your logo has plenty of imperfections, please fix it. + + The logo couldn't be simpler. I made it in five minutes in Figma. I am a terrible designer. + Also nothing is perfect. Heck, we are talking about software here, where bugs are + the norm, so, I think it's fine. + + Also that isn't a question. + +* ### Why the name "Lanat"? + + I had a tough time finding a name for this project. I wanted something short, easy to remember, + and that sounded good. It had to be related to the project in some way. + Sadly most names I came up with were already taken... Anyway, so Lanat is actually an acronym for + **L**iterally **A**ll **N**ames **A**re **T**aken. + Yeah, I'm good at naming things. + + * #### How is "Lanat" related to the project? + + It's not. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index fba61275..dd8e31aa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,11 +4,11 @@ plugins { } group = "com.darvil" -version = "0.2.1" +version = "0.3.0" description = "Command line argument parser" dependencies { - implementation("com.darvil:utils:0.0.2") + implementation("com.darvil:utils:0.1.0") implementation("com.darvil:terminal-text-formatter:1.0.0") implementation("org.jetbrains:annotations:24.0.1") diff --git a/src/main/java/lanat/Argument.java b/src/main/java/lanat/Argument.java index 9f157e5c..6b617822 100644 --- a/src/main/java/lanat/Argument.java +++ b/src/main/java/lanat/Argument.java @@ -32,7 +32,7 @@ *

* An Argument specifies a value that the user can introduce to the command. This value will be parsed by the specified * {@link ArgumentType} each time the Argument is used. Once finished parsing, the value may be retrieved by using - * {@link ParsedArguments#get(String)} on the {@link ParsedArguments} object returned by + * {@link ParseResult#get(String)} on the {@link ParseResult} object returned by * {@link ArgumentParser#parse(CLInput)}. * *

@@ -276,8 +276,8 @@ public void setDefaultValue(@Nullable TInner value) { * names. *

*

- * Single character names can be used in argument name lists (e.g. -abc), each alphabetic character - * being an argument name, that is, -a -b -c. + * Single character names can be used in argument name lists (e.g. {@code -abc}), each alphabetic character + * being an argument name, that is, {@code -a -b -c}. *

* * @param names the names that should be added to this argument. @@ -463,8 +463,8 @@ public void setRepresentationColor(@NotNull Color color) { /** * Checks if this argument matches the given name, including the prefix. *

- * For example, if the prefix is '-' and the argument has the name "help", this method - * will return {@code true} if the name is "--help". + * For example, if the prefix is {@code '-'} and the argument has the name {@code "help"}, this method + * will return {@code true} if the name is {@code "--help"}. *

* * @param name the name to check @@ -651,7 +651,7 @@ private PrefixChar(char character) { *

* NOTE:
* The constant fields of this class should be used instead of this method. Other characters could break - * compatibility with shells using special characters as prefixes, such as the | or ; + * compatibility with shells using special characters as prefixes, such as the {@code |} or {@code ;} * characters. *

* @@ -749,6 +749,4 @@ public void invokeCallbacks() { if (this.onErrorCallback == null) return; this.onErrorCallback.accept(this); } -} - - +} \ No newline at end of file diff --git a/src/main/java/lanat/ArgumentGroup.java b/src/main/java/lanat/ArgumentGroup.java index 8343600d..198cb2ca 100644 --- a/src/main/java/lanat/ArgumentGroup.java +++ b/src/main/java/lanat/ArgumentGroup.java @@ -23,7 +23,7 @@ * arguments that are in different groups. For example, given the following group tree: *
  *            +-----------------------+
- *            |  Group 1 (restricted)  |
+ *            |  Group 1 (restricted) |
  *            |-----------------------|
  *            | Argument 1            |
  *            +-----------------------+
@@ -31,7 +31,7 @@
  *          +---------------------------+
  *          |                           |
  *  +---------------+      +-------------------------+
- *  |    Group 2    |      |   Group 3 (restricted)   |
+ *  |    Group 2    |      |   Group 3 (restricted)  |
  *  |---------------|      |-------------------------|
  *  | Argument 2.1  |      | Argument 3.1            |
  *  | Argument 2.2  |      | Argument 3.2            |
diff --git a/src/main/java/lanat/ArgumentParser.java b/src/main/java/lanat/ArgumentParser.java
index fb5a94b0..5fed7fc7 100644
--- a/src/main/java/lanat/ArgumentParser.java
+++ b/src/main/java/lanat/ArgumentParser.java
@@ -23,13 +23,14 @@
  * 

Argument Parser

*

* Provides the ability to parse a command line input and later gather the values of the parsed arguments. + * @see Command */ public class ArgumentParser extends Command { + /** This is used to be able to tell if we should reset the state of all the commands before parsing */ private boolean isParsed = false; private @Nullable String license; private @Nullable String version; - /** * Creates a new command with the given name and description. * @param programName The name of the command. This is the name the user will use to indicate that they want to use this @@ -52,8 +53,9 @@ public ArgumentParser(@NotNull String programName) { /** * Creates a new command based on the given {@link CommandTemplate}. This does not take Sub-Commands into account. - * If you want to add Sub-Commands, use {@link #from(Class)} instead. + * If you want to add Sub-Commands, use {@link ArgumentParser#from(Class)} instead. * @param templateClass The class of the template to use. + * @see CommandTemplate */ public ArgumentParser(@NotNull Class templateClass) { super(templateClass); @@ -62,6 +64,16 @@ public ArgumentParser(@NotNull Class templateClass) { /** * Constructs a new {@link ArgumentParser} based on the given {@link CommandTemplate}, taking Sub-Commands into * account. + *

+ * This is basically a shortcut for the following code: + *

{@code
+	 * new ArgumentParser(clazz) {{
+	 *     this.addCommand(new Command(subCmdClazz)); // do this for all possible sub-commands
+	 * }};
+	 * }
+ * This method basically makes it easier to add Sub-Commands to the given {@link CommandTemplate}. It looks for + * {@link lanat.CommandTemplate.CommandAccessor} annotations in the given class and adds the corresponding + * sub-commands to the {@link Command} object. This is done recursively. * @param templateClass The class of the {@link CommandTemplate} to use. * @return A new {@link ArgumentParser} based on the given {@link CommandTemplate}. * @see CommandTemplate @@ -81,14 +93,12 @@ public static ArgumentParser from(@NotNull Class temp *

* This is basically a shortcut for the following code: *

{@code
-	 * new ArgumentParser(clazz).parse(input).into(clazz);
+	 * ArgumentParser.from(clazz).parse(input).into(clazz);
 	 * }
*

Example:

* This code: *
{@code
-	 * MyTemplate parsed = new ArgumentParser(MyTemplate.class) {{
-	 *     addCommand(new Command(MyTemplate.SubTemplate.class));
-	 * }}
+	 * ArgumentParser.from(MyTemplate.class)
 	 *     .parse(input)
 	 *     .printErrors()
 	 *     .exitIfErrors()
@@ -100,7 +110,10 @@ public static ArgumentParser from(@NotNull Class temp
 	 * MyTemplate parsed = ArgumentParser.parseFromInto(MyTemplate.class, input);
 	 * }
 	 * 
- * + * The example above uses the {@link #parseFromInto(Class, CLInput)} overload, which sets the default options for + * the {@link AfterParseOptions} object. + *

+ * This method uses {@link #from(Class)}. See that method for more info. * @param templateClass The class to use as a template. * @param input The input to parse. * @param options A consumer that can be used for configuring the parsing process. @@ -108,6 +121,8 @@ public static ArgumentParser from(@NotNull Class temp * @return The parsed template. * @see #parseFromInto(Class, CLInput) * @see CommandTemplate + * @see #from(Class) + * @see AfterParseOptions */ public static @NotNull T parseFromInto( @NotNull Class templateClass, @@ -124,12 +139,14 @@ public static ArgumentParser from(@NotNull Class temp /** * Constructs a new {@link ArgumentParser} based on the given {@link CommandTemplate}, parses the given input, and * populates the template with the parsed values. - * + *

+ * See {@link #parseFromInto(Class, CLInput, Consumer)} for more info. * @param templateClass The class to use as a template. * @param input The input to parse. * @param The type of the template. * @return The parsed template. * @see CommandTemplate + * @see #parseFromInto(Class, CLInput, Consumer) */ public static @NotNull T parseFromInto(@NotNull Class templateClass, @NotNull CLInput input) { @@ -218,11 +235,11 @@ private void parseTokens() { @Override - @NotNull ParsedArgumentsRoot getParsedArguments() { - return new ParsedArgumentsRoot( + @NotNull ParseResultRoot getParseResult() { + return new ParseResultRoot( this, - this.getParser().getParsedArgumentsHashMap(), - this.getCommands().stream().map(Command::getParsedArguments).toList(), + this.getParser().getParsedArgsMap(), + this.getCommands().stream().map(Command::getParseResult).toList(), this.getForwardValue() ); } @@ -359,10 +376,10 @@ public AfterParseOptions exitIfNoInput() { } /** - * Returns a {@link ParsedArgumentsRoot} object that contains all the parsed arguments. + * Returns a {@link ParseResultRoot} object that contains all the parsed arguments. */ - public @NotNull ParsedArgumentsRoot getParsedArguments() { - return ArgumentParser.this.getParsedArguments(); + public @NotNull ParseResultRoot getResult() { + return ArgumentParser.this.getParseResult(); } /** @@ -375,17 +392,17 @@ public AfterParseOptions exitIfNoInput() { * @see CommandTemplate */ public T into(@NotNull Class clazz) { - return AfterParseOptions.into(clazz, this.getParsedArguments()); + return AfterParseOptions.into(clazz, this.getResult()); } /** * {@link #into(Class)} helper method. * @param templateClass The Command Template class to instantiate. - * @param parsedArgs The parsed arguments to set the fields of the Command Template class. + * @param parseResult The parsed arguments to set the fields of the Command Template class. */ private static T into( @NotNull Class templateClass, - @NotNull ParsedArguments parsedArgs + @NotNull ParseResult parseResult ) { final T instance = UtlReflection.instantiate(templateClass); @@ -393,7 +410,7 @@ private static T into( // set the values of the fields Stream.of(templateClass.getFields()) .filter(f -> f.isAnnotationPresent(Argument.Define.class)) - .forEach(field -> AfterParseOptions.into$setFieldValue(field, parsedArgs, instance)); + .forEach(field -> AfterParseOptions.into$setFieldValue(field, parseResult, instance)); // now handle the sub-command field accessors (if any) Stream.of(templateClass.getDeclaredClasses()) @@ -410,22 +427,23 @@ private static T into( ); }); - AfterParseOptions.into$handleCommandAccessor(instance, commandAccesorField, parsedArgs); + AfterParseOptions.into$handleCommandAccessor(instance, commandAccesorField, parseResult); }); + instance.afterInstantiation(parseResult); return instance; } /** * {@link #into(Class)} helper method. Sets the value of the given field based on the parsed arguments. * @param field The field to set the value of. - * @param parsedArgs The parsed arguments to set the field value from. + * @param parseResult The parsed arguments to set the field value from. * @param instance The instance of the current Command Template class. * @param The type of the Command Template class. */ private static void into$setFieldValue( @NotNull Field field, - @NotNull ParsedArguments parsedArgs, + @NotNull ParseResult parseResult, @NotNull T instance ) { final var annotation = field.getAnnotation(Argument.Define.class); @@ -433,7 +451,7 @@ private static T into( // get the name of the argument from the annotation or field name final String argName = annotation.names().length == 0 ? field.getName() : annotation.names()[0]; - final @NotNull Optional parsedValue = parsedArgs.get(argName); + final @NotNull Optional parsedValue = parseResult.get(argName); try { // if the field has a value already set and the parsed value is empty, skip it (keep the old value) @@ -470,13 +488,13 @@ private static T into( * {@link #into(Class)} helper method. Handles the {@link CommandTemplate.CommandAccessor} annotation. * @param parsedTemplateInstance The instance of the current Command Template class. * @param commandAccesorField The field annotated with {@link CommandTemplate.CommandAccessor}. - * @param parsedArgs The parsed arguments to set the fields of the Command Template class. + * @param parseResult The parsed arguments to set the fields of the Command Template class. */ @SuppressWarnings("unchecked") private static void into$handleCommandAccessor( @NotNull T parsedTemplateInstance, @NotNull Field commandAccesorField, - @NotNull ParsedArguments parsedArgs + @NotNull ParseResult parseResult ) { final Class fieldType = commandAccesorField.getType(); @@ -493,7 +511,7 @@ private static T into( commandAccesorField.set(parsedTemplateInstance, AfterParseOptions.into( (Class)fieldType, - parsedArgs.getSubParsedArgs(cmdName) + parseResult.getSubResult(cmdName) ) ); } catch (IllegalAccessException e) { diff --git a/src/main/java/lanat/ArgumentType.java b/src/main/java/lanat/ArgumentType.java index d84fada2..571a7334 100644 --- a/src/main/java/lanat/ArgumentType.java +++ b/src/main/java/lanat/ArgumentType.java @@ -287,7 +287,7 @@ int getLastReceivedValuesNum() { /** * Iterates over the values that this argument received when being parsed. This also sets - * this.currentArgValueIndex to the current index of the value. + * {@code this.currentArgValueIndex} to the current index of the value. * * @param args The values that this argument received when being parsed. * @param consumer The consumer that will be called for each value. diff --git a/src/main/java/lanat/Command.java b/src/main/java/lanat/Command.java index 67523e51..fe52c322 100644 --- a/src/main/java/lanat/Command.java +++ b/src/main/java/lanat/Command.java @@ -39,7 +39,7 @@ */ public class Command extends ErrorsContainerImpl - implements ErrorCallbacks, + implements ErrorCallbacks, ArgumentAdder, ArgumentGroupAdder, CommandAdder, @@ -59,7 +59,7 @@ public class Command // error handling callbacks private @Nullable Consumer onErrorCallback; - private @Nullable Consumer onCorrectCallback; + private @Nullable Consumer onCorrectCallback; private final @NotNull ModifyRecord helpFormatter = ModifyRecord.of(new HelpFormatter()); private final @NotNull ModifyRecord<@NotNull CallbacksInvocationOption> callbackInvocationOption = @@ -92,6 +92,7 @@ public Command(@NotNull String name) { /** * Creates a new command based on the given {@link CommandTemplate}. This does not take Sub-Commands into account. + * If you want to add Sub-Commands, use {@link ArgumentParser#from(Class)} instead. * @param templateClass The class of the template to use. * @see CommandTemplate */ @@ -184,10 +185,10 @@ public void registerToCommand(@NotNull Command parentCommand) { * fail, the program will return the result of the OR bit operation that will be applied to all other command * results. For example: *

    - *
  • Command 'foo' has a return value of 2. (0b010)
  • - *
  • Command 'bar' has a return value of 5. (0b101)
  • + *
  • Command 'foo' has a return value of 2. {@code (0b010)}
  • + *
  • Command 'bar' has a return value of 5. {@code (0b101)}
  • *
- * Both commands failed, so in this case the resultant return value would be 7 (0b111). + * Both commands failed, so in this case the resultant return value would be 7 {@code (0b111)}. * @param errorCode The error code to return when this command fails. */ public void setErrorCode(int errorCode) { @@ -310,14 +311,14 @@ boolean uniqueArgumentReceivedValue(@Nullable Argument exclude) { } /** - * Returns a new {@link ParsedArguments} object that contains all the parsed arguments of this command and all its + * Returns a new {@link ParseResult} object that contains all the parsed arguments of this command and all its * Sub-Commands. */ - @NotNull ParsedArguments getParsedArguments() { - return new ParsedArguments( + @NotNull ParseResult getParseResult() { + return new ParseResult( this, - this.parser.getParsedArgumentsHashMap(), - this.subCommands.stream().map(Command::getParsedArguments).toList() + this.parser.getParsedArgsMap(), + this.subCommands.stream().map(Command::getParseResult).toList() ); } @@ -480,19 +481,19 @@ public void setOnErrorCallback(@Nullable Consumer<@NotNull Command> callback) { *

*/ @Override - public void setOnOkCallback(@Nullable Consumer<@NotNull ParsedArguments> callback) { + public void setOnOkCallback(@Nullable Consumer<@NotNull ParseResult> callback) { this.onCorrectCallback = callback; } @Override public void invokeCallbacks() { if (this.shouldExecuteCorrectCallback()) { - if (this.onCorrectCallback != null) this.onCorrectCallback.accept(this.getParsedArguments()); + if (this.onCorrectCallback != null) this.onCorrectCallback.accept(this.getParseResult()); } else { if (this.onErrorCallback != null) this.onErrorCallback.accept(this); } - this.parser.getParsedArgumentsHashMap() + this.parser.getParsedArgsMap() .entrySet() .stream() .sorted((x, y) -> Argument.compareByPriority(x.getKey(), y.getKey())) // sort by priority when invoking callbacks! diff --git a/src/main/java/lanat/CommandTemplate.java b/src/main/java/lanat/CommandTemplate.java index 39d4a969..972a6b5e 100644 --- a/src/main/java/lanat/CommandTemplate.java +++ b/src/main/java/lanat/CommandTemplate.java @@ -61,10 +61,19 @@ * @Argument.Define(names = {"name", "n"}, argType = StringArgumentType.class) * public String name; * - * @Argument.Define(argType = IntegerArgumentType.class, required = true) + * @Argument.Define // name: "numbers". argType: MultipleNumbersArgumentType + * public Integer[] numbers; + * + * @Argument.Define(required = true) // name: "file". argType: NumberRangeArgumentType * public Integer number; - * } - * }
+ * + * @InitDef + * public static void beforeInit(CommandBuildHelper helper) { + * // set the argument type to NumberRangeArgumentType + * helper., Integer>getArgument("number") + * .withArgType(new NumberRangeArgumentType<>(0, 10); + * } + * }} * *

Defining Sub-Commands

*

@@ -100,6 +109,63 @@ */ @Command.Define public abstract class CommandTemplate { + /** The parsed arguments of the command. */ + private ParseResult parseResult; + + /** + * Called right after the Command Template is instantiated by the Argument Parser. + * Sets the {@link #parseResult} field and calls {@link #onValuesReceived()} if the command was used. + *

+ * The reason this is used instead of a constructor is because we don't want to force inheritors to call + * {@code super()} in their constructors. Also, this method is called first by the innermost Command in + * the hierarchy, and then by the parent Commands. + * @param parseResult The parsed arguments of the command. + */ + void afterInstantiation(@NotNull ParseResult parseResult) { + this.parseResult = parseResult; + if (this.wasUsed()) this.onValuesReceived(); + } + + /** + * Returns the {@link ParseResult} instance used by this Command Template. This instance is the one that was + * used to initialize this Command Template. + * @return The {@link ParseResult} instance used by this Command Template. + */ + public final @NotNull ParseResult getParseResult() { + if (this.parseResult == null) + throw new IllegalStateException("Command Template was not properly initialized by the Argument Parser."); + return this.parseResult; + } + + /** + * Returns the {@link Command} instance used by this Command Template. This instance is the one that was + * used to initialize this Command Template. + * @return The {@link Command} instance used by this Command Template. + */ + public final @NotNull Command getCommand() { + return this.getParseResult().getCommand(); + } + + /** + * Returns {@code true} if the Command of this Template was used in the command line. + * @return {@code true} if the Command of this Template was used in the command line, {@code false} otherwise. + */ + public final boolean wasUsed() { + return this.getParseResult().wasUsed(); + } + + /** + * Called when the Command of this Template is used in the command line. + * This method is called after the parsed values are set. + *

+ * This method may be overridden to perform actions when the command is used. + *

+ * This method should not be called manually. It is called automatically by the Argument Parser once the Command + * Template is initialized. + */ + public void onValuesReceived() {} + + /** * Annotation used to define an init method for a Command Template. * @see CommandTemplate#beforeInit(CommandBuildHelper) @@ -157,19 +223,19 @@ ArgumentBuilder arg(@NotNull String name) { * public Integer numberRange; * * @InitDef - * public static void beforeInit(CommandBuildHelper cmdBuildHelper) { + * public static void beforeInit(CommandBuildHelper helper) { * // set the argument type to NumberRangeArgumentType - * cmdBuildHelper., Integer>getArgument("numberRange") + * helper., Integer>getArgument("numberRange") * .withArgType(new NumberRangeArgumentType<>(0, 10); * } * } * } - * @param cmdBuildHelper A helper object that contains the command being initialized and the list of argument builders that may + * @param helper A helper object that contains the command being initialized and the list of argument builders that may * be altered. */ @InitDef - public static void beforeInit(@NotNull CommandBuildHelper cmdBuildHelper) {} + public static void beforeInit(@NotNull CommandBuildHelper helper) {} /** * This method is called after the Command is initialized. This is after the Arguments are instantiated and added diff --git a/src/main/java/lanat/ParsedArguments.java b/src/main/java/lanat/ParseResult.java similarity index 59% rename from src/main/java/lanat/ParsedArguments.java rename to src/main/java/lanat/ParseResult.java index 68e51b3e..ed5c8bd5 100644 --- a/src/main/java/lanat/ParsedArguments.java +++ b/src/main/java/lanat/ParseResult.java @@ -14,20 +14,47 @@ /** * Container for all the parsed arguments and their respective values. */ -public class ParsedArguments { - private final @NotNull HashMap<@NotNull Argument, @Nullable Object> parsedArgs; +public class ParseResult { + /** The parsed arguments. Pairs of each argument and it's respective value */ + private final @NotNull HashMap<@NotNull Argument, @Nullable Object> parsedArgumentValues; + + /** The {@link Command} that this {@link ParseResult} object belongs to */ private final @NotNull Command cmd; - private final @NotNull List<@NotNull ParsedArguments> subParsedArguments; - ParsedArguments( + /** Whether the command was used or not */ + private final boolean wasUsed; + + /** The other inner {@link ParseResult}s for the sub-commands */ + protected final @NotNull List<@NotNull ParseResult> subResults; + + + ParseResult( @NotNull Command cmd, - @NotNull HashMap<@NotNull Argument, @Nullable Object> parsedArgs, - @NotNull List<@NotNull ParsedArguments> subParsedArguments - ) - { - this.parsedArgs = parsedArgs; + @NotNull HashMap<@NotNull Argument, @Nullable Object> parsedArgumentValues, + @NotNull List<@NotNull ParseResult> subResults + ) { + this.parsedArgumentValues = parsedArgumentValues; this.cmd = cmd; - this.subParsedArguments = subParsedArguments; + this.wasUsed = cmd.getTokenizer().hasFinished(); + this.subResults = subResults; + } + + + /** + * Returns {@code true} if the command was used, {@code false} otherwise. + * @return {@code true} if the command was used, {@code false} otherwise + */ + public boolean wasUsed() { + return this.wasUsed; + } + + /** + * Returns the {@link Command} that this {@link ParseResult} object belongs to. This is the Command + * that was used to parse the arguments. + * @return The {@link Command} that this {@link ParseResult} object belongs to + */ + public @NotNull Command getCommand() { + return this.cmd; } /** @@ -39,27 +66,27 @@ public class ParsedArguments { */ @SuppressWarnings("unchecked") // we'll just have to trust the user public @NotNull Optional get(@NotNull Argument arg) { - if (!this.parsedArgs.containsKey(arg)) { + if (!this.parsedArgumentValues.containsKey(arg)) { throw new ArgumentNotFoundException(arg); } - return Optional.ofNullable((T)this.parsedArgs.get(arg)); + return Optional.ofNullable((T)this.parsedArgumentValues.get(arg)); } /** * Returns the parsed value of the argument with the given name. In order to access arguments in sub-commands, use - * the . separator to specify the route to the argument. + * the {@code .} separator to specify the route to the argument. * *

* * Example: *
-	 * {@code var argValue = parsedArguments.get("rootcommand.subCommand.argument")}
+	 * {@code var argValue = result.get("rootcommand.subCommand.argument")}
 	 * 
*

* More info at {@link #get(String...)} * - * @param argRoute The route to the argument, separated by the . character. + * @param argRoute The route to the argument, separated by the {@code .} character. * @param The type of the value of the argument. This is used to avoid casting. A type that does not match the * argument's type will result in a {@link ClassCastException}. * @return An {@link Optional} containing the parsed value of the argument with the given name, or @@ -68,6 +95,9 @@ public class ParsedArguments { * @throws ArgumentNotFoundException If the argument specified in the route does not exist */ public @NotNull Optional get(@NotNull String argRoute) { + if (!argRoute.contains(".")) + return this.get(new String[] { argRoute }); + return this.get(UtlString.split(argRoute, '.')); } @@ -79,7 +109,7 @@ public class ParsedArguments { * * Example: *

-	 * {@code var argValue = parsedArguments.get("rootcommand", "subCommand", "argument")}
+	 * {@code var argValue = result.get("rootcommand", "subCommand", "argument")}
 	 * 
* Returns the parsed value of the argument in the next command hierarchy: *
    @@ -93,6 +123,7 @@ public class ParsedArguments { *
* * @throws CommandNotFoundException If the command specified in the route does not exist + * @throws ArgumentNotFoundException If the argument specified in the route does not exist * @return An {@link Optional} containing the parsed value of the argument with the given name, or * {@link Optional#empty()} if the argument was not found. * @param The type of the value of the argument. @@ -103,24 +134,20 @@ public class ParsedArguments { throw new IllegalArgumentException("argument route must not be empty"); } - ParsedArguments matchedParsedArgs; - if (argRoute.length == 1) { return (Optional)this.get(this.getArgument(argRoute[0])); - } else if ((matchedParsedArgs = this.getSubParsedArgs(argRoute[0])) != null) { - return matchedParsedArgs.get(Arrays.copyOfRange(argRoute, 1, argRoute.length)); - } else { - throw new CommandNotFoundException(argRoute[0]); } + + return this.getSubResult(argRoute[0]) + .get(Arrays.copyOfRange(argRoute, 1, argRoute.length)); } /** - * Returns the argument in {@link #parsedArgs} with the given name. - * + * Returns the argument in {@link #parsedArgumentValues} with the given name. * @throws ArgumentNotFoundException If no argument with the given name is found */ private @NotNull Argument getArgument(@NotNull String name) { - for (var arg : this.parsedArgs.keySet()) { + for (var arg : this.parsedArgumentValues.keySet()) { if (arg.hasName(name)) { return arg; } @@ -129,15 +156,17 @@ public class ParsedArguments { } /** - * Returns the sub {@link ParsedArguments} with the given name. If none is found with the given name, returns - * {@code null}. + * Returns the sub {@link ParseResult} with the given name. If none is found with the given name, + * {@link CommandNotFoundException} is thrown. * * @param name The name of the sub command - * @return The sub {@link ParsedArguments} with the given name, or {@code null} if none is found + * @throws CommandNotFoundException If no sub command with the given name is found + * @return The sub {@link ParseResult} with the given name */ - public ParsedArguments getSubParsedArgs(@NotNull String name) { - for (var sub : this.subParsedArguments) + public @NotNull ParseResult getSubResult(@NotNull String name) { + for (var sub : this.subResults) if (sub.cmd.hasName(name)) return sub; - return null; + + throw new CommandNotFoundException(name); } } \ No newline at end of file diff --git a/src/main/java/lanat/ParseResultRoot.java b/src/main/java/lanat/ParseResultRoot.java new file mode 100644 index 00000000..3ca4d1ac --- /dev/null +++ b/src/main/java/lanat/ParseResultRoot.java @@ -0,0 +1,62 @@ +package lanat; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; + +/** + * Container for all the parsed arguments and their respective values. + * Provides methods specific to the root command. + */ +public class ParseResultRoot extends ParseResult { + private final @Nullable String forwardValue; + + ParseResultRoot( + @NotNull ArgumentParser cmd, + @NotNull HashMap<@NotNull Argument, @Nullable Object> parsedArgumentValues, + @NotNull List<@NotNull ParseResult> subArgs, + @Nullable String forwardValue + ) { + super(cmd, parsedArgumentValues, subArgs); + this.forwardValue = forwardValue; + } + + /** + * Returns the forward value. The forward value is the string that is passed after the {@code --} token. + * @return An {@link Optional} containing the forward value, or {@link Optional#empty()} if there is no forward + * value. + */ + public @NotNull Optional getForwardValue() { + return Optional.ofNullable(this.forwardValue); + } + + /** + * Returns a list of all the {@link ParseResult} objects that belong to a command that was used by the user. + * The list is ordered from the root command to the last used command. + *

+ * The list contains this {@link ParseResultRoot} object as well. + * @return A list of all the {@link ParseResult} objects that belong to a command that was used by the user + */ + public @NotNull List<@NotNull ParseResult> getUsedResults() { + if (!this.wasUsed()) + return List.of(); + + ParseResult current = this; + var list = new ArrayList(1); + + while (current != null) { + list.add(current); + + current = current.subResults.stream() + .filter(ParseResult::wasUsed) + .findFirst() + .orElse(null); + } + + return list; + } +} \ No newline at end of file diff --git a/src/main/java/lanat/ParsedArgumentsRoot.java b/src/main/java/lanat/ParsedArgumentsRoot.java deleted file mode 100644 index 940146b7..00000000 --- a/src/main/java/lanat/ParsedArgumentsRoot.java +++ /dev/null @@ -1,36 +0,0 @@ -package lanat; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.List; -import java.util.Optional; - -/** - * Container for all the parsed arguments and their respective values. - * Provides methods specific to the root command. - */ -public class ParsedArgumentsRoot extends ParsedArguments { - private final @Nullable String forwardValue; - - ParsedArgumentsRoot( - @NotNull ArgumentParser cmd, - @NotNull HashMap<@NotNull Argument, @Nullable Object> parsedArgs, - @NotNull List<@NotNull ParsedArguments> subArgs, - @Nullable String forwardValue - ) - { - super(cmd, parsedArgs, subArgs); - this.forwardValue = forwardValue; - } - - /** - * Returns the forward value. The forward value is the string that is passed after the {@code --} token. - * @return An {@link Optional} containing the forward value, or {@link Optional#empty()} if there is no forward - * value. - */ - public @NotNull Optional getForwardValue() { - return Optional.ofNullable(this.forwardValue); - } -} diff --git a/src/main/java/lanat/argumentTypes/TupleArgumentType.java b/src/main/java/lanat/argumentTypes/TupleArgumentType.java index f7b9e6ba..498a39ec 100644 --- a/src/main/java/lanat/argumentTypes/TupleArgumentType.java +++ b/src/main/java/lanat/argumentTypes/TupleArgumentType.java @@ -52,7 +52,7 @@ public TupleArgumentType(@NotNull Range range, @NotNull ArgumentType argument return null; return argTypeRepr - .concat(new TextFormatter(this.argCount.getRegexRange()).withForegroundColor(Color.BRIGHT_YELLOW)); + .concat(new TextFormatter(this.argCount.getRepresentation()).withForegroundColor(Color.BRIGHT_YELLOW)); } @Override diff --git a/src/main/java/lanat/helpRepresentation/descriptions/RouteParser.java b/src/main/java/lanat/helpRepresentation/descriptions/RouteParser.java index f47b4bbb..cdd84fed 100644 --- a/src/main/java/lanat/helpRepresentation/descriptions/RouteParser.java +++ b/src/main/java/lanat/helpRepresentation/descriptions/RouteParser.java @@ -13,28 +13,28 @@ import java.util.function.BiFunction; /** - * Parser for simple route syntax used in description tags (e.g. args.myArg1.type). + * Parser for simple route syntax used in description tags (e.g. {@code args.myArg1.type}). *

* The route syntax is very simple. It is a dot-separated list of names indicating the path to the object to be * returned. By default, the route initial target is the command the user belongs to. If the route starts with - * !, the user itself becomes the initial target. If the route is empty or null, the command the user + * {@code !}, the user itself becomes the initial target. If the route is empty or null, the command the user * belongs to is returned. *

*

* These are the objects that can be accessed using the route syntax: *

    - *
  • args: the arguments of the command. + *
  • {@code args}: the arguments of the command. *
      - *
    • type: the type of the argument. + *
    • {@code type}: the type of the argument. *

      * Note that this only works if the current target is an {@link Argument}. *

      *
    • *
    *
  • - *
  • groups: the groups of the command.
  • + *
  • {@code groups}: the groups of the command.
  • *
  • - * cmds: the subcommands of the command. + * {@code cmds}: the subcommands of the command. *

    * Note that after selecting a command, all the selectors above can be used again to select inner objects of the command. *

    @@ -44,20 +44,20 @@ *

    Examples

    *
      *
    1. - * Select the type of the argument myArg1 of the current command: - * "args.myArg1.type" + * Select the type of the argument {@code myArg1} of the current command: + * {@code "args.myArg1.type"} *
    2. *
    3. - * Select the Sub-Command myCmd of the current command: - * "cmds.myCmd" + * Select the Sub-Command {@code myCmd} of the current command: + * {@code "cmds.myCmd"} *
    4. *
    5. - * Select the type of the argument myArg1 of the Sub-Command myCmd of the current command: - * "cmds.myCmd.args.myArg1.type" + * Select the type of the argument {@code myArg1} of the Sub-Command {@code myCmd} of the current command: + * {@code "cmds.myCmd.args.myArg1.type"} *
    6. *
    7. * Select the {@link ArgumentType} of the {@link Argument} that is requesting to parse this description: - * "!.type" + * {@code "!.type"} *
    8. *
    */ @@ -69,7 +69,7 @@ public class RouteParser { private RouteParser(@NotNull NamedWithDescription user, @Nullable String route) { // if route is empty, the command the user belongs to is the target - if (UtlString.isNullOrEmpty(route)) { + if (UtlString.isNullOrBlank(route)) { this.currentTarget = RouteParser.getCommandOf(user); this.route = new String[0]; return; @@ -94,7 +94,7 @@ private RouteParser(@NotNull NamedWithDescription user, @Nullable String route) * to is returned. *

    * The reason why the user is needed is because its likely that it will be needed to gather the Command it belongs - * to, and also if the route starts with !, the user itself becomes the initial target. + * to, and also if the route starts with {@code !}, the user itself becomes the initial target. *

    * * @param user the user that is requesting to parse the route @@ -171,4 +171,4 @@ void setCurrentTarget(List list, BiFunction predicate) { + UtlReflection.getSimpleName(this.currentTarget.getClass()) + ' ' + this.currentTarget.getName()) ); } -} +} \ No newline at end of file diff --git a/src/main/java/lanat/parsing/Parser.java b/src/main/java/lanat/parsing/Parser.java index 40273a27..fc7961a1 100644 --- a/src/main/java/lanat/parsing/Parser.java +++ b/src/main/java/lanat/parsing/Parser.java @@ -21,7 +21,7 @@ * of the arguments that are being parsed. *

    * When finished parsing, this class will contain a map of the arguments to their parsed values. This map can be accessed - * by calling {@link Parser#getParsedArgumentsHashMap()}. + * by calling {@link Parser#getParsedArgsMap()}. */ public final class Parser extends ParsingStateBase { /** @@ -41,10 +41,10 @@ public final class Parser extends ParsingStateBase { /** * The parsed arguments. This is a map of the argument to the value that it parsed. The reason this is saved is that - * we don't want to run {@link Parser#getParsedArgumentsHashMap()} multiple times because that can break stuff badly + * we don't want to run {@link Parser#getParsedArgsMap()} multiple times because that can break stuff badly * in relation to error handling. */ - private HashMap<@NotNull Argument, @Nullable Object> parsedArguments; + private HashMap<@NotNull Argument, @Nullable Object> parsedArgumentValues; /** Contains the forward value if one was found. */ private @Nullable String forwardValue; @@ -316,13 +316,13 @@ private void checkForSimilarArgumentName(@NotNull String str) { * This function invokes the {@link Argument#finishParsing()} method on each argument the first time it is called. * After that, it will return the same hashmap. * */ - public @NotNull HashMap<@NotNull Argument, @Nullable Object> getParsedArgumentsHashMap() { - if (this.parsedArguments == null) { - this.parsedArguments = new HashMap<>() {{ + public @NotNull HashMap<@NotNull Argument, @Nullable Object> getParsedArgsMap() { + if (this.parsedArgumentValues == null) { + this.parsedArgumentValues = new HashMap<>() {{ Parser.this.command.getArguments().forEach(arg -> this.put(arg, arg.finishParsing())); }}; } - return this.parsedArguments; + return this.parsedArgumentValues; } private void argumentTypeParseValues(@NotNull Argument argument, @NotNull String... values) { @@ -343,4 +343,4 @@ private void addIncorrectValueNumberError(@NotNull Argument argument, int this.currentTokenIndex, argument, valueCount, isInArgNameList, this.isInTuple )); } -} +} \ No newline at end of file diff --git a/src/main/java/lanat/parsing/Tokenizer.java b/src/main/java/lanat/parsing/Tokenizer.java index 879ecc1b..7f21c37a 100644 --- a/src/main/java/lanat/parsing/Tokenizer.java +++ b/src/main/java/lanat/parsing/Tokenizer.java @@ -268,7 +268,7 @@ private void tokenizeCurrentValue() { } /** - * Returns {@code true} if the given string can be an argument name list, eg: "-fbq". + * Returns {@code true} if the given string can be an argument name list, eg: {@code "-fbq"}. *

    * This returns {@code true} if at least the first character is a valid argument prefix and at least one of the * next characters is a valid argument name. @@ -300,7 +300,7 @@ private boolean isArgNameList(@NotNull String str) { } /** - * Returns {@code true} if the given string can be an argument name, eg: "--help". + * Returns {@code true} if the given string can be an argument name, eg: {@code "--help"}. *

    * This returns {@code true} if the given string is a valid argument name with a double prefix. *

    @@ -378,4 +378,4 @@ private boolean isCharAtRelativeIndex(int index, @NotNull Predicate<@NotNull Cha public String getInputString() { return this.inputString; } -} +} \ No newline at end of file diff --git a/src/test/java/lanat/test/TestingParser.java b/src/test/java/lanat/test/TestingParser.java index eb5a3cfb..04702503 100644 --- a/src/test/java/lanat/test/TestingParser.java +++ b/src/test/java/lanat/test/TestingParser.java @@ -3,7 +3,7 @@ import lanat.ArgumentParser; import lanat.CLInput; import lanat.CommandTemplate; -import lanat.ParsedArgumentsRoot; +import lanat.ParseResultRoot; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -27,9 +27,9 @@ public List parseGetErrors(String args) { return this.parse(CLInput.from(args)).getErrors(); } - public @NotNull ParsedArgumentsRoot parseGetValues(@NotNull String args) { - var res = this.parse(CLInput.from(args)).printErrors().getParsedArguments(); + public @NotNull ParseResultRoot parseGetValues(@NotNull String args) { + var res = this.parse(CLInput.from(args)).printErrors().getResult(); assertNotNull(res, "The result of the parsing was null (Arguments have failed)"); return res; } -} +} \ No newline at end of file diff --git a/src/test/java/lanat/test/exampleTests/CommandTemplateExample.java b/src/test/java/lanat/test/exampleTests/CommandTemplateExample.java index 980f6dc5..d7178ff2 100644 --- a/src/test/java/lanat/test/exampleTests/CommandTemplateExample.java +++ b/src/test/java/lanat/test/exampleTests/CommandTemplateExample.java @@ -12,8 +12,6 @@ @Command.Define(names = "my-program", description = "This is a test program.") public class CommandTemplateExample extends CommandTemplate.Default { - public CommandTemplateExample() {} - @Argument.Define(argType = StringArgumentType.class, description = "This is a string argument.") public Optional string; @@ -26,7 +24,7 @@ public CommandTemplateExample() {} @Argument.Define(names = "arg1", argType = StringArgumentType.class) public String arg1; - @Argument.Define(description = "") + @Argument.Define(description = ". Must be executable.") public File file; @Argument.Define(names = "arg1a", argType = StringArgumentType.class) @@ -67,10 +65,13 @@ public static void afterInit(@NotNull Command cmd) { }}); } + @Override + public void onValuesReceived() { + System.out.println("This is the value of number: " + this.number); + } @Command.Define(names = "sub-command", description = "This is a sub-command.") public static class MySubCommand extends CommandTemplate.Default { - public MySubCommand() {} @Argument.Define(argType = CounterArgumentType.class, description = "This is a counter", names = "c") public int counter = 0; @@ -78,12 +79,20 @@ public MySubCommand() {} @CommandAccessor public AnotherSubCommand anotherSubCommand; + @Override + public void onValuesReceived() { + System.out.println("This is the value of counter: " + this.counter); + } + @Command.Define(names = "another-sub-command", description = "This is a sub-command.") public static class AnotherSubCommand extends CommandTemplate { - public AnotherSubCommand() {} - @Argument.Define(argType = CounterArgumentType.class, description = "This is a counter", names = "c") public int counter = 0; + + @Override + public void onValuesReceived() { + System.out.println("This is the value of counter: " + this.counter); + } } } } \ No newline at end of file diff --git a/src/test/java/lanat/test/exampleTests/ExampleTest.java b/src/test/java/lanat/test/exampleTests/ExampleTest.java index f4bf472e..85d2d515 100644 --- a/src/test/java/lanat/test/exampleTests/ExampleTest.java +++ b/src/test/java/lanat/test/exampleTests/ExampleTest.java @@ -1,10 +1,6 @@ package lanat.test.exampleTests; import lanat.*; -import lanat.argumentTypes.CounterArgumentType; -import lanat.argumentTypes.IntegerArgumentType; -import lanat.argumentTypes.MultipleStringsArgumentType; -import lanat.argumentTypes.NumberRangeArgumentType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.Test; @@ -18,32 +14,26 @@ public void main() { // TextFormatter.enableSequences = false; // ErrorFormatter.errorFormatterClass = SimpleErrorFormatter.class; - var ap = new ArgumentParser("my-program") {{ - this.setCallbackInvocationOption(CallbacksInvocationOption.NO_ERROR_IN_ARGUMENT); - this.setDescription("This is the description of my program. What do you think about it? Oh by the way, don't forget to use the argument!"); - this.setLicense("Here's some more extra information about my program. Really don't know how to fill this out..."); - this.addHelpArgument(); - this.addArgument(Argument.create(new CounterArgumentType(), "counter", "c").onOk(System.out::println)); - this.addArgument(Argument.create(new Example1Type(), "user", "u").required().positional().withDescription("Specify the user/s to use.")); - this.addArgument(Argument.createOfBoolType("t").onOk(v -> System.out.println("present"))); - this.addArgument(Argument.create(new NumberRangeArgumentType<>(0.0, 15.23), "number").onOk(System.out::println).withDescription("The value that matters the most (not really). Hey, this thing is generated automatically as well!: ")); - this.addArgument(Argument.create(new MultipleStringsArgumentType(Range.from(3).to(5)), "string", "s").onOk(System.out::println).withPrefix(Argument.PrefixChar.PLUS)); - this.addArgument(Argument.create(new IntegerArgumentType(), "test").onOk(System.out::println).allowsUnique()); +// ArgumentParser.parseFromInto( +// CommandTemplateExample.class, +// CLInput.from("--number 12 sub-command -ccc"), +// opts -> opts.printErrors() +// ); - this.addCommand(new Command("sub1", "testing") {{ - this.addArgument(Argument.create(new IntegerArgumentType(), "required").required().positional()); - this.addArgument(Argument.create(new NumberRangeArgumentType<>(0.0, 15.23), "number").onOk(System.out::println)); - this.setDescription("Now this is the description of the subcommand inside the main command."); - this.addCommand(new Command("sub2", "testing") {{ - this.addArgument(Argument.create(new IntegerArgumentType(), "required").required().positional()); - this.addArgument(Argument.create(new NumberRangeArgumentType<>(0.0, 15.23), "number").onOk(System.out::println)); - }}); - }}); - }}; + ArgumentParser.from(CommandTemplateExample.class) + .parse(CLInput.from("--number 12 sub-command -ccc")) + .getResult() + .getUsedResults() + .forEach(result -> { + var cmdName = result.getCommand().getName(); + System.out.println(cmdName + " was used!"); - ap.parse(CLInput.from("josh ! --number 2 sub1 --required 1 --number 121")) - .printErrors() - .getParsedArguments(); + switch (cmdName) { + case "my-program" -> System.out.println("number value: " + result.get("number")); + case "sub-command" -> System.out.println("c value: " + result.get("c")); + case "another-sub-command" -> System.out.println("c value: " + result.get("c")); + } + }); } public static class Example1Type extends ArgumentType { diff --git a/src/test/java/lanat/test/units/TestParsedValues.java b/src/test/java/lanat/test/units/TestParsedValues.java index 8caa50ba..d079d8e4 100644 --- a/src/test/java/lanat/test/units/TestParsedValues.java +++ b/src/test/java/lanat/test/units/TestParsedValues.java @@ -1,6 +1,6 @@ package lanat.test.units; -import lanat.ParsedArgumentsRoot; +import lanat.ParseResultRoot; import lanat.exceptions.ArgumentNotFoundException; import lanat.test.UnitTests; import org.junit.jupiter.api.DisplayName; @@ -11,7 +11,7 @@ import static org.junit.jupiter.api.Assertions.*; public class TestParsedValues extends UnitTests { - private ParsedArgumentsRoot parseArgs(String args) { + private ParseResultRoot parseArgs(String args) { return this.parser.parseGetValues(args); } diff --git a/src/test/java/module-info.java b/src/test/java/module-info.java index 5cc43e2a..c03609d2 100644 --- a/src/test/java/module-info.java +++ b/src/test/java/module-info.java @@ -6,7 +6,7 @@ requires textFormatter; exports lanat.test to org.junit.platform.commons, lanat; - exports lanat.test.exampleTests to org.junit.platform.commons, lanat; + exports lanat.test.exampleTests to org.junit.platform.commons, lanat, utils; exports lanat.test.units to lanat, org.junit.platform.commons; exports lanat.test.units.commandTemplates to lanat, org.junit.platform.commons, utils; } \ No newline at end of file