diff options
Diffstat (limited to 'BJC-Utils2/src/main/java/bjc/utils/cli')
5 files changed, 403 insertions, 0 deletions
diff --git a/BJC-Utils2/src/main/java/bjc/utils/cli/CLICommander.java b/BJC-Utils2/src/main/java/bjc/utils/cli/CLICommander.java new file mode 100644 index 0000000..f48f08e --- /dev/null +++ b/BJC-Utils2/src/main/java/bjc/utils/cli/CLICommander.java @@ -0,0 +1,60 @@ +package bjc.utils.cli; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Runs a CLI interface from the provided set of streams + * + * @author ben + * + */ +public class CLICommander { + private InputStream input; + private OutputStream output; + private OutputStream error; + private ICommandMode initialMode; + + /** + * Create a new CLI interface powered by streams + * + * @param input + * The stream to get user input from + * @param output + * The stream to send user output to + * @param error + * The stream to send error messages to + */ + public CLICommander(InputStream input, OutputStream output, + OutputStream error) { + if (input == null) { + throw new NullPointerException( + "Input stream must not be null"); + } else if (output == null) { + throw new NullPointerException( + "Output stream must not be null"); + } else if (error == null) { + throw new NullPointerException( + "Error stream must not be null"); + } + + this.input = input; + this.output = output; + this.error = error; + } + + /** + * Set the initial command mode to use + * + * @param initialMode + * The initial command mode to use + */ + public void setInitialCommandMode(ICommandMode initialMode) { + if (initialMode == null) { + throw new NullPointerException( + "Initial mode must be non-zero"); + } + + this.initialMode = initialMode; + } +} diff --git a/BJC-Utils2/src/main/java/bjc/utils/cli/GeneralCommandMode.java b/BJC-Utils2/src/main/java/bjc/utils/cli/GeneralCommandMode.java new file mode 100644 index 0000000..4a41c1c --- /dev/null +++ b/BJC-Utils2/src/main/java/bjc/utils/cli/GeneralCommandMode.java @@ -0,0 +1,246 @@ +package bjc.utils.cli; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * A general command mode, with a customizable set of commands + * + * There is a small set of commands which is handled by default. The first + * is 'list', which lists all the commands the user can input. The second + * is 'alias', which allows the user to bind a new name to a command + * + * @author ben + * + */ +public class GeneralCommandMode implements ICommandMode { + private Map<String, ICommandHandler> commandHandlers; + private String customPrompt; + + private Map<String, ICommandHandler> defaultHandlers; + + private Consumer<String> errorOutput; + private String modeName; + + private Consumer<String> normalOutput; + + private BiConsumer<String, String[]> unknownCommandHandler; + + /** + * Create a new general command mode + * + * @param normalOutput + * The function to use for normal output + * @param errorOutput + * The function to use for error output + */ + public GeneralCommandMode(Consumer<String> normalOutput, + Consumer<String> errorOutput) { + this.normalOutput = normalOutput; + this.errorOutput = errorOutput; + + commandHandlers = new HashMap<>(); + defaultHandlers = new HashMap<>(); + + defaultHandlers.put("list", (args) -> { + listCommands(); + + return this; + }); + + defaultHandlers.put("alias", (args) -> { + aliasCommands(args); + + return this; + }); + } + + /** + * Add an alias to an existing command + * + * @param commandName + * The name of the command to add an alias for + * @param aliasName + * The new alias for the command + * + * @throws IllegalArgumentException + * if the specified command doesn't have a bound handler, + * or if the alias name already has a bound value + */ + public void addCommandAlias(String commandName, String aliasName) { + if (commandName == null) { + throw new NullPointerException( + "Command name must not be null"); + } else if (aliasName == null) { + throw new NullPointerException("Alias name must not be null"); + } else if (!commandHandlers.containsKey(commandName)) { + throw new IllegalArgumentException( + "Cannot alias non-existant command '" + commandName + + "'"); + } else if (commandHandlers.containsKey(aliasName)) { + throw new IllegalArgumentException("Cannot bind alias '" + + aliasName + "' to a command with a bound handler"); + } else { + commandHandlers.put(aliasName, + commandHandlers.get(commandName)); + } + } + + /** + * Add a command to this command mode + * + * @param command + * The command to add + * @param handler + * The handler to use for the specified command + * + * @throws IllegalArgumentException + * if the specified command already has a handler + * registered + */ + public void addCommandHandler(String command, + ICommandHandler handler) { + if (command == null) { + throw new NullPointerException("Command must not be null"); + } else if (handler == null) { + throw new NullPointerException("Handler must not be null"); + } else if (canHandleCommand(command)) { + throw new IllegalArgumentException("Command " + command + + " already has a handler registered"); + } else { + commandHandlers.put(command, handler); + } + } + + private void aliasCommands(String[] args) { + if (args.length != 2) { + errorOutput.accept("ERROR: Alias requires two arguments. " + + "The command name, and the alias for that command"); + } else { + String commandName = args[0]; + String aliasName = args[1]; + + if (!canHandleCommand(commandName)) { + errorOutput.accept("ERROR: '" + commandName + + "' is not a valid command."); + } else if (canHandleCommand(aliasName)) { + errorOutput.accept("ERROR: Cannot overwrite command '" + + aliasName + "'"); + } else { + addCommandAlias(commandName, aliasName); + } + } + } + + @Override + public boolean canHandleCommand(String command) { + return commandHandlers.containsKey(command) + || defaultHandlers.containsKey(command); + } + + @Override + public String getCustomPrompt() { + if (customPrompt != null) { + return customPrompt; + } + + return ICommandMode.super.getCustomPrompt(); + } + + @Override + public String getName() { + if (modeName != null) { + return modeName; + } + + return ICommandMode.super.getName(); + } + + private void listCommands() { + normalOutput.accept( + "The available commands for this mode are as follows:\n"); + + commandHandlers.keySet().forEach((commandName) -> { + normalOutput.accept("\t" + commandName); + }); + + normalOutput.accept( + "\nThe following commands are available in all modes:\n"); + + defaultHandlers.keySet().forEach((commandName) -> { + normalOutput.accept("\t" + commandName); + }); + + normalOutput.accept("\n"); + } + + @Override + public ICommandMode processCommand(String command, String[] args) { + normalOutput.accept("\n"); + + if (defaultHandlers.containsKey(command)) { + return defaultHandlers.get(command).handle(args); + } else if (commandHandlers.containsKey(command)) { + return commandHandlers.get(command).handle(args); + } else { + if (unknownCommandHandler == null) { + throw new UnsupportedOperationException( + "Command " + command + " is invalid."); + } else if (args != null) { + errorOutput.accept("ERROR: Unrecognized command " + command + + String.join(" ", args)); + } else { + errorOutput + .accept("ERROR: Unrecognized command " + command); + } + + unknownCommandHandler.accept(command, args); + } + + return this; + } + + /** + * Set the custom prompt for this mode + * + * @param prompt + * The custom prompt for this mode, or null to disable the + * custom prompt + */ + public void setCustomPrompt(String prompt) { + customPrompt = prompt; + } + + /** + * Set the name of this mode + * + * @param name + * The desired name of this mode, or null to use the default + * name + */ + public void setModeName(String name) { + modeName = name; + } + + /** + * Set the handler to use for unknown commands + * + * @param handler + * The handler to use for unknown commands + */ + public void setUnknownCommandHandler( + BiConsumer<String, String[]> handler) { + if (handler == null) { + throw new NullPointerException("Handler must not be null"); + } + + unknownCommandHandler = handler; + } + + @Override + public boolean useCustomPrompt() { + return customPrompt != null; + } +}
\ No newline at end of file diff --git a/BJC-Utils2/src/main/java/bjc/utils/cli/ICommandHandler.java b/BJC-Utils2/src/main/java/bjc/utils/cli/ICommandHandler.java new file mode 100644 index 0000000..f806676 --- /dev/null +++ b/BJC-Utils2/src/main/java/bjc/utils/cli/ICommandHandler.java @@ -0,0 +1,22 @@ +package bjc.utils.cli; + +import java.util.function.Function; + +/** + * A handler for a command + * + * @author ben + * + */ +public interface ICommandHandler extends Function<String[], ICommandMode> { + /** + * Handle the command this handler handles + * + * @param args + * The arguments for this command + * @return The command mode to go to after this command + */ + public default ICommandMode handle(String[] args) { + return this.apply(args); + } +} diff --git a/BJC-Utils2/src/main/java/bjc/utils/cli/ICommandMode.java b/BJC-Utils2/src/main/java/bjc/utils/cli/ICommandMode.java new file mode 100644 index 0000000..5208657 --- /dev/null +++ b/BJC-Utils2/src/main/java/bjc/utils/cli/ICommandMode.java @@ -0,0 +1,68 @@ +package bjc.utils.cli; + +/** + * A mode for determining the commands that are valid to enter, and then + * handling those commands + * + * @author ben + * + */ +public interface ICommandMode { + /** + * Process a command in this mode + * + * @param command + * The command to process + * @param args + * A list of arguments to the command + * @return The command mode to use for the next command. Defaults to + * returning this, and doing nothing else + */ + public default ICommandMode processCommand(String command, + String[] args) { + return this; + }; + + /** + * Check to see if this mode can handle the specified command + * + * @param command + * The command to check + * @return Whether or not this mode can handle the command. It is + * assumed not by default + */ + public default boolean canHandleCommand(String command) { + return false; + } + + /** + * Get the name of this command mode + * + * @return The name of this command mode, which is "crawler" by default + */ + public default String getName() { + return "crawler"; + } + + /** + * Check if this mode uses a custom prompt + * + * @return Whether or not this mode uses a custom prompt + */ + public default boolean useCustomPrompt() { + return false; + } + + /** + * Get the custom prompt for this mode + * + * @return the custom prompt for this mode + * + * @throws UnsupportedOperationException + * if this mode doesn't support a custom prompt + */ + public default String getCustomPrompt() { + throw new UnsupportedOperationException( + "This mode doesn't support a custom prompt"); + } +} diff --git a/BJC-Utils2/src/main/java/bjc/utils/cli/package-info.java b/BJC-Utils2/src/main/java/bjc/utils/cli/package-info.java new file mode 100644 index 0000000..a87aa24 --- /dev/null +++ b/BJC-Utils2/src/main/java/bjc/utils/cli/package-info.java @@ -0,0 +1,7 @@ +/** + * Holds classes for easier CLI design + * + * @author ben + * + */ +package bjc.utils.cli;
\ No newline at end of file |
