|
| 1 | +# Command Interpreter |
| 2 | + |
| 3 | +OLCUT provides a Command Interpreter that can be used for invoking or interacting |
| 4 | +with your software. It can be used as a test harness to poke and prod different |
| 5 | +parts of your code, or can be used inside a JVM that is also running other |
| 6 | +pieces of software that you'd like to monitor or in some other way interact |
| 7 | +with. It is also a great way to build simple shell-based utilities without having |
| 8 | +to make a million different main classes or command line arguments. |
| 9 | + |
| 10 | +The `CommandInterpreter` has a number of built-in commands for things like shell |
| 11 | +history, status, file redirection, running a script of commands, etc. These |
| 12 | +commands are all grouped together for convenience, meaning they'll appear |
| 13 | +together when you run "help". |
| 14 | + |
| 15 | +The gnu-readline capability in the `CommandInterpreter` is provided by the |
| 16 | +[jline3 library](https://github.com/jline/jline3). It offers native readline-style |
| 17 | +support (including history and tab completion) on the following platforms: Solaris, |
| 18 | +Linux, OS X, FreeBSD, and Windows. While OLCUT is 100% Java, JLine does require |
| 19 | +a few native bits to run the Command Interpreter properly on these platforms. |
| 20 | + |
| 21 | +## Defining commands |
| 22 | + |
| 23 | +To add your own commands to the shell you simply define and annotate methods |
| 24 | +that take a `CommandInterpreter` as their first argument and supported types |
| 25 | +as any additional arguments. Any object containing commands must implement |
| 26 | +the `CommandGroup` interface. All commands contained within the object will |
| 27 | +be put together in the same group. |
| 28 | + |
| 29 | +Defining a command looks like this |
| 30 | + |
| 31 | +```java |
| 32 | + public class Processor implements CommandGroup { |
| 33 | + protected int numProcessed = 0; |
| 34 | + |
| 35 | + public String getName() { |
| 36 | + return "Process"; |
| 37 | + } |
| 38 | + |
| 39 | + public String getDescription() { |
| 40 | + return "Commands for the Oracle Labs Sound Processor"; |
| 41 | + } |
| 42 | + |
| 43 | + @Command(usage="<fileName> - filter the given file") |
| 44 | + public String filter(CommandInterpreter ci, File file) { |
| 45 | + pipeline.filter(file); |
| 46 | + ci.out.println("Processing " + file.getName()); |
| 47 | + numProcessed++; |
| 48 | + return ""; |
| 49 | + } |
| 50 | + |
| 51 | + @Command(usage="Prints stats for this processor") |
| 52 | + public String printStats(CommandInterpreter ci) { |
| 53 | + return "numProcessed: " + numProcessed; |
| 54 | + } |
| 55 | + } |
| 56 | +``` |
| 57 | + |
| 58 | +Then you can create and start a shell with that command in your main program, |
| 59 | +perhaps after having loaded any relevant configuration. |
| 60 | + |
| 61 | +```java |
| 62 | + Processor p = new Processor(pipelineInstance); |
| 63 | + CommandInterpreter shell = new CommandInterpreter(); |
| 64 | + shell.add(p); |
| 65 | + shell.run(); |
| 66 | +``` |
| 67 | + |
| 68 | +Your main thread will block in a read/eval/print loop at this point. |
| 69 | +`CommandInterpreter` may also be run as a separate thread by invoking `start()`. |
| 70 | + |
| 71 | +Any number of parameters may be given to a `Command`. The `CommandInterpreter` will |
| 72 | +check the number provided and give appropriate error messages including the |
| 73 | +usage string in the `@Command` annotation. It will parse the arguments and convert |
| 74 | +them to the appropriate types and invoke the Command if possible. Supported |
| 75 | +argument types are: |
| 76 | + |
| 77 | +* `String` |
| 78 | +* `Integer`, `int` |
| 79 | +* `Long`, `long` |
| 80 | +* `Double`, `double` |
| 81 | +* `Float`, `float` |
| 82 | +* `Boolean`, `boolean` |
| 83 | +* `File` |
| 84 | +* `Enum` of any type |
| 85 | +* `String[]` (as the only parameter type) |
| 86 | + |
| 87 | +Note that there is a one-to-one correspondence between method names and |
| 88 | +`Commands`. No distinctions are made for method signatures with different |
| 89 | +parameters. No guarantee is made for the behavior of a shell with multiple |
| 90 | +methods that have the same name (See Layered Command Interpreter below). |
| 91 | +The `CommandInterpreter` instance that is passed in will be the currently |
| 92 | +running shell. It is a best practice to use the shell's output (`ci.out`) for writing |
| 93 | +output so that output can be easily redirected to any output stream the |
| 94 | +shell may be embedded in. |
| 95 | + |
| 96 | +## Optional parameters |
| 97 | + |
| 98 | +For more flexibility in your commands, you may specify that trailing method |
| 99 | +parameters be optional. Any optional parameter must have a default value |
| 100 | +assigned to it, expressed as a string that would be entered on the command |
| 101 | +line. The default value may be the text "null" if you choose, although this |
| 102 | +should only be used with object/reference types and not base types. Optional |
| 103 | +parameters are tagged with the `@Optional` annotation. The above filter method |
| 104 | +could take an optional parameter to specify where the output of the pipeline |
| 105 | +should go: |
| 106 | + |
| 107 | +```java |
| 108 | + @Command(usage="<inFile> [<outFile>] - run this filter on a file") |
| 109 | + public String filter(CommandInterpreter ci, |
| 110 | + File inFile, |
| 111 | + @Optional(val="/tmp/output.au", File outFile) { |
| 112 | + pipeline.filter(inFile, outFile); |
| 113 | + return ""; |
| 114 | + } |
| 115 | +``` |
| 116 | + |
| 117 | +## Tab Completion |
| 118 | + |
| 119 | +Commands added to a `CommandInterpreter` may provide tab completion for each of |
| 120 | +their arguments. A separate method may be used to specify how each argument should |
| 121 | +be completed. The method returns an array of `Completer` objects (described below), |
| 122 | +one per argument, and takes no parameters. These methods may either be associated |
| 123 | +in the `@Command` annotation (described below), or can follow a naming convention |
| 124 | +to be paired with the method they provide completers for. The following example |
| 125 | +method would be used to find completers for the `filter` command: |
| 126 | + |
| 127 | +```java |
| 128 | + public Completer[] filterCompleters() { |
| 129 | + return new Completer[]{ |
| 130 | + new FileNameCompleter(), |
| 131 | + new FileNameCompleter() |
| 132 | + }; |
| 133 | + } |
| 134 | +``` |
| 135 | + |
| 136 | +In this example, an array of completers is returned, one per argument. This |
| 137 | +could actually be simplified because the behavior of the completers is to |
| 138 | +reuse the last completer in the array for all further arguments. Simply |
| 139 | +providing a single `FileNameCompleter` would work the same for this method. To |
| 140 | +prevent tab-completion for a particular parameter, or to prevent the last |
| 141 | +completer from repeating, place a `NullCompleter` in the array in the appropriate |
| 142 | +spot. |
| 143 | + |
| 144 | +The following types of completers are available in OLCUT. These are provided |
| 145 | +by the [jline library](https://github.com/jline/jline3). |
| 146 | + |
| 147 | +* `FileNameCompleter` - Completes file names starting in the PWD |
| 148 | +* `StringsCompleter` - Pass it a list of strings or a Supplier to give it the values it should complete to |
| 149 | +* `EnumCompleter` - Completes with values from a specified Enum type |
| 150 | +* `NullCompleter` - Does not complete with anything. |
| 151 | +* `IntCompleter` - Just kidding. |
| 152 | + |
| 153 | +If you wish to reuse a method that generates completers, you can use an |
| 154 | +attribute of the `@Command` annotation to specify the name of the completer method |
| 155 | +to use instead of relying on the `xxxCompleters` convention. For example, |
| 156 | +if multiple commands take a single File parameter, you might make a method such |
| 157 | +as this one: |
| 158 | + |
| 159 | +```java |
| 160 | + public Completers[] fileCompleter() { |
| 161 | + return new Completer[]{ |
| 162 | + new FileNameCompleter(), |
| 163 | + new NullCompleter() |
| 164 | + } |
| 165 | + } |
| 166 | +``` |
| 167 | + |
| 168 | +Then when annotating a method that takes a `File` as its parameter, you would |
| 169 | +specify: |
| 170 | + |
| 171 | +```java |
| 172 | + @Command(usage="Processes a single file", |
| 173 | + completers="fileCompleter") |
| 174 | +``` |
| 175 | + |
| 176 | +Multiple annotated commands may share the same completer method in this way. |
| 177 | + |
| 178 | +## Manual argument processing |
| 179 | + |
| 180 | +In some cases, taking the supported types as arguments doesn't provide enough |
| 181 | +flexibility for the way you want your command to work. In this case, you can |
| 182 | +instead have your method use `String[]` as its second parameter (after the |
| 183 | +`CommandInterpreter`) and all arguments provided in the shell will be passed |
| 184 | +through verbatim to allow you to do your own argument parsing. This is |
| 185 | +particularly useful if you want to support a varargs-style syntax. You may |
| 186 | +still provide `Completer`s even if the arguments are not otherwise specified. |
| 187 | +
|
| 188 | +## Layered Command Interpreter (Mixing in more commands) |
| 189 | +
|
| 190 | +Sometimes when building a large shell, you may have multiple `CommandGroup`s that |
| 191 | +provide commands with the same name. To avoid namespace collisions, you can add |
| 192 | +your commands to a layer, then add the layer to the top-level shell. |
| 193 | +
|
| 194 | +```java |
| 195 | + CommandInterpreter shell = new CommandInterpreter(); |
| 196 | + LayeredCommandInterpreter lci = new LayeredCommandInterpreter("pipe", "Pipeline Commands"); |
| 197 | + lci.add(processor); |
| 198 | + shell.add(lci); |
| 199 | +``` |
| 200 | +
|
| 201 | +To avoid ambiguity, all commands defined in `processor` can now be referred to |
| 202 | +with a ".pipe" extension (e.g. `filter.pipe`), but may also be used without any |
| 203 | +extension when there is no conflict. |
0 commit comments