Fix availability feature
- Bring back some missing functionality which got missing during the rework to new command model. - Polish some classes. - Restore origin sample. - Add availability things into help templates and its representation model. - Fixes #423
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 the original author or authors.
|
* Copyright 2017-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.shell;
|
package org.springframework.shell;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,20 +22,21 @@ package org.springframework.shell;
|
|||||||
*/
|
*/
|
||||||
public class CommandNotCurrentlyAvailable extends RuntimeException {
|
public class CommandNotCurrentlyAvailable extends RuntimeException {
|
||||||
|
|
||||||
private final String command;
|
private final String command;
|
||||||
private final Availability availability;
|
private final Availability availability;
|
||||||
|
|
||||||
public CommandNotCurrentlyAvailable(String command, Availability availability) {
|
public CommandNotCurrentlyAvailable(String command, Availability availability) {
|
||||||
super(String.format("Command '%s' exists but is not currently available because %s", command, availability.getReason()));
|
super(String.format("Command '%s' exists but is not currently available because %s", command,
|
||||||
this.command = command;
|
availability.getReason()));
|
||||||
this.availability = availability;
|
this.command = command;
|
||||||
}
|
this.availability = availability;
|
||||||
|
}
|
||||||
|
|
||||||
public String getCommand() {
|
public String getCommand() {
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Availability getAvailability() {
|
public Availability getAvailability() {
|
||||||
return availability;
|
return availability;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import org.springframework.core.convert.ConversionService;
|
|||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
|
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
|
||||||
import org.springframework.messaging.support.MessageBuilder;
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
import org.springframework.shell.Availability;
|
||||||
|
import org.springframework.shell.CommandNotCurrentlyAvailable;
|
||||||
import org.springframework.shell.command.CommandParser.CommandParserException;
|
import org.springframework.shell.command.CommandParser.CommandParserException;
|
||||||
import org.springframework.shell.command.CommandParser.CommandParserResults;
|
import org.springframework.shell.command.CommandParser.CommandParserResults;
|
||||||
import org.springframework.shell.command.CommandRegistration.TargetInfo;
|
import org.springframework.shell.command.CommandRegistration.TargetInfo;
|
||||||
@@ -95,6 +97,11 @@ public interface CommandExecution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Object evaluate(CommandRegistration registration, String[] args) {
|
public Object evaluate(CommandRegistration registration, String[] args) {
|
||||||
|
// fast fail with availability before doing anything else
|
||||||
|
Availability availability = registration.getAvailability();
|
||||||
|
if (availability != null && !availability.isAvailable()) {
|
||||||
|
return new CommandNotCurrentlyAvailable(registration.getCommand(), availability);
|
||||||
|
}
|
||||||
|
|
||||||
List<CommandOption> options = registration.getOptions();
|
List<CommandOption> options = registration.getOptions();
|
||||||
CommandParser parser = CommandParser.of(conversionService);
|
CommandParser parser = CommandParser.of(conversionService);
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import org.junit.jupiter.params.provider.ValueSource;
|
|||||||
import org.springframework.core.convert.ConversionService;
|
import org.springframework.core.convert.ConversionService;
|
||||||
import org.springframework.core.convert.support.DefaultConversionService;
|
import org.springframework.core.convert.support.DefaultConversionService;
|
||||||
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
|
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
|
||||||
|
import org.springframework.shell.Availability;
|
||||||
|
import org.springframework.shell.CommandNotCurrentlyAvailable;
|
||||||
import org.springframework.shell.command.CommandExecution.CommandParserExceptionsException;
|
import org.springframework.shell.command.CommandExecution.CommandParserExceptionsException;
|
||||||
import org.springframework.shell.command.CommandRegistration.OptionArity;
|
import org.springframework.shell.command.CommandRegistration.OptionArity;
|
||||||
|
|
||||||
@@ -459,4 +461,22 @@ public class CommandExecutionTests extends AbstractCommandTests {
|
|||||||
execution.evaluate(r1, new String[]{});
|
execution.evaluate(r1, new String[]{});
|
||||||
}).isInstanceOf(CommandParserExceptionsException.class);
|
}).isInstanceOf(CommandParserExceptionsException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCommandNotAvailable() {
|
||||||
|
CommandRegistration r1 = CommandRegistration.builder()
|
||||||
|
.command("command1")
|
||||||
|
.description("help")
|
||||||
|
.withOption()
|
||||||
|
.longNames("arg1")
|
||||||
|
.description("some arg1")
|
||||||
|
.and()
|
||||||
|
.availability(() -> Availability.unavailable("fake reason"))
|
||||||
|
.withTarget()
|
||||||
|
.function(function1)
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
Object result = execution.evaluate(r1, new String[]{"--arg1", "myarg1value"});
|
||||||
|
assertThat(result).isInstanceOf(CommandNotCurrentlyAvailable.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package org.springframework.shell.command;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
|
import org.springframework.shell.Availability;
|
||||||
import org.springframework.shell.command.CommandRegistration.OptionArity;
|
import org.springframework.shell.command.CommandRegistration.OptionArity;
|
||||||
import org.springframework.shell.command.CommandRegistration.TargetInfo.TargetType;
|
import org.springframework.shell.command.CommandRegistration.TargetInfo.TargetType;
|
||||||
import org.springframework.shell.context.InteractionMode;
|
import org.springframework.shell.context.InteractionMode;
|
||||||
@@ -410,4 +411,27 @@ public class CommandRegistrationTests extends AbstractCommandTests {
|
|||||||
assertThat(registration.getExitCode()).isNotNull();
|
assertThat(registration.getExitCode()).isNotNull();
|
||||||
assertThat(registration.getExitCode().getMappingFunctions()).hasSize(4);
|
assertThat(registration.getExitCode().getMappingFunctions()).hasSize(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAvailability() {
|
||||||
|
CommandRegistration registration;
|
||||||
|
registration = CommandRegistration.builder()
|
||||||
|
.command("command1")
|
||||||
|
.withTarget()
|
||||||
|
.function(function1)
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
assertThat(registration.getAvailability()).isNotNull();
|
||||||
|
assertThat(registration.getAvailability().isAvailable()).isTrue();
|
||||||
|
|
||||||
|
registration = CommandRegistration.builder()
|
||||||
|
.command("command1")
|
||||||
|
.availability(() -> Availability.unavailable("fake"))
|
||||||
|
.withTarget()
|
||||||
|
.function(function1)
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
assertThat(registration.getAvailability()).isNotNull();
|
||||||
|
assertThat(registration.getAvailability().isAvailable()).isFalse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017-2021 the original author or authors.
|
* Copyright 2017-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -29,29 +29,37 @@ import org.springframework.shell.standard.ShellMethodAvailability;
|
|||||||
@ShellComponent
|
@ShellComponent
|
||||||
public class DynamicCommands {
|
public class DynamicCommands {
|
||||||
|
|
||||||
private boolean connected;
|
private boolean connected;
|
||||||
|
|
||||||
public Availability authenticateAvailability() {
|
private boolean authenticated;
|
||||||
return connected ? Availability.available() : Availability.unavailable("you are not connected");
|
|
||||||
}
|
|
||||||
|
|
||||||
@ShellMethod(value = "Authenticate with the system", group = "Dynamic Commands")
|
public Availability authenticateAvailability() {
|
||||||
public void authenticate(String credentials) {
|
return connected ? Availability.available() : Availability.unavailable("you are not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ShellMethod(value = "Connect to the system", group = "Dynamic Commands")
|
@ShellMethod(value = "Authenticate with the system", group = "Dynamic Commands")
|
||||||
public void connect() {
|
public void authenticate(String credentials) {
|
||||||
connected = true;
|
authenticated = "sesame".equals(credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ShellMethod(value = "Disconnect from the system", group = "Dynamic Commands")
|
@ShellMethod(value = "Connect to the system", group = "Dynamic Commands")
|
||||||
public void disconnect() {
|
public void connect() {
|
||||||
connected = false;
|
connected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ShellMethod(value = "Blow Everything up", group = "Dynamic Commands")
|
@ShellMethod(value = "Disconnect from the system", group = "Dynamic Commands")
|
||||||
@ShellMethodAvailability("dangerousAvailability")
|
public void disconnect() {
|
||||||
public String blowUp() {
|
connected = false;
|
||||||
return "Boom!";
|
}
|
||||||
}
|
|
||||||
|
@ShellMethod(value = "Blow Everything up", group = "Dynamic Commands")
|
||||||
|
@ShellMethodAvailability("dangerousAvailability")
|
||||||
|
public String blowUp() {
|
||||||
|
return "Boom!";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Availability dangerousAvailability() {
|
||||||
|
return connected && authenticated ? Availability.available()
|
||||||
|
: Availability.unavailable("you failed to authenticate. Try 'sesame'.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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.standard.commands;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model encapsulating info about {@code command availability}.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*/
|
||||||
|
class CommandAvailabilityInfoModel {
|
||||||
|
|
||||||
|
private boolean available;
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
CommandAvailabilityInfoModel(boolean available, String reason) {
|
||||||
|
this.available = available;
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds {@link CommandAvailabilityInfoModel}.
|
||||||
|
*
|
||||||
|
* @param available the available flag
|
||||||
|
* @param reason the reason
|
||||||
|
* @return a command parameter availability model
|
||||||
|
*/
|
||||||
|
static CommandAvailabilityInfoModel of(boolean available, String reason) {
|
||||||
|
return new CommandAvailabilityInfoModel(available, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getAvailable() {
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReason() {
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import java.util.List;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.springframework.shell.Availability;
|
||||||
import org.springframework.shell.command.CommandOption;
|
import org.springframework.shell.command.CommandOption;
|
||||||
import org.springframework.shell.command.CommandRegistration;
|
import org.springframework.shell.command.CommandRegistration;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
@@ -34,11 +35,14 @@ class CommandInfoModel {
|
|||||||
private String name;
|
private String name;
|
||||||
private String description;
|
private String description;
|
||||||
private List<CommandParameterInfoModel> parameters;
|
private List<CommandParameterInfoModel> parameters;
|
||||||
|
private CommandAvailabilityInfoModel availability;
|
||||||
|
|
||||||
CommandInfoModel(String name, String description, List<CommandParameterInfoModel> parameters) {
|
CommandInfoModel(String name, String description, List<CommandParameterInfoModel> parameters,
|
||||||
|
CommandAvailabilityInfoModel availability) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.parameters = parameters;
|
this.parameters = parameters;
|
||||||
|
this.availability = availability;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +69,15 @@ class CommandInfoModel {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
String description = registration.getDescription();
|
String description = registration.getDescription();
|
||||||
return new CommandInfoModel(name, description, parameters);
|
boolean available = true;
|
||||||
|
String availReason = "";
|
||||||
|
if (registration.getAvailability() != null) {
|
||||||
|
Availability a = registration.getAvailability();
|
||||||
|
available = a.isAvailable();
|
||||||
|
availReason = a.getReason();
|
||||||
|
}
|
||||||
|
CommandAvailabilityInfoModel availModel = CommandAvailabilityInfoModel.of(available, availReason);
|
||||||
|
return new CommandInfoModel(name, description, parameters, availModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String commandOptionType(CommandOption o) {
|
private static String commandOptionType(CommandOption o) {
|
||||||
@@ -88,4 +100,8 @@ class CommandInfoModel {
|
|||||||
public List<CommandParameterInfoModel> getParameters() {
|
public List<CommandParameterInfoModel> getParameters() {
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandAvailabilityInfoModel getAvailability() {
|
||||||
|
return availability;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,11 +36,14 @@ class GroupsInfoModel {
|
|||||||
private boolean showGroups = true;
|
private boolean showGroups = true;
|
||||||
private final List<GroupCommandInfoModel> groups;
|
private final List<GroupCommandInfoModel> groups;
|
||||||
private final List<CommandInfoModel> commands;
|
private final List<CommandInfoModel> commands;
|
||||||
|
private boolean hasUnavailableCommands = false;
|
||||||
|
|
||||||
GroupsInfoModel(boolean showGroups, List<GroupCommandInfoModel> groups, List<CommandInfoModel> commands) {
|
GroupsInfoModel(boolean showGroups, List<GroupCommandInfoModel> groups, List<CommandInfoModel> commands,
|
||||||
|
boolean hasUnavailableCommands) {
|
||||||
this.showGroups = showGroups;
|
this.showGroups = showGroups;
|
||||||
this.groups = groups;
|
this.groups = groups;
|
||||||
this.commands = commands;
|
this.commands = commands;
|
||||||
|
this.hasUnavailableCommands = hasUnavailableCommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,7 +74,17 @@ class GroupsInfoModel {
|
|||||||
List<CommandInfoModel> commands = gcims.stream()
|
List<CommandInfoModel> commands = gcims.stream()
|
||||||
.flatMap(gcim -> gcim.getCommands().stream())
|
.flatMap(gcim -> gcim.getCommands().stream())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return new GroupsInfoModel(showGroups, gcims, commands);
|
boolean hasUnavailableCommands = commands.stream()
|
||||||
|
.map(c -> {
|
||||||
|
if (c.getAvailability() != null) {
|
||||||
|
return c.getAvailability().getAvailable();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.filter(a -> !a)
|
||||||
|
.findFirst()
|
||||||
|
.isPresent();
|
||||||
|
return new GroupsInfoModel(showGroups, gcims, commands, hasUnavailableCommands);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getShowGroups() {
|
public boolean getShowGroups() {
|
||||||
@@ -85,4 +98,8 @@ class GroupsInfoModel {
|
|||||||
public List<CommandInfoModel> getCommands() {
|
public List<CommandInfoModel> getCommands() {
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getHasUnavailableCommands() {
|
||||||
|
return hasUnavailableCommands;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,15 @@ options(options) ::= <<
|
|||||||
<options:{ o | <option(o)>}>
|
<options:{ o | <option(o)>}>
|
||||||
>>
|
>>
|
||||||
|
|
||||||
|
// AVAILABILITY
|
||||||
|
availability(availability) ::= <<
|
||||||
|
<if(!availability.available)>
|
||||||
|
|
||||||
|
<("CURRENTLY UNAVAILABLE"); format="style-highlight">
|
||||||
|
<availability.reason>
|
||||||
|
<endif>
|
||||||
|
>>
|
||||||
|
|
||||||
// main
|
// main
|
||||||
main(model) ::= <<
|
main(model) ::= <<
|
||||||
<name(model.name, model.description)>
|
<name(model.name, model.description)>
|
||||||
@@ -64,4 +73,5 @@ main(model) ::= <<
|
|||||||
<synopsis(model.name, model.parameters)>
|
<synopsis(model.name, model.parameters)>
|
||||||
|
|
||||||
<options(model.parameters)>
|
<options(model.parameters)>
|
||||||
|
<availability(model.availability)>
|
||||||
>>
|
>>
|
||||||
|
|||||||
@@ -3,8 +3,21 @@ name() ::= <<
|
|||||||
|
|
||||||
>>
|
>>
|
||||||
|
|
||||||
|
availability(availability) ::= <%
|
||||||
|
<if(!availability.available)>
|
||||||
|
<("* "); format="style-highlight">
|
||||||
|
<endif>
|
||||||
|
%>
|
||||||
|
|
||||||
|
availabilityDesc(hasUnavailableCommands) ::= <<
|
||||||
|
<if(hasUnavailableCommands)>
|
||||||
|
<("Commands marked with (*) are currently unavailable.")>
|
||||||
|
<("Type `help <command>` to learn more.")>
|
||||||
|
<endif>
|
||||||
|
>>
|
||||||
|
|
||||||
command(command) ::= <<
|
command(command) ::= <<
|
||||||
<(command.name); format="style-highlight"><(":"); format="style-highlight"> <command.description>
|
<availability(command.availability)><(command.name); format="style-highlight"><(":"); format="style-highlight"> <command.description>
|
||||||
|
|
||||||
>>
|
>>
|
||||||
|
|
||||||
@@ -29,4 +42,5 @@ main(model) ::= <<
|
|||||||
<else>
|
<else>
|
||||||
<flat(model.commands)>
|
<flat(model.commands)>
|
||||||
<endif>
|
<endif>
|
||||||
|
<availabilityDesc(model.hasUnavailableCommands)>
|
||||||
>>
|
>>
|
||||||
|
|||||||
Reference in New Issue
Block a user