Rework command subsystem
- Focus of these changes are to introduce a new command system based on real registrations (new way) instead of continuously (old way) resolve methods and its parameters via reflection. - There's a lot of changes as this resolution via reflection had its hooks almost everywhere and thus most changes are just refactorings. - Order to understand real changes I'd start to look classes under `org.springframework.shell.command` package as it defines new registration, catalog and parser classes. Also samples contain new classes to demonstrate new functionality. - Fixes #380
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
=== Command Catalog
|
||||
`CommandCatalog` is an interface defining how command registrations exists in
|
||||
a shell application. It is possible to dynamically register and de-register
|
||||
commands which gives flexibility for a user cases where possible commands will
|
||||
come and go depending on a states shell is at.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/CommandCatalogSnippets.java[tag=snippet1]
|
||||
----
|
||||
====
|
||||
|
||||
==== Command Resolver
|
||||
`CommandResolver` is an interface you can implement and define as a bean to dynamically
|
||||
resolve mappings from a command names to its `CommandRegistration` instances. Its use
|
||||
case looks something like:
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/CommandCatalogSnippets.java[tag=snippet2]
|
||||
----
|
||||
====
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
Current limitation of a `CommandResolver` is that it is used every time commands are resolved.
|
||||
Thus it's adviced not to use it if command resolve call takes a long time as it would
|
||||
make shell feel sluggish.
|
||||
====
|
||||
|
||||
==== Command Catalog Customizer
|
||||
`CommandCatalogCustomizer` is an interface which can be used to customize a `CommandCatalog`.
|
||||
Its main use case is to modify catalog and within `spring-shell` _auto-configuration_ this
|
||||
interface is used to register existing `CommandRegistration` beans into a catalog.
|
||||
Its use case looks something like:
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/CommandCatalogSnippets.java[tag=snippet3]
|
||||
----
|
||||
====
|
||||
|
||||
Create `CommandCatalogCustomizer` as a bean and `spring-shell` will handle rest.
|
||||
@@ -0,0 +1,20 @@
|
||||
=== Command Context
|
||||
`CommandContext` is an interface which gives access to a currently executing
|
||||
context. It can be used to get access to options:
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/CommandContextSnippets.java[tag=snippet1]
|
||||
----
|
||||
====
|
||||
|
||||
If you need to print something into a shell you can get `Terminal`
|
||||
and use its writer to print something:
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/CommandContextSnippets.java[tag=snippet2]
|
||||
----
|
||||
====
|
||||
@@ -0,0 +1,3 @@
|
||||
=== Command Execution
|
||||
When _command parsing_ has done its job togethere with resolving _command registration_, execution
|
||||
will do the hard work and execute a real user level code.
|
||||
@@ -0,0 +1,3 @@
|
||||
=== Command Parser
|
||||
Before a command can be executed we need to parse commands and options provided by a user. Parsing
|
||||
sits between _command registration_ and _command execution_.
|
||||
@@ -0,0 +1,106 @@
|
||||
[#appendix-tech-intro-registration]
|
||||
=== Command Registration
|
||||
ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs]
|
||||
|
||||
Defining a command registation is a first step to introduce a structure of a commands and its options
|
||||
and parameters. This is loosely decoupled what happens later like parsing command-line and executing
|
||||
actual target code. Essentially it is a definition of an command API shown to a user.
|
||||
|
||||
==== Commands
|
||||
Command in a `spring-shell` structure is defined as an array of commands. This will give
|
||||
you something like:
|
||||
|
||||
====
|
||||
[source, bash]
|
||||
----
|
||||
command1 sub1
|
||||
command2 sub1 subsub1
|
||||
command2 sub2 subsub1
|
||||
command2 sub2 subsub2
|
||||
----
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
We don't currently support mapping commands to explicit parent if sub-commands are defined.
|
||||
For example there can't be `command1 sub1` and `command1 sub1 subsub1` registered.
|
||||
====
|
||||
|
||||
==== Interaction Mode
|
||||
Spring Shell has been designed to work on two modes one being interactive which essentially
|
||||
is a `REPL` where you have an active shell instance throughout commands and secondly
|
||||
non-interactive mode where commands are executed one by one from a command line.
|
||||
|
||||
Differentation between these modes are mostly around limitations what can be done
|
||||
in each mode as for example it would not be feasible to show what was a previous stacktrace
|
||||
of a command if shell is not alive anymore and generally things around information
|
||||
if shell is alive or not.
|
||||
|
||||
Also being on an active `REPL` session may provide more info about what user has been
|
||||
doing within an active session.
|
||||
|
||||
==== Options
|
||||
Options can be defined as long and short where prefixing is `--` and `-` respectively.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/CommandRegistrationSnippets.java[tag=snippet1]
|
||||
----
|
||||
====
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/CommandRegistrationSnippets.java[tag=snippet2]
|
||||
----
|
||||
====
|
||||
|
||||
==== Target
|
||||
Target defines what is an execution target of a command. It can be a _method_ in a `POJO`,
|
||||
`Consumer` or `Function`.
|
||||
|
||||
===== Method
|
||||
Using a `Method` is a way to define target as a method in an existing pojo.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/CommandTargetSnippets.java[tag=snippet11]
|
||||
----
|
||||
====
|
||||
|
||||
Having existing class shown above you can then register its method.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/CommandTargetSnippets.java[tag=snippet12]
|
||||
----
|
||||
====
|
||||
|
||||
===== Function
|
||||
Using a `Function` as a target gives a lot of flexibility to handle what
|
||||
happens in a command execution as you can handle many things manually using
|
||||
a `CommandContext` given to a `Function`. Return type from a `Function` is
|
||||
then what gets printed into a shell as a result.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/CommandTargetSnippets.java[tag=snippet2]
|
||||
----
|
||||
====
|
||||
|
||||
===== Consumer
|
||||
Using a `Consumer` is basically same as `Function` with difference being
|
||||
that there is not return type. If you need to print something into a shell
|
||||
you can get a reference to a `Terminal` from a context and print something
|
||||
through it.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/CommandTargetSnippets.java[tag=snippet3]
|
||||
----
|
||||
====
|
||||
@@ -0,0 +1,15 @@
|
||||
[appendix]
|
||||
[#appendix-tech-intro]
|
||||
== Techical Introduction
|
||||
This section contains information for a developers and others who would like to know more about how _spring-shell_
|
||||
internally works and what are its design decisions.
|
||||
|
||||
include::appendices-techical-intro-registration.adoc[]
|
||||
|
||||
include::appendices-techical-intro-parser.adoc[]
|
||||
|
||||
include::appendices-techical-intro-execution.adoc[]
|
||||
|
||||
include::appendices-techical-intro-commandcontext.adoc[]
|
||||
|
||||
include::appendices-techical-intro-commandcatalog.adoc[]
|
||||
1
spring-shell-docs/src/main/asciidoc/appendices.adoc
Normal file
1
spring-shell-docs/src/main/asciidoc/appendices.adoc
Normal file
@@ -0,0 +1 @@
|
||||
include::appendices-techical-intro.adoc[]
|
||||
@@ -20,3 +20,5 @@ include::introduction.adoc[]
|
||||
include::getting-started.adoc[]
|
||||
|
||||
include::using-shell.adoc[]
|
||||
|
||||
include::appendices.adoc[]
|
||||
|
||||
14
spring-shell-docs/src/main/asciidoc/using-shell-basics.adoc
Normal file
14
spring-shell-docs/src/main/asciidoc/using-shell-basics.adoc
Normal file
@@ -0,0 +1,14 @@
|
||||
[[using-shell-basics]]
|
||||
=== Basics
|
||||
You are here to learn basics of a _spring shell_. Before going forward to define actual _commands_ and _options_
|
||||
lets take this moment to go trough some fundamental concepts of a _spring shell_.
|
||||
|
||||
Essentially few things needs to happen before you have a working _spring shell_ app:
|
||||
|
||||
- Create a _spring boot_ application
|
||||
- Define commands and its option
|
||||
- Package an application
|
||||
- Execute either interactively or non-interactively
|
||||
|
||||
You will get a full working _spring shell_ application without defining any user level commands
|
||||
as some basic build-in commands are provided out of a box like `help` and `history`.
|
||||
@@ -1,13 +1,21 @@
|
||||
=== Writing Your Own Commands
|
||||
=== Define a Command
|
||||
ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs]
|
||||
|
||||
The way Spring Shell decides to turn a method into an actual shell command is entirely pluggable
|
||||
(see <<extending-spring-shell>>). However, as of Spring Shell 2.x, the recommended way to write commands
|
||||
is to use the new API described in this section (the standard API).
|
||||
In this section we go through an actual command registration and leave command options
|
||||
and execution later in a documentation. More detailed info can be found from
|
||||
<<appendix-tech-intro-registration>>.
|
||||
|
||||
There are two different ways to define a command. Firstly through an annotation model and
|
||||
secondly through programmatic model. Annotation model if where you define your methods
|
||||
in a class and annotate class and methods with a spesific annotations. Programmatic model
|
||||
is where things are done on a more low level ways by defining command registrations either
|
||||
as beans or registering those with a command catalog dynamically.
|
||||
|
||||
==== Annotation Model
|
||||
When you use the standard API, methods on beans are turned into executable commands, provided that:
|
||||
|
||||
* The bean class bears the `@ShellComponent` annotation. This is used to restrict the set of beans that
|
||||
are considered.
|
||||
* The bean class bears the `@ShellComponent` annotation. This is used to restrict the set of beans
|
||||
that are considered.
|
||||
* The method bears the `@ShellMethod` annotation.
|
||||
|
||||
[TIP]
|
||||
@@ -18,17 +26,22 @@ you can used it in addition to the filtering mechanism to declare beans (for exa
|
||||
You can customize the name of the created bean by using the `value` attribute of the annotation.
|
||||
====
|
||||
|
||||
[[documenting-the-command]]
|
||||
==== Documenting the Command
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/AnnotationRegistrationSnippets.java[tag=snippet1]
|
||||
----
|
||||
====
|
||||
|
||||
The only required attribute of the `@ShellMethod` annotation is its `value` attribute, which should have
|
||||
a short, one-sentence, description of what the command does. This lets your users
|
||||
get consistent help about your commands without having to leave the shell (see <<help-command>>).
|
||||
|
||||
NOTE: The description of your command should be short -- no more than one or two sentences. For better
|
||||
[NOTE]
|
||||
====
|
||||
The description of your command should be short -- no more than one or two sentences. For better
|
||||
consistency, it should starts with a capital letter and end with a period.
|
||||
|
||||
==== Customizing the Command Name(s)
|
||||
====
|
||||
|
||||
By default, there is no need to specify the key for your command (that is, the word(s) that should be used
|
||||
to invoke it in the shell). The name of the method is used as the command key, turning camelCase names into
|
||||
@@ -37,19 +50,31 @@ dashed, gnu-style, names (that is, `sayHello()` becomes `say-hello`).
|
||||
You can, however, explicitly set the command key, by using the `key` attribute of the annotation:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
[source, java, indent=0]
|
||||
----
|
||||
@ShellMethod(value = "Add numbers.", key = "sum")
|
||||
public int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
include::{snippets}/AnnotationRegistrationSnippets.java[tag=snippet2]
|
||||
----
|
||||
====
|
||||
|
||||
NOTE: The `key` attribute accepts multiple values.
|
||||
[NOTE]
|
||||
====
|
||||
The `key` attribute accepts multiple values.
|
||||
If you set multiple keys for a single method, the command is registered with those different aliases.
|
||||
====
|
||||
|
||||
TIP: The command key can contain pretty much any character, including spaces. When coming up with names though,
|
||||
[TIP]
|
||||
====
|
||||
The command key can contain pretty much any character, including spaces. When coming up with names though,
|
||||
keep in mind that consistency is often appreciated by users (that is, you should avoid mixing dashed-names with
|
||||
spaced names and other inconsistencies).
|
||||
====
|
||||
|
||||
==== Programmatic Model
|
||||
`CommandRegistration` can be defined as a `@Bean` and it's automatically registered.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
----
|
||||
include::{snippets}/CommandRegistrationBeanSnippets.java[tag=snippet1]
|
||||
----
|
||||
====
|
||||
@@ -1,5 +1,6 @@
|
||||
[[uicomponents]]
|
||||
=== UI Components
|
||||
ifndef::snippets[:snippets: ../../test/java/org/springframework/shell/docs]
|
||||
|
||||
Starting from _2.1.x_ there is a new component model which provides
|
||||
easier way to create higher level user interaction for usual use cases
|
||||
@@ -32,49 +33,18 @@ via code then gives you flexibility to do whatever you need.
|
||||
Programmatic way to render is simple as to create a `Function`:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
[source, java, indent=0]
|
||||
----
|
||||
class StringInputCustomRenderer implements Function<StringInputContext, List<AttributedString>> {
|
||||
@Override
|
||||
public List<AttributedString> apply(StringInputContext context) {
|
||||
AttributedStringBuilder builder = new AttributedStringBuilder();
|
||||
builder.append(context.getName());
|
||||
builder.append(" ");
|
||||
if (context.getResultValue() != null) {
|
||||
builder.append(context.getResultValue());
|
||||
}
|
||||
else {
|
||||
String input = context.getInput();
|
||||
if (StringUtils.hasText(input)) {
|
||||
builder.append(input);
|
||||
}
|
||||
else {
|
||||
builder.append("[Default " + context.getDefaultValue() + "]");
|
||||
}
|
||||
}
|
||||
return Arrays.asList(builder.toAttributedString());
|
||||
}
|
||||
}
|
||||
include::{snippets}/UiComponentSnippets.java[tag=snippet1]
|
||||
----
|
||||
====
|
||||
|
||||
And then hook it with a component:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
[source, java, indent=0]
|
||||
----
|
||||
@ShellMethod(key = "component stringcustom", value = "String input", group = "Components")
|
||||
public String stringInputCustom(boolean mask) {
|
||||
StringInput component = new StringInput(getTerminal(), "Enter value", "myvalue",
|
||||
new StringInputCustomRenderer());
|
||||
component.setResourceLoader(getResourceLoader());
|
||||
component.setTemplateExecutor(getTemplateExecutor());
|
||||
if (mask) {
|
||||
component.setMaskCharater('*');
|
||||
}
|
||||
StringInputContext context = component.run(StringInputContext.empty());
|
||||
return "Got value " + context.getResultValue();
|
||||
}
|
||||
include::{snippets}/UiComponentSnippets.java[tag=snippet2]
|
||||
----
|
||||
====
|
||||
|
||||
@@ -147,26 +117,13 @@ Used to ask a simple text input from a user, optionally masking values
|
||||
if content contains something sensitive.
|
||||
|
||||
====
|
||||
[source, java]
|
||||
[source, java, indent=0]
|
||||
----
|
||||
@ShellComponent
|
||||
public class ComponentCommands extends AbstractShellComponent {
|
||||
|
||||
@ShellMethod(key = "component string", value = "String input", group = "Components")
|
||||
public String stringInput(boolean mask) {
|
||||
StringInput component = new StringInput(getTerminal(), "Enter value", "myvalue");
|
||||
component.setResourceLoader(getResourceLoader());
|
||||
component.setTemplateExecutor(getTemplateExecutor());
|
||||
if (mask) {
|
||||
component.setMaskCharater('*');
|
||||
}
|
||||
StringInputContext context = component.run(StringInputContext.empty());
|
||||
return "Got value " + context.getResultValue();
|
||||
}
|
||||
}
|
||||
include::{snippets}/UiComponentSnippets.java[tag=snippet3]
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
image::images/component-text-input-1.svg[text input]
|
||||
|
||||
Context object is `StringInputContext`.
|
||||
@@ -200,20 +157,9 @@ Context object is `StringInputContext`.
|
||||
Used to ask a `Path` from a user and gives additional info about a path itself.
|
||||
|
||||
====
|
||||
[source, java]
|
||||
[source, java, indent=0]
|
||||
----
|
||||
@ShellComponent
|
||||
public class ComponentCommands extends AbstractShellComponent {
|
||||
|
||||
@ShellMethod(key = "component path", value = "Path input", group = "Components")
|
||||
public String pathInput() {
|
||||
PathInput component = new PathInput(getTerminal(), "Enter value");
|
||||
component.setResourceLoader(getResourceLoader());
|
||||
component.setTemplateExecutor(getTemplateExecutor());
|
||||
PathInputContext context = component.run(PathInputContext.empty());
|
||||
return "Got value " + context.getResultValue();
|
||||
}
|
||||
}
|
||||
include::{snippets}/UiComponentSnippets.java[tag=snippet4]
|
||||
----
|
||||
====
|
||||
|
||||
@@ -236,20 +182,9 @@ Used to ask a simple confirmation from a user and essentially is
|
||||
yes/no question.
|
||||
|
||||
====
|
||||
[source, java]
|
||||
[source, java, indent=0]
|
||||
----
|
||||
@ShellComponent
|
||||
public class ComponentCommands extends AbstractShellComponent {
|
||||
|
||||
@ShellMethod(key = "component confirmation", value = "Confirmation input", group = "Components")
|
||||
public String confirmationInput(boolean no) {
|
||||
ConfirmationInput component = new ConfirmationInput(getTerminal(), "Enter value", !no);
|
||||
component.setResourceLoader(getResourceLoader());
|
||||
component.setTemplateExecutor(getTemplateExecutor());
|
||||
ConfirmationInputContext context = component.run(ConfirmationInputContext.empty());
|
||||
return "Got value " + context.getResultValue();
|
||||
}
|
||||
}
|
||||
include::{snippets}/UiComponentSnippets.java[tag=snippet5]
|
||||
----
|
||||
====
|
||||
|
||||
@@ -275,26 +210,9 @@ Used to ask an item from a list and is essentially similar to simple
|
||||
dropbox implementation.
|
||||
|
||||
====
|
||||
[source, java]
|
||||
[source, java, indent=0]
|
||||
----
|
||||
@ShellComponent
|
||||
public class ComponentCommands extends AbstractShellComponent {
|
||||
|
||||
@ShellMethod(key = "component single", value = "Single selector", group = "Components")
|
||||
public String singleSelector() {
|
||||
List<SelectorItem<String>> items = new ArrayList<>();
|
||||
items.add(SelectorItem.of("key1", "value1"));
|
||||
items.add(SelectorItem.of("key2", "value2"));
|
||||
SingleItemSelector<String, SelectorItem<String>> component = new SingleItemSelector<>(getTerminal(),
|
||||
items, "testSimple", null);
|
||||
component.setResourceLoader(getResourceLoader());
|
||||
component.setTemplateExecutor(getTemplateExecutor());
|
||||
SingleItemSelectorContext<String, SelectorItem<String>> context = component
|
||||
.run(SingleItemSelectorContext.empty());
|
||||
String result = context.getResultItem().flatMap(si -> Optional.ofNullable(si.getItem())).get();
|
||||
return "Got value " + result;
|
||||
}
|
||||
}
|
||||
include::{snippets}/UiComponentSnippets.java[tag=snippet6]
|
||||
----
|
||||
====
|
||||
|
||||
@@ -322,29 +240,9 @@ Context object is `SingleItemSelectorContext`.
|
||||
Used to ask an items from a list.
|
||||
|
||||
====
|
||||
[source, java]
|
||||
[source, java, indent=0]
|
||||
----
|
||||
@ShellComponent
|
||||
public class ComponentCommands extends AbstractShellComponent {
|
||||
|
||||
@ShellMethod(key = "component multi", value = "Multi selector", group = "Components")
|
||||
public String multiSelector() {
|
||||
List<SelectorItem<String>> items = new ArrayList<>();
|
||||
items.add(SelectorItem.of("key1", "value1"));
|
||||
items.add(SelectorItem.of("key2", "value2", false));
|
||||
items.add(SelectorItem.of("key3", "value3"));
|
||||
MultiItemSelector<String, SelectorItem<String>> component = new MultiItemSelector<>(getTerminal(),
|
||||
items, "testSimple", null);
|
||||
component.setResourceLoader(getResourceLoader());
|
||||
component.setTemplateExecutor(getTemplateExecutor());
|
||||
MultiItemSelectorContext<String, SelectorItem<String>> context = component
|
||||
.run(MultiItemSelectorContext.empty());
|
||||
String result = context.getResultItems().stream()
|
||||
.map(si -> si.getItem())
|
||||
.collect(Collectors.joining(","));
|
||||
return "Got value " + result;
|
||||
}
|
||||
}
|
||||
include::{snippets}/UiComponentSnippets.java[tag=snippet7]
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@ more relevant on a java space. Moving to new major version also allows
|
||||
us to clean up codebase and make some needed breaking changes.
|
||||
====
|
||||
|
||||
include::using-shell-write-command.adoc[]
|
||||
include::using-shell-basics.adoc[]
|
||||
|
||||
include::using-shell-define-command.adoc[]
|
||||
|
||||
include::using-shell-invoke-command.adoc[]
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.springframework.shell.docs;
|
||||
|
||||
import org.springframework.shell.standard.ShellComponent;
|
||||
import org.springframework.shell.standard.ShellMethod;
|
||||
|
||||
public class AnnotationRegistrationSnippets {
|
||||
|
||||
// tag::snippet1[]
|
||||
@ShellComponent
|
||||
static class MyCommands {
|
||||
|
||||
@ShellMethod
|
||||
public void mycommand() {
|
||||
}
|
||||
}
|
||||
// end::snippet1[]
|
||||
|
||||
static class Dump1 {
|
||||
|
||||
// tag::snippet2[]
|
||||
@ShellMethod(value = "Add numbers.", key = "sum")
|
||||
public int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
// end::snippet2[]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.springframework.shell.docs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.shell.command.CommandCatalog;
|
||||
import org.springframework.shell.command.CommandCatalogCustomizer;
|
||||
import org.springframework.shell.command.CommandRegistration;
|
||||
import org.springframework.shell.command.CommandResolver;
|
||||
|
||||
public class CommandCatalogSnippets {
|
||||
|
||||
CommandCatalog catalog = CommandCatalog.of();
|
||||
|
||||
void dump1() {
|
||||
// tag::snippet1[]
|
||||
CommandRegistration registration = CommandRegistration.builder().build();
|
||||
catalog.register(registration);
|
||||
// end::snippet1[]
|
||||
}
|
||||
|
||||
// tag::snippet2[]
|
||||
static class CustomCommandResolver implements CommandResolver {
|
||||
List<CommandRegistration> registrations = new ArrayList<>();
|
||||
|
||||
CustomCommandResolver() {
|
||||
CommandRegistration resolved = CommandRegistration.builder()
|
||||
.command("resolve command")
|
||||
.build();
|
||||
registrations.add(resolved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CommandRegistration> resolve() {
|
||||
return registrations;
|
||||
}
|
||||
}
|
||||
// end::snippet2[]
|
||||
|
||||
// tag::snippet3[]
|
||||
static class CustomCommandCatalogCustomizer implements CommandCatalogCustomizer {
|
||||
|
||||
@Override
|
||||
public void customize(CommandCatalog commandCatalog) {
|
||||
CommandRegistration registration = CommandRegistration.builder()
|
||||
.command("resolve command")
|
||||
.build();
|
||||
commandCatalog.register(registration);
|
||||
}
|
||||
}
|
||||
// end::snippet3[]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.springframework.shell.docs;
|
||||
|
||||
import javax.validation.constraints.Null;
|
||||
|
||||
import org.springframework.shell.command.CommandContext;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class CommandContextSnippets {
|
||||
|
||||
CommandContext ctx = CommandContext.of(null, null, null);
|
||||
|
||||
void dump1() {
|
||||
// tag::snippet1[]
|
||||
String arg = ctx.getOptionValue("arg");
|
||||
// end::snippet1[]
|
||||
}
|
||||
|
||||
void dump2() {
|
||||
// tag::snippet2[]
|
||||
ctx.getTerminal().writer().println("hi");
|
||||
// end::snippet2[]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.springframework.shell.docs;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.shell.command.CommandRegistration;
|
||||
|
||||
public class CommandRegistrationBeanSnippets {
|
||||
|
||||
// tag::snippet1[]
|
||||
@Bean
|
||||
CommandRegistration commandRegistration() {
|
||||
return CommandRegistration.builder()
|
||||
.command("mycommand")
|
||||
.build();
|
||||
}
|
||||
// end::snippet1[]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.springframework.shell.docs;
|
||||
|
||||
import org.springframework.shell.command.CommandRegistration;
|
||||
|
||||
public class CommandRegistrationSnippets {
|
||||
|
||||
void dump1() {
|
||||
// tag::snippet1[]
|
||||
CommandRegistration.builder()
|
||||
.withOption()
|
||||
.longNames("myopt")
|
||||
.and()
|
||||
.build();
|
||||
// end::snippet1[]
|
||||
}
|
||||
|
||||
void dump2() {
|
||||
// tag::snippet2[]
|
||||
CommandRegistration.builder()
|
||||
.withOption()
|
||||
.shortNames('s')
|
||||
.and()
|
||||
.build();
|
||||
// end::snippet2[]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.springframework.shell.docs;
|
||||
|
||||
import org.springframework.shell.command.CommandRegistration;
|
||||
|
||||
public class CommandTargetSnippets {
|
||||
|
||||
// tag::snippet11[]
|
||||
public static class CommandPojo {
|
||||
|
||||
String command(String arg) {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
// end::snippet11[]
|
||||
|
||||
void dump1() {
|
||||
// tag::snippet12[]
|
||||
CommandPojo pojo = new CommandPojo();
|
||||
CommandRegistration.builder()
|
||||
.command("command")
|
||||
.withTarget()
|
||||
.method(pojo, "command")
|
||||
.and()
|
||||
.withOption()
|
||||
.longNames("arg")
|
||||
.and()
|
||||
.build();
|
||||
// end::snippet12[]
|
||||
}
|
||||
|
||||
void dump2() {
|
||||
// tag::snippet2[]
|
||||
CommandRegistration.builder()
|
||||
.command("command")
|
||||
.withTarget()
|
||||
.function(ctx -> {
|
||||
String arg = ctx.getOptionValue("arg");
|
||||
return String.format("hi, arg value is '%s'", arg);
|
||||
})
|
||||
.and()
|
||||
.withOption()
|
||||
.longNames("arg")
|
||||
.and()
|
||||
.build();
|
||||
// end::snippet2[]
|
||||
}
|
||||
|
||||
void dump3() {
|
||||
// tag::snippet3[]
|
||||
CommandRegistration.builder()
|
||||
.command("command")
|
||||
.withTarget()
|
||||
.consumer(ctx -> {
|
||||
String arg = ctx.getOptionValue("arg");
|
||||
ctx.getTerminal().writer()
|
||||
.println(String.format("hi, arg value is '%s'", arg));
|
||||
})
|
||||
.and()
|
||||
.withOption()
|
||||
.longNames("arg")
|
||||
.and()
|
||||
.build();
|
||||
// end::snippet3[]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package org.springframework.shell.docs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.jline.utils.AttributedStringBuilder;
|
||||
|
||||
import org.springframework.shell.component.ConfirmationInput;
|
||||
import org.springframework.shell.component.MultiItemSelector;
|
||||
import org.springframework.shell.component.PathInput;
|
||||
import org.springframework.shell.component.SingleItemSelector;
|
||||
import org.springframework.shell.component.StringInput;
|
||||
import org.springframework.shell.component.ConfirmationInput.ConfirmationInputContext;
|
||||
import org.springframework.shell.component.MultiItemSelector.MultiItemSelectorContext;
|
||||
import org.springframework.shell.component.PathInput.PathInputContext;
|
||||
import org.springframework.shell.component.SingleItemSelector.SingleItemSelectorContext;
|
||||
import org.springframework.shell.component.StringInput.StringInputContext;
|
||||
import org.springframework.shell.component.support.SelectorItem;
|
||||
import org.springframework.shell.standard.AbstractShellComponent;
|
||||
import org.springframework.shell.standard.ShellComponent;
|
||||
import org.springframework.shell.standard.ShellMethod;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class UiComponentSnippets {
|
||||
|
||||
// tag::snippet1[]
|
||||
class StringInputCustomRenderer implements Function<StringInputContext, List<AttributedString>> {
|
||||
@Override
|
||||
public List<AttributedString> apply(StringInputContext context) {
|
||||
AttributedStringBuilder builder = new AttributedStringBuilder();
|
||||
builder.append(context.getName());
|
||||
builder.append(" ");
|
||||
if (context.getResultValue() != null) {
|
||||
builder.append(context.getResultValue());
|
||||
}
|
||||
else {
|
||||
String input = context.getInput();
|
||||
if (StringUtils.hasText(input)) {
|
||||
builder.append(input);
|
||||
}
|
||||
else {
|
||||
builder.append("[Default " + context.getDefaultValue() + "]");
|
||||
}
|
||||
}
|
||||
return Arrays.asList(builder.toAttributedString());
|
||||
}
|
||||
}
|
||||
// end::snippet1[]
|
||||
|
||||
class Dump1 extends AbstractShellComponent {
|
||||
// tag::snippet2[]
|
||||
@ShellMethod(key = "component stringcustom", value = "String input", group = "Components")
|
||||
public String stringInputCustom(boolean mask) {
|
||||
StringInput component = new StringInput(getTerminal(), "Enter value", "myvalue",
|
||||
new StringInputCustomRenderer());
|
||||
component.setResourceLoader(getResourceLoader());
|
||||
component.setTemplateExecutor(getTemplateExecutor());
|
||||
if (mask) {
|
||||
component.setMaskCharater('*');
|
||||
}
|
||||
StringInputContext context = component.run(StringInputContext.empty());
|
||||
return "Got value " + context.getResultValue();
|
||||
}
|
||||
// end::snippet2[]
|
||||
}
|
||||
|
||||
class Dump2 {
|
||||
// tag::snippet3[]
|
||||
@ShellComponent
|
||||
public class ComponentCommands extends AbstractShellComponent {
|
||||
|
||||
@ShellMethod(key = "component string", value = "String input", group = "Components")
|
||||
public String stringInput(boolean mask) {
|
||||
StringInput component = new StringInput(getTerminal(), "Enter value", "myvalue");
|
||||
component.setResourceLoader(getResourceLoader());
|
||||
component.setTemplateExecutor(getTemplateExecutor());
|
||||
if (mask) {
|
||||
component.setMaskCharater('*');
|
||||
}
|
||||
StringInputContext context = component.run(StringInputContext.empty());
|
||||
return "Got value " + context.getResultValue();
|
||||
}
|
||||
}
|
||||
// end::snippet3[]
|
||||
}
|
||||
|
||||
class Dump3 {
|
||||
// tag::snippet4[]
|
||||
@ShellComponent
|
||||
public class ComponentCommands extends AbstractShellComponent {
|
||||
|
||||
@ShellMethod(key = "component path", value = "Path input", group = "Components")
|
||||
public String pathInput() {
|
||||
PathInput component = new PathInput(getTerminal(), "Enter value");
|
||||
component.setResourceLoader(getResourceLoader());
|
||||
component.setTemplateExecutor(getTemplateExecutor());
|
||||
PathInputContext context = component.run(PathInputContext.empty());
|
||||
return "Got value " + context.getResultValue();
|
||||
}
|
||||
}
|
||||
// end::snippet4[]
|
||||
}
|
||||
|
||||
class Dump4 {
|
||||
// tag::snippet5[]
|
||||
@ShellComponent
|
||||
public class ComponentCommands extends AbstractShellComponent {
|
||||
|
||||
@ShellMethod(key = "component confirmation", value = "Confirmation input", group = "Components")
|
||||
public String confirmationInput(boolean no) {
|
||||
ConfirmationInput component = new ConfirmationInput(getTerminal(), "Enter value", !no);
|
||||
component.setResourceLoader(getResourceLoader());
|
||||
component.setTemplateExecutor(getTemplateExecutor());
|
||||
ConfirmationInputContext context = component.run(ConfirmationInputContext.empty());
|
||||
return "Got value " + context.getResultValue();
|
||||
}
|
||||
}
|
||||
// end::snippet5[]
|
||||
}
|
||||
|
||||
class Dump5 {
|
||||
// tag::snippet6[]
|
||||
@ShellComponent
|
||||
public class ComponentCommands extends AbstractShellComponent {
|
||||
|
||||
@ShellMethod(key = "component single", value = "Single selector", group = "Components")
|
||||
public String singleSelector() {
|
||||
List<SelectorItem<String>> items = new ArrayList<>();
|
||||
items.add(SelectorItem.of("key1", "value1"));
|
||||
items.add(SelectorItem.of("key2", "value2"));
|
||||
SingleItemSelector<String, SelectorItem<String>> component = new SingleItemSelector<>(getTerminal(),
|
||||
items, "testSimple", null);
|
||||
component.setResourceLoader(getResourceLoader());
|
||||
component.setTemplateExecutor(getTemplateExecutor());
|
||||
SingleItemSelectorContext<String, SelectorItem<String>> context = component
|
||||
.run(SingleItemSelectorContext.empty());
|
||||
String result = context.getResultItem().flatMap(si -> Optional.ofNullable(si.getItem())).get();
|
||||
return "Got value " + result;
|
||||
}
|
||||
}
|
||||
// end::snippet6[]
|
||||
}
|
||||
|
||||
class Dump6 {
|
||||
// tag::snippet7[]
|
||||
@ShellComponent
|
||||
public class ComponentCommands extends AbstractShellComponent {
|
||||
|
||||
@ShellMethod(key = "component multi", value = "Multi selector", group = "Components")
|
||||
public String multiSelector() {
|
||||
List<SelectorItem<String>> items = new ArrayList<>();
|
||||
items.add(SelectorItem.of("key1", "value1"));
|
||||
items.add(SelectorItem.of("key2", "value2", false));
|
||||
items.add(SelectorItem.of("key3", "value3"));
|
||||
MultiItemSelector<String, SelectorItem<String>> component = new MultiItemSelector<>(getTerminal(),
|
||||
items, "testSimple", null);
|
||||
component.setResourceLoader(getResourceLoader());
|
||||
component.setTemplateExecutor(getTemplateExecutor());
|
||||
MultiItemSelectorContext<String, SelectorItem<String>> context = component
|
||||
.run(MultiItemSelectorContext.empty());
|
||||
String result = context.getResultItems().stream()
|
||||
.map(si -> si.getItem())
|
||||
.collect(Collectors.joining(","));
|
||||
return "Got value " + result;
|
||||
}
|
||||
}
|
||||
// end::snippet7[]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user