(window.webpackJsonp=window.webpackJsonp||[]).push([[336],{763:function(e,t,a){"use strict";a.r(t);var n=a(56),o=Object(n.a)({},(function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[a("h1",{attrs:{id:"spring-shell-reference-documentation"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#spring-shell-reference-documentation"}},[e._v("#")]),e._v(" Spring Shell Reference Documentation")]),e._v(" "),a("h2",{attrs:{id:"what-is-spring-shell"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#what-is-spring-shell"}},[e._v("#")]),e._v(" What is Spring Shell?")]),e._v(" "),a("p",[e._v("Not all applications need a fancy web user interface!\nSometimes, interacting with an application using an interactive terminal is\nthe most appropriate way to get things done.")]),e._v(" "),a("p",[e._v("Spring Shell allows one to easily create such a runnable application, where the\nuser will enter textual commands that will get executed until the program terminates.\nThe Spring Shell project provides the infrastructure to create such a REPL (Read, Eval,\nPrint Loop), allowing the developer to concentrate on the commands implementation, using\nthe familiar Spring programming model.")]),e._v(" "),a("p",[e._v("Advanced features such as parsing, TAB completion, colorization of output, fancy ascii-art\ntable display, input conversion and validation all come for free, with the developer only\nhaving to focus on core command logic.")]),e._v(" "),a("h2",{attrs:{id:"using-spring-shell"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#using-spring-shell"}},[e._v("#")]),e._v(" Using Spring Shell")]),e._v(" "),a("h3",{attrs:{id:"getting-started"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#getting-started"}},[e._v("#")]),e._v(" Getting Started")]),e._v(" "),a("p",[e._v("To see what Spring Shell has to offer, let’s write a trivial shell application that\nhas a simple command to add two numbers together.")]),e._v(" "),a("h4",{attrs:{id:"let-s-write-a-simple-boot-app"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#let-s-write-a-simple-boot-app"}},[e._v("#")]),e._v(" Let’s Write a Simple Boot App")]),e._v(" "),a("p",[e._v("Starting with version 2, Spring Shell has been rewritten from the ground up with various\nenhancements in mind, one of which is easy integration with Spring Boot, although it is\nnot a strong requirement.\nFor the purpose of this tutorial, let’s create a simple Boot application, for example\nusing "),a("a",{attrs:{href:"https://start.spring.io",target:"_blank",rel:"noopener noreferrer"}},[e._v("start.spring.io"),a("OutboundLink")],1),e._v(". This minimal application only depends on "),a("code",[e._v("spring-boot-starter")]),e._v("and configures the "),a("code",[e._v("spring-boot-maven-plugin")]),e._v(", generating an executable über-jar:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("...\n\n \n org.springframework.boot\n spring-boot-starter\n \n ...\n")])])]),a("h4",{attrs:{id:"adding-a-dependency-on-spring-shell"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#adding-a-dependency-on-spring-shell"}},[e._v("#")]),e._v(" Adding a Dependency on Spring Shell")]),e._v(" "),a("p",[e._v("The easiest way to get going with Spring Shell is to depend on the "),a("code",[e._v("spring-shell-starter")]),e._v(" artifact.\nThis comes with everything one needs to use Spring Shell and plays nicely with Boot,\nconfiguring only the necessary beans as needed:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("...\n\n org.springframework.shell\n spring-shell-starter\n 2.0.1.RELEASE\n\n...\n")])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Given that Spring Shell will kick in and start the REPL by virtue of this dependency being present,"),a("br"),e._v("you’ll need to either build skipping tests ("),a("code",[e._v("-DskipTests")]),e._v(") throughout this tutorial or remove the sample integration test"),a("br"),e._v("that was generated by "),a("a",{attrs:{href:"https://start.spring.io",target:"_blank",rel:"noopener noreferrer"}},[e._v("start.spring.io"),a("OutboundLink")],1),e._v(". If you don’t do so, the integration test will create"),a("br"),e._v("the Spring "),a("code",[e._v("ApplicationContext")]),e._v(" and, depending on your build tool, will stay stuck in the eval loop or crash with a NPE.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h4",{attrs:{id:"your-first-command"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#your-first-command"}},[e._v("#")]),e._v(" Your first command")]),e._v(" "),a("p",[e._v("It’s time to add our first command. Create a new class (name it however you want) and\nannotate it with "),a("code",[e._v("@ShellComponent")]),e._v(" (a variation of "),a("code",[e._v("@Component")]),e._v(" that is used to restrict\nthe set of classes that are scanned for candidate commands).")]),e._v(" "),a("p",[e._v("Then, create an "),a("code",[e._v("add")]),e._v(" method that takes two ints ("),a("code",[e._v("a")]),e._v(" and "),a("code",[e._v("b")]),e._v(") and returns their sum. Annotate it\nwith "),a("code",[e._v("@ShellMethod")]),e._v(" and provide a description of the command in the annotation (the only piece of\ninformation that is required):")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('package com.example.demo;\n\nimport org.springframework.shell.standard.ShellMethod;\nimport org.springframework.shell.standard.ShellComponent;\n\n@ShellComponent\npublic class MyCommands {\n\n @ShellMethod("Add two integers together.")\n public int add(int a, int b) {\n return a + b;\n }\n}\n')])])]),a("h4",{attrs:{id:"let-s-give-it-a-ride"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#let-s-give-it-a-ride"}},[e._v("#")]),e._v(" Let’s Give It a Ride!")]),e._v(" "),a("p",[e._v("Build the application and run the generated jar, like so;")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("./mvnw clean install -DskipTests\n[...]\n\njava -jar target/demo-0.0.1-SNAPSHOT.jar\n")])])]),a("p",[e._v("You’ll be greeted by the following screen (the banner comes from Spring Boot, and can be customized"),a("a",{attrs:{href:"https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-banner",target:"_blank",rel:"noopener noreferrer"}},[e._v("as usual"),a("OutboundLink")],1),e._v("):")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v(" . ____ _ __ _ _\n /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\\n( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\\n \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )\n ' |____| .__|_| |_|_| |_\\__, | / / / /\n =========|_|==============|___/=/_/_/_/\n :: Spring Boot :: (v1.5.6.RELEASE)\n\nshell:>\n")])])]),a("p",[e._v("Below is a yellow "),a("code",[e._v("shell:>")]),e._v(" prompt that invites you to type commands. Type "),a("code",[e._v("add 1 2")]),e._v(" then ENTER and admire the magic!")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>add 1 2\n3\n")])])]),a("p",[e._v("Try to play with the shell (hint: there is a "),a("code",[e._v("help")]),e._v(" command) and when you’re done, type "),a("code",[e._v("exit")]),e._v(" ENTER.")]),e._v(" "),a("p",[e._v("The rest of this document delves deeper into the whole Spring Shell programming model.")]),e._v(" "),a("h3",{attrs:{id:"writing-your-own-commands"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#writing-your-own-commands"}},[e._v("#")]),e._v(" Writing your own Commands")]),e._v(" "),a("p",[e._v("The way Spring Shell decides to turn a method into an actual shell command is entirely pluggable\n(see "),a("a",{attrs:{href:"#extending-spring-shell"}},[e._v("Extending Spring Shell")]),e._v("), but as of Spring Shell 2.x, the recommended way to write commands\nis to use the new API described in this section (the so-called "),a("em",[e._v("standard")]),e._v(" API).")]),e._v(" "),a("p",[e._v("Using the "),a("em",[e._v("standard")]),e._v(" API, methods on beans will be turned into executable commands provided that")]),e._v(" "),a("ul",[a("li",[a("p",[e._v("the bean class bears the "),a("code",[e._v("@ShellComponent")]),e._v(" annotation. This is used to restrict the set of beans that\nare considered.")])]),e._v(" "),a("li",[a("p",[e._v("the method bears the "),a("code",[e._v("@ShellMethod")]),e._v(" annotation.")])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("The "),a("code",[e._v("@ShellComponent")]),e._v(" is a stereotype annotation itself meta-annotated with "),a("code",[e._v("@Component")]),e._v(". As such, it"),a("br"),e._v("can be used in addition to the filtering mechanism to also "),a("em",[e._v("declare")]),e._v(" beans ("),a("em",[e._v("e.g.")]),e._v(" using "),a("code",[e._v("@ComponentScan")]),e._v(")."),a("br"),a("br"),e._v("The name of the created bean can be customized using the "),a("code",[e._v("value")]),e._v(" attribute of the annotation.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h4",{attrs:{id:"it-s-all-about-documentation"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#it-s-all-about-documentation"}},[e._v("#")]),e._v(" It’s all about Documentation!")]),e._v(" "),a("p",[e._v("The only required attribute of the "),a("code",[e._v("@ShellMethod")]),e._v(" annotation is its "),a("code",[e._v("value")]),e._v(" attribute, which should be used\nto write a short, one-sentence, description of what the command does. This is important so that your users can\nget consistent help about your commands without having to leave the shell (see "),a("a",{attrs:{href:"#help-command"}},[e._v("Integrated Documentation with the "),a("code",[e._v("help")]),e._v(" Command")]),e._v(").")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("The description of your command should be short, one or two sentences only. For better consistency, it is"),a("br"),e._v("recommended that it starts with a capital letter and ends with a dot.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h4",{attrs:{id:"customizing-the-command-name-s"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#customizing-the-command-name-s"}},[e._v("#")]),e._v(" Customizing the Command Name(s)")]),e._v(" "),a("p",[e._v("By default, there is no need to specify the "),a("em",[e._v("key")]),e._v(" for your command ("),a("em",[e._v("i.e.")]),e._v(" the word(s) that should be used\nto invoke it in the shell). The name of the method will be used as the command key, turning camelCase names into\ndashed, gnu-style, names (that is, "),a("code",[e._v("sayHello()")]),e._v(" will become "),a("code",[e._v("say-hello")]),e._v(").")]),e._v(" "),a("p",[e._v("It is possible, however, to explicitly set the command key, using the "),a("code",[e._v("key")]),e._v(" attribute of the annotation, like so:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v(' @ShellMethod(value = "Add numbers.", key = "sum")\n public int add(int a, int b) {\n return a + b;\n }\n')])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("The "),a("code",[e._v("key")]),e._v(" attribute accepts multiple values."),a("br"),e._v("If you set multiple keys for a single method, then the command will be registered using those different aliases.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("The command key can contain pretty much any character, including spaces. When coming up with names though,"),a("br"),e._v("keep in mind that consistency is often appreciated by users ("),a("em",[e._v("i.e.")]),e._v(" avoid mixing dashed-names with spaced names, "),a("em",[e._v("etc.")]),e._v(")")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h3",{attrs:{id:"invoking-your-commands"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#invoking-your-commands"}},[e._v("#")]),e._v(" Invoking your Commands")]),e._v(" "),a("h4",{attrs:{id:"by-name-vs-positional-parameters"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#by-name-vs-positional-parameters"}},[e._v("#")]),e._v(" By Name "),a("em",[e._v("vs.")]),e._v(" Positional Parameters")]),e._v(" "),a("p",[e._v("As seen above, decorating a method with "),a("code",[e._v("@ShellMethod")]),e._v(" is the sole requirement for creating a command.\nWhen doing so, the user can set the value of all method parameters in two possible ways:")]),e._v(" "),a("ul",[a("li",[a("p",[e._v("using a parameter key ("),a("em",[e._v("e.g.")]),e._v(" "),a("code",[e._v("--arg value")]),e._v('). This approach is called "by name" parameters')])]),e._v(" "),a("li",[a("p",[e._v('or without a key, simply setting parameter values in the same order they appear in the method signature ("positional" parameters).')])])]),e._v(" "),a("p",[e._v("These two approaches can be mixed and matched, with named parameters always taking precedence (as they are less\nprone to ambiguity). As such, given the following command")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v(' @ShellMethod("Display stuff.")\n public String echo(int a, int b, int c) {\n return String.format("You said a=%d, b=%d, c=%d", a, b, c);\n }\n')])])]),a("p",[e._v("then the following invocations are all equivalent, as witnessed by the output:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>echo 1 2 3 (1)\nYou said a=1, b=2, c=3\n\nshell:>echo --a 1 --b 2 --c 3 (2)\nYou said a=1, b=2, c=3\n\nshell:>echo --b 2 --c 3 --a 1 (3)\nYou said a=1, b=2, c=3\n\nshell:>echo --a 1 2 3 (4)\nYou said a=1, b=2, c=3\n\nshell:>echo 1 --c 3 2 (5)\nYou said a=1, b=2, c=3\n")])])]),a("table",[a("thead",[a("tr",[a("th",[a("strong",[e._v("1")])]),e._v(" "),a("th",[e._v("This uses positional parameters")])])]),e._v(" "),a("tbody",[a("tr",[a("td",[a("strong",[e._v("2")])]),e._v(" "),a("td",[e._v("This is an example of full by-name parameters")])]),e._v(" "),a("tr",[a("td",[a("strong",[e._v("3")])]),e._v(" "),a("td",[e._v("By-name parameters can be reordered as desired")])]),e._v(" "),a("tr",[a("td",[a("strong",[e._v("4")])]),e._v(" "),a("td",[e._v("You can use a mix of the two approaches")])]),e._v(" "),a("tr",[a("td",[a("strong",[e._v("5")])]),e._v(" "),a("td",[e._v("The non by-name parameters are resolved in the order they appear")])])])]),e._v(" "),a("h5",{attrs:{id:"customizing-the-named-parameter-key-s"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#customizing-the-named-parameter-key-s"}},[e._v("#")]),e._v(" Customizing the Named Parameter Key(s)")]),e._v(" "),a("p",[e._v("As seen above, the default strategy for deriving the key for a named parameter is to use the java\nname of the method signature and prefixing it with two dashes ("),a("code",[e._v("--")]),e._v("). This can be customized in two ways:")]),e._v(" "),a("ol",[a("li",[a("p",[e._v("to change the default prefix for the whole method, use the "),a("code",[e._v("prefix()")]),e._v(" attribute of the"),a("code",[e._v("@ShellMethod")]),e._v(" annotation")])]),e._v(" "),a("li",[a("p",[e._v("to override the "),a("em",[e._v("whole")]),e._v(" key on a per-parameter fashion, annotate the parameter with the "),a("code",[e._v("@ShellOption")]),e._v(" annotation.")])])]),e._v(" "),a("p",[e._v("Have a look at the following example:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v(' @ShellMethod(value = "Display stuff.", prefix="-")\n public String echo(int a, int b, @ShellOption("--third") int c) {\n return String.format("You said a=%d, b=%d, c=%d", a, b, c);\n }\n')])])]),a("p",[e._v("For such a setup, the possible parameter keys will be "),a("code",[e._v("-a")]),e._v(", "),a("code",[e._v("-b")]),e._v(" and "),a("code",[e._v("--third")]),e._v(".")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("It is possible to specify several keys for a single parameter. If so, these will be mutually exclusive ways"),a("br"),e._v("to specify the same parameter (so only one of them can be used). As an example, here is the signature of the"),a("br"),e._v("built-in "),a("a",{attrs:{href:"#help-command"}},[a("code",[e._v("help")])]),e._v(" command:"),a("br"),a("br"),a("code",[e._v('
@ShellMethod("Describe a command.")
public String help(@ShellOption({"-C", "--command"}) String command) {
...
}
')])])])]),e._v(" "),a("tbody")]),e._v(" "),a("h4",{attrs:{id:"optional-parameters-and-default-values"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#optional-parameters-and-default-values"}},[e._v("#")]),e._v(" Optional Parameters and Default Values")]),e._v(" "),a("p",[e._v("Spring Shell provides the ability to give parameters default values, which will allow the user to omit\nthose parameters:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v(' @ShellMethod("Say hello.")\n public String greet(@ShellOption(defaultValue="World") String who) {\n return "Hello " + who;\n }\n')])])]),a("p",[e._v("Now, the "),a("code",[e._v("greet")]),e._v(" command can still be invoked as "),a("code",[e._v("greet Mother")]),e._v(" (or "),a("code",[e._v("greet --who Mother")]),e._v("), but the following\nis also possible:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>greet\nHello World\n")])])]),a("h4",{attrs:{id:"parameter-arity"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#parameter-arity"}},[e._v("#")]),e._v(" Parameter Arity")]),e._v(" "),a("p",[e._v("Up to now, it has always been assumed that each parameter mapped to a single word entered by the user.\nSituations may arise though, when a parameter value should be "),a("em",[e._v("multi valued")]),e._v(". This is driven by the "),a("code",[e._v("arity()")]),e._v("attribute of the "),a("code",[e._v("@ShellOption")]),e._v(" annotation. Simply use a collection or array for the parameter type, and specify how\nmany values are expected:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v(' @ShellMethod("Add Numbers.")\n public float add(@ShellOption(arity=3) float[] numbers) {\n return numbers[0] + numbers[1] + numbers[2];\n }\n')])])]),a("p",[e._v("The command may then be invoked using any of the following syntax:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>add 1 2 3.3\n6.3\nshell:>add --numbers 1 2 3.3\n6.3\n")])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("When using the "),a("em",[e._v("by-name")]),e._v(" parameter approach, the key should "),a("strong",[e._v("not")]),e._v(" be repeated. The following does "),a("strong",[e._v("not")]),e._v(" work:"),a("br"),a("br"),a("code",[e._v("
shell:>add --numbers 1 --numbers 2 --numbers 3.3
")])])])]),e._v(" "),a("tbody")]),e._v(" "),a("h5",{attrs:{id:"infinite-arity"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#infinite-arity"}},[e._v("#")]),e._v(" Infinite Arity")]),e._v(" "),a("p",[e._v("TO BE IMPLEMENTED")]),e._v(" "),a("h5",{attrs:{id:"special-handling-of-boolean-parameters"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#special-handling-of-boolean-parameters"}},[e._v("#")]),e._v(" Special Handling of Boolean Parameters")]),e._v(" "),a("p",[e._v("When it comes to parameter arity, there is a kind of parameters that receives a special treatment by default, as\nis often the case in command-line utilities.\nBoolean (that is, "),a("code",[e._v("boolean")]),e._v(" as well as "),a("code",[e._v("java.lang.Boolean")]),e._v(") parameters behave like they have an "),a("code",[e._v("arity()")]),e._v(" of "),a("code",[e._v("0")]),e._v(' by default, allowing users to set their values using a "flag" approach.\nTake a look at the following:')]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v(' @ShellMethod("Terminate the system.")\n public String shutdown(boolean force) {\n return "You said " + force;\n }\n')])])]),a("p",[e._v("This allows the following invocations:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>shutdown\nYou said false\nshell:>shutdown --force\nYou said true\n")])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("This special treatment plays well with the "),a("a",{attrs:{href:"#optional-parameters-default-values"}},[e._v("default value")]),e._v(" specification. Although the default"),a("br"),e._v("for boolean parameters is to have their default value be "),a("code",[e._v("false")]),e._v(", you can specify otherwise ("),a("em",[e._v("i.e.")]),a("code",[e._v('@ShellOption(defaultValue="true")')]),e._v(") and the behavior will be inverted (that is, not specifying the parameter"),a("br"),e._v("will result in the value being "),a("code",[e._v("true")]),e._v(", and specifying the flag will result in the value being "),a("code",[e._v("false")]),e._v(")")])])]),e._v(" "),a("tbody")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Having this behavior of implicit "),a("code",[e._v("arity()=0")]),e._v(" prevents the user from specifying a value ("),a("em",[e._v("e.g.")]),e._v(" "),a("code",[e._v("shutdown --force true")]),e._v(")."),a("br"),e._v("If you would like to allow this behavior (and forego the flag approach), then force an arity of "),a("code",[e._v("1")]),e._v(" using the annotation:"),a("br"),a("br"),a("code",[e._v('
@ShellMethod("Terminate the system.")
public String shutdown(@ShellOption(arity=1, defaultValue="false") boolean force) {
return "You said " + force;
}
')])])])]),e._v(" "),a("tbody")]),e._v(" "),a("h4",{attrs:{id:"quotes-handling"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#quotes-handling"}},[e._v("#")]),e._v(" Quotes Handling")]),e._v(" "),a("p",[e._v("Spring Shell takes user input and tokenizes it in "),a("em",[e._v("words")]),e._v(", splitting on space characters.\nIf the user wants to provide a parameter value that contains spaces, that value needs to be quoted.\nBoth single ("),a("code",[e._v("'")]),e._v(") and double ("),a("code",[e._v('"')]),e._v(") quotes are supported, and those quotes will not be part of the value:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v(' @ShellMethod("Prints what has been entered.")\n public String echo(String what) {\n return "You said " + what;\n }\n')])])]),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>echo Hello\nYou said Hello\nshell:>echo 'Hello'\nYou said Hello\nshell:>echo 'Hello World'\nYou said Hello World\nshell:>echo \"Hello World\"\nYou said Hello World\n")])])]),a("p",[e._v("Supporting both single and double quotes allows the user to easily embed one type of quotes into\na value:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('shell:>echo "I\'m here!"\nYou said I\'m here!\nshell:>echo \'He said "Hi!"\'\nYou said He said "Hi!"\n')])])]),a("p",[e._v("Should the user need to embed the same kind of quote that was used to quote the whole parameter,\nthe escape sequence uses the backslash ("),a("code",[e._v("\\")]),e._v(") character:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>echo 'I\\'m here!'\nYou said I'm here!\nshell:>echo \"He said \\\"Hi!\\\"\"\nYou said He said \"Hi!\"\nshell:>echo I\\'m here!\nYou said I'm here!\n")])])]),a("p",[e._v("It is also possible to escape space characters when not using enclosing quotes, as such:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>echo This\\ is\\ a\\ single\\ value\nYou said This is a single value\n")])])]),a("h4",{attrs:{id:"interacting-with-the-shell"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#interacting-with-the-shell"}},[e._v("#")]),e._v(" Interacting with the Shell")]),e._v(" "),a("p",[e._v("The Spring Shell project builds on top of the "),a("a",{attrs:{href:"https://github.com/jline/jline3",target:"_blank",rel:"noopener noreferrer"}},[e._v("JLine"),a("OutboundLink")],1),e._v(" library, and as such brings\na lot of nice interactive features, some of which are detailed in this section.")]),e._v(" "),a("p",[e._v("First and foremost, Spring Shell supports TAB completion almost everywhere possible. So if there\nis an "),a("code",[e._v("echo")]),e._v(" command and the user presses e, c, TAB then "),a("code",[e._v("echo")]),e._v(" will appear.\nShould there be several commands that start with "),a("code",[e._v("ec")]),e._v(", then the user will be prompted to choose (using TAB orShift+TAB to navigate, and ENTER for selection.)")]),e._v(" "),a("p",[e._v("But completion does not stop at command keys. It also works for parameter keys ("),a("code",[e._v("--arg")]),e._v(") and even\nparameter values, if the application developer registered the appropriate beans (see "),a("a",{attrs:{href:"#providing-tab-completion"}},[e._v("Providing TAB Completion Proposals")]),e._v(").")]),e._v(" "),a("p",[e._v("Another nice feature of Spring Shell apps is support for line continuation. If a command and its parameters\nis too long and does not fit nicely on screen, a user may chunk it and terminate a line with a backslash ("),a("code",[e._v("\\")]),e._v(") character\nthen hit ENTER and continue on the next line. Uppon submission of the whole command, this will\nbe parsed as if the user entered a single space on line breaks.")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>register module --type source --name foo \\ (1)\n> --uri file:///tmp/bar\nSuccessfully registered module 'source:foo'\n")])])]),a("table",[a("thead",[a("tr",[a("th",[a("strong",[e._v("1")])]),e._v(" "),a("th",[e._v("command continues on next line")])])]),e._v(" "),a("tbody")]),e._v(" "),a("p",[e._v("Line continuation also automatically triggers if the user has opened a quote (see "),a("a",{attrs:{href:"#quotes-handling"}},[e._v("Quotes Handling")]),e._v(")\nand hits ENTER while still in the quotes:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('shell:>echo "Hello (1)\ndquote> World"\nYou said Hello World\n')])])]),a("table",[a("thead",[a("tr",[a("th",[a("strong",[e._v("1")])]),e._v(" "),a("th",[e._v("user presses ENTER here")])])]),e._v(" "),a("tbody")]),e._v(" "),a("p",[e._v("Lastly, Spring Shell apps benefit from a lot of keyboard shortcuts you may already be familiar with when\nworking with your regular OS Shell, borrowed from Emacs. Notable shortcuts include Ctrl+r to perform\na reverse search, Ctrl+a and Ctrl+e to move to beginning and end of line respectively or Esc f andEsc b to move forward ("),a("em",[e._v("resp.")]),e._v(" backward) one word at a time.")]),e._v(" "),a("h5",{attrs:{id:"providing-tab-completion-proposals"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#providing-tab-completion-proposals"}},[e._v("#")]),e._v(" Providing TAB Completion Proposals")]),e._v(" "),a("p",[e._v("TBD")]),e._v(" "),a("h3",{attrs:{id:"validating-command-arguments"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#validating-command-arguments"}},[e._v("#")]),e._v(" Validating Command Arguments")]),e._v(" "),a("p",[e._v("Spring Shell integrates with the "),a("a",{attrs:{href:"http://beanvalidation.org/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Bean Validation API"),a("OutboundLink")],1),e._v(" to support\nautomatic and self documenting constraints on command parameters.")]),e._v(" "),a("p",[e._v("Annotations found on command parameters as well as annotations at the method level will be\nhonored and trigger validation prior to the command executing. Given the following command:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v(' @ShellMethod("Change password.")\n public String changePassword(@Size(min = 8, max = 40) String password) {\n return "Password successfully set to " + password;\n }\n')])])]),a("p",[e._v("You’ll get this behavior, for free:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>change-password hello\nThe following constraints were not met:\n\t--password string : size must be between 8 and 40 (You passed 'hello')\n")])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Applies to All Command Implementations"),a("br"),a("br"),e._v("It is important to note that bean validation applies to all command implementations, whether"),a("br"),e._v('they use the "standard" API or any other API, through the use of an adapter (see '),a("a",{attrs:{href:"#support-for-shell-1-and-jcommander"}},[e._v("Supporting Other APIs")]),e._v(")")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h3",{attrs:{id:"dynamic-command-availability"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#dynamic-command-availability"}},[e._v("#")]),e._v(" Dynamic Command Availability")]),e._v(" "),a("p",[e._v("There may be times when registered commands don’t make sense, due to internal state of the application.\nFor example, maybe there is a "),a("code",[e._v("download")]),e._v(" command, but it only works once the user has used "),a("code",[e._v("connect")]),e._v(" on a remote\nserver. Now, if the user tries to use the "),a("code",[e._v("download")]),e._v(" command, the shell should gracefully explain that\nthe command "),a("em",[e._v("does")]),e._v(" exist, but that it is not available at the time.\nSpring Shell lets the developer do that, even providing a short explanation of the reason for\nthe command not being available.")]),e._v(" "),a("p",[e._v("There are three possible ways for a command to indicate availability.\nThey all leverage a no-arg method that returns an instance of "),a("code",[e._v("Availability")]),e._v(".\nLet’s start with a simple example:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@ShellComponent\npublic class MyCommands {\n\n private boolean connected;\n\n @ShellMethod("Connect to the server.")\n public void connect(String user, String password) {\n [...]\n connected = true;\n }\n\n @ShellMethod("Download the nuclear codes.")\n public void download() {\n [...]\n }\n\n public Availability downloadAvailability() {\n return connected\n ? Availability.available()\n : Availability.unavailable("you are not connected");\n }\n}\n')])])]),a("p",[e._v("Here you see the "),a("code",[e._v("connect")]),e._v(" method is used to connect to the server (details omitted), altering state\nof the command through the "),a("code",[e._v("connected")]),e._v(" boolean when done.\nThe "),a("code",[e._v("download")]),e._v(" command will be marked as "),a("em",[e._v("unavailable")]),e._v(" till the user has connected, thanks to the presence\nof a method named exactly as the "),a("code",[e._v("download")]),e._v(" command method with the "),a("code",[e._v("Availability")]),e._v(" suffix in its name.\nThe method returns an instance of "),a("code",[e._v("Availability")]),e._v(", constructed with one of the two factory methods.\nIn case of the command not being available, an explanation has to be provided.\nNow, if the user tries to invoke the command while not being connected, here is what happens:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>download\nCommand 'download' exists but is not currently available because you are not connected.\nDetails of the error have been omitted. You can use the stacktrace command to print the full stacktrace.\n")])])]),a("p",[e._v("Information about currently unavailable commands is also leveraged in the integrated help. See "),a("a",{attrs:{href:"#help-command"}},[e._v("Integrated Documentation with the "),a("code",[e._v("help")]),e._v(" Command")]),e._v(".")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v('The reason provided when the command is not available should read nicely if appended after "Because …​"'),a("br"),a("br"),e._v("It’s best not to start the sentence with a capital and not add a final dot.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("p",[e._v("If for some reason naming the availability method after the name of the command method does not suit you, you\ncan provide an explicit name using the "),a("code",[e._v("@ShellMethodAvailability")]),e._v(", like so:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v(' @ShellMethod("Download the nuclear codes.")\n @ShellMethodAvailability("availabilityCheck") (1)\n public void download() {\n [...]\n }\n\n public Availability availabilityCheck() { (1)\n return connected\n ? Availability.available()\n : Availability.unavailable("you are not connected");\n }\n')])])]),a("table",[a("thead",[a("tr",[a("th",[a("strong",[e._v("1")])]),e._v(" "),a("th",[e._v("the names have to match")])])]),e._v(" "),a("tbody")]),e._v(" "),a("p",[e._v("Lastly, it is often the case that several commands in the same class share the same internal state and thus\nshould all be available or unavailable all at one. Instead of having to stick the "),a("code",[e._v("@ShellMethodAvailability")]),e._v("on all command methods, Spring Shell allows the user to flip things around and put the "),a("code",[e._v("@ShellMethodAvailabilty")]),e._v("annotation on the availability method, specifying the names of the commands that it controls:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v(' @ShellMethod("Download the nuclear codes.")\n public void download() {\n [...]\n }\n\n @ShellMethod("Disconnect from the server.")\n public void disconnect() {\n [...]\n }\n\n @ShellMethodAvailability({"download", "disconnect"})\n public Availability availabilityCheck() {\n return connected\n ? Availability.available()\n : Availability.unavailable("you are not connected");\n }\n')])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("The default value for the "),a("code",[e._v("@ShellMethodAvailability.value()")]),e._v(" attribute is "),a("code",[e._v('"*"')]),e._v(" and this serves as a special"),a("br"),e._v("wildcard that matches all command names. It’s thus easy to turn all commands of a single class on or off"),a("br"),e._v("with a single availability method. Here is an example below:"),a("br"),a("br"),a("code",[e._v('
@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() {}
}
')])])])]),e._v(" "),a("tbody")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Spring Shell does not impose much constraints on how to write commands and how to organize classes."),a("br"),e._v("But it’s often good practice to put related commands in the same class, and the availability indicators"),a("br"),e._v("can benefit from that.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h3",{attrs:{id:"organizing-commands"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#organizing-commands"}},[e._v("#")]),e._v(" Organizing Commands")]),e._v(" "),a("p",[e._v("When your shell starts to provide a lot of functionality, you may en up\nwith a lot of commands, which could be confusing for your users. Typing "),a("code",[e._v("help")]),e._v("they would see a daunting list of commands, organized by alphabetical order,\nwhich may not always make sense.")]),e._v(" "),a("p",[e._v("To alleviate this, Spring Shell provides the ability to group commands together,\nwith reasonable defaults. Related commands would then end up in the same "),a("em",[e._v("group")]),e._v(" ("),a("em",[e._v("e.g.")]),e._v(" "),a("code",[e._v("User Management Commands")]),e._v(")\nand be displayed together in the help screen and other places.")]),e._v(" "),a("p",[e._v("By default, commands will be grouped according to the class they are implemented in,\nturning the camel case class name into separate words (so "),a("code",[e._v("URLRelatedCommands")]),e._v(" becomes "),a("code",[e._v("URL Related Commands")]),e._v(").\nThis is a very sensible default, as related commands are often already in the class anyway,\nfor they need to use the same collaborating objects.")]),e._v(" "),a("p",[e._v("If however, this behavior does not suit you, you can override the group for a\ncommand in the following ways, in order of priority:")]),e._v(" "),a("ul",[a("li",[a("p",[e._v("specifying a "),a("code",[e._v("group()")]),e._v(" in the "),a("code",[e._v("@ShellMethod")]),e._v(" annotation")])]),e._v(" "),a("li",[a("p",[e._v("placing a "),a("code",[e._v("@ShellCommandGroup")]),e._v(" on the class the command is defined in. This will apply\nthe group for all commands defined in that class (unless overridden as above)")])]),e._v(" "),a("li",[a("p",[e._v("placing a "),a("code",[e._v("@ShellCommandGroup")]),e._v(" on the package ("),a("em",[e._v("via")]),e._v(" "),a("code",[e._v("package-info.java")]),e._v(")\nthe command is defined in. This will apply to all commands defined in the\npackage (unless overridden at the method or class level as explained above)")])])]),e._v(" "),a("p",[e._v("Here is a short example:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('public class UserCommands {\n @ShellMethod(value = "This command ends up in the \'User Commands\' group")\n public void foo() {}\n\n @ShellMethod(value = "This command ends up in the \'Other Commands\' group",\n group = "Other Commands")\n public void bar() {}\n}\n\n...\n\n@ShellCommandGroup("Other Commands")\npublic class SomeCommands {\n @ShellMethod(value = "This one is in \'Other Commands\'")\n public void wizz() {}\n\n @ShellMethod(value = "And this one is \'Yet Another Group\'",\n group = "Yet Another Group")\n public void last() {}\n}\n')])])]),a("h3",{attrs:{id:"built-in-commands"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#built-in-commands"}},[e._v("#")]),e._v(" Built-In Commands")]),e._v(" "),a("p",[e._v("Any application built using the "),a("code",[e._v("spring-shell-starter")]),e._v(" artifact\n(or, to be more precise, the "),a("code",[e._v("spring-shell-standard-commands")]),e._v(" dependency) comes with a set of built-in commands.\nThese commands can be overridden or disabled individually (see "),a("a",{attrs:{href:"#overriding-or-disabling-built-in-commands"}},[e._v("Overriding or Disabling Built-In Commands")]),e._v("), but if they’re\nnot, this section describes their behavior.")]),e._v(" "),a("h4",{attrs:{id:"integrated-documentation-with-the-help-command"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#integrated-documentation-with-the-help-command"}},[e._v("#")]),e._v(" Integrated Documentation with the "),a("code",[e._v("help")]),e._v(" Command")]),e._v(" "),a("p",[e._v("Running a shell application often implies that the user is in a graphically limited environment. And although, in the era of mobile\nphones we’re always connected, accessing a web browser or any other rich UI application such as a pdf viewer may not always\nbe possible. This is why it is important that the shell commands are correctly self documented, and this is where the "),a("code",[e._v("help")]),e._v("command comes in.")]),e._v(" "),a("p",[e._v("Typing "),a("code",[e._v("help")]),e._v(" + ENTER will list all the known commands to the shell (including "),a("a",{attrs:{href:"#dynamic-command-availability"}},[e._v("unavailable")]),e._v(" commands)\nand a short description of what they do:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>help\nAVAILABLE COMMANDS\n add: Add numbers together.\n * authenticate: Authenticate with the system.\n * blow-up: Blow Everything up.\n clear: Clear the shell screen.\n connect: Connect to the system\n disconnect: Disconnect from the system.\n exit, quit: Exit the shell.\n help: Display help about available commands.\n register module: Register a new module.\n script: Read and execute commands from a file.\n stacktrace: Display the full stacktrace of the last error.\n\nCommands marked with (*) are currently unavailable.\nType `help ` to learn more.\n")])])]),a("p",[e._v("Typing "),a("code",[e._v("help ")]),e._v(" will display more detailed information about a command, including the available parameters, their\ntype and whether they are mandatory or not, "),a("em",[e._v("etc.")])]),e._v(" "),a("p",[e._v("Here is the "),a("code",[e._v("help")]),e._v(" command applied to itself:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>help help\n\nNAME\n\thelp - Display help about available commands.\n\nSYNOPSYS\n\thelp [[-C] string]\n\nOPTIONS\n\t-C or --command string\n\t\tThe command to obtain help for. [Optional, default = ]\n")])])]),a("h4",{attrs:{id:"clearing-the-screen"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#clearing-the-screen"}},[e._v("#")]),e._v(" Clearing the Screen")]),e._v(" "),a("p",[e._v("The "),a("code",[e._v("clear")]),e._v(" command does what you would expect and clears the screen, resetting the prompt\nin the top left corner.")]),e._v(" "),a("h4",{attrs:{id:"exitting-the-shell"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#exitting-the-shell"}},[e._v("#")]),e._v(" Exitting the Shell")]),e._v(" "),a("p",[e._v("The "),a("code",[e._v("quit")]),e._v(" command (also aliased as "),a("code",[e._v("exit")]),e._v(") simply requests the shell to quit, gracefully\nclosing the Spring application context. If not overridden, a JLine "),a("code",[e._v("History")]),e._v(" bean will write a history of all\ncommands executed to disk, so that they are available again (see "),a("a",{attrs:{href:"#interacting-with-the-shell"}},[e._v("Interacting with the Shell")]),e._v(") on next launch.")]),e._v(" "),a("h4",{attrs:{id:"displaying-details-about-an-error"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#displaying-details-about-an-error"}},[e._v("#")]),e._v(" Displaying Details about an Error")]),e._v(" "),a("p",[e._v("When an exception occurs inside command code, it is caught by the shell and a simple, one-line message is displayed\nso as not to overflow the user with too much information.\nThere are cases though when understanding what exactly happened is important (especially if the exception has a nested cause).")]),e._v(" "),a("p",[e._v("To this purpose, Spring Shell remembers the last exception that occurred and the user can later use the "),a("code",[e._v("stacktrace")]),e._v("command to print all the gory details on the console.")]),e._v(" "),a("h4",{attrs:{id:"running-a-batch-of-commands"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#running-a-batch-of-commands"}},[e._v("#")]),e._v(" Running a Batch of Commands")]),e._v(" "),a("p",[e._v("The "),a("code",[e._v("script")]),e._v(" command accepts a local file as an argument and will replay commands found there, one at a time.")]),e._v(" "),a("p",[e._v("Reading from the file behaves exactly like inside the interactive shell, so lines starting with "),a("code",[e._v("//")]),e._v(" will be considered\nas comments and ignored, while lines ending with "),a("code",[e._v("\\")]),e._v(" will trigger line continuation.")]),e._v(" "),a("h3",{attrs:{id:"customizing-the-shell"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#customizing-the-shell"}},[e._v("#")]),e._v(" Customizing the Shell")]),e._v(" "),a("h4",{attrs:{id:"overriding-or-disabling-built-in-commands"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#overriding-or-disabling-built-in-commands"}},[e._v("#")]),e._v(" Overriding or Disabling Built-In Commands")]),e._v(" "),a("p",[a("a",{attrs:{href:"#built-in-commands"}},[e._v("Built-in commands")]),e._v(" are provided with Spring Shell to achieve everyday tasks that many if not\nall shell applications need. If you’re not happy with the way they behave though, you can disable or override them, as explained in this section.")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Disabling all Built-in Commands"),a("br"),a("br"),e._v('If you don’t need built-in commands at all, then there is an easy way to "disable" them: just don’t include them!'),a("br"),e._v("Either use a maven exclusion on "),a("code",[e._v("spring-shell-standard-commands")]),e._v(" or, if you’re selectively including Spring Shell dependencies,"),a("br"),e._v("don’t bring that one in!"),a("br"),a("br"),a("code",[e._v("

org.springframework.shell
spring-shell-starter
2.0.1.RELEASE


org.springframework.shell
spring-shell-standard-commands



")])])])]),e._v(" "),a("tbody")]),e._v(" "),a("h5",{attrs:{id:"disabling-specific-commands"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#disabling-specific-commands"}},[e._v("#")]),e._v(" Disabling Specific Commands")]),e._v(" "),a("p",[e._v("To disable a single built-in command, simply set the "),a("code",[e._v("spring.shell.command..enabled")]),e._v(" property to "),a("code",[e._v("false")]),e._v(" in the app"),a("code",[e._v("Environment")]),e._v(". One easy way to do this is to pass extra args to the Boot application in your "),a("code",[e._v("main()")]),e._v(" entry point:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v(' public static void main(String[] args) throws Exception {\n String[] disabledCommands = {"--spring.shell.command.help.enabled=false"}; (1)\n String[] fullArgs = StringUtils.concatenateStringArrays(args, disabledCommands);\n SpringApplication.run(MyApp.class, fullArgs);\n }\n')])])]),a("table",[a("thead",[a("tr",[a("th",[a("strong",[e._v("1")])]),e._v(" "),a("th",[e._v("This disables the integrated "),a("code",[e._v("help")]),e._v(" command")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h5",{attrs:{id:"overriding-specific-commands"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#overriding-specific-commands"}},[e._v("#")]),e._v(" Overriding Specific Commands")]),e._v(" "),a("p",[e._v("If, instead of disabling a command you’d rather provide your own implementation, then you can either")]),e._v(" "),a("ul",[a("li",[a("p",[e._v("disable the command like explained above and have your implementation registered with the same name")])]),e._v(" "),a("li",[a("p",[e._v("have your implementing class implement the "),a("code",[e._v(".Command")]),e._v(" interface. As an example, here is how\nto override the "),a("code",[e._v("clear")]),e._v(" command:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('public class MyClear implements Clear.Command {\n\n @ShellMethod("Clear the screen, only better.")\n public void clear() {\n // ...\n }\n}\n')])])])])]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Please Consider Contributing your Changes"),a("br"),a("br"),e._v("If you feel like your implementation of a standard command could be valuable to the community,"),a("br"),e._v("please consider opening a pull-request at "),a("a",{attrs:{href:"https://github.com/spring-projects/spring-shell",target:"_blank",rel:"noopener noreferrer"}},[e._v("github.com/spring-projects/spring-shell"),a("OutboundLink")],1),e._v("."),a("br"),a("br"),e._v("Alternatively, before making any changes on your own, you can open an issue with the project. Feedback is"),a("br"),e._v("always welcome!")])])]),e._v(" "),a("tbody")]),e._v(" "),a("h4",{attrs:{id:"resulthandlers"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#resulthandlers"}},[e._v("#")]),e._v(" ResultHandlers")]),e._v(" "),a("h4",{attrs:{id:"promptprovider"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#promptprovider"}},[e._v("#")]),e._v(" PromptProvider")]),e._v(" "),a("p",[e._v("After each command invocation, the shell waits for new input from the user, displaying\na "),a("em",[e._v("prompt")]),e._v(" in yellow:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v("shell:>\n")])])]),a("p",[e._v("It is possible to customize this behavior by registering a bean of type "),a("code",[e._v("PromptProvider")]),e._v(".\nSuch a bean may use internal state to decide what to display to the user (it may for example\nreact to "),a("a",{attrs:{href:"https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-functionality-events-annotation",target:"_blank",rel:"noopener noreferrer"}},[e._v("application events"),a("OutboundLink")],1),e._v(")\nand can use JLine’s "),a("code",[e._v("AttributedCharSequence")]),e._v(" to display fancy ANSI text.")]),e._v(" "),a("p",[e._v("Here is a fictional example:")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@Component\npublic class CustomPromptProvider implements PromptProvider {\n\n private ConnectionDetails connection;\n\n @Override\n public AttributedString getPrompt() {\n if (connection != null) {\n return new AttributedString(connection.getHost() + ":>",\n AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW));\n }\n else {\n return new AttributedString("server-unknown:>",\n AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));\n }\n }\n\n @EventListener\n public void handle(ConnectionUpdatedEvent event) {\n this.connection = event.getConnectionDetails();\n }\n}\n')])])]),a("h4",{attrs:{id:"customizing-command-line-options-behavior"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#customizing-command-line-options-behavior"}},[e._v("#")]),e._v(" Customizing Command Line Options Behavior")]),e._v(" "),a("p",[e._v("Spring Shell comes with two default Spring Boot "),a("code",[e._v("ApplicationRunners")]),e._v(":")]),e._v(" "),a("ul",[a("li",[a("p",[a("code",[e._v("InteractiveShellApplicationRunner")]),e._v(" bootstraps the Shell REPL. It sets up the JLine infrastructure and eventually\ncalls "),a("code",[e._v("Shell.run()")])])]),e._v(" "),a("li",[a("p",[a("code",[e._v("ScriptShellApplicationRunner")]),e._v(" looks for program arguments that start with "),a("code",[e._v("@")]),e._v(", assumes those are local file names and\ntries to run commands contained in those files (with the same semantics as the "),a("a",{attrs:{href:"#script-command"}},[e._v("script command")]),e._v(") and\nthen exits the process (by effectively disabling the "),a("code",[e._v("InteractiveShellApplicationRunner")]),e._v(", see below).")])])]),e._v(" "),a("p",[e._v("If this behavior does not suit you, simply provide one (or more) bean of type "),a("code",[e._v("ApplicationRunner")]),e._v("and optionally disable the standard ones. You’ll want to take inspiration from the "),a("code",[e._v("ScriptShellApplicationRunner")]),e._v(":")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@Order(InteractiveShellApplicationRunner.PRECEDENCE - 100) // Runs before InteractiveShellApplicationRunner\npublic class ScriptShellApplicationRunner implements ApplicationRunner {\n\n @Override\n public void run(ApplicationArguments args) throws Exception {\n List scriptsToRun = args.getNonOptionArgs().stream()\n .filter(s -> s.startsWith("@"))\n .map(s -> new File(s.substring(1)))\n .collect(Collectors.toList());\n\n boolean batchEnabled = environment.getProperty(SPRING_SHELL_SCRIPT_ENABLED, boolean.class, true);\n\n if (!scriptsToRun.isEmpty() && batchEnabled) {\n InteractiveShellApplicationRunner.disable(environment);\n for (File file : scriptsToRun) {\n try (Reader reader = new FileReader(file);\n FileInputProvider inputProvider = new FileInputProvider(reader, parser)) {\n shell.run(inputProvider);\n }\n }\n }\n }\n\n...\n')])])]),a("h4",{attrs:{id:"customizing-arguments-conversion"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#customizing-arguments-conversion"}},[e._v("#")]),e._v(" Customizing Arguments Conversion")]),e._v(" "),a("p",[e._v("Conversion from text input to actual method arguments uses the standard Spring"),a("a",{attrs:{href:"https://docs.spring.io/spring/docs/4.3.11.RELEASE/spring-framework-reference/htmlsingle/#core-convert",target:"_blank",rel:"noopener noreferrer"}},[e._v("conversion"),a("OutboundLink")],1),e._v(" mechanism.\nSpring Shell installs a new "),a("code",[e._v("DefaultConversionService")]),e._v(" (with built-in converters enabled)\nand registers to it any bean of type "),a("code",[e._v("Converter")]),e._v(", "),a("code",[e._v("GenericConverter")]),e._v(" or"),a("code",[e._v("ConverterFactory")]),e._v(" that it finds in the application context.")]),e._v(" "),a("p",[e._v("This means that it’s really easy to customize conversion to your custom objects of type "),a("code",[e._v("Foo")]),e._v(":\njust install a "),a("code",[e._v("Converter")]),e._v(" bean in the context.")]),e._v(" "),a("div",{staticClass:"language- extra-class"},[a("pre",{pre:!0,attrs:{class:"language-text"}},[a("code",[e._v('@ShellComponent\nclass ConversionCommands {\n\n @ShellMethod("Shows conversion using Spring converter")\n public String conversionExample(DomainObject object) {\n return object.getClass();\n }\n\n}\n\nclass DomainObject {\n private final String value;\n\n DomainObject(String value) {\n this.value = value;\n }\n\n public String toString() {\n return value;\n }\n}\n\n@Component\nclass CustomDomainConverter implements Converter {\n\n @Override\n public DomainObject convert(String source) {\n return new DomainObject(source);\n }\n}\n')])])]),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("Mind your String representation"),a("br"),a("br"),e._v("As in the example above, it’s probably a good idea if you can to have"),a("br"),e._v("your "),a("code",[e._v("toString()")]),e._v(" implementations return the converse of what was used"),a("br"),e._v("to create the object instance. This is because when a value fails"),a("br"),e._v("validation, Spring Shell prints"),a("br"),a("br"),a("code",[e._v("
The following constraints were not met:
\t--arg : (You passed '')
")]),a("br"),a("br"),e._v("See "),a("a",{attrs:{href:"#validating-command-arguments"}},[e._v("Validating Command Arguments")]),e._v(" for more information.")])])]),e._v(" "),a("tbody")]),e._v(" "),a("table",[a("thead",[a("tr",[a("th"),e._v(" "),a("th",[e._v("If you want to customize the "),a("code",[e._v("ConversionService")]),e._v(" further, you can either"),a("br"),a("br"),e._v("* Have the default one injected in your code and act upon it in some way"),a("br"),a("br"),e._v("* Override it altogether with your own (custom converters will need to be registered by hand)."),a("br"),e._v(" The ConversionService used by Spring Shell needs to be "),a("a",{attrs:{href:"https://docs.spring.io/spring/docs/4.3.12.RELEASE/spring-framework-reference/htmlsingle/#beans-autowired-annotation-qualifiers",target:"_blank",rel:"noopener noreferrer"}},[e._v("qualified"),a("OutboundLink")],1),e._v(" as "),a("code",[e._v('"spring-shell"')]),e._v(".")])])]),e._v(" "),a("tbody")])])}),[],!1,null,null,null);t.default=o.exports}}]);