From 5a3d2875f6f761b64d08865dd118d4f94e460bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= <4096670+Citymonstret@users.noreply.github.com> Date: Sun, 4 Feb 2024 21:40:09 +0100 Subject: [PATCH] feat: 2.0.0 beta.2 docs (#29) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ---- 📚 Documentation preview 📚: https://incendocloud--29.org.readthedocs.build/en/29/ --- .editorconfig | 244 +++++++++++++- .github/workflows/gradle.yml | 30 ++ README.md | 6 + code/build.gradle.kts | 21 ++ code/gradle.properties | 5 + code/gradle/libs.versions.toml | 12 + code/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 63721 bytes code/gradle/wrapper/gradle-wrapper.properties | 7 + code/gradlew | 249 ++++++++++++++ code/gradlew.bat | 92 ++++++ code/settings.gradle.kts | 29 ++ .../cloud/snippet/AggregateParserExample.java | 43 +++ .../incendo/cloud/snippet/EitherExample.java | 29 ++ .../cloud/snippet/UUIDParseException.java | 43 +++ .../org/incendo/cloud/snippet/UUIDParser.java | 29 ++ .../MinecraftExceptionHandlerExample.java | 39 +++ .../minecraft/MinecraftHelpExample.java | 92 ++++++ docs/annotations/index.md | 137 ++++---- docs/core/index.md | 305 ++++++------------ docs/discord/discord4j.md | 25 +- docs/discord/jda5.md | 25 +- docs/discord/kord.md | 25 +- docs/kotlin/annotations.md | 13 +- docs/kotlin/coroutines.md | 13 +- docs/kotlin/extensions.md | 13 +- docs/localization/index.md | 129 ++++++++ docs/minecraft/bukkit.md | 20 +- docs/minecraft/bungee.md | 39 +-- docs/minecraft/minecraft-extras.md | 120 ++----- docs/minecraft/modded/index.md | 2 +- docs/minecraft/paper.md | 43 +-- docs/minecraft/velocity.md | 41 +-- docs/processors/confirmation.md | 16 +- docs/processors/cooldown.md | 16 +- docs/processors/requirements.md | 16 +- docs/requirements.txt | 1 + docs/spring/index.md | 10 +- main.py | 63 ++++ mkdocs.yml | 95 +++--- renovate.json | 8 +- versions.yml | 15 + 41 files changed, 1489 insertions(+), 671 deletions(-) create mode 100644 .github/workflows/gradle.yml create mode 100644 code/build.gradle.kts create mode 100644 code/gradle.properties create mode 100644 code/gradle/libs.versions.toml create mode 100644 code/gradle/wrapper/gradle-wrapper.jar create mode 100644 code/gradle/wrapper/gradle-wrapper.properties create mode 100755 code/gradlew create mode 100644 code/gradlew.bat create mode 100644 code/settings.gradle.kts create mode 100644 code/src/main/java/org/incendo/cloud/snippet/AggregateParserExample.java create mode 100644 code/src/main/java/org/incendo/cloud/snippet/EitherExample.java create mode 100644 code/src/main/java/org/incendo/cloud/snippet/UUIDParseException.java create mode 100644 code/src/main/java/org/incendo/cloud/snippet/UUIDParser.java create mode 100644 code/src/main/java/org/incendo/cloud/snippet/minecraft/MinecraftExceptionHandlerExample.java create mode 100644 code/src/main/java/org/incendo/cloud/snippet/minecraft/MinecraftHelpExample.java create mode 100644 docs/localization/index.md create mode 100644 main.py create mode 100644 versions.yml diff --git a/.editorconfig b/.editorconfig index 11e4d67..a205b23 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,4 +7,246 @@ indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true -insert_final_newline = true \ No newline at end of file +insert_final_newline = true + +[*.py] +indent_size = 4 + +[*.java] +indent_size = 2 +max_line_length = 130 +tab_width = 2 +ij_continuation_indent_size = 2 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_smart_tabs = false +ij_wrap_on_typing = true +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = true +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 1 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_at_first_column = true +ij_java_call_parameters_new_line_after_left_paren = true +ij_java_call_parameters_right_paren_on_new_line = true +ij_java_call_parameters_wrap = on_every_item +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 100000 +ij_java_class_names_in_javadoc = 1 +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_while_brace_force = always +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = true +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = false +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_enum_constants_wrap = split_into_lines +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = normal +ij_java_field_annotation_wrap = split_into_lines +ij_java_finally_on_new_line = false +ij_java_for_brace_force = always +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = true +ij_java_generate_final_parameters = true +ij_java_if_brace_force = always +ij_java_imports_layout = *,|,$* +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 0 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_at_first_column = true +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = on_every_item +ij_java_method_parameters_new_line_after_left_paren = true +ij_java_method_parameters_right_paren_on_new_line = true +ij_java_method_parameters_wrap = on_every_item +ij_java_modifier_list_wrap = false +ij_java_names_count_to_use_import_on_demand = 100000 +ij_java_new_line_after_lparen_in_record_header = false +ij_java_parameter_annotation_wrap = off +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = false +ij_java_prefer_parameters_wrap = true +ij_java_record_components_wrap = normal +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_record_header = false +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_subclass_name_suffix = Impl +ij_java_ternary_operation_signs_on_next_line = true +ij_java_ternary_operation_wrap = on_every_item +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = off +ij_java_throws_list_wrap = normal +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = always +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = true +ij_java_wrap_long_lines = false diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..f2d4cba --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,30 @@ +name: Gradle Build +on: + push: + branches: ["**"] + tags-ignore: ["**"] + pull_request: + release: + types: [published] +jobs: + build: + # Only run on PRs if the source branch is on someone else's repo + if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }} + runs-on: "ubuntu-latest" + defaults: + run: + working-directory: code + steps: + - uses: actions/checkout@v4 + - uses: gradle/wrapper-validation-action@v1 + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: 17 + - uses: gradle/gradle-build-action@v2 + with: + # allow master and *-dev branches to write caches (default is only master/main) + cache-read-only: ${{ github.ref != 'refs/heads/master' && !(endsWith(github.ref, '-dev') && startsWith(github.ref, 'refs/heads/')) }} + - name: Build + run: ./gradlew build diff --git a/README.md b/README.md index 0a54feb..d6d51b5 100644 --- a/README.md +++ b/README.md @@ -40,3 +40,9 @@ then you may run prettier using: ```shell $ npx prettier . --write ``` + +### Snippets + +The `code` directory contains a Gradle project with compiled examples. +Snippets from these docs are used in the examples using the `{{ snippet("File.java") }}` macro. +The project will be built by the CI pipeline to validate that the snippets compile. diff --git a/code/build.gradle.kts b/code/build.gradle.kts new file mode 100644 index 0000000..f883304 --- /dev/null +++ b/code/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + alias(libs.plugins.indra) +} + +dependencies { + implementation(libs.cloud.core) + implementation(libs.cloud.minecraft.extras) +} + +indra { + javaVersions { + minimumToolchain(8) + target(8) + } +} + +tasks { + withType { + options.compilerArgs.addAll(listOf("-Xlint:-processing,-classfile,-serial", "-Werror")) + } +} diff --git a/code/gradle.properties b/code/gradle.properties new file mode 100644 index 0000000..8694d9d --- /dev/null +++ b/code/gradle.properties @@ -0,0 +1,5 @@ +group=org.incendo +version=1.0.0-SNAPSHOT +description=cloud-docs snippets +org.gradle.caching=true +org.gradle.parallel=true diff --git a/code/gradle/libs.versions.toml b/code/gradle/libs.versions.toml new file mode 100644 index 0000000..beb8c46 --- /dev/null +++ b/code/gradle/libs.versions.toml @@ -0,0 +1,12 @@ +[versions] +indra = "3.1.3" + +cloud = "2.0.0-beta.2" +cloudMinecraft = "2.0.0-beta.2" + +[plugins] +indra = { id = "net.kyori.indra", version.ref = "indra" } + +[libraries] +cloud-core = { group = "org.incendo", name = "cloud-core", version.ref = "cloud" } +cloud-minecraft-extras = { group = "org.incendo", name = "cloud-minecraft-extras", version.ref = "cloudMinecraft" } diff --git a/code/gradle/wrapper/gradle-wrapper.jar b/code/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7f93135c49b765f8051ef9d0a6055ff8e46073d8 GIT binary patch literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc literal 0 HcmV?d00001 diff --git a/code/gradle/wrapper/gradle-wrapper.properties b/code/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3fa8f86 --- /dev/null +++ b/code/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/code/gradlew b/code/gradlew new file mode 100755 index 0000000..1aa94a4 --- /dev/null +++ b/code/gradlew @@ -0,0 +1,249 @@ +#!/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 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/HEAD/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 + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + 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 +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # 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 + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# 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" "$@" diff --git a/code/gradlew.bat b/code/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/code/gradlew.bat @@ -0,0 +1,92 @@ +@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 +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +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. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/code/settings.gradle.kts b/code/settings.gradle.kts new file mode 100644 index 0000000..8abe398 --- /dev/null +++ b/code/settings.gradle.kts @@ -0,0 +1,29 @@ +pluginManagement { + repositories { + gradlePluginPortal() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0" +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenCentral() + maven("https://oss.sonatype.org/content/repositories/snapshots/") { + name = "sonatypeOssSnapshots" + mavenContent { + snapshotsOnly() + } + } + maven("https://m2.dv8tion.net/releases") { + name = "dv8tion" + mavenContent { releasesOnly() } + } + } +} + +rootProject.name = "cloud-docs-snippets" + diff --git a/code/src/main/java/org/incendo/cloud/snippet/AggregateParserExample.java b/code/src/main/java/org/incendo/cloud/snippet/AggregateParserExample.java new file mode 100644 index 0000000..1645db1 --- /dev/null +++ b/code/src/main/java/org/incendo/cloud/snippet/AggregateParserExample.java @@ -0,0 +1,43 @@ +package org.incendo.cloud.snippet; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.parser.aggregate.AggregateParser; + +import static org.incendo.cloud.parser.standard.IntegerParser.integerParser; +import static org.incendo.cloud.parser.standard.StringParser.stringParser; + +/** + * Example of {@link AggregateParser}. + */ +public class AggregateParserExample { + + public void example(final Command.@NonNull Builder commandBuilder) { + // --8<-- [start:snippet] + final AggregateParser locationParser = AggregateParser + .builder() + .withComponent("world", stringParser()) + .withComponent("x", integerParser()) + .withComponent("y", integerParser()) + .withComponent("z", integerParser()) + .withMapper(Location.class, (commandContext, aggregateCommandContext) -> { + final String world = aggregateCommandContext.get("world"); + final int x = aggregateCommandContext.get("x"); + final int y = aggregateCommandContext.get("y"); + final int z = aggregateCommandContext.get("z"); + return ArgumentParseResult.successFuture(new Location(world, x, y, z)); + }).build(); + // --8<-- [end:snippet] + } + + public static final class Location { + + public Location(final String world, final int x, final int y, final int z) { + } + } + + public static final class CommandSender { + + } +} diff --git a/code/src/main/java/org/incendo/cloud/snippet/EitherExample.java b/code/src/main/java/org/incendo/cloud/snippet/EitherExample.java new file mode 100644 index 0000000..843781e --- /dev/null +++ b/code/src/main/java/org/incendo/cloud/snippet/EitherExample.java @@ -0,0 +1,29 @@ +package org.incendo.cloud.snippet; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.parser.ArgumentParser; +import org.incendo.cloud.type.Either; + +import static org.incendo.cloud.parser.standard.BooleanParser.booleanParser; +import static org.incendo.cloud.parser.standard.IntegerParser.integerParser; + +/** + * Example of {@link Either}. + */ +public class EitherExample { + + public void example(final Command.@NonNull Builder commandBuilder) { + // --8<-- [start:snippet] + commandBuilder.required("either", ArgumentParser.firstOf(integerParser(), booleanParser())) + .handler(context -> { + Either either = context.get("either"); + if (either.primary().isPresent()) { + int integer = either.primary().get(); + } else { + boolean bool = either.fallback().get(); + } + }); + // --8<-- [end:snippet] + } +} diff --git a/code/src/main/java/org/incendo/cloud/snippet/UUIDParseException.java b/code/src/main/java/org/incendo/cloud/snippet/UUIDParseException.java new file mode 100644 index 0000000..a28348d --- /dev/null +++ b/code/src/main/java/org/incendo/cloud/snippet/UUIDParseException.java @@ -0,0 +1,43 @@ +package org.incendo.cloud.snippet; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.caption.CaptionVariable; +import org.incendo.cloud.caption.StandardCaptionKeys; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.exception.parsing.ParserException; + +import java.util.Objects; + +public final class UUIDParseException extends ParserException { + + private final String input; + + public UUIDParseException(final @NonNull String input, final @NonNull CommandContext context) { + super( + UUIDParser.class, + context, + StandardCaptionKeys.ARGUMENT_PARSE_FAILURE_UUID, + CaptionVariable.of("input", input) + ); + this.input = input; + } + + public String input() { + return this.input; + } + + public boolean equals(final Object o) { + if (this == o) { + return true; + } else if (o != null && this.getClass() == o.getClass()) { + final UUIDParseException that = (UUIDParseException) o; + return this.input.equals(that.input()); + } else { + return false; + } + } + + public int hashCode() { + return Objects.hash(new Object[]{this.input}); + } +} diff --git a/code/src/main/java/org/incendo/cloud/snippet/UUIDParser.java b/code/src/main/java/org/incendo/cloud/snippet/UUIDParser.java new file mode 100644 index 0000000..62e8807 --- /dev/null +++ b/code/src/main/java/org/incendo/cloud/snippet/UUIDParser.java @@ -0,0 +1,29 @@ +package org.incendo.cloud.snippet; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.parser.ArgumentParser; + +import java.util.UUID; + +// --8<-- [start:snippet] +public class UUIDParser implements ArgumentParser { + + @Override + public @NonNull ArgumentParseResult parse( + @NonNull CommandContext context, + @NonNull CommandInput commandInput + ) { + final String input = commandInput.peekString(); // Does not remove the string from the input! + try { + final UUID uuid = UUID.fromString(input); + commandInput.readString(); // Removes the string from the input. + return ArgumentParseResult.success(uuid); + } catch (final IllegalArgumentException e) { + return ArgumentParseResult.failure(new UUIDParseException(input, context)); + } + } +} +// --8<-- [end:snippet] diff --git a/code/src/main/java/org/incendo/cloud/snippet/minecraft/MinecraftExceptionHandlerExample.java b/code/src/main/java/org/incendo/cloud/snippet/minecraft/MinecraftExceptionHandlerExample.java new file mode 100644 index 0000000..cb56726 --- /dev/null +++ b/code/src/main/java/org/incendo/cloud/snippet/minecraft/MinecraftExceptionHandlerExample.java @@ -0,0 +1,39 @@ +package org.incendo.cloud.snippet.minecraft; + +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.format.NamedTextColor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.minecraft.extras.MinecraftExceptionHandler; + +import static net.kyori.adventure.text.Component.text; + +public class MinecraftExceptionHandlerExample { + + public void nativeExceptionHandler() { + // --8<-- [start:native] + MinecraftExceptionHandler.createNative() + .decorator(component -> text() + .append(text("[Example] ", NamedTextColor.DARK_RED)) + .append(component) + .build() + ); + // --8<-- [end:native] + } + + public void completeExample(final @NonNull CommandManager manager) { + // --8<-- [start:complete] + MinecraftExceptionHandler.createNative() + .defaultHandlers() + .decorator(component -> text() + .append(text("[Example] ", NamedTextColor.DARK_RED)) + .append(component) + .build() + ).registerTo(manager); + // --8<-- [end:complete] + } + + public static final class NativeSenderType implements Audience { + + } +} diff --git a/code/src/main/java/org/incendo/cloud/snippet/minecraft/MinecraftHelpExample.java b/code/src/main/java/org/incendo/cloud/snippet/minecraft/MinecraftHelpExample.java new file mode 100644 index 0000000..89a28f8 --- /dev/null +++ b/code/src/main/java/org/incendo/cloud/snippet/minecraft/MinecraftHelpExample.java @@ -0,0 +1,92 @@ +package org.incendo.cloud.snippet.minecraft; + +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.format.NamedTextColor; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.CommandManager; +import org.incendo.cloud.component.DefaultValue; +import org.incendo.cloud.help.result.CommandEntry; +import org.incendo.cloud.minecraft.extras.AudienceProvider; +import org.incendo.cloud.minecraft.extras.MinecraftHelp; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +import java.util.stream.Collectors; + +import static org.incendo.cloud.parser.standard.StringParser.greedyStringParser; + +public class MinecraftHelpExample { + + public @NonNull MinecraftHelp nativeHelp(final @NonNull CommandManager commandManager) { + // --8<-- [start:native] + MinecraftHelp help = MinecraftHelp.builder() + .commandManager(commandManager) + .audienceProvider(AudienceProvider.nativeAudience()) + .commandPrefix("/helpcommand") + .colors(MinecraftHelp.helpColors(NamedTextColor.GREEN, NamedTextColor.RED, + NamedTextColor.AQUA, NamedTextColor.BLACK, NamedTextColor.WHITE + )) + /* other settings... */ + .build(); + // --8<-- [end:native] + return help; + } + + public void nonNativeHelp(final @NonNull CommandManager commandManager) { + // --8<-- [start:non_native] + AudienceProvider audienceProvider = SenderType::audience; + MinecraftHelp help = MinecraftHelp.builder() + .commandManager(commandManager) + .audienceProvider(audienceProvider) + .commandPrefix("/helpcommand") + .colors(MinecraftHelp.helpColors(NamedTextColor.GREEN, NamedTextColor.RED, + NamedTextColor.AQUA, NamedTextColor.BLACK, NamedTextColor.WHITE + )) + /* other settings... */ + .build(); + // --8<-- [end:non_native] + } + + public void helpCommand(final @NonNull CommandManager commandManager) { + final MinecraftHelp help = this.nativeHelp(commandManager); + // --8<-- [start:help_command] + commandManager.command( + commandManager.commandBuilder("helpcommand") + .optional("query", greedyStringParser(), DefaultValue.constant("")) + .handler(context -> { + help.queryCommands(context.get("query"), context.sender()); + }) + ); + // --8<-- [end:help_command] + commandManager.command( + commandManager.commandBuilder("helpcommand") + // --8<-- [start:help_suggestions] + .optional( + "query", + greedyStringParser(), + DefaultValue.constant(""), + SuggestionProvider.blocking((ctx, in) -> commandManager.createHelpHandler() + .queryRootIndex(ctx.sender()) + .entries() + .stream() + .map(CommandEntry::syntax) + .map(Suggestion::simple) + .collect(Collectors.toList()) + ) + ) + // --8<-- [end:help_suggestions] + .handler(context -> { + help.queryCommands(context.get("query"), context.sender()); + }) + ); + } + + public static final class NativeSenderType implements Audience { + + } + + public static abstract class SenderType { + + public abstract @NonNull Audience audience(); + } +} diff --git a/docs/annotations/index.md b/docs/annotations/index.md index ea6002f..887c585 100644 --- a/docs/annotations/index.md +++ b/docs/annotations/index.md @@ -23,56 +23,10 @@ Examples can be found on Cloud Annotations is available through [Maven Central](https://central.sonatype.com/artifact/org.incendo/cloud-annotations). - -=== "Maven" - - ```xml - - - org.incendo - cloud-annotations - 2.0.0-beta.1 - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.incendo - cloud-annotations - 2.0.0-beta.1 - - - - - - - ``` - -=== "Gradle (Kotlin)" - - ```kotlin - implementation("org.incendo:cloud-annotations:2.0.0-beta.1") - // Optional: - annotationProcessor("org.incendo:cloud-annotations:2.0.0-beta.1") - ``` - -=== "Gradle (Groovy)" - - ```groovy - implementation 'org.incendo:cloud-annotations:2.0.0-beta.1' - // Optional: - annotationProcessor 'org.incendo:cloud-annotations:2.0.0-beta.1' - ``` +{{ dependency_listing("annotations", "core") }} You then need to create an -[`AnnotationParser`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/AnnotationParser.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/AnnotationParser.html", "AnnotationParser") }} instance. When creating the annotation parser you can supply an optional function that maps parser parameters to [command meta](../core/index.md#command-meta), these @@ -91,7 +45,7 @@ AnnotationParser annotationParser = new AnnotationParser( ``` To parse & register the different annotated methods you simply invoke -[`AnnotationParser#parse(Object)`]() +{{ javadoc("", "AnnotationParser#parse(Object)") }} with an instance of the class that you wish to parse. ## Command Methods @@ -101,16 +55,26 @@ with an instance of the class that you wish to parse. Command methods are annotated methods that are used to construct and handle commands. The method has to be annotated with a -[`@Command`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Command.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Command.html", "@Command") }} annotation that specifies the command syntax. The parsed command components are mapped to the method parameters. The parameters may also be mapped to [injected](#injections) values, such as the command sender instance, the -[`CommandContext`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/context/CommandContext.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/context/CommandContext.html", "CommandContext") }} or custom injections. The annotation may be repeated in order to generate multiple commands from the same method. +The command method may return {{ javadoc("https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/CompletableFuture.html", "CompletableFuture") }} +in which case the execution coordinator will wait for the returned future to complete: + +```java +@Command("command") +public CompletableFuture command() { + return CompletableFuture.supplyAsync(() -> null); +} +``` + ### Syntax There are three different parts that make up the command syntax: @@ -131,7 +95,7 @@ The types of the variable components are determined from the method parameters. ### Command Components -[`@Argument`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Argument.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Argument.html", "@Argument") }} annotations on method parameters is used to map the method parameter to the corresponding syntax fragment. If you compile with the `-parameters` compiler option then you do not need to specify the name in the annotation, and @@ -154,13 +118,33 @@ public void yourCommand( #### Default values [Default values](../core/index.md#optional) can be specified using the -[`@Default`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Default.html) -annotation. These values will always be parsed: +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Default.html", "@Default") }} +annotation. You may choose to either supply a value that will get parsed, or refer to a named default-providing method. ```java +// Parsed: @Default("foo") @Argument String string + +// Referencing a method: +@Default(name = "method") @Argument String string +``` + +Methods annotated with {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Default.html", "@Default") }} +will get parsed by the annotation parser. The only accepted method parameter is +{{ javadoc("https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/reflect/Parameter.html", "Parameter") }}, which +refers to the parameter that the default value is being generated for. +The method must return an instance of {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/component/DefaultValue.html", "DefaultValue") }}. +You may choose to specify a name. If you do not specify a name, the method name will be used as the name of the provider. + +```java title="Creating a default-providing method" +@Default(name = "method") // Could also be @Default without an explicit name! +public DefaultValue method(Parameter parameter) { + return DefaultValue.dynamic(context -> /* your logic */); +} ``` + + #### Either [Either](../core/index.md#either) may be used as a parameter type. Cloud will use the generic parameters to @@ -176,25 +160,25 @@ public void yourCommand(Either either) { ### Flags Flags can be generated by using the -[`@Flag`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Flag.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Flag.html", "@Flag") }} annotation. Similarly to -[`@Argument`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Argument.html), +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Argument.html", "@Argument") }}, this annotation can be used to specify suggestion providers, parsers, etc. If a boolean parameter is annotated with -[`@Flag`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Flag.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Flag.html", "@Flag") }} then it will generate a presence flag. Otherwise, it will become a value flag with the parameter type as the value type. Flags should _not_ be annotated with -[`@Argument`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Argument.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Argument.html", "@Argument") }} and should not present in the -[`@Command`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Command.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Command.html", "@Command") }} syntax. ### Descriptions -[`@CommandDescription`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/CommandDescription.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/CommandDescription.html", "@CommandDescription") }} can be added to an annotated command method to set the command description. You can override how the descriptions are mapped by setting replacing the description mapper: @@ -205,24 +189,35 @@ annotationParser.descriptionMapper(string -> Description.of("blablabla " + strin ### Permissions -[`@Permission`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Permission.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Permission.html", "@Permission") }} can be added to a command method to set the command permission. -Only simple string-permissions can be used. + +```java +// Simple string permission. +@Permission("the.permission") + +// Compound permissions are also supported. +// - Equivalent to Permission.anyOf: +@Permission(value = { "permission.1", "permission.2" }, mode = Permission.Mode.ANY_OF) +// - Equivalent to Permission.allOf: +@Permission(value = { "permission.1", "permission.2" }, mode = Permission.Mode.ALL_OF) +``` + You may use a [builder modifier](#builder-modifiers) to do more complex mappings. ### Proxies -[`@ProxiedBy`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/ProxiedBy.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/ProxiedBy.html", "@ProxiedBy") }} can be used to generate a command proxy. In most cases it is recommended to use multiple -[`@Command`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Command.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/Command.html", "@Command") }} annotations instead as it allows for better control over the generated command. ## Parsers You may create [parsers](../core/index.md#parsers) from annotated methods by using the -[`@Parser`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/parser/Parser.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/parser/Parser.html", "@Parser") }} annotation. If no value is passed to the annotation then the parser will become the default parser for the method return type. You may also pass a suggestion provider name to the annotation to bind the parser to a specific suggestion provider. @@ -238,12 +233,12 @@ public YourParsedType parserName(CommandContext context, CommandInput input) ``` Exceptions will be wrapped in -[`ArgumentParseResult.failure`](). +{{ javadoc("", "ArgumentParseResult.failure") }}. ## Suggestion Providers You may create [suggestion providers](../core/index.md#suggestions) from annotated methods by using the -[`@Suggestions`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/suggestion/Suggestions.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/suggestion/Suggestions.html", "@Suggestions") }} annotation. The signature of the suggestion methods is quite flexible, and you may use [injected values](#injections). @@ -267,12 +262,12 @@ public Iterable suggestions(CommandContext context, String input) { / ## Exception Handlers You may create [exception handlers](../core/index.md#exception-handling) from annotated methods by using the -[`@ExceptionHandler`](https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/exception/ExceptionHandler.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-annotations/latest/org/incendo/cloud/annotations/exception/ExceptionHandler.html", "@ExceptionHandler") }} annotation. You must specify which exception you want to handle. The method parameter can be any [injected](#injections) value, the command sender, -[`CommandContext`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/context/CommandContext.html), -[`ExceptionContext`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/handling/ExceptionContext.html), +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/context/CommandContext.html", "CommandContext") }}, +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/handling/ExceptionContext.html", "ExceptionContext") }}, or the exception type specified in the annotation. ```java title="Example exception handler" @@ -289,7 +284,7 @@ Common examples are the command sender objects as well as the command context. These values are referred to as _injected values_. Injected values are retrieved from the -[`ParameterInjectorRegistry`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/injection/ParameterInjectorRegistry.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/injection/ParameterInjectorRegistry.html", "ParameterInjectorRegistry") }} using injector services. You may register parameter injectors to the default service, or register your own injection service that hooks into an external dependency injection system. diff --git a/docs/core/index.md b/docs/core/index.md index 956a629..fea1787 100644 --- a/docs/core/index.md +++ b/docs/core/index.md @@ -17,28 +17,7 @@ Generally you'll want to depend on a platform module which implements Cloud for Cloud is available through [Maven Central](https://central.sonatype.com/artifact/org.incendo/cloud-core). - -=== "Maven" - - ```xml - - org.incendo - cloud-core - 2.0.0-beta.1 - - ``` - -=== "Gradle (Kotlin)" - - ```kotlin - implementation("org.incendo:cloud-core:2.0.0-beta.1") - ``` - -=== "Gradle (Groovy)" - - ```groovy - implementation 'org.incendo:cloud-core:2.0.0-beta.1' - ``` +{{ dependency_listing("core") }} ## Command @@ -112,18 +91,18 @@ and suggestions with tooltips in The execution coordinator is responsible for coordinating command parsing and execution. You may create a simple execution coordinator by using -[`ExecutionCoordinator.simpleCoordinator()`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/execution/ExecutionCoordinator.html#simpleCoordinator()", "ExecutionCoordinator.simpleCoordinator()") }} which will not enforce any particular executor and both parsing and suggestion generation will take place on the calling thread unless the parser or suggestion provider redirects to another executor. You may also use -[`ExecutionCoordinator.asyncCoordinator()`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/execution/ExecutionCoordinator.html#asyncCoordinator()", "ExecutionCoordinator.asyncCoordinator()") }} to create an execution coordinator that will perform parsing and suggestion generation asynchronously. You may customize the asynchronous coordinator by using -[`ExecutionCoordinator.builder()`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/execution/ExecutionCoordinator.html#builder()", "ExecutionCoordinator.builder()") }} and supply different executors for different execution steps, or use -[`ExecutionCoordinator.coordinatorFor(Executor)`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/execution/ExecutionCoordinator.html#coordinatorFor(java.util.concurrent.Executor)", "ExecutionCoordinator.coordinatorFor(Executor)") }} to supply an executor which is used at every execution step. ### Building a command @@ -133,9 +112,9 @@ to supply an executor which is used at every execution step. Commands are created using a command builder. You may either create a new builder by calling -[`Command#newBuilder`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/Command.html#newBuilder(java.lang.String,org.incendo.cloud.meta.CommandMeta,org.incendo.cloud.description.Description,java.lang.String...)", "Command#newBuilder") }} or through the command manager using -[`CommandManager#commandBuilder`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandBuilderSource.html). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandBuilderSource.html", "CommandManager#commandBuilder") }}. It is recommended to use the command manager to create a new command builder, as this ties the command builder to the [parser registry](#parser-registry). @@ -144,9 +123,9 @@ This allows you to store intermediate steps and reuse them to build multiple dis You must register your command to the command manager for it to be recognized. You do this by calling -[`CommandManager#command(Command)`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandManager.html#command(org.incendo.cloud.Command)", "CommandManager#command(Command)") }} or -[`CommandManager#command(Command.Builder)`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandManager.html#command(org.incendo.cloud.Command.Builder)", "CommandManager#command(Command.Builder)") }}. #### Descriptions @@ -174,11 +153,11 @@ builder.description(Description.of("The description")) ##### Command descriptions Command descriptions can be added through the command builder by calling -[`Command.Builder#commandDescription(CommandDescription)`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/Command.Builder.html#commandDescription(org.incendo.cloud.description.CommandDescription)", "Command.Builder#commandDescription(CommandDescription)") }}. The -[`CommandDescription`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/description/CommandDescription.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/description/CommandDescription.html", "CommandDescription") }} instance contains two instances of -[`Description`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/description/Description.html), +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/description/Description.html", "Description") }}, one short version and an optional verbose version. ```java @@ -204,9 +183,9 @@ Depending on the platform, it might also determine who is allowed to _see_ the c The permission is ultimately evaluated by the platform integration. Though, cloud has support for some more complex permission types, such as: -- [`Permission.anyOf(Permission...)`](): Takes in multiple permissions and evaluates to `true` if any of the permissions evaluate to `true`. -- [`Permission.allOf(Permission...)`](): Takes in multiple permissions and evaluates to `true` if all the permissions evaluate to `true`. -- [`PredicatePermission.of(Predicate)`](): Evaluates to `true` if the predicate evaluates to `true`. +- {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/permission/Permission.html#allOf(org.incendo.cloud.permission.Permission...)", "Permission.anyOf(Permission...)") }}: Takes in multiple permissions and evaluates to `true` if any of the permissions evaluate to `true`. +- {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/permission/Permission.html#anyOf(org.incendo.cloud.permission.Permission...)", "Permission.allOf(Permission...)") }}: Takes in multiple permissions and evaluates to `true` if all the permissions evaluate to `true`. +- {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/permission/Permission.html#of(java.lang.String)", "PredicatePermission.of(Predicate)") }}: Evaluates to `true` if the predicate evaluates to `true`. #### Sender types @@ -220,7 +199,7 @@ that maps between your custom type and the native command sender type. When you create a command you may override the sender type for that specific command, as long as the new sender type has `` as its supertype. This is done by using the -[`Command.Builder#senderType(Class)`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/Command.Builder.html#senderType(java.lang.Class)", "Command.Builder#senderType(Class)") }} method. Cloud will make sure that the sender is of the right type when executing the command, and will fail exceptionally if it isn't. @@ -263,7 +242,7 @@ They may have secondary aliases, depending on the platform you're targeting. Literals may be placed after required variable components, but never after optional variable components. The literals are created by using the various different -[`Command.Builder#literal`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/Command.Builder.html#literal(java.lang.String,java.lang.String...)", "Command.Builder#literal") }} methods, for example: ```java title="Example of literals" @@ -282,20 +261,20 @@ Literals are always required. Variable components are parsed using parsers. You can create a variable component either by using a -[`CommandComponent.Builder`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/component/CommandComponent.Builder.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/component/CommandComponent.Builder.html", "CommandComponent.Builder") }} that you create using -[`CommandComponent.builder`](), +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/component/CommandComponent.html#builder()", "CommandComponent.builder") }}, or by using one of the many different -[`Command.Builder`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/Command.Builder.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/Command.Builder.html", "Command.Builder") }} overloads. The component wraps a [parser](#parsers), but in many cases you will want to work with a -[`ParserDescriptor`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ParserDescriptor.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ParserDescriptor.html", "ParserDescriptor") }} instead. A -[`ParserDescriptor`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ParserDescriptor.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ParserDescriptor.html", "ParserDescriptor") }} is a structure containing an -[`ArgumentParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ArgumentParser.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ArgumentParser.html", "ArgumentParser") }} as well as a `TypeToken` that describes the object produced by the parser. If you do not provide a parser descriptor, then you will have to manually specify the value type. @@ -303,33 +282,33 @@ If you do not provide a parser descriptor, then you will have to manually specif All variable components have a name. When you want to extract the parsed values in a [command handler](#handler) you do so using the component name. You may use a -[`CloudKey`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/key/CloudKey.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/key/CloudKey.html", "CloudKey") }} instead of the name, which then allows you to retrieve the parsed values in a type-safe manner. ##### Required You can create a required variable component either by using -[`CommandComponent.Builder#required()`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/component/CommandComponent.Builder.html#required()", "CommandComponent.Builder#required()") }} or any of the many different overloaded `required` factory methods in -[`Command.Builder`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/Command.Builder.html). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/Command.Builder.html", "Command.Builder") }}. ##### Optional You can create a required variable component either by using -[`CommandComponent.Builder#optional()`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/component/CommandComponent.Builder.html#optional()", "CommandComponent.Builder#optional()") }} or any of the many different overloaded `optional` factory methods in -[`Command.Builder`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/Command.Builder.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/Command.Builder.html", "Command.Builder") }} . When creating an optional variable component you may supply a default value. The default value will be used in the case that the user has not supplied any input for the component. There are three different types of default values: -- [`DefaultValue.constant(Value)`](): A constant default value. -- [`DefaultValue.dynamic(Function)`](): A dynamic value that is evaluated when the command is parsed. -- [`DynamicValue.parsed(String)`](): A string that is parsed by the component parser when the command is parsed. +- {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/component/DefaultValue.html#constant(T)", "DefaultValue.constant(Value)") }}: A constant default value. +- {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/component/DefaultValue.html#dynamic(org.incendo.cloud.component.DefaultValue)", "DefaultValue.dynamic(Function)") }}: A dynamic value that is evaluated when the command is parsed. +- {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/component/DefaultValue.html#parsed(java.lang.String)", "DynamicValue.parsed(String)") }}: A string that is parsed by the component parser when the command is parsed. ##### Component pre-processing @@ -343,12 +322,12 @@ You can find it here: #### Command context -The [`CommandContext`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/context/CommandContext.html) +The {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/context/CommandContext.html", "CommandContext") }} is used to store values throughout the parsing process, such as parsed component values, values from preprocessors, parsed flags, etc. You can fetch values from the command context using both strings and -[`CloudKey`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/key/CloudKey.html). It is recommended to use +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/key/CloudKey.html", "CloudKey") }}. It is recommended to use keys to access values from the context as they are type-safe. ```java title="Example context usage" @@ -369,7 +348,7 @@ final List cats = commandContext.inject(new TypeToken>() {}); #### Handler The command handler is an instance of -[`CommandExecutionHandler`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/execution/CommandExecutionHandler.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/execution/CommandExecutionHandler.html", "CommandExecutionHandler") }} and is invoked when a command has been parsed successfully. Depending on the command execution coordinator the handler might be invoked asynchronously. The handler is passed an instance of the [command context](#command-context). @@ -381,27 +360,32 @@ builder.handler(ctx -> { ``` You may implement -[`CommandExecutionHandler.FutureCommandExecutionHandler`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/execution/CommandExecutionHandler.FutureCommandExecutionHandler.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/execution/CommandExecutionHandler.FutureCommandExecutionHandler.html", "CommandExecutionHandler.FutureCommandExecutionHandler") }} to have the handler be a future-returning -function. Cloud will wait for the future to complete and will handle any completion exceptions gracefully. +function. Cloud will wait for the future to complete and will handle any completion exceptions gracefully. You may +use the `futureHandler` command builder method to specify a future-returning handler: + +```java +builder.futureHandler(ctx -> CompletableFuture.completedFuture(null)) +``` You may delegate to other handlers using -[`CommandExecutionHandler.delegatingExecutionHandler`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/execution/CommandExecutionHandler.html#delegatingExecutionHandler(java.util.List)", "CommandExecutionHandler.delegatingExecutionHandler") }}. The command builder also has some utility functions for creating handlers that delegate to the existing handler, like -[`Command.Builder#prependHandler`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/Command.Builder.html#prependHandler(org.incendo.cloud.execution.CommandExecutionHandler)", "Command.Builder#prependHandler") }} and -[`Command.Builder#appendHandler`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/Command.Builder.html#appendHandler(org.incendo.cloud.execution.CommandExecutionHandler)", "Command.Builder#appendHandler") }}. ### Registering commands The command may be registered to the command manager by using -[`CommandManager#command(Command)`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandManager.html#command(org.incendo.cloud.Command)", "CommandManager#command(Command)") }}. You may also register a command builder using -[`CommandManager#command(Command.Builder)`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandManager.html#command(org.incendo.cloud.Command.Builder)", "CommandManager#command(Command.Builder)") }} in which case the command will be built by the manager. Commands may also be registered by passing a -[`CommandFactory`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandFactory.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandFactory.html", "CommandFactory") }} to the command manager. The command factory is an interface which outputs a list of commands. @@ -411,23 +395,23 @@ The command factory is an interface which outputs a list of commands. When a command is entered by a command sender, it goes through the following stages: -1. It is converted into a [`CommandInput`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/context/CommandInput.html) instance. +1. It is converted into a {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/context/CommandInput.html", "CommandInput") }} instance. 2. A command context is created for the input. 3. The context is passed to the preprocessors, which may alter the command input or write to the context. - - If a command processor causes an interrupt using [`ConsumerService.interrupt()`]() the context will be filtered out + - If a command processor causes an interrupt using {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-services/latest/org/incendo/cloud/services/type/ConsumerService.html#interrupt()", "ConsumerService.interrupt()") }} the context will be filtered out and the command will not be parsed. 4. The input is parsed into a command chain, and the parsed values are written to the context. - If the input cannot be parsed into a command that the sender is allowed to execute, the sender is notified and the parsing is canceled. 5. The command postprocessors get to act on the command, and may alter the command context. They may also postpone command execution, which can be used to require confirmations, etc. - - If a postprocessor causes an interrupt using [`ConsumerService.interrupt()`]() the command will not be executed. + - If a postprocessor causes an interrupt using {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-services/latest/org/incendo/cloud/services/type/ConsumerService.html#interrupt()", "ConsumerService.interrupt()") }} the command will not be executed. 6. The command is executed using the command executor. The pre- and post-processors can be registered to the command manager using -[`CommandManager#registerCommandPreProcessor`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandManager.html#registerCommandPreProcessor(org.incendo.cloud.execution.preprocessor.CommandPreprocessor)", "CommandManager#registerCommandPreProcessor") }} and -[`CommandManager#registerCommandPostProcessor`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandManager.html#registerCommandPostProcessor(org.incendo.cloud.execution.postprocessor.CommandPostprocessor)", "CommandManager#registerCommandPostProcessor") }}. Incendo maintains some processors that you may depend on in your projects: @@ -439,9 +423,9 @@ Incendo maintains some processors that you may depend on in your projects: Cloud v2 introduced a new exception handling system. You may register exception handlers through the -[`ExceptionController`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/handling/ExceptionController.html), +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/handling/ExceptionController.html", "ExceptionController") }}, which can be retrieved using -[`CommandManager#exceptionController`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandManager.html#exceptionController()", "CommandManager#exceptionController") }}. Cloud will attempt to match a thrown exception to any of the registered exception handlers, giving preference to the most specific exception type and to the last registered handler. @@ -452,14 +436,14 @@ Cloud will iterate over the exception handlers (giving preference to the last re consumes the exception, which allows for the registration of default handlers. Some exception types, such as -[`ArgumentParseException`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/ArgumentParseException.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/ArgumentParseException.html", "ArgumentParseException") }} and -[`CommandExecutionException`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/CommandExecutionException.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/CommandExecutionException.html", "CommandExecutionException") }} wrap the actual exceptions thrown by the parser or command handler. By default, Cloud will forward the wrapper exceptions. If you instead want to be able to register exception handlers for the causes, then you may use the -[`ExceptionHandler.unwrappingHandler()`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/handling/ExceptionHandler.html#unwrappingHandler()", "ExceptionHandler.unwrappingHandler()") }} methods to unwrap these exceptions. You can choose to unwrap all instances of a given exception type, all instances with a given cause type or all instances that pass a given predicate. @@ -467,52 +451,16 @@ all instances that pass a given predicate. Command exceptions are thrown whenever a command cannot be parsed or executed normally. This can be for several reasons, such as: -- The command sender does not have the required permission ([`NoPermissionException`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/NoPermissionException.html)) -- The command sender is of the wrong type ([`InvalidCommandSenderException`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/InvalidCommandSenderException.html)) -- The requested command does not exist ([`NoSuchCommandException`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/NoSuchCommandException.html)) -- The provided command input is invalid ([`InvalidSyntaxException`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/InvalidSyntaxException.html)) -- The parser cannot parse the input provided ([`ArgumentParseException`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/ArgumentParseException.html)) - -##### Captions - -[`ParserException`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/parsing/ParserException.html) -makes use of Cloud's caption system. -(Nearly) all argument parsers in Cloud will throw -[`ParserException`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/parsing/ParserException.html) on invalid input, in which case you're able -to override the exception message by configuring the manager's [`CaptionRegistry`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/caption/CaptionRegistry.html). - -The caption registry allows you to register caption providers that provide values for caption keys. -You may register multiple caption providers and the registry will iterate over them until one responds -with a non-`null` value. -There are some static factory methods in -[`CaptionProvider`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/caption/CaptionProvider.html) -that help generating providers for constant values. -All standard caption keys can be found in -[StandardCaptionKeys](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/caption/StandardCaptionKeys.html). -Some platform adapters have their own caption key classes as well. - -The JavaDoc for the caption keys list their replacement variables. -The message registered for the caption will have those variables replaced with variables specific to the parser. -`` is accepted by all parser captions, and will be replaced with the input that caused the exception -to be thrown. +- The command sender does not have the required permission ({{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/NoPermissionException.html)", "NoPermissionException") }} +- The command sender is of the wrong type ({{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/InvalidCommandSenderException.html)", "InvalidCommandSenderException") }} +- The requested command does not exist ({{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/NoSuchCommandException.html)", "NoSuchCommandException") }} +- The provided command input is invalid ({{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/InvalidSyntaxException.html)", "InvalidSyntaxException") }} +- The parser cannot parse the input provided ({{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/ArgumentParseException.html)", "ArgumentParseException") }} - -!!! example annotate "Example caption registry usage" - ```java - final CaptionRegistry registry = manager.captionRegistry(); - registry.registerProvider(CaptionProvider.constantProvider( - StandardCaptionKeys.ARGUMENT_PARSE_FAILURE_BOOLEAN, - "'' ain't a boolean >:(" - )); - ``` +##### Localization -You may create a custom caption formatter that generates more complex output types than strings. -This is particularly useful if you want to route the captions through some external system to generate -platform-native message types (i.e. `Component` for Minecraft). You may format captions using this custom -type by invoking -[`ParserException#formatCaption`]() -or -[`CommandContext#formatCaption`](). +The default exception handlers make use of translatable captions. +You may learn more about customizing the messages in the section about [Localization](../localization/index.md). ## Parsers @@ -526,7 +474,7 @@ The parser registry is primarily used in two different places: If you are creating a library or using [cloud-annotations](../annotations/index.md), it is recommended to register your parser in the parser registry. You can access the parser registry via -[`CommandManager#parserRegistry`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandManager.html#parserRegistry()", "CommandManager#parserRegistry") }}. The parser suppliers get access to a structure containing parser parameters. These parameters are most often mapped to annotations, and allow for the customization of the parsers when using [cloud-annotations](../annotations/index.md). @@ -558,7 +506,7 @@ Cloud has four different string "modes": The string parsers do not produce suggestions by default. The string parsers can be created using the static factory methods found in -[`StringParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/StringParser.html). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/StringParser.html", "StringParser") }}. #### String Array @@ -569,7 +517,7 @@ it encounters a [flag](#flags). The string array parser does not produce suggestions by default. The string array parser can be created using the static factory methods found in -[`StringArrayParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/StringArrayParser.html). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/StringArrayParser.html", "StringArrayParser") }}. #### Character @@ -577,7 +525,7 @@ This parses a single space-delimited character. The character parser does not produce suggestions by default. The character parser can be created using the static factory methods found in -[`CharacterParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/CharacterParser.html). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/CharacterParser.html", "CharacterParser") }}. #### Numbers @@ -588,12 +536,12 @@ Cloud will generate suggestions for all numerical types, except for `float` and The numerical parsers can be created using the static factory methods found in: -- [`ByteParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/ByteParser.html) -- [`ShortParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/ShortParser.html) -- [`IntegerParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/IntegerParser.html) -- [`LongParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/LongParser.html) -- [`DoubleParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/DoubleParser.html) -- [`FloatParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/FloatParser.html) +- {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/ByteParser.html", "ByteParser") }} +- {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/ShortParser.html", "ShortParser") }} +- {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/IntegerParser.html", "IntegerParser") }} +- {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/LongParser.html", "LongParser") }} +- {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/DoubleParser.html", "DoubleParser") }} +- {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/FloatParser.html", "FloatParser") }} #### Boolean @@ -602,7 +550,7 @@ A strict boolean parser only accepts (independent of the case) `true` and `false A liberal boolean parser also accepts `yes`, `no`, `on` and `off`. The boolean parser can be created using the static factory methods found in -[`BooleanParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/BooleanParser.html). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/BooleanParser.html", "BooleanParser") }}. #### Enum @@ -610,14 +558,14 @@ The enum parser matches the input (independent of the case) to the names of an e the enum values as suggestions. The enum parser can be created using the static factory methods found in -[`EnumParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/EnumParser.html). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/EnumParser.html", "EnumParser") }}. #### UUID The UUID parser parses dash-separated UUIDs. The UUID parser can be created using the static factory methods found in -[`UUIDParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/UUIDParser.html). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/standard/UUIDParser.html", "UUIDParser") }}. ### Flags @@ -634,9 +582,9 @@ When referring to the full name of a flag, you use `--name` whereas an alias use You can chain the aliases of multiple presence flags together, such that `-a -b -c` is equivalent to `-abc`. The flag values are contained in -[`FlagContext`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/flag/FlagContext.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/flag/FlagContext.html", "FlagContext") }} which can be retrieved using -[`CommandContext#flags()`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/context/CommandContext.html#flags()", "CommandContext#flags()") }}. !!! example "Example of a command with a presence flag" @@ -655,27 +603,12 @@ An aggregate parser is a combination of multiple parsers that maps the intermedi type using a mapper. You may either implement the -[`AggregateParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/aggregate/AggregateParser.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/aggregate/AggregateParser.html", "AggregateParser") }} interface, or using construct the parser by using a builder that you create by calling -[`AggregateParser.builder()`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/aggregate/AggregateParser.html#builder()", "AggregateParser.builder()") }}. - -!!! example "Example Aggregate Parser" - ```java - final AggregateParser locationParser = AggregateParser.builder() - .withComponent("world", worldParser()) - .withComponent("x", integerParser()) - .withComponent("y", integerParser()) - .withComponent("z", integerParser()) - .withMapper(Location.class, (commandContext, aggregateCommandContext) -> { - final World world = aggregateCommandContext.get("world"); - final int x = aggregateCommandContext.get("x"); - final int y = aggregateCommandContext.get("y"); - final int z = aggregateCommandContext.get("z"); - return CompletableFuture.completedFuture(new Location(world, x, y, z)); - }).build(); - ``` +{{ snippet("AggregateParserExample.java", title = "Example Aggregate Parser") }} ### Either @@ -687,17 +620,7 @@ The parser will first attempt to parse the primary type `A`, and if this fails i fallback type `B`. The suggestions of both the primary and fallback parsers will be joined when using the parser as the suggestion provider. -```java title="Example of Either" -commandBuilder.required("either", ArgumentParser.firstOf(integerParser(), booleanParser())) - .handler(context -> { - Either either = context.get("either"); - if (either.primary().isPresent()){ - int integer = either.primary().get(); - } else { - boolean bool = either.fallback().get(); - } - }); -``` +{{ snippet("EitherExample.java", title = "Example of Either") }} ### Custom Parsers @@ -729,7 +652,7 @@ The recommended way of parsing an argument is to: The parser has two different choices when it comes to which method to implement. If the parser implements -[`ArgumentParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ArgumentParser.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ArgumentParser.html", "ArgumentParser") }} then the signature looks like ```java @@ -739,14 +662,14 @@ public ArgumentParseResult parse( ``` where the -[`ArgumentParseResult`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ArgumentParseResult.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ArgumentParseResult.html", "ArgumentParseResult") }} can either be a -[`ArgumentParseResult.success(OutputType)`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ArgumentParseResult.html#success(T)", "ArgumentParseResult.success(OutputType)") }} or -[`ArgumentParseResult.failure(Exception)`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ArgumentParseResult.html#failure(java.lang.Throwable)", "ArgumentParseResult.failure(Exception)") }}. The parser may also implement -[`ArgumentParser.FutureArgumentParser`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ArgumentParser.FutureArgumentParser.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/parser/ArgumentParser.FutureArgumentParser.html", "ArgumentParser.FutureArgumentParser") }} in which case the signature looks like ```java @@ -759,34 +682,14 @@ in which case, a successful result is returned as a completed future, and a fail exceptionally completed future. Returning a future is useful when the parsing needs to take place on a specific thread. - -!!! example annotate "Example Parser" - ```java - public class UUIDParser implements ArgumentParser { - - @Override - public ArgumentParseResult parse( - CommandContext context, - CommandInput input - ) { - final String input = input.peekString(); // Does not remove the string from the input! - try { - final UUID uuid = UUID.fromString(input); - input.readString(); // Removes the string from the input. - return ArgumentParseResult.success(uuid); - } catch(final IllegalArgumentException e) { - return ArgumentParseResult.failure(new UUIDParseException(input, commandContext)); - } - } - } - ``` +{{ snippet("UUIDParser.java", title = "Example Parser") }} 1. The command sender type. #### Exceptions It is recommended to make use of -[`ParserException`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/parsing/ParserException.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/exception/parsing/ParserException.html", "ParserException") }} when returning a failed result. This allows for integration with the caption system, see [exception handling](#exception-handling) for more information. @@ -797,19 +700,19 @@ These suggestions will be used to provide suggestions for the component using th unless the component is created using a custom suggestion provider. Parsers implement -[`SuggestionProviderHolder`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/suggestion/SuggestionProviderHolder.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/suggestion/SuggestionProviderHolder.html", "SuggestionProviderHolder") }} which means that they can return a suggestion provider by overriding the -[`suggestionProvider`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/suggestion/SuggestionProviderHolder.html#suggestionProvider()", "suggestionProvider") }} method. However, the recommended way of providing suggestions is by implementing one of the suggestion provider -interfaces ([`SuggestionProvider`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/suggestion/SuggestionProvider.html), -[`BlockingSuggestionProvider`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/suggestion/BlockingSuggestionProvider.html), +interfaces ({{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/suggestion/SuggestionProvider.html", "SuggestionProvider") }}, +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/suggestion/BlockingSuggestionProvider.html", "BlockingSuggestionProvider") }}, or -[`BlockingSuggestionProvider.Strings`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/suggestion/BlockingSuggestionProvider.Strings.html)). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/suggestion/BlockingSuggestionProvider.Strings.html)", "BlockingSuggestionProvider.Strings") }}. If the parser implements a suggestion provider interface it does not need to override the -[`suggestionProvider`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/suggestion/SuggestionProviderHolder.html#suggestionProvider()", "suggestionProvider") }} method, as it'll return `this` by default. ## Extra @@ -818,15 +721,15 @@ method, as it'll return `this` by default. Cloud has a system that assists in querying for command information. This is accessible through the -[`HelpHandler`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/help/HelpHandler.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/help/HelpHandler.html", "HelpHandler") }} that can be accessed using -[`CommandManager#createHelpHandler`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandManager.html#createHelpHandler()", "CommandManager#createHelpHandler") }}. This invokes a -[`HelpHandlerFactory`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/help/HelpHandlerFactory.html). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/help/HelpHandlerFactory.html", "HelpHandlerFactory") }}. You may replace the default -[`HelpHandlerFactory`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/help/HelpHandlerFactory.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/help/HelpHandlerFactory.html", "HelpHandlerFactory") }} using -[`CommandManager#helpHandlerFactory(HelpHandlerFactory)`]() +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/CommandManager.html#helpHandlerFactory(org.incendo.cloud.help.HelpHandlerFactory)", "CommandManager#helpHandlerFactory(HelpHandlerFactory)") }} to change how the information is generated. The help handler will try to output as much information as it can, depending on how precise the query is. @@ -837,9 +740,9 @@ There are three types of query results: - **Verbose**: Returns verbose information about a specific command. You may query for results by using -[`HelpHandler#query(HelpQuery)`](). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/help/HelpHandler.html#query(org.incendo.cloud.help.HelpQuery)", "HelpHandler#query(HelpQuery)") }}. The help handler does not display any information, this is instead done by a -[`HelpRenderer`](https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/help/HelpRenderer.html). +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/help/HelpRenderer.html", "HelpRenderer") }}. `cloud-core` does not contain any implementations of the help renderer as this is highly platform-specific, but [cloud-minecraft-extras](../minecraft/minecraft-extras.md#minecraft-help) contains an opinionated implementation of the help system for Minecraft. diff --git a/docs/discord/discord4j.md b/docs/discord/discord4j.md index dfc543c..64c14d1 100644 --- a/docs/discord/discord4j.md +++ b/docs/discord/discord4j.md @@ -8,30 +8,7 @@ An example bot using cloud-discord4j can be found [here](https://github.com/Ince Cloud for Discord4J is available through [Maven Central](https://central.sonatype.com/artifact/org.incendo/cloud-discord4j). - -=== "Maven" - - ```xml - - - org.incendo - cloud-discord4j - 1.0.0-beta.1 - - - ``` - -=== "Gradle (Kotlin)" - - ```kotlin - implementation("org.incendo:cloud-discord4j:1.0.0-beta.1") - ``` - -=== "Gradle (Groovy)" - - ```groovy - implementation 'org.incendo:cloud-discord4j:1.0.0-beta.1' - ``` +{{ dependency_listing("discord4j") }} ## Usage diff --git a/docs/discord/jda5.md b/docs/discord/jda5.md index ab64494..73ff4b9 100644 --- a/docs/discord/jda5.md +++ b/docs/discord/jda5.md @@ -8,30 +8,7 @@ An example bot using cloud-jda5 can be found [here](https://github.com/Incendo/c Cloud for JDA5 is available through [Maven Central](https://central.sonatype.com/artifact/org.incendo/cloud-jda5). - -=== "Maven" - - ```xml - - - org.incendo - cloud-jda5 - 1.0.0-beta.1 - - - ``` - -=== "Gradle (Kotlin)" - - ```kotlin - implementation("org.incendo:cloud-jda5:1.0.0-beta.1") - ``` - -=== "Gradle (Groovy)" - - ```groovy - implementation 'org.incendo:cloud-jda5:1.0.0-beta.1' - ``` +{{ dependency_listing("jda5") }} ## Usage diff --git a/docs/discord/kord.md b/docs/discord/kord.md index b5340a6..18bc874 100644 --- a/docs/discord/kord.md +++ b/docs/discord/kord.md @@ -8,30 +8,7 @@ An example bot using cloud-kord can be found [here](https://github.com/Incendo/c Cloud for Kord is available through [Maven Central](https://central.sonatype.com/artifact/org.incendo/cloud-kord). - -=== "Maven" - - ```xml - - - org.incendo - cloud-kord - 1.0.0-beta.1 - - - ``` - -=== "Gradle (Kotlin)" - - ```kotlin - implementation("org.incendo:cloud-kord:1.0.0-beta.1") - ``` - -=== "Gradle (Groovy)" - - ```groovy - implementation 'org.incendo:cloud-kord:1.0.0-beta.1' - ``` +{{ dependency_listing("kord") }} ## Usage diff --git a/docs/kotlin/annotations.md b/docs/kotlin/annotations.md index fc4c84e..bd3bc95 100644 --- a/docs/kotlin/annotations.md +++ b/docs/kotlin/annotations.md @@ -27,18 +27,7 @@ suspend fun yourCommand( Cloud is available through [Maven Central](https://central.sonatype.com/artifact/org.incendo/cloud-kotlin-coroutines-annotations). - -=== "Gradle (Kotlin)" - - ```kotlin - implementation("org.incendo:cloud-kotlin-coroutines-annotations:2.0.0-beta.1") - ``` - -=== "Gradle (Groovy)" - - ```groovy - implementation 'org.incendo:cloud-kotlin-coroutines-annotations:2.0.0-beta.1' - ``` +{{ dependency_listing("kotlin-coroutines-annotations", "core") }} You then need to install the `AnnotationParser` extension: diff --git a/docs/kotlin/coroutines.md b/docs/kotlin/coroutines.md index 566c384..a710c94 100644 --- a/docs/kotlin/coroutines.md +++ b/docs/kotlin/coroutines.md @@ -16,18 +16,7 @@ For suspending commands methods, see [cloud-kotlin-coroutines-annotations](./ann Cloud is available through [Maven Central](https://central.sonatype.com/artifact/org.incendo/cloud-kotlin-coroutines). - -=== "Gradle (Kotlin)" - - ```kotlin - implementation("org.incendo:cloud-kotlin-coroutines:2.0.0-beta.1") - ``` - -=== "Gradle (Groovy)" - - ```groovy - implementation 'org.incendo:cloud-kotlin-coroutines:2.0.0-beta.1' - ``` +{{ dependency_listing("kotlin-coroutines", "core") }} ## Suspending command execution handlers diff --git a/docs/kotlin/extensions.md b/docs/kotlin/extensions.md index 644dc41..d7f0a5f 100644 --- a/docs/kotlin/extensions.md +++ b/docs/kotlin/extensions.md @@ -15,18 +15,7 @@ This module contains extensions to different parts of Cloud. Cloud is available through [Maven Central](https://central.sonatype.com/artifact/org.incendo/cloud-kotlin-extensions). - -=== "Gradle (Kotlin)" - - ```kotlin - implementation("org.incendo:cloud-kotlin-extensions:2.0.0-beta.1") - ``` - -=== "Gradle (Groovy)" - - ```groovy - implementation 'org.incendo:cloud-kotlin-extensions:2.0.0-beta.1' - ``` +{{ dependency_listing("kotlin-extensions", "core") }} ## MutableCommandBuilder diff --git a/docs/localization/index.md b/docs/localization/index.md new file mode 100644 index 0000000..bcb163c --- /dev/null +++ b/docs/localization/index.md @@ -0,0 +1,129 @@ +# Localization + +## Exceptions + +### Captions + +The exception handlers shipped with Cloud (including [`MinecraftExceptionHandler`](../minecraft/minecraft-extras.md#minecraft-exception-handler)) +uses the +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/caption/Caption.html", "Caption") }} system. +A {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/caption/Caption.html", "Caption") }} is a key +to a configurable message, which allows you to override the default messages on a per-sender level. +The default messages are simple strings, but it's possible to use a custom [formatter](#formatting) to format +the messages into rich objects. + +The messages retrieved using caption providers that are registered to the +command manager's {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/caption/CaptionRegistry.html", "CaptionRegistry") }}. +You may register however many providers you want. The system will iterate over the providers until one returns a non-`null` +result. + +```java title="Example caption registration" +manager.captionRegistry().registerProvider((caption, sender) -> { + // You may want to map your sender to a locale, + // and look up the translations using a locale-based system: + Locale locale = sender.getLocale(); + return yourTranslationSystem.getTranslation(locale, caption.key()); +}); +``` + +There are also utilities for registering caption-specific providers: + +```java title="Per-caption provider" +manager.captionRegistry().registerProvider( + CaptionProvider.forCaption(theCaption, sender -> "the value") +); +``` + +If your application does not require translations, you may also register constant caption values: + +```java title="Constant captions" +manager.captionRegistry().registerProvider( + CaptionProvider.constantProvider(theCaption, "the value") +); +``` + +The captions for [`cloud-core`](../core/index.md) can be found in +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-core/latest/org/incendo/cloud/caption/StandardCaptionKeys.html", "StandardCaptionKeys") }}. +The platform adapters may include additional captions. See the platform-specific documentation for more information. + +#### Formatting + +The configured caption messages may contain placeholders, most often in the form ``. +The JavaDoc for the caption keys list the available placeholders for the caption. +The message registered for the caption will have those variables replaced with variables specific to the parser. + +You can replace the default formatter if you want to, this can be done by invoking +{{ javadoc("", "CommandManager#captionFormatter(CaptionFormatter)") }}. + +You may create a custom caption formatter that generates more complex output types than strings. +This is particularly useful if you want to route the captions through some external system to generate +platform-native message types (i.e. `Component` for Minecraft). You may format captions using this custom +type by invoking +{{ javadoc("", "ParserException#formatCaption") }} +or +{{ javadoc("", "CommandContext#formatCaption") }}. + +## Translations + +{{ javadoc("https://github.com/incendo/cloud-translations", "cloud-translations") }} contains community-contributed translations of the [captions](#captions) +used in the official Cloud modules. You may see the translation progress (and also contribute) yourself at +our [Crowdin](https://crowdin.com/project/incendo-cloud) page. + +### Usage + +These modules are available on [Maven Central](https://search.maven.org/search?q=g:org.incendo%20AND%20a:cloud-translations-*). + +#### [cloud-translations-core](https://github.com/Incendo/cloud-translations/tree/main/cloud-translations-core) + +This module contains tooling for creating caption providers from files with translations. +The module also contains translations for the captions in `cloud-core`. + +```java title="Registration of cloud-core translations" +// You need to create an extractor which maps the sender type to a locale. +LocaleExtractor extractor = yourSenderType::locale; +// You then create the translation bundle, which is a CaptionProvider. +TranslationBundle bundle = TranslationBundle.core(extractor); +// Then you register the caption provider. +manager.captionRegistry().registerProvider(bundle); +``` + +#### Minecraft Modules + +##### [cloud-translations-bukkit](https://github.com/Incendo/cloud-translations/tree/main/cloud-translations-bukkit) + +This module contains translations for the captions in `cloud-bukkit`. + +```java title="Registration of cloud-bukkit translations" +// You need to create an extractor which maps the sender type to a locale. +LocaleExtractor extractor = yourSenderType::locale; +// You then create the translation bundle, which is a CaptionProvider. +TranslationBundle bundle = BukkitTranslationBundle.bukkit(extractor); +// Then you register the caption provider. +manager.captionRegistry().registerProvider(bundle); +``` + +##### [cloud-translations-bungee](https://github.com/Incendo/cloud-translations/tree/main/cloud-translations-bungee) + +This module contains translations for the captions in `cloud-bungee`. + +```java title="Registration of cloud-bungee translations" +// You need to create an extractor which maps the sender type to a locale. +LocaleExtractor extractor = yourSenderType::locale; +// You then create the translation bundle, which is a CaptionProvider. +TranslationBundle bundle = BungeeTranslationBundle.bungee(extractor); +// Then you register the caption provider. +manager.captionRegistry().registerProvider(bundle); +``` + +##### [cloud-translations-velocity](https://github.com/Incendo/cloud-translations/tree/main/cloud-translations-velocity) + +This module contains translations for the captions in `cloud-velocity`. + +```java title="Registration of cloud-velocity translations" +// You need to create an extractor which maps the sender type to a locale. +LocaleExtractor extractor = yourSenderType::locale; +// You then create the translation bundle, which is a CaptionProvider. +TranslationBundle bundle = VelocityTranslationBundle.velocity(extractor); +// Then you register the caption provider. +manager.captionRegistry().registerProvider(bundle); +``` diff --git a/docs/minecraft/bukkit.md b/docs/minecraft/bukkit.md index 1421d0a..aa1bf35 100644 --- a/docs/minecraft/bukkit.md +++ b/docs/minecraft/bukkit.md @@ -2,7 +2,7 @@ The `cloud-bukkit` module is home to parsers and other classes that make up the base of Cloud for Bukkit-based platforms. `cloud-bukkit` is not intended to be consumed as a direct dependency, instead it should be consumed as -a transitive dependency of [`cloud-paper`](paper.md). +a transitive dependency of {{ javadoc("paper.md", "cloud-paper") }}. ## Links @@ -37,7 +37,7 @@ a transitive dependency of [`cloud-paper`](paper.md). ### NamespacedKeyParser The -[`NamespacedKeyParser`](https://javadoc.io/doc/org.incendo/cloud-bukkit/latest/org/incendo/cloud/bukkit/parser/NamespacedKeyParser.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-bukkit/latest/org/incendo/cloud/bukkit/parser/NamespacedKeyParser.html", "NamespacedKeyParser") }} parses namespaced key in the form `namespace:key`. #### Annotations @@ -45,14 +45,14 @@ parses namespaced key in the form `namespace:key`. ###### `@DefaultNamespace` Use -[`@DefaultNamespace("namespace")`](https://javadoc.io/doc/org.incendo/cloud-bukkit/latest/org/incendo/cloud/bukkit/annotation/specifier/DefaultNamespace.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-bukkit/latest/org/incendo/cloud/bukkit/annotation/specifier/DefaultNamespace.html", "@DefaultNamespace(\"namespace\")") }} on a component to set the namespace which will be used in the case that no namespace is supplied by the command sender. ###### `@RequireExplicitNamespace` Use -[`@RequireExplicitNamespace`](https://javadoc.io/doc/org.incendo/cloud-bukkit/latest/org/incendo/cloud/bukkit/annotation/specifier/RequireExplicitNamespace.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-bukkit/latest/org/incendo/cloud/bukkit/annotation/specifier/RequireExplicitNamespace.html", "@RequireExplicitNamespace") }} to fail parsing if the command sender does not supply a namespace. ### Selectors @@ -62,7 +62,7 @@ to fail parsing if the command sender does not supply a namespace. ##### `@AllowEmptySelection` Use -[`@AllowEmptySelection`](https://javadoc.io/doc/org.incendo/cloud-bukkit/latest/org/incendo/cloud/bukkit/annotation/specifier/AllowEmptySelection.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-bukkit/latest/org/incendo/cloud/bukkit/annotation/specifier/AllowEmptySelection.html", "@AllowEmptySelection") }} to allow the command sender to execute a command with a selector which selects zero entities. ## Descriptions @@ -71,6 +71,14 @@ Cloud will register all root literals to the Bukkit [CommandMap](https://jd.pape which means that they will show up in the Bukkit help menu. Cloud will try to determine the description for the Bukkit help menu by: -1. Use the [`BukkitCommandMeta.BUKKIT_DESCRIPTION`](https://javadoc.io/doc/org.incendo/cloud-bukkit/latest/org/incendo/cloud/bukkit/BukkitCommandMeta.html) [meta](../core/index.md#command-meta) value of the command, if it exists. +1. Use the {{ javadoc("https://javadoc.io/doc/org.incendo/cloud-bukkit/latest/org/incendo/cloud/bukkit/BukkitCommandMeta.html", "BukkitCommandMeta.BUKKIT_DESCRIPTION") }} [meta](../core/index.md#command-meta) value of the command, if it exists. 2. Using the [CommandDescription](../core/index.md#command-descriptions), if a command is attached directly to the root literal. 3. Use the root literal [Description](../core/index.md#component-descriptions), if it's non-empty. + +## Localization + +`cloud-bukkit` provides additional caption keys for the [localization](../localization/index.md) system. +These can be found in +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-bukkit/latest/org/incendo/cloud/bukkit/BukkitCaptionKeys.html", "BukkitCaptionKeys") }}. +The default caption values can be found in +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-bukkit/latest/org/incendo/cloud/bukkit/BukkitDefaultCaptionsProvider.html", "BukkitDefaultCaptionsProvider") }}. diff --git a/docs/minecraft/bungee.md b/docs/minecraft/bungee.md index 68df32f..76b1b60 100644 --- a/docs/minecraft/bungee.md +++ b/docs/minecraft/bungee.md @@ -16,35 +16,12 @@ Cloud for BungeeCord is available through [Maven Central](https://central.sonatype.com/artifact/org.incendo/cloud-bungee). - -=== "Maven" - - ```xml - - - org.incendo - cloud-bungee - 2.0.0-beta.1 - - - ``` - -=== "Gradle (Kotlin)" - - ```kotlin - implementation("org.incendo:cloud-bungee:2.0.0-beta.1") - ``` - -=== "Gradle (Groovy)" - - ```groovy - implementation 'org.incendo:cloud-bungee:2.0.0-beta.1' - ``` +{{ dependency_listing("bungee") }} ## Usage `cloud-bungee` has a command manager implementation called -[`BungeeCommandManager`](https://javadoc.io/doc/org.incendo/cloud-bungee/latest/org/incendo/cloud/bungee/BungeeCommandManager.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-bungee/latest/org/incendo/cloud/bungee/BungeeCommandManager.html", "BungeeCommandManager") }} that can be created like: ```{ .java .annotate } @@ -58,8 +35,8 @@ BungeeCommandManager commandManager = new BungeeCommandManager<> 1. Information about execution coordinators in general can be found [here](../core/index.md#execution-coordinators). 2. The sender mapper is a two-way mapping between BungeeCord's - [`CommandSender`](https://ci.md-5.net/job/BungeeCord/ws/api/target/apidocs/net/md_5/bungee/api/CommandSender.html) and your custom sender type. - You may use [`SenderMapper.identity()`]() if using [`CommandSender`](https://ci.md-5.net/job/BungeeCord/ws/api/target/apidocs/net/md_5/bungee/api/CommandSender.html) as the sender type. + {{ javadoc("https://ci.md-5.net/job/BungeeCord/ws/api/target/apidocs/net/md_5/bungee/api/CommandSender.html", "CommandSender") }} and your custom sender type. + You may use {{ javadoc("", "SenderMapper.identity()") }} if using {{ javadoc("https://ci.md-5.net/job/BungeeCord/ws/api/target/apidocs/net/md_5/bungee/api/CommandSender.html", "CommandSender") }} as the sender type. ## Parsers @@ -67,3 +44,11 @@ BungeeCommandManager commandManager = new BungeeCommandManager<> | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | [PlayerParser](https://javadoc.io/doc/org.incendo/cloud-bungee/latest/org/incendo/cloud/bungee/parser/PlayerParser.html) | [ProxiedPlayer](https://ci.md-5.net/job/BungeeCord/ws/api/target/apidocs/net/md_5/bungee/api/connection/ProxiedPlayer.html) | | [ServerParser](https://javadoc.io/doc/org.incendo/cloud-bungee/latest/org/incendo/cloud/bungee/parser/ServerParser.html) | [ServerInfo](https://ci.md-5.net/job/BungeeCord/ws/api/target/apidocs/net/md_5/bungee/api/config/ServerInfo.html) | + +## Localization + +`cloud-bungee` provides additional caption keys for the [localization](../localization/index.md) system. +These can be found in +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-bungee/latest/org/incendo/cloud/bungee/BungeeCaptionKeys.html", "BungeeCaptionKeys") }}. +The default caption values can be found in +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-bungee/latest/org/incendo/cloud/bungee/BungeeCommandManager.html", "BungeeCommandManager") }}. diff --git a/docs/minecraft/minecraft-extras.md b/docs/minecraft/minecraft-extras.md index f5d89c8..c72254d 100644 --- a/docs/minecraft/minecraft-extras.md +++ b/docs/minecraft/minecraft-extras.md @@ -10,6 +10,8 @@
+- [:fontawesome-brands-github: Source Code](https://github.com/Incendo/cloud-minecraft/tree/master/cloud-minecraft-extras) +- [:fontawesome-brands-java: JavaDoc](https://javadoc.io/doc/org.incendo/cloud-minecraft-extras) - [MinecraftHelp](#minecraft-help) - [MinecraftExceptionHandler](#minecraft-exception-handler) - [RichDescription](#rich-description) @@ -21,50 +23,15 @@ Cloud Minecraft Extras is available through [Maven Central](https://central.sonatype.com/artifact/org.incendo/cloud-minecraft-extras). - -=== "Maven" - - ```xml - - - org.incendo - cloud-minecraft-extras - 2.0.0-beta.1 - - - ``` - -=== "Gradle (Kotlin)" - - ```kotlin - implementation("org.incendo:cloud-minecraft-extras:2.0.0-beta.1") - ``` - -=== "Gradle (Groovy)" - - ```groovy - implementation 'org.incendo:cloud-minecraft-extras:2.0.0-beta.1' - ``` +{{ dependency_listing("minecraft-extras") }} ## Minecraft Help `MinecraftHelp` is an opinionated implementation of the [help system](../core/index.md#help-generation) using Adventure components for styling and click handling. - - -
- ![Minecraft Help Index](../assets/images/minecraft/mce_help_index.png) -
Index View
-
- -
- ![Minecraft Help Verbose](../assets/images/minecraft/mce_help_verbose.png) -
Verbose View
-
+{{ figure("../assets/images/minecraft/mce_help_index.png", "Index View") }} +{{ figure("../assets/images/minecraft/mce_help_verbose.png", "Verbose View") }} All interactions with the Minecraft help system will take place through a `MinecraftHelp` instance. @@ -96,58 +63,20 @@ or you may override the defaults by using a builder: === "Native Audience" - ```java - MinecraftHelp help = MinecraftHelp.builder() - .commandManager(commandManager) - .audienceProvider(AudienceProvider.nativeProvider()) - .commandPrefix("/helpcommand") - .colors(MinecraftHelp.helpColors(/* colors... */)) - /* other settings... */ - .build(); - ``` + {{ snippet("minecraft/MinecraftHelpExample.java", section = "native", title = "", indent = 4) }} === "Other" - ```java - MinecraftHelp help = MinecraftHelp.builder() - .commandManager(commandManager) - .audienceProvider(yourAudienceProvider) - .commandPrefix("/helpcommand") - .colors(MinecraftHelp.helpColors(/* colors... */)) - /* other settings... */ - .build(); - ``` + {{ snippet("minecraft/MinecraftHelpExample.java", section="non_native", title = "", indent = 4) }} You then want to invoke `MinecraftHelp.queryCommands(query, recipient)` in order to query the commands and display the results to the recipient. -```java title="Example Help Command" -commandManager.command( - commandManager.commandBuilder("helpcommand") - .optional("query", greedyStringParser(), DefaultValue.constant("")) - .handler(context -> { - help.queryCommands(context.get("query"), context.sender()); - }) -); -``` +{{ snippet("minecraft/MinecraftHelpExample.java", section = "help_command", title = "Example Help Command") }} You may choose to add suggestions to the query argument as well: -```java title="Query Suggestions" -.optional( - "query", - greedyStringParser(), - DefaultValue.constant(""), - SuggestionProvider.blocking((ctx, in) -> commandManager.createHelpHandler() - .queryRootIndex(ctx.sender()) - .entries() - .stream() - .map(CommandEntry::syntax) - .map(Suggestion::simple) - .collect(Collectors.toList()) - ) -) -``` +{{ snippet("minecraft/MinecraftHelpExample.java", section = "help_suggestions", title = "Query Suggestions") }} ## Minecraft Exception Handler @@ -176,28 +105,23 @@ If you want to register the default handlers for all types you may use `defaultH You may supply a decorator which will transform the created components. This is useful if you want to prefix all messages, or apply specific styling. -```java title="Example decorator" -MinecraftExceptionHandler.creativeNative() - .decorator(component -> text() - .append("[Example] ", NamedTextColor.DARK_RED) - .append(component) - .build() - ); -``` +{{ snippet("minecraft/MinecraftExceptionHandlerExample.java", section = "native", title = "Example decorator") }} When you're done configuring the builder you need to apply it to the command manager by using `registerTo(CommandManager)`. -```java title="Complete example" -MinecraftExceptionHandler.createNative() - .defaultHandlers() - .decorator(component -> text() - .append("[Example] ", NamedTextColor.DARK_RED) - .append(component) - .build() - ) - .registerTo(manager); -``` +{{ snippet("minecraft/MinecraftExceptionHandlerExample.java", section = "complete", title = "Complete example") }} + +### Localization + +`MinecraftExceptionHandler` uses the [localization](../localization/index.md) system. By default, the exception +handler will make use of a +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-minecraft-extras/latest/org/incendo/cloud/minecraft/extras/ComponentCaptionFormatter.html", "ComponentCaptionFormatter") }} +that wraps the caption value in a text component. + +You may choose to replace the caption formatter with a component formatter that uses [MiniMessage](https://docs.advntr.dev/minimessage/index.html) by using +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-minecraft-extras/latest/org/incendo/cloud/minecraft/extras/ComponentCaptionFormatter.html", "ComponentCaptionFormatter.miniMessage()") }}. +[MiniMessage](https://docs.advntr.dev/minimessage/index.html) will then be used for both styling and placeholder replacements. ## Rich Description diff --git a/docs/minecraft/modded/index.md b/docs/minecraft/modded/index.md index ecd9859..e9228ec 100644 --- a/docs/minecraft/modded/index.md +++ b/docs/minecraft/modded/index.md @@ -14,7 +14,7 @@ | Minecraft version range | `cloud-minecraft-modded` version | | ----------------------- | -------------------------------- | -| 1.19.4+ | 2.0.0-beta.1 | +| 1.19.4+ | {{ version.modded }} | Keep in mind only the latest release is supported. diff --git a/docs/minecraft/paper.md b/docs/minecraft/paper.md index 80ac3d5..079e2b7 100644 --- a/docs/minecraft/paper.md +++ b/docs/minecraft/paper.md @@ -20,35 +20,12 @@ The following documentation is written with the assumption that you have already Cloud for Paper is available through [Maven Central](https://central.sonatype.com/artifact/org.incendo/cloud-paper). - -=== "Maven" - - ```xml - - - org.incendo - cloud-paper - 2.0.0-beta.1 - - - ``` - -=== "Gradle (Kotlin)" - - ```kotlin - implementation("org.incendo:cloud-paper:2.0.0-beta.1") - ``` - -=== "Gradle (Groovy)" - - ```groovy - implementation 'org.incendo:cloud-paper:2.0.0-beta.1' - ``` +{{ dependency_listing("paper") }} ## Usage `cloud-paper` has a command manager implementation called -[`PaperCommandManager`](https://javadoc.io/doc/org.incendo/cloud-paper/latest/org/incendo/cloud/paper/PaperCommandManager.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-paper/latest/org/incendo/cloud/paper/PaperCommandManager.html", "PaperCommandManager") }} that can be created in two ways. With a custom sender type: @@ -61,7 +38,7 @@ PaperCommandManager commandManager = new PaperCommandManager<>( ); ``` -Or, using Bukkit's [`CommandSender`](https://jd.papermc.io/paper/1.20/org/bukkit/command/CommandSender.html): +Or, using Bukkit's {{ javadoc("https://jd.papermc.io/paper/1.20/org/bukkit/command/CommandSender.html", "CommandSender") }}: ```java PaperCommandManager commandManager = PaperCommandManager.createNative( @@ -76,9 +53,9 @@ PaperCommandManager commandManager = PaperCommandManager.createNa [here](../core/index.md#execution-coordinators). See [below](#execution-coordinators) for info specific to Bukkit-based platforms. 3. The sender mapper is a two-way mapping between Bukkit's - [`CommandSender`](https://jd.papermc.io/paper/1.20/org/bukkit/command/CommandSender.html) and your custom sender type. - Using [`SenderMapper.identity()`]() - is equivalent to the [`createNative`]() + {{ javadoc("https://jd.papermc.io/paper/1.20/org/bukkit/command/CommandSender.html", "CommandSender") }} and your custom sender type. + Using {{ javadoc("", "SenderMapper.identity()") }} + is equivalent to the {{ javadoc("", "createNative") }} static factory method. ## Brigadier @@ -87,7 +64,7 @@ Paper exposes [Brigadier](https://github.com/mojang/brigadier), which means that from [cloud-brigadier](brigadier.md) on Paper servers. You may enable Brigadier mappings using -[`PaperCommandManager#registerBrigadier()`](). +{{ javadoc("", "PaperCommandManager#registerBrigadier()") }}. You should make use of the capability system to make sure that Brigadier is available on the server your plugin is running on: @@ -108,7 +85,7 @@ Paper allows for non-blocking suggestions. You are highly recommended to make us the argument parsers during suggestion generation which ideally should not take place on the main server thread. You may enable asynchronous completions using -[`PaperCommandManager#registerAsynchronousCompletions()`](). +{{ javadoc("", "PaperCommandManager#registerAsynchronousCompletions()") }}. You should make use of the capability system to make sure that this is available on the server your plugin is running on: ```java @@ -120,9 +97,9 @@ if (commandManager.hasCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION ## Execution coordinators Due to Bukkit blocking the main thread for suggestion requests, it's potentially unsafe to use anything other than -[`ExecutionCoordinator.nonSchedulingExecutor()`]() +{{ javadoc("", "ExecutionCoordinator.nonSchedulingExecutor()") }} for -[`ExecutionCoordinator.Builder#suggestionsExecutor(Executor)`](). +{{ javadoc("", "ExecutionCoordinator.Builder#suggestionsExecutor(Executor)") }}. Once the coordinator, a suggestion provider, parser, or similar routes suggestion logic off of the calling \(main) thread, it won't be possible to schedule further logic back to the main thread without a deadlock. When Brigadier support is active, this issue is avoided, as it allows diff --git a/docs/minecraft/velocity.md b/docs/minecraft/velocity.md index b69bf01..76085a0 100644 --- a/docs/minecraft/velocity.md +++ b/docs/minecraft/velocity.md @@ -16,35 +16,12 @@ Cloud for Velocity is available through [Maven Central](https://central.sonatype.com/artifact/org.incendo/cloud-paper). - -=== "Maven" - - ```xml - - - org.incendo - cloud-velocity - 2.0.0-beta.1 - - - ``` - -=== "Gradle (Kotlin)" - - ```kotlin - implementation("org.incendo:cloud-velocity:2.0.0-beta.1") - ``` - -=== "Gradle (Groovy)" - - ```groovy - implementation 'org.incendo:cloud-velocity:2.0.0-beta.1' - ``` +{{ dependency_listing("velocity") }} ## Usage `cloud-velocity` has a command manager implementation called -[`VelocityCommandManager`](https://javadoc.io/doc/org.incendo/cloud-velocity/latest/org/incendo/cloud/velocity/VelocityCommandManager.html) +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-velocity/latest/org/incendo/cloud/velocity/VelocityCommandManager.html", "VelocityCommandManager") }} that can be created in two ways. By using a Guice injector: @@ -78,9 +55,9 @@ VelocityCommandManager commandManager = new VelocityCommandManag 1. Information about execution coordinators in general can be found [here](../core/index.md#execution-coordinators). 2. The sender mapper is a two-way mapping between Velocity's - [`CommandSource`](https://jd.papermc.io/velocity/3.0.0/com/velocitypowered/api/command/CommandSource.html) and your custom sender type. - You may use [`SenderMapper.identity()`]() - if using [`CommandSource`](https://jd.papermc.io/velocity/3.0.0/com/velocitypowered/api/command/CommandSource.html) as the sender type. + {{ javadoc("https://jd.papermc.io/velocity/3.0.0/com/velocitypowered/api/command/CommandSource.html", "CommandSource") }} and your custom sender type. + You may use {{ javadoc("", "SenderMapper.identity()") }} + if using {{ javadoc("https://jd.papermc.io/velocity/3.0.0/com/velocitypowered/api/command/CommandSource.html", "CommandSource") }} as the sender type. ## Brigadier @@ -93,3 +70,11 @@ use the features from [cloud-brigadier](brigadier.md). | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | [PlayerParser](https://javadoc.io/doc/org.incendo/cloud-velocity/latest/org/incendo/cloud/velocity/parser/PlayerParser.html) | [Player](https://jd.papermc.io/velocity/3.0.0/com/velocitypowered/api/proxy/Player.html) | | [ServerParser](https://javadoc.io/doc/org.incendo/cloud-velocity/latest/org/incendo/cloud/velocity/parser/ServerParser.html) | [RegisteredServer](https://jd.papermc.io/velocity/3.0.0/com/velocitypowered/api/proxy/server/RegisteredServer.html) | + +## Localization + +`cloud-velocity` provides additional caption keys for the [localization](../localization/index.md) system. +These can be found in +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-velocity/latest/org/incendo/cloud/velocity/VelocityCaptionKeys.html", "VelocityCaptionKeys") }}. +The default caption values can be found in +{{ javadoc("https://javadoc.io/doc/org.incendo/cloud-velocity/latest/org/incendo/cloud/velocity/VelocityCommandManager.html", "VelocityCommandManager") }}. diff --git a/docs/processors/confirmation.md b/docs/processors/confirmation.md index 5208c7a..85cf7f0 100644 --- a/docs/processors/confirmation.md +++ b/docs/processors/confirmation.md @@ -4,21 +4,7 @@ Postprocessor that adds the ability to require an extra confirmation before exec ## Installation -Snapshots are available on the Sonatype Snapshots Repository: - -```xml - - - sonatype-snapshots - https://oss.sonatype.org/content/repositories/snapshots/ - - - - org.incendo - cloud-processors-confirmation - 1.0.0-beta.1 - -``` +{{ dependency_listing("processors-confirmation", "processors") }} ## Usage diff --git a/docs/processors/cooldown.md b/docs/processors/cooldown.md index f630969..2b077c5 100644 --- a/docs/processors/cooldown.md +++ b/docs/processors/cooldown.md @@ -4,21 +4,7 @@ Postprocessor that adds command cooldowns. ## Installation -Snapshots are available on the Sonatype Snapshots Repository: - -```xml - - - sonatype-snapshots - https://oss.sonatype.org/content/repositories/snapshots/ - - - - org.incendo - cloud-processors-cooldown - 1.0.0-beta.1 - -``` +{{ dependency_listing("processors-cooldown", "processors") }} ## Usage diff --git a/docs/processors/requirements.md b/docs/processors/requirements.md index fd2cfc5..53c66ae 100644 --- a/docs/processors/requirements.md +++ b/docs/processors/requirements.md @@ -8,21 +8,7 @@ are defined on a per-command basis. ## Installation -Snapshots are available on the Sonatype Snapshots Repository: - -```xml - - - sonatype-snapshots - https://oss.sonatype.org/content/repositories/snapshots/ - - - - org.incendo - cloud-processors-requirements - 1.0.0-beta.1 - -``` +{{ dependency_listing("processors-requirements", "processors") }} ## Usage diff --git a/docs/requirements.txt b/docs/requirements.txt index 5c17bde..5a72962 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,3 +2,4 @@ mkdocs mkdocs-material mkdocs-git-revision-date-localized-plugin mkdocs-git-committers-plugin-2 +mkdocs-macros-plugin diff --git a/docs/spring/index.md b/docs/spring/index.md index 4f5ec17..bb0dff2 100644 --- a/docs/spring/index.md +++ b/docs/spring/index.md @@ -38,15 +38,7 @@ The example module contains a Spring Boot application with a couple of commands. ## usage -```xml - - - org.incendo - cloud-spring - 1.0.0-beta.1 - - -``` +{{ dependency_listing("spring") }} You should first familiarize yourself with the [cloud-core](../core/index.md) docs. The easiest way to use the `SpringCommandManager` is by using `SpringCommandSender` as the command sender type, diff --git a/main.py b/main.py new file mode 100644 index 0000000..613a5d2 --- /dev/null +++ b/main.py @@ -0,0 +1,63 @@ +def define_env(env): + @env.macro + def dependency_listing(name: str, version: str = None) -> str: + if version is None: + version = name + + return """ + +=== "Maven" + + ```xml + + + org.incendo + cloud-{name} + {version} + + + ``` + +=== "Gradle (Kotlin)" + + ```kotlin + implementation("org.incendo:cloud-{name}:{version}") + ``` + +=== "Gradle (Groovy)" + + ```groovy + implementation 'org.incendo:cloud-{name}:{version}' + ``` + """.format(name=name, version=env.variables.version[version]) + + @env.macro + def javadoc(link: str, title: str = None) -> str: + if title is None: + split = link.split("/") + title = split[len(split) - 1].replace(".html", "") + if link.startswith("<"): + link = link[1: len(link) - 1] + + return '[`{title}`](<{link}> "Click to open the JavaDoc")'.format(link=link, title=title) + + @env.macro + def snippet(path: str, section: str = "snippet", title: str = None, indent = 0) -> str: + if title is None: + title = path + if title: + title = 'title="{title}"'.format(title = title) + + if section is not None: + path = path + ":" + section + + return ''.join((' ' * indent) + line for line in """ +```java {title} +--8<-- "{path}" +``` + """.format(path=path, title=title).splitlines(True)) + + @env.macro + def figure(path: str, caption: str) -> str: + return ("
![{caption}]({path})
{caption}
" + .format(path = path, caption = caption)) diff --git a/mkdocs.yml b/mkdocs.yml index ff24e7b..46b8a97 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,47 +7,48 @@ edit_uri: edit/main/docs/ nav: - Cloud: - Cloud: index.md - - "Cloud v2": cloud-v2.md - - "Cloud v1 docs ⮺": https://github.com/Incendo/cloud/tree/1.9.0-dev/docs - - cloud-core: - - cloud-core: core/index.md - - cloud-annotations: - - cloud-annotations: annotations/index.md - - cloud-kotlin: - - cloud-kotlin: kotlin/index.md - - annotations: kotlin/annotations.md - - coroutines: kotlin/coroutines.md - - extensions: kotlin/extensions.md - - cloud-discord: - - cloud-discord: discord/index.md - - cloud-discord4j: discord/discord4j.md - - "cloud-javacord ⮺": https://github.com/Incendo/cloud-discord/tree/master/cloud-javacord - - "cloud-jda ⮺": https://github.com/Incendo/cloud-discord/tree/master/cloud-jda - - cloud-jda5: discord/jda5.md - - cloud-kord: discord/kord.md - - cloud-minecraft: - - cloud-minecraft: minecraft/index.md - - cloud-brigadier: minecraft/brigadier.md - - cloud-minecraft-extras: minecraft/minecraft-extras.md - - cloud-bukkit: minecraft/bukkit.md - - cloud-paper: minecraft/paper.md - - cloud-velocity: minecraft/velocity.md - - cloud-bungee: minecraft/bungee.md - - cloud-cloudburst: minecraft/cloudburst.md - - cloud-sponge: minecraft/sponge.md - - cloud-minecraft-modded: - - cloud-minecraft-modded: minecraft/modded/index.md - - cloud-fabric: minecraft/modded/fabric.md - - cloud-neoforge: minecraft/modded/neoforge.md - - cloud-spring: - - cloud-spring: spring/index.md - - cloud-processors: - - cloud-processors: processors/index.md - - confirmation: processors/confirmation.md - - cooldown: processors/cooldown.md - - requirements: processors/requirements.md - - Other: - - cloud-irc: other/irc.md + - Changelog: cloud-v2.md + - Core: core/index.md + - Localization: localization/index.md + - Annotations: + - Annotations: annotations/index.md + - Kotlin: + - Kotlin: kotlin/index.md + - Annotations: kotlin/annotations.md + - Coroutines: kotlin/coroutines.md + - Extensions: kotlin/extensions.md + - Discord: + - Discord: discord/index.md + - Discord4J: discord/discord4j.md + - Javacord: https://github.com/Incendo/cloud-discord/tree/master/cloud-javacord + - JDA: https://github.com/Incendo/cloud-discord/tree/master/cloud-jda + - JDA5: discord/jda5.md + - Kord: discord/kord.md + - Minecraft: + - Minecraft: minecraft/index.md + - Brigadier: minecraft/brigadier.md + - "Minecraft Extras": minecraft/minecraft-extras.md + - Platforms: + - Bukkit: minecraft/bukkit.md + - Paper: minecraft/paper.md + - Velocity: minecraft/velocity.md + - Bungee: minecraft/bungee.md + - Cloudburst: minecraft/cloudburst.md + - Sponge: minecraft/sponge.md + - Modded Minecraft: + - cloud-minecraft-modded: minecraft/modded/index.md + - Fabric: minecraft/modded/fabric.md + - NeoForge: minecraft/modded/neoforge.md + - Spring: + - spring: spring/index.md + - Processors: + - Processors: processors/index.md + - Confirmation: processors/confirmation.md + - Cooldown: processors/cooldown.md + - Requirements: processors/requirements.md + # - Misc: + # - irc: other/irc.md + - "Legacy Docs": https://github.com/Incendo/cloud/tree/1.9.0-dev/docs theme: name: material features: @@ -58,6 +59,7 @@ theme: - navigation.indexes - navigation.top - navigation.footer + # - navigation.expand - toc.follow - search.suggest - search.highlight @@ -104,10 +106,18 @@ markdown_extensions: - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.snippets: + base_path: + - "./code/src/main/java/org/incendo/cloud/snippet/" + check_paths: true + dedent_subsections: true - attr_list - md_in_html plugins: - search + - macros: + include_yaml: + - version: versions.yml - git-revision-date-localized: enable_creation_date: false fallback_to_build_date: true @@ -148,3 +158,6 @@ extra: copyright: > Copyright © 2024 Incendo – Change cookie settings +watch: + - code + - main.py diff --git a/renovate.json b/renovate.json index 20584c1..71d0409 100644 --- a/renovate.json +++ b/renovate.json @@ -5,8 +5,12 @@ "labels": ["dependencies"], "packageRules": [ { - "matchManagers": ["github-actions"], - "groupName": "github actions" + "matchManagers": ["github-actions", "gradle-wrapper"], + "groupName": "gradle and github actions" + }, + { + "matchDepTypes": ["plugin"], + "groupName": "gradle and github actions" } ], "semanticCommitType": "build", diff --git a/versions.yml b/versions.yml new file mode 100644 index 0000000..268e12d --- /dev/null +++ b/versions.yml @@ -0,0 +1,15 @@ +core: 2.0.0-beta.2 +processors: 1.0.0-beta.1 +# Discord +discord4j: 1.0.0-beta.2 +jda5: 1.0.0-beta.2 +kord: 1.0.0-beta.2 +# Minecraft +paper: 2.0.0-beta.2 +velocity: 2.0.0-beta.2 +bungee: 2.0.0-beta.2 +cloudburst: 2.0.0-beta.2 +modded: 2.0.0-beta.2 +minecraft-extras: 2.0.0-beta.2 +# Other +spring: 1.0.0-beta.1