Let's take a properties file that we'll use in our application and then add library code to load the properties in the file and then override those properties on the command line.
The finished examples for this section are kept in the examples/ directory and
are not built by default; there are three sets of examples:
01-apache-properties: Apache commons configuration implementation;02-java-properties: Java utils properties implementation; and03-com-typesafe-properties: com.typesafe HOCON implementationAssuming the main API has been built, the first two can be built by changing
directory into the examples/ directory and running mvn clean install.
To build the com.typesafe implementation, first build the library for
com.typesafe implementation by changing directory into
implementations/com.typesafe/ and running mvn clean install. Then change
directory into examples/03-com-typesafe-properties/ and run mvn clean install.
The binaries to run the examples are as follows and kept in the
target/appassembler/bin/ directory with the application names as follows:
| Directory | Applciation Name |
|---|---|
examples/01-apache-properties |
apache-props-demo |
examples/02-java-properties |
java-props-demo |
examples/03-com-typesafe-properties |
com-typesafe-props-demo |
The listed names are for the non-Windows binaries; the Windows quivalents will
have the file extension .bat.
First, let's start by defining some properties for an imagined application for processing directories of media files and uploading the results to a server on a specific port using a defined protocol:
host.ip = localhost
host.port = 1234
host.protocol = https
strip.exif = false
file.mimeTypes = text/html, media/jpg
file.extensions = mp3, jpeg
in.dir = media/processing-in
out.dir = media/processing-out
The properties will be packaged into the application directly (using Maven this
is in src/main/resources/) - while this means that the file (and therefore the
properties) - cannot be modified directly, this isn't an issue - we're going to
override the properties using values overridden from the command line.
Next we need to write our actual code within the body of the constructor
method - in this example, the main class we're calling from is named
ApachePropertiesDemo, using Apache commons-configuration2:
public static void main(String[] args) {
new ApachePropertiesDemo(args);
}
public ApachePropertiesDemo(String[] args) {
try {
PropertiesBuilder<Configuration> builder = new PropertiesConfigurationBuilder();
builder.addProperties(ApachePropertiesDemo.class.getResourceAsStream("/cliapp.props"));
Configuration props = builder.build(args);
} catch (Exception ex) {
System.err.println(ex.getMessage());
}
}
The above code is relatively simple. Starting wtih the first line within the
try declaration, which creates an Apache Configuration properties builder.
The second line adds the properties from our application. Finallly, on the third
line, we call the builder to build with command line arguments passed in the the
main(String[] args) method and obtain the converted properties.
There's still a bit more code to add - accessing the properties - but we'll leave that for the moment and continue the walk-through.
We can easily do the same but using Java properties instead of
commons-configuration2 properties by replacing the code within the try with
the following code - the main class in this example is the JavaPropertiesDemo
class:
PropertiesBuilder<Properties> builder = new JavaPropertiesBuilder();
builder.addProperties(JavaPropertiesDemo.class.getResourceAsStream("/cliapp.props"));
Properties props = builder.build(args);
Note that the com.typesafe implementation named ComTypeSafePropertiesDemo is
omitted from this section.
The addProperties(InputStream) call can be called multiple times in order to
load properties from several different sources; in addition there's also the
addProperties(File) API call to load properties directly from a file rather
than an input stream. Both calls can be intermixed and called in any order for
any number of properties files or streams.
Before overriding properties using command line arguments, let's invoke the
baked-in help provided by the API by passing in a single argument: --help.
The following output is produced:
usage: Default generated property help.
Auto-generated content.
--file-extensions <arg> Overrides property 'file.extensions', default
value 'mp3, jpeg'
--file-mimeTypes <arg> Overrides property 'file.mimeTypes', default
value 'text/html, media/jpg'
--help Print this help then exit.
--host-ip <arg> Overrides property 'host.ip', default value
'localhost'
--host-port <arg> Overrides property 'host.port', default value
'1234'
--host-protocol <arg> Overrides property 'host.protocol', default
value 'https'
--strip-exif <arg> Overrides property 'strip.exif', default
value 'false'
End of auto-generated content.
Notice that the switches to override any given property are the same as the
property name (ignoring the leading double-hyphen) but with non-alphanumeric
characters replaced with hyphens. For example to override the host.protocol
property, we supply the command line switch --host-protocol.
Also note that only long options are displayed - properties-based command line arguments, out of the box, do not support short options since the maximmum number of short options is limited to the upper- and lowercase characters and numbers, and properties files could easily run out of characters. However, it is possible to override this behaviour to be able to add short options, as well as to be able to use short options only. It is also possible to override the long option names (especially useful when property names are very long). Overriding property switch values is defined below.
All we need to do now is add the code to print the properties out, following the
call for the builder to build(args) - which is illustrative in this example in
order to show the output:
if (!Clc.getInstance().isParseArgs()) {
System.exit(0);
} else {
System.out.println("IP address : " + props.getString("host.ip"));
System.out.println("Port : " + props.getString("host.port"));
System.out.println("Protocol : " + props.getString("host.protocol"));
System.out.println("MIME type : " + props.getString("file.mimeTypes"));
System.out.println("extensions : " + props.getString("file.extensions"));
System.out.println("Strip EXIF : " + props.getString("strip.exit"));
System.out.println("In Dir : " + props.getString("in.dir"));
System.out.println("Out Dir : " + props.getString("out.dir"));
}
The check for Clc.getInstance().isParseArgs() is a way of determining if the
default help was invoked. When help is invoked, there's no point continuing and
thus the call to System.exit(0). Otherwise, the main application code resides
in the else statement, where for any other application would contain the main
code to process the options and arguments as required.
Likewise to print the property values of the Java properties version, we'd use:
if (!Clc.getInstance().isParseArgs()) {
System.exit(0);
} else {
System.out.println("IP address : " + props.getProperty("host.ip"));
System.out.println("Port : " + props.getProperty("host.port"));
System.out.println("Protocol : " + props.getProperty("host.protocol"));
System.out.println("MIME type : " + props.getProperty("file.mimeTypes"));
System.out.println("extensions : " + props.getProperty("file.extensions"));
System.out.println("Strip EXIF : " + props.getProperty("strip.exif"));
System.out.println("In Dir : " + props.getProperty("in.dir"));
System.out.println("Out Dir : " + props.getProperty("out.dir"));
}
Now let's run the application with the following arguments:
--host-ip 192.168.1.15 --host-port 8080 --file-extensions mp3,avi,ogg --in-dir audio/mp3
Once we run the application, we get the following output - notice how the properties that have been overridden by our command line arguments have been replaced:
IP address : 192.168.1.15
Port : 8080
Protocol : https
MIME type : text/html, media/jpg
extensions : mp3,avi,ogg
Strip EXIF : false
In Dir : audio/mp3
Out Dir : media/processing-out
The help output is intended to be terse and only outline what properties are available to override. However, we can include a CLC configuration to override the help output, as well as modify other elements of the configuration - dealt with in a different section, below.
Under the hood, although you don't see it for both the Apache properties and Java properties, the API is generating a CLC - command line configuration, then processing this against the arguments passed in and returning the overridden properties. The CLC format is highly flexible and enables callers of the API to insert custom CLC entries in order to do things such as:
<arg>
values in the above output. For example we can ensure that rather than display
--host-port <arg> in the help output, --host-port <port> is rendered,
instead. We can also override the default description.So far, all arguments have been Java Strings. The following subsections
outline different ways of manipulating the API to enable callers to change the
value types of arguments and how to add custom CLC entries, with minimal effort,
to change how the application can work with command line arguments.
The API offers the ability, when parsing properties, to attempt to determine what underlying types the properties are. Typically this is restricted to simple Java primitive types such as whole numbers, decimal-place numbers and booleans; utltimately, however, it is dependant on the underlying properties implementation. Further customisation of other types is available using the CLC format directly, discussed further down; we'll just focus on using the API directly for now to infer underlying types.
Both Apache configuration properties and Java properties offer the ability to infer types according to the following rules:
ints;floats;
andtrue or false, then properties that
match this will be treated as Java booleans.This can be achieved in-code by adding the following to our properties builder
prior to the call to build(args):
builder.withTypeInferralConfig(new TypeInferralConfigBuilder()
.withInferTypes().build());
This will ensure all whole numbers are treated as ints, real numbers are
treated as floats, and values that are true or false be treated as
booleans.
It's worth noting that prior to the call to inferring types, the generated CLC
for the host.port property looks as follows:
option.host-port.opts=host-port
option.host-port.hasArg=true
option.host-port.description=Overrides property 'host.port', default value '1234'
After inferring types, the generated CLC looks like so:
option.host-port.opts=host-port
option.host-port.hasArg=true
option.host-port.description=Overrides property 'host.port', default value '1234'
option.host-port.type=int
If we wanted to look at the content generated in order to parse arguments into
properties, the configuration data can be viewed by changing the line where the
build(args) is called with the following line:
System.out.println(builder.buildConfigurationData());
The above code will print out the generated CLC data that callers do not see
when invoking the build(String[]) method.
There's also another way to print out CLC data, using the cli-gen-clc command,
from the tools/cli-gen-clc/ directory. Assuming that cli-gen-clc is in the
path, from the root directory of the project, run:
cli-gen-clc --infer-types examples/01-apache-properties/src/main/resources/cliapp.props
The above command once executed will print out the generated CLC content that is being created when we invoke the API.
Type inference allows setting a default type for whole and decimal numbers, too. To override what type whole numbers are assigned we can call:
builder.withTypeInferralConfig(new TypeInferralConfigBuilder()
.withNaturalNumbersAs(<type>).build());
… Where <type> can be one of byte, short, int, long or biginteger.
Likewise to override what type real numbers are assigned we can call:
builder.withTypeInferralConfig(new TypeInferralConfigBuilder()
.withRealNumbersAs(<type>).build());
… Where <type> can be one of float, double or bigdecimal.
This can be seen as entirely desirable with regard to Java properties which are,
by default, always treated as Java Strings. By constrast, the Apache
properties library offers several different calls to get different types from
read properties; for example, when accessing the host.port property, we could
call:
System.out.println("Port : " + props.getInt("host.port"));
However, there's a hidden benefit to using type inferrence: It forces callers to also have to use the same inferred type when overriding property values, acting as a very simple for of error checking.
For example the host.port has the value 1234; by using type inference, if we
pass in (for example) --host-port fifty-five, then the following error is
produced:
Error: option host-port: Invalid integer value: 'fifty-five'.
For boolean properties the API offers the ability, using type inference, to
change false property values to unary switches; rather than setting (for
example) a false value to true via a binary switch, we can either keep the
value as false or by supplying the unary switch set it to true.
Consider the property strip.exif from the defined properties, above. By adding
the following to the API:
TypeInferralConfigBuilder typeInferral = new TypeInferralConfigBuilder();
builder.withTypeInferralConfig(typeInferral
.withInferTypes().withFalseAsUnarySwitch().build());
… And running --help again, the following output is produced for the
strip.exif property (trimmed for brevity):
--strip-exif Overrides property 'strip.exif', default value
'false'
Notice how the help output no longer supplies the <arg> value: That's because
the property is now treated as a unary switch. If the switch isn't supplied on
the command line, the property value will be a boolean with with the value
false; if it is supplied, the property value will be set to the boolean
value true.
Type inferrence is a simple but effective method to convert underlying string-based properties to more useful data types.
The next section details how more options can be added to a custom overrides file to take advantage of the CLC API.
In this section we continue with the properties-to-command line example above and outline how custom CLC entries can be added to an application.
As well as using type inference as described above to determine value types to arguments, custom CLC entries can be added to a CLC file that will override and add to the properties already read in. The two methods - type inferrence and adding custom CLC entries - are not mutually exclusive and can be mixed and matched as required.
The API offers the ability to load additional CLC properties that will be
injected into the generated content such that callers can add to the basic
default definitions. In the above example this would enable callers to add extra
option.<config-name>.<option> = <value> entries.
Furthermore not only can the command line options be added to, but also the global configuration that contains properties for updating help and help formatting (among other things).
First though, let's add some CLC entries to do the following:
To do so, we need to create a new file - let's call it overrides.clc - with
the following contents:
global.help.command.usage = ${manifest:app-name} [options...] <files...>
global.help.command.header = Process files in the given input directory and \
move them to the specified output directory.
global.help.command.footer = See some random URL for information
The global.help.command.usage will replace the top-section of the help output
(that is, the usage section). global.help.command.header and
global.help.command.footer will replace the header and footer text,
respectively.
The ${manifest:app-name} declaration will substitute in the value
within the manifest file for the manifest key app-name. An error will
be created if the manifest entry is missing. When the Apache properties example
is run, the following output will be produced for the usage line of the help
output:
usage: apache-props-demo [options] <files...>
Likewise, the Java properties example will output the following:
usage: java-props-demo [options] <files...>
Notice that the global.help.command.header is split onto two lines for
readability. Many of the configuration options can be treated this way. When
defining multiline configurations, all trailing whitespace before the closing
backslash will be preserved, and succeeding lines will have leading spaces
stripped; the lines will then be joined together. Escaping lines in such a way
is not mandatory and only used to aid readability, as stated previously.
We'll actually place the overrides.clc files at the same level as the
properties file so it will be packaged into the JAR file at build time. All we
need to do is add the following line after the call to create the builder but
before the call to build(String[]), and rebuild the sources:
builder.withConfiguration(ApachePropertiesDemo.class.getResourceAsStream("/overrides.clc"));
Now, let's run the --help command again and observe the output for the Apache
configuration properties demo:
usage: apache-props-demo [options] <in-dir> <out-dir>
Process files in the given input directory and move them to the specified
output directory.
--file-extensions <arg> Overrides property 'file.extensions', default
value 'mp3, jpeg'
--file-mimeTypes <arg> Overrides property 'file.mimeTypes', default
value 'text/html, media/jpg'
--help Print this help then exit.
--host-ip <arg> Overrides property 'host.ip', default value
'localhost'
--host-port <arg> Overrides property 'host.port', default value
'1234'
--host-protocol <arg> Overrides property 'host.protocol', default
value 'https'
--in-dir <arg> Overrides property 'in.dir', default value
'media/processing-in'
--out-dir <arg> Overrides property 'out.dir', default value
'media/processing-out'
--strip-exif Overrides property 'strip.exif', default
value 'false'
See some random URL for information
Notice how, with just three custom CLC entries, we've now converted the help output to be a little (but not much) more useful.
We can also override the property descriptions and argument names (the latter of
which default to <arg>); let's do that for one of the properties -
file.mimeTypes. To override any property description for the individual help
output, we need to add a CLC entry, however we must use the converted name (same
as the command line switch without the -- prefix). Remember, the command
switch names are the same as the property names with all non-alphanumeric
characters converted to hyphens. So if we wish to add an entry for the
file.mimeTypes property, we need to use the name file-mimeTypes.
Add the following entries to enable custom output for the file.mimeTypes
property:
option.file-mimeTypes.argName=mimeTypes...
option.file-mimeTypes.description=MIME types to consider; files that do not \
match any of the given MIME types will be left in place. Separate multiple \
MIME types with commas; if there are spaces in the list, surround the \
arguments with double quotes.
… And observe the output from running --help (trimmed for brevity):
--file-mimeTypes <mimeTypes...> MIME types to consider; files that do
not match any of the given MIME types
will be left in place. Separate
multiple MIME types with commas; if
there are spaces in the list,
surround the arguments with double
quotes.
There are other help-based options (detailed in the CLC format section below),
not detailed here that enable formatting the resultant output so that it has
wider columns etc. when invoking --help.
It's the exception rather than the rule for command line applications not to provide a version. The CLC API enables versioning out of the box for properties and is very simple to do.
To add versioning, simply call the builder with withVersion(). This adds the
command line switch --version to the application, the version being determined
according to the following rules:
version property in the properties, that value will be
used; orImplementation-Version value, that value
will be used.When help is invoked, the version will be presented as follows (trimmed from the other options for brevity):
--version Print version then exit.
With only the Implementation-Version present the output will be rather terse,
for example
apache-props-demo --version
1.0
We can make use of manifest entries and adding in a version property to the properties file, for example:
version = ${manifest:app-name}, version ${manifest:Implementation-Version}
The output when version is invoked will now be a little more verbose:
apache-props-demo --version
apache-props-demo, version 1.1
If the properties already has a version that is used for a different purpose,
the withVersion(String propertyVersionKey) call can be used. This will use the
specified property converted to a command line value. For example, calling
withVersion("app.version") will enable the version to be called from the
command line via the --app-version switch.
Next, there are a few options that can be converted to non-string types. Using
type inference we've already converted host.port to an integer. So let's add
some extra error checking to make sure users enter a valid port number. Under
the hood the library converts all inferred types to a value type - a
non-string based value. Almost all value types have properties associated with
them to enable out-of-the-box error checking, rather than having to do it
ourselves.
Using type inferrence we already know that the host.port property has been
inferred to be an int. We can add int-based properties to override any CLI
argument by adding the following to our overrides.clc file:
option.<config-name>.properties = <properties...>
To override the properties for host.port we can add the following - we can
supply either a min, max, or both together, separated by a comma (order
doesn't matter):
option.host-port.properties = min = 1025, max = 65535
min and max are numeric value type properties that can be applied to all
types that are considered numbers. They are both optional and only one of them
needs to be supplied, if chosen. This enables callers to either add a minimum,
maximum or both to ensure correct values when the application is run.
Defining a value that is less than the specified minimum, we'll get the
following error when specifying --host-port 100:
Error: option host-port: 100 is less than specified minimum: 1025
Now let's continue by converting the in.dir and out.dir properties to be
directories. To do so, add the following entries to the overrides.clc file:
option.in-dir.type = dir
option.in-dir.properties = dirType=exists
option.out-dir.type=dir
option.out-dir.properties = dirType=exists
The property defined on each directory type, dirType=exists, means the API
will check that the specified directory exists, reducing the amount of code we
need to generate - for example, specifying --in-dir as an invalid directory
yields the following example output for a non-existant directory named
audio-output-dir/:
Error: option in-dir: Specified directory audio-output-dir does not exist.
We can also take advantage of the list value type since two of the properties
contain comma-separated values. In particular, file.extensions and
file.mimeTypes both contain data separated by commas. To do so, adapt
overrides.clc and add the following entries:
option.file-mimeTypes.type = list
option.file-extensions.type = list
Beware that once types are converted to the underlying property implementation, the calls to get the correct values will in fact change depending on the implementation.
For example with the new types in place, the calls for Apache configuration
properties don't actually need to change; calling getString(String) will still
return a Java string. However, if we want to get the actual value converted by
the CLC API, the println calls need to be changed to:
System.out.println("IP address : " + props.getString("host.ip"));
System.out.println("Port : " + props.getInt("host.port"));
System.out.println("Protocol : " + props.getString("host.protocol"));
System.out.println("MIME types : " + props.getList("file.mimeTypes"));
List<String> list = props.getList(String.class, "file.mimeTypes");
for (int i = 0; i < list.size(); i++) {
System.out.println("MIME type " + i + " : " + list.get(i));
}
System.out.println("Extensions : " + props.getList("file.extensions"));
List<String> list2 = props.getList(String.class, "file.extensions");
for (int i = 0; i < list2.size(); i++) {
System.out.println("Extension " + i + " : " + list2.get(i));
}
System.out.println("Strip EXIF : " + props.getString("strip.exif"));
System.out.println("In-dir : " + props.get(File.class, "iin.dir"));
System.out.println("Out-dir : " + props.get(File.class, "out.dir"));
For the Java properties implementation, this will change to:
System.out.println("IP address : " + props.getProperty("host.ip"));
System.out.println("Port : " + (int) props.get("host.port"));
System.out.println("Protocol : " + props.getProperty("host.protocol"));
System.out.println("MIME Types : " + props.get("file.mimeTypes"));
List<String> list = (List<String>) props.get("file.mimeTypes");
for (int i = 0; i < list.size(); i++) {
System.out.println("MIME type " + i + " : " + list.get(i));
}
System.out.println("Extensions : " + (List<String>) props.get("file.extensions"));
List<String> list2 = (List<String>) props.get("file.extensions");
for (int i = 0; i < list2.size(); i++) {
System.out.println("Extension " + i + " : " + list2.get(i));
}
System.out.println("Strip EXIF : " + (boolean) props.get("strip.exif"));
System.out.println("In-dir : " + (File) props.get("in-dir"));
System.out.println("Out-dir : " + (File) props.get("out-dir"));
The changes for Java-based properties is because getProperty(String) returns
String, and changing the value types to non-strings means we need to use the
get(String) call which returns an object of the appropriate type.
Lists aren't just limited to string values: It is possible to convert list values to any other value type. Refer to the list type documentation in the Value Types section for more information.
It's possible using CLC overrides to override the names of the command line arguments that have been generated from the underlying property names. This section focuses on standard options; overriding global option names (help, version) is discussed in the next section.
This can be desirable for large property files that use large namespaces to
describe properties. For example, consider the property
application.gui.panels.user-management.title - when displayed using help, the
following output is producced:
--application-gui-panels-user-management-title <arg> Overrides
property
'application.gui
.panels.user-man
agement.title',
default value
'"User
Management
Panel"'
This would be unwieldly for the user to type out as well as making the help descriptions difficult to read.
Command line names can be overridden using the opts option; all that's
required is to add an entry into the overrides CLC file of the form:
option.<config-name>.opts = <long-option-name>
Continuing the previous examples, to convert the host.port property to be able
to be invoked using --port, we can add the following entry to the overrides
CLC file:
option.host-port.opts = port
When running with --help, the following output is now produced (trimmed for
brevity):
--port <arg> Overrides property 'host.port', default
value '1234'
Under the hood, the API maps the new name port to the property name
host.port.
By default, all properties-based implementations use the global option
declaration global.options.opts-type with value LONG, meaning all options
available via the command line will be long options prefixed with two hyphens.
There are several other types available:
BOTH: All declared options must be come in short and long form using the
declaration <short-name>/<long-name>. In such a case, each property
must have an opts declaration;SHORT: All declared options must be overridden and a short option (single
character) defined for it - all properties must be overridden to contain
a relevant short-option definition; andANY: Options can be a mix of short-only, long-only or short-and-long
values. In this case all properties that are not overridden will continue
to be long options; other properties can be modified to be a short option
(single character), or a short and long option of the form
<short-name>/<long-name>Some examples:
To use global option type BOTH, we need to add the global definition, plus all
options overridden with an opts definition of the form
<short-name>/<long-name>. Continuing the previous example (and only including
one property definition for brevity), the CLC overrides file would contain the
following:
global.options.opts-type = BOTH
option.host-port.opts = p / port
# ^ Other opts omitted; you'd have to do this for every defined option because
# options type expects short AND long options to be defined for each property
Likewise, to use only short-name types, the following definition would be used:
global.options.opts-type = SHORT
option.host-port.opts = p
# ^ Other opts omitted; you'd have to do this for every defined option because
# options type expects short options to be defined for each property
Remember: When overriding BOTH and SHORT, all properties will require an
opts override.
Finally, for the ANY global options type, we could mix and match the above
short and short-and-long definitions, the definitions that are not overridden
staying as long based options. This means that for any opts declaration, we
can use just a short option, a long option or a short and long option. For
example, the following shows one option using a short option, one using a long
option and one using both a short and long option:
global.options.opts-type = ANY
option.host-port.opts = p
option.host-ip.opts = ip
option.host-protocol.opts = P / protocol
option.file-mimeTypes.opts = mimeTypes
When we invoke help the following output is produced:
usage: apache-props-demo [options] <in-dir> <out-dir>
Process files in the given input directory and move them to the specified
output directory.
--extensions <arg> Overrides property 'file.extensions',
default value 'mp3, jpeg'
-h,--help Print this help then exit.
--ip <arg> Overrides property 'host.ip', default
value 'localhost'
--mimeTypes <mimeTypes...> MIME types to consider; files that do not
match any of the given MIME types will be
left in place. Separate multiple MIME
types with commas; if there are spaces in
the list, surround the arguments with
double quotes.
-P,--protocol <arg> Overrides property 'host.protocol',
default value 'https'
-p,--port <arg> Overrides property 'host.port', default
value '1234'
--strip-exif Overrides property 'strip.exif', default
value 'false'
-v,--version Print version then exit.
See some random URL for information
Overriding command line names for help and versioning is slightly different to standard options.
To change the options for help options, override global.help.switch.opts with
the required options. For example, to change the default long version from the
default help to show-help, the following would be added to the overrides CLC
file:
global.help.switch.opts = show-help
Versioning acts the same but version options are modified using the
global.version.switch.opts global option. For example, to specify the version
command line switch as get-version-info, add the following to the CLC
overrides file:
global.help.switch.opts = get-version-info
Although help is provided out of the box, it is possible to define your own help if the provided help is not quite what is required.
To override the default help, ensure help(false) is added to the properties
builder when building the properties - this will prevent the default help from
being generated.
The next step is to add your application as an OptionListener and override
the option method, as well as to add custom CLC entries to the CLC overrides
file.
For the CLC overrides, add necessary CLC values, for example:
option.help.opts = help
option.help.description = Show some help.
option.help.ignoreCliArgs = true
ignoreCliArgs is set to ensure arguments are not parsed when the API is
invoked; it's used to determine if to continue processing arguments.
The next step is to add the application as a listener for the help command.
The application must implement org.statefive.clic.OptionListener with the
following implementation, adding in the custom help invocation where the comment
is defined:
@Override
public void option(final String option, final Object value) {
if ("help".equals(option)) {
// Custom help code here
}
}
It's also possible to pass an argument to the help command; for example a topic-based help command where a topic is passed to the help command line switch. To do so, modify the CLC definition as follows:
option.help.opts = help
option.help.description = Show some help for the given topic. Topics are 'foo' and 'bar'.
option.help.ignoreCliArgs = true
option.help.hasArg = true
option.help.argName = topic
We can now override the listener to filter on topic and display help accordingly:
@Override
public void option(final String option, final Object value) {
if ("help".equals(option)) {
String topic = value.toString();
switch (topic) {
case "foo":
// display 'foo' based topic
break;
case "foo":
// display 'bar' based topic
break;
default:
System.err.println("Unknown topic: " + topic);
}
}
}
In the above example application, there were enough options such that we didn't
need to consider arguments - those values that are not command line switches.
For example with the GNU/Linux command ls, the arguments are the files that we
wish to run the list command on. Arguments are the string values left over on
the command line after all command line switches have been processed.
The CLC API enables obtaining the arguments once all options have been processed
via the PropertiesListenerBindings.getInstance().getArgs() API call.
To view arguments passed to an application, add the following code underneath
the println statements to print the property values:
List<String> cliArgs = PropertiesListenerBindings.getInstance().getArgs();
for (int i = 0; i < cliArgs.size() ; i++) {
System.out.println("Argument " + (i+1) + " = '" + cliArgs.get(i) + "'");
}
We can now supply the following arguments on the command line, for example
--host-ip 192.168.1.15 --host-port 8080 arg1 arg-2 " arg 3" "#4"
… And get the following output (omitting the printed properties themselves):
Argument 1 = 'arg1'
Argument 2 = 'arg-2'
Argument 3 = ' arg 3'
Argument 4 = '#4'
Since our example application, as it stands, doesn't take any arguments (that
we want to process, anyway), let's cap the arguments at zero which will cause an
error if callers add any additional arguments. To do so, update the
overrides.clc file with the following entry:
args.capped-at-zero.length = 0
That's all. The decision to name the entry capped-at-zero is entirely
arbitrary, and could have been called x, foo, etc. To test it, invoke the
program with the following additional arguments as to those provided in the previous example:
--host-ip 192.168.1.15 --host-port 8080 --file-extensions mp3,avi,ogg --in-dir /tmp foo bar
Both foo and bar will be treated as non-switch arguments; however, since
we've stated that the application doesn't take arguments, the following error is
produced:
Error: Configuration does not accept arguments.
So let's turn things around - we can remove the two properties in.dir and
out.dir, and replace them with argument configurations both specified as type
dir. The properties file without in.dir and out.dir looks like so after
the properties are removed:
host.ip = localhost
host.port = 1234
host.protocol = https
strip.exif = false
file.mimeTypes = text/html, media/jpg
file.extensions = mp3, jpeg
Since in.dir and out.dir will need to be treated as arguments, we need to
add the following entries to before the args.capped-at-zero.length=0 line in
overrides.clc so the end of the file now looks as follows:
args.in-dir.length = 1
args.in-dir.type = dir
args.in-dir.properties = dirType = exists
args.out-dir.length = 1
args.out-dir.type = dir
args.out-dir.properties = dirType = exists
args.capped-at-zero.length = 0
The properties declaration dirType = exists means that when the arguments
are processed, an error will be thrown if either directory does not exist. There
is another property !exists that can be used to ensure that an argument that
is a directory doesn't exist, throwing an error if it does.
This means that any time the application is run, it will expect there to be two (and only two) arguments left once all options have been processed. The will enable us to make the call
<program> <options> <in-dir> <out-dir>
For example:
--host-ip 192.168.1.15 --host-port 8080 --file-extensions mp3,avi,ogg audio/input audio/output
Notice how audio/input and audio/output are no longer defined by command
line switches but are part of the arguments passed to the application.
Furthermore, if either directory doesn't exist, an error will be thrown.
To access the arguments via the API, the last two lines to obtain in-dir and
out-dir need to be removed from the property println statements and replaced
with the following code:
File inDir = (File) Clc.getInstance().getArgsValueTypes().get(0);
System.out.println("Input Dir : " + inDir.getAbsolutePath());
File outDir = (File) Clc.getInstance().getArgsValueTypes().get(1);
System.out.println("Output Dir : " + outDir.getAbsolutePath());
That's it. It's been a fairly brief demonstration of how to:
Implementation-Version or a properties version can be used;The following sections focus on the CLC format, as well as how to add listeners to receive updates for options and arguments.
Both the Apache and Java properties final implementations are available in the
examples/ directory.