The quarrel package provides a simple API for producing command-line applications.
Using the package consists of the following steps:
The
quarrel package does not intend to provide a fully generic command-line parser
that can emulate the argument parsing style of every application ever conceived. It is patterned on a strict
subset of the kind of command-line parsing used by programs such as
podman.
Briefly, the command line interface is divided into discrete
commands. Commands may be grouped into
command groups. A command accepts a possibly empty list of named
parameters, followed by a possibly empty list of positional parameters. Named
parameters have a configurable
cardinality; a named parameter can be declared as "must be specified exactly one", "may
be specified any number of times", "must be specified at least once", and so on. A named parameter must be
supplied with a
value
at run-time, but can also be specified to have a given
default value
if one is not provided. Both positional and named parameters are
typed, with values being parsed by looking up a parser in a directory
of value converters. The package comes with a useful set of
value converters
for many of the standard Java types, but new converters can be registered by the application developer.
The
quarrel package provides an application abstraction that, given metadata such as
the application name and version, automatically provides useful commands such as
help
and
version
to relieve the application developer of the burden of having to implement such standard commands. The application
abstraction also handles the dispatching of arguments to a tree of nested commands/command groups.
The quarrel package also provides detailed, localized, and structured error reporting
to assist users of command-line applications built using quarrel. For example, if the
user fails to provide a required named argument, or provides a value that cannot be parsed as a value of the
argument's declared type, they will receive an error message such as the following:
Application programmers must provide an implementation of the
QCommandType
interface for each command in the application.
In order to implement the QCommandType interface, the following pieces of information
must be provided:
The
metadata for the command consists of the name of the command, a short one-line
description of the command used in generated
help messages, and an optional long description also
used in help messages. The metadata must be returned by the implementation of the
metadata
method:
Localizable strings such as the descriptions are specified as values of the
QStringsType
type so that the
quarrel package knows whether to try to look up their value in
resources, or use their string constant values directly. Command names are not localizable, because having a
different command-line interface depending on locale would create severe problems when using applications from
scripts.
Command names cannot begin with the
@ character, because this would conflict with
the provided
@ syntax.
Command names cannot contain any characters in the Unicode \p{Z} set. This
includes any kind of whitespace or invisible separator.
Command names must be non-empty.
A command may accept zero or more
named parameters. A command implementation must
return zero or more parameter declarations from the
onListNamedParameters
method. The values returned are used later by the implemented
onExecute
method to retrieve the parsed values supplied to those parameters.
A named parameter is an instance of the sealed QParameterNamedType. Every named
parameter has a name, a type, and descriptive metadata used for help messages. Named parameters may also declare
alternative names
(for example, to provide short versions of parameters such as -f in addition
to --file). Parameter names must be unique within a given command (including
alternative names). Each of the subclasses of QParameterNamedType encodes a different
parameter cardinality:
A named parameter may have a value of any type for which there is an appropriate
value converter
registered.
Parameter names cannot begin with the
@ character, because this would conflict with
the provided
@ syntax.
Parameter names cannot contain any characters in the Unicode \p{Z} set. This
includes any kind of whitespace or invisible separator.
Parameter names must be non-empty.
A command may accept zero or more positional parameters. A command implementation must
return a value of the QParametersPositionalType type from the implemented
onListPositionalParameters
method that indicates how the command accepts positional parameters:
As with
named parameters, positional parameters must
have names and types, although the names are only used for documentation and are otherwise ignored. If a
sequence of positional parameters are provided, all parameters must be supplied on the command-line; positional
parameters cannot be optional - use named parameters for optional parameters.
A positional parameter may have a value of any type for which there is an appropriate
value converter
registered.
Parameter names cannot begin with the
@ character, because this would conflict with
the provided
@ syntax.
Parameter names cannot contain any characters in the Unicode \p{Z} set. This
includes any kind of whitespace or invisible separator.
Parameter names must be non-empty.
The command implementation must implement the
onExecute
method. This method contains the actual code executed by the application after the command is invoked and all
argument parsing has succeeded. The method is passed a value of type QCommandContextType
that provides access to:
To obtain access to the values of named parameters, the application code can call any of the various context
methods, passing in the original parameter declarations. For example:
The command context contains methods specialized to each
cardinality subclass
in order to return values of an appropriate collection type without requiring the programmer to do any unsafe
casting. For example a parameter declared with a value of type
T with a cardinality of
"exactly one" will return a value of
T directly. However, a parameter declared with a
value of type
T with a cardinality of "zero or more times" will return a value
of
List<T>.
The onExecute method is defined to allow the raising of any subclass
of Exception, but is also defined to require the returning of a value of
type QCommandStatus. The value
QCommandStatus.SUCCESS
indicates that the command succeeded, and the value
QCommandStatus.FAILURE
indicates that the command failed. This may seem slightly counterintuitive, after all raising an exception
clearly indicates failure. However, consider the standard POSIX utilities such as
grep; the grep command returns a non-zero exit code on
the command line if the input did not match a given regular expression. The command did not "fail" in a way that
would justify raising an exception (such as the user asking
grep
to open a file that turned out not to exist), but it did return some feedback that justified adjusting the exit
code such that scripts can easily observe the results without having to parse the command's output.
The quarrel package has to accept and display arbitrary strings returned from code
implemented by users. In order to properly support localization, the package needs to know whether it needs to
localize a given returned string. The QStringType can refer to either of the following
types:
The quarrel package provides a top-level application type that acts as a container for
a tree of commands and command groups. Application developers should instantiate a value of type
QApplicationType
using the provided mutable builder type.
First, place the application metadata into a value of type
QApplicationMetadata:
Create an application builder and register some commands, and set the application's string resources (used to
localize the strings returned by the application's commands):
For each
QString the
quarrel
package encounters, it first tries to localize the string using the application's provided resource bundle. If the
string does not exist there, it tries to localize the string using its own internal resource bundle.
Optionally, commands can be placed into groups:
Groups and commands form a tree structure, with the root of the tree effectively being an anonymous command group.
The command-line arguments passed to an application instance are consumed left to right in order to walk the tree
and locate a specific command. For example, if the given command line arguments are podman
system connection list -q, assuming that the first argument "podman" is
stripped away
, then the application receives the
arguments "system" "connection" "list" "-q". The application will look for a
top-level command or group named
"system". If "system" names a command, the command
will be passed the arguments
"connection" "list" "-q"
and the search terminates. If "system" names a command group, then the command
group will be inspected for a command or group named "connection". This procedure
continues recursively until either a command is located, a group doesn't contain the given name (resulting in an
error), or the argument list is exhausted (resulting in an error).
Finally, build an application. Application instances are immutable.
Given the application instance
constructed
previously, the instance can be passed a set of command line arguments. The application will either raise an
exception indicating that arguments could not be parsed or were incorrect in some manner, or it will return a
command context. The command context can be used to execute the parsed command.
The
quarrel package uses a
QException type containing
structured error information to produce useful error messages. The
QApplicationType
type contains a convenience method named
run that effectively implements the above
code with some provided error formatting. Given an
SLF4J
logger, the
run method can reduce the above code to:
Executing the example program with no arguments yields a generated usage page:
The
QCommandType interface contains a default method
isHidden
that, if implemented to return
true, will cause the command implementing it to be
hidden in the listings produced by the
help
command. This can be useful if an application contains internal commands that are not intended to be user-facing.
The default behaviour for the method is to return false, thus making commands
visible.
The
help command is registered at the top level of the resulting application and can
produce detailed usage messages when given the name of an existing command. For example, the
cmd-everything
command from the
example application
yields the following output when inspected using
help cmd-everything:
The
version command is registered at the top level of the resulting application and
prints version information based on the metadata provided to the application instance. For example, the
version
command from the
example application
yields the following output:
The command outputs the application ID, followed by a space, followed by the version number, followed by a space,
followed by the build identifier, followed by a newline.
If the first character of the first argument passed to the application is
@, then the argument (minus the
@
character) is interpreted as a filename containing command-line arguments for the application.
The file named using @ is read line-by-line. Lines beginning with the character
#
are ignored. Leading and trailing whitespace is trimmed from each line, and empty/blank lines are ignored. After
all lines have been read, each line is interpreted as being a single argument, and the resulting list of arguments
is prepended onto the arguments following the
@
argument.
The processing of
@ syntax may be disabled by setting
allowAtSyntax to
false on the
application builder.
A value converter is a class that can convert values to and from strings. Essentially,
they combine a parsing function with a serialization function, and also allow for returning some descriptive
information such as the expected syntax of string values. Value converters are collected into a value converter
directory, represented by an instance of the
QValueConverterDirectoryType
type. The directory can be thought of as a map from Java Class values to values of
type QValueConverterType.
The quarrel package includes a standard directory covering many of the scalar types
within the JDK. The list includes but is not necessarily limited to:
Additionally, the default implementation of the QValueConverterDirectoryType
automatically supports the use of any Java enum type.