Document PromptProvider, ApplicationRunner and ConversionService.

Make the default ConversionService register converters in the ctx
This commit is contained in:
Eric Bottard
2017-09-19 13:57:28 +02:00
parent b9df6c48a9
commit a50b741af1
4 changed files with 200 additions and 5 deletions

View File

@@ -16,14 +16,20 @@
package org.springframework.shell;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.shell.result.ResultHandlerConfig;
@@ -39,8 +45,22 @@ public class SpringShellAutoConfiguration {
@Bean
@ConditionalOnMissingBean(ConversionService.class)
public ConversionService conversionService() {
return new DefaultConversionService();
public ConversionService conversionService(ApplicationContext applicationContext) {
Collection<Converter> converters = applicationContext.getBeansOfType(Converter.class).values();
Collection<GenericConverter> genericConverters = applicationContext.getBeansOfType(GenericConverter.class).values();
Collection<ConverterFactory> converterFactories = applicationContext.getBeansOfType(ConverterFactory.class).values();
DefaultConversionService defaultConversionService = new DefaultConversionService();
for (Converter converter : converters) {
defaultConversionService.addConverter(converter);
}
for (GenericConverter genericConverter : genericConverters) {
defaultConversionService.addConverter(genericConverter);
}
for (ConverterFactory converterFactory : converterFactories) {
defaultConversionService.addConverterFactory(converterFactory);
}
return defaultConversionService;
}
@Bean

View File

@@ -46,9 +46,10 @@ import org.springframework.shell.Shell;
*
* @author Eric Bottard
*/
//tag::documentation[]
@Order(DefaultShellApplicationRunner.PRECEDENCE)
public class DefaultShellApplicationRunner implements ApplicationRunner {
//end::documentation[]
public static final int PRECEDENCE = 0;
private final LineReader lineReader;
@@ -66,6 +67,7 @@ public class DefaultShellApplicationRunner implements ApplicationRunner {
this.shell = shell;
}
//tag::documentation[]
@Override
public void run(ApplicationArguments args) throws Exception {
List<File> scriptsToRun = args.getNonOptionArgs().stream()
@@ -84,7 +86,7 @@ public class DefaultShellApplicationRunner implements ApplicationRunner {
}
}
}
//end::documentation[]
public static class JLineInputProvider implements InputProvider {
private final LineReader lineReader;

View File

@@ -444,6 +444,7 @@ kbd:[Esc b] to move forward (_resp._ backward) one word at a time.
TBD
[[validating-command-arguments]]
=== Validating Command Arguments
Spring Shell integrates with the http://beanvalidation.org/[Bean Validation API] to support
@@ -669,6 +670,7 @@ There are cases though when understanding what exactly happened is important (es
To this purpose, Spring Shell remembers the last exception that occurred and the user can later use the `stacktrace`
command to print all the gory details on the console.
[[script-command]]
==== Running a Batch of Commands
The `script` command accepts a local file as an argument and will replay commands found there, one at a time.
@@ -750,10 +752,132 @@ always welcome!
==== ResultHandlers
==== 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.
Here is a fictional example:
[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
Spring Shell comes with a default Spring Boot `ApplicationRunner`
that bootstraps the Shell REPL. It sets up the JLine infrastructure and eventually
calls `Shell.run()`.
==== ConversionService
If the application is started with arguments that start with `@` though, it assumes those
are local file names and tries to run commands contained in those files (with the same
semantics as the xref:script-command[script command]) and then exits the process.
If this behavior does not suit you, simply provide one (or more) bean of type `ApplicationRunner`
and it will replace the default. You'll want to take inspiration from the `DefaultShellApplicationRunner`:
[source,java]
----
include::../../../../spring-shell-core/src/main/java/org/springframework/shell/jline/DefaultShellApplicationRunner.java[tag=documentation]
...
----
==== Customizing Arguments Conversion
Conversion from text input to actual method arguments uses the standard Spring
https://docs.spring.io/spring/docs/4.3.11.RELEASE/spring-framework-reference/htmlsingle/#core-convert[conversion] mechanism.
Spring Shell installs a new `DefaultConversionService` (with built-in converters enabled)
and registers to it any bean of type `Converter<S, T>`, `GenericConverter` or
`ConverterFactory<S, T>` that it finds in the application context.
This means that it's really easy to customize conversion to your custom objects of type `Foo`:
just install a `Converter<String, Foo>` bean in the context.
[source, java]
----
@ShellComponent
class ConversionCommands {
@ShellMethod("Shows conversion using Spring converter")
public String conversionExample(DomainObject object) {
return object.getClass();
}
}
class DomainObject {
private final String value;
DomainObject(String value) {
this.value = value;
}
public String toString() {
return value;
}
}
@Component
class CustomDomainConverter implements Converter<String, DomainObject> {
@Override
public DomainObject convert(String source) {
return new DomainObject(source);
}
}
----
[TIP]
.Mind your String representation
====
As in the example above, it's probably a good idea if you can to have
your `toString()` implementations return the converse of what was used
to create the object instance. This is because when a value fails
validation, Spring Shell prints
[source]
----
The following constraints were not met:
--arg <type> : <message> (You passed '<value.toString()>')
----
See xref:validating-command-arguments[] for more information.
====
[NOTE]
====
If you want to customize the `ConversionService` further, you can either
* Have the default one injected in your code and act upon it in some way
* Override it altogether with your own (custom converters will need to be registered by hand)
====
//==== Overriding the JLine Parser

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.samples.standard;
import org.springframework.core.convert.converter.Converter;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.stereotype.Component;
@ShellComponent
class ConversionCommands {
@ShellMethod("Shows conversion using Spring converter")
public Object conversionExample(DomainObject object) {
return object;
}
}
class DomainObject {
private final String value;
DomainObject(String value) {
this.value = value;
}
}
@Component
class CustomDomainConverter implements Converter<String, DomainObject> {
@Override
public DomainObject convert(String source) {
return new DomainObject(source);
}
}