Document PromptProvider, ApplicationRunner and ConversionService.
Make the default ConversionService register converters in the ctx
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user