Once a configuration has been defined, the next step is to define listeners to be informed by the API of when commands, switches and arguments are passed to the application by the command line.
Commands, if present, are parsed first, followed by switches, then finally (if present) followed by arguments. If commands are present they are consumed and then remaining options and arguments passed to that command for processing; otherwise, options and arguments are treated as top-level options and arguments.
There are two types of listeners - option listeners and arguments listeners, each with a command-based version for a total of four listeners:
org.statefive.clic.OptionListener: Listen for updates to top-level command
line switches;org.statefive.clic.ArgsListener: Listen for updates to top-level arguments;org.statefive.clic.CommandOptionListener: Listen for updates for command
line switches for a specific command; and, finallyorg.statefive.clic.CommandArgsListener: Listen for updates for arguments
for a specific command.Each of these are discussed in the following sections.
To listen for updates to top-level switches, implementations must implement the
org.statefive.clic.OptionListener interface with the following method:
void option(String option, Object value)
The option will be the switch passed in from the command line without any
leading hyphens; for example, if the switch -f config.txt is passed in via the
command line then the option will be f and the value will be config.txt as
a string, unless a value type was associated with the configuration for that
option. For example if the file value type was set for the given option
configuration, then the value will be a java.io.File.
Let's look at the implementation of this, assuming that there's a short option
-f, long option --file and the value type is a string:
private String filename;
// getters omitted
public void option(String option, Object value) {
switch(option) {
case "f":
case "file":
filename = value.toString();
// or filename = (String) value;
break;
// other options omitted
}
}
If we're using the file/java.io.File value type, the code would be:
private File filename;
// getters omitted
public void option(String option, Object value) {
switch(option) {
case "f":
case "file":
filename = (File) value;
break;
// other options omitted
}
}
In both cases we can access the values of the listener by calling the get methods for the different members of the listener.
For unary switches, it's a little bit different: The value for all unary
switches will always be null. It is advised to use boolean values for such
switches. For example, let's assume there's also a unary switch short and long
option, -L and --no-limit, respectively; then the code for our application
(including the file option) would look as follows:
private File filename;
private boolean noLimit;
// getters omitted
public void option(String option, Object value) {
switch(option) {
case "f":
case "file":
filename = (File) value;
break;
case "L":
case "no-limit":
noLimit = true;
break;
// other options omitted
}
}
Finally, if global.help or global.version were defined, we simply need to
add System.exit(0) calls to the switch/case statement for the corresponding
match; by the time this reaches the listener, help or version will have been
output already. This example assumes both help and version are using the default
switch values provided by the API:
public void option(String option, Object value) {
switch(option) {
case "h":
case "help":
System.exit(0);
break;
case "v":
case "version":
System.exit(0);
break;
// other options omitted
}
}
As such, applications do not necessarily need to define argument listeners for
top-level arguments or command arguments - API callers can access arguments
directly through the API by calling Clc.getInstance().getArgs() which returns
List<String>. There's also the Clc.getInstance().getArgsValueTypes() that
returns List<Object>. The listeners are provided for extra flexibility in case
callers wish to be informed of options as they are parsed in the order they are
defined. For example callers may want to provide validation of arguments as they
are processed.
To receive top-level arguments, callers must implement the
org.statefive.clic.ArgsListener interface and implement the following method:
void argument(String name, int index, Object value);
If no argument configurations are defined then the name will be the same as
value which will be the string value of the argument. The index will be the
position of the argument, indexing starting at 0.
If argument configurations are defined, then the name will be the argument
configuration name - that is, the args.<config-name> value. The value of the
argument will be a string if no value type was defined, otherwise it will be
the type defined in the configuration.
Let's look at code for updating arguments when no argument configurations are defined. In such a case this means that the argument list can either be empty or any number of arguments.
public void argument(String name, int index, Object value) {
switch(index) {
case 0:
// perform checks on first argument
break;
}
if (index >= 5) {
// checks for arguments greater than or equals to index 5, i.e. arguments 6 and onward
}
}
For defined argument configuration, consider the following CLC declaration for an application that takes an operator and a list of doubles to perform the operation on:
args.operator.type = char
args.operator.properties = includes = +-/*^, regex = false
args.operator.length = 1
args.double-vals.type = double
The above configuration defines that the first argument will be a character for addition, subtraction, division, multiplication or power; the second configuration defines any number of non-optional (i.e. must be at least one) decimal numbers.
Now, the implementation:
private Double doubleVal = null;
private Character operator = null;
public void argument(String name, int index, Object value) {
switch (name) {
case "operator":
operator = (Character) value;
break;
case "float-vals":
if (doubleVal == null) {
doubleVal = (Double) value;
} else {
switch (operator) {
case '+':
doubleVal = doubleVal + (Double) value;
break;
case '-':
doubleVal = doubleVal - (Double) value;
break;
case '/':
doubleVal = doubleVal / (Double) value;
break;
case '*':
doubleVal = doubleVal * (Double) value;
break;
case '^':
doubleVal = Math.pow(doubleVal, (Double) value);
break;
}
}
break;
}
}