Compare commits

...

37 commits

Author SHA1 Message Date
6c71b666af add descriptions.,,. 2023-06-11 20:33:13 +07:00
Liyan Zhao
f20bede62a
Fix wrong redirect behavior (MC-256419) (#124)
* Fix wrong redirect behavior

* Set foundCommand to `true` if redirect modifier returns no result.
2023-03-30 14:29:57 +02:00
parberge
a3f3eb2929 Remove unused aws secrets 2023-03-28 11:33:33 +02:00
parberge
514387ba11 Remove the task to upload to AWS
We no longer use AWS so we shouldn't upload more stuff to it.
2023-03-27 08:48:41 +02:00
Petr Mrázek
a6c4baa1b6 Correct the pr check workflow 2022-10-26 17:12:10 +02:00
Benny
b0f5818a59
Change azure blob storage (#117) 2022-10-26 16:59:13 +02:00
Petr Mrázek
b411777f47 Remove travis-ci file 2022-10-26 16:55:22 +02:00
Petr Mrázek
0c9c87e4e6 Add PR check using GitHub Actions 2022-10-26 16:55:22 +02:00
Petr Mrázek
3c773a7188 Only publish on master 2022-10-19 10:55:57 +02:00
Petr Mrázek
06df81922f Implement ADO pipeline builds 2022-10-19 10:55:57 +02:00
slicedlime (Mikael Hedberg)
cf754c4ef6 Use rootian locale for case conversion. 2021-03-23 12:52:01 +01:00
slicedlime (Mikael Hedberg)
60a94e529d Cache lowercase versions of inputs and literals to avoid repeated calls to toLowerCase().
Thank you to Spottedleaf.
2021-03-23 12:52:01 +01:00
Daniel Ennis
242de3fe73
Don't sort children on node addition to save on performance (#68)
* Use a Tree Map for children to save on performance

Everytime a child is added to the CommandNode, the children was sorted.
This action is extremely heavy with large node trees such as Minecraft.

From what I can see, sort order was not even needed by type since
the dispatcher parse checks argument and literal nodes instead.

Testing on /bossbar command seemed to have no impact to behavior.

Credit to dda9680be5

Co-authored-by: virustotalop <virustotalop@gmail.com>

* Restore back to a LinkedHashMap

since iteration order isn't important anyways.

Co-authored-by: virustotalop <virustotalop@gmail.com>
2021-03-18 15:36:43 +01:00
BeetMacol
55f6e25c03 Correct README punctuation
Add two missing dots and remove space before exclamation mark
2021-03-18 15:08:34 +01:00
boq
559d8f3972 Allow single quote in strings 2019-02-19 13:34:51 +01:00
Sleepy Flower Girl
447845ba89 Fix very minor grammer mistakes
with changes
2018-10-14 14:51:58 -04:00
PROgrm_JARvis
6eec4e50ac Update LICENSE formatting (#24) 2018-10-14 14:44:36 -04:00
DoNotSpamPls
8986ae2428 Upgrade Gradle to the latest version, small preparations for Gradle 5
Also upgraded dependencies in a non-breaking manner
2018-10-14 14:35:42 -04:00
PROgrm_JARvis
e527fec986 Make <Stars> clickable 2018-10-14 14:32:32 -04:00
Elias Ojala
5a7ea58024 Make fork button clickable 2018-10-14 14:32:32 -04:00
MrMicky
7ee589b29b Remove unnecessary Guava usages (#13) 2018-10-04 13:29:08 +02:00
Adam Poloczek
bcbd596c24 link shields respectively to the latest release and license file 2018-10-02 17:54:48 +02:00
Leon Linhart
d156febab6 Make gradlew executable 2018-10-02 17:54:19 +02:00
Leon Linhart
9d8019b407 Add travis CI configuration 2018-10-02 17:54:19 +02:00
PROgrm_JARvis
d3cb749c4b Add shields (from shields.io) to README.md (#12) 2018-10-02 11:54:25 +02:00
Earthcomputer
e60b24f36b Fix long argument examples 2018-09-28 14:04:53 +02:00
boq
bbfb8a7da1 Remove unused generic 2018-09-26 20:43:26 +02:00
boq
107b852c74 Implement calculation of suggestions for any text position 2018-09-26 20:43:26 +02:00
Earthcomputer
b10b4479e2 Add string reader long tests 2018-09-26 20:43:13 +02:00
Earthcomputer
88714288a7 Add long argument 2018-09-26 20:43:13 +02:00
TheIntelloBoX
6a15305c3d Update README.md (#9) 2018-09-26 20:42:33 +02:00
Nathan Adams
8e9859e471 Added javadoc to CommandDispatcher 2018-09-26 10:42:35 +02:00
Nathan Adams
1fe283c136 Added readme 2018-09-26 10:42:35 +02:00
Nathan Adams
189c7d0433 Add MIT license 2018-09-26 10:42:35 +02:00
Nathan Adams
872658c737 Add copyright to all code 2018-09-26 10:42:35 +02:00
boq
019c0e3727 Pass StringReader instead of strings to CommandDispatcher 2018-07-24 14:52:17 +02:00
Nathan Adams
4dca222938 Don't publish .md5 files 2018-07-24 14:49:11 +02:00
83 changed files with 1703 additions and 187 deletions

51
.ado/build.yml Normal file
View file

@ -0,0 +1,51 @@
name: $(Rev:r)
trigger:
branches:
include:
- '*'
exclude:
- master
pr:
branches:
include:
- '*'
jobs:
- job: 'Build'
displayName: 'Build for testing'
pool: 'MC-Build-1ES-Azure-Pipeline-Linux'
container: adoptopenjdk/openjdk8:latest
workspace:
clean: all
steps:
- task: Gradle@2
displayName: Build and Test
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
options: '-PbuildNumber=0'
javaHomeOption: 'JDKVersion'
jdkUserInputPath: '/usr/java/openjdk-8'
testResultsFiles: '**/TEST-*.xml'
tasks: 'build test publish'
# This is a workaround for ComponentGovernanceComponentDetection@0 not recognizing the generated `.pom` file(s)
- task: Bash@3
displayName: Copy pom for component governance
inputs:
targetType: 'inline'
script: |
pompath=`find build/repo -name *.pom`
cp "${pompath}" build/pom.xml
- task: ComponentGovernanceComponentDetection@0
inputs:
scanType: 'Register'
verbosity: 'Verbose'
alertWarningLevel: 'High'

87
.ado/release.yml Normal file
View file

@ -0,0 +1,87 @@
name: $(Rev:r)
trigger:
branches:
include:
- master
pr: none
variables:
rConnection: 'mc-java-sc'
storageAccount: 'librariesminecraftnet'
storageAccountContainer: 'librariesminecraftnet'
keyVault: 'mc-java-vault'
jobs:
- job: 'Build'
displayName: 'Build for release'
pool: 'MC-Build-1ES-Azure-Pipeline-Linux'
container: adoptopenjdk/openjdk8:latest
workspace:
clean: all
steps:
- task: Gradle@2
displayName: Build and Test
inputs:
workingDirectory: ''
gradleWrapperFile: 'gradlew'
gradleOptions: '-Xmx3072m'
options: '-PbuildNumber=$(Build.BuildNumber)'
javaHomeOption: 'JDKVersion'
jdkUserInputPath: '/usr/java/openjdk-8'
testResultsFiles: '**/TEST-*.xml'
tasks: 'build test publish report'
# This is a workaround for ComponentGovernanceComponentDetection@0 not recognizing the generated `.pom` file(s)
- task: Bash@3
displayName: Copy pom for component governance
inputs:
targetType: 'inline'
script: |
pompath=`find build/repo -name *.pom`
cp "${pompath}" build/pom.xml
- task: ComponentGovernanceComponentDetection@0
inputs:
scanType: 'Register'
verbosity: 'Verbose'
alertWarningLevel: 'High'
- publish: 'build/repo/'
artifact: repo
- job: 'Publish'
displayName: 'Publish release'
dependsOn: Build
condition: eq(variables['Build.SourceBranch'], 'refs/heads/master')
pool: 'MC-Build-1ES-Azure-Pipeline-Linux'
workspace:
clean: all
steps:
- download: current
artifact: repo
- task: AzureKeyVault@1
displayName: 'Fetching secrets'
name: secrets
inputs:
azureSubscription: '$(rConnection)'
KeyVaultName: '$(keyVault)'
SecretsFilter: 'access-key-prod-librariesminecraftnet'
RunAsPreJob: false
- task: AzureCLI@2
displayName: Azure CLI
inputs:
azureSubscription: '$(rConnection)'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az storage blob upload-batch -s '$(Pipeline.Workspace)/repo' -d $(storageAccountContainer) --account-name $(storageAccount) --account-key $(access-key-prod-librariesminecraftnet)

22
.github/workflows/pr-check.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: pr-check
on: [ pull_request ]
jobs:
build:
name: Build and test
runs-on: ubuntu-latest
container:
image: adoptopenjdk/openjdk8:latest
steps:
- uses: actions/checkout@v3
- name: Build with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: build test
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
if: always()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

135
README.md Normal file
View file

@ -0,0 +1,135 @@
# Brigadier [![Latest release](https://img.shields.io/github/release/Mojang/brigadier.svg)](https://github.com/Mojang/brigadier/releases/latest) [![License](https://img.shields.io/github/license/Mojang/brigadier.svg)](https://github.com/Mojang/brigadier/blob/master/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:
```groovy
maven {
url "https://libraries.minecraft.net"
}
```
And then use this library (change `(the latest version)` to the latest version!):
```groovy
compile 'com.mojang:brigadier:(the latest version)'
```
## Maven
First include our repository:
```xml
<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!):
```xml
<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](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) 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 `CommandNode`s 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:
```java
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 `LiteralCommandNode`s 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:
```java
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](https://img.shields.io/github/forks/Mojang/brigadier.svg?style=social&label=Fork)](https://github.com/Mojang/brigadier/fork) [![GitHub stars](https://img.shields.io/github/stars/Mojang/brigadier.svg?style=social&label=Stars)](https://github.com/Mojang/brigadier/stargazers)

View file

@ -1,6 +1,4 @@
import groovy.io.FileType
import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider
import com.amazonaws.services.s3.AmazonS3Client
apply plugin: 'java-library'
apply plugin: 'maven-publish'
@ -8,10 +6,6 @@ apply plugin: 'maven-publish'
group = 'com.mojang'
version = project.hasProperty('buildNumber') ? "${project.majorMinor}.${project.buildNumber}" : "${project.majorMinor}.0-SNAPSHOT"
task wrapper(type: Wrapper) {
gradleVersion = '4.0'
}
buildscript {
repositories {
mavenCentral()
@ -19,10 +13,6 @@ buildscript {
url "https://libraries.minecraft.net"
}
}
dependencies {
classpath 'com.amazonaws:aws-java-sdk:1.11.33'
}
}
repositories {
@ -33,13 +23,13 @@ repositories {
}
dependencies {
api 'com.google.guava:guava:21.0'
testCompile 'junit:junit-dep:4.10'
testCompile 'org.hamcrest:hamcrest-library:1.2.1'
testCompile 'org.mockito:mockito-core:1.8.5'
testCompile 'com.google.guava:guava-testlib:21.0'
testCompile 'org.openjdk.jmh:jmh-core:1.19'
testCompile 'org.openjdk.jmh:jmh-generator-annprocess:1.19'
testCompile 'com.google.guava:guava:26.0-jre'
testCompile 'junit:junit-dep:4.11'
testCompile 'org.hamcrest:hamcrest-library:1.3'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile 'com.google.guava:guava-testlib:26.0-jre'
testCompile 'org.openjdk.jmh:jmh-core:1.21'
annotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.21'
}
task sourcesJar(type: Jar) {
@ -90,18 +80,12 @@ publishing {
task report {
doLast {
println "##teamcity[buildNumber '${project.version}']"
println "##vso[build.updatebuildnumber]${project.version}"
}
}
def publishDir = file("$buildDir/repo")
def uploadFile(s3, bucket, path, filename) {
println "Uploading $filename to $bucket as $path"
s3.putObject(bucket, path, filename)
}
clean.doLast {
delete publishDir
}
@ -118,14 +102,13 @@ if (version.endsWith("SNAPSHOT")) {
}
publish.doLast {
def AWSRoleARN = (System.getenv("AWS_ROLE_ARN") != null && System.getenv("AWS_ROLE_ARN") != "" ? System.getenv("AWS_ROLE_ARN") : null)
if (AWSRoleARN == null) throw new GradleException("AWS Role has not been configured, use the `AWS_ROLE_ARN` environment variable")
def auth = new STSAssumeRoleSessionCredentialsProvider.Builder(AWSRoleARN, "JavaBrigadierPublish").build()
def s3 = new AmazonS3Client(auth)
publishDir.eachFileRecurse {
if (!it.name.contains(".xml") && it.isFile()) {
def relPath = publishDir.toPath().relativize(it.toPath()).toFile().toString().replaceAll('\\\\', '/')
uploadFile(s3, "minecraft-libraries", relPath, it)
if (!it.isFile()) {
return
}
// Remove junk files
if (it.name.contains(".xml") || it.name.contains(".md5")) {
it.delete()
}
}
}

View file

@ -1 +1 @@
majorMinor: 1.0
majorMinor: 1.1

Binary file not shown.

View file

@ -1,6 +1,6 @@
#Wed Jun 21 14:07:34 CEST 2017
#Sat Oct 06 16:17:40 EEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip

0
gradlew vendored Normal file → Executable file
View file

View file

@ -1 +1,2 @@
rootProject.name = 'brigadier'
enableFeaturePreview('STABLE_PUBLISHING')

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
import com.mojang.brigadier.tree.CommandNode;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
import com.mojang.brigadier.context.CommandContext;

View file

@ -1,13 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.StringRange;
import com.mojang.brigadier.context.SuggestionContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
@ -19,16 +18,36 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* The core command dispatcher, for registering, parsing, and executing commands.
*
* @param <S> a custom "source" type, such as a user or originator of a command
*/
public class CommandDispatcher<S> {
/**
* The string required to separate individual arguments in an input string
*
* @see #ARGUMENT_SEPARATOR_CHAR
*/
public static final String ARGUMENT_SEPARATOR = " ";
/**
* The char required to separate individual arguments in an input string
*
* @see #ARGUMENT_SEPARATOR
*/
public static final char ARGUMENT_SEPARATOR_CHAR = ' ';
private static final String USAGE_OPTIONAL_OPEN = "[";
private static final String USAGE_OPTIONAL_CLOSE = "]";
private static final String USAGE_REQUIRED_OPEN = "(";
@ -36,6 +55,7 @@ public class CommandDispatcher<S> {
private static final String USAGE_OR = "|";
private final RootCommandNode<S> root;
private final Predicate<CommandNode<S>> hasCommand = new Predicate<CommandNode<S>>() {
@Override
public boolean test(final CommandNode<S> input) {
@ -45,29 +65,144 @@ public class CommandDispatcher<S> {
private ResultConsumer<S> consumer = (c, s, r) -> {
};
/**
* Create a new {@link CommandDispatcher} with the specified root node.
*
* <p>This is often useful to copy existing or pre-defined command trees.</p>
*
* @param root the existing {@link RootCommandNode} to use as the basis for this tree
*/
public CommandDispatcher(final RootCommandNode<S> root) {
this.root = root;
}
/**
* Creates a new {@link CommandDispatcher} with an empty command tree.
*/
public CommandDispatcher() {
this(new RootCommandNode<>());
}
/**
* Utility method for registering new commands.
*
* <p>This is a shortcut for calling {@link RootCommandNode#addChild(CommandNode)} after building the provided {@code command}.</p>
*
* <p>As {@link RootCommandNode} can only hold literals, this method will only allow literal arguments.</p>
*
* @param command a literal argument builder to add to this command tree
* @return the node added to this tree
*/
public LiteralCommandNode<S> register(final LiteralArgumentBuilder<S> command) {
final LiteralCommandNode<S> build = command.build();
root.addChild(build);
return build;
}
/**
* Sets a callback to be informed of the result of every command.
*
* @param consumer the new result consumer to be called
*/
public void setConsumer(final ResultConsumer<S> consumer) {
this.consumer = consumer;
}
/**
* Parses and executes a given command.
*
* <p>This is a shortcut to first {@link #parse(StringReader, Object)} and then {@link #execute(ParseResults)}.</p>
*
* <p>It is recommended to parse and execute as separate steps, as parsing is often the most expensive step, and easiest to cache.</p>
*
* <p>If this command returns a value, then it successfully executed something. If it could not parse the command, or the execution was a failure,
* then an exception will be thrown. Most exceptions will be of type {@link CommandSyntaxException}, but it is possible that a {@link RuntimeException}
* may bubble up from the result of a command. The meaning behind the returned result is arbitrary, and will depend
* entirely on what command was performed.</p>
*
* <p>If the command passes through a node that is {@link CommandNode#isFork()} then it will be 'forked'.
* A forked command will not bubble up any {@link CommandSyntaxException}s, and the 'result' returned will turn into
* 'amount of successful commands executes'.</p>
*
* <p>After each and any command is ran, a registered callback given to {@link #setConsumer(ResultConsumer)}
* will be notified of the result and success of the command. You can use that method to gather more meaningful
* results than this method will return, especially when a command forks.</p>
*
* @param input a command string to parse &amp; execute
* @param source a custom "source" object, usually representing the originator of this command
* @return a numeric result from a "command" that was performed
* @throws CommandSyntaxException if the command failed to parse or execute
* @throws RuntimeException if the command failed to execute and was not handled gracefully
* @see #parse(String, Object)
* @see #parse(StringReader, Object)
* @see #execute(ParseResults)
* @see #execute(StringReader, Object)
*/
public int execute(final String input, final S source) throws CommandSyntaxException {
return execute(new StringReader(input), source);
}
/**
* Parses and executes a given command.
*
* <p>This is a shortcut to first {@link #parse(StringReader, Object)} and then {@link #execute(ParseResults)}.</p>
*
* <p>It is recommended to parse and execute as separate steps, as parsing is often the most expensive step, and easiest to cache.</p>
*
* <p>If this command returns a value, then it successfully executed something. If it could not parse the command, or the execution was a failure,
* then an exception will be thrown. Most exceptions will be of type {@link CommandSyntaxException}, but it is possible that a {@link RuntimeException}
* may bubble up from the result of a command. The meaning behind the returned result is arbitrary, and will depend
* entirely on what command was performed.</p>
*
* <p>If the command passes through a node that is {@link CommandNode#isFork()} then it will be 'forked'.
* A forked command will not bubble up any {@link CommandSyntaxException}s, and the 'result' returned will turn into
* 'amount of successful commands executes'.</p>
*
* <p>After each and any command is ran, a registered callback given to {@link #setConsumer(ResultConsumer)}
* will be notified of the result and success of the command. You can use that method to gather more meaningful
* results than this method will return, especially when a command forks.</p>
*
* @param input a command string to parse &amp; execute
* @param source a custom "source" object, usually representing the originator of this command
* @return a numeric result from a "command" that was performed
* @throws CommandSyntaxException if the command failed to parse or execute
* @throws RuntimeException if the command failed to execute and was not handled gracefully
* @see #parse(String, Object)
* @see #parse(StringReader, Object)
* @see #execute(ParseResults)
* @see #execute(String, Object)
*/
public int execute(final StringReader input, final S source) throws CommandSyntaxException {
final ParseResults<S> parse = parse(input, source);
return execute(parse);
}
/**
* Executes a given pre-parsed command.
*
* <p>If this command returns a value, then it successfully executed something. If the execution was a failure,
* then an exception will be thrown.
* Most exceptions will be of type {@link CommandSyntaxException}, but it is possible that a {@link RuntimeException}
* may bubble up from the result of a command. The meaning behind the returned result is arbitrary, and will depend
* entirely on what command was performed.</p>
*
* <p>If the command passes through a node that is {@link CommandNode#isFork()} then it will be 'forked'.
* A forked command will not bubble up any {@link CommandSyntaxException}s, and the 'result' returned will turn into
* 'amount of successful commands executes'.</p>
*
* <p>After each and any command is ran, a registered callback given to {@link #setConsumer(ResultConsumer)}
* will be notified of the result and success of the command. You can use that method to gather more meaningful
* results than this method will return, especially when a command forks.</p>
*
* @param parse the result of a successful {@link #parse(StringReader, Object)}
* @return a numeric result from a "command" that was performed.
* @throws CommandSyntaxException if the command failed to parse or execute
* @throws RuntimeException if the command failed to execute and was not handled gracefully
* @see #parse(String, Object)
* @see #parse(StringReader, Object)
* @see #execute(String, Object)
* @see #execute(StringReader, Object)
*/
public int execute(final ParseResults<S> parse) throws CommandSyntaxException {
if (parse.getReader().canRead()) {
if (parse.getExceptions().size() == 1) {
@ -95,8 +230,7 @@ public class CommandDispatcher<S> {
final CommandContext<S> child = context.getChild();
if (child != null) {
forked |= context.isForked();
if (!child.getNodes().isEmpty()) {
foundCommand = true;
if (child.hasNodes()) {
final RedirectModifier<S> modifier = context.getRedirectModifier();
if (modifier == null) {
if (next == null) {
@ -113,6 +247,8 @@ public class CommandDispatcher<S> {
for (final S source : results) {
next.add(child.copyFor(source));
}
} else {
foundCommand = true;
}
} catch (final CommandSyntaxException ex) {
consumer.onCommandComplete(context, false, 0);
@ -150,26 +286,73 @@ public class CommandDispatcher<S> {
return forked ? successfulForks : result;
}
/**
* Parses a given command.
*
* <p>The result of this method can be cached, and it is advised to do so where appropriate. Parsing is often the
* most expensive step, and this allows you to essentially "precompile" a command if it will be ran often.</p>
*
* <p>If the command passes through a node that is {@link CommandNode#isFork()} then the resulting context will be marked as 'forked'.
* Forked contexts may contain child contexts, which may be modified by the {@link RedirectModifier} attached to the fork.</p>
*
* <p>Parsing a command can never fail, you will always be provided with a new {@link ParseResults}.
* However, that does not mean that it will always parse into a valid command. You should inspect the returned results
* to check for validity. If its {@link ParseResults#getReader()} {@link StringReader#canRead()} then it did not finish
* parsing successfully. You can use that position as an indicator to the user where the command stopped being valid.
* You may inspect {@link ParseResults#getExceptions()} if you know the parse failed, as it will explain why it could
* not find any valid commands. It may contain multiple exceptions, one for each "potential node" that it could have visited,
* explaining why it did not go down that node.</p>
*
* <p>When you eventually call {@link #execute(ParseResults)} with the result of this method, the above error checking
* will occur. You only need to inspect it yourself if you wish to handle that yourself.</p>
*
* @param command a command string to parse
* @param source a custom "source" object, usually representing the originator of this command
* @return the result of parsing this command
* @see #parse(StringReader, Object)
* @see #execute(ParseResults)
* @see #execute(String, Object)
*/
public ParseResults<S> parse(final String command, final S source) {
final StringReader reader = new StringReader(command);
final CommandContextBuilder<S> context = new CommandContextBuilder<>(this, source, 0);
return parseNodes(root, reader, context);
return parse(new StringReader(command), source);
}
private static class PartialParse<S> {
public final CommandContextBuilder<S> context;
public final ParseResults<S> parse;
private PartialParse(final CommandContextBuilder<S> context, final ParseResults<S> parse) {
this.context = context;
this.parse = parse;
}
/**
* Parses a given command.
*
* <p>The result of this method can be cached, and it is advised to do so where appropriate. Parsing is often the
* most expensive step, and this allows you to essentially "precompile" a command if it will be ran often.</p>
*
* <p>If the command passes through a node that is {@link CommandNode#isFork()} then the resulting context will be marked as 'forked'.
* Forked contexts may contain child contexts, which may be modified by the {@link RedirectModifier} attached to the fork.</p>
*
* <p>Parsing a command can never fail, you will always be provided with a new {@link ParseResults}.
* However, that does not mean that it will always parse into a valid command. You should inspect the returned results
* to check for validity. If its {@link ParseResults#getReader()} {@link StringReader#canRead()} then it did not finish
* parsing successfully. You can use that position as an indicator to the user where the command stopped being valid.
* You may inspect {@link ParseResults#getExceptions()} if you know the parse failed, as it will explain why it could
* not find any valid commands. It may contain multiple exceptions, one for each "potential node" that it could have visited,
* explaining why it did not go down that node.</p>
*
* <p>When you eventually call {@link #execute(ParseResults)} with the result of this method, the above error checking
* will occur. You only need to inspect it yourself if you wish to handle that yourself.</p>
*
* @param command a command string to parse
* @param source a custom "source" object, usually representing the originator of this command
* @return the result of parsing this command
* @see #parse(String, Object)
* @see #execute(ParseResults)
* @see #execute(String, Object)
*/
public ParseResults<S> parse(final StringReader command, final S source) {
final CommandContextBuilder<S> context = new CommandContextBuilder<>(this, source, root, command.getCursor());
return parseNodes(root, command, context);
}
private ParseResults<S> parseNodes(final CommandNode<S> node, final StringReader originalReader, final CommandContextBuilder<S> contextSoFar) {
final S source = contextSoFar.getSource();
Map<CommandNode<S>, CommandSyntaxException> errors = null;
List<PartialParse<S>> potentials = null;
List<ParseResults<S>> potentials = null;
final int cursor = originalReader.getCursor();
for (final CommandNode<S> child : node.getRelevantNodes(originalReader)) {
@ -202,8 +385,7 @@ public class CommandDispatcher<S> {
if (reader.canRead(child.getRedirect() == null ? 2 : 1)) {
reader.skip();
if (child.getRedirect() != null) {
final CommandContextBuilder<S> childContext = new CommandContextBuilder<>(this, source, reader.getCursor());
childContext.withNode(child.getRedirect(), StringRange.between(cursor, reader.getCursor() - 1));
final CommandContextBuilder<S> childContext = new CommandContextBuilder<>(this, source, child.getRedirect(), reader.getCursor());
final ParseResults<S> parse = parseNodes(child.getRedirect(), reader, childContext);
context.withChild(parse.getContext());
return new ParseResults<>(context, parse.getReader(), parse.getExceptions());
@ -212,42 +394,63 @@ public class CommandDispatcher<S> {
if (potentials == null) {
potentials = new ArrayList<>(1);
}
potentials.add(new PartialParse<>(context, parse));
potentials.add(parse);
}
} else {
if (potentials == null) {
potentials = new ArrayList<>(1);
}
potentials.add(new PartialParse<>(context, new ParseResults<>(context, reader, Collections.emptyMap())));
potentials.add(new ParseResults<>(context, reader, Collections.emptyMap()));
}
}
if (potentials != null) {
if (potentials.size() > 1) {
potentials.sort((a, b) -> {
if (!a.parse.getReader().canRead() && b.parse.getReader().canRead()) {
if (!a.getReader().canRead() && b.getReader().canRead()) {
return -1;
}
if (a.parse.getReader().canRead() && !b.parse.getReader().canRead()) {
if (a.getReader().canRead() && !b.getReader().canRead()) {
return 1;
}
if (a.parse.getExceptions().isEmpty() && !b.parse.getExceptions().isEmpty()) {
if (a.getExceptions().isEmpty() && !b.getExceptions().isEmpty()) {
return -1;
}
if (!a.parse.getExceptions().isEmpty() && b.parse.getExceptions().isEmpty()) {
if (!a.getExceptions().isEmpty() && b.getExceptions().isEmpty()) {
return 1;
}
return 0;
});
}
return potentials.get(0).parse;
return potentials.get(0);
}
return new ParseResults<>(contextSoFar, originalReader, errors == null ? Collections.emptyMap() : errors);
}
/**
* Gets all possible executable commands following the given node.
*
* <p>You may use {@link #getRoot()} as a target to get all usage data for the entire command tree.</p>
*
* <p>The returned syntax will be in "simple" form: {@code <param>} and {@code literal}. "Optional" nodes will be
* listed as multiple entries: the parent node, and the child nodes.
* For example, a required literal "foo" followed by an optional param "int" will be two nodes:</p>
* <ul>
* <li>{@code foo}</li>
* <li>{@code foo <int>}</li>
* </ul>
*
* <p>The path to the specified node will <b>not</b> be prepended to the output, as there can theoretically be many
* ways to reach a given node. It will only give you paths relative to the specified node, not absolute from root.</p>
*
* @param node target node to get child usage strings for
* @param source a custom "source" object, usually representing the originator of this command
* @param restricted if true, commands that the {@code source} cannot access will not be mentioned
* @return array of full usage strings under the target node
*/
public String[] getAllUsage(final CommandNode<S> node, final S source, final boolean restricted) {
final ArrayList<String> result = Lists.newArrayList();
final ArrayList<String> result = new ArrayList<>();
getAllUsage(node, source, result, "", restricted);
return result.toArray(new String[result.size()]);
}
@ -271,8 +474,29 @@ public class CommandDispatcher<S> {
}
}
/**
* Gets the possible executable commands from a specified node.
*
* <p>You may use {@link #getRoot()} as a target to get usage data for the entire command tree.</p>
*
* <p>The returned syntax will be in "smart" form: {@code <param>}, {@code literal}, {@code [optional]} and {@code (either|or)}.
* These forms may be mixed and matched to provide as much information about the child nodes as it can, without being too verbose.
* For example, a required literal "foo" followed by an optional param "int" can be compressed into one string:</p>
* <ul>
* <li>{@code foo [<int>]}</li>
* </ul>
*
* <p>The path to the specified node will <b>not</b> be prepended to the output, as there can theoretically be many
* ways to reach a given node. It will only give you paths relative to the specified node, not absolute from root.</p>
*
* <p>The returned usage will be restricted to only commands that the provided {@code source} can use.</p>
*
* @param node target node to get child usage strings for
* @param source a custom "source" object, usually representing the originator of this command
* @return array of full usage strings under the target node
*/
public Map<CommandNode<S>, String> getSmartUsage(final CommandNode<S> node, final S source) {
final Map<CommandNode<S>, String> result = Maps.newLinkedHashMap();
final Map<CommandNode<S>, String> result = new LinkedHashMap<>();
final boolean optional = node.getCommand() != null;
for (final CommandNode<S> child : node.getChildren()) {
@ -306,7 +530,7 @@ public class CommandDispatcher<S> {
return self + ARGUMENT_SEPARATOR + usage;
}
} else if (children.size() > 1) {
final Set<String> childUsage = Sets.newLinkedHashSet();
final Set<String> childUsage = new LinkedHashSet<>();
for (final CommandNode<S> child : children) {
final String usage = getSmartUsage(child, source, childOptional, true);
if (usage != null) {
@ -338,38 +562,41 @@ public class CommandDispatcher<S> {
return self;
}
/**
* Gets suggestions for a parsed input string on what comes next.
*
* <p>As it is ultimately up to custom argument types to provide suggestions, it may be an asynchronous operation,
* for example getting in-game data or player names etc. As such, this method returns a future and no guarantees
* are made to when or how the future completes.</p>
*
* <p>The suggestions provided will be in the context of the end of the parsed input string, but may suggest
* new or replacement strings for earlier in the input string. For example, if the end of the string was
* {@code foobar} but an argument preferred it to be {@code minecraft:foobar}, it will suggest a replacement for that
* whole segment of the input.</p>
*
* @param parse the result of a {@link #parse(StringReader, Object)}
* @return a future that will eventually resolve into a {@link Suggestions} object
*/
public CompletableFuture<Suggestions> getCompletionSuggestions(final ParseResults<S> parse) {
final CommandContextBuilder<S> rootContext = parse.getContext();
final CommandContextBuilder<S> context = rootContext.getLastChild();
final CommandNode<S> parent;
final int start;
if (context.getNodes().isEmpty()) {
parent = root;
start = 0;
} else if (parse.getReader().canRead()) {
final Map.Entry<CommandNode<S>, StringRange> entry = Iterables.getLast(context.getNodes().entrySet());
parent = entry.getKey();
start = entry.getValue().getEnd() + 1;
} else if (context.getNodes().size() > 1) {
final Map.Entry<CommandNode<S>, StringRange> entry = Iterables.get(context.getNodes().entrySet(), context.getNodes().size() - 2);
parent = entry.getKey();
start = entry.getValue().getEnd() + 1;
} else if (rootContext != context && context.getNodes().size() > 0) {
final Map.Entry<CommandNode<S>, StringRange> entry = Iterables.getLast(context.getNodes().entrySet());
parent = entry.getKey();
start = entry.getValue().getEnd() + 1;
} else {
parent = root;
start = 0;
return getCompletionSuggestions(parse, parse.getReader().getTotalLength());
}
public CompletableFuture<Suggestions> getCompletionSuggestions(final ParseResults<S> parse, int cursor) {
final CommandContextBuilder<S> context = parse.getContext();
final SuggestionContext<S> nodeBeforeCursor = context.findSuggestionContext(cursor);
final CommandNode<S> parent = nodeBeforeCursor.parent;
final int start = Math.min(nodeBeforeCursor.startPos, cursor);
final String fullInput = parse.getReader().getString();
final String truncatedInput = fullInput.substring(0, cursor);
final String truncatedInputLowerCase = truncatedInput.toLowerCase(Locale.ROOT);
@SuppressWarnings("unchecked") final CompletableFuture<Suggestions>[] futures = new CompletableFuture[parent.getChildren().size()];
int i = 0;
for (final CommandNode<S> node : parent.getChildren()) {
CompletableFuture<Suggestions> future = Suggestions.empty();
try {
future = node.listSuggestions(context.build(parse.getReader().getString()), new SuggestionsBuilder(parse.getReader().getString(), start));
future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, truncatedInputLowerCase, start));
} catch (final CommandSyntaxException ignored) {
}
futures[i++] = future;
@ -377,20 +604,43 @@ public class CommandDispatcher<S> {
final CompletableFuture<Suggestions> result = new CompletableFuture<>();
CompletableFuture.allOf(futures).thenRun(() -> {
final List<Suggestions> suggestions = Lists.newArrayList();
final List<Suggestions> suggestions = new ArrayList<>();
for (final CompletableFuture<Suggestions> future : futures) {
suggestions.add(future.join());
}
result.complete(Suggestions.merge(parse.getReader().getString(), suggestions));
result.complete(Suggestions.merge(fullInput, suggestions));
});
return result;
}
/**
* Gets the root of this command tree.
*
* <p>This is often useful as a target of a {@link com.mojang.brigadier.builder.ArgumentBuilder#redirect(CommandNode)},
* {@link #getAllUsage(CommandNode, Object, boolean)} or {@link #getSmartUsage(CommandNode, Object)}.
* You may also use it to clone the command tree via {@link #CommandDispatcher(RootCommandNode)}.</p>
*
* @return root of the command tree
*/
public RootCommandNode<S> getRoot() {
return root;
}
/**
* Finds a valid path to a given node on the command tree.
*
* <p>There may theoretically be multiple paths to a node on the tree, especially with the use of forking or redirecting.
* As such, this method makes no guarantees about which path it finds. It will not look at forks or redirects,
* and find the first instance of the target node on the tree.</p>
*
* <p>The only guarantee made is that for the same command tree and the same version of this library, the result of
* this method will <b>always</b> be a valid input for {@link #findNode(Collection)}, which should return the same node
* as provided to this method.</p>
*
* @param target the target node you are finding a path for
* @return a path to the resulting node, or an empty list if it was not found
*/
public Collection<String> getPath(final CommandNode<S> target) {
final List<List<CommandNode<S>>> nodes = new ArrayList<>();
addPaths(root, nodes, new ArrayList<>());
@ -410,6 +660,17 @@ public class CommandDispatcher<S> {
return Collections.emptyList();
}
/**
* Finds a node by its path
*
* <p>Paths may be generated with {@link #getPath(CommandNode)}, and are guaranteed (for the same tree, and the
* same version of this library) to always produce the same valid node by this method.</p>
*
* <p>If a node could not be found at the specified path, then {@code null} will be returned.</p>
*
* @param path a generated path to a node
* @return the node at the given path, or null if not found
*/
public CommandNode<S> findNode(final Collection<String> path) {
CommandNode<S> node = root;
for (final String name : path) {
@ -421,6 +682,16 @@ public class CommandDispatcher<S> {
return node;
}
/**
* Scans the command tree for potential ambiguous commands.
*
* <p>This is a shortcut for {@link CommandNode#findAmbiguities(AmbiguityConsumer)} on {@link #getRoot()}.</p>
*
* <p>Ambiguities are detected by testing every {@link CommandNode#getExamples()} on one node verses every sibling
* node. This is not fool proof, and relies a lot on the providers of the used argument types to give good examples.</p>
*
* @param consumer a callback to be notified of potential ambiguities
*/
public void findAmbiguities(final AmbiguityConsumer<S> consumer) {
root.findAmbiguities(consumer);
}

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
public interface ImmutableStringReader {

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
public class LiteralMessage implements Message {

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
public interface Message {

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
import com.mojang.brigadier.context.CommandContextBuilder;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
import com.mojang.brigadier.context.CommandContext;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
import com.mojang.brigadier.context.CommandContext;

View file

@ -1,10 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.util.Collection;
@FunctionalInterface
public interface SingleRedirectModifier<S> {
S apply(CommandContext<S> context) throws CommandSyntaxException;

View file

@ -1,10 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
public class StringReader implements ImmutableStringReader {
private static final char SYNTAX_ESCAPE = '\\';
private static final char SYNTAX_QUOTE = '"';
private static final char SYNTAX_DOUBLE_QUOTE = '"';
private static final char SYNTAX_SINGLE_QUOTE = '\'';
private final String string;
private int cursor;
@ -84,6 +88,10 @@ public class StringReader implements ImmutableStringReader {
return c >= '0' && c <= '9' || c == '.' || c == '-';
}
public static boolean isQuotedStringStart(char c) {
return c == SYNTAX_DOUBLE_QUOTE || c == SYNTAX_SINGLE_QUOTE;
}
public void skipWhitespace() {
while (canRead() && Character.isWhitespace(peek())) {
skip();
@ -107,6 +115,23 @@ public class StringReader implements ImmutableStringReader {
}
}
public long readLong() throws CommandSyntaxException {
final int start = cursor;
while (canRead() && isAllowedNumber(peek())) {
skip();
}
final String number = string.substring(start, cursor);
if (number.isEmpty()) {
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerExpectedLong().createWithContext(this);
}
try {
return Long.parseLong(number);
} catch (final NumberFormatException ex) {
cursor = start;
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerInvalidLong().createWithContext(this, number);
}
}
public double readDouble() throws CommandSyntaxException {
final int start = cursor;
while (canRead() && isAllowedNumber(peek())) {
@ -160,16 +185,22 @@ public class StringReader implements ImmutableStringReader {
public String readQuotedString() throws CommandSyntaxException {
if (!canRead()) {
return "";
} else if (peek() != SYNTAX_QUOTE) {
}
final char next = peek();
if (!isQuotedStringStart(next)) {
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerExpectedStartOfQuote().createWithContext(this);
}
skip();
return readStringUntil(next);
}
public String readStringUntil(char terminator) throws CommandSyntaxException {
final StringBuilder result = new StringBuilder();
boolean escaped = false;
while (canRead()) {
final char c = read();
if (escaped) {
if (c == SYNTAX_QUOTE || c == SYNTAX_ESCAPE) {
if (c == terminator || c == SYNTAX_ESCAPE) {
result.append(c);
escaped = false;
} else {
@ -178,7 +209,7 @@ public class StringReader implements ImmutableStringReader {
}
} else if (c == SYNTAX_ESCAPE) {
escaped = true;
} else if (c == SYNTAX_QUOTE) {
} else if (c == terminator) {
return result.toString();
} else {
result.append(c);
@ -189,11 +220,15 @@ public class StringReader implements ImmutableStringReader {
}
public String readString() throws CommandSyntaxException {
if (canRead() && peek() == SYNTAX_QUOTE) {
return readQuotedString();
} else {
return readUnquotedString();
if (!canRead()) {
return "";
}
final char next = peek();
if (isQuotedStringStart(next)) {
skip();
return readStringUntil(next);
}
return readUnquotedString();
}
public boolean readBoolean() throws CommandSyntaxException {

View file

@ -1,8 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
@ -12,7 +14,7 @@ import java.util.Collections;
import java.util.concurrent.CompletableFuture;
public interface ArgumentType<T> {
<S> T parse(StringReader reader) throws CommandSyntaxException;
T parse(StringReader reader) throws CommandSyntaxException;
default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
return Suggestions.empty();

View file

@ -1,8 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
@ -26,16 +28,16 @@ public class BoolArgumentType implements ArgumentType<Boolean> {
}
@Override
public <S> Boolean parse(final StringReader reader) throws CommandSyntaxException {
public Boolean parse(final StringReader reader) throws CommandSyntaxException {
return reader.readBoolean();
}
@Override
public <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
if ("true".startsWith(builder.getRemaining().toLowerCase())) {
if ("true".startsWith(builder.getRemainingLowerCase())) {
builder.suggest("true");
}
if ("false".startsWith(builder.getRemaining().toLowerCase())) {
if ("false".startsWith(builder.getRemainingLowerCase())) {
builder.suggest("false");
}
return builder.buildFuture();

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.StringReader;
@ -43,7 +46,7 @@ public class DoubleArgumentType implements ArgumentType<Double> {
}
@Override
public <S> Double parse(final StringReader reader) throws CommandSyntaxException {
public Double parse(final StringReader reader) throws CommandSyntaxException {
final int start = reader.getCursor();
final double result = reader.readDouble();
if (result < minimum) {

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.StringReader;
@ -43,7 +46,7 @@ public class FloatArgumentType implements ArgumentType<Float> {
}
@Override
public <S> Float parse(final StringReader reader) throws CommandSyntaxException {
public Float parse(final StringReader reader) throws CommandSyntaxException {
final int start = reader.getCursor();
final float result = reader.readFloat();
if (result < minimum) {

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.StringReader;
@ -43,7 +46,7 @@ public class IntegerArgumentType implements ArgumentType<Integer> {
}
@Override
public <S> Integer parse(final StringReader reader) throws CommandSyntaxException {
public Integer parse(final StringReader reader) throws CommandSyntaxException {
final int start = reader.getCursor();
final int result = reader.readInt();
if (result < minimum) {
@ -81,4 +84,9 @@ public class IntegerArgumentType implements ArgumentType<Integer> {
return "integer(" + minimum + ", " + maximum + ")";
}
}
@Override
public Collection<String> getExamples() {
return EXAMPLES;
}
}

View file

@ -0,0 +1,92 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.util.Arrays;
import java.util.Collection;
public class LongArgumentType implements ArgumentType<Long> {
private static final Collection<String> EXAMPLES = Arrays.asList("0", "123", "-123");
private final long minimum;
private final long maximum;
private LongArgumentType(final long minimum, final long maximum) {
this.minimum = minimum;
this.maximum = maximum;
}
public static LongArgumentType longArg() {
return longArg(Long.MIN_VALUE);
}
public static LongArgumentType longArg(final long min) {
return longArg(min, Long.MAX_VALUE);
}
public static LongArgumentType longArg(final long min, final long max) {
return new LongArgumentType(min, max);
}
public static long getLong(final CommandContext<?> context, final String name) {
return context.getArgument(name, long.class);
}
public long getMinimum() {
return minimum;
}
public long getMaximum() {
return maximum;
}
@Override
public Long parse(final StringReader reader) throws CommandSyntaxException {
final int start = reader.getCursor();
final long result = reader.readLong();
if (result < minimum) {
reader.setCursor(start);
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.longTooLow().createWithContext(reader, result, minimum);
}
if (result > maximum) {
reader.setCursor(start);
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.longTooHigh().createWithContext(reader, result, maximum);
}
return result;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof LongArgumentType)) return false;
final LongArgumentType that = (LongArgumentType) o;
return maximum == that.maximum && minimum == that.minimum;
}
@Override
public int hashCode() {
return 31 * Long.hashCode(minimum) + Long.hashCode(maximum);
}
@Override
public String toString() {
if (minimum == Long.MIN_VALUE && maximum == Long.MAX_VALUE) {
return "longArg()";
} else if (maximum == Long.MAX_VALUE) {
return "longArg(" + minimum + ")";
} else {
return "longArg(" + minimum + ", " + maximum + ")";
}
}
@Override
public Collection<String> getExamples() {
return EXAMPLES;
}
}

View file

@ -1,8 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.util.Arrays;
@ -36,7 +38,7 @@ public class StringArgumentType implements ArgumentType<String> {
}
@Override
public <S> String parse(final StringReader reader) throws CommandSyntaxException {
public String parse(final StringReader reader) throws CommandSyntaxException {
if (type == StringType.GREEDY_PHRASE) {
final String text = reader.getRemaining();
reader.setCursor(reader.getTotalLength());

View file

@ -1,6 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.builder;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.Message;
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.SingleRedirectModifier;
import com.mojang.brigadier.tree.CommandNode;
@ -17,6 +21,7 @@ public abstract class ArgumentBuilder<S, T extends ArgumentBuilder<S, T>> {
private CommandNode<S> target;
private RedirectModifier<S> modifier = null;
private boolean forks;
private Message description;
protected abstract T getThis();
@ -49,6 +54,15 @@ public abstract class ArgumentBuilder<S, T extends ArgumentBuilder<S, T>> {
return command;
}
public T describe(final Message description) {
this.description = description;
return getThis();
}
public Message getDescription() {
return description;
}
public T requires(final Predicate<S> requirement) {
this.requirement = requirement;
return getThis();

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.builder;
import com.mojang.brigadier.tree.CommandNode;
@ -25,7 +28,7 @@ public class LiteralArgumentBuilder<S> extends ArgumentBuilder<S, LiteralArgumen
@Override
public LiteralCommandNode<S> build() {
final LiteralCommandNode<S> result = new LiteralCommandNode<>(getLiteral(), getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork());
final LiteralCommandNode<S> result = new LiteralCommandNode<>(getLiteral(), getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork(), getDescription());
for (final CommandNode<S> argument : getArguments()) {
result.addChild(argument);

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.builder;
import com.mojang.brigadier.arguments.ArgumentType;
@ -42,7 +45,7 @@ public class RequiredArgumentBuilder<S, T> extends ArgumentBuilder<S, RequiredAr
}
public ArgumentCommandNode<S, T> build() {
final ArgumentCommandNode<S, T> result = new ArgumentCommandNode<>(getName(), getType(), getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork(), getSuggestionsProvider());
final ArgumentCommandNode<S, T> result = new ArgumentCommandNode<>(getName(), getType(), getCommand(), getRequirement(), getRedirect(), getRedirectModifier(), isFork(), getSuggestionsProvider(), getDescription());
for (final CommandNode<S> argument : getArguments()) {
result.addChild(argument);

View file

@ -1,29 +1,48 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.context;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Primitives;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.tree.CommandNode;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CommandContext<S> {
private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER = new HashMap<>();
static {
PRIMITIVE_TO_WRAPPER.put(boolean.class, Boolean.class);
PRIMITIVE_TO_WRAPPER.put(byte.class, Byte.class);
PRIMITIVE_TO_WRAPPER.put(short.class, Short.class);
PRIMITIVE_TO_WRAPPER.put(char.class, Character.class);
PRIMITIVE_TO_WRAPPER.put(int.class, Integer.class);
PRIMITIVE_TO_WRAPPER.put(long.class, Long.class);
PRIMITIVE_TO_WRAPPER.put(float.class, Float.class);
PRIMITIVE_TO_WRAPPER.put(double.class, Double.class);
}
private final S source;
private final String input;
private final Command<S> command;
private final Map<String, ParsedArgument<S, ?>> arguments;
private final Map<CommandNode<S>, StringRange> nodes;
private final CommandNode<S> rootNode;
private final List<ParsedCommandNode<S>> nodes;
private final StringRange range;
private final CommandContext<S> child;
private final RedirectModifier<S> modifier;
private final boolean forks;
public CommandContext(final S source, final String input, final Map<String, ParsedArgument<S, ?>> arguments, final Command<S> command, final Map<CommandNode<S>, StringRange> nodes, final StringRange range, final CommandContext<S> child, final RedirectModifier<S> modifier, boolean forks) {
public CommandContext(final S source, final String input, final Map<String, ParsedArgument<S, ?>> arguments, final Command<S> command, final CommandNode<S> rootNode, final List<ParsedCommandNode<S>> nodes, final StringRange range, final CommandContext<S> child, final RedirectModifier<S> modifier, boolean forks) {
this.source = source;
this.input = input;
this.arguments = arguments;
this.command = command;
this.rootNode = rootNode;
this.nodes = nodes;
this.range = range;
this.child = child;
@ -35,7 +54,7 @@ public class CommandContext<S> {
if (this.source == source) {
return this;
}
return new CommandContext<>(source, input, arguments, command, nodes, range, child, modifier, forks);
return new CommandContext<>(source, input, arguments, command, rootNode, nodes, range, child, modifier, forks);
}
public CommandContext<S> getChild() {
@ -67,7 +86,7 @@ public class CommandContext<S> {
}
final Object result = argument.getResult();
if (Primitives.wrap(clazz).isAssignableFrom(result.getClass())) {
if (PRIMITIVE_TO_WRAPPER.getOrDefault(clazz, clazz).isAssignableFrom(result.getClass())) {
return (V) result;
} else {
throw new IllegalArgumentException("Argument '" + name + "' is defined as " + result.getClass().getSimpleName() + ", not " + clazz);
@ -82,7 +101,8 @@ public class CommandContext<S> {
final CommandContext that = (CommandContext) o;
if (!arguments.equals(that.arguments)) return false;
if (!Iterables.elementsEqual(nodes.entrySet(), that.nodes.entrySet())) return false;
if (!rootNode.equals(that.rootNode)) return false;
if (nodes.size() != that.nodes.size() || !nodes.equals(that.nodes)) return false;
if (command != null ? !command.equals(that.command) : that.command != null) return false;
if (!source.equals(that.source)) return false;
if (child != null ? !child.equals(that.child) : that.child != null) return false;
@ -95,6 +115,7 @@ public class CommandContext<S> {
int result = source.hashCode();
result = 31 * result + arguments.hashCode();
result = 31 * result + (command != null ? command.hashCode() : 0);
result = 31 * result + rootNode.hashCode();
result = 31 * result + nodes.hashCode();
result = 31 * result + (child != null ? child.hashCode() : 0);
return result;
@ -112,10 +133,18 @@ public class CommandContext<S> {
return input;
}
public Map<CommandNode<S>, StringRange> getNodes() {
public CommandNode<S> getRootNode() {
return rootNode;
}
public List<ParsedCommandNode<S>> getNodes() {
return nodes;
}
public boolean hasNodes() {
return !nodes.isEmpty();
}
public boolean isForked() {
return forks;
}

View file

@ -1,16 +1,22 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.context;
import com.google.common.collect.Maps;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.tree.CommandNode;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class CommandContextBuilder<S> {
private final Map<String, ParsedArgument<S, ?>> arguments = Maps.newLinkedHashMap();
private final Map<CommandNode<S>, StringRange> nodes = Maps.newLinkedHashMap();
private final Map<String, ParsedArgument<S, ?>> arguments = new LinkedHashMap<>();
private final CommandNode<S> rootNode;
private final List<ParsedCommandNode<S>> nodes = new ArrayList<>();
private final CommandDispatcher<S> dispatcher;
private S source;
private Command<S> command;
@ -19,7 +25,8 @@ public class CommandContextBuilder<S> {
private RedirectModifier<S> modifier = null;
private boolean forks;
public CommandContextBuilder(final CommandDispatcher<S> dispatcher, final S source, final int start) {
public CommandContextBuilder(final CommandDispatcher<S> dispatcher, final S source, final CommandNode<S> rootNode, final int start) {
this.rootNode = rootNode;
this.dispatcher = dispatcher;
this.source = source;
this.range = StringRange.at(start);
@ -34,6 +41,10 @@ public class CommandContextBuilder<S> {
return source;
}
public CommandNode<S> getRootNode() {
return rootNode;
}
public CommandContextBuilder<S> withArgument(final String name, final ParsedArgument<S, ?> argument) {
this.arguments.put(name, argument);
return this;
@ -49,7 +60,7 @@ public class CommandContextBuilder<S> {
}
public CommandContextBuilder<S> withNode(final CommandNode<S> node, final StringRange range) {
nodes.put(node, range);
nodes.add(new ParsedCommandNode<>(node, range));
this.range = StringRange.encompassing(this.range, range);
this.modifier = node.getRedirectModifier();
this.forks = node.isFork();
@ -57,10 +68,10 @@ public class CommandContextBuilder<S> {
}
public CommandContextBuilder<S> copy() {
final CommandContextBuilder<S> copy = new CommandContextBuilder<>(dispatcher, source, range.getStart());
final CommandContextBuilder<S> copy = new CommandContextBuilder<>(dispatcher, source, rootNode, range.getStart());
copy.command = command;
copy.arguments.putAll(arguments);
copy.nodes.putAll(nodes);
copy.nodes.addAll(nodes);
copy.child = child;
copy.range = range;
copy.forks = forks;
@ -88,12 +99,12 @@ public class CommandContextBuilder<S> {
return command;
}
public Map<CommandNode<S>, StringRange> getNodes() {
public List<ParsedCommandNode<S>> getNodes() {
return nodes;
}
public CommandContext<S> build(final String input) {
return new CommandContext<>(source, input, arguments, command, nodes, range, child == null ? null : child.build(input), modifier, forks);
return new CommandContext<>(source, input, arguments, command, rootNode, nodes, range, child == null ? null : child.build(input), modifier, forks);
}
public CommandDispatcher<S> getDispatcher() {
@ -103,4 +114,33 @@ public class CommandContextBuilder<S> {
public StringRange getRange() {
return range;
}
public SuggestionContext<S> findSuggestionContext(final int cursor) {
if (range.getStart() <= cursor) {
if (range.getEnd() < cursor) {
if (child != null) {
return child.findSuggestionContext(cursor);
} else if (!nodes.isEmpty()) {
final ParsedCommandNode<S> last = nodes.get(nodes.size() - 1);
return new SuggestionContext<>(last.getNode(), last.getRange().getEnd() + 1);
} else {
return new SuggestionContext<>(rootNode, range.getStart());
}
} else {
CommandNode<S> prev = rootNode;
for (final ParsedCommandNode<S> node : nodes) {
final StringRange nodeRange = node.getRange();
if (nodeRange.getStart() <= cursor && cursor <= nodeRange.getEnd()) {
return new SuggestionContext<>(prev, nodeRange.getStart());
}
prev = node.getNode();
}
if (prev == null) {
throw new IllegalStateException("Can't find node before cursor");
}
return new SuggestionContext<>(prev, range.getStart());
}
}
throw new IllegalStateException("Can't find node before cursor");
}
}

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.context;
import java.util.Objects;

View file

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.context;
import com.mojang.brigadier.tree.CommandNode;
import java.util.Objects;
public class ParsedCommandNode<S> {
private final CommandNode<S> node;
private final StringRange range;
public ParsedCommandNode(CommandNode<S> node, StringRange range) {
this.node = node;
this.range = range;
}
public CommandNode<S> getNode() {
return node;
}
public StringRange getRange() {
return range;
}
@Override
public String toString() {
return node + "@" + range;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ParsedCommandNode<?> that = (ParsedCommandNode<?>) o;
return Objects.equals(node, that.node) &&
Objects.equals(range, that.range);
}
@Override
public int hashCode() {
return Objects.hash(node, range);
}
}

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.context;
import com.mojang.brigadier.ImmutableStringReader;

View file

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.context;
import com.mojang.brigadier.tree.CommandNode;
public class SuggestionContext<S> {
public final CommandNode<S> parent;
public final int startPos;
public SuggestionContext(CommandNode<S> parent, int startPos) {
this.parent = parent;
this.startPos = startPos;
}
}

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.exceptions;
public interface BuiltInExceptionProvider {
@ -13,6 +16,10 @@ public interface BuiltInExceptionProvider {
Dynamic2CommandExceptionType integerTooHigh();
Dynamic2CommandExceptionType longTooLow();
Dynamic2CommandExceptionType longTooHigh();
DynamicCommandExceptionType literalIncorrect();
SimpleCommandExceptionType readerExpectedStartOfQuote();
@ -27,6 +34,10 @@ public interface BuiltInExceptionProvider {
SimpleCommandExceptionType readerExpectedInt();
DynamicCommandExceptionType readerInvalidLong();
SimpleCommandExceptionType readerExpectedLong();
DynamicCommandExceptionType readerInvalidDouble();
SimpleCommandExceptionType readerExpectedDouble();

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.exceptions;
import com.mojang.brigadier.LiteralMessage;
@ -12,6 +15,9 @@ public class BuiltInExceptions implements BuiltInExceptionProvider {
private static final Dynamic2CommandExceptionType INTEGER_TOO_SMALL = new Dynamic2CommandExceptionType((found, min) -> new LiteralMessage("Integer must not be less than " + min + ", found " + found));
private static final Dynamic2CommandExceptionType INTEGER_TOO_BIG = new Dynamic2CommandExceptionType((found, max) -> new LiteralMessage("Integer must not be more than " + max + ", found " + found));
private static final Dynamic2CommandExceptionType LONG_TOO_SMALL = new Dynamic2CommandExceptionType((found, min) -> new LiteralMessage("Long must not be less than " + min + ", found " + found));
private static final Dynamic2CommandExceptionType LONG_TOO_BIG = new Dynamic2CommandExceptionType((found, max) -> new LiteralMessage("Long must not be more than " + max + ", found " + found));
private static final DynamicCommandExceptionType LITERAL_INCORRECT = new DynamicCommandExceptionType(expected -> new LiteralMessage("Expected literal " + expected));
private static final SimpleCommandExceptionType READER_EXPECTED_START_OF_QUOTE = new SimpleCommandExceptionType(new LiteralMessage("Expected quote to start a string"));
@ -20,6 +26,8 @@ public class BuiltInExceptions implements BuiltInExceptionProvider {
private static final DynamicCommandExceptionType READER_INVALID_BOOL = new DynamicCommandExceptionType(value -> new LiteralMessage("Invalid bool, expected true or false but found '" + value + "'"));
private static final DynamicCommandExceptionType READER_INVALID_INT = new DynamicCommandExceptionType(value -> new LiteralMessage("Invalid integer '" + value + "'"));
private static final SimpleCommandExceptionType READER_EXPECTED_INT = new SimpleCommandExceptionType(new LiteralMessage("Expected integer"));
private static final DynamicCommandExceptionType READER_INVALID_LONG = new DynamicCommandExceptionType(value -> new LiteralMessage("Invalid long '" + value + "'"));
private static final SimpleCommandExceptionType READER_EXPECTED_LONG = new SimpleCommandExceptionType((new LiteralMessage("Expected long")));
private static final DynamicCommandExceptionType READER_INVALID_DOUBLE = new DynamicCommandExceptionType(value -> new LiteralMessage("Invalid double '" + value + "'"));
private static final SimpleCommandExceptionType READER_EXPECTED_DOUBLE = new SimpleCommandExceptionType(new LiteralMessage("Expected double"));
private static final DynamicCommandExceptionType READER_INVALID_FLOAT = new DynamicCommandExceptionType(value -> new LiteralMessage("Invalid float '" + value + "'"));
@ -62,6 +70,16 @@ public class BuiltInExceptions implements BuiltInExceptionProvider {
return INTEGER_TOO_BIG;
}
@Override
public Dynamic2CommandExceptionType longTooLow() {
return LONG_TOO_SMALL;
}
@Override
public Dynamic2CommandExceptionType longTooHigh() {
return LONG_TOO_BIG;
}
@Override
public DynamicCommandExceptionType literalIncorrect() {
return LITERAL_INCORRECT;
@ -97,6 +115,16 @@ public class BuiltInExceptions implements BuiltInExceptionProvider {
return READER_EXPECTED_INT;
}
@Override
public DynamicCommandExceptionType readerInvalidLong() {
return READER_INVALID_LONG;
}
@Override
public SimpleCommandExceptionType readerExpectedLong() {
return READER_EXPECTED_LONG;
}
@Override
public DynamicCommandExceptionType readerInvalidDouble() {
return READER_INVALID_DOUBLE;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.exceptions;
public interface CommandExceptionType {

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.exceptions;
import com.mojang.brigadier.Message;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.exceptions;
import com.mojang.brigadier.ImmutableStringReader;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.exceptions;
import com.mojang.brigadier.ImmutableStringReader;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.exceptions;
import com.mojang.brigadier.ImmutableStringReader;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.exceptions;
import com.mojang.brigadier.ImmutableStringReader;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.exceptions;
import com.mojang.brigadier.ImmutableStringReader;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.exceptions;
import com.mojang.brigadier.ImmutableStringReader;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.suggestion;
import com.mojang.brigadier.Message;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.suggestion;
import com.mojang.brigadier.Message;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.suggestion;
import com.mojang.brigadier.context.CommandContext;

View file

@ -1,9 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.suggestion;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.mojang.brigadier.context.StringRange;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@ -12,7 +14,7 @@ import java.util.Set;
import java.util.concurrent.CompletableFuture;
public class Suggestions {
private static final Suggestions EMPTY = new Suggestions(StringRange.at(0), Lists.newArrayList());
private static final Suggestions EMPTY = new Suggestions(StringRange.at(0), new ArrayList<>());
private final StringRange range;
private final List<Suggestion> suggestions;
@ -89,11 +91,11 @@ public class Suggestions {
end = Math.max(suggestion.getRange().getEnd(), end);
}
final StringRange range = new StringRange(start, end);
final Set<Suggestion> texts = Sets.newHashSet();
final Set<Suggestion> texts = new HashSet<>();
for (final Suggestion suggestion : suggestions) {
texts.add(suggestion.expand(command, range));
}
final List<Suggestion> sorted = Lists.newArrayList(texts);
final List<Suggestion> sorted = new ArrayList<>(texts);
sorted.sort((a, b) -> a.compareToIgnoreCase(b));
return new Suggestions(range, sorted);
}

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.suggestion;
import com.mojang.brigadier.Message;
@ -5,18 +8,27 @@ import com.mojang.brigadier.context.StringRange;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
public class SuggestionsBuilder {
private final String input;
private final String inputLowerCase;
private final int start;
private final String remaining;
private final String remainingLowerCase;
private final List<Suggestion> result = new ArrayList<>();
public SuggestionsBuilder(final String input, final int start) {
public SuggestionsBuilder(final String input, final String inputLowerCase, final int start) {
this.input = input;
this.inputLowerCase = inputLowerCase;
this.start = start;
this.remaining = input.substring(start);
this.remainingLowerCase = inputLowerCase.substring(start);
}
public SuggestionsBuilder(final String input, final int start) {
this(input, input.toLowerCase(Locale.ROOT), start);
}
public String getInput() {
@ -31,6 +43,10 @@ public class SuggestionsBuilder {
return remaining;
}
public String getRemainingLowerCase() {
return remainingLowerCase;
}
public Suggestions build() {
return Suggestions.create(input, result);
}
@ -71,10 +87,10 @@ public class SuggestionsBuilder {
}
public SuggestionsBuilder createOffset(final int start) {
return new SuggestionsBuilder(input, start);
return new SuggestionsBuilder(input, inputLowerCase, start);
}
public SuggestionsBuilder restart() {
return new SuggestionsBuilder(input, start);
return createOffset(start);
}
}

View file

@ -1,6 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.tree;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.Message;
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
@ -26,7 +30,11 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
private final SuggestionProvider<S> customSuggestions;
public ArgumentCommandNode(final String name, final ArgumentType<T> type, final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks, final SuggestionProvider<S> customSuggestions) {
super(command, requirement, redirect, modifier, forks);
this(name, type, command, requirement, redirect, modifier, forks, customSuggestions, null);
}
public ArgumentCommandNode(final String name, final ArgumentType<T> type, final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks, final SuggestionProvider<S> customSuggestions, final Message description) {
super(command, requirement, redirect, modifier, forks, description);
this.name = name;
this.type = type;
this.customSuggestions = customSuggestions;
@ -120,4 +128,9 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
public Collection<String> getExamples() {
return type.getExamples();
}
@Override
public String toString() {
return "<argument " + name + ":" + type +">";
}
}

View file

@ -1,12 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.tree;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mojang.brigadier.AmbiguityConsumer;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.*;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
@ -16,35 +13,45 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
private Map<String, CommandNode<S>> children = Maps.newLinkedHashMap();
private Map<String, LiteralCommandNode<S>> literals = Maps.newLinkedHashMap();
private Map<String, ArgumentCommandNode<S, ?>> arguments = Maps.newLinkedHashMap();
private final Map<String, CommandNode<S>> children = new LinkedHashMap<>();
private final Map<String, LiteralCommandNode<S>> literals = new LinkedHashMap<>();
private final Map<String, ArgumentCommandNode<S, ?>> arguments = new LinkedHashMap<>();
private final Predicate<S> requirement;
private final CommandNode<S> redirect;
private final RedirectModifier<S> modifier;
private final boolean forks;
private Command<S> command;
private Message description;
protected CommandNode(final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks) {
this(command, requirement, redirect, modifier, forks, null);
}
protected CommandNode(final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks, final Message description) {
this.command = command;
this.requirement = requirement;
this.redirect = redirect;
this.modifier = modifier;
this.forks = forks;
this.description = description;
}
public Command<S> getCommand() {
return command;
}
public Message getDescription() {
return description;
}
public Collection<CommandNode<S>> getChildren() {
return children.values();
}
@ -87,12 +94,10 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
arguments.put(node.getName(), (ArgumentCommandNode<S, ?>) node);
}
}
children = children.entrySet().stream().sorted(Map.Entry.comparingByValue()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
public void findAmbiguities(final AmbiguityConsumer<S> consumer) {
Set<String> matches = Sets.newHashSet();
Set<String> matches = new HashSet<>();
for (final CommandNode<S> child : children.values()) {
for (final CommandNode<S> sibling : children.values()) {
@ -108,7 +113,7 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
if (matches.size() > 0) {
consumer.ambiguous(this, child, sibling, matches);
matches = Sets.newHashSet();
matches = new HashSet<>();
}
}
@ -173,11 +178,11 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
@Override
public int compareTo(final CommandNode<S> o) {
return ComparisonChain
.start()
.compareTrueFirst(this instanceof LiteralCommandNode, o instanceof LiteralCommandNode)
.compare(getSortedKey(), o.getSortedKey())
.result();
if (this instanceof LiteralCommandNode == o instanceof LiteralCommandNode) {
return getSortedKey().compareTo(o.getSortedKey());
}
return (o instanceof LiteralCommandNode) ? 1 : -1;
}
public boolean isFork() {

View file

@ -1,6 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.tree;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.Message;
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
@ -13,15 +17,22 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
public class LiteralCommandNode<S> extends CommandNode<S> {
private final String literal;
private final String literalLowerCase;
public LiteralCommandNode(final String literal, final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks) {
super(command, requirement, redirect, modifier, forks);
this(literal, command, requirement, redirect, modifier, forks, null);
}
public LiteralCommandNode(final String literal, final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks, final Message description) {
super(command, requirement, redirect, modifier, forks, description);
this.literal = literal;
this.literalLowerCase = literal.toLowerCase(Locale.ROOT);
}
public String getLiteral() {
@ -63,7 +74,7 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
@Override
public CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
if (literal.toLowerCase().startsWith(builder.getRemaining().toLowerCase())) {
if (literalLowerCase.startsWith(builder.getRemainingLowerCase())) {
return builder.suggest(literal).buildFuture();
} else {
return Suggestions.empty();
@ -118,4 +129,9 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
public Collection<String> getExamples() {
return Collections.singleton(literal);
}
@Override
public String toString() {
return "<literal " + literal + ">";
}
}

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.tree;
import com.mojang.brigadier.StringReader;
@ -14,7 +17,7 @@ import java.util.concurrent.CompletableFuture;
public class RootCommandNode<S> extends CommandNode<S> {
public RootCommandNode() {
super(null, c -> true, null, s -> Collections.singleton(s.getSource()), false);
super(null, c -> true, null, s -> Collections.singleton(s.getSource()), false, null);
}
@Override
@ -62,4 +65,9 @@ public class RootCommandNode<S> extends CommandNode<S> {
public Collection<String> getExamples() {
return Collections.emptyList();
}
@Override
public String toString() {
return "<root>";
}
}

View file

@ -1,6 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
import com.google.common.collect.Lists;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@ -11,6 +15,9 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal;
import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument;
@ -43,6 +50,12 @@ public class CommandDispatcherTest {
when(command.run(any())).thenReturn(42);
}
private static StringReader inputWithOffset(final String input, final int offset) {
final StringReader result = new StringReader(input);
result.setCursor(offset);
return result;
}
@SuppressWarnings("unchecked")
@Test
public void testCreateAndExecuteCommand() throws Exception {
@ -52,6 +65,15 @@ public class CommandDispatcherTest {
verify(command).run(any(CommandContext.class));
}
@SuppressWarnings("unchecked")
@Test
public void testCreateAndExecuteOffsetCommand() throws Exception {
subject.register(literal("foo").executes(command));
assertThat(subject.execute(inputWithOffset("/foo", 1), source), is(42));
verify(command).run(any(CommandContext.class));
}
@SuppressWarnings("unchecked")
@Test
public void testCreateAndMergeCommands() throws Exception {
@ -241,23 +263,33 @@ public class CommandDispatcherTest {
@SuppressWarnings("unchecked")
@Test
public void testExecuteRedirectedMultipleTimes() throws Exception {
subject.register(literal("actual").executes(command));
subject.register(literal("redirected").redirect(subject.getRoot()));
final LiteralCommandNode<Object> concreteNode = subject.register(literal("actual").executes(command));
final LiteralCommandNode<Object> redirectNode = subject.register(literal("redirected").redirect(subject.getRoot()));
final String input = "redirected redirected actual";
final ParseResults<Object> parse = subject.parse(input, source);
assertThat(parse.getContext().getRange().get(input), equalTo("redirected"));
assertThat(parse.getContext().getNodes().size(), is(1));
assertThat(parse.getContext().getRootNode(), is(subject.getRoot()));
assertThat(parse.getContext().getNodes().get(0).getRange(), equalTo(parse.getContext().getRange()));
assertThat(parse.getContext().getNodes().get(0).getNode(), is(redirectNode));
final CommandContextBuilder<Object> child1 = parse.getContext().getChild();
assertThat(child1, is(notNullValue()));
assertThat(child1.getRange().get(input), equalTo("redirected redirected"));
assertThat(child1.getNodes().size(), is(2));
assertThat(child1.getRange().get(input), equalTo("redirected"));
assertThat(child1.getNodes().size(), is(1));
assertThat(child1.getRootNode(), is(subject.getRoot()));
assertThat(child1.getNodes().get(0).getRange(), equalTo(child1.getRange()));
assertThat(child1.getNodes().get(0).getNode(), is(redirectNode));
final CommandContextBuilder<Object> child2 = child1.getChild();
assertThat(child2, is(notNullValue()));
assertThat(child2.getRange().get(input), equalTo("redirected actual"));
assertThat(child2.getNodes().size(), is(2));
assertThat(child2.getRange().get(input), equalTo("actual"));
assertThat(child2.getNodes().size(), is(1));
assertThat(child2.getRootNode(), is(subject.getRoot()));
assertThat(child2.getNodes().get(0).getRange(), equalTo(child2.getRange()));
assertThat(child2.getNodes().get(0).getNode(), is(concreteNode));
assertThat(subject.execute(parse), is(42));
verify(command).run(any(CommandContext.class));
@ -272,19 +304,25 @@ public class CommandDispatcherTest {
when(modifier.apply(argThat(hasProperty("source", is(source))))).thenReturn(Lists.newArrayList(source1, source2));
subject.register(literal("actual").executes(command));
subject.register(literal("redirected").fork(subject.getRoot(), modifier));
final LiteralCommandNode<Object> concreteNode = subject.register(literal("actual").executes(command));
final LiteralCommandNode<Object> redirectNode = subject.register(literal("redirected").fork(subject.getRoot(), modifier));
final String input = "redirected actual";
final ParseResults<Object> parse = subject.parse(input, source);
assertThat(parse.getContext().getRange().get(input), equalTo("redirected"));
assertThat(parse.getContext().getNodes().size(), is(1));
assertThat(parse.getContext().getRootNode(), equalTo(subject.getRoot()));
assertThat(parse.getContext().getNodes().get(0).getRange(), equalTo(parse.getContext().getRange()));
assertThat(parse.getContext().getNodes().get(0).getNode(), is(redirectNode));
assertThat(parse.getContext().getSource(), is(source));
final CommandContextBuilder<Object> parent = parse.getContext().getChild();
assertThat(parent, is(notNullValue()));
assertThat(parent.getRange().get(input), equalTo("redirected actual"));
assertThat(parent.getNodes().size(), is(2));
assertThat(parent.getRange().get(input), equalTo("actual"));
assertThat(parent.getNodes().size(), is(1));
assertThat(parse.getContext().getRootNode(), equalTo(subject.getRoot()));
assertThat(parent.getNodes().get(0).getRange(), equalTo(parent.getRange()));
assertThat(parent.getNodes().get(0).getNode(), is(concreteNode));
assertThat(parent.getSource(), is(source));
assertThat(subject.execute(parse), is(2));
@ -292,6 +330,37 @@ public class CommandDispatcherTest {
verify(command).run(argThat(hasProperty("source", is(source2))));
}
@Test
public void testIncompleteRedirectShouldThrow() {
final LiteralCommandNode<Object> foo = subject.register(literal("foo")
.then(literal("bar")
.then(argument("value", integer()).executes(context -> IntegerArgumentType.getInteger(context, "value"))))
.then(literal("awa").executes(context -> 2)));
final LiteralCommandNode<Object> baz = subject.register(literal("baz").redirect(foo));
try {
int result = subject.execute("baz bar", source);
fail("Should have thrown an exception");
} catch (CommandSyntaxException e) {
assertThat(e.getType(), is(CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand()));
}
}
@Test
public void testRedirectModifierEmptyResult() {
final LiteralCommandNode<Object> foo = subject.register(literal("foo")
.then(literal("bar")
.then(argument("value", integer()).executes(context -> IntegerArgumentType.getInteger(context, "value"))))
.then(literal("awa").executes(context -> 2)));
final RedirectModifier<Object> emptyModifier = context -> Collections.emptyList();
final LiteralCommandNode<Object> baz = subject.register(literal("baz").fork(foo, emptyModifier));
try {
int result = subject.execute("baz bar 100", source);
assertThat(result, is(0)); // No commands executed, so result is 0
} catch (CommandSyntaxException e) {
fail("Should not throw an exception");
}
}
@Test
public void testExecuteOrphanedSubcommand() throws Exception {
subject.register(literal("foo").then(

View file

@ -1,6 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.CommandNode;
@ -99,7 +103,11 @@ public class CommandDispatcherUsagesTest {
}
private CommandNode<Object> get(final String command) {
return Iterators.getLast(subject.parse(command, source).getContext().getNodes().keySet().iterator());
return Iterables.getLast(subject.parse(command, source).getContext().getNodes()).getNode();
}
private CommandNode<Object> get(final StringReader command) {
return Iterables.getLast(subject.parse(command, source).getContext().getNodes()).getNode();
}
@Test
@ -174,4 +182,18 @@ public class CommandDispatcherUsagesTest {
.build()
));
}
@Test
public void testSmartUsage_offsetH() throws Exception {
final StringReader offsetH = new StringReader("/|/|/h");
offsetH.setCursor(5);
final Map<CommandNode<Object>, String> results = subject.getSmartUsage(get(offsetH), source);
assertThat(results, equalTo(ImmutableMap.builder()
.put(get("h 1"), "[1] i")
.put(get("h 2"), "[2] i ii")
.put(get("h 3"), "[3]")
.build()
));
}
}

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
import com.google.common.collect.Lists;
@ -11,6 +14,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.List;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static com.mojang.brigadier.arguments.StringArgumentType.word;
import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal;
@ -30,6 +35,24 @@ public class CommandSuggestionsTest {
subject = new CommandDispatcher<>();
}
private void testSuggestions(final String contents, final int cursor, final StringRange range, final String... suggestions) {
final Suggestions result = subject.getCompletionSuggestions(subject.parse(contents, source), cursor).join();
assertThat(result.getRange(), equalTo(range));
final List<Suggestion> expected = Lists.newArrayList();
for (final String suggestion : suggestions) {
expected.add(new Suggestion(range, suggestion));
}
assertThat(result.getList(), equalTo(expected));
}
private static StringReader inputWithOffset(final String input, final int offset) {
final StringReader result = new StringReader(input);
result.setCursor(offset);
return result;
}
@Test
public void getCompletionSuggestions_rootCommands() throws Exception {
subject.register(literal("foo"));
@ -42,6 +65,18 @@ public class CommandSuggestionsTest {
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.at(0), "bar"), new Suggestion(StringRange.at(0), "baz"), new Suggestion(StringRange.at(0), "foo"))));
}
@Test
public void getCompletionSuggestions_rootCommands_withInputOffset() throws Exception {
subject.register(literal("foo"));
subject.register(literal("bar"));
subject.register(literal("baz"));
final Suggestions result = subject.getCompletionSuggestions(subject.parse(inputWithOffset("OOO", 3), source)).join();
assertThat(result.getRange(), equalTo(StringRange.at(3)));
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.at(3), "bar"), new Suggestion(StringRange.at(3), "baz"), new Suggestion(StringRange.at(3), "foo"))));
}
@Test
public void getCompletionSuggestions_rootCommands_partial() throws Exception {
subject.register(literal("foo"));
@ -54,6 +89,18 @@ public class CommandSuggestionsTest {
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(0, 1), "bar"), new Suggestion(StringRange.between(0, 1), "baz"))));
}
@Test
public void getCompletionSuggestions_rootCommands_partial_withInputOffset() throws Exception {
subject.register(literal("foo"));
subject.register(literal("bar"));
subject.register(literal("baz"));
final Suggestions result = subject.getCompletionSuggestions(subject.parse(inputWithOffset("Zb", 1), source)).join();
assertThat(result.getRange(), equalTo(StringRange.between(1, 2)));
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(1, 2), "bar"), new Suggestion(StringRange.between(1, 2), "baz"))));
}
@Test
public void getCompletionSuggestions_subCommands() throws Exception {
subject.register(
@ -69,6 +116,31 @@ public class CommandSuggestionsTest {
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.at(7), "bar"), new Suggestion(StringRange.at(7), "baz"), new Suggestion(StringRange.at(7), "foo"))));
}
@Test
public void getCompletionSuggestions_movingCursor_subCommands() throws Exception {
subject.register(
literal("parent_one")
.then(literal("faz"))
.then(literal("fbz"))
.then(literal("gaz"))
);
subject.register(
literal("parent_two")
);
testSuggestions("parent_one faz ", 0, StringRange.at(0), "parent_one", "parent_two");
testSuggestions("parent_one faz ", 1, StringRange.between(0, 1), "parent_one", "parent_two");
testSuggestions("parent_one faz ", 7, StringRange.between(0, 7), "parent_one", "parent_two");
testSuggestions("parent_one faz ", 8, StringRange.between(0, 8), "parent_one");
testSuggestions("parent_one faz ", 10, StringRange.at(0));
testSuggestions("parent_one faz ", 11, StringRange.at(11), "faz", "fbz", "gaz");
testSuggestions("parent_one faz ", 12, StringRange.between(11, 12), "faz", "fbz");
testSuggestions("parent_one faz ", 13, StringRange.between(11, 13), "faz");
testSuggestions("parent_one faz ", 14, StringRange.at(0));
testSuggestions("parent_one faz ", 15, StringRange.at(0));
}
@Test
public void getCompletionSuggestions_subCommands_partial() throws Exception {
subject.register(
@ -85,6 +157,22 @@ public class CommandSuggestionsTest {
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(7, 8), "bar"), new Suggestion(StringRange.between(7, 8), "baz"))));
}
@Test
public void getCompletionSuggestions_subCommands_partial_withInputOffset() throws Exception {
subject.register(
literal("parent")
.then(literal("foo"))
.then(literal("bar"))
.then(literal("baz"))
);
final ParseResults<Object> parse = subject.parse(inputWithOffset("junk parent b", 5), source);
final Suggestions result = subject.getCompletionSuggestions(parse).join();
assertThat(result.getRange(), equalTo(StringRange.between(12, 13)));
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(12, 13), "bar"), new Suggestion(StringRange.between(12, 13), "baz"))));
}
@Test
public void getCompletionSuggestions_redirect() throws Exception {
final LiteralCommandNode<Object> actual = subject.register(literal("actual").then(literal("sub")));
@ -109,6 +197,42 @@ public class CommandSuggestionsTest {
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(9, 10), "sub"))));
}
@Test
public void getCompletionSuggestions_movingCursor_redirect() throws Exception {
final LiteralCommandNode<Object> actualOne = subject.register(literal("actual_one")
.then(literal("faz"))
.then(literal("fbz"))
.then(literal("gaz"))
);
final LiteralCommandNode<Object> actualTwo = subject.register(literal("actual_two"));
subject.register(literal("redirect_one").redirect(actualOne));
subject.register(literal("redirect_two").redirect(actualOne));
testSuggestions("redirect_one faz ", 0, StringRange.at(0), "actual_one", "actual_two", "redirect_one", "redirect_two");
testSuggestions("redirect_one faz ", 9, StringRange.between(0, 9), "redirect_one", "redirect_two");
testSuggestions("redirect_one faz ", 10, StringRange.between(0, 10), "redirect_one");
testSuggestions("redirect_one faz ", 12, StringRange.at(0));
testSuggestions("redirect_one faz ", 13, StringRange.at(13), "faz", "fbz", "gaz");
testSuggestions("redirect_one faz ", 14, StringRange.between(13, 14), "faz", "fbz");
testSuggestions("redirect_one faz ", 15, StringRange.between(13, 15), "faz");
testSuggestions("redirect_one faz ", 16, StringRange.at(0));
testSuggestions("redirect_one faz ", 17, StringRange.at(0));
}
@Test
public void getCompletionSuggestions_redirectPartial_withInputOffset() throws Exception {
final LiteralCommandNode<Object> actual = subject.register(literal("actual").then(literal("sub")));
subject.register(literal("redirect").redirect(actual));
final ParseResults<Object> parse = subject.parse(inputWithOffset("/redirect s", 1), source);
final Suggestions result = subject.getCompletionSuggestions(parse).join();
assertThat(result.getRange(), equalTo(StringRange.between(10, 11)));
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.between(10, 11), "sub"))));
}
@Test
public void getCompletionSuggestions_redirect_lots() throws Exception {
final LiteralCommandNode<Object> loop = subject.register(literal("redirect"));

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@ -153,6 +156,30 @@ public class StringReaderTest {
assertThat(reader.getRemaining(), equalTo(""));
}
@Test
public void readSingleQuotedString() throws Exception {
final StringReader reader = new StringReader("'hello world'");
assertThat(reader.readQuotedString(), equalTo("hello world"));
assertThat(reader.getRead(), equalTo("'hello world'"));
assertThat(reader.getRemaining(), equalTo(""));
}
@Test
public void readMixedQuotedString_doubleInsideSingle() throws Exception {
final StringReader reader = new StringReader("'hello \"world\"'");
assertThat(reader.readQuotedString(), equalTo("hello \"world\""));
assertThat(reader.getRead(), equalTo("'hello \"world\"'"));
assertThat(reader.getRemaining(), equalTo(""));
}
@Test
public void readMixedQuotedString_singleInsideDouble() throws Exception {
final StringReader reader = new StringReader("\"hello 'world'\"");
assertThat(reader.readQuotedString(), equalTo("hello 'world'"));
assertThat(reader.getRead(), equalTo("\"hello 'world'\""));
assertThat(reader.getRemaining(), equalTo(""));
}
@Test
public void readQuotedString_empty() throws Exception {
final StringReader reader = new StringReader("");
@ -239,6 +266,40 @@ public class StringReaderTest {
}
}
@Test
public void readQuotedString_invalidQuoteEscape() throws Exception {
try {
new StringReader("'hello\\\"\'world").readQuotedString();
} catch (final CommandSyntaxException ex) {
assertThat(ex.getType(), is(CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerInvalidEscape()));
assertThat(ex.getCursor(), is(7));
}
}
@Test
public void readString_noQuotes() throws Exception {
final StringReader reader = new StringReader("hello world");
assertThat(reader.readString(), equalTo("hello"));
assertThat(reader.getRead(), equalTo("hello"));
assertThat(reader.getRemaining(), equalTo(" world"));
}
@Test
public void readString_singleQuotes() throws Exception {
final StringReader reader = new StringReader("'hello world'");
assertThat(reader.readString(), equalTo("hello world"));
assertThat(reader.getRead(), equalTo("'hello world'"));
assertThat(reader.getRemaining(), equalTo(""));
}
@Test
public void readString_doubleQuotes() throws Exception {
final StringReader reader = new StringReader("\"hello world\"");
assertThat(reader.readString(), equalTo("hello world"));
assertThat(reader.getRead(), equalTo("\"hello world\""));
assertThat(reader.getRemaining(), equalTo(""));
}
@Test
public void readInt() throws Exception {
final StringReader reader = new StringReader("1234567890");
@ -291,6 +352,58 @@ public class StringReaderTest {
assertThat(reader.getRemaining(), equalTo("foo bar"));
}
@Test
public void readLong() throws Exception {
final StringReader reader = new StringReader("1234567890");
assertThat(reader.readLong(), is(1234567890L));
assertThat(reader.getRead(), equalTo("1234567890"));
assertThat(reader.getRemaining(), equalTo(""));
}
@Test
public void readLong_negative() throws Exception {
final StringReader reader = new StringReader("-1234567890");
assertThat(reader.readLong(), is(-1234567890L));
assertThat(reader.getRead(), equalTo("-1234567890"));
assertThat(reader.getRemaining(), equalTo(""));
}
@Test
public void readLong_invalid() throws Exception {
try {
new StringReader("12.34").readLong();
} catch (final CommandSyntaxException ex) {
assertThat(ex.getType(), is(CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerInvalidLong()));
assertThat(ex.getCursor(), is(0));
}
}
@Test
public void readLong_none() throws Exception {
try {
new StringReader("").readLong();
} catch (final CommandSyntaxException ex) {
assertThat(ex.getType(), is(CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerExpectedLong()));
assertThat(ex.getCursor(), is(0));
}
}
@Test
public void readLong_withRemaining() throws Exception {
final StringReader reader = new StringReader("1234567890 foo bar");
assertThat(reader.readLong(), is(1234567890L));
assertThat(reader.getRead(), equalTo("1234567890"));
assertThat(reader.getRemaining(), equalTo(" foo bar"));
}
@Test
public void readLong_withRemainingImmediate() throws Exception {
final StringReader reader = new StringReader("1234567890foo bar");
assertThat(reader.readLong(), is(1234567890L));
assertThat(reader.getRead(), equalTo("1234567890"));
assertThat(reader.getRemaining(), equalTo("foo bar"));
}
@Test
public void readDouble() throws Exception {
final StringReader reader = new StringReader("123");

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.StringReader;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.google.common.testing.EqualsTester;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.google.common.testing.EqualsTester;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.google.common.testing.EqualsTester;

View file

@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.google.common.testing.EqualsTester;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static com.mojang.brigadier.arguments.LongArgumentType.longArg;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
@RunWith(MockitoJUnitRunner.class)
public class LongArgumentTypeTest {
private LongArgumentType type;
@Mock
private CommandContextBuilder<Object> context;
@Before
public void setUp() throws Exception {
type = longArg(-100, 1_000_000_000_000L);
}
@Test
public void parse() throws Exception {
final StringReader reader = new StringReader("15");
assertThat(longArg().parse(reader), is(15L));
assertThat(reader.canRead(), is(false));
}
@Test
public void parse_tooSmall() throws Exception {
final StringReader reader = new StringReader("-5");
try {
longArg(0, 100).parse(reader);
fail();
} catch (final CommandSyntaxException ex) {
assertThat(ex.getType(), is(CommandSyntaxException.BUILT_IN_EXCEPTIONS.longTooLow()));
assertThat(ex.getCursor(), is(0));
}
}
@Test
public void parse_tooBig() throws Exception {
final StringReader reader = new StringReader("5");
try {
longArg(-100, 0).parse(reader);
fail();
} catch (final CommandSyntaxException ex) {
assertThat(ex.getType(), is(CommandSyntaxException.BUILT_IN_EXCEPTIONS.longTooHigh()));
assertThat(ex.getCursor(), is(0));
}
}
@Test
public void testEquals() throws Exception {
new EqualsTester()
.addEqualityGroup(longArg(), longArg())
.addEqualityGroup(longArg(-100, 100), longArg(-100, 100))
.addEqualityGroup(longArg(-100, 50), longArg(-100, 50))
.addEqualityGroup(longArg(-50, 100), longArg(-50, 100))
.testEquals();
}
@Test
public void testToString() throws Exception {
assertThat(longArg(), hasToString("longArg()"));
assertThat(longArg(-100), hasToString("longArg(-100)"));
assertThat(longArg(-100, 100), hasToString("longArg(-100, 100)"));
assertThat(longArg(Long.MIN_VALUE, 100), hasToString("longArg(-9223372036854775808, 100)"));
}
}

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.arguments;
import com.mojang.brigadier.StringReader;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.benchmarks;
import com.google.common.collect.Lists;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.benchmarks;
import com.mojang.brigadier.CommandDispatcher;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.builder;
import com.mojang.brigadier.tree.CommandNode;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.builder;
import com.mojang.brigadier.Command;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.builder;
import com.mojang.brigadier.Command;

View file

@ -1,16 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.context;
import com.google.common.testing.EqualsTester;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
@ -23,9 +26,12 @@ public class CommandContextTest {
@Mock
private CommandDispatcher<Object> dispatcher;
@Mock
private CommandNode<Object> rootNode;
@Before
public void setUp() throws Exception {
builder = new CommandContextBuilder<>(dispatcher, source, 0);
builder = new CommandContextBuilder<>(dispatcher, source, rootNode, 0);
}
@Test(expected = IllegalArgumentException.class)
@ -50,22 +56,30 @@ public class CommandContextTest {
assertThat(builder.build("").getSource(), is(source));
}
@Test
public void testRootNode() throws Exception {
assertThat(builder.build("").getRootNode(), is(rootNode));
}
@SuppressWarnings("unchecked")
@Test
public void testEquals() throws Exception {
final Object otherSource = new Object();
final Command<Object> command = mock(Command.class);
final Command<Object> otherCommand = mock(Command.class);
final CommandNode<Object> rootNode = mock(CommandNode.class);
final CommandNode<Object> otherRootNode = mock(CommandNode.class);
final CommandNode<Object> node = mock(CommandNode.class);
final CommandNode<Object> otherNode = mock(CommandNode.class);
new EqualsTester()
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, 0).build(""), new CommandContextBuilder<>(dispatcher, source, 0).build(""))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, otherSource, 0).build(""), new CommandContextBuilder<>(dispatcher, otherSource, 0).build(""))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, 0).withCommand(command).build(""), new CommandContextBuilder<>(dispatcher, source, 0).withCommand(command).build(""))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, 0).withCommand(otherCommand).build(""), new CommandContextBuilder<>(dispatcher, source, 0).withCommand(otherCommand).build(""))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, 0).withArgument("foo", new ParsedArgument<>(0, 1, 123)).build("123"), new CommandContextBuilder<>(dispatcher, source, 0).withArgument("foo", new ParsedArgument<>(0, 1, 123)).build("123"))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, 0).withNode(node, StringRange.between(0, 3)).withNode(otherNode, StringRange.between(4, 6)).build("123 456"), new CommandContextBuilder<>(dispatcher, source, 0).withNode(node, StringRange.between(0, 3)).withNode(otherNode, StringRange.between(4, 6)).build("123 456"))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, 0).withNode(otherNode, StringRange.between(0, 3)).withNode(node, StringRange.between(4, 6)).build("123 456"), new CommandContextBuilder<>(dispatcher, source, 0).withNode(otherNode, StringRange.between(0, 3)).withNode(node, StringRange.between(4, 6)).build("123 456"))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, rootNode, 0).build(""), new CommandContextBuilder<>(dispatcher, source, rootNode, 0).build(""))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, otherRootNode, 0).build(""), new CommandContextBuilder<>(dispatcher, source, otherRootNode, 0).build(""))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, otherSource, rootNode, 0).build(""), new CommandContextBuilder<>(dispatcher, otherSource, rootNode, 0).build(""))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withCommand(command).build(""), new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withCommand(command).build(""))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withCommand(otherCommand).build(""), new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withCommand(otherCommand).build(""))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withArgument("foo", new ParsedArgument<>(0, 1, 123)).build("123"), new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withArgument("foo", new ParsedArgument<>(0, 1, 123)).build("123"))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withNode(node, StringRange.between(0, 3)).withNode(otherNode, StringRange.between(4, 6)).build("123 456"), new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withNode(node, StringRange.between(0, 3)).withNode(otherNode, StringRange.between(4, 6)).build("123 456"))
.addEqualityGroup(new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withNode(otherNode, StringRange.between(0, 3)).withNode(node, StringRange.between(4, 6)).build("123 456"), new CommandContextBuilder<>(dispatcher, source, rootNode, 0).withNode(otherNode, StringRange.between(0, 3)).withNode(node, StringRange.between(4, 6)).build("123 456"))
.testEquals();
}
}

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.context;
import com.google.common.testing.EqualsTester;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.exceptions;
import com.mojang.brigadier.LiteralMessage;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.exceptions;
import com.mojang.brigadier.LiteralMessage;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.suggestion;
import com.mojang.brigadier.context.StringRange;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.suggestion;
import com.google.common.collect.Lists;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.suggestion;
import com.google.common.collect.Lists;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.tree;
import com.mojang.brigadier.Command;

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.tree;
import com.google.common.testing.EqualsTester;
@ -29,7 +32,7 @@ public class ArgumentCommandNodeTest extends AbstractCommandNodeTest {
@Before
public void setUp() throws Exception {
node = argument("foo", integer()).build();
contextBuilder = new CommandContextBuilder<>(new CommandDispatcher<>(), new Object(), 0);
contextBuilder = new CommandContextBuilder<>(new CommandDispatcher<>(), new Object(), new RootCommandNode<>(), 0);
}
@Test

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.tree;
import com.google.common.collect.Lists;
@ -34,7 +37,7 @@ public class LiteralCommandNodeTest extends AbstractCommandNodeTest {
@Before
public void setUp() throws Exception {
node = literal("foo").build();
contextBuilder = new CommandContextBuilder<>(new CommandDispatcher<>(), new Object(), 0);
contextBuilder = new CommandContextBuilder<>(new CommandDispatcher<>(), new Object(), new RootCommandNode<>(), 0);
}
@Test

View file

@ -1,3 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package com.mojang.brigadier.tree;
import com.google.common.testing.EqualsTester;
@ -31,7 +34,7 @@ public class RootCommandNodeTest extends AbstractCommandNodeTest {
@Test
public void testParse() throws Exception {
final StringReader reader = new StringReader("hello world");
node.parse(reader, new CommandContextBuilder<>(new CommandDispatcher<>(), new Object(), 0));
node.parse(reader, new CommandContextBuilder<>(new CommandDispatcher<>(), new Object(), new RootCommandNode<>(), 0));
assertThat(reader.getCursor(), is(0));
}