Make command not found configurable
- New CommandNotFoundResultHandler which handles CommandNotFound to be able to customize error shown. - New CommandNotFoundMessageProvider which is a plain function given a "context" and returns a string. Context contains common info to provide better error messages. - Default provider gives same message but removes long stacktrace(which previously originated from a common ThrowableResultHandler. - Relates #778
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Copyright 2017-2023
|
||||
* 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
|
||||
@@ -18,17 +17,28 @@ package org.springframework.shell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.shell.command.CommandRegistration;
|
||||
|
||||
/**
|
||||
* A result to be handled by the {@link ResultHandler} when no command could be mapped to user input
|
||||
*/
|
||||
public class CommandNotFound extends RuntimeException {
|
||||
|
||||
private final List<String> words;
|
||||
private final Map<String, CommandRegistration> registrations;
|
||||
private final String text;
|
||||
|
||||
public CommandNotFound(List<String> words) {
|
||||
this(words, null, null);
|
||||
}
|
||||
|
||||
public CommandNotFound(List<String> words, Map<String, CommandRegistration> registrations, String text) {
|
||||
this.words = words;
|
||||
this.registrations = registrations;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,4 +54,22 @@ public class CommandNotFound extends RuntimeException {
|
||||
public List<String> getWords(){
|
||||
return new ArrayList<>(words);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets command registrations known when this error was created.
|
||||
*
|
||||
* @return known command registrations
|
||||
*/
|
||||
public Map<String, CommandRegistration> getRegistrations() {
|
||||
return registrations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a raw text input.
|
||||
*
|
||||
* @return raw text input
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.shell;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.nio.channels.ClosedByInterruptException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -201,13 +202,14 @@ public class Shell {
|
||||
String line = words.stream().collect(Collectors.joining(" ")).trim();
|
||||
String command = findLongestCommand(line, false);
|
||||
|
||||
Map<String, CommandRegistration> registrations = commandRegistry.getRegistrations();
|
||||
if (command == null) {
|
||||
return new CommandNotFound(words);
|
||||
return new CommandNotFound(words, new HashMap<>(registrations), input.rawText());
|
||||
}
|
||||
|
||||
log.debug("Evaluate input with line=[{}], command=[{}]", line, command);
|
||||
|
||||
Optional<CommandRegistration> commandRegistration = commandRegistry.getRegistrations().values().stream()
|
||||
Optional<CommandRegistration> commandRegistration = registrations.values().stream()
|
||||
.filter(r -> {
|
||||
if (r.getCommand().equals(command)) {
|
||||
return true;
|
||||
@@ -222,7 +224,7 @@ public class Shell {
|
||||
.findFirst();
|
||||
|
||||
if (commandRegistration.isEmpty()) {
|
||||
return new CommandNotFound(words);
|
||||
return new CommandNotFound(words, new HashMap<>(registrations), input.rawText());
|
||||
}
|
||||
|
||||
if (this.exitCodeMappings != null) {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2023 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
|
||||
*
|
||||
* https://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.result;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.shell.command.CommandRegistration;
|
||||
import org.springframework.shell.result.CommandNotFoundMessageProvider.ProviderContext;
|
||||
|
||||
/**
|
||||
* Provider for a message used within {@link CommandNotFoundResultHandler}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CommandNotFoundMessageProvider extends Function<ProviderContext, String> {
|
||||
|
||||
static ProviderContext contextOf(Throwable error, List<String> commands, Map<String, CommandRegistration> registrations, String text) {
|
||||
return new ProviderContext() {
|
||||
|
||||
@Override
|
||||
public Throwable error() {
|
||||
return error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> commands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, CommandRegistration> registrations() {
|
||||
return registrations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String text() {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Context for {@link CommandNotFoundResultHandler}.
|
||||
*/
|
||||
interface ProviderContext {
|
||||
|
||||
/**
|
||||
* Gets an actual error.
|
||||
*
|
||||
* @return actual error
|
||||
*/
|
||||
Throwable error();
|
||||
|
||||
/**
|
||||
* Gets a list of commands parsed.
|
||||
*
|
||||
* @return list of commands parsed
|
||||
*/
|
||||
List<String> commands();
|
||||
|
||||
/**
|
||||
* Gets a command registrations.
|
||||
*
|
||||
* @return a command registrations
|
||||
*/
|
||||
Map<String, CommandRegistration> registrations();
|
||||
|
||||
/**
|
||||
* Gets a raw input text.
|
||||
*
|
||||
* @return a raw input text
|
||||
*/
|
||||
String text();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.springframework.shell.result;
|
||||
|
||||
import org.jline.terminal.Terminal;
|
||||
import org.jline.utils.AttributedString;
|
||||
import org.jline.utils.AttributedStyle;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.shell.CommandNotFound;
|
||||
import org.springframework.shell.ResultHandler;
|
||||
import org.springframework.shell.result.CommandNotFoundMessageProvider.ProviderContext;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link ResultHandler} for {@link CommandNotFound} using
|
||||
* {@link CommandNotFoundMessageProvider} to provide an error message.
|
||||
* Default internal provider simply provides message from a {@link CommandNotFound}
|
||||
* with a red color. Provider can be defined by providing a custom
|
||||
* {@link CommandNotFoundMessageProvider} bean.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public final class CommandNotFoundResultHandler extends TerminalAwareResultHandler<CommandNotFound> {
|
||||
|
||||
private CommandNotFoundMessageProvider provider;
|
||||
|
||||
public CommandNotFoundResultHandler(Terminal terminal, ObjectProvider<CommandNotFoundMessageProvider> provider) {
|
||||
super(terminal);
|
||||
Assert.notNull(provider, "provider cannot be null");
|
||||
this.provider = provider.getIfAvailable(() -> new DefaultProvider());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHandleResult(CommandNotFound result) {
|
||||
ProviderContext context = CommandNotFoundMessageProvider.contextOf(result, result.getWords(),
|
||||
result.getRegistrations(), result.getText());
|
||||
String message = provider.apply(context);
|
||||
if (StringUtils.hasText(message)) {
|
||||
terminal.writer().println(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DefaultProvider implements CommandNotFoundMessageProvider {
|
||||
|
||||
@Override
|
||||
public String apply(ProviderContext context) {
|
||||
String message = new AttributedString(context.error().getMessage(),
|
||||
AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)).toAnsi();
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,4 +70,10 @@ public class ResultHandlerConfig {
|
||||
ShellContext shellContext, ObjectProvider<InteractiveShellRunner> interactiveApplicationRunner) {
|
||||
return new ThrowableResultHandler(terminal, commandCatalog, shellContext, interactiveApplicationRunner);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CommandNotFoundResultHandler commandNotFoundResultHandler(Terminal terminal,
|
||||
ObjectProvider<CommandNotFoundMessageProvider> provider) {
|
||||
return new CommandNotFoundResultHandler(terminal, provider);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user