Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 76 additions & 32 deletions aesh/src/main/java/org/aesh/AeshConsoleRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,48 +33,67 @@
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.
*
* @author <a href="mailto:stalep@gmail.com">Ståle Pedersen</a>
*/
public class AeshConsoleRunner {
private List<Class<? extends Command>> commands;
private AeshCommandRegistryBuilder<CommandInvocation> registryBuilder;
private Settings settings;
private Prompt prompt;
private ReadlineConsole console;
private Connection connection;

private AeshConsoleRunner() {
commands = new ArrayList<>();
}

public static AeshConsoleRunner builder() {
return new AeshConsoleRunner();
}

public AeshConsoleRunner commands(Class<? extends Command>... 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<? extends Command> commands) {
if(commands != null)
this.commands.add(commands);
public AeshConsoleRunner command(Class<? extends Command> 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<Class<Command>> commands) {
if(commands != null)
this.commands.addAll(commands);
public AeshConsoleRunner commandRegistryBuilder(AeshCommandRegistryBuilder<CommandInvocation> 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;
Expand Down Expand Up @@ -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;
}

Expand All @@ -127,35 +152,54 @@ 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<CommandInvocation> 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<CommandInvocation> 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<? extends Command> command : commands)
registryBuilder.command(command);
settings = SettingsBuilder.builder()
.commandRegistry(registryBuilder.create())
.commandRegistry(finalRegistry)
.enableAlias(false)
.enableExport(false)
.enableMan(false)
.persistHistory(false)
.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<? extends Command> 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);
Expand All @@ -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);
}
}

Expand Down
82 changes: 82 additions & 0 deletions aesh/src/test/java/org/aesh/AeshConsoleRunnerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,88 @@ public void testMultipleCommands() throws InterruptedException {

}

@Test
@SuppressWarnings("unchecked")
public void testCommandRegistryDirect() throws Exception {
TestConnection connection = new TestConnection();

AeshCommandRegistryBuilder<CommandInvocation> builder = AeshCommandRegistryBuilder.<CommandInvocation>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<CommandInvocation> registry = AeshCommandRegistryBuilder.<CommandInvocation>builder()
.command(HelloCommand.class)
.create();

Settings<CommandInvocation, ConverterInvocation, CompleterInvocation, ValidatorInvocation,
OptionActivator, CommandActivator>
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<CommandInvocation> builder = AeshCommandRegistryBuilder.<CommandInvocation>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<CommandInvocation> builder = AeshCommandRegistryBuilder.<CommandInvocation>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 {
Expand Down
2 changes: 1 addition & 1 deletion examples/dependency-reduced-pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<artifactId>aesh-all</artifactId>
<groupId>org.aesh</groupId>
<version>2.8.4</version>
<version>3.0-dev</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>aesh-examples</artifactId>
Expand Down