From 942108e612361134e14e4519e71528a760cceb2b Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Sat, 23 Apr 2022 08:35:14 +0100 Subject: [PATCH] Update docs - Split adoc files - Relates #383 --- .../main/asciidoc/extending-spring-shell.adoc | 14 - .../src/main/asciidoc/getting-started.adoc | 120 ++ .../src/main/asciidoc/index.adoc | 12 +- ...is-spring-shell.adoc => introduction.adoc} | 3 +- .../asciidoc/using-shell-availability.adoc | 147 ++ .../main/asciidoc/using-shell-built-in.adoc | 139 ++ .../main/asciidoc/using-shell-customize.adoc | 228 +++ .../using-shell-interaction-mode.adoc | 16 + .../asciidoc/using-shell-invoke-command.adoc | 359 ++++ .../src/main/asciidoc/using-shell-native.adoc | 31 + .../main/asciidoc/using-shell-organize.adoc | 54 + .../main/asciidoc/using-shell-styling.adoc | 49 + .../asciidoc/using-shell-ui-components.adoc | 368 ++++ .../using-shell-validate-command.adoc | 33 + .../asciidoc/using-shell-write-command.adoc | 55 + .../src/main/asciidoc/using-shell.adoc | 33 + .../src/main/asciidoc/using-spring-shell.adoc | 1627 ----------------- 17 files changed, 1639 insertions(+), 1649 deletions(-) delete mode 100644 spring-shell-docs/src/main/asciidoc/extending-spring-shell.adoc create mode 100644 spring-shell-docs/src/main/asciidoc/getting-started.adoc rename spring-shell-docs/src/main/asciidoc/{what-is-spring-shell.adoc => introduction.adoc} (94%) create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell-availability.adoc create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell-built-in.adoc create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell-customize.adoc create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell-interaction-mode.adoc create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell-invoke-command.adoc create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell-native.adoc create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell-organize.adoc create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell-styling.adoc create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell-ui-components.adoc create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell-validate-command.adoc create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell-write-command.adoc create mode 100644 spring-shell-docs/src/main/asciidoc/using-shell.adoc delete mode 100644 spring-shell-docs/src/main/asciidoc/using-spring-shell.adoc diff --git a/spring-shell-docs/src/main/asciidoc/extending-spring-shell.adoc b/spring-shell-docs/src/main/asciidoc/extending-spring-shell.adoc deleted file mode 100644 index 9e3087fd..00000000 --- a/spring-shell-docs/src/main/asciidoc/extending-spring-shell.adoc +++ /dev/null @@ -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 - - diff --git a/spring-shell-docs/src/main/asciidoc/getting-started.adoc b/spring-shell-docs/src/main/asciidoc/getting-started.adoc new file mode 100644 index 00000000..eb8fccf8 --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/getting-started.adoc @@ -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] +---- +... + + + org.springframework.boot + spring-boot-starter + + ... + +---- +==== + +[[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+] +---- +... + + org.springframework.shell + {spring-shell-starter} + {project-version} + +... +---- +==== + +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. diff --git a/spring-shell-docs/src/main/asciidoc/index.adoc b/spring-shell-docs/src/main/asciidoc/index.adoc index 297cb05e..d5a9dc83 100644 --- a/spring-shell-docs/src/main/asciidoc/index.adoc +++ b/spring-shell-docs/src/main/asciidoc/index.adoc @@ -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[] diff --git a/spring-shell-docs/src/main/asciidoc/what-is-spring-shell.adoc b/spring-shell-docs/src/main/asciidoc/introduction.adoc similarity index 94% rename from spring-shell-docs/src/main/asciidoc/what-is-spring-shell.adoc rename to spring-shell-docs/src/main/asciidoc/introduction.adoc index 2a9d60c5..139e409a 100644 --- a/spring-shell-docs/src/main/asciidoc/what-is-spring-shell.adoc +++ b/spring-shell-docs/src/main/asciidoc/introduction.adoc @@ -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. diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-availability.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-availability.adoc new file mode 100644 index 00000000..1eeedc9c --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell-availability.adoc @@ -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 <>. + +[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. diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-built-in.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-built-in.adoc new file mode 100644 index 00000000..244f9675 --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell-built-in.adoc @@ -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 <>). +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 <> 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 ` to learn more. +---- +==== + +Typing `help ` 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 = ] +---- +==== + +==== 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 <>) 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] +---- + +---- +==== + +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`. diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-customize.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-customize.adoc new file mode 100644 index 00000000..94f0bfa2 --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell-customize.adoc @@ -0,0 +1,228 @@ +=== Customizing the Shell + +[[overriding-or-disabling-built-in-commands]] +==== Overriding or Disabling Built-In Commands + +Spring Shell provides <> 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+] +---- + + org.springframework.shell + {starter-artifactId} + {project-version} + + + org.springframework.shell + spring-shell-standard-commands + + + +---- +==== +===== + +[[disabling-specific-commands]] +===== Disabling Specific Commands + +To disable a single built-in command, set the `spring.shell.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 <> and have your implementation registered +with the same name. +* Have your implementing class implement the `.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`, `GenericConverter`, or +`ConverterFactory` that it finds in the application context. + +This means that you can customize conversion to your custom objects +by installing a `Converter` 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 { + + @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 : (You passed '') +---- +==== + +See <> 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"`. +==== diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-interaction-mode.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-interaction-mode.adoc new file mode 100644 index 00000000..5206ea7f --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell-interaction-mode.adoc @@ -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 <>. + +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. diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-invoke-command.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-invoke-command.adoc new file mode 100644 index 00000000..2ce0915a --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell-invoke-command.adoc @@ -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 <>, 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 <>, 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 <> 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 <> 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 <>). + +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 <>) +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 diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-native.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-native.adoc new file mode 100644 index 00000000..a74d2b3b --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell-native.adoc @@ -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. +... +---- +==== diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-organize.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-organize.adoc new file mode 100644 index 00000000..100eec47 --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell-organize.adoc @@ -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() {} +} +---- +==== diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-styling.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-styling.adoc new file mode 100644 index 00000000..d8810bc7 --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell-styling.adoc @@ -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); +---- +==== diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-ui-components.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-ui-components.adoc new file mode 100644 index 00000000..db731cf0 --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell-ui-components.adoc @@ -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> { + @Override + public List 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 <> +|=== + +===== 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 <> +|=== + +===== 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 <> +|=== + +===== 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> items = new ArrayList<>(); + items.add(SelectorItem.of("key1", "value1")); + items.add(SelectorItem.of("key2", "value2")); + SingleItemSelector> component = new SingleItemSelector<>(getTerminal(), + items, "testSimple", null); + component.setResourceLoader(getResourceLoader()); + component.setTemplateExecutor(getTemplateExecutor()); + SingleItemSelectorContext> 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 <> +|=== + +===== 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> items = new ArrayList<>(); + items.add(SelectorItem.of("key1", "value1")); + items.add(SelectorItem.of("key2", "value2", false)); + items.add(SelectorItem.of("key3", "value3")); + MultiItemSelector> component = new MultiItemSelector<>(getTerminal(), + items, "testSimple", null); + component.setResourceLoader(getResourceLoader()); + component.setTemplateExecutor(getTemplateExecutor()); + MultiItemSelectorContext> 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 <> +|=== diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-validate-command.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-validate-command.adoc new file mode 100644 index 00000000..c33c99b5 --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell-validate-command.adoc @@ -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 <>) diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-write-command.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-write-command.adoc new file mode 100644 index 00000000..6bdbfefa --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell-write-command.adoc @@ -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 <>). 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 <>). + +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). diff --git a/spring-shell-docs/src/main/asciidoc/using-shell.adoc b/spring-shell-docs/src/main/asciidoc/using-shell.adoc new file mode 100644 index 00000000..22391e88 --- /dev/null +++ b/spring-shell-docs/src/main/asciidoc/using-shell.adoc @@ -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[] diff --git a/spring-shell-docs/src/main/asciidoc/using-spring-shell.adoc b/spring-shell-docs/src/main/asciidoc/using-spring-shell.adoc deleted file mode 100644 index d8e457d5..00000000 --- a/spring-shell-docs/src/main/asciidoc/using-spring-shell.adoc +++ /dev/null @@ -1,1627 +0,0 @@ -:starter-artifactId: spring-shell-starter - -== 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. -==== - -=== 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] ----- -... - - - org.springframework.boot - spring-boot-starter - - ... - ----- -==== - -==== 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+] ----- -... - - org.springframework.shell - {starter-artifactId} - {project-version} - -... ----- -==== - -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. - -[[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; - } -} ----- -==== - -==== 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. - -=== Writing Your Own Commands - -The way Spring Shell decides to turn a method into an actual shell command is entirely pluggable -(see <>). 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 <>). - -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). - - -=== 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 <>, 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 <>, 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 <> 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 <> 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 <>). - -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 <>) -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 - -[[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 <>) - -[[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 <>. - -[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. - -[[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() {} -} ----- -==== - -[[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 <>). -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 <> 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 ` to learn more. ----- -==== - -Typing `help ` 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 = ] ----- -==== - -==== 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 <>) 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] ----- - ----- -==== - -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`. - -=== 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 <>. - -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. - -[[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. -... ----- -==== - -[[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); ----- -==== - -[[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> { - @Override - public List 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 <> -|=== - -===== 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 <> -|=== - -===== 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 <> -|=== - -===== 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> items = new ArrayList<>(); - items.add(SelectorItem.of("key1", "value1")); - items.add(SelectorItem.of("key2", "value2")); - SingleItemSelector> component = new SingleItemSelector<>(getTerminal(), - items, "testSimple", null); - component.setResourceLoader(getResourceLoader()); - component.setTemplateExecutor(getTemplateExecutor()); - SingleItemSelectorContext> 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 <> -|=== - -===== 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> items = new ArrayList<>(); - items.add(SelectorItem.of("key1", "value1")); - items.add(SelectorItem.of("key2", "value2", false)); - items.add(SelectorItem.of("key3", "value3")); - MultiItemSelector> component = new MultiItemSelector<>(getTerminal(), - items, "testSimple", null); - component.setResourceLoader(getResourceLoader()); - component.setTemplateExecutor(getTemplateExecutor()); - MultiItemSelectorContext> 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 <> -|=== - -=== Customizing the Shell - -[[overriding-or-disabling-built-in-commands]] -==== Overriding or Disabling Built-In Commands - -Spring Shell provides <> 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+] ----- - - org.springframework.shell - {starter-artifactId} - {project-version} - - - org.springframework.shell - spring-shell-standard-commands - - - ----- -==== -===== - -[[disabling-specific-commands]] -===== Disabling Specific Commands - -To disable a single built-in command, set the `spring.shell.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 <> and have your implementation registered with the same name. -* Have your implementing class implement the `.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`, `GenericConverter`, or -`ConverterFactory` that it finds in the application context. - -This means that you can customize conversion to your custom objects -by installing a `Converter` 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 { - - @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 : (You passed '') ----- -==== - -See <> 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"`. -==== - -//==== Overriding the JLine Parser - -//=== Using Without Spring Boot