This is an introduction on how to use Colipa. The full documentation is in the comments in the interface section of
the colipa.m file. You can also find an example program in the test.m file.
This file is released under the terms of the GNU Free Documentation License, Version 1.3 or any later version. See COPYING.FDL for details.
Colipa supports GNU style command line arguments. This means you have short arguments, which begin with a single
dash (-a) and long arguments, which begin with a double dash (--arg). Both can have a value, which can be
placed inside the parameter or in the next parameter (-aval, --arg=val, -a val or --arg val). Parameters
can be arguments (beginning with a dash) or non-arguments (everything else). Arguments can occur anywhere in the
command line, not just at the beginning. Everything after the special parameter -- is treated as a non-argument.
Long arguments can be abbreviated as long as they are unique.
Command line arguments are identified by unique strings. These strings are semantically wrapped inside the cla
type. This makes it possible to reuse and share command line argument definitions. (Previously, in the 1 versions
of Colipa, the arguments were identified by an enumeration type. That made it impossible to define command line
arguments in one module and use them in another.)
This is the type of the command line argument identifiers:
:- type cla ---> cla(cla :: string).
The argument identifiers have the form cla(Str), where Str is a unique string that names the argument. It is
meant not to change, although it’s relatively easy to change it later - you just need to replace all occurences of
cla(XXX) with cla(YYY), where XXX and YYY are the strings which identify the argument, before and after the
change.
You describe each command line argument with a list of properties. You have one big list of all the arguments and
their properties. The required type for that list is assoc_list(cla, list(property)). For instance:
:- func argdescl = assoc_list(cla, list(property)).
argdescl = [
cla("dir") -
[ prop_long("dir"),
prop_switch_value("Dir"),
prop_description("Name of the directory.")
],
cla("archive") -
[ prop_short('a'),
prop_long("archive"),
prop_switch,
prop_description("If the program is to archive the data.")
],
...
].
All arguments need to have a name, by which they appear on the command line. It can be either short (prop_short)
or long (prop_long) or both. If you want to use the automatic usage info generation, you need to give your
arguments descriptions, with the prop_description property. See colipa.m for a list of all supported
properties.
The prop_default property, for setting a default value, is different. It must be specified like this:
'new prop_default'(Default)
The new prefix is needed because the data constructor uses an existential type. See the chapter
Existential types in
the Mercury Language Reference Manual. But don’t worry, you don’t need to understand existential types for using
Colipa.
Next, you need to call the command line parser. The most simple way should be to use the colipa/5 predicate:
:- pred colipa(
assoc_list(cla, list(property))::in,
arguments::out,
list(string)::out,
io::di, io::uo)
is det.
You pass it, as the first parameter, your argument description list. The recognized command line arguments are
returned in the second parameter, in a value of type arguments. Later you use it when querying the command line
arguments. The non-argument parameters are returned in the third parameter of colipa/5. So the call looks like
this:
colipa(argdescl, Arguments, NonArguments, !IO)
Now you’re ready to get the information you want out of the parsed command line arguments. This is done by query
functions. The function to use is determined by what you want to get out of the parameters. For instance, for an
argument that can be specified zero or one times, and has a value, you would use the arg_maybe_value function. If
your argument is of type T, then arg_maybe_value will return a value of type maybe(T). When the argument
hasn’t been given on the command line, it would return no. If it has, it would return yes(Value).
The used query function must match the argument definition (the argument’s property list). For instance, you can’t
query an argument that doesn’t take a value, with arg_value or arg_maybe_value. It is probably a bug, when this
should happen. If such a mismatch is detected, an arg_desc_error is thrown. You can catch and output that error
and get an elaborate message about what went wrong.
The query functions all take the Arguments value and the argument identifier as parameters, and return the
queried value. Using a query function could, for instance, look like this:
Dir = arg_maybe_value(Arguments, cla("dir"))
When the type of the argument value isn’t clear from the context, you will get a compile time error. In this case, you need to explicitly specify the type. Like this:
Dir = arg_maybe_value(Arguments, cla("dir")) : maybe(string)
The user can make errors. For instance, supplying a value to an argument that doesn’t take one, or supplying a value that isn’t a well-formed, parseable representation of the argument’s type. The programmer can make errors as well - bugs. For instance, by defining an argument with contradictory properties, or by querying an argument with a query function for the wrong type. Not all programmer errors can be detected at compile time.
This means that there are two types, which can be thrown by Colipa: command_line_error for errors that the user
makes, and arg_desc_error for errors by the programmer. Both can be triggered by the colipa/5 predicate and
by a query function.
You catch them like this:
try [io(!IO)] ...
then ...
catch (ADE : arg_desc_error) ->
print_on_terminal("Bug found: " ++ ade_message(ADE) ++ "\n", !IO)
catch (CLE : command_line_error) ->
print_on_terminal(cle_message(CLE) ++ "\n", !IO).
You can also catch individual errors like this:
catch (cle_missing_value(ArgDesc, No, Last) : command_line_error) ->
...
The print_on_terminal predicate determines the width of the terminal (80 is used if not connected to a terminal)
and wraps lines such that no words are split up. cle_message/1 and ade_message/1 make an error message, from
a command_line_error or arg_desc_error, respectively.
The predicate which catches the exceptions must have the cc_multi determinism category. See Exception
Handling in the Mercury
Language Reference Manual.
Usage information about the command line arguments can be generated automatically from the argument descriptions.
Use the prop_description property to add a descriptive text. This text should not be broken down into lines by
newline characters. Colipa does that by itself. But newlines are allowed and will be respected.
To print a usage summary on stdout, use this predicate:
print_usage_info(argdescl, !IO)
There are more and more flexible predicates to do this. You can also sort the argument list by the name of the first long argument:
sort_argdescl(argdescl, ArgDescL2),
print_usage_info(ArgDescL2, !IO)
Here’s an example of what it looks like, in a terminal of width 58. Color isn’t shown here:
-v This argument can be given up to three
times.
-a --archive This is a switch. It can be specified
zero or one times. It's value is a
boolean.
-c --count Count This argument takes an integer. It
also has a default and a checker.
--dir Dir Name of the directory.
--float F This is an example of an argument that
takes a floating point number as its
value.
...
By default, Colipa supports the argument types string, int and float. You can add new argument types easily
by instantiating the argument typeclass. You need to define a parser for that type, which goes into the
from_str method. It will return fs_ok(Val) for a successful parse, and fs_error(Msg) for a parse error, where
Msg is an error message. When the parse failed, Colipa will throw a cle_malformed_value exception, which
includes the error message.
This is an example:
:- instance argument(bool) where [
from_str(Str) = Res :-
( if Str = "yes"
then Res = fs_ok(yes)
else if Str = "no"
then Res = fs_ok(no)
else Res = fs_error("Expected ""yes"" or ""no"".")
)
].
While argument descriptions can place constraints and checks on individual arguments, they can’t place restrictions
on groups of arguments. For this purpose, group constraints predicates have been added. They begin with args_.
For instance, the args_one/3 predicate checks if exactly one of a group of arguments is provided on the command
line. They each check something about several arguments and throw a command_line_error if the constraint isn’t
observed.
Checkers check an argument value from the command line, after it has been successfully parsed. This means, they
place additional constraints on a command line argument. For instance, there are two predefined checkers in Colipa,
called nonneg_integer and positive_integer, respectively. They check an integer for being non-negative or
positive (one or bigger). When a checker fails, a cle_invalid_value error is thrown.
You can define a checker with the make_checker function. For instance, nonneg_integer is defined like this:
nonneg_integer = make_checker(nni).
:- pred nni(int::in, maybe(string)::out) is det.
nni(I, Err) :-
( if I < 0 then Err = yes("Non-negative integer expected.")
else Err = no ).
make_checker takes a predicate, which checks the value and returns an error message in case the check failed.
This message is included in the cle_invalid_value exception. The type of make_checker is:
:- func make_checker(
pred(T, maybe(string))::pred(in, out) is det
) = (checker::out) is det
<= argument(T).
Checkers are added to a command line argument with the prop_check property, such as prop_check(nonneg_integer).
Colipa provides a simple framework for dealing with command line arguments and subcommands.
The program/6 predicate strives to unburden you when your program uses a subcommand. It is passed a stripped down
version of your main predicate. Part of the subcommand and command line handling is done by program/6, so you
don’t have to do it there. It passes the following pieces to your new main predicate:
program/6 and it exits which an error message.colipa/5 or colipa1/5 don’t need to be called by the programmer any
longer.It handles the special subcommand “help” and prints usage information (including subcommands and command line arguments). It also handles invalid subcommands. If no subcommand is specified, a short message is printed, which tells the user to call the program with “help”.
See colipa.m for the invocation details of program/6.
The framework also provides a predicate catch_cle_ade/5:
:- pred catch_cle_ade(
int::in, % Exit code for command line error
int::in, % Exit code for argument description error
pred(io, io)::in(pred(di, uo) is cc_multi), % Predicate to wrap
io::di, io::uo
) is cc_multi.
This wraps some IO predicate and deals with command_line_errors and arg_desc_errors by printing an error
message to stderr and signaling the program to exit. This signaling is done by throwing a value of this type:
:- type shutdown ---> shutdown(int).
The enclosed integer is the intended exit code of the program. The top-level main program is meant to catch such shutdown exceptions, set the exit code and leave. Like this:
main(!IO) :-
try [io(!IO)]
main1(!IO)
then true
catch (colipa.shutdown(ExitCode)) ->
io.set_exit_status(ExitCode, !IO).
The predicate main1/2 here is the rest of the main program. It typically begins with a call to catch_cle_ade/5.