Add automatic command grouping

Fixes #163

Introduce Command and Command.Help
This commit is contained in:
Eric Bottard
2017-09-27 10:49:12 +02:00
parent 531dc35abe
commit f0e5c45ee9
16 changed files with 404 additions and 53 deletions

View File

@@ -0,0 +1,77 @@
/*
* 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;
import java.util.Objects;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
public interface Command {
/**
* Encapsulates help metadata about a shell command.
*
* @author Eric Bottard
*/
class Help {
/**
* Optional group to gather related commands together.
*/
private final String group;
/**
* A required, short one sentence description of the command. Should start with a capital and end with a dot
* for consistency.
*/
private final String description;
public Help(String description) {
this(description, null);
}
public Help(String description, String group) {
Assert.isTrue(StringUtils.hasText(description), "Command description cannot be null or empty");
this.description = description;
this.group = StringUtils.hasText(group) ? group : "";
}
public String getDescription() {
return description;
}
public String getGroup() {
return group;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Help help = (Help) o;
return Objects.equals(group, help.group) &&
Objects.equals(description, help.description);
}
@Override
public int hashCode() {
return Objects.hash(group, description);
}
}
}

View File

@@ -29,15 +29,13 @@ import org.springframework.util.ReflectionUtils;
*
* @author Eric Bottard
*/
public class MethodTarget {
public class MethodTarget implements Command {
private final Method method;
private final Object bean;
private final String help;
private final String group;
private final Help help;
/**
* If not null, returns whether or not the command is currently available. Implementations must be idempotent.
@@ -45,22 +43,21 @@ public class MethodTarget {
private final Supplier<Availability> availabilityIndicator;
public MethodTarget(Method method, Object bean, String help) {
this(method, bean, help, null, null);
this(method, bean, new Help(help, null), null);
}
public MethodTarget(Method method, Object bean, String help, Supplier<Availability> availabilityIndicator) {
this(method, bean, help, null, availabilityIndicator);
this(method, bean, new Help(help, null), availabilityIndicator);
}
public MethodTarget(Method method, Object bean, String help, String group, Supplier<Availability> availabilityIndicator) {
public MethodTarget(Method method, Object bean, Help help, Supplier<Availability> availabilityIndicator) {
Assert.notNull(method, "Method cannot be null");
Assert.notNull(bean, "Bean cannot be null");
Assert.hasText(help, String.format("Help cannot be blank when trying to define command based on '%s'", method));
Assert.hasText(help.getDescription(), String.format("Help cannot be blank when trying to define command based on '%s'", method));
ReflectionUtils.makeAccessible(method);
this.method = method;
this.bean = bean;
this.help = help;
this.group = group != null ? group : "";
this.availabilityIndicator = availabilityIndicator != null ? availabilityIndicator : () -> Availability.available();
}
@@ -68,30 +65,22 @@ public class MethodTarget {
* Construct a MethodTarget for the unique method named {@literal name} on the given object. Fails with an exception
* in case of overloaded method.
*/
public static MethodTarget of(String name, Object bean, String help) {
return of(name, bean, help, null, null);
public static MethodTarget of(String name, Object bean, Help help) {
return of(name, bean, help, null);
}
/**
* Construct a MethodTarget for the unique method named {@literal name} on the given object. Fails with an exception
* in case of overloaded method.
*/
public static MethodTarget of(String name, Object bean, String help, String group) {
return of(name, bean, help, group, null);
}
/**
* Construct a MethodTarget for the unique method named {@literal name} on the given object. Fails with an exception
* in case of overloaded method.
*/
public static MethodTarget of(String name, Object bean, String help, String group, Supplier<Availability> availabilityIndicator) {
public static MethodTarget of(String name, Object bean, Help help, Supplier<Availability> availabilityIndicator) {
Set<Method> found = new HashSet<>();
ReflectionUtils.doWithMethods(bean.getClass(), found::add, m -> m.getName().equals(name));
if (found.size() != 1) {
throw new IllegalArgumentException(String.format("Could not find unique method named '%s' on object of class %s. Found %s",
name, bean.getClass(), found));
}
return new MethodTarget(found.iterator().next(), bean, help, group, availabilityIndicator);
return new MethodTarget(found.iterator().next(), bean, help, availabilityIndicator);
}
public Method getMethod() {
@@ -103,11 +92,11 @@ public class MethodTarget {
}
public String getHelp() {
return help;
return help.getDescription();
}
public String getGroup() {
return group;
return help.getGroup();
}
public Availability getAvailability() {
@@ -123,7 +112,7 @@ public class MethodTarget {
if (!method.equals(that.method)) return false;
if (!bean.equals(that.bean)) return false;
if (!group.equals(that.group)) return false;
if (!help.equals(that.help)) return false;
return help.equals(that.help);
}
@@ -133,7 +122,7 @@ public class MethodTarget {
int result = method.hashCode();
result = 31 * result + bean.hashCode();
result = 31 * result + help.hashCode();
result = 31 * result + group.hashCode();
result = 31 * result + help.hashCode();
return result;
}

View File

@@ -20,7 +20,6 @@ import static org.hamcrest.collection.IsMapContaining.hasEntry;
import static org.hamcrest.collection.IsMapContaining.hasKey;
import static org.junit.Assert.*;
import org.hamcrest.collection.IsMapContaining;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -38,7 +37,7 @@ public class ConfigurableCommandRegistryTest {
@Test
public void testRegistration() {
ConfigurableCommandRegistry registry = new ConfigurableCommandRegistry();
registry.register("foo", MethodTarget.of("toString", this, "some command"));
registry.register("foo", MethodTarget.of("toString", this, new Command.Help("some command")));
assertThat(registry.listCommands(), hasKey("foo"));
}
@@ -46,14 +45,14 @@ public class ConfigurableCommandRegistryTest {
@Test
public void testDoubleRegistration() {
ConfigurableCommandRegistry registry = new ConfigurableCommandRegistry();
registry.register("foo", MethodTarget.of("toString", this, "some command"));
registry.register("foo", MethodTarget.of("toString", this, new Command.Help("some command")));
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("foo");
thrown.expectMessage("toString");
thrown.expectMessage("hashCode");
registry.register("foo", MethodTarget.of("hashCode", this, "some command"));
registry.register("foo", MethodTarget.of("hashCode", this, new Command.Help("some command")));
}
}

View File

@@ -83,7 +83,7 @@ public class ShellTest {
when(parameterResolver.resolve(any(), any())).thenReturn(valueResult);
doThrow(new Exit()).when(resultHandler).handleResult(any());
shell.methodTargets = Collections.singletonMap("hello world", MethodTarget.of("helloWorld", this, "Say hello"));
shell.methodTargets = Collections.singletonMap("hello world", MethodTarget.of("helloWorld", this, new Command.Help("Say hello")));
try {
shell.run(inputProvider);
@@ -101,7 +101,7 @@ public class ShellTest {
when(inputProvider.readInput()).thenReturn(() -> "hello world how are you doing ?", null);
doThrow(new Exit()).when(resultHandler).handleResult(isA(CommandNotFound.class));
shell.methodTargets = Collections.singletonMap("bonjour", MethodTarget.of("helloWorld", this, "Say hello"));
shell.methodTargets = Collections.singletonMap("bonjour", MethodTarget.of("helloWorld", this, new Command.Help("Say hello")));
try {
shell.run(inputProvider);
@@ -118,7 +118,7 @@ public class ShellTest {
when(inputProvider.readInput()).thenReturn(() -> "helloworld how are you doing ?", null);
doThrow(new Exit()).when(resultHandler).handleResult(isA(CommandNotFound.class));
shell.methodTargets = Collections.singletonMap("hello", MethodTarget.of("helloWorld", this, "Say hello"));
shell.methodTargets = Collections.singletonMap("hello", MethodTarget.of("helloWorld", this, new Command.Help("Say hello")));
try {
shell.run(inputProvider);
@@ -137,7 +137,7 @@ public class ShellTest {
when(parameterResolver.resolve(any(), any())).thenReturn(valueResult);
doThrow(new Exit()).when(resultHandler).handleResult(any());
shell.methodTargets = Collections.singletonMap("hello world", MethodTarget.of("helloWorld", this, "Say hello"));
shell.methodTargets = Collections.singletonMap("hello world", MethodTarget.of("helloWorld", this, new Command.Help("Say hello")));
try {
shell.run(inputProvider);
@@ -156,7 +156,7 @@ public class ShellTest {
when(inputProvider.readInput()).thenReturn(() -> "fail", null);
doThrow(new Exit()).when(resultHandler).handleResult(isA(SomeException.class));
shell.methodTargets = Collections.singletonMap("fail", MethodTarget.of("failing", this, "Will throw an exception"));
shell.methodTargets = Collections.singletonMap("fail", MethodTarget.of("failing", this, new Command.Help("Will throw an exception")));
try {
shell.run(inputProvider);
@@ -185,7 +185,7 @@ public class ShellTest {
shell.applicationContext = mock(ApplicationContext.class);
when(shell.applicationContext.getBeansOfType(MethodTargetRegistrar.class))
.thenReturn(Collections.singletonMap("foo", r -> {
r.register("hw", MethodTarget.of("helloWorld", this, "hellow world"));
r.register("hw", MethodTarget.of("helloWorld", this, new Command.Help("hellow world")));
}));
thrown.expect(ParameterResolverMissingException.class);
@@ -198,8 +198,8 @@ public class ShellTest {
when(parameterResolver.supports(any())).thenReturn(true);
when(shell.applicationContext.getBeansOfType(MethodTargetRegistrar.class))
.thenReturn(Collections.singletonMap("foo", r -> {
r.register("hello world", MethodTarget.of("helloWorld", this, "hellow world"));
r.register("another command", MethodTarget.of("helloWorld", this, "another command"));
r.register("hello world", MethodTarget.of("helloWorld", this, new Command.Help("hellow world")));
r.register("another command", MethodTarget.of("helloWorld", this, new Command.Help("another command")));
}));
shell.gatherMethodTargets();

View File

@@ -596,6 +596,57 @@ But it's often good practice to put related commands in the same class, and the
can benefit from that.
====
[[organizing-commands]]
=== Organizing Commands
When your shell starts to provide a lot of functionality, you may en up
with a lot of commands, which could be confusing for your users. Typing `help`
they would see a daunting list of commands, organized by alphabetical order,
which may not always make sense.
To alleviate this, Spring Shell provides the ability to group commands together,
with reasonable defaults. Related commands would then end up in the same _group_ (_e.g._ `User Management Commands`)
and be displayed together in the help screen and other places.
By default, commands will be grouped according to the class they are implemented in,
turning the camel case class name into separate words (so `URLRelatedCommands` becomes `URL Related Commands`).
This is a very sensible default, as related commands are often already in the class anyway,
for they need to use the same collaborating objects.
If however, this behavior does not suit you, you can override the group for a
command in the following ways, in order of priority:
* specifying a `group()` in the `@ShellMethod` annotation
* placing a `@ShellCommandGroup` on the class the command is defined in. This will apply
the group for all commands defined in that class (unless overridden as above)
* placing a `@ShellCommandGroup` on the package (_via_ `package-info.java`)
the command is defined in. This will apply to all commands defined in the
package (unless overridden at the method or class level as explained above)
Here is a short example:
[source,java]
----
public class UserCommands {
@ShellCommand(value = "This command ends up in the 'User Commands' group")
public void foo() {}
@ShellCommand(value = "This command ends up in the 'Other Commands' group",
group = "Other Commands")
public void bar() {}
}
...
@ShellCommandGroup("Other Commands")
public class SomeCommands {
@ShellMethod(value = "This one is in 'Other Commands'")
public void wizz() {}
@ShellMethod(value = "And this one is 'Yet Another Group'",
group = "Yet Another Group")
public void last() {}
}
----
[[built-in-commands]]
=== Built-In Commands
Any application built using the `{starter-artifactId}` artifact

View File

@@ -27,6 +27,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.shell.Command;
import org.springframework.shell.ConfigurableCommandRegistry;
import org.springframework.shell.MethodTarget;
import org.springframework.shell.MethodTargetRegistrar;
@@ -56,7 +57,7 @@ public class LegacyMethodTargetRegistrarTest {
assertThat(targets).contains(entry(
"register module",
MethodTarget.of("register", legacyCommands, "Register a new module")
MethodTarget.of("register", legacyCommands, new Command.Help("Register a new module"))
));
}

View File

@@ -19,4 +19,7 @@
*
* @author Eric Bottard
*/
@ShellCommandGroup("Built-In Commands")
package org.springframework.shell.standard.commands;
import org.springframework.shell.standard.ShellCommandGroup;

View File

@@ -19,7 +19,6 @@ package org.springframework.shell.standard.commands;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
@@ -38,6 +37,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.ClassPathResource;
import org.springframework.shell.Command;
import org.springframework.shell.standard.StandardParameterResolver;
import org.springframework.shell.MethodTarget;
import org.springframework.shell.ParameterResolver;
@@ -48,7 +48,6 @@ import org.springframework.shell.standard.ShellOption;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.ReflectionUtils;
import javax.validation.constraints.Max;
@@ -114,21 +113,21 @@ public class HelpTest {
public CommandRegistry shell() {
return () -> {
Map<String, MethodTarget> result = new HashMap<>();
MethodTarget methodTarget = MethodTarget.of("firstCommand", commands(), "A rather extensive description of some command.");
MethodTarget methodTarget = MethodTarget.of("firstCommand", commands(), new Command.Help("A rather extensive description of some command."));
result.put("first-command", methodTarget);
result.put("1st-command", methodTarget);
methodTarget = MethodTarget.of("secondCommand", commands(), "The second command. This one is known under several aliases as well.");
methodTarget = MethodTarget.of("secondCommand", commands(), new Command.Help("The second command. This one is known under several aliases as well."));
result.put("second-command", methodTarget);
result.put("yet-another-command", methodTarget);
methodTarget = MethodTarget.of("thirdCommand", commands(), "The last command.");
methodTarget = MethodTarget.of("thirdCommand", commands(), new Command.Help("The last command."));
result.put("third-command", methodTarget);
methodTarget = MethodTarget.of("firstCommandInGroup", commands(), "The first command in a separate group.", "Example Group");
methodTarget = MethodTarget.of("firstCommandInGroup", commands(), new Command.Help("The first command in a separate group.", "Example Group"));
result.put("first-group-command", methodTarget);
methodTarget = MethodTarget.of("secondCommandInGroup", commands(), "The second command in a separate group.", "Example Group");
methodTarget = MethodTarget.of("secondCommandInGroup", commands(), new Command.Help("The second command in a separate group.", "Example Group"));
result.put("second-group-command", methodTarget);
return result;

View File

@@ -0,0 +1,50 @@
/*
* 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.standard;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Used to indicate the default group of shell commands, either at the package or class level.
*
* @author Eric Bottard
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PACKAGE, ElementType.TYPE})
@Documented
public @interface ShellCommandGroup {
/**
* The default value for the group label, which when set<ul>
* <li>on a class, will mean to look at the package level</li>
* <li>on a package, to go back at the class level and infer a name from the class name.</li>
* </ul>
*/
String INHERIT_AND_INFER = "";
/**
* @return
* An explicit value for the group, which will apply to all commands in the owning class or package, depending
* on where this annotation is set.
*/
String value() default INHERIT_AND_INFER;
}

View File

@@ -33,6 +33,13 @@ import java.lang.annotation.Target;
@Documented
public @interface ShellMethod {
/**
* The default value for {@link #group()}, meaning that the group will be inherited from the explicit value set
* on the containing element (class then package) or ultimately inferred.
* @see ShellCommandGroup
*/
String INHERITED = "";
/**
* The name(s) by which this method can be invoked via Spring Shell. If not specified, the actual method name
* will be used (turning camelCase humps into "-").
@@ -55,9 +62,10 @@ public @interface ShellMethod {
/**
* The command group which this command belongs to. The command group is used when printing a list of
* commands to group related commands.
* commands to group related commands. By default, group is first looked up from owning class then package,
* and if not explicitly set, is inferred from class name.
* @return name of the command group
*/
String group() default "";
String group() default INHERITED;
}

View File

@@ -21,14 +21,15 @@ import static org.springframework.util.StringUtils.collectionToDelimitedString;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.shell.*;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* The standard implementation of {@link MethodTargetRegistrar} for new shell
@@ -61,10 +62,10 @@ public class StandardMethodTargetRegistrar implements MethodTargetRegistrar {
if (keys.length == 0) {
keys = new String[] { Utils.unCamelify(method.getName()) };
}
String group = shellMapping.group();
String group = getOrInferGroup(method);
for (String key : keys) {
Supplier<Availability> availabilityIndicator = findAvailabilityIndicator(keys, bean, method);
MethodTarget target = new MethodTarget(method, bean, shellMapping.value(), group, availabilityIndicator);
MethodTarget target = new MethodTarget(method, bean, new Command.Help(shellMapping.value(), group), availabilityIndicator);
registry.register(key, target);
commands.put(key, target);
}
@@ -72,6 +73,32 @@ public class StandardMethodTargetRegistrar implements MethodTargetRegistrar {
}
}
/**
* Gets the group from the following places, in order:<ul>
* <li>explicit annotation at the method level</li>
* <li>explicit annotation at the class level</li>
* <li>explicit annotation at the package level</li>
* <li>implicit from the class name</li>
* </ul>
*/
private String getOrInferGroup(Method method) {
ShellMethod methodAnn = AnnotationUtils.getAnnotation(method, ShellMethod.class);
if (!methodAnn.group().equals(ShellMethod.INHERITED)) {
return methodAnn.group();
}
Class<?> clazz = method.getDeclaringClass();
ShellCommandGroup classAnn = AnnotationUtils.getAnnotation(clazz, ShellCommandGroup.class);
if (classAnn != null && !classAnn.value().equals(ShellCommandGroup.INHERIT_AND_INFER)) {
return classAnn.value();
}
ShellCommandGroup packageAnn = AnnotationUtils.getAnnotation(clazz.getPackage(), ShellCommandGroup.class);
if (packageAnn != null && !packageAnn.value().equals(ShellCommandGroup.INHERIT_AND_INFER)) {
return packageAnn.value();
}
// Shameful copy/paste from https://stackoverflow.com/questions/7593969/regex-to-split-camelcase-or-titlecase-advanced
return StringUtils.arrayToDelimitedString(clazz.getSimpleName().split("(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])"), " ");
}
/**
* Tries to locate an availability indicator (a no-arg method that returns
* {@link Availability}) for the given command method. The following are tried in order

View File

@@ -16,8 +16,7 @@
package org.springframework.shell.standard;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.assertj.core.api.Assertions;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -26,6 +25,9 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.shell.Availability;
import org.springframework.shell.ConfigurableCommandRegistry;
import org.springframework.shell.MethodTarget;
import org.springframework.shell.standard.test1.GroupOneCommands;
import org.springframework.shell.standard.test2.GroupTwoCommands;
import org.springframework.shell.standard.test2.GroupThreeCommands;
import org.springframework.util.ReflectionUtils;
import static org.hamcrest.Matchers.hasEntry;
@@ -33,6 +35,8 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.util.Map;
/**
* Unit tests for {@link StandardMethodTargetRegistrar}.
*
@@ -242,4 +246,20 @@ public class StandardMethodTargetRegistrarTest {
}
}
}
@Test
public void testGrouping() {
ApplicationContext context = new AnnotationConfigApplicationContext(GroupOneCommands.class,
GroupTwoCommands.class, GroupThreeCommands.class);
registrar.setApplicationContext(context);
registrar.register(registry);
Map<String, MethodTarget> commands = registry.listCommands();
Assertions.assertThat(commands.get("explicit1").getGroup()).isEqualTo("Explicit Group Method Level 1");
Assertions.assertThat(commands.get("explicit2").getGroup()).isEqualTo("Explicit Group Method Level 2");
Assertions.assertThat(commands.get("explicit3").getGroup()).isEqualTo("Explicit Group Method Level 3");
Assertions.assertThat(commands.get("implicit1").getGroup()).isEqualTo("Implicit Group Package Level 1");
Assertions.assertThat(commands.get("implicit2").getGroup()).isEqualTo("Group Two Commands");
Assertions.assertThat(commands.get("implicit3").getGroup()).isEqualTo("Explicit Group 3 Class Level");
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.standard.test1;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
@ShellComponent
public class GroupOneCommands {
@ShellMethod(value = "Do Something.", group = "Explicit Group Method Level 1")
public void explicit1() {
}
@ShellMethod(value = "Do Something Else")
public void implicit1() {
}
}

View File

@@ -0,0 +1,20 @@
/*
* 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.
*/
@ShellCommandGroup("Implicit Group Package Level 1")
package org.springframework.shell.standard.test1;
import org.springframework.shell.standard.ShellCommandGroup;

View File

@@ -0,0 +1,37 @@
/*
* 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.standard.test2;
import org.springframework.shell.standard.ShellCommandGroup;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
@ShellComponent
@ShellCommandGroup("Explicit Group 3 Class Level")
public class GroupThreeCommands {
@ShellMethod(value = "Do Something.", group = "Explicit Group Method Level 3")
public void explicit3() {
}
@ShellMethod(value = "Do Something Else")
public void implicit3() {
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.standard.test2;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
@ShellComponent
public class GroupTwoCommands {
@ShellMethod(value = "Do Something.", group = "Explicit Group Method Level 2")
public void explicit2() {
}
@ShellMethod(value = "Do Something Else")
public void implicit2() {
}
}