diff --git a/aesh/src/main/java/org/aesh/AeshConsoleRunner.java b/aesh/src/main/java/org/aesh/AeshConsoleRunner.java index 2b045dff..efa3067d 100644 --- a/aesh/src/main/java/org/aesh/AeshConsoleRunner.java +++ b/aesh/src/main/java/org/aesh/AeshConsoleRunner.java @@ -24,6 +24,7 @@ import org.aesh.command.CommandResult; import org.aesh.command.impl.registry.AeshCommandRegistryBuilder; import org.aesh.command.invocation.CommandInvocation; +import org.aesh.command.registry.CommandRegistry; import org.aesh.command.registry.CommandRegistryException; import org.aesh.command.settings.Settings; import org.aesh.command.settings.SettingsBuilder; @@ -32,9 +33,6 @@ import org.aesh.terminal.Connection; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; /** * Use the AeshConsoleRunner when you want to easily create an interactive CLI application. @@ -42,14 +40,13 @@ * @author Ståle Pedersen */ public class AeshConsoleRunner { - private List> commands; + private AeshCommandRegistryBuilder registryBuilder; private Settings settings; private Prompt prompt; private ReadlineConsole console; private Connection connection; private AeshConsoleRunner() { - commands = new ArrayList<>(); } public static AeshConsoleRunner builder() { @@ -57,23 +54,46 @@ public static AeshConsoleRunner builder() { } public AeshConsoleRunner commands(Class... commands) { - if(commands != null) - this.commands.addAll(Arrays.asList(commands)); + if(commands != null) { + ensureRegistryBuilderInitialized(); + try { + registryBuilder.commands(commands); + } catch (CommandRegistryException e) { + throw new RuntimeException("Error when adding commands: " + e.getMessage(), e); + } + } return this; } - public AeshConsoleRunner command(Class commands) { - if(commands != null) - this.commands.add(commands); + public AeshConsoleRunner command(Class command) { + if(command != null) { + ensureRegistryBuilderInitialized(); + try { + registryBuilder.command(command); + } catch (CommandRegistryException e) { + throw new RuntimeException("Error when adding command: " + e.getMessage(), e); + } + } return this; } - public AeshConsoleRunner commands(List> commands) { - if(commands != null) - this.commands.addAll(commands); + public AeshConsoleRunner commandRegistryBuilder(AeshCommandRegistryBuilder commandRegistryBuilder) { + if(registryBuilder != null) { + throw new RuntimeException("Cannot set CommandRegistryBuilder after it has been initialized. " + + "CommandRegistryBuilder must be set before adding any commands."); + } + if(commandRegistryBuilder != null) { + this.registryBuilder = commandRegistryBuilder; + } return this; } + private void ensureRegistryBuilderInitialized() { + if(registryBuilder == null) { + registryBuilder = AeshCommandRegistryBuilder.builder(); + } + } + public AeshConsoleRunner settings(Settings settings) { if(settings != null) this.settings = settings; @@ -102,7 +122,12 @@ public AeshConsoleRunner prompt(Prompt prompt) { } public AeshConsoleRunner addExitCommand() { - commands.add(ExitCommand.class); + ensureRegistryBuilderInitialized(); + try { + registryBuilder.command(ExitCommand.class); + } catch (CommandRegistryException e) { + throw new RuntimeException("Error when adding exit command: " + e.getMessage(), e); + } return this; } @@ -127,18 +152,42 @@ public void stop() { @SuppressWarnings("unchecked") private void init() { - if(commands.isEmpty() && (settings == null || - settings.commandRegistry() == null || - settings.commandRegistry().getAllCommandNames().isEmpty())) + // Build the command registry from the builder + CommandRegistry builtRegistry = null; + if(registryBuilder != null) { + try { + builtRegistry = registryBuilder.create(); + } catch (Exception e) { + throw new RuntimeException("Error creating command registry: " + e.getMessage(), e); + } + } + + // Check if both builder and settings.commandRegistry have commands + if(builtRegistry != null && !builtRegistry.getAllCommandNames().isEmpty() && + settings != null && settings.commandRegistry() != null && + !settings.commandRegistry().getAllCommandNames().isEmpty()) { + throw new RuntimeException("Cannot define commands in both AeshConsoleRunner (via command() or commandRegistryBuilder()) " + + "and Settings.commandRegistry(). Please use only one method to specify commands."); + } + + // Determine which registry to use + CommandRegistry finalRegistry = null; + if(builtRegistry != null && !builtRegistry.getAllCommandNames().isEmpty()) { + finalRegistry = builtRegistry; + } else if(settings != null && settings.commandRegistry() != null && + !settings.commandRegistry().getAllCommandNames().isEmpty()) { + finalRegistry = settings.commandRegistry(); + } + + // Validate that we have at least one command + if(finalRegistry == null || finalRegistry.getAllCommandNames().isEmpty()) { throw new RuntimeException("No commands added, nothing to run"); + } try { if(settings == null) { - AeshCommandRegistryBuilder registryBuilder = AeshCommandRegistryBuilder.builder(); - for(Class command : commands) - registryBuilder.command(command); settings = SettingsBuilder.builder() - .commandRegistry(registryBuilder.create()) + .commandRegistry(finalRegistry) .enableAlias(false) .enableExport(false) .enableMan(false) @@ -146,16 +195,11 @@ private void init() { .connection(connection) .build(); } - //user added its own settings object, but no commands in registry - else if(!commands.isEmpty() && - (settings.commandRegistry() == null || - settings.commandRegistry().getAllCommandNames().isEmpty())) { - - AeshCommandRegistryBuilder registryBuilder = AeshCommandRegistryBuilder.builder(); - for(Class command : commands) - registryBuilder.command(command); + // User added their own settings object, but we need to add or replace the registry + else if(settings.commandRegistry() == null || + settings.commandRegistry().getAllCommandNames().isEmpty()) { SettingsBuilder settingsBuilder = new SettingsBuilder(settings) - .commandRegistry(registryBuilder.create()); + .commandRegistry(finalRegistry); if(connection != null) settingsBuilder.connection(connection); @@ -165,8 +209,8 @@ else if(!commands.isEmpty() && console = new ReadlineConsole(settings); } - catch (CommandRegistryException e) { - throw new RuntimeException("Error when adding command: "+e.getMessage()); + catch (Exception e) { + throw new RuntimeException("Error when initializing console: " + e.getMessage(), e); } } diff --git a/aesh/src/test/java/org/aesh/AeshConsoleRunnerTest.java b/aesh/src/test/java/org/aesh/AeshConsoleRunnerTest.java index 81f37768..051e4c95 100644 --- a/aesh/src/test/java/org/aesh/AeshConsoleRunnerTest.java +++ b/aesh/src/test/java/org/aesh/AeshConsoleRunnerTest.java @@ -103,6 +103,88 @@ public void testMultipleCommands() throws InterruptedException { } + @Test + @SuppressWarnings("unchecked") + public void testCommandRegistryDirect() throws Exception { + TestConnection connection = new TestConnection(); + + AeshCommandRegistryBuilder builder = AeshCommandRegistryBuilder.builder() + .command(HelloCommand.class) + .command(AeshConsoleRunner.ExitCommand.class); + + AeshConsoleRunner runner = AeshConsoleRunner.builder() + .connection(connection) + .commandRegistryBuilder(builder); + + runner.start(); + + connection.read("hello"+getLineSeparator()); + connection.assertBufferEndsWith("Hello from Aesh!"+getLineSeparator()); + connection.read("exit"+getLineSeparator()); + Thread.sleep(200); + assertTrue(connection.closed()); + } + + @Test(expected = RuntimeException.class) + @SuppressWarnings("unchecked") + public void testDuplicateCommandRegistry() throws Exception { + TestConnection connection = new TestConnection(); + + CommandRegistry registry = AeshCommandRegistryBuilder.builder() + .command(HelloCommand.class) + .create(); + + Settings + settings = SettingsBuilder.builder() + .logging(true) + .connection(connection) + .commandRegistry(registry) + .build(); + + // This should throw an exception because we're defining commands in both places + AeshConsoleRunner runner = AeshConsoleRunner.builder() + .settings(settings) + .command(Bar1Command.class); // Adding command when settings already has a non-empty registry + + runner.start(); + } + + @Test + @SuppressWarnings("unchecked") + public void testCommandRegistryBuilder() throws Exception { + TestConnection connection = new TestConnection(); + + AeshCommandRegistryBuilder builder = AeshCommandRegistryBuilder.builder() + .command(HelloCommand.class); + + AeshConsoleRunner runner = AeshConsoleRunner.builder() + .connection(connection) + .commandRegistryBuilder(builder) + .addExitCommand(); + + runner.start(); + + connection.read("hello"+getLineSeparator()); + connection.assertBufferEndsWith("Hello from Aesh!"+getLineSeparator()); + connection.read("exit"+getLineSeparator()); + Thread.sleep(200); + assertTrue(connection.closed()); + } + + @Test(expected = RuntimeException.class) + @SuppressWarnings("unchecked") + public void testCommandRegistryBuilderAfterInit() { + AeshCommandRegistryBuilder builder = AeshCommandRegistryBuilder.builder(); + + // Add a command first, which initializes the default builder + AeshConsoleRunner runner = AeshConsoleRunner.builder() + .command(HelloCommand.class); + + // This should throw an exception because the builder was already initialized + runner.commandRegistryBuilder(builder); + } + @CommandDefinition(name = "hello", description = "hello from aesh") public static class HelloCommand implements Command { diff --git a/examples/dependency-reduced-pom.xml b/examples/dependency-reduced-pom.xml index 69898a87..6b621e61 100644 --- a/examples/dependency-reduced-pom.xml +++ b/examples/dependency-reduced-pom.xml @@ -3,7 +3,7 @@ aesh-all org.aesh - 2.8.4 + 3.0-dev 4.0.0 aesh-examples