summaryrefslogtreecommitdiff
path: root/BJC-Utils2/src/main/java/bjc/utils/cli
diff options
context:
space:
mode:
Diffstat (limited to 'BJC-Utils2/src/main/java/bjc/utils/cli')
-rw-r--r--BJC-Utils2/src/main/java/bjc/utils/cli/CLICommander.java60
-rw-r--r--BJC-Utils2/src/main/java/bjc/utils/cli/GeneralCommandMode.java246
-rw-r--r--BJC-Utils2/src/main/java/bjc/utils/cli/ICommandHandler.java22
-rw-r--r--BJC-Utils2/src/main/java/bjc/utils/cli/ICommandMode.java68
-rw-r--r--BJC-Utils2/src/main/java/bjc/utils/cli/package-info.java7
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