Compare commits

...
10 Commits
Author SHA1 Message Date
Fortern 110c9d01cd release
(cherry picked from commit 161924a5931b64a94f6d13685f828f5be20199fb)
2024-08-13 03:35:22 +08:00
Fortern 6ce25e6370 Make sure arguments are preserved after redirects
https://github.com/Mojang/brigadier/pull/142
2024-08-13 03:33:01 +08:00
Fortern 8d48b13cdf Ensure that the CompletableFuture returned by getCompletionSuggestions is always completed
fix
https://github.com/Mojang/brigadier/pull/81
2024-08-13 03:28:27 +08:00
Fortern 48ab1ab497 Context-aware CommandNode#canUse method and Fix childless redirects
issue
https://github.com/Mojang/brigadier/issues/46
fix
https://github.com/PaperMC/velocity-brigadier/pull/1
https://github.com/PaperMC/velocity-brigadier/pull/2
2024-08-13 03:18:50 +08:00
Fortern 4c8d2abf45 Allow for multiple spaces between arguments
https://github.com/Mojang/brigadier/pull/136
2024-08-13 01:38:13 +08:00
Fortern d2e25c1bfe Add greedy charset option to string argument
https://github.com/Mojang/brigadier/pull/131
2024-08-13 01:38:12 +08:00
Fortern 7ceb01d4e5 update gradle 2024-08-13 01:22:53 +08:00
Gegy b5419b186e Add ArgumentType.parse() overload that can accept source context 2024-08-07 15:38:44 +02:00
Bartek Bok b92c420b2a Add utilities for running commands in stages (#140)
* Add more comprehensive test for context propagation in redirect

* Add tests for forking and exceptional execution

* Add test for redirect+execute nodes

* Cleanup some warnings from CommandDispatcherTest

* Remove leftover check (was needed when redirect was not cached)

* Refactor command execution loop and change "no command" behavior to not
depend on runtime

* Add utilities for running command chains manually

* Bump version
2023-10-09 15:42:10 +02: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
30 changed files with 1109 additions and 513 deletions
-51
View File
@@ -1,51 +0,0 @@
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
View File
@@ -1,87 +0,0 @@
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
View File
@@ -1,22 +0,0 @@
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'
+7 -17
View File
@@ -9,44 +9,34 @@ Brigadier is available to Maven & Gradle via `libraries.minecraft.net`. Its grou
First include our repository: First include our repository:
```groovy ```groovy
maven { maven {
url "https://libraries.minecraft.net" url "https://maven.fortern.xyz/repository/maven-public/"
} }
``` ```
And then use this library (change `(the latest version)` to the latest version!): And then use this library (change `(the latest version)` to the latest version!):
```groovy ```groovy
compile 'com.mojang:brigadier:(the latest version)' compile 'xyz.fortern:brigadier:(the latest version)'
``` ```
## Maven ## Maven
First include our repository: First include my repository:
```xml ```xml
<repository> <repository>
<id>minecraft-libraries</id> <id>fortern-libraries</id>
<name>Minecraft Libraries</name> <name>Fortern Libraries</name>
<url>https://libraries.minecraft.net</url> <url>https://maven.fortern.xyz/repository/maven-public/</url>
</repository> </repository>
``` ```
And then use this library (change `(the latest version)` to the latest version!): And then use this library (change `(the latest version)` to the latest version!):
```xml ```xml
<dependency> <dependency>
<groupId>com.mojang</groupId> <groupId>xyz.fortern</groupId>
<artifactId>brigadier</artifactId> <artifactId>brigadier</artifactId>
<version>(the latest version)</version> <version>(the latest version)</version>
</dependency> </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 [[email protected]](mailto:[email protected]) with any additional questions or comments.
# Usage # Usage
At the heart of Brigadier, you need a `CommandDispatcher<S>`, where `<S>` is any custom object you choose to identify a "command source". At the heart of Brigadier, you need a `CommandDispatcher<S>`, where `<S>` is any custom object you choose to identify a "command source".
+35 -94
View File
@@ -1,116 +1,57 @@
import groovy.io.FileType plugins {
id 'java-library'
id 'maven-publish'
}
apply plugin: 'java-library' group = 'xyz.fortern'
apply plugin: 'maven-publish' version ='1.1.0'
group = 'com.mojang' java {
version = project.hasProperty('buildNumber') ? "${project.majorMinor}.${project.buildNumber}" : "${project.majorMinor}.0-SNAPSHOT" sourceCompatibility = JavaVersion.VERSION_1_8
withSourcesJar()
buildscript {
repositories {
mavenCentral()
maven {
url "https://libraries.minecraft.net"
}
}
} }
repositories { repositories {
maven {
url "https://libraries.minecraft.net"
}
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
testCompile 'com.google.guava:guava:26.0-jre' // JUnit Jupiter (Aggregator) https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
testCompile 'junit:junit-dep:4.11' testImplementation 'org.junit.jupiter:junit-jupiter:5.10.3'
testCompile 'org.hamcrest:hamcrest-library:1.3' // Guava: Google Core Libraries For Java https://mvnrepository.com/artifact/com.google.guava/guava
testCompile 'org.mockito:mockito-core:1.10.19' implementation 'com.google.guava:guava:33.2.1-jre'
testCompile 'com.google.guava:guava-testlib:26.0-jre' // Guava Testing Library https://mvnrepository.com/artifact/com.google.guava/guava-testlib
testCompile 'org.openjdk.jmh:jmh-core:1.21' testImplementation 'com.google.guava:guava-testlib:33.2.1-jre'
annotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.21' // Hamcrest Library https://mvnrepository.com/artifact/org.hamcrest/hamcrest-library
} testImplementation 'org.hamcrest:hamcrest-library:3.0'
// Mockito Core https://mvnrepository.com/artifact/org.mockito/mockito-core
task sourcesJar(type: Jar) { testImplementation 'org.mockito:mockito-core:1.10.19'
classifier = 'sources' // JMH Core https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core
from sourceSets.main.allSource testImplementation 'org.openjdk.jmh:jmh-core:1.37'
} // JMH Generators: Annotation Processors https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess
testImplementation 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
sourceSets {
main {
java {
srcDirs = ['src/main/java']
}
resources {
srcDirs = ['src/main/resources']
}
}
test {
java {
srcDirs = ['src/test/java']
}
resources {
srcDirs = ['src/test/resources']
}
}
}
artifacts {
archives jar
archives sourcesJar
}
test {
testLogging {
events "failed", "skipped"
showStandardStreams = true
showExceptions true
}
} }
publishing { publishing {
publications { publications {
mavenJava(MavenPublication) { maven(MavenPublication) {
from components.java from components.java
artifact sourcesJar }
}
repositories {
maven {
name = 'ForternRelease'
url = "https://maven.fortern.xyz/repository/maven-releases/"
credentials {
username = findProperty('repo.fortern.user')
password = findProperty('repo.fortern.password')
}
} }
} }
} }
task report { tasks.register('report') {
doLast { doLast {
println "##vso[build.updatebuildnumber]${project.version}" println "##vso[build.updatebuildnumber]${project.version}"
} }
} }
def publishDir = file("$buildDir/repo")
clean.doLast {
delete publishDir
}
if (version.endsWith("SNAPSHOT")) {
publishing.repositories {
mavenLocal()
}
} else {
publishing.repositories {
maven {
url "$buildDir/repo"
}
}
publish.doLast {
publishDir.eachFileRecurse {
if (!it.isFile()) {
return
}
// Remove junk files
if (it.name.contains(".xml") || it.name.contains(".md5")) {
it.delete()
}
}
}
}
-1
View File
@@ -1 +0,0 @@
majorMinor: 1.1
Binary file not shown.
+2 -2
View File
@@ -1,6 +1,6 @@
#Sat Oct 06 16:17:40 EEST 2018 #Sun Aug 11 12:15:09 CST 2024
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip
Vendored Executable → Regular
+174 -112
View File
@@ -1,78 +1,129 @@
#!/usr/bin/env sh #!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS="" DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
@@ -89,84 +140,95 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD=$( ulimit -H -n ) ||
MAX_FD="$MAX_FD_LIMIT" warn "Could not query maximum file descriptor limit"
fi esac
ulimit -n $MAX_FD case $MAX_FD in #(
if [ $? -ne 0 ] ; then '' | soft) :;; #(
warn "Could not set maximum file descriptor limit: $MAX_FD" *)
fi ulimit -n "$MAX_FD" ||
else warn "Could not set maximum file descriptor limit to $MAX_FD"
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=$(save "$@") # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong JAVACMD=$( cygpath --unix "$JAVACMD" )
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")" # Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"
Vendored
+24 -19
View File
@@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS= set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init if "%ERRORLEVEL%" == "0" goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init if exist "%JAVA_EXE%" goto execute
echo. echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,28 +64,14 @@ echo location of your Java installation.
goto fail goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
-1
View File
@@ -1,2 +1 @@
rootProject.name = 'brigadier' rootProject.name = 'brigadier'
enableFeaturePreview('STABLE_PUBLISHING')
@@ -6,6 +6,7 @@ package com.mojang.brigadier;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ContextChain;
import com.mojang.brigadier.context.SuggestionContext; import com.mojang.brigadier.context.SuggestionContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.Suggestions;
@@ -22,6 +23,7 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate; import java.util.function.Predicate;
@@ -214,75 +216,16 @@ public class CommandDispatcher<S> {
} }
} }
int result = 0;
int successfulForks = 0;
boolean forked = false;
boolean foundCommand = false;
final String command = parse.getReader().getString(); final String command = parse.getReader().getString();
final CommandContext<S> original = parse.getContext().build(command); final CommandContext<S> original = parse.getContext().build(command);
List<CommandContext<S>> contexts = Collections.singletonList(original);
ArrayList<CommandContext<S>> next = null;
while (contexts != null) { final Optional<ContextChain<S>> flatContext = ContextChain.tryFlatten(original);
final int size = contexts.size(); if (!flatContext.isPresent()) {
for (int i = 0; i < size; i++) {
final CommandContext<S> context = contexts.get(i);
final CommandContext<S> child = context.getChild();
if (child != null) {
forked |= context.isForked();
if (child.hasNodes()) {
foundCommand = true;
final RedirectModifier<S> modifier = context.getRedirectModifier();
if (modifier == null) {
if (next == null) {
next = new ArrayList<>(1);
}
next.add(child.copyFor(context.getSource()));
} else {
try {
final Collection<S> results = modifier.apply(context);
if (!results.isEmpty()) {
if (next == null) {
next = new ArrayList<>(results.size());
}
for (final S source : results) {
next.add(child.copyFor(source));
}
}
} catch (final CommandSyntaxException ex) {
consumer.onCommandComplete(context, false, 0);
if (!forked) {
throw ex;
}
}
}
}
} else if (context.getCommand() != null) {
foundCommand = true;
try {
final int value = context.getCommand().run(context);
result += value;
consumer.onCommandComplete(context, true, value);
successfulForks++;
} catch (final CommandSyntaxException ex) {
consumer.onCommandComplete(context, false, 0);
if (!forked) {
throw ex;
}
}
}
}
contexts = next;
next = null;
}
if (!foundCommand) {
consumer.onCommandComplete(original, false, 0); consumer.onCommandComplete(original, false, 0);
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parse.getReader()); throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parse.getReader());
} }
return forked ? successfulForks : result; return flatContext.get().executeAll(original.getSource(), consumer);
} }
/** /**
@@ -382,24 +325,41 @@ public class CommandDispatcher<S> {
context.withCommand(child.getCommand()); context.withCommand(child.getCommand());
if (reader.canRead(child.getRedirect() == null ? 2 : 1)) { if (reader.canRead(child.getRedirect() == null ? 2 : 1)) {
reader.skip(); do {
reader.skip();
} while (reader.canRead() && reader.peek() == ARGUMENT_SEPARATOR_CHAR);
if (child.getRedirect() != null) { if (child.getRedirect() != null) {
final CommandContextBuilder<S> childContext = new CommandContextBuilder<>(this, source, child.getRedirect(), reader.getCursor()); final CommandContextBuilder<S> childContext = new CommandContextBuilder<>(this, source, child.getRedirect(), reader.getCursor());
childContext.withArguments(context.getArguments());
final ParseResults<S> parse = parseNodes(child.getRedirect(), reader, childContext); final ParseResults<S> parse = parseNodes(child.getRedirect(), reader, childContext);
context.withChild(parse.getContext()); context.withChild(parse.getContext());
return new ParseResults<>(context, parse.getReader(), parse.getExceptions()); final ParseResults<S> redirect = new ParseResults<>(context, parse.getReader(), parse.getExceptions());
if (child.canUse(redirect)) {
return redirect;
}
} else { } else {
final ParseResults<S> parse = parseNodes(child, reader, context); final ParseResults<S> parse = parseNodes(child, reader, context);
if (!child.canUse(parse)) {
continue;
}
if (potentials == null) { if (potentials == null) {
potentials = new ArrayList<>(1); potentials = new ArrayList<>(1);
} }
potentials.add(parse); potentials.add(parse);
} }
} else { } else {
final CommandNode<S> redirect = child.getRedirect();
if (redirect != null && redirect.getCommand() != null) {
context.withCommand(redirect.getCommand());
}
final ParseResults<S> parse = new ParseResults<>(context, reader, Collections.emptyMap());
if (!child.canUse(parse)) {
continue;
}
if (potentials == null) { if (potentials == null) {
potentials = new ArrayList<>(1); potentials = new ArrayList<>(1);
} }
potentials.add(new ParseResults<>(context, reader, Collections.emptyMap())); potentials.add(parse);
} }
} }
@@ -601,16 +561,15 @@ public class CommandDispatcher<S> {
futures[i++] = future; futures[i++] = future;
} }
final CompletableFuture<Suggestions> result = new CompletableFuture<>(); return CompletableFuture.allOf(futures).handle((voidResult, exception) -> {
CompletableFuture.allOf(futures).thenRun(() -> {
final List<Suggestions> suggestions = new ArrayList<>(); final List<Suggestions> suggestions = new ArrayList<>();
for (final CompletableFuture<Suggestions> future : futures) { for (final CompletableFuture<Suggestions> future : futures) {
suggestions.add(future.join()); if (!future.isCompletedExceptionally()) {
suggestions.add(future.join());
}
} }
result.complete(Suggestions.merge(fullInput, suggestions)); return Suggestions.merge(fullInput, suggestions);
}); });
return result;
} }
/** /**
@@ -174,9 +174,21 @@ public class StringReader implements ImmutableStringReader {
|| c == '.' || c == '+'; || c == '.' || c == '+';
} }
public static boolean isAllowedInUnquotedStringGreedy(final char c) {
return c != ' ' && c != SYNTAX_DOUBLE_QUOTE && c != SYNTAX_SINGLE_QUOTE && c != SYNTAX_ESCAPE;
}
public String readUnquotedString() { public String readUnquotedString() {
return readUnquotedString(true);
}
public String readUnquotedStringGreedy() {
return readUnquotedString(false);
}
private String readUnquotedString(boolean asciiOnly) {
final int start = cursor; final int start = cursor;
while (canRead() && isAllowedInUnquotedString(peek())) { while (canRead() && (asciiOnly ? isAllowedInUnquotedString(peek()) : isAllowedInUnquotedStringGreedy(peek()))) {
skip(); skip();
} }
return string.substring(start, cursor); return string.substring(start, cursor);
@@ -220,6 +232,14 @@ public class StringReader implements ImmutableStringReader {
} }
public String readString() throws CommandSyntaxException { public String readString() throws CommandSyntaxException {
return readString(true);
}
public String readStringGreedy() throws CommandSyntaxException {
return readString(false);
}
private String readString(boolean asciiOnly) throws CommandSyntaxException {
if (!canRead()) { if (!canRead()) {
return ""; return "";
} }
@@ -228,7 +248,7 @@ public class StringReader implements ImmutableStringReader {
skip(); skip();
return readStringUntil(next); return readStringUntil(next);
} }
return readUnquotedString(); return readUnquotedString(asciiOnly);
} }
public boolean readBoolean() throws CommandSyntaxException { public boolean readBoolean() throws CommandSyntaxException {
@@ -16,6 +16,10 @@ import java.util.concurrent.CompletableFuture;
public interface ArgumentType<T> { public interface ArgumentType<T> {
T parse(StringReader reader) throws CommandSyntaxException; T parse(StringReader reader) throws CommandSyntaxException;
default <S> T parse(final StringReader reader, final S source) throws CommandSyntaxException {
return parse(reader);
}
default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) { default <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
return Suggestions.empty(); return Suggestions.empty();
} }
@@ -11,22 +11,32 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
public class StringArgumentType implements ArgumentType<String> { public class StringArgumentType implements ArgumentType<String> {
private final boolean greedyCharset;
private final StringType type; private final StringType type;
private StringArgumentType(final StringType type) { private StringArgumentType(final StringType type, final boolean greedyCharset) {
this.type = type; this.type = type;
this.greedyCharset = greedyCharset;
} }
public static StringArgumentType word() { public static StringArgumentType word() {
return new StringArgumentType(StringType.SINGLE_WORD); return word(false);
}
public static StringArgumentType word(boolean greedyCharset) {
return new StringArgumentType(StringType.SINGLE_WORD, greedyCharset);
} }
public static StringArgumentType string() { public static StringArgumentType string() {
return new StringArgumentType(StringType.QUOTABLE_PHRASE); return string(false);
}
public static StringArgumentType string(boolean greedyCharset) {
return new StringArgumentType(StringType.QUOTABLE_PHRASE, greedyCharset);
} }
public static StringArgumentType greedyString() { public static StringArgumentType greedyString() {
return new StringArgumentType(StringType.GREEDY_PHRASE); return new StringArgumentType(StringType.GREEDY_PHRASE, false);
} }
public static String getString(final CommandContext<?> context, final String name) { public static String getString(final CommandContext<?> context, final String name) {
@@ -37,6 +47,10 @@ public class StringArgumentType implements ArgumentType<String> {
return type; return type;
} }
public boolean hasGreedyCharset() {
return greedyCharset;
}
@Override @Override
public String parse(final StringReader reader) throws CommandSyntaxException { public String parse(final StringReader reader) throws CommandSyntaxException {
if (type == StringType.GREEDY_PHRASE) { if (type == StringType.GREEDY_PHRASE) {
@@ -44,9 +58,17 @@ public class StringArgumentType implements ArgumentType<String> {
reader.setCursor(reader.getTotalLength()); reader.setCursor(reader.getTotalLength());
return text; return text;
} else if (type == StringType.SINGLE_WORD) { } else if (type == StringType.SINGLE_WORD) {
return reader.readUnquotedString(); if (this.greedyCharset) {
return reader.readUnquotedStringGreedy();
} else {
return reader.readUnquotedString();
}
} else { } else {
return reader.readString(); if (this.greedyCharset) {
return reader.readStringGreedy();
} else {
return reader.readString();
}
} }
} }
@@ -4,6 +4,7 @@
package com.mojang.brigadier.builder; package com.mojang.brigadier.builder;
import com.mojang.brigadier.Command; import com.mojang.brigadier.Command;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.RedirectModifier; import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.SingleRedirectModifier; import com.mojang.brigadier.SingleRedirectModifier;
import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.CommandNode;
@@ -17,6 +18,7 @@ public abstract class ArgumentBuilder<S, T extends ArgumentBuilder<S, T>> {
private final RootCommandNode<S> arguments = new RootCommandNode<>(); private final RootCommandNode<S> arguments = new RootCommandNode<>();
private Command<S> command; private Command<S> command;
private Predicate<S> requirement = s -> true; private Predicate<S> requirement = s -> true;
private Predicate<ParseResults<S>> contextRequirement = parse -> true;
private CommandNode<S> target; private CommandNode<S> target;
private RedirectModifier<S> modifier = null; private RedirectModifier<S> modifier = null;
private boolean forks; private boolean forks;
@@ -61,6 +63,15 @@ public abstract class ArgumentBuilder<S, T extends ArgumentBuilder<S, T>> {
return requirement; return requirement;
} }
public T requiresWithContext(final Predicate<ParseResults<S>> requirement) {
this.contextRequirement = requirement;
return getThis();
}
public Predicate<ParseResults<S>> getContextRequirement() {
return contextRequirement;
}
public T redirect(final CommandNode<S> target) { public T redirect(final CommandNode<S> target) {
return forward(target, null, false); return forward(target, null, false);
} }
@@ -28,7 +28,7 @@ public class LiteralArgumentBuilder<S> extends ArgumentBuilder<S, LiteralArgumen
@Override @Override
public LiteralCommandNode<S> build() { 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(), getContextRequirement(), getRedirect(), getRedirectModifier(), isFork());
for (final CommandNode<S> argument : getArguments()) { for (final CommandNode<S> argument : getArguments()) {
result.addChild(argument); result.addChild(argument);
@@ -45,8 +45,7 @@ public class RequiredArgumentBuilder<S, T> extends ArgumentBuilder<S, RequiredAr
} }
public ArgumentCommandNode<S, T> build() { 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(), getContextRequirement(), getRedirect(), getRedirectModifier(), isFork(), getSuggestionsProvider());
for (final CommandNode<S> argument : getArguments()) { for (final CommandNode<S> argument : getArguments()) {
result.addChild(argument); result.addChild(argument);
} }
@@ -28,13 +28,30 @@ public class CommandContext<S> {
private final S source; private final S source;
private final String input; private final String input;
/**
* Executable part of command. Will be run only when context is last in chain.
*/
private final Command<S> command; private final Command<S> command;
private final Map<String, ParsedArgument<S, ?>> arguments; private final Map<String, ParsedArgument<S, ?>> arguments;
private final CommandNode<S> rootNode; private final CommandNode<S> rootNode;
private final List<ParsedCommandNode<S>> nodes; private final List<ParsedCommandNode<S>> nodes;
private final StringRange range; private final StringRange range;
private final CommandContext<S> child; private final CommandContext<S> child;
/**
* Modifier of source. Will be run only when context has children (i.e. is not last in chain).
*/
private final RedirectModifier<S> modifier; private final RedirectModifier<S> modifier;
/**
* Special modifier for running this context and children.
* Only relevant if it's not last in chain.
* <br/>
*
* Effects:
* <ul>
* <li>Exceptions from {@link #command} or {@link #modifier} will be ignored</li>
* <li>Result of command will be number of elements run by element in chain (instead of sum of {@link #command} results</li>
* </ul>
*/
private final boolean forks; private final 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) { 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) {
@@ -141,6 +158,10 @@ public class CommandContext<S> {
return nodes; return nodes;
} }
public Map<String, ParsedArgument<S, ?>> getArguments() {
return arguments;
}
public boolean hasNodes() { public boolean hasNodes() {
return !nodes.isEmpty(); return !nodes.isEmpty();
} }
@@ -50,6 +50,11 @@ public class CommandContextBuilder<S> {
return this; return this;
} }
public CommandContextBuilder<S> withArguments(Map<String, ParsedArgument<S, ?>> arguments) {
this.arguments.putAll(arguments);
return this;
}
public Map<String, ParsedArgument<S, ?>> getArguments() { public Map<String, ParsedArgument<S, ?>> getArguments() {
return arguments; return arguments;
} }
@@ -0,0 +1,142 @@
package com.mojang.brigadier.context;
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.ResultConsumer;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public class ContextChain<S> {
// TODO ideally those two would have separate types, but modifiers and executables expect full context
private final List<CommandContext<S>> modifiers;
private final CommandContext<S> executable;
private ContextChain<S> nextStageCache = null;
public ContextChain(final List<CommandContext<S>> modifiers, final CommandContext<S> executable) {
if (executable.getCommand() == null) {
throw new IllegalArgumentException("Last command in chain must be executable");
}
this.modifiers = modifiers;
this.executable = executable;
}
public static <S> Optional<ContextChain<S>> tryFlatten(final CommandContext<S> rootContext) {
final List<CommandContext<S>> modifiers = new ArrayList<>();
CommandContext<S> current = rootContext;
while (true) {
final CommandContext<S> child = current.getChild();
if (child == null) {
// Last entry must be executable command
if (current.getCommand() == null) {
return Optional.empty();
}
return Optional.of(new ContextChain<>(modifiers, current));
}
modifiers.add(current);
current = child;
}
}
public static <S> Collection<S> runModifier(final CommandContext<S> modifier, final S source, final ResultConsumer<S> resultConsumer, final boolean forkedMode) throws CommandSyntaxException {
final RedirectModifier<S> sourceModifier = modifier.getRedirectModifier();
// Note: source currently in context is irrelevant at this point, since we might have updated it in one of earlier stages
if (sourceModifier == null) {
// Simple redirect, just propagate source to next node
return Collections.singleton(source);
}
final CommandContext<S> contextToUse = modifier.copyFor(source);
try {
return sourceModifier.apply(contextToUse);
} catch (final CommandSyntaxException ex) {
resultConsumer.onCommandComplete(contextToUse, false, 0);
if (forkedMode) {
return Collections.emptyList();
}
throw ex;
}
}
public static <S> int runExecutable(final CommandContext<S> executable, final S source, final ResultConsumer<S> resultConsumer, final boolean forkedMode) throws CommandSyntaxException {
final CommandContext<S> contextToUse = executable.copyFor(source);
try {
final int result = executable.getCommand().run(contextToUse);
resultConsumer.onCommandComplete(contextToUse, true, result);
return forkedMode ? 1 : result;
} catch (final CommandSyntaxException ex) {
resultConsumer.onCommandComplete(contextToUse, false, 0);
if (forkedMode) {
return 0;
}
throw ex;
}
}
public int executeAll(final S source, final ResultConsumer<S> resultConsumer) throws CommandSyntaxException {
if (modifiers.isEmpty()) {
// Fast path - just a single stage
return runExecutable(executable, source, resultConsumer, false);
}
boolean forkedMode = false;
List<S> currentSources = Collections.singletonList(source);
for (final CommandContext<S> modifier : modifiers) {
forkedMode |= modifier.isForked();
List<S> nextSources = new ArrayList<>();
for (final S sourceToRun : currentSources) {
nextSources.addAll(runModifier(modifier, sourceToRun, resultConsumer, forkedMode));
}
if (nextSources.isEmpty()) {
return 0;
}
currentSources = nextSources;
}
int result = 0;
for (final S executionSource : currentSources) {
result += runExecutable(executable, executionSource, resultConsumer, forkedMode);
}
return result;
}
public Stage getStage() {
return modifiers.isEmpty() ? Stage.EXECUTE : Stage.MODIFY;
}
public CommandContext<S> getTopContext() {
if (modifiers.isEmpty()) {
return executable;
}
return modifiers.get(0);
}
public ContextChain<S> nextStage() {
final int modifierCount = modifiers.size();
if (modifierCount == 0) {
return null;
}
if (nextStageCache == null) {
nextStageCache = new ContextChain<>(modifiers.subList(1, modifierCount), executable);
}
return nextStageCache;
}
public enum Stage {
MODIFY,
EXECUTE,
}
}
@@ -4,6 +4,7 @@
package com.mojang.brigadier.tree; package com.mojang.brigadier.tree;
import com.mojang.brigadier.Command; import com.mojang.brigadier.Command;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.RedirectModifier; import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.StringReader; import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.ArgumentType;
@@ -28,6 +29,13 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
private final ArgumentType<T> type; private final ArgumentType<T> type;
private final SuggestionProvider<S> customSuggestions; private final SuggestionProvider<S> customSuggestions;
public ArgumentCommandNode(final String name, final ArgumentType<T> type, final Command<S> command, final Predicate<S> requirement, final Predicate<ParseResults<S>> contextRequirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks, final SuggestionProvider<S> customSuggestions) {
super(command, requirement, contextRequirement, redirect, modifier, forks);
this.name = name;
this.type = type;
this.customSuggestions = 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) { 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); super(command, requirement, redirect, modifier, forks);
this.name = name; this.name = name;
@@ -56,7 +64,7 @@ public class ArgumentCommandNode<S, T> extends CommandNode<S> {
@Override @Override
public void parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException { public void parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandSyntaxException {
final int start = reader.getCursor(); final int start = reader.getCursor();
final T result = type.parse(reader); final T result = type.parse(reader, contextBuilder.getSource());
final ParsedArgument<S, T> parsed = new ParsedArgument<>(start, reader.getCursor(), result); final ParsedArgument<S, T> parsed = new ParsedArgument<>(start, reader.getCursor(), result);
contextBuilder.withArgument(name, parsed); contextBuilder.withArgument(name, parsed);
@@ -5,6 +5,7 @@ package com.mojang.brigadier.tree;
import com.mojang.brigadier.AmbiguityConsumer; import com.mojang.brigadier.AmbiguityConsumer;
import com.mojang.brigadier.Command; import com.mojang.brigadier.Command;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.RedirectModifier; import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.StringReader; import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.ArgumentBuilder;
@@ -28,6 +29,7 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
private final Map<String, LiteralCommandNode<S>> literals = new LinkedHashMap<>(); private final Map<String, LiteralCommandNode<S>> literals = new LinkedHashMap<>();
private final Map<String, ArgumentCommandNode<S, ?>> arguments = new LinkedHashMap<>(); private final Map<String, ArgumentCommandNode<S, ?>> arguments = new LinkedHashMap<>();
private final Predicate<S> requirement; private final Predicate<S> requirement;
private final Predicate<ParseResults<S>> contextRequirement;
private final CommandNode<S> redirect; private final CommandNode<S> redirect;
private final RedirectModifier<S> modifier; private final RedirectModifier<S> modifier;
private final boolean forks; private final boolean forks;
@@ -36,6 +38,16 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
protected CommandNode(final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks) { protected CommandNode(final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks) {
this.command = command; this.command = command;
this.requirement = requirement; this.requirement = requirement;
this.contextRequirement = parse -> true;
this.redirect = redirect;
this.modifier = modifier;
this.forks = forks;
}
protected CommandNode(final Command<S> command, final Predicate<S> requirement, final Predicate<ParseResults<S>> contextRequirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks) {
this.command = command;
this.requirement = requirement;
this.contextRequirement = contextRequirement;
this.redirect = redirect; this.redirect = redirect;
this.modifier = modifier; this.modifier = modifier;
this.forks = forks; this.forks = forks;
@@ -65,6 +77,10 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
return requirement.test(source); return requirement.test(source);
} }
public boolean canUse(final ParseResults<S> parse) {
return contextRequirement.test(parse);
}
public void addChild(final CommandNode<S> node) { public void addChild(final CommandNode<S> node) {
if (node instanceof RootCommandNode) { if (node instanceof RootCommandNode) {
throw new UnsupportedOperationException("Cannot add a RootCommandNode as a child to any other CommandNode"); throw new UnsupportedOperationException("Cannot add a RootCommandNode as a child to any other CommandNode");
@@ -138,6 +154,10 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
return requirement; return requirement;
} }
public Predicate<ParseResults<S>> getContextRequirement() {
return contextRequirement;
}
public abstract String getName(); public abstract String getName();
public abstract String getUsageText(); public abstract String getUsageText();
@@ -4,6 +4,7 @@
package com.mojang.brigadier.tree; package com.mojang.brigadier.tree;
import com.mojang.brigadier.Command; import com.mojang.brigadier.Command;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.RedirectModifier; import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.StringReader; import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
@@ -30,6 +31,12 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
this.literalLowerCase = literal.toLowerCase(Locale.ROOT); this.literalLowerCase = literal.toLowerCase(Locale.ROOT);
} }
public LiteralCommandNode(final String literal, final Command<S> command, final Predicate<S> requirement, final Predicate<ParseResults<S>> contextRequirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks) {
super(command, requirement, contextRequirement, redirect, modifier, forks);
this.literal = literal;
this.literalLowerCase = literal.toLowerCase(Locale.ROOT);
}
public String getLiteral() { public String getLiteral() {
return literal; return literal;
} }
@@ -4,33 +4,43 @@
package com.mojang.brigadier; package com.mojang.brigadier;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder; import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import org.hamcrest.CustomMatcher;
import org.hamcrest.Matcher;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import static com.mojang.brigadier.arguments.IntegerArgumentType.getInteger;
import static com.mojang.brigadier.arguments.IntegerArgumentType.integer; import static com.mojang.brigadier.arguments.IntegerArgumentType.integer;
import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal; import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal;
import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument; import static com.mojang.brigadier.builder.RequiredArgumentBuilder.argument;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class CommandDispatcherTest { public class CommandDispatcherTest {
@@ -39,6 +49,8 @@ public class CommandDispatcherTest {
private Command<Object> command; private Command<Object> command;
@Mock @Mock
private Object source; private Object source;
@Mock
private ResultConsumer<Object> consumer;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@@ -82,7 +94,7 @@ public class CommandDispatcherTest {
} }
@Test @Test
public void testExecuteUnknownCommand() throws Exception { public void testExecuteUnknownCommand() {
subject.register(literal("bar")); subject.register(literal("bar"));
subject.register(literal("baz")); subject.register(literal("baz"));
@@ -96,7 +108,7 @@ public class CommandDispatcherTest {
} }
@Test @Test
public void testExecuteImpermissibleCommand() throws Exception { public void testExecuteImpermissibleCommand() {
subject.register(literal("foo").requires(s -> false)); subject.register(literal("foo").requires(s -> false));
try { try {
@@ -109,7 +121,7 @@ public class CommandDispatcherTest {
} }
@Test @Test
public void testExecuteEmptyCommand() throws Exception { public void testExecuteEmptyCommand() {
subject.register(literal("")); subject.register(literal(""));
try { try {
@@ -122,7 +134,7 @@ public class CommandDispatcherTest {
} }
@Test @Test
public void testExecuteUnknownSubcommand() throws Exception { public void testExecuteUnknownSubcommand() {
subject.register(literal("foo").executes(command)); subject.register(literal("foo").executes(command));
try { try {
@@ -135,7 +147,7 @@ public class CommandDispatcherTest {
} }
@Test @Test
public void testExecuteIncorrectLiteral() throws Exception { public void testExecuteIncorrectLiteral() {
subject.register(literal("foo").executes(command).then(literal("bar"))); subject.register(literal("foo").executes(command).then(literal("bar")));
try { try {
@@ -148,7 +160,7 @@ public class CommandDispatcherTest {
} }
@Test @Test
public void testExecuteAmbiguousIncorrectArgument() throws Exception { public void testExecuteAmbiguousIncorrectArgument() {
subject.register( subject.register(
literal("foo").executes(command) literal("foo").executes(command)
.then(literal("bar")) .then(literal("bar"))
@@ -182,9 +194,8 @@ public class CommandDispatcherTest {
verify(subCommand).run(any(CommandContext.class)); verify(subCommand).run(any(CommandContext.class));
} }
@SuppressWarnings("unchecked")
@Test @Test
public void testParseIncompleteLiteral() throws Exception { public void testParseIncompleteLiteral() {
subject.register(literal("foo").then(literal("bar").executes(command))); subject.register(literal("foo").then(literal("bar").executes(command)));
final ParseResults<Object> parse = subject.parse("foo ", source); final ParseResults<Object> parse = subject.parse("foo ", source);
@@ -192,9 +203,8 @@ public class CommandDispatcherTest {
assertThat(parse.getContext().getNodes().size(), is(1)); assertThat(parse.getContext().getNodes().size(), is(1));
} }
@SuppressWarnings("unchecked")
@Test @Test
public void testParseIncompleteArgument() throws Exception { public void testParseIncompleteArgument() {
subject.register(literal("foo").then(argument("bar", integer()).executes(command))); subject.register(literal("foo").then(argument("bar", integer()).executes(command)));
final ParseResults<Object> parse = subject.parse("foo ", source); final ParseResults<Object> parse = subject.parse("foo ", source);
@@ -202,6 +212,16 @@ public class CommandDispatcherTest {
assertThat(parse.getContext().getNodes().size(), is(1)); assertThat(parse.getContext().getNodes().size(), is(1));
} }
@Test
public void testParseChildlessRedirect() throws Exception {
final CommandNode<Object> target = subject.register(literal("foo").executes(command));
final CommandNode<Object> redirect = subject.register(literal("redirect").redirect(target));
final ParseResults<Object> parse = subject.parse("redirect", source);
assertThat(parse.getContext().getCommand(), equalTo(target.getCommand()));
assertThat(parse.getContext().getNodes().get(0).getNode(), equalTo(redirect));
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void testExecuteAmbiguiousParentSubcommand() throws Exception { public void testExecuteAmbiguiousParentSubcommand() throws Exception {
@@ -291,6 +311,49 @@ public class CommandDispatcherTest {
verify(command).run(any(CommandContext.class)); verify(command).run(any(CommandContext.class));
} }
@Test
public void testCorrectExecuteContextAfterRedirect() throws Exception {
final CommandDispatcher<Integer> subject = new CommandDispatcher<>();
final RootCommandNode<Integer> root = subject.getRoot();
final LiteralArgumentBuilder<Integer> add = literal("add");
final LiteralArgumentBuilder<Integer> blank = literal("blank");
final RequiredArgumentBuilder<Integer, Integer> addArg = argument("value", integer());
final LiteralArgumentBuilder<Integer> run = literal("run");
subject.register(add.then(addArg.redirect(root, c -> c.getSource() + getInteger(c, "value"))));
subject.register(blank.redirect(root));
subject.register(run.executes(CommandContext::getSource));
assertThat(subject.execute("run", 0), is(0));
assertThat(subject.execute("run", 1), is(1));
assertThat(subject.execute("add 5 run", 1), is(1 + 5));
assertThat(subject.execute("add 5 add 6 run", 2), is(2 + 5 + 6));
assertThat(subject.execute("add 5 blank run", 1), is(1 + 5));
assertThat(subject.execute("blank add 5 run", 1), is(1 + 5));
assertThat(subject.execute("add 5 blank add 6 run", 2), is(2 + 5 + 6));
assertThat(subject.execute("add 5 blank blank add 6 run", 2), is(2 + 5 + 6));
}
@Test
public void testSharedRedirectAndExecuteNodes() throws CommandSyntaxException {
final CommandDispatcher<Integer> subject = new CommandDispatcher<>();
final RootCommandNode<Integer> root = subject.getRoot();
final LiteralArgumentBuilder<Integer> add = literal("add");
final RequiredArgumentBuilder<Integer, Integer> addArg = argument("value", integer());
subject.register(add.then(
addArg
.redirect(root, c -> c.getSource() + getInteger(c, "value"))
.executes(CommandContext::getSource)
));
assertThat(subject.execute("add 5", 1), is(1));
assertThat(subject.execute("add 5 add 6", 1), is(1 + 5));
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void testExecuteRedirected() throws Exception { public void testExecuteRedirected() throws Exception {
@@ -327,7 +390,50 @@ public class CommandDispatcherTest {
} }
@Test @Test
public void testExecuteOrphanedSubcommand() throws Exception { 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)));
subject.register(literal("baz").redirect(foo));
try {
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() throws CommandSyntaxException {
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();
subject.register(literal("baz").fork(foo, emptyModifier));
int result = subject.execute("baz bar 100", source);
assertThat(result, is(0)); // No commands executed, so result is 0
}
@Test
public void testRedirectPreservesPreviousArguments() throws CommandSyntaxException {
final LiteralCommandNode<Object> ending = literal("ending")
.executes(context -> context.getArgument("number", int.class)).build();
final ArgumentCommandNode<Object, Integer> lowNumber = argument("number", integer(1, 10))
.then(ending).build();
final ArgumentCommandNode<Object, Integer> highNumber = argument("number", integer(11, 20))
.redirect(lowNumber).build();
subject.register(literal("base")
.then(literal("low").then(lowNumber))
.then(literal("high").then(highNumber)));
assertThat(subject.execute("base low 5 ending", source), is(5));
assertThat(subject.execute("base high 15 ending", source), is(15));
}
@Test
public void testExecuteOrphanedSubcommand() {
subject.register(literal("foo").then( subject.register(literal("foo").then(
argument("bar", integer()) argument("bar", integer())
).executes(command)); ).executes(command));
@@ -341,6 +447,7 @@ public class CommandDispatcherTest {
} }
} }
@SuppressWarnings("unchecked")
@Test @Test
public void testExecute_invalidOther() throws Exception { public void testExecute_invalidOther() throws Exception {
final Command<Object> wrongCommand = mock(Command.class); final Command<Object> wrongCommand = mock(Command.class);
@@ -353,7 +460,7 @@ public class CommandDispatcherTest {
} }
@Test @Test
public void parse_noSpaceSeparator() throws Exception { public void parse_noSpaceSeparator() {
subject.register(literal("foo").then(argument("bar", integer()).executes(command))); subject.register(literal("foo").then(argument("bar", integer()).executes(command)));
try { try {
@@ -365,8 +472,17 @@ public class CommandDispatcherTest {
} }
} }
@SuppressWarnings("unchecked")
@Test @Test
public void testExecuteInvalidSubcommand() throws Exception { public void parse_multipleSpaceSeparator() throws Exception {
subject.register(literal("foo").then(literal("bar").executes(command)));
assertThat(subject.execute("foo bar", source), is(42));
verify(command).run(any(CommandContext.class));
}
@Test
public void testExecuteInvalidSubcommand() {
subject.register(literal("foo").then( subject.register(literal("foo").then(
argument("bar", integer()) argument("bar", integer())
).executes(command)); ).executes(command));
@@ -400,4 +516,187 @@ public class CommandDispatcherTest {
public void testFindNodeDoesntExist() { public void testFindNodeDoesntExist() {
assertThat(subject.findNode(Lists.newArrayList("foo", "bar")), is(nullValue())); assertThat(subject.findNode(Lists.newArrayList("foo", "bar")), is(nullValue()));
} }
@SuppressWarnings("unchecked")
@Test
public void testCompletionWithErroredFutureReturnsCompletedFuture() {
final LiteralCommandNode<Object> bar = literal("bar").build();
final LiteralCommandNode<Object> baz = mock(LiteralCommandNode.class);
when(baz.getLiteral()).thenReturn("baz");
when(baz.listSuggestions(any(), any())).thenAnswer(x -> {
final CompletableFuture<Suggestions> future = new CompletableFuture<>();
future.completeExceptionally(new IllegalArgumentException());
return future;
});
subject.register(literal("foo").then(bar).then(baz));
final ParseResults<Object> parseResults = subject.parse("foo b", source);
final Suggestions suggestions = subject.getCompletionSuggestions(parseResults).join();
final Collection<String> suggestionCollection = suggestions.getList().stream().map(Suggestion::getText).collect(Collectors.toList());
assertThat(Lists.newArrayList("bar"), is(suggestionCollection));
}
@Test
public void testResultConsumerInNonErrorRun() throws CommandSyntaxException {
subject.setConsumer(consumer);
subject.register(literal("foo").executes(command));
when(command.run(any())).thenReturn(5);
assertThat(subject.execute("foo", source), is(5));
verify(consumer).onCommandComplete(any(), eq(true), eq(5));
verifyNoMoreInteractions(consumer);
}
@Test
public void testResultConsumerInForkedNonErrorRun() throws CommandSyntaxException {
subject.setConsumer(consumer);
subject.register(literal("foo").executes(c -> (Integer)(c.getSource())));
final Object[] contexts = new Object[] {9, 10, 11};
subject.register(literal("repeat").fork(subject.getRoot(), context -> Arrays.asList(contexts)));
assertThat(subject.execute("repeat foo", source), is(contexts.length));
verify(consumer).onCommandComplete(argThat(contextSourceMatches(contexts[0])), eq(true), eq(9));
verify(consumer).onCommandComplete(argThat(contextSourceMatches(contexts[1])), eq(true), eq(10));
verify(consumer).onCommandComplete(argThat(contextSourceMatches(contexts[2])), eq(true), eq(11));
verifyNoMoreInteractions(consumer);
}
@Test
public void testExceptionInNonForkedCommand() throws CommandSyntaxException {
subject.setConsumer(consumer);
subject.register(literal("crash").executes(command));
final CommandSyntaxException exception = CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerExpectedBool().create();
when(command.run(any())).thenThrow(exception);
try {
subject.execute("crash", source);
fail();
} catch (final CommandSyntaxException ex) {
assertThat(ex, is(exception));
}
verify(consumer).onCommandComplete(any(), eq(false), eq(0));
verifyNoMoreInteractions(consumer);
}
@Test
public void testExceptionInNonForkedRedirectedCommand() throws CommandSyntaxException {
subject.setConsumer(consumer);
subject.register(literal("crash").executes(command));
subject.register(literal("redirect").redirect(subject.getRoot()));
final CommandSyntaxException exception = CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerExpectedBool().create();
when(command.run(any())).thenThrow(exception);
try {
subject.execute("redirect crash", source);
fail();
} catch (final CommandSyntaxException ex) {
assertThat(ex, is(exception));
}
verify(consumer).onCommandComplete(any(), eq(false), eq(0));
verifyNoMoreInteractions(consumer);
}
@Test
public void testExceptionInForkedRedirectedCommand() throws CommandSyntaxException {
subject.setConsumer(consumer);
subject.register(literal("crash").executes(command));
subject.register(literal("redirect").fork(subject.getRoot(), Collections::singleton));
final CommandSyntaxException exception = CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerExpectedBool().create();
when(command.run(any())).thenThrow(exception);
assertThat(subject.execute("redirect crash", source), is(0));
verify(consumer).onCommandComplete(any(), eq(false), eq(0));
verifyNoMoreInteractions(consumer);
}
@Test
public void testExceptionInNonForkedRedirect() throws CommandSyntaxException {
final CommandSyntaxException exception = CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerExpectedBool().create();
subject.setConsumer(consumer);
subject.register(literal("noop").executes(command));
subject.register(literal("redirect").redirect(subject.getRoot(), context -> {
throw exception;
}));
when(command.run(any())).thenReturn(3);
try {
subject.execute("redirect noop", source);
fail();
} catch (final CommandSyntaxException ex) {
assertThat(ex, is(exception));
}
verifyZeroInteractions(command);
verify(consumer).onCommandComplete(any(), eq(false), eq(0));
verifyNoMoreInteractions(consumer);
}
@Test
public void testExceptionInForkedRedirect() throws CommandSyntaxException {
final CommandSyntaxException exception = CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerExpectedBool().create();
subject.setConsumer(consumer);
subject.register(literal("noop").executes(command));
subject.register(literal("redirect").fork(subject.getRoot(), context -> {
throw exception;
}));
when(command.run(any())).thenReturn(3);
assertThat(subject.execute("redirect noop", source), is(0));
verifyZeroInteractions(command);
verify(consumer).onCommandComplete(any(), eq(false), eq(0));
verifyNoMoreInteractions(consumer);
}
@Test
public void testPartialExceptionInForkedRedirect() throws CommandSyntaxException {
final CommandSyntaxException exception = CommandSyntaxException.BUILT_IN_EXCEPTIONS.readerExpectedBool().create();
final Object otherSource = new Object();
final Object rejectedSource = new Object();
subject.setConsumer(consumer);
subject.register(literal("run").executes(command));
subject.register(literal("split").fork(subject.getRoot(), context -> Arrays.asList(source, rejectedSource, otherSource)));
subject.register(literal("filter").fork(subject.getRoot(), context -> {
final Object currentSource = context.getSource();
if (currentSource == rejectedSource) {
throw exception;
}
return Collections.singleton(currentSource);
}));
when(command.run(any())).thenReturn(3);
assertThat(subject.execute("split filter run", source), is(2));
verify(command).run(argThat(contextSourceMatches(source)));
verify(command).run(argThat(contextSourceMatches(otherSource)));
verifyNoMoreInteractions(command);
verify(consumer).onCommandComplete(argThat(contextSourceMatches(rejectedSource)), eq(false), eq(0));
verify(consumer).onCommandComplete(argThat(contextSourceMatches(source)), eq(true), eq(3));
verify(consumer).onCommandComplete(argThat(contextSourceMatches(otherSource)), eq(true), eq(3));
verifyNoMoreInteractions(consumer);
}
public static Matcher<CommandContext<Object>> contextSourceMatches(final Object source) {
return new CustomMatcher<CommandContext<Object>>("source " + source) {
@Override
public boolean matches(Object object) {
return (object instanceof CommandContext) && ((CommandContext<?>) object).getSource() == source;
}
};
}
} }
@@ -253,6 +253,26 @@ public class CommandSuggestionsTest {
assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.at(33), "loop")))); assertThat(result.getList(), equalTo(Lists.newArrayList(new Suggestion(StringRange.at(33), "loop"))));
} }
@Test
public void getCompletionSuggestions_redirectPreservesArguments() throws Exception {
subject.register(literal("command")
.then(
argument("first", integer())
.then(
argument("second", integer())
.suggests((context, builder) -> {
builder.suggest(String.valueOf(context.getLastChild().getArgument("first", int.class) + 1));
return builder.buildFuture();
})
)
));
subject.register(literal("redirect").redirect(subject.getRoot()));
testSuggestions("command 1 ", 10, StringRange.at(10), "2");
testSuggestions("redirect command 1 ", 19, StringRange.at(19), "2");
testSuggestions("redirect redirect command 1 ", 28, StringRange.at(28), "2");
}
@Test @Test
public void getCompletionSuggestions_execute_simulation() throws Exception { public void getCompletionSuggestions_execute_simulation() throws Exception {
final LiteralCommandNode<Object> execute = subject.register(literal("execute")); final LiteralCommandNode<Object> execute = subject.register(literal("execute"));
@@ -132,6 +132,30 @@ public class StringReaderTest {
assertThat(reader.getRemaining(), equalTo(" world")); assertThat(reader.getRemaining(), equalTo(" world"));
} }
@Test
public void readUnquotedString_strictCharset() throws Exception {
final StringReader reader = new StringReader("1+1=2 2+2=4");
assertThat(reader.readString(), equalTo("1+1"));
assertThat(reader.getRead(), equalTo("1+1"));
assertThat(reader.getRemaining(), equalTo("=2 2+2=4"));
// Should not be able to read further -- as invalid character is present
assertThat(reader.readString(), equalTo(""));
}
@Test
public void readUnquotedString_strictCharsetQuoted() throws Exception {
final StringReader reader = new StringReader("\"1+1=2\" \"2+2=4\"");
assertThat(reader.readString(), equalTo("1+1=2"));
assertThat(reader.getRead(), equalTo("\"1+1=2\""));
assertThat(reader.getRemaining(), equalTo(" \"2+2=4\""));
reader.skipWhitespace();
assertThat(reader.readString(), equalTo("2+2=4"));
assertThat(reader.getRead(), equalTo("\"1+1=2\" \"2+2=4\""));
}
@Test @Test
public void readUnquotedString_empty() throws Exception { public void readUnquotedString_empty() throws Exception {
final StringReader reader = new StringReader(""); final StringReader reader = new StringReader("");
@@ -148,6 +172,40 @@ public class StringReaderTest {
assertThat(reader.getRemaining(), equalTo(" hello world")); assertThat(reader.getRemaining(), equalTo(" hello world"));
} }
@Test
public void readUnquotedStringGreedy() throws Exception {
final StringReader reader = new StringReader("hello world");
assertThat(reader.readStringGreedy(), equalTo("hello"));
assertThat(reader.getRead(), equalTo("hello"));
assertThat(reader.getRemaining(), equalTo(" world"));
}
@Test
public void readUnquotedStringGreedy_strictCharset() throws Exception {
final StringReader reader = new StringReader("1+1=2 2+2=4");
assertThat(reader.readStringGreedy(), equalTo("1+1=2"));
assertThat(reader.getRead(), equalTo("1+1=2"));
assertThat(reader.getRemaining(), equalTo(" 2+2=4"));
reader.skipWhitespace();
assertThat(reader.readStringGreedy(), equalTo("2+2=4"));
}
@Test
public void readUnquotedStringGreedy_empty() throws Exception {
final StringReader reader = new StringReader("");
assertThat(reader.readUnquotedStringGreedy(), equalTo(""));
assertThat(reader.getRead(), equalTo(""));
assertThat(reader.getRemaining(), equalTo(""));
}
@Test
public void readUnquotedStringGreedy_empty_withRemaining() throws Exception {
final StringReader reader = new StringReader(" hello world");
assertThat(reader.readUnquotedStringGreedy(), equalTo(""));
assertThat(reader.getRead(), equalTo(""));
assertThat(reader.getRemaining(), equalTo(" hello world"));
}
@Test @Test
public void readQuotedString() throws Exception { public void readQuotedString() throws Exception {
final StringReader reader = new StringReader("\"hello world\""); final StringReader reader = new StringReader("\"hello world\"");
@@ -27,6 +27,22 @@ public class StringArgumentTypeTest {
@Mock @Mock
private CommandContextBuilder<Object> context; private CommandContextBuilder<Object> context;
@Test
public void testParseWord_greedyCharset() throws Exception {
final StringReader reader = mock(StringReader.class);
when(reader.readUnquotedStringGreedy()).thenReturn("1+1=2");
assertThat(word(true).parse(reader), equalTo("1+1=2"));
verify(reader).readUnquotedStringGreedy();
}
@Test
public void testParseString_greedyCharset() throws Exception {
final StringReader reader = mock(StringReader.class);
when(reader.readStringGreedy()).thenReturn("1+1=2 2+2=4");
assertThat(string(true).parse(reader), equalTo("1+1=2 2+2=4"));
verify(reader).readStringGreedy();
}
@Test @Test
public void testParseWord() throws Exception { public void testParseWord() throws Exception {
final StringReader reader = mock(StringReader.class); final StringReader reader = mock(StringReader.class);
@@ -7,13 +7,15 @@ import com.google.common.testing.EqualsTester;
import com.mojang.brigadier.Command; import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import java.util.HashMap;
import java.util.Map;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@@ -51,6 +53,16 @@ public class CommandContextTest {
assertThat(context.getArgument("foo", int.class), is(123)); assertThat(context.getArgument("foo", int.class), is(123));
} }
@Test
public void testGetArguments() throws Exception {
Map<String, ParsedArgument<Object, ?>> arguments = new HashMap<>();
arguments.put("foo", new ParsedArgument<>(0, 1, 123));
arguments.put("bar", new ParsedArgument<>(0, 1, "123"));
final CommandContext<Object> context = builder.withArguments(arguments).build("123");
assertThat(context.getArgument("foo", int.class), is(123));
assertThat(context.getArgument("bar", String.class), is("123"));
}
@Test @Test
public void testSource() throws Exception { public void testSource() throws Exception {
assertThat(builder.build("").getSource(), is(source)); assertThat(builder.build("").getSource(), is(source));
@@ -0,0 +1,137 @@
package com.mojang.brigadier.context;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.CommandDispatcherTest;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.ResultConsumer;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.Optional;
import static com.mojang.brigadier.builder.LiteralArgumentBuilder.literal;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class ContextChainTest {
@Test
@SuppressWarnings("unchecked")
public void testExecuteAllForSingleCommand() throws CommandSyntaxException {
final ResultConsumer<Object> consumer = mock(ResultConsumer.class);
final Command<Object> command = mock(Command.class);
when(command.run(any())).thenReturn(4);
final CommandDispatcher<Object> dispatcher = new CommandDispatcher<>();
dispatcher.register(literal("foo").executes(command));
final Object source = "compile_source";
final ParseResults<Object> result = dispatcher.parse("foo", source);
final CommandContext<Object> topContext = result.getContext().build("foo");
final ContextChain<Object> chain = ContextChain.tryFlatten(topContext).orElseThrow(AssertionError::new);
final Object runtimeSource = "runtime_source";
assertThat(chain.executeAll(runtimeSource, consumer), is(4));
verify(command).run(argThat(CommandDispatcherTest.contextSourceMatches(runtimeSource)));
verify(consumer).onCommandComplete(argThat(CommandDispatcherTest.contextSourceMatches(runtimeSource)), eq(true), eq(4));
verifyNoMoreInteractions(consumer);
}
@Test
@SuppressWarnings("unchecked")
public void testExecuteAllForRedirectedCommand() throws CommandSyntaxException {
final ResultConsumer<Object> consumer = mock(ResultConsumer.class);
final Command<Object> command = mock(Command.class);
when(command.run(any())).thenReturn(4);
final Object redirectedSource = "redirected_source";
final CommandDispatcher<Object> dispatcher = new CommandDispatcher<>();
dispatcher.register(literal("foo").executes(command));
dispatcher.register(literal("bar").redirect(dispatcher.getRoot(), context -> redirectedSource));
final Object source = "compile_source";
final ParseResults<Object> result = dispatcher.parse("bar foo", source);
final CommandContext<Object> topContext = result.getContext().build("bar foo");
final ContextChain<Object> chain = ContextChain.tryFlatten(topContext).orElseThrow(AssertionError::new);
final Object runtimeSource = "runtime_source";
assertThat(chain.executeAll(runtimeSource, consumer), is(4));
verify(command).run(argThat(CommandDispatcherTest.contextSourceMatches(redirectedSource)));
verify(consumer).onCommandComplete(argThat(CommandDispatcherTest.contextSourceMatches(redirectedSource)), eq(true), eq(4));
verifyNoMoreInteractions(consumer);
}
@Test
public void testSingleStageExecution() {
final CommandDispatcher<Object> dispatcher = new CommandDispatcher<>();
dispatcher.register(literal("foo").executes(context -> 1));
final Object source = new Object();
final ParseResults<Object> result = dispatcher.parse("foo", source);
final CommandContext<Object> topContext = result.getContext().build("foo");
final ContextChain<Object> chain = ContextChain.tryFlatten(topContext).orElseThrow(AssertionError::new);
assertThat(chain.getStage(), is(ContextChain.Stage.EXECUTE));
assertThat(chain.getTopContext(), is(topContext));
assertThat(chain.nextStage(), nullValue());
}
@Test
public void testMultiStageExecution() {
final CommandDispatcher<Object> dispatcher = new CommandDispatcher<>();
dispatcher.register(literal("foo").executes(context -> 1));
dispatcher.register(literal("bar").redirect(dispatcher.getRoot()));
final Object source = new Object();
final ParseResults<Object> result = dispatcher.parse("bar bar foo", source);
final CommandContext<Object> topContext = result.getContext().build("bar bar foo");
final ContextChain<Object> stage0 = ContextChain.tryFlatten(topContext).orElseThrow(AssertionError::new);
assertThat(stage0.getStage(), is(ContextChain.Stage.MODIFY));
assertThat(stage0.getTopContext(), is(topContext));
final ContextChain<Object> stage1 = stage0.nextStage();
assertThat(stage1, notNullValue());
assertThat(stage1.getStage(), is(ContextChain.Stage.MODIFY));
assertThat(stage1.getTopContext(), is(topContext.getChild()));
final ContextChain<Object> stage2 = stage1.nextStage();
assertThat(stage2, notNullValue());
assertThat(stage2.getStage(), is(ContextChain.Stage.EXECUTE));
assertThat(stage2.getTopContext(), is(topContext.getChild().getChild()));
assertThat(stage2.nextStage(), nullValue());
}
@Test
public void testMissingExecute() {
final CommandDispatcher<Object> dispatcher = new CommandDispatcher<>();
dispatcher.register(literal("foo").executes(context -> 1));
dispatcher.register(literal("bar").redirect(dispatcher.getRoot()));
final Object source = new Object();
final ParseResults<Object> result = dispatcher.parse("bar bar", source);
final CommandContext<Object> topContext = result.getContext().build("bar bar");
assertThat(ContextChain.tryFlatten(topContext), is(Optional.empty()));
}
}