From fa770403df304410d51484549bd683182847b4cc Mon Sep 17 00:00:00 2001 From: Aled Sage Date: Sat, 28 Feb 2015 23:09:27 +0000 Subject: [PATCH] Improve CLI customisability for downstream projects - Make ASCII art banner customisable - Make info command customisable - Remove redundant duplicate Main.cliScriptName, as already declared in AbstractMain - Adds tests --- .../main/java/brooklyn/cli/AbstractMain.java | 14 +++- .../cli/src/main/java/brooklyn/cli/Main.java | 24 ++++--- .../src/test/java/brooklyn/cli/CliTest.java | 71 +++++++++++++++++++ 3 files changed, 97 insertions(+), 12 deletions(-) diff --git a/usage/cli/src/main/java/brooklyn/cli/AbstractMain.java b/usage/cli/src/main/java/brooklyn/cli/AbstractMain.java index 1378c82e87..7e524619e0 100644 --- a/usage/cli/src/main/java/brooklyn/cli/AbstractMain.java +++ b/usage/cli/src/main/java/brooklyn/cli/AbstractMain.java @@ -70,7 +70,7 @@ public abstract class AbstractMain { private static final Logger log = LoggerFactory.getLogger(AbstractMain.class); // Launch banner - public static final String BANNER = + public static final String DEFAULT_BANNER = " _ _ _ \n" + "| |__ _ __ ___ ___ | | _| |_ _ _ __ (R)\n" + "| '_ \\| '__/ _ \\ / _ \\| |/ / | | | | '_ \\ \n" + @@ -84,6 +84,16 @@ public abstract class AbstractMain { public static final int EXECUTION_ERROR = 2; public static final int CONFIGURATION_ERROR = 3; + /** + * Field intended for sub-classes (with their own {@code main()}) to customize the banner. + * All accesses to the banner are done through this field, to ensure consistent customization. + * + * Note that a {@code getBanner()} method is not an option for supporting this, because + * it is accessed from static inner-classes (such as {@link InfoCommand}, so non-static + * methods are not an option (and one can't override static methods). + */ + protected static volatile String banner = DEFAULT_BANNER; + /** abstract superclass for commands defining global options, but not arguments, * as that prevents Help from being injectable in the {@link HelpCommand} subclass */ public static abstract class BrooklynCommand implements Callable { @@ -163,7 +173,7 @@ public Void call() throws Exception { if (log.isDebugEnabled()) log.debug("Invoked info command: {}", this); warnIfArguments(); - System.out.println(BANNER); + System.out.println(banner); System.out.println("Version: " + BrooklynVersion.get()); if (BrooklynVersion.INSTANCE.isSnapshot()) { System.out.println("Git SHA1: " + BrooklynVersion.INSTANCE.getSha1FromOsgiManifest()); diff --git a/usage/cli/src/main/java/brooklyn/cli/Main.java b/usage/cli/src/main/java/brooklyn/cli/Main.java index 7a797213b2..e674c1d084 100644 --- a/usage/cli/src/main/java/brooklyn/cli/Main.java +++ b/usage/cli/src/main/java/brooklyn/cli/Main.java @@ -351,7 +351,7 @@ public Void call() throws Exception { try { if (log.isDebugEnabled()) log.debug("Invoked launch command {}", this); - if (!quiet) stdout.println(BANNER); + if (!quiet) stdout.println(banner); if (verbose) { if (app != null) { @@ -773,7 +773,7 @@ public Void call() throws Exception { try { log.info("Retrieving and copying persisted state to "+destinationDir+(Strings.isBlank(destinationLocation) ? "" : " @ "+destinationLocation)); - if (!quiet) stdout.println(BANNER); + if (!quiet) stdout.println(banner); PersistMode persistMode = PersistMode.AUTO; HighAvailabilityMode highAvailabilityMode = HighAvailabilityMode.DISABLED; @@ -828,12 +828,6 @@ public ToStringHelper string() { } } - /** method intended for overriding when the script filename is different - * @return the name of the script the user has invoked */ - protected String cliScriptName() { - return "brooklyn"; - } - /** method intended for overriding when a different {@link Cli} is desired, * or when the subclass wishes to change any of the arguments */ @SuppressWarnings("unchecked") @@ -841,10 +835,10 @@ protected String cliScriptName() { protected CliBuilder cliBuilder() { CliBuilder builder = Cli.builder(cliScriptName()) .withDescription("Brooklyn Management Service") - .withDefaultCommand(DefaultInfoCommand.class) + .withDefaultCommand(cliDefaultInfoCommand()) .withCommands( HelpCommand.class, - InfoCommand.class, + cliInfoCommand(), GeneratePasswordCommand.class, CopyStateCommand.class, ListAllCommand.class, @@ -877,4 +871,14 @@ protected CliBuilder cliBuilder() { protected Class cliLaunchCommand() { return LaunchCommand.class; } + + /** method intended for overriding when a custom {@link InfoCommand} is being specified */ + protected Class cliInfoCommand() { + return InfoCommand.class; + } + + /** method intended for overriding when a custom {@link InfoCommand} is being specified */ + protected Class cliDefaultInfoCommand() { + return DefaultInfoCommand.class; + } } diff --git a/usage/cli/src/test/java/brooklyn/cli/CliTest.java b/usage/cli/src/test/java/brooklyn/cli/CliTest.java index 79e1b56f61..a50b0586bc 100644 --- a/usage/cli/src/test/java/brooklyn/cli/CliTest.java +++ b/usage/cli/src/test/java/brooklyn/cli/CliTest.java @@ -24,6 +24,7 @@ import static org.testng.Assert.fail; import groovy.lang.GroovyClassLoader; import io.airlift.command.Cli; +import io.airlift.command.Command; import io.airlift.command.ParseException; import java.io.ByteArrayInputStream; @@ -48,6 +49,7 @@ import org.testng.annotations.Test; import brooklyn.cli.AbstractMain.BrooklynCommand; +import brooklyn.cli.AbstractMain.BrooklynCommandCollectingArgs; import brooklyn.cli.AbstractMain.HelpCommand; import brooklyn.cli.Main.GeneratePasswordCommand; import brooklyn.cli.Main.LaunchCommand; @@ -410,6 +412,71 @@ public void testGeneratePasswordFailsIfPasswordBlank() throws Throwable { } } + @Test + public void testInfoShowsDefaultBanner() throws Exception { + List stdoutLines = runCommand(ImmutableList.of("info"), ""); + + for (String line : Splitter.on("\n").split(Main.DEFAULT_BANNER)) { + assertTrue(stdoutLines.contains(line), "out="+stdoutLines); + } + } + + @Test + public void testInfoSupportsCustomizedBanner() throws Exception { + String origBanner = Main.banner; + String origBannerFirstLine = Iterables.get(Splitter.on("\n").split(Main.DEFAULT_BANNER), 0); + try { + String customBanner = "My Custom Banner"; + Main.banner = customBanner; + List stdoutLines = runCommand(ImmutableList.of("info"), ""); + + assertTrue(stdoutLines.contains(customBanner), "out="+stdoutLines); + assertFalse(stdoutLines.contains(origBannerFirstLine), "out="+stdoutLines); + } finally { + Main.banner = origBanner; + } + } + + @Test + public void testCanCustomiseInfoCommand() throws Exception { + Main main = new Main() { + protected Class cliInfoCommand() { + return CustomInfoCommand.class; + } + }; + List stdoutLines = runCommand(main.cliBuilder().build(), ImmutableList.of("info"), ""); + assertTrue(stdoutLines.contains("My Custom Info"), "out="+stdoutLines); + } + + @Command(name = "info", description = "Display information about brooklyn") + public static class CustomInfoCommand extends BrooklynCommandCollectingArgs { + @Override + public Void call() throws Exception { + System.out.println("My Custom Info"); + return null; + } + } + + @Test + public void testCanCustomiseLaunchCommand() throws Exception { + Main main = new Main() { + protected Class cliLaunchCommand() { + return CustomLaunchCommand.class; + } + }; + List stdoutLines = runCommand(main.cliBuilder().build(), ImmutableList.of("launch"), ""); + assertTrue(stdoutLines.contains("My Custom Launch"), "out="+stdoutLines); + } + + @Command(name = "launch", description = "Starts a server, optionally with applications") + public static class CustomLaunchCommand extends BrooklynCommandCollectingArgs { + @Override + public Void call() throws Exception { + System.out.println("My Custom Launch"); + return null; + } + } + protected Throwable runCommandExpectingException(Iterable args, String input) throws Exception { try { List stdout = runCommand(args, input); @@ -422,6 +489,10 @@ protected Throwable runCommandExpectingException(Iterable args, String i protected List runCommand(Iterable args, String input) throws Exception { Cli cli = buildCli(); + return runCommand(cli, args, input); + } + + protected List runCommand(Cli cli, Iterable args, String input) throws Exception { final BrooklynCommand command = cli.parse(args); final AtomicReference exception = new AtomicReference();