Update docs

- Split adoc files
- Relates #383
This commit is contained in:
Janne Valkealahti
2022-04-23 08:35:14 +01:00
parent 412053d414
commit 942108e612
17 changed files with 1639 additions and 1649 deletions

View File

@@ -1,14 +0,0 @@
[[extending-spring-shell]]
== Extending Spring Shell
[[support-for-shell-1-and-jcommander]]
=== Support for Spring Shell 1 and JCommander
=== Discovering Methods that Can Act as Commands
=== Resolving Parameter Values
==== Supporting kbd:[TAB] Completion

View File

@@ -0,0 +1,120 @@
== Getting Started
To see what Spring Shell has to offer, we can write a trivial shell application that
has a simple command to add two numbers.
=== Writing a Simple Boot Application
Starting with version 2, Spring Shell has been rewritten from the ground up with various
enhancements in mind, one of which is easy integration with Spring Boot, although it is
not a strong requirement.
For the purpose of this tutorial, we create a simple Boot application by
using https://start.spring.io. This minimal application depends only on `spring-boot-starter`
and configures the `spring-boot-maven-plugin` to generate an executable über-jar:
====
[source, xml]
----
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
...
</dependencies>
----
====
[[using-spring-shell-add-dependency]]
=== Adding a Dependency on Spring Shell
The easiest way to get going with Spring Shell is to depend on the `{starter-artifactId}` artifact.
This comes with everything one needs to use Spring Shell and plays nicely with Boot,
configuring only the necessary beans as needed:
====
[source, xml, subs=attributes+]
----
...
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>{spring-shell-starter}</artifactId>
<version>{project-version}</version>
</dependency>
...
----
====
CAUTION: Given that Spring Shell starts the REPL by virtue of this dependency being present,
you need to either skip tests when you build (`-DskipTests`) throughout this tutorial or remove the sample integration test
that was generated by https://start.spring.io. If you do not remove it, the integration test creates
the Spring `ApplicationContext` and, depending on your build tool, stays stuck in the eval loop or crashes with a NPE.
[[using-spring-shell-your-first-command]]
=== Your First Command
Now we can add our first command. To do so, create a new class (named whatever you want) and
annotate it with `@ShellComponent` (a variation of `@Component` that is used to restrict
the set of classes that are scanned for candidate commands).
Then create an `add` method that takes two ints (`a` and `b`) and returns their sum. Annotate it
with `@ShellMethod` and provide a description of the command in the annotation (the only piece of
information that is required):
====
[source, java]
----
package com.example.demo;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellComponent;
@ShellComponent
public class MyCommands {
@ShellMethod("Add two integers together.")
public int add(int a, int b) {
return a + b;
}
}
----
====
[[using-spring-shell-try-application]]
=== Trying the Application
To build the application and run the generated jar, run the following command:
====
[source, bash]
----
./mvnw clean install -DskipTests
[...]
java -jar target/demo-0.0.1-SNAPSHOT.jar
----
====
You are greeted by the following screen (the banner comes from Spring Boot and can be
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-banner[customized]:
====
[source]
----
shell:>
----
====
A yellow `shell:>` prompt invites you to type commands. Type `add 1 2`, press `ENTER`, and admire the magic:
====
[source, bash]
----
shell:>add 1 2
3
----
====
Try to play with the shell (hint: there is a `help` command). When you are done, type `exit` and press `ENTER`.
The rest of this document delves deeper into the whole Spring Shell programming model.

View File

@@ -1,11 +1,11 @@
= Spring Shell Reference Documentation
Eric Bottard; Janne Valkealahti; Jay Bryant;
Eric Bottard; Janne Valkealahti; Jay Bryant, Corneil du Plessis;
:doctype: book
:hide-uri-scheme:
:icons: font
:experimental: // For kbd: macro
:spring-shell-starter: spring-shell-starter
ifdef::backend-html5[]
*{projectVersion}*
(C) 2017 - 2022 VMware, Inc.
@@ -15,10 +15,8 @@ others, provided that you do not charge any fee for such copies and further
provided that each copy contains this Copyright Notice, whether distributed in
print or electronically._
// ======================================================================================
include::introduction.adoc[]
include::what-is-spring-shell.adoc[]
include::getting-started.adoc[]
include::using-spring-shell.adoc[]
// include::extending-spring-shell.adoc[]
include::using-shell.adoc[]

View File

@@ -1,5 +1,6 @@
== What is Spring Shell?
== Introduction
=== What is Spring Shell?
Not all applications need a fancy web user interface!
Sometimes, interacting with an application through an interactive terminal is
the most appropriate way to get things done.

View File

@@ -0,0 +1,147 @@
[[dynamic-command-availability]]
=== Dynamic Command Availability
Registered commands do not always make sense, due to the internal state of the application.
For example, there may be a `download` command, but it only works once the user has used `connect` on a remote
server. Now, if the user tries to use the `download` command, the shell should gracefully explain that
the command exist but that it is not available at the time.
Spring Shell lets you do that, even letting you provide a short explanation of the reason for
the command not being available.
There are three possible ways for a command to indicate availability.
They all leverage a no-arg method that returns an instance of `Availability`.
Consider the following example:
====
[source, java]
----
@ShellComponent
public class MyCommands {
private boolean connected;
@ShellMethod("Connect to the server.")
public void connect(String user, String password) {
[...]
connected = true;
}
@ShellMethod("Download the nuclear codes.")
public void download() {
[...]
}
public Availability downloadAvailability() {
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
}
----
====
The `connect` method is used to connect to the server (details omitted), altering the state
of the command through the `connected` boolean when done.
The `download` command as marked as unavailable until the user has connected, thanks to the presence
of a method named exactly as the `download` command method with the `Availability` suffix in its name.
The method returns an instance of `Availability`, constructed with one of the two factory methods.
If the command is not available, an explanation has to be provided.
Now, if the user tries to invoke the command while not being connected, here is what happens:
====
[source]
----
shell:>download
Command 'download' exists but is not currently available because you are not connected.
Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.
----
====
Information about currently unavailable commands is also used in the integrated help. See <<help-command>>.
[TIP]
====
The reason provided when the command is not available should read nicely if appended after "`Because`".
You should not start the sentence with a capital or add a final period
====
If naming the availability method after the name of the command method does not suit you, you
can provide an explicit name by using the `@ShellMethodAvailability` annotation:
====
[source, java]
----
@ShellMethod("Download the nuclear codes.")
@ShellMethodAvailability("availabilityCheck") // <1>
public void download() {
[...]
}
public Availability availabilityCheck() { // <1>
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
----
<1> the names have to match
====
Lastly, it is often the case that several commands in the same class share the same internal state and, thus,
should all be available or unavailable as a group. Instead of having to stick the `@ShellMethodAvailability`
on all command methods, Spring Shell lets you flip things around and put the `@ShellMethodAvailabilty`
annotation on the availability method, specifying the names of the commands that it controls:
====
[source, java]
----
@ShellMethod("Download the nuclear codes.")
public void download() {
[...]
}
@ShellMethod("Disconnect from the server.")
public void disconnect() {
[...]
}
@ShellMethodAvailability({"download", "disconnect"})
public Availability availabilityCheck() {
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
----
====
[TIP]
=====
The default value for the `@ShellMethodAvailability.value()` attribute is `*`. This special
wildcard matches all command names. This makes it easy to turn all commands of a single class on or off
with a single availability method:
====
[source,java]
----
@ShellComponent
public class Toggles {
@ShellMethodAvailability
public Availability availabilityOnWeekdays() {
return Calendar.getInstance().get(DAY_OF_WEEK) == SUNDAY
? Availability.available()
: Availability.unavailable("today is not Sunday");
}
@ShellMethod
public void foo() {}
@ShellMethod
public void bar() {}
}
----
====
=====
TIP: Spring Shell does not impose many constraints on how to write commands and how to organize classes.
However, it is often good practice to put related commands in the same class, and the availability indicators
can benefit from that.

View File

@@ -0,0 +1,139 @@
[[built-in-commands]]
=== Built-In Commands
Any application built by using the `{starter-artifactId}` artifact
(or, to be more precise, the `spring-shell-standard-commands` dependency) comes with a set of built-in commands.
You can override or disable these commands individually (see <<overriding-or-disabling-built-in-commands>>).
However, if they are not overridden or disabled, this section describes their behavior.
[[help-command]]
==== Help
Running a shell application often implies that the user is in a graphically limited
environment. Also, while we are nearly always connected in the era of mobile phones,
accessing a web browser or any other rich UI application (such as a PDF viewer) may not always
be possible. This is why it is important that the shell commands are correctly self documented, and this is where the `help`
command comes in.
Typing `help` + `ENTER` lists all the commands known to the shell (including <<dynamic-command-availability,unavailable>> commands)
and a short description of what they do, similar to the following:
====
[source]
----
shell:>help
AVAILABLE COMMANDS
add: Add numbers together.
* authenticate: Authenticate with the system.
* blow-up: Blow Everything up.
clear: Clear the shell screen.
connect: Connect to the system
disconnect: Disconnect from the system.
exit, quit: Exit the shell.
help: Display help about available commands.
register module: Register a new module.
script: Read and execute commands from a file.
stacktrace: Display the full stacktrace of the last error.
Commands marked with (*) are currently unavailable.
Type `help <command>` to learn more.
----
====
Typing `help <command>` shows more detailed information about a command, including the available parameters, their
type, whether they are mandatory or not, and other details.
The follwoing listing shows the `help` command applied to itself:
====
----
shell:>help help
NAME
help - Display help about available commands.
SYNOPSYS
help [[-C] string]
OPTIONS
-C or --command string
The command to obtain help for. [Optional, default = <none>]
----
====
==== Clear
The `clear` command does what you would expect and clears the screen, resetting the prompt
in the top left corner.
==== Exit
The `quit` command (also aliased as `exit`) requests the shell to quit, gracefully
closing the Spring application context. If not overridden, a JLine `History` bean writes a history of all
commands to disk, so that they are available again (see <<interacting-with-the-shell>>) on the next launch.
==== Stacktrace
When an exception occurs inside command code, it is caught by the shell and a simple, one-line message is displayed
so as not to overflow the user with too much information.
There are cases though when understanding what exactly happened is important (especially if the exception has a nested cause).
To this end, Spring Shell remembers the last exception that occurred, and the user can later use the `stacktrace`
command to print all the details on the console.
[[script-command]]
==== Script
The `script` command accepts a local file as an argument and replays commands found there, one at a time.
Reading from the file behaves exactly like inside the interactive shell, so lines starting with `//` are considered
to be comments and are ignored, while lines ending with `\` trigger line continuation.
==== History
The `history` command shows history of a commands which has been executed.
==== Completion
The `completion` command set allows you to create _scripts_ files which can be used
with am OS shell implementations to provide completion. This is very usefull when
working with non-interactive mode.
Currently only implementation is for _bash_ which works with `bash` sub-command.
==== Version
The `version` command shows existing _build_ and _git_ info by integrating into
Boot's `BuildProperties` and `GitProperties` if those exists in a shell app.
On default only version info is shown and other can be enabled via configuration
options.
Settings are under `spring.shell.command.version` where you can use `enabled` to
disable command and optionally define your own template with `template`. Options
`show-build-artifact`, `show-build-group`, `show-build-name`, `show-build-time`,
`show-build-version`, `show-git-branch`, `show-git-commit-id`,
`show-git-short-commit-id` and `show-git-commit-time` can be used to control
fields in a default template.
Template default to `classpath:template/version-default.st` and you can define
your own, for example having:
====
[source]
----
<buildVersion>
----
====
Which would simply output something like:
====
[source]
----
X.X.X
----
====
Attributes added to default template rendering are `buildVersion`, `buildGroup`,
`buildGroup`, `buildName`, `buildTime`, `gitShortCommitId`, `gitCommitId`,
`gitBranch` and `gitCommitTime`.

View File

@@ -0,0 +1,228 @@
=== Customizing the Shell
[[overriding-or-disabling-built-in-commands]]
==== Overriding or Disabling Built-In Commands
Spring Shell provides <<built-in-commands,Built-in commands>> to let people achieve everyday tasks that many if not
all shell applications need. If you are not happy with the way they behave, though, you can disable or override them,
as explained in this section.
[TIP]
.Disabling all Built-in Commands
=====
If you do not need built-in commands at all, there is an easy way to "`disable`" them: don't include them.
Either use a maven exclusion on `spring-shell-standard-commands` or, if you are selectively including Spring Shell
dependencies, don't include that one in. The follwoing example shows how to exclude `spring-shell-standard-commands`:
====
[source,xml,subs=attributes+]
----
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>{starter-artifactId}</artifactId>
<version>{project-version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-standard-commands</artifactId>
</exclusion>
</exclusions>
</dependency>
----
====
=====
[[disabling-specific-commands]]
===== Disabling Specific Commands
To disable a single built-in command, set the `spring.shell.command.<command>.enabled` property to `false` in the
application `Environment`. One way to do so is to pass extra arguments to the Boot application in your `main()`
entry point:
====
[source, java]
----
public static void main(String[] args) throws Exception {
String[] disabledCommands = {"--spring.shell.command.help.enabled=false"}; // <1>
String[] fullArgs = StringUtils.concatenateStringArrays(args, disabledCommands);
SpringApplication.run(MyApp.class, fullArgs);
}
----
<1> This disables the integrated `help` command
====
===== Overriding Specific Commands
If, instead of disabling a command, you would rather provide your own implementation, then you can either:
* Disable the command as explained <<disabling-specific-commands,earlier>> and have your implementation registered
with the same name.
* Have your implementing class implement the `<Command>.Command` interface. As an example, here is how
to override the `clear` command:
+
====
[source, java]
----
public class MyClear implements Clear.Command {
@ShellMethod("Clear the screen, only better.")
public void clear() {
// ...
}
}
----
====
[NOTE]
.Please Consider Contributing your Changes
====
If you feel like your implementation of a standard command could be valuable to the community,
please open a pull-request at https://github.com/spring-projects/spring-shell.
Alternatively, before making any changes on your own, you can open an issue with the project. Feedback is
always welcome!
====
// ==== ResultHandlers
// TBD
==== PromptProvider
After each command invocation, the shell waits for new input from the user, displaying
a prompt in yellow:
====
[source]
----
shell:>
----
====
It is possible to customize this behavior by registering a bean of type `PromptProvider`.
Such a bean may use internal state to decide what to display to the user (it may, for example,
react to https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-functionality-events-annotation[application events])
and can use JLine's `AttributedCharSequence` to display fancy ANSI text.
The following example shows how to use a `PromptProvider`:
====
[source, java]
----
@Component
public class CustomPromptProvider implements PromptProvider {
private ConnectionDetails connection;
@Override
public AttributedString getPrompt() {
if (connection != null) {
return new AttributedString(connection.getHost() + ":>",
AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW));
}
else {
return new AttributedString("server-unknown:>",
AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
}
}
@EventListener
public void handle(ConnectionUpdatedEvent event) {
this.connection = event.getConnectionDetails();
}
}
----
====
==== Customizing Command Line Options Behavior
There can be exactly one shell spesific `ShellApplicationRunner` which simply extends
Boot's `ApplicationRunner`. Default behariour is to have actual runner logic in
various `ShellRunner` implementations where candidate will be picked up.
[IMPORTANT]
====
This is a breaking change in `2.1.x` as previous shell versions had an confusing
logic how `ApplicationRunner` instances were used. These changes were made
to have a better support for interactive and non-interactive modes in a same
shell application as it's convenient to fully work on command-line and still
have ability to enter interactive mode.
You can override bean type of `ShellApplicationRunner` if there's a need to
customise shell running logic.
====
==== Customizing Arguments Conversion
Conversion from text input to actual method arguments uses the standard Spring
https://docs.spring.io/spring/docs/4.3.11.RELEASE/spring-framework-reference/htmlsingle/#core-convert[conversion] mechanism.
Spring Shell installs a new `DefaultConversionService` (with built-in converters enabled)
and registers to it any bean of type `Converter<S, T>`, `GenericConverter`, or
`ConverterFactory<S, T>` that it finds in the application context.
This means that you can customize conversion to your custom objects
by installing a `Converter<String, Foo>` bean in the context:
====
[source, java]
----
@ShellComponent
class ConversionCommands {
@ShellMethod("Shows conversion using Spring converter")
public String conversionExample(DomainObject object) {
return object.getClass();
}
}
class DomainObject {
private final String value;
DomainObject(String value) {
this.value = value;
}
public String toString() {
return value;
}
}
@Component
class CustomDomainConverter implements Converter<String, DomainObject> {
@Override
public DomainObject convert(String source) {
return new DomainObject(source);
}
}
----
====
[TIP]
.Mind your String representation
=====
As in the preceding example, you should have
your `toString()` implementations return the converse of what was used
to create the object instance. This is because, when a value fails
validation, Spring Shell prints:
====
[source]
----
The following constraints were not met:
--arg <type> : <message> (You passed '<value.toString()>')
----
====
See <<validating-command-arguments>> for more information.
=====
[NOTE]
====
If you want to customize the `ConversionService` further, you can:
* Have the default one injected in your code and act upon it in some way.
* Override it altogether with your own (custom converters need to be registered by hand).
The `ConversionService` used by Spring Shell needs to be https://docs.spring.io/spring/docs/4.3.12.RELEASE/spring-framework-reference/htmlsingle/#beans-autowired-annotation-qualifiers[qualified] as `"spring-shell"`.
====

View File

@@ -0,0 +1,16 @@
=== Interaction Mode
Starting from _2.1.x_ a build-in support has been added to distinguish between interactive
and non-interactive modes. This has been added so that it's easier to use shell as a
simple command-line tool without requiring customisation to accomplish that.
Currently interactive mode is entered if any command line options are passed when starting
or running a shell from a command-line. This especially works well when shell application
is compiled with <<native>>.
Some commands may not have any usefull meaning if running on interactive mode
or vice versa on non-interactive mode. For example a build-in `exit` command
have no meaning in non-interactive mode as it's used to exit interactive mode.
Annotation `@ShellMethod` has a field `interactionMode` which can be used to instruct
shell when particular command is available.

View File

@@ -0,0 +1,359 @@
=== Invoking your Commands
This section addresses how you can control the way in which your commands are invoked.
==== By Name Versus Positional Parameters
As seen <<documenting-the-command,earlier>>, decorating a method with `@ShellMethod` is the sole requirement for creating a command.
The user can set the value of all the method parameters in either of two ways:
* By using a parameter key (for example, `--arg value`). This approach is called "`by name parameters.`"
* Without a key, by setting parameter values in the order in which they appear in the method signature (called "`positional parameters`").
These two approaches can be mixed and matched, with named parameters always taking precedence (as they are less
prone to ambiguity). Consider the following command definition:
====
[source, java]
----
@ShellMethod("Display stuff.")
public String echo(int a, int b, int c) {
return String.format("You said a=%d, b=%d, c=%d", a, b, c);
}
----
====
Given the preceding command definiton, the following invocations are all equivalent, as shown in the output:
====
[source, bash]
----
shell:>echo 1 2 3 <1>
You said a=1, b=2, c=3
shell:>echo --a 1 --b 2 --c 3 <2>
You said a=1, b=2, c=3
shell:>echo --b 2 --c 3 --a 1 <3>
You said a=1, b=2, c=3
shell:>echo --a 1 2 3 <4>
You said a=1, b=2, c=3
shell:>echo 1 --c 3 2 <5>
You said a=1, b=2, c=3
----
<1> This uses positional parameters.
<2> This is an example of full by-name parameters.
<3> By-name parameters can be reordered as desired.
<4> You can use a mix of the two approaches.
<5> The non by-name parameters are resolved in the order in which they appear.
====
===== Customizing the Named Parameter Keys
As seen <<your-first-command,earlier>>, the default strategy for deriving the key for a named parameter is to use the Java
name of the method signature and prefix it with two dashes (`--`). You can customize this in two ways:
* Use the `prefix()` attribute of the `@ShellMethod` annotation to change the default prefix for the whole method.
* Annotate the parameter with the `@ShellOption` annotation to override the entire key in a per-parameter fashion.
Consider the following example:
====
[source, java]
----
@ShellMethod(value = "Display stuff.", prefix="-")
public String echo(int a, int b, @ShellOption("--third") int c) {
return String.format("You said a=%d, b=%d, c=%d", a, b, c);
}
----
====
For such a setup, the possible parameter keys are `-a`, `-b` and `--third`.
[TIP]
=====
You can specify several keys for a single parameter. If you do so, these keys are mutually exclusive (only one of them can be used) ways
to specify the same parameter. The following example shows the signature of the
built-in <<help-command,`help`>> command:
====
[source, java]
----
@ShellMethod("Describe a command.")
public String help(@ShellOption({"-C", "--command"}) String command) {
...
}
----
====
=====
[[optional-parameters-default-values]]
==== Optional Parameters and Default Values
Spring Shell provides the ability to give parameters default values, which lets users omit
those parameters. Consider the following command definition:
====
[source, java]
----
@ShellMethod("Say hello.")
public String greet(@ShellOption(defaultValue="World") String who) {
return "Hello " + who;
}
----
====
With the preceding definition, the `greet` command can still be invoked as `greet Mother` (or `greet --who Mother`), but the following
is also possible:
====
[source]
----
shell:>greet
Hello World
----
====
==== Parameter Arity
Up to now, it has always been assumed that each parameter maps to a single word entered by the user.
Situations may arise, though, when a parameter value should be multi-valued. This is driven by the `arity()`
attribute of the `@ShellOption` annotation. You can use a collection or array for the parameter type and specify how
many values are expected:
====
[source, java]
----
@ShellMethod("Add Numbers.")
public float add(@ShellOption(arity=3) float[] numbers) {
return numbers[0] + numbers[1] + numbers[2];
}
----
====
The users can then invoke the command by using any of the following syntax:
====
[source]
----
shell:>add 1 2 3.3
6.3
shell:>add --numbers 1 2 3.3
6.3
----
====
[WARNING]
=====
When using the _by-name_ parameter approach, the key should *not* be repeated. The following does *not* work:
====
[source]
----
shell:>add --numbers 1 --numbers 2 --numbers 3.3
----
====
=====
===== Varying Amount Arity
The above example demonstrates requiring a known, constant arity for a parameter, three in this case. Allowing any number of multiple values of a parameter can be achieved by leaving `arity` unspecified and using Spring's built-in comma separated value parsing for collections and/or arrays:
[source, java]
----
@ShellMethod("Add a Varying Amount of Numbers.")
public double add(@ShellOption double[] numbers) {
return Arrays.stream(numbers).sum();
}
----
The command may then be invoked with any amount of `numbers`:
====
[source]
----
shell:>add 1,2,3.3
6.3
shell:>add --numbers 42
42.0
shell:>add --numbers 1,2,3.3,4,5
15.3
----
====
===== Special Handling of Boolean Parameters
When it comes to parameter arity, one kind of parameter receives a special treatment by default, as
is often the case in command-line utilities.
Boolean (that is, `boolean` as well as `java.lang.Boolean`) parameters behave like they have an `arity()` of `0` by default, allowing users to set their values by using a "`flag`" approach.
Consider the following command definition:
====
[source, java]
----
@ShellMethod("Terminate the system.")
public String shutdown(boolean force) {
return "You said " + force;
}
----
====
This preceding command definition allows the following invocations:
====
[source]
----
shell:>shutdown
You said false
shell:>shutdown --force
You said true
----
====
TIP: This special treatment plays well with the <<optional-parameters-default-values,default value>> specification. Although the default
for boolean parameters is to have their default value be `false`, you can specify otherwise (that is,
`@ShellOption(defaultValue="true")`), and the behavior is inverted (that is, not specifying the parameter
results in the value being `true`, and specifying the flag results in the value being `false`)
[WARNING]
=====
Having this behavior of implicit `arity()=0` prevents the user from specifying a value (for example, `shutdown --force true`).
If you would like to allow this behavior (and forego the flag approach), then force an arity of `1` by using the annotation as follows:
====
[source, java]
----
@ShellMethod("Terminate the system.")
public String shutdown(@ShellOption(arity=1, defaultValue="false") boolean force) {
return "You said " + force;
}
----
====
=====
[[quotes-handling]]
==== Quotes Handling
Spring Shell takes user input and tokenizes it into words, splitting on space characters.
If the user wants to provide a parameter value that contains spaces, that value needs to be quoted.
Both single (`'`) and double (`"`) quotes are supported, and those quotes are not part of the value:
Consider the following command definition:
====
[source, java]
----
@ShellMethod("Prints what has been entered.")
public String echo(String what) {
return "You said " + what;
}
----
====
The following commands all invoke the preceding command definition:
====
[source]
----
shell:>echo Hello
You said Hello
shell:>echo 'Hello'
You said Hello
shell:>echo 'Hello World'
You said Hello World
shell:>echo "Hello World"
You said Hello World
----
====
Supporting both single and double quotes lets the user embed one type of quotes into
a value:
====
[source]
----
shell:>echo "I'm here!"
You said I'm here!
shell:>echo 'He said "Hi!"'
You said He said "Hi!"
----
====
That way, the user can use a single quote as an apostrophe in a message.
Should the user need to embed the same kind of quote that was used to quote the whole parameter,
the escape sequence uses the backslash (`\`) character:
====
[source]
----
shell:>echo 'I\'m here!'
You said I'm here!
shell:>echo "He said \"Hi!\""
You said He said "Hi!"
shell:>echo I\'m here!
You said I'm here!
----
====
It is also possible to escape space characters when not using enclosing quotes:
====
[source]
----
shell:>echo This\ is\ a\ single\ value
You said This is a single value
----
====
[[interacting-with-the-shell]]
==== Interacting with the Shell
The Spring Shell project builds on top of the https://github.com/jline/jline3[JLine] library and, as a result, brings
a lot of nice interactive features, some of which are detailed in this section.
First and foremost, Spring Shell supports tab completion almost everywhere possible. So, if there
is an `echo` command and the user types `ec` and presses `TAB`, `echo` appears.
Should there be several commands that start with `ec`, then the user is prompted to choose (using `TAB` or
`Shift + TAB` to navigate and `ENTER` to select.)
But completion does not stop at command keys. It also works for parameter keys (`--arg`) and even
parameter values, if the application developer registered the appropriate beans (see <<providing-tab-completion>>).
Another nice feature of Spring Shell applications is support for line continuation. If a command and its parameters
is too long and does not fit nicely on the screen, a user can chunk it by ending a line with a backslash
(`\`) character, pressing `ENTER`, and continuing on the next line. Upon submission of the whole command, this is
parsed as if the user entered a single space on line breaks. The following listing shows an example of this behavior:
====
[source]
----
shell:>register module --type source --name foo \ <1>
> --uri file:///tmp/bar
Successfully registered module 'source:foo'
----
<1> command continues on next line
====
Line continuation also automatically triggers if the user has opened a quote (see <<quotes-handling>>)
and presses `ENTER` while still in the quotes:
====
[source]
----
shell:>echo "Hello <1>
dquote> World"
You said Hello World
----
<1> The user pressed `ENTER` here.
====
Finally, Spring Shell applications benefit from a lot of keyboard shortcuts (borrowed from Emacs) with which you may
already be familiar from working with your regular OS Shell. Notable shortcuts include `Ctrl+r` to perform
a reverse search, `Ctrl+a`] and `Ctrl+e` to move to the beginning and the end of the current line (respectively),
and `Esc f` and `Esc b` to move forward or backward (respectively) one word at a time.
[[providing-tab-completion]]
// ===== Providing TAB Completion Proposals
// TBD

View File

@@ -0,0 +1,31 @@
[[native]]
=== Native Support
Re-work with _2.1.x_ brings in an experimental support for compiling shell application
into _native_ application with _GraalVM_ and _spring-native_. As underlying _jline_
library works with _GraalVM_ most of a things should just work.
Project can be compiled with native profile to get sample compiled as an native
application:
====
----
$ ./mvnw clean package -Pnative
----
====
You can then run sample either with interactive or non-interactive mode:
====
----
$ ./spring-shell-samples/target/spring-shell-samples help
AVAILABLE COMMANDS
Built-In Commands
completion bash: Generate bash completion script
help: Display help about available commands.
history: Display or save the history of previously run commands
script: Read and execute commands from a file.
...
----
====

View File

@@ -0,0 +1,54 @@
[[organizing-commands]]
=== Organizing Commands
When your shell starts to provide a lot of functionality, you may end up
with a lot of commands, which could be confusing for your users. By typing `help`,
they would see a daunting list of commands, organized in alphabetical order,
which may not always make sense.
To alleviate this possible confusion, Spring Shell provides the ability to group commands together,
with reasonable defaults. Related commands would then end up in the same group (for example, `User Management Commands`)
and be displayed together in the help screen and other places.
By default, commands are grouped according to the class they are implemented in,
turning the camel case class name into separate words (so `URLRelatedCommands` becomes `URL Related Commands`).
This is a very sensible default, as related commands are often already in the class anyway,
because they need to use the same collaborating objects.
If, however, this behavior does not suit you, you can override the group for a
command in the following ways, in order of priority:
. Specifying a `group()` in the `@ShellMethod` annotation.
. Placing a `@ShellCommandGroup` on the class in which the command is defined. This applies
the group for all commands defined in that class (unless overridden, as explained earlier).
. Placing a `@ShellCommandGroup` on the package (through `package-info.java`)
in which the command is defined. This applies to all the commands defined in the
package (unless overridden at the method or class level, as explained earlier)
The following listing shows an example:
====
[source,java]
----
public class UserCommands {
@ShellMethod(value = "This command ends up in the 'User Commands' group")
public void foo() {}
@ShellMethod(value = "This command ends up in the 'Other Commands' group",
group = "Other Commands")
public void bar() {}
}
...
@ShellCommandGroup("Other Commands")
public class SomeCommands {
@ShellMethod(value = "This one is in 'Other Commands'")
public void wizz() {}
@ShellMethod(value = "And this one is 'Yet Another Group'",
group = "Yet Another Group")
public void last() {}
}
----
====

View File

@@ -0,0 +1,49 @@
[[styling]]
=== Styling
Starting with _2.1.x_ there is a support for centrally handling styling and theming.
There is a default theme named _default_ which can be changed using property
`spring.shell.theme.name`.
To create a new theme register new `Theme` bean with custom `ThemeSettings` where
you can tweak styles.
====
[source, java]
----
@Configuration
static class CustomThemeConfig {
@Bean
public Theme myTheme() {
return new Theme() {
@Override
public String getName() {
return "mytheme";
}
@Override
public ThemeSettings getSettings() {
return new MyThemeSettings();
}
};
}
}
static class MyThemeSettings extends ThemeSettings {
}
----
====
`ThemeResolver` can be used to resolve styles if you want to create
_jline_ styled strings programmatically.
====
[source, java]
----
@Autowired
private ThemeResolver themeResolver;
String resolvedStyle = themeResolver.resolveTag(TAG_TITLE);
AttributedStyle style = themeResolver.resolveStyle(resolvedStyle);
----
====

View File

@@ -0,0 +1,368 @@
[[uicomponents]]
=== UI Components
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
like asking input in a various forms. These usually are just plain text
input or choosing something from a list.
Templates for build-in components are in classpath under
_org/springframework/shell/component_.
Build-in components generally follow logic:
* Enter run loop for user input
* Generate component related context
* Render runtime status of a component state
* Exit
* Render final status of a component state
==== Component Render
There are two ways to implement component rendering, firstly fully
programmatically or secondly using a _ANTLR Stringtemplate_. Though
strictly speaking there is just a simple `Function` renderer interface
which takes `Context` as an input and outputs a list of `AttributedString`
but this allows to choose between _templating_ and _code_.
Templating is a good choice if you don't need to anything complex or
you just want to slightly modify existing component layouts. Rendering
via code then gives you flexibility to do whatever you need.
Programmatic way to render is simple as to create a `Function`:
====
[source, java]
----
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());
}
}
----
====
And then hook it with a component:
====
[source, java]
----
@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();
}
----
====
Component have their own context but usually shares some functionality
from a parent component types, those context variables are shown below.
[[textcomponentcontext-template-variables]]
.TextComponentContext Template Variables
|===
|Key |Description
|resultValue
|Value after component renders its result.
|name
|Name of a component, aka its title.
|message
|Possible message set for component.
|messageLevel
|Level of a message, either INFO, WARN or ERROR
|hasMessageLevelInfo
|Return true if level is INFO, false otherwise.
|hasMessageLevelWarn
|Return true if level is WARN, false otherwise.
|hasMessageLevelError
|Return true if level is ERROR, false otherwise.
|input
|Raw user input.
|===
[[selectorcomponentcontext-template-variables]]
.SelectorComponentContext Template Variables
|===
|Key |Description
|name
|Name of a component, aka title.
|input
|Raw user input, mostly for filter.
|itemStates
|Full list of item states.
|itemStateView
|Visible list of item states.
|isResult
|Return if context is in a result mode.
|cursorRow
|Current cursor row in a selector
|===
==== Build-in Components
===== String Input
Used to ask a simple text input from a user, optionally masking values
if content contains something sensitive.
====
[source, java]
----
@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();
}
}
----
====
image::images/component-text-input-1.svg[text input]
Context object is `StringInputContext`.
[[stringinputcontext-template-variables]]
.StringInputContext Template Variables
|===
|Key |Description
|defaultValue
|Default value if set, null otherwise.
|maskedInput
|Masked input value
|maskedResultValue
|Masked result value
|maskCharacter
|Mask character if set, null otherwise.
|hasMaskCharacter
|Is true if mask character is set, false otherwise.
|model
|Parent context variables <<textcomponentcontext-template-variables>>
|===
===== Path Input
Used to ask a `Path` from a user and gives additional info about a path itself.
====
[source, java]
----
@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();
}
}
----
====
image::images/component-path-input-1.svg[text input]
Context object is `PathInputContext`.
[[pathinputcontext-template-variables]]
.PathInputContext Template Variables
|===
|Key |Description
|model
|Parent context variables <<textcomponentcontext-template-variables>>
|===
===== Confirmation
Used to ask a simple confirmation from a user and essentially is
yes/no question.
====
[source, java]
----
@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();
}
}
----
====
image::images/component-confirmation-1.svg[text input]
Context object is `ConfirmationInputContext`.
[[confirmationinputcontext-template-variables]]
.ConfirmationInputContext Template Variables
|===
|Key |Description
|defaultValue
|Default value, either true or false.
|model
|Parent context variables <<textcomponentcontext-template-variables>>
|===
===== Single Select
Used to ask an item from a list and is essentially similar to simple
dropbox implementation.
====
[source, java]
----
@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;
}
}
----
====
image::images/component-single-select-1.svg[text input]
Context object is `SingleItemSelectorContext`.
[[singleitemselectorcontext-template-variables]]
.SingleItemSelectorContext Template Variables
|===
|Key |Description
|value
|Returned value when component exists.
|rows
|Visible items where rows list contains maps of name and selected items.
|model
|Parent context variables <<selectorcomponentcontext-template-variables>>
|===
===== Multi Select
Used to ask an items from a list.
====
[source, java]
----
@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;
}
}
----
====
image::images/component-multi-select-1.svg[text input]
Context object is `MultiItemSelectorContext`.
[[multiitemselectorcontext-template-variables]]
.MultiItemSelectorContext Template Variables
|===
|Key |Description
|values
|Returned values when component exists.
|rows
|Visible items where rows list contains maps of name, selected, onrow and enabled items.
|model
|Parent context variables <<selectorcomponentcontext-template-variables>>
|===

View File

@@ -0,0 +1,33 @@
[[validating-command-arguments]]
=== Validating Command Arguments
Spring Shell integrates with the https://beanvalidation.org/[Bean Validation API] to support
automatic and self-documenting constraints on command parameters.
Annotations found on command parameters as well as annotations at the method level are
honored and trigger validation prior to the command executing. Consider the following command:
====
[source, java]
----
@ShellMethod("Change password.")
public String changePassword(@Size(min = 8, max = 40) String password) {
return "Password successfully set to " + password;
}
----
====
From the preceding example, you get the following behavior for free:
====
----
shell:>change-password hello
The following constraints were not met:
--password string : size must be between 8 and 40 (You passed 'hello')
----
====
.Applies to All Command Implementations
NOTE: It is important to note that bean validation applies to all command implementations, whether
they use the "standard" API or any other API, through the use of an adapter
(see <<support-for-shell-1-and-jcommander,Supporting Other APIs>>)

View File

@@ -0,0 +1,55 @@
=== Writing Your Own Commands
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).
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 method bears the `@ShellMethod` annotation.
[TIP]
====
The `@ShellComponent` is a stereotype annotation that is itself meta-annotated with `@Component`. As a result,
you can used it in addition to the filtering mechanism to declare beans (for example, by using `@ComponentScan`).
You can customize the name of the created bean by using the `value` attribute of the annotation.
====
[[documenting-the-command]]
==== Documenting the Command
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
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
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]
----
@ShellMethod(value = "Add numbers.", key = "sum")
public int add(int a, int b) {
return a + b;
}
----
====
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,
keep in mind that consistency is often appreciated by users (that is, you should avoid mixing dashed-names with
spaced names and other inconsistencies).

View File

@@ -0,0 +1,33 @@
== Using Spring Shell
This section describes how to use Spring Shell.
[IMPORTANT]
====
_Spring Shell 2.1.x_ is a major rework to bring codebase up-to-date with
existing _Spring Boot_ versions, adding new features and especially
making it work with _GraalVM_ which makes command-line applications much
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-invoke-command.adoc[]
include::using-shell-validate-command.adoc[]
include::using-shell-availability.adoc[]
include::using-shell-organize.adoc[]
include::using-shell-built-in.adoc[]
include::using-shell-interaction-mode.adoc[]
include::using-shell-native.adoc[]
include::using-shell-styling.adoc[]
include::using-shell-ui-components.adoc[]
include::using-shell-customize.adoc[]

File diff suppressed because it is too large Load Diff