From 78da071e86c8a9df92a2b1128e5785c2fd426b5a Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Thu, 19 Jan 2023 11:32:37 +0000 Subject: [PATCH] Document String array option type - Backport #558 as it brings missing option type docs - Backport #628 - Fixes #629 --- .../asciidoc/using-shell-options-types.adoc | 137 ++++++++++ .../main/asciidoc/using-shell-options.adoc | 4 + .../shell/docs/OptionTypesSnippets.java | 182 +++++++++++++ .../shell/samples/e2e/BaseE2ECommands.java | 29 +- .../shell/samples/e2e/OptionTypeCommands.java | 250 +++++++++++++++++- 5 files changed, 600 insertions(+), 2 deletions(-) create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell-options-types.adoc create mode 100644 spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionTypesSnippets.java diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-options-types.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-options-types.adoc new file mode 100644 index 00000000..fac265bb --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell-options-types.adoc @@ -0,0 +1,137 @@ +[[using-shell-options-types]] +=== Types +ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs] + +This section talks about how particular data type is used as an option value. + +==== String + +`String` is a most simplest type as there's no conversion involved as what's +coming in from a user is always a string. + +==== +[source, java, indent=0] +---- +include::{snippets}/OptionTypesSnippets.java[tag=option-type-string-anno] +---- +==== + +While it's not strictly required to define type as a `String` it's always +adviced to do so. + +==== +[source, java, indent=0] +---- +include::{snippets}/OptionTypesSnippets.java[tag=option-type-string-reg] +---- +==== + +==== Boolean + +Using boolean types is a bit more involved as there are `boolean` and +`Boolean` where latter can be _null_. Boolean types are usually used as +flags meaning argument value may not be needed. + +==== +[source, java, indent=0] +---- +include::{snippets}/OptionTypesSnippets.java[tag=option-type-boolean-anno] +---- +==== + +==== +[source, bash] +---- +shell:>example +arg1=false arg2=true arg3=false arg4=false arg5=true arg6=false + +shell:>example --arg4 +arg1=false arg2=true arg3=false arg4=true arg5=true arg6=false + +shell:>example --arg4 false +arg1=false arg2=true arg3=false arg4=false arg5=true arg6=false +---- +==== + +==== +[source, java, indent=0] +---- +include::{snippets}/OptionTypesSnippets.java[tag=option-type-boolean-reg] +---- +==== + +==== +[source, bash] +---- +shell:>example +arg1=false arg2=true arg3=false arg4=null arg5=true arg6=false + +shell:>example --arg4 +arg1=false arg2=true arg3=false arg4=true arg5=true arg6=false + +shell:>example --arg4 false +arg1=false arg2=true arg3=false arg4=false arg5=true arg6=false +---- +==== + +==== Number + +Numbers are converted as is. + +==== +[source, java, indent=0] +---- +include::{snippets}/OptionTypesSnippets.java[tag=option-type-integer-anno] +---- +==== + +==== +[source, java, indent=0] +---- +include::{snippets}/OptionTypesSnippets.java[tag=option-type-integer-reg] +---- +==== + +==== Enum + +Conversion to enums is possible if given value is exactly matching enum itself. +Currently you can convert assuming case insensitivity. + +==== +[source, java, indent=0] +---- +include::{snippets}/OptionTypesSnippets.java[tag=option-type-enum-class] +---- +==== + +==== +[source, java, indent=0] +---- +include::{snippets}/OptionTypesSnippets.java[tag=option-type-enum-anno] +---- +==== + +==== +[source, java, indent=0] +---- +include::{snippets}/OptionTypesSnippets.java[tag=option-type-enum-reg] +---- +==== + +==== Array + +Arrays can be used as is with strings and primitive types. + +==== +[source, java, indent=0] +---- +include::{snippets}/OptionTypesSnippets.java[tag=option-type-string-array-anno] +---- +==== + +==== +[source, java, indent=0] +---- +include::{snippets}/OptionTypesSnippets.java[tag=option-type-string-array-reg] +---- +==== diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-options.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-options.adoc index a61ac564..05bf1305 100644 --- a/spring-shell-docs/src/main/asciidoc/using-shell-options.adoc +++ b/spring-shell-docs/src/main/asciidoc/using-shell-options.adoc @@ -20,3 +20,7 @@ include::using-shell-options-default.adoc[] include::using-shell-options-validation.adoc[] include::using-shell-options-label.adoc[] + +include::using-shell-options-types.adoc[] + +include::using-shell-options-naming.adoc[] diff --git a/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionTypesSnippets.java b/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionTypesSnippets.java new file mode 100644 index 00000000..a5542c25 --- /dev/null +++ b/spring-shell-docs/src/test/java/org/springframework/shell/docs/OptionTypesSnippets.java @@ -0,0 +1,182 @@ +/* + * Copyright 2022 the original author or 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. + */ +package org.springframework.shell.docs; + +import org.springframework.shell.command.CommandRegistration; +import org.springframework.shell.standard.ShellOption; + +class OptionTypesSnippets { + + class Dump1 { + // tag::option-type-boolean-anno[] + String example( + @ShellOption() boolean arg1, + @ShellOption(defaultValue = "true") boolean arg2, + @ShellOption(defaultValue = "false") boolean arg3, + @ShellOption() Boolean arg4, + @ShellOption(defaultValue = "true") Boolean arg5, + @ShellOption(defaultValue = "false") Boolean arg6 + ) { + return String.format("arg1=%s arg2=%s arg3=%s arg4=%s arg5=%s arg6=%s", + arg1, arg2, arg3, arg4, arg5, arg6); + } + // end::option-type-boolean-anno[] + void dump() { + // tag::option-type-boolean-reg[] + CommandRegistration.builder() + .command("example") + .withOption() + .longNames("arg1").type(boolean.class).and() + .withOption() + .longNames("arg2").type(boolean.class).defaultValue("true").and() + .withOption() + .longNames("arg3").type(boolean.class).defaultValue("false").and() + .withOption() + .longNames("arg4").type(Boolean.class).and() + .withOption() + .longNames("arg5").type(Boolean.class).defaultValue("true").and() + .withOption() + .longNames("arg6").type(Boolean.class).defaultValue("false").and() + .withTarget() + .function(ctx -> { + boolean arg1 = ctx.hasMappedOption("arg1") + ? ctx.getOptionValue("arg1") + : false; + boolean arg2 = ctx.getOptionValue("arg2"); + boolean arg3 = ctx.getOptionValue("arg3"); + Boolean arg4 = ctx.getOptionValue("arg4"); + Boolean arg5 = ctx.getOptionValue("arg5"); + Boolean arg6 = ctx.getOptionValue("arg6"); + return String.format("Hello arg1=%s arg2=%s arg3=%s arg4=%s arg5=%s arg6=%s", + arg1, arg2, arg3, arg4, arg5, arg6); + }) + .and() + .build(); + // end::option-type-boolean-reg[] + } + } + + class Dump2 { + // tag::option-type-integer-anno[] + String example(@ShellOption(value = "arg1") int arg1) { + return "Hello " + arg1; + } + // end::option-type-integer-anno[] + void dump() { + // tag::option-type-integer-reg[] + CommandRegistration.builder() + .command("example") + .withOption() + .longNames("arg1") + .type(int.class) + .required() + .and() + .withTarget() + .function(ctx -> { + boolean arg1 = ctx.getOptionValue("arg1"); + return "Hello " + arg1; + }) + .and() + .build(); + // end::option-type-integer-reg[] + } + } + + class Dump3 { + // tag::option-type-string-anno[] + String example(@ShellOption(value = "arg1") String arg1) { + return "Hello " + arg1; + } + // end::option-type-string-anno[] + void dump() { + // tag::option-type-string-reg[] + CommandRegistration.builder() + .command("example") + .withOption() + .longNames("arg1") + .type(String.class) + .required() + .and() + .withTarget() + .function(ctx -> { + String arg1 = ctx.getOptionValue("arg1"); + return "Hello " + arg1; + }) + .and() + .build(); + // end::option-type-string-reg[] + } + } + + static class Dump4 { + + // tag::option-type-enum-class[] + enum OptionTypeEnum { + ONE,TWO,THREE + } + // end::option-type-enum-class[] + + // tag::option-type-enum-anno[] + String example(@ShellOption(value = "arg1") OptionTypeEnum arg1) { + return "Hello " + arg1; + } + // end::option-type-enum-anno[] + void dump() { + // tag::option-type-enum-reg[] + CommandRegistration.builder() + .command("example") + .withOption() + .longNames("arg1") + .type(OptionTypeEnum.class) + .required() + .and() + .withTarget() + .function(ctx -> { + OptionTypeEnum arg1 = ctx.getOptionValue("arg1"); + return "Hello " + arg1; + }) + .and() + .build(); + // end::option-type-enum-reg[] + } + } + + class Dump5 { + // tag::option-type-string-array-anno[] + String example(@ShellOption(value = "arg1") String[] arg1) { + return "Hello " + arg1; + } + // end::option-type-string-array-anno[] + void dump() { + // tag::option-type-string-array-reg[] + CommandRegistration.builder() + .command("example") + .withOption() + .longNames("arg1") + .type(String[].class) + .required() + .and() + .withTarget() + .function(ctx -> { + String[] arg1 = ctx.getOptionValue("arg1"); + return "Hello " + arg1; + }) + .and() + .build(); + // end::option-type-string-array-reg[] + } + } +} diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/BaseE2ECommands.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/BaseE2ECommands.java index 5bfe2102..0d4d5e30 100644 --- a/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/BaseE2ECommands.java +++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/BaseE2ECommands.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2022-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,11 @@ */ package org.springframework.shell.samples.e2e; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.springframework.util.StringUtils; + /** * Base class for all e2e commands. * @@ -25,4 +30,26 @@ abstract class BaseE2ECommands { static final String GROUP = "E2E Commands"; static final String REG = "e2e reg"; static final String LEGACY_ANNO = "e2e anno "; + + static String stringOfStrings(String[] values) { + return String.format("[%s]", StringUtils.arrayToCommaDelimitedString(values)); + } + + static String stringOfInts(int[] values) { + String joined = IntStream.range(0, values.length) + .mapToLong(i -> values[i]) + .boxed() + .map(d -> d.toString()) + .collect(Collectors.joining(",")); + return String.format("[%s]", joined); + } + + static String stringOfFloats(float[] values) { + String joined = IntStream.range(0, values.length) + .mapToDouble(i -> values[i]) + .boxed() + .map(d -> d.toString()) + .collect(Collectors.joining(",")); + return String.format("[%s]", joined); + } } diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/OptionTypeCommands.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/OptionTypeCommands.java index 7238f8ed..5a48e404 100644 --- a/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/OptionTypeCommands.java +++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/OptionTypeCommands.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2022-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ import java.io.PrintWriter; import org.springframework.context.annotation.Bean; import org.springframework.shell.command.CommandRegistration; import org.springframework.shell.standard.ShellComponent; +import org.springframework.shell.standard.ShellMethod; +import org.springframework.shell.standard.ShellOption; /** * Commands used for e2e test. @@ -62,4 +64,250 @@ public class OptionTypeCommands extends BaseE2ECommands { .build(); } + // + // String + // + + @ShellMethod(key = LEGACY_ANNO + "option-type-string", group = GROUP) + public String optionTypeStringAnnotation( + @ShellOption(help = "Desc arg1") String arg1 + ) { + return "Hello " + arg1; + } + + @Bean + public CommandRegistration optionTypeStringRegistration() { + return CommandRegistration.builder() + .command(REG, "option-type-string") + .group(GROUP) + .withOption() + .longNames("arg1") + .type(String.class) + .required() + .and() + .withTarget() + .function(ctx -> { + String arg1 = ctx.getOptionValue("arg1"); + return "Hello " + arg1; + }) + .and() + .build(); + } + + // + // Boolean + // + + @ShellMethod(key = LEGACY_ANNO + "option-type-boolean", group = GROUP) + public String optionTypeBooleanAnnotation( + @ShellOption() boolean arg1, + @ShellOption(defaultValue = "true") boolean arg2, + @ShellOption(defaultValue = "false") boolean arg3, + @ShellOption() Boolean arg4, + @ShellOption(defaultValue = "true") Boolean arg5, + @ShellOption(defaultValue = "false") Boolean arg6 + ) { + return String.format("Hello arg1=%s arg2=%s arg3=%s arg4=%s arg5=%s arg6=%s", arg1, arg2, arg3, arg4, arg5, + arg6); + } + + @Bean + public CommandRegistration optionTypeBooleanRegistration() { + return CommandRegistration.builder() + .command(REG, "option-type-boolean") + .group(GROUP) + .withOption() + .longNames("arg1") + .type(boolean.class) + .and() + .withOption() + .longNames("arg2") + .type(boolean.class) + .defaultValue("true") + .and() + .withOption() + .longNames("arg3") + .type(boolean.class) + .defaultValue("false") + .and() + .withOption() + .longNames("arg4") + .type(Boolean.class) + .and() + .withOption() + .longNames("arg5") + .type(Boolean.class) + .defaultValue("true") + .and() + .withOption() + .longNames("arg6") + .type(Boolean.class) + .defaultValue("false") + .and() + .withTarget() + .function(ctx -> { + boolean arg1 = ctx.hasMappedOption("arg1") ? ctx.getOptionValue("arg1") : false; + boolean arg2 = ctx.getOptionValue("arg2"); + boolean arg3 = ctx.getOptionValue("arg3"); + Boolean arg4 = ctx.getOptionValue("arg4"); + Boolean arg5 = ctx.getOptionValue("arg5"); + Boolean arg6 = ctx.getOptionValue("arg6"); + return String.format("Hello arg1=%s arg2=%s arg3=%s arg4=%s arg5=%s arg6=%s", arg1, arg2, arg3, + arg4, arg5, arg6); + }) + .and() + .build(); + } + + // + // Integer + // + + @ShellMethod(key = LEGACY_ANNO + "option-type-integer", group = GROUP) + public String optionTypeIntegerAnnotation( + @ShellOption int arg1, + @ShellOption Integer arg2 + ) { + return String.format("Hello '%s' '%s'", arg1, arg2); + } + + @Bean + public CommandRegistration optionTypeIntegerRegistration() { + return CommandRegistration.builder() + .command(REG, "option-type-integer") + .group(GROUP) + .withOption() + .longNames("arg1") + .type(int.class) + .required() + .and() + .withOption() + .longNames("arg2") + .type(Integer.class) + .required() + .and() + .withTarget() + .function(ctx -> { + int arg1 = ctx.getOptionValue("arg1"); + Integer arg2 = ctx.getOptionValue("arg2"); + return String.format("Hello '%s' '%s'", arg1, arg2); + }) + .and() + .build(); + } + + // + // Enum + // + + public static enum OptionTypeEnum { + ONE,TWO,THREE + } + + @ShellMethod(key = LEGACY_ANNO + "option-type-enum", group = GROUP) + public String optionTypeEnumAnnotation( + @ShellOption(help = "Desc arg1") OptionTypeEnum arg1 + ) { + return "Hello " + arg1; + } + + @Bean + public CommandRegistration optionTypeEnumRegistration() { + return CommandRegistration.builder() + .command(REG, "option-type-enum") + .group(GROUP) + .withOption() + .longNames("arg1") + .type(OptionTypeEnum.class) + .required() + .and() + .withTarget() + .function(ctx -> { + OptionTypeEnum arg1 = ctx.getOptionValue("arg1"); + return "Hello " + arg1; + }) + .and() + .build(); + } + + // + // String[] + // + + @ShellMethod(key = LEGACY_ANNO + "option-type-string-array", group = GROUP) + public String optionTypeStringArrayAnnotation( + @ShellOption(help = "Desc arg1") String[] arg1 + ) { + return "Hello " + stringOfStrings(arg1); + } + + @Bean + public CommandRegistration optionTypeStringArrayRegistration() { + return CommandRegistration.builder() + .command(REG, "option-type-string-array") + .group(GROUP) + .withOption() + .longNames("arg1") + .type(String[].class) + .required() + .and() + .withTarget() + .function(ctx -> { + String[] arg1 = ctx.getOptionValue("arg1"); + return "Hello " + stringOfStrings(arg1); + }) + .and() + .build(); + } + // + // int[] + // + + @ShellMethod(key = LEGACY_ANNO + "option-type-int-array", group = GROUP) + public String optionTypeIntArrayAnnotation( + @ShellOption(help = "Desc arg1") int[] arg1 + ) { + return "Hello " + stringOfInts(arg1); + } + + @Bean + public CommandRegistration optionTypeIntArrayRegistration() { + return CommandRegistration.builder() + .command(REG, "option-type-int-array") + .group(GROUP) + .withOption() + .longNames("arg1") + .type(int[].class) + .required() + .and() + .withTarget() + .function(ctx -> { + int[] arg1 = ctx.getOptionValue("arg1"); + return "Hello " + stringOfInts(arg1); + }) + .and() + .build(); + } + + // + // Void + // + + @Bean + public CommandRegistration optionTypeVoidRegistration() { + return CommandRegistration.builder() + .command(REG, "option-type-void") + .group(GROUP) + .withOption() + .longNames("arg1") + .type(void.class) + .and() + .withTarget() + .function(ctx -> { + return "Hello "; + }) + .and() + .build(); + } + }