Giter VIP home page Giter VIP logo

brigadier's Introduction

Brigadier Latest release License

Brigadier is a command parser & dispatcher, designed and developed for Minecraft: Java Edition and now freely available for use elsewhere under the MIT license.

Installation

Brigadier is available to Maven & Gradle via libraries.minecraft.net. Its group is com.mojang, and artifact name is brigadier.

Gradle

First include our repository:

maven {
    url "https://libraries.minecraft.net"
}

And then use this library (change (the latest version) to the latest version!):

compile 'com.mojang:brigadier:(the latest version)'

Maven

First include our repository:

<repository>
  <id>minecraft-libraries</id>
  <name>Minecraft Libraries</name>
  <url>https://libraries.minecraft.net</url>
</repository>

And then use this library (change (the latest version) to the latest version!):

<dependency>
    <groupId>com.mojang</groupId>
    <artifactId>brigadier</artifactId>
    <version>(the latest version)</version>
</dependency>

Contributing

Contributions are welcome! :D

Most contributions will require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

Usage

At the heart of Brigadier, you need a CommandDispatcher<S>, where <S> is any custom object you choose to identify a "command source".

A command dispatcher holds a "command tree", which is a series of CommandNodes that represent the various possible syntax options that form a valid command.

Registering a new command

Before we can start parsing and dispatching commands, we need to build up our command tree. Every registration is an append operation, so you can freely extend existing commands in a project without needing access to the source code that created them.

Command registration also encourages the use of a builder pattern to keep code cruft to a minimum.

A "command" is a fairly loose term, but typically it means an exit point of the command tree. Every node can have an executes function attached to it, which signifies that if the input stops here then this function will be called with the context so far.

Consider the following example:

CommandDispatcher<CommandSourceStack> dispatcher = new CommandDispatcher<>();

dispatcher.register(
    literal("foo")
        .then(
            argument("bar", integer())
                .executes(c -> {
                    System.out.println("Bar is " + getInteger(c, "bar"));
                    return 1;
                })
        )
        .executes(c -> {
            System.out.println("Called foo with no arguments");
            return 1;
        })
);

This snippet registers two "commands": foo and foo <bar>. It is also common to refer to the <bar> as a "subcommand" of foo, as it's a child node.

At the start of the tree is a "root node", and it must have LiteralCommandNodes as children. Here, we register one command under the root: literal("foo"), which means "the user must type the literal string 'foo'".

Under that is two extra definitions: a child node for possible further evaluation, or an executes block if the user input stops here.

The child node works exactly the same way, but is no longer limited to literals. The other type of node that is now allowed is an ArgumentCommandNode, which takes in a name and an argument type.

Arguments can be anything, and you are encouraged to build your own for seamless integration into your own product. There are some standard arguments included in brigadier, such as IntegerArgumentType.

Argument types will be asked to parse input as much as they can, and then store the "result" of that argument however they see fit or throw a relevant error if they can't parse.

For example, an integer argument would parse "123" and store it as 123 (int), but throw an error if the input were onetwothree.

When a command is actually run, it can access these arguments in the context provided to the registered function.

Parsing user input

So, we've registered some commands and now we're ready to take in user input. If you're in a rush, you can just call dispatcher.execute("foo 123", source) and call it a day.

The result of execute is an integer that was returned from an evaluated command. The meaning of this integer depends on the command, and will typically not be useful to programmers.

The source is an object of <S>, your own custom class to track users/players/etc. It will be provided to the command so that it has some context on what's happening.

If the command failed or could not parse, some form of CommandSyntaxException will be thrown. It is also possible for a RuntimeException to be bubbled up, if not properly handled in a command.

If you wish to have more control over the parsing & executing of commands, or wish to cache the parse results so you can execute it multiple times, you can split it up into two steps:

final ParseResults<S> parse = dispatcher.parse("foo 123", source);
final int result = execute(parse);

This is highly recommended as the parse step is the most expensive, and may be easily cached depending on your application.

You can also use this to do further introspection on a command, before (or without) actually running it.

Inspecting a command

If you parse some input, you can find out what it will perform (if anything) and provide hints to the user safely and immediately.

The parse will never fail, and the ParseResults<S> it returns will contain a possible context that a command may be called with (and from that, you can inspect which nodes the user entered, complete with start/end positions in the input string). It also contains a map of parse exceptions for each command node it encountered. If it couldn't build a valid context, then the reason why is inside this exception map.

Displaying usage info

There are two forms of "usage strings" provided by this library, both require a target node.

getAllUsage(node, source, restricted) will return a list of all possible commands (executable end-points) under the target node and their human readable path. If restricted, it will ignore commands that source does not have access to. This will look like [foo, foo <bar>].

getSmartUsage(node, source) will return a map of the child nodes to their "smart usage" human readable path. This tries to squash future-nodes together and show optional & typed information, and can look like foo (<bar>).

GitHub forks GitHub stars

brigadier's People

Contributors

aikar avatar beetmacol avatar bhellstream avatar boq avatar dinnerbone avatar donotspampls avatar earthcomputer avatar jarviscraft avatar mrmicky-fr avatar parberge avatar peterix avatar piratjsk avatar seargedp avatar slicedlime avatar theel0ja avatar theintellobox avatar themrmilchmann avatar yourwaifu avatar zly2006 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

brigadier's Issues

`SuggestionsBuilder.getRemainingLowerCase()` does not work correctly for Unicode special casing rules

Version

1.0.18

Description

This is basically what has been raised as concern in #86 (comment)

The current SuggestionsBuilder code does not correctly handle cases where after lowercasing the string, the length respectively the character indices change.
For language-insensitive conversion (Locale.ROOT) this can currently only occur for İ (U+0130; "Latin Capital Letter I with Dot Above"), see also Unicode Special Casing (but it could affect more code points in the future).

The effect of this is that command nodes and argument types which use SuggestionsBuilder.getRemainingLowerCase() fail to provide command suggestions.
Affected built-in command nodes / argument types:

  • LiteralCommandNode
  • BoolArgumentType

Possible solutions

Manually trying to map between the source and the lowercased string is probably rather difficult, if not impossible.
Instead an alternative might be to use one of the Character methods for lowercasing (toLowerCase(char) or toLowerCase(int)), which maintain a 1:1 relationship. However, that would require manually going through all characters / code points and converting them which is very likely less performant than the String.toLowerCase methods.

Example (Minecraft)

Version: 21w17a

Type in chat:

/execute as @e[name="İ"] 

❌ Notice how no suggestions are shown

Example (code)

public class BrigadierTest {
    public static void main(String[] args) throws CommandSyntaxException {
        List<CommandDispatcher<Object>> dispatchers = Arrays.asList(createWithLiteral(), createWithBool());
        for (CommandDispatcher<Object> dispatcher : dispatchers) {
            for (String s : Arrays.asList("'!'", "'\u0130'")) {
                ParseResults<Object> parseResults = dispatcher.parse("command " + s + " ", null);
                System.out.println(s + ": " + dispatcher.getCompletionSuggestions(parseResults).join());
            }
        }
    }

    private static CommandDispatcher<Object> createWithLiteral() {
        CommandDispatcher<Object> dispatcher = new CommandDispatcher<>();
        dispatcher.register(LiteralArgumentBuilder.literal("command")
                .then(RequiredArgumentBuilder.argument("first", StringArgumentType.string()).executes(c -> {
                    return 1;
                }).then(LiteralArgumentBuilder.literal("second")).executes(c -> {
                    return 1;
                })));
        return dispatcher;
    }

    private static CommandDispatcher<Object> createWithBool() {
        CommandDispatcher<Object> dispatcher = new CommandDispatcher<>();
        dispatcher.register(LiteralArgumentBuilder.literal("command")
                .then(RequiredArgumentBuilder.argument("first", StringArgumentType.string()).executes(c -> {
                    return 1;
                }).then(RequiredArgumentBuilder.argument("second", BoolArgumentType.bool()).executes(c -> {
                    return 1;
                }))));
        return dispatcher;
    }
}

❌ When the first argument contains İ (\u0130) CommandDispatcher does not provide any suggestions

No response on commands without arguments

commandDispatcher.register(Commands.literal(".ping") .executes(c -> { c.getSource() .getChannel().sendMessage("Pong!"); return 1; }) );

And, using this code:
CommandNode<DiscordCommandSource> commandNode = Iterables.getLast(results.getContext().getNodes().keySet()); Map<CommandNode<DiscordCommandSource>, String> map = commandDispatcher.getSmartUsage(commandNode, s.getSource());
It does not return an information about commands without arguments. Am I doing something wrong?

Hardcoded int return type

Currently commands are limited to returning only signed 32 bit integers. This restriction has been known to be very limiting in minecraft commands, and likely even more limiting in any other projects that may wish to use Brigadier.

As such I have started a fork/branch that removes this restriction and allows any return type, as long as that type implements a specific interface, or a wrapper is written for that type. There are still a few issues that need to be resolved, however it is functioning (all existing unit tests pass).

I have not created a pull request yet because it is not in a state where it should be merged, so I am making an issue here to get feedback on the current implementation and what should be changed. The branch can be found at vdvman1/brigadier.

Unfortunately, the current implementation is a breaking change, and I explain the reason in an issue on the fork. I do want to make it backwards compatible, but I haven't managed it yet.

I am fully open to any criticism of this change, and willing to change any part of it to improve the functionality, I am even willing to scrap it altogether if a better solution is put forth. I will not create a pull request until I am told that Mojang are happy with this, since I am aware that this might not fit the ideals of the project.


Details

The result of a command is now an interface called Value. Any type that needs to be returned by a command should either implement Value directly, or a wrapper class can be made.

Value has defined methods for converting to a set of "standard" types: byte, char, short, int, long, float, double, string, list, map, and long char (which uses an int to store a UTF-32 character, which will likely be used when char would normally be used)
These types should cover the vast majority of use cases, and if a command expects anything more specific than this an instanceof check and a cast can be used to get access to more specific methods.
It is already very likely that this standard list of conversions will be reduced, and split into different interfaces (or maybe classes).

The Value interface is intentionally named very generically, so that it may be used in other projects for data structures of arbitrary types

已经删除

算了,在GitHub上骂迷你世界不好。但是我不知道怎么删除这条评论。

[Suggestion] Host javadocs online

I believe it would be beneficial to host the javadocs online as it is far more convenient to browse through that rather than the source code. Furthermore, it allows the javadocs of dependent projects to link back to the javadocs for brigadier.

Interesting issue with single argument children alongside multi argument children

Consider the following command tree:

dispatcher.register(literal("goal")
    .then(argument("y", integer())
        .executes(c -> {
            setGoal(new GoalYLevel(
                c.getArgument("y", Integer.class)
            ));
            return 0;
        })
    )
    .then(argument("x", integer())
        .then(argument("z", integer())
            .executes(c -> {
                setGoal(new GoalXZ(
                    c.getArgument("x", Integer.class),
                    c.getArgument("z", Integer.class)
                ));
                return 0;
            })
        )
    )
    .then(argument("x", integer())
        .then(argument("y", integer())
            .then(argument("z", integer())
                .executes(c -> {
                    setGoal(new GoalBlock(
                        c.getArgument("x", Integer.class),
                        c.getArgument("y", Integer.class),
                        c.getArgument("z", Integer.class)
                    ));
                    return 0;
                })
            )
        )
    )
);

This should have 3 possible usages

goal <y>
goal <x> <z>
goal <x> <y> <z>

However, when I try to execute the command with a single integer argument (via goal 1) an exception is thrown!

com.mojang.brigadier.exceptions.CommandSyntaxException: Unknown command at position 6: goal 1<--[HERE]
  at com.mojang.brigadier.exceptions.SimpleCommandExceptionType.createWithContext(SimpleCommandExceptionType.java:21)
  at com.mojang.brigadier.CommandDispatcher.execute(CommandDispatcher.java:283)
  at com.mojang.brigadier.CommandDispatcher.execute(CommandDispatcher.java:178)
  at com.mojang.brigadier.CommandDispatcher.execute(CommandDispatcher.java:143)
  ...

Both the 2-arg and 3-arg usages work as intended:
Chat Output

[Bug] Merging of sibling nodes with same name is faulty

It appears it is intended that sibling nodes with the same name exist (see this comment). However the way they are treated is in my opinion rather unintuitive.

The method CommandNode.addChild(CommandNode<S>) replaces the command of the existing node and adds the children of the new node to the existing one. This is, at least from my point of view, not the wanted behavior since

  • The old command is lost
  • Other attributes of the new node like the requirement are lost

It might be best to either remove this merging and allow siblings with the same name, or to prevent them when building.

Example code

CommandDispatcher<Object> dispatcher = new CommandDispatcher<>();
    
dispatcher.register(
    literal("foo")
    .then(
        argument("bar", word())
        .executes(c -> {
            System.out.println("string");
            return 1;
        })
    )
    .then(
        argument("bar", bool())
        .executes(c -> {
            System.out.println("bool");
            return 1;
        })
    )
);

dispatcher.execute("foo not_a_bool", null);

Pass context to ArgumentType.parse

For a few of my projects I'm considering using Brigadier, however one limitation has been stopping me from using this: ArgumentType.parse is not context-aware.

In many cases, the object to be returned requires context of where or by who the command was invoked. The simple solution would be to pass the source object to the parse method as second parameter, however this would cause breaking changes in the library.

To solve this, I propose to give the parse method two signatures: parse(StringReader reader) and parse(StringReader reader, Object context), both with a default implementation which returns null.
Sadly due to how the current arguments are implemented they do not supply the type of the context, but on a per-application basis this could be done with a manual cast in the parse() method. To handle both functions, the following could be done:

    // ArgumentCommandNode.java
    @Override
    public void parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException {
        final int start = reader.getCursor();
        final T result = type.parse(reader);
        if (result == null) {
            // The context-unaware method was not implemented, call the context-aware method
            result = type.parse(reader, contextBuilder.getSource())
        }
        if (result == null) {
            // Throw an error to avoid magic NullPointerExceptions in the user's code
            throw CommandSyntaxException("No implementation for parse on argument type!")  // Simple error as example, should be improved
        }
        final ParsedArgument<S, T> parsed = new ParsedArgument<>(start, reader.getCursor(), result);

        contextBuilder.withArgument(name, parsed);
        contextBuilder.withNode(this, parsed.getRange());
    }

This way users can implement context-aware parse methods without breaking any existing code, unless someone implements an ArgumentType that returns a nullable value.

Long chains of optional arguments = lots of code duplicated

Long chains of optional arguments in a row leads to very verbose and repetitive code, as seen for example in this custom command. I already tried a solution in #18 , but as Dinnerbone pointed out it adds the extra burden/expectation on argument types to provide extra defaulted methods.
I'm not 100% sure how to solve this problem yet, but it's definitely one that needs to be solved, so I'm opening this issue for discussion.

Please consider adding example code

The provided snipped on the project's readme doesn't even state which classes it's importing. I was unable to find out what argument is or where it is coming from. That's just one example but there are many more. I think a full example of a few simple commands should suffice.

[BUG] `StringReader.readUnquotedString()` does not support non-ASCII characters

I'm the maintainer of Chimera, a library that allows the Brigadier command framework to be used in Spigot plugins. Recently, a developer opened an interesting issue in which they reported that argument parsing failed when non-ASCII characters were specified.

Sample error

Looking into the issue, the cause of the argument parsing failing for non-ASCII characters can be traced to:

    // StringArgumentType's parse() method
    @Override
    public String parse(final StringReader reader) throws CommandSyntaxException {
        if (type == StringType.GREEDY_PHRASE) {
            final String text = reader.getRemaining();
            reader.setCursor(reader.getTotalLength());
            return text;
        } else if (type == StringType.SINGLE_WORD) {
            return reader.readUnquotedString(); // <-- calls this
        } else {
            return reader.readString();
        }
    }
    
    // StringReader's readUnquotedString() method
    public String readUnquotedString() {
        final int start = cursor;
        while (canRead() && isAllowedInUnquotedString(peek())) { // <-- calls this
            skip();
        }
        return string.substring(start, cursor);
    }
    
    // Cause of failure
    public static boolean isAllowedInUnquotedString(final char c) {
        return c >= '0' && c <= '9'
            || c >= 'A' && c <= 'Z'
            || c >= 'a' && c <= 'z'
            || c == '_' || c == '-'
            || c == '.' || c == '+';
    }

This affects StringArgumentType.word(), StringArgumentType.string(), StringReader.readString() and StringReader.readUnquotedString() and any other dependencies on these classes/methods.

Maybe I'm missing some context but it seems weird to only allow a small subset of ASCII characters. In my opinion, this implementation of StringReader.readUnquotedString() is extremely wonky and should be refactored to support non-ASCII characters.

In the interim, I decided to create a simple utility method that reads strings until a whitespace is encountered. It behaves similarly to StringReader.readUnquotedString() while supporting special characters.

Source

    public static String unquoted(StringReader reader) {
        var start = reader.getCursor();
        while (reader.canRead() && reader.peek() != ' ') {
            reader.skip();
        }
        
        return reader.getString().substring(start, reader.getCursor());
    }

[Suggestion] Add exception type for command execution failure

Currently the only existing exception type is CommandSyntaxException, wich is also used by Command.run(CommandContext<S>).

However, in most (if not all) cases exceptions thrown by run are unrelated to the syntax of the command, they are rather related to the state of the application (e.g. the state of the Minecraft world). Therefore it might be good to add a new separate exception type, such as CommandExecutionException and add it as thrown exception type to run.
Assuming that this would be a checked exception type, such a change might cause backward incompatibilities in some situations.

Additionally the following changes could be done:

  • Remove CommandSyntaxException from the throws clause of run
  • Remove CommandSyntaxException constructor without input and cursor parameter; 'real' syntax exceptions should have enough context to specify input and cursor values

This would require larger refactoring for dependent projects because they are currently throwing CommandSyntaxException inside run, and maybe there are edge cases where throwing CommandSyntaxException from run is reasonable.

Will this upgrade to Java16

I wanted to use this library in my Android application so I can use up to Java8.

Minecraft was updated to Java16.

Will this library use Java16?

[Suggestion] Allow registration of 'docs' for completions

Example from worldedit, which has managed to add flags into the completion engine.

image

When completions are difficult to understand, or a reminder is needed on what a specific completion does, it would be handy for the client to be able to get some registered help text associated with an argument position.

e.g. additional text could be displayed for either the selected argument in game, or all the arguments in the list.

How??

Ok first: How do I use this?!
second: Could I use this with 1.7 or 1.8?
third: i prolly have more questions

Can node graphs have cycles?

I was reading the Minecraft Wiki article about Brigadier which states:

/execute run redirects to the root node of the vanilla command dispatcher

Which implies that the command 'tree' can have cycles (which would mean that it's technically not a tree, which must be acyclic, but in in fact a rooted unidirected graph).

So I just wanted to clarify whether or not what the Wiki claims is correct, and whether it is technically possible for node graphs to have cycles (perhaps even back to the root node) or whether Minecraft does something special to achieve execute's recursive/cyclic functionality.

(If that's the case, it may be worth clarifying this in the README.md.)

[Bug] Duplicate argument names are not prevented

Currently there can be multiple arguments with the same name. This likely indicates a mistake by the programmer since the last argument would overwrite the values of all previous arguments in the CommandContext.

dispatcher.register(
    literal("foo")
    .then(
        argument("bar", integer())
        .executes(c -> {
            System.out.println("Bar1 called");
            return 1;
        })
        .then(
            argument("bar", bool())
            .executes(c -> {
                System.out.println("Bar2 called");
                return 1;
            })
        )
    )
);

dispatcher.execute("foo 123 true", null);

[Question] Attach a command description to each Node?

Problem

I want to provide a user interface with similiar information as this:
grafik

The leading name in dark aqua is the name of the command, the text in dark gray is the description of this command (i.e. what it does when invoked), the <number> is the amount of children and the red text is the usage.

Current status (I am aware of)

Now on to the parts it displays and how I think you can find them in Brigardier:

Feature Implementation note
Amount of children A call to getChildren and its size
Usage A call to get(Smart)Usage
Name Literal/Argument name. Might differ from the intended display name though.
Description I did not see any reference to anything like this in the source and Minecrafts's help command does not show anything like it either.

Is anything like this planned or within the scope of the project? Are there any constraints it would have to fulfill, such as being localizable?

Workarounds

  • Make my own Map from "fully qualified command name" to "description" via a config
  • Another possibility that was raised was using my own wrapper type, replacing the needed factories (literal, argument) to return that and then casting the children and commands to it, to retrieve the extra data. Doesn't sound particularly awesome, but should work as well.

I apologize if I missed something that already allows this. I apologize if that is the case and would be happy, if the issue would be closed with a pointer to where I should look :)

Tests (where relevant) don't actually make sure that an exception was thrown.

Tests like these:

@Test
public void readQuotedString_noOpen() throws Exception {
try {
new StringReader("hello world\"").readQuotedString();
} catch (final CommandSyntaxException ex) {
assertThat(ex.getType(), is(CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerExpectedStartOfQuote()));
assertThat(ex.getCursor(), is(0));
}
}

will actually pass if no exception is thrown.

Maven not working

Hello Mojang (Microsoft?),
The maven dependency is not functional. When trying to insert it into my pom.xml (version: 1.0.18 artifactId: brigadier groupId: com.mojang), it simply cannot find the package. When searching for it on the maven search website, it comes back with nothing.

Is there something wrong with the maven integration?

Redirected command needs to have arguments

Problem

I wanted to make an alias for a command, so I registered a node that just redirected to my target, which worked nicely if the target command had arguments.

MWE

CommandDispatcher<Object> subject = new CommandDispatcher<>();
Object source = new Object();

LiteralCommandNode<Object> node = subject.register(
    literal("Hey")
        .executes((it) -> {
            System.out.println("Hey!");
            return 0;
        })
);
subject.register(
    literal("redirected")
        .redirect(node)
);
System.out.println(subject.execute("redirected", source));

Expected result

Hey!
0

Actual result

com.mojang.brigadier.exceptions.CommandSyntaxException: Unknown command at position 10: redirected<--[HERE]

	at com.mojang.brigadier.exceptions.SimpleCommandExceptionType.createWithContext(SimpleCommandExceptionType.java:21)
	at com.mojang.brigadier.CommandDispatcher.execute(CommandDispatcher.java:283)
	at com.mojang.brigadier.CommandDispatcher.execute(CommandDispatcher.java:176)
	at com.mojang.brigadier.CommandDispatcher.execute(CommandDispatcher.java:141)

Small analysis

It looks like this might be by design, if so documenting it would be nice (or a RTFM link :)

If am not mistaken the reason reason for this is as follows:

context.withCommand(child.getCommand());

// (1)
if (reader.canRead(child.getRedirect() == null ? 2 : 1)) {
    reader.skip();
    if (child.getRedirect() != null) {
        final CommandContextBuilder<S> childContext = new CommandContextBuilder<>(this, source, child.getRedirect(), reader.getCursor());

        // (2)
        final ParseResults<S> parse = parseNodes(child.getRedirect(), reader, childContext);
        context.withChild(parse.getContext());
        return new ParseResults<>(context, parse.getReader(), parse.getExceptions());
    } else {
        final ParseResults<S> parse = parseNodes(child, reader, context);
        if (potentials == null) {
            potentials = new ArrayList<>(1);
        }
        potentials.add(parse);
    }

Line (1) prevents the command from being parsed correctly, as the input ends after the literal of the redirected command. This is also the line that makes me think it might be deliberate.

You'd need to change that 1 to 0 in the first if and special case the case when the reader can not read anymore. Additionally you need to take care that the child context is correctly set, so that redirect modifiers work.

Further remarks

Line (1) could also hint at somebody just wanting to require an argument separator behind a redirected command and allow it to have no arguments, but that has a few flaws:

  • It doesn't work if there just is no space at the end, which is likely the norm
  • It doesn't work. It will recursively try to parse the rest and find nothing (as the reader is now at the end thanks to the skip call), which means that the recursive parseNodes call (2) fails. This leads to the current context having the wrong node, and the child context having no nodes.
    This context is then passed on to execute, which does the following:
    1. Finds that contexts is not empty and enters the loop
    2. Reads the first (and only) context and finds that the child is not null
    3. Enters the if(child != null)
    4. Checks if(child.hasNodes()), which will be false, as outlined above - it didn't match anything in the recursive call
    5. This leads to the whole portion reassigning next to be skipped, commandFound is not set and the outer while loop exists, as contexts is set to next which means to null.
    6. The last if realizes no command was found and throws an exception.

Closing remarks

I think that wall of text can be summed up with a few lines:

  1. Is redirecting to commands without any arguments deliberately forbidden?
  2. Why does the if check if the command is followed by only one character if it has a modifier, when that does not seem to change the outcome?
  3. Some stuff about why it happens and a way to likely make it work.

Thank you for reading up until here :)

Argument node is not considered when literal is impermissible with same name

Consider a literal that has two children: a literal called bar, and an argument node of type StringArgumentType.word(). The literal is impermissible, i.e. the requirement returns false, while the argument node has a non-null Command. That is,

subject.register(literal("foo")
    .then(
        literal("bar")
            .requires(source -> false)
    )
    .then(
        argument("argument", StringArgumentType.word())
            .executes(command)
    )
);

Now, executing foo bar will fail with Incorrect argument for command at position 4: foo <--[HERE] since the dispatcher doesn't consider the argument nodes as a fallback when the literal requirements are not met. Is this by design?
Failing test
Edit: I'm working on a PR

License

Brigadier is currently MIT license. Would you be willing to relicense it to dual MIT/Apache-2? That would be really useful, thanks.

Unexpected argument collision in single-argument branches

Consider the following command tree:

dispatcher.register(literal("goal")
    .then(argument("y", integer())
        .executes(c -> {
            setGoal(new GoalYLevel(
                c.getArgument("y", Integer.class)
            ));
            return 0;
        })
    )
    .then(argument("x", integer())
        .then(argument("z", integer())
            .executes(c -> {
                setGoal(new GoalXZ(
                    c.getArgument("x", Integer.class),
                    c.getArgument("z", Integer.class)
                ));
                return 0;
            })
        )
    )
    .then(argument("x", integer())
        .then(argument("y", integer())
            .then(argument("z", integer())
                .executes(c -> {
                    setGoal(new GoalBlock(
                        c.getArgument("x", Integer.class),
                        c.getArgument("y", Integer.class),
                        c.getArgument("z", Integer.class)
                    ));
                    return 0;
                })
            )
        )
    )
);

This should have 3 possible usages

goal <y>
goal <x> <z>
goal <x> <y> <z>

However, when I try to execute the command with a single integer argument (via goal 1) an exception is thrown!

com.mojang.brigadier.exceptions.CommandSyntaxException: Unknown command at position 6: goal 1<--[HERE]
  at com.mojang.brigadier.exceptions.SimpleCommandExceptionType.createWithContext(SimpleCommandExceptionType.java:21)
  at com.mojang.brigadier.CommandDispatcher.execute(CommandDispatcher.java:283)
  at com.mojang.brigadier.CommandDispatcher.execute(CommandDispatcher.java:178)
  at com.mojang.brigadier.CommandDispatcher.execute(CommandDispatcher.java:143)
  ...

Both the 2-arg and 3-arg usages work as intended:
Chat Output

I have found that the only solution to this is to rename the argument in the single argument branch from y.
I believe this issue relates to the comment made by Dinnerbone Here.

[Suggestion] Optional Arguments

Ok this is a bit rough to start.

When commands in Minecraft are created, you have either 2 ways of creating commands that are executed.
Either implement every single execution itself. (If you have 5 optional parameters that's getting annoying really quickly)
Or you implement a CommandContext Wrapper that basically does this:

	public boolean hasValue(String id, Class<?> type)
	{
		try
		{
			return source.getArgument(id, type) != null;
		}
		catch(Exception e)
		{
			return false;
		}
	}
	
	public <T> T getOrDefault(String id, Class<T> type, T defaultValue)
	{
		try
		{
			return source.getArgument(id, type);
		}
		catch(Exception e)
		{
		}
		return defaultValue;
	}

It would be nice if you could check if arguments have been defined or allow to getOrDefault.
This is mainly there to reduce code. Implementing everything 5 times. Even if Lambdas are being used that call a helper function that basically does the same.
Instead this could look a lot cleaner.

Here is a example with my CommandBuilder & Command Wrapper

	public static CommandBuilder createGenStart()
	{
		CommandBuilder builder = new CommandBuilder("gen");
		//Normal Gen
		Command<CommandSource> radius = GenCommand::executeRadius;
		builder.addLiteral("radius");
		builder.addArgument("Task Name", StringArgumentType.word());
		builder.addArgument("Shape", StringArgumentType.word(), GenCommand::listShape);
		builder.addArgument("Center", ColumnPosArgument.columnPos(), CenterArgument::listSimpleSuggestion);
		builder.addArgument("Radius", IntegerArgumentType.integer(1, 25000), radius);
		builder.addArgument("Dimension", DimensionArgument.getDimension(), radius); //Optional
		builder.addArgument("Generation Type", StringArgumentType.word(), GenCommand::listSuggestions, radius).popTop(); //PopTop basically goes down the logic tree allowing to make a new branch. A single/multi pop exists too.
		
		Command<CommandSource> expansion = GenCommand::executeExpansion;
		builder.addLiteral("expansion");
		builder.addArgument("Task Name", StringArgumentType.word());
		builder.addArgument("Shape", StringArgumentType.word(), GenCommand::listShape);
		builder.addArgument("Center", ColumnPosArgument.columnPos(), CenterArgument::listSimpleSuggestion);
		builder.addArgument("Min Radius", IntegerArgumentType.integer(1));
		builder.addArgument("Max Radius", IntegerArgumentType.integer(1), expansion);
		builder.addArgument("Dimension", DimensionArgument.getDimension(), expansion);
		builder.addArgument("Generation Type", StringArgumentType.word(), GenCommand::listSuggestions, expansion).popTop();
		return builder;
	}
	
	private static int executeRadius(CommandContext<CommandSource> source)
	{
		CommandWrapper wrapper = new CommandWrapper(source);
		String name = wrapper.get("Task Name", String.class);
		GenShape shape = wrapper.hasValue("Shape", String.class) ? GenShape.valueOf(wrapper.get("Shape", String.class)) : wrapper.get("Shape", GenShape.class);
		BlockPos center = CenterArgument.getVanillaBlockPos(wrapper.get("Center", ILocationArgument.class), source.getSource());
		int radius = wrapper.get("Radius", Integer.class);
		ResourceLocation dimension = wrapper.getOrDefault("Dimension", ResourceLocation.class, wrapper.getSource().getWorld().getDimensionKey().getLocation());
		return 0;
	}

The Reason for hasValue is used for the GenShape is a example. What if the software that the CommandContext uses only exists on 1 side. It can detect if a single side exists, or if dual sided implementation is present.
Or in modding terms: ServerOnly Mods.

This is how I would implement it.
If you want I can make it a offical PR, or you can just grab the implementation.
Reference Code: https://github.com/Mojang/brigadier/blob/master/src/main/java/com/mojang/brigadier/context/CommandContext.java#L81

    public <V> boolean hasArgument(final String name, final Class<V> clazz) {
        final ParsedArgument<S, ?> argument = arguments.get(name);

        if (argument == null) {
            return false;
        }
        final Object result = argument.getResult();
        if (PRIMITIVE_TO_WRAPPER.getOrDefault(clazz, clazz).isAssignableFrom(result.getClass())) {
            return true;
        } else {
            return false;
        }
    }
	
    public <V> V getArgument(final String name, final Class<V> clazz, V defaultValue) {
        final ParsedArgument<S, ?> argument = arguments.get(name);

        if (argument == null) {
            return defaultValue;
        }

        final Object result = argument.getResult();
        if (PRIMITIVE_TO_WRAPPER.getOrDefault(clazz, clazz).isAssignableFrom(result.getClass())) {
            return (V) result;
        } else {
            return defaultValue;
        }
    }

Maven Central deployment

As now only Travis is used for CI

we can turn off our private CI for auto building and now everyone can look at the actual build results
@Dinnerbone

It may be the time to move to Maven Central as a binary repository (as discussed in Twitter) which will be comparatively easy and will let more libraries depend on Brigadier without any problems.

The downside of using libraries.minecraft.net is that it limits those using binaries from there in deploying their own projects to Central as (according to rules) no custom repositories can occur in project build configuration if publishing there.

As for now it seems the right moment when migration won't be hard.
The recommended sollution is making Travis perform deployment when the branch is master and (recommended) when the commit is tagged.
I may take the control over this if the PR gets positive feedback. As of requirements someone will have to generate some RSA keys to sign binaries.

StringArgumentType with regex

Adding in StringArgumentType a regex method for regex input verification.

Example:

LiteralArgumentBuilder.literal("example")
	.then(RequierdArgumentBuilder.argument("date", StringArgumentType.regex("REGEX")))

Example:

  • /example 04-27-2000
  • /example 04.27.2000

[Bug] argument parser cannot parse string with ":"

So while I was playing with this lib, I got it slowly working with a few commands except one.

I have tried every StringArgumentType (word and string) but it cannot read arguments with :

errors trown with toast123:toast456
word and string: throws: com.mojang.brigadier.exceptions.CommandSyntaxException: Expected whitespace to end one argument, but found trailing data at position 16: ...t toast123<--[HERE]

Note: I can't use greedyString since I use more arguments after this argument.
It's late in the evening, may have overlooked something or I missed a class that I could use.

require in suggestions LOL

i got bored and wanted to change the bukkit command system to brigadier's in 1.8 xD. Now i am facing the following problem:
if a literalargumenttype was registered with require in the dispatcher, it will still be displayed in suggestions if require is returning false 😒
if I have done something wrong, please tell me 😂.

image
image
image

How to run benchmarks?

I'm planning on doing some experiments with improving the performance (and maybe the functionality) of brigadier.
I have forked the repository, cloned it to my local machine, and imported the gradle project into IntelliJ IDEA.
All the tests are running successfully, but I can't seem to figure out how to get the benchmarks to run. There does not seem to be any gradle tasks for running the benchmarks, and I have not been able to get the JMH plugin for IntelliJ IDEA to run the benchmarks.
Are there any instructions on how to run the benchmarks? And if not, could someone please explain and/or link me to some resources on getting the benchmarks to work?
Everything I have been able to find either requires a maven project (which this repo does not seem to contain), or would require changes to the gradle project. I would prefer not to have to change the build process if I can avoid it

Repository not updated to 1.0.500

Recently brigadier 1.0.500 was pushed to Mojang’s maven repository. However, this repository was last updated almost a year ago and is still on 1.0.17. Are there any plans to update this repository?

[Suggestion] Argument class combining name and type

Currently when creating an argument, the name and argument type are not joined in any way. This can lead to errors where the command uses the wrong type when accessing the value.

Therefore it might be good to have a class which combines:

  • The argument name
  • The argument type

Then the same instance of this class can be used when creating the tree and inside the commands. Additionally the methods getting the value from the context would not have to be static anymore, but could be generic, like T getValue(CommandContext<?>). This would make #18 easier as well since the getTypeNameOrDefault method could be non-static and implemented by the base class only.

Example

CommandDispatcher<CommandSourceStack> dispatcher = new CommandDispatcher<>();

Argument<Integer> barArgument = new Argument<>(barArgument, integer());

dispatcher.register(
    literal("foo")
        .then(
            argument(barArgument)
                .executes(c -> {
                    System.out.println("Bar is " + barArgument.getValue(c));
                    return 1;
                })
        )
        .executes(c -> {
            System.out.println("Called foo with no arguments");
            return 1;
        })
);

[Bug] Smart usage does not include all optional command nodes

Because of the lack of documentation, I have no idea if this is by design or not.
Consider following example:

dispatcher.register(literal("foo")
    .executes(ctx -> 0)
    .then(literal("bar")
        .executes(ctx -> 1)
        .then(literal("baz")
            .executes( ctx -> 2)
        )
    )
);

for (String usage : dispatcher.getAllUsage(dispatcher.getRoot(), null, false)) {
    System.out.println(usage);
}

dispatcher.getSmartUsage(dispatcher.getRoot(), null).forEach((k, v) -> System.out.println(k + ": " + v));

The expected output is:

foo
foo bar
foo bar baz
<literal foo>: foo [bar] [baz]

The actual output is:

foo
foo bar
foo bar baz
<literal foo>: foo [bar]

Note, that the smart usage is missing the optional baz argument at the end.

The command dispatcher actually does have a javadoc, however for smart usage it only says "These forms may be mixed and matched to provide as much information about the child nodes as it can, without being too verbose". (These forms is referring to literal, <arg>, [opt] and (either|or)).
It's unclear how "providing as much information as it can without being too verbose" is actually meant.

Imo there is no reason not to provide this information. It's also confusing since the user probably does not expect any further possible arguments after bar.

Feature the minimal required Java version (more prominently)

While this is an important piece of information that every developer looking for a library needs it is currently neither available in the README, nor the build.gradle. To make it easier to decide whether or not one can use this library I suggest featuring this somewhere near the top of the README.

No response on commands without arguments

commandDispatcher.register(Commands.literal(".ping") .executes(c -> { c.getSource() .getChannel().sendMessage("Pong!"); return 1; }) );

And, using this code:
CommandNode<DiscordCommandSource> commandNode = Iterables.getLast(results.getContext().getNodes().keySet()); Map<CommandNode<DiscordCommandSource>, String> map = commandDispatcher.getSmartUsage(commandNode, s.getSource());
It does not return an information about commands without arguments. Am I doing something wrong?

[Suggestion] Add a method to add a collection of literals/arguments

Hello, i use Brigadier API and i would make a thing like that :

List<RequiredArgumentBuilder> list;
for (var argument : list) {
    builder.then(argument);
}
builder.execute(myCommand);

So, i have a list of arguments, and I would like my command to be executed only after all the arguments are saved. But I did not find how.
Even if there is an existing solution in the API, it would still be handy to create two new methods that would support collections. Something like this :

    public T then(final ArgumentBuilder<S, ?>... arguments) {
        if (target != null) {
            throw new IllegalStateException("Cannot add children to a redirected node");
        }
        for (var argument : arguments) {
            this.arguments.addChild(argument.build());
        }
        return getThis();
    }

I have made a pull request here : #70

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.