The hsoptions package

Flags are declared in the code by using the make function, which takes the
flag's name, help text and type as arguments.

The flags are parsed from the command line stream of from a file
if the --usingFile <filename> flag is sent to the program.

Flags can be customized by calling configuration function, such as
defaultIs or aliasIs, that change how the flag behaves, how it
is parsed and validated.

The processMain function needs to be called at the beginning of the main
function. This function takes as arguments:

The program description

A list of all declared flags

Three callbacks:

* success

* failure

* display help

If there is any kind of validation error failure is
called with the list of errors. If the --help flag was sent by the user
display help is called. Otherwise if there are no problems the success
function is called.

A default implementation of failure and display help is provided in the
library (defaultDisplayHelp, defaultDisplayErrors) with a basic bahavior.

Basically success becomes the 'real' main function. It takes as argument
a tuple (FlagResults, ArgsResults). FlagResults is a data structure
that can be used to query flags by using the get function. ArgsResults is
just an array of String containing the remaining not-flag arguments.

At the processMain function each of the input flags is validated against the
declared flags. Within the success function you can be sure that all required
flags exist, all flag types are correct and all validation was successful.

Downloads

Maintainers' corner

Readme for hsoptions-1.0.0.0

HsOptions

HsOptions is a Haskell library that supports command-line flag processing.

It is equivalent to getOpt(), but for Haskell, and with a lot of neat extra
features. Typically, an application specifies what flags it is expecting from
the user -- like --user_id or -file <filepath> -- somehow in the code,
HsOptions provides a declarative way to define the flags in the code by using
the make function.

Most flag processing libraries requires all the flags to be defined in a single
point, such as the main file, but HsOptions allows the flags to be scattered
around the code, promoting code reuse and scalability. A module defines the
flags it needs and when this module is used in other modules it's flags are
handled by HsOptions.

HsOptions is completely functional, specially because no global state is
modified. The only IO actions performed are to get the command-line arguments
and to expand the configuration files.

Another important feature of HsOptions is that it can process flags from text
files as well as from command-line. This feature is available with the use of
the special --usingFile <filename> flag.

Each configuration file is expanded after it is processed, so it can include
more configuration files and create a tree. This is useful to create different
environments, like production.conf, dev.conf and qa.conf just to name a few.

$ runhaskell Program.hs --help
Simple example for HsOptions.
--age the age of the user
-u --user_name the user name of the app
--usingFile read flags from configuration file
-h --help show this help

API

Defining flags

A flag is defined using the make function. It takes the name of the flag, the
help text and the parser. The parser specified how to parse the string value of
the flag to the correct type. A set of default parsers are provided in the
library for common types.

To define a flag of type Int:

age :: Flag Int
age = make ("age", "age of the user", [parser intParser])

To define the same flag of type Maybe Int:

age :: Flag (Maybe Int)
age = make ("age", "age of the user", [maybeParser intParser])

The function maybeParser is a wrapper for a parser of any type that converts
that parser to a Maybe data type, allowing the value to be Nothing. This is
used mostly for optional flags.

Instead of intParser the user can specify his custom function to parse the
string value to the corresponding flag type. This is useful to allow the user to
create flags of any custom type.

Process flags

To process the flags the processMain function is used. This function serves as
a middle man between the real main and the flag processing. Takes 5 arguments:

The description of the program: used when printing the help text.

A collection of all the defined flags

Three callback functions:

Success callback: called with the process results if no errors occurred

Failure callback: called if any error while processing flags occurred

Display help callback: called if the user sent the --help flag

This is an example on how to call the processMain function:

import System.Console.HsOptions
-- flags definitions
name = make ("name", "the name of the user", [parser stringParser])
age = make ("age", "the age of the user", [parser intParser])
-- collection of all flags
all_flags = combine [flagToData age, flagToData name]
-- real main
main = processMain "Example program for processMain"
all_flags
successMain
defaultDisplayErrors
defaultDisplayHelp
-- new main function
successMain (flags, args) = putStrLn $ flags `get` name

In this example, the provided implementations for the failure and the display
help callback were used (defaultDisplayErrors and defaultDisplayHelp), so
that we do not need to define how to print errors or how to print help.

As mentioned before, if no errors were found then successMain function is
called. The argument sent is a tuple (FlagResults, ArgsResults).
FlagResults is a data structure that can be used to get the flag's value with
the get function. ArgResults is just a list of the non-flag positional
arguments.

If there was any kind of errors while processing the flags the display errors
callback argument is called with the list of FlagError as argument. The user
can specify a custom function so he handles the argument as he wishes.

The third callback, display help, is called when the user sent the special help
flag (--help or -h). It takes the program description and all the
information of the flags as a list of (flag_name, [flag_alias],
flag_helptext). The defaultDisplayHelp is a default implementation that
prints the helptext in a standard format, usually this is the way to go unless
the user wants to print the help text in a custom format.

Get flag value

A flag value is obtained by using the get function. It takes the FlagResults
and a defined flag as a parameter, and it will look for the value of the flag
inside the FlagResults. In a way you can think of FlagResults as a data
structure that can be queried with flags to retrieve flag values.

The FlagResults are obtained by processing the flags with the
processMain function.

The return type of get is the type of the flag, so if the flag is Flag Int
then get returns an Int (so the flag value is typed).

For a given flag:

repeat = make ("repeat", "how many times to repeat", [parser intParser])

Optional and Required flags

By default all flags are marked as required. If you want to make an optional
flag then two things are required:

* First, the type of the flag must be `Flag (Maybe a)`, so that the flag can
be `Nothing` if it was not provided and `Just value` if it was.
* Second, the flag must be configured using the `isOptional` flag
configuration.

Configuration files

Flags can be processed not only from command-line input, but also from configuration text
files. These text files are included at any point in the command-line stream by using the
special flag --usingFile <filename>.

When the flag processor encounters a usingFile it reads the content of the file and
runs the processor again with this content, consuming the usingFile flag and replacing
it with all the new flags found inside the configuration file.

A configuration file can itself include other configuration files as well, by using the
usingFile flag inside the file, so a tree of files can be created (a file can have a
parent file, and a grandparent file, or a file can include multiple files to combine
them together).

If there is any kind of error while reading the file, or there is a syntax error inside
the file then that error is reported to the user.

This is an example of a configuration file that has comments, and that includes two more
files.

So if we have a Program.hs that is configured with the flags database, flagA and flagB,
and that prints the remaining positional arguments, then this is the output of the program
for the following scenarios:

... as you can observe superman and robin are respectively at the start and end
of the positional arguments, that is because first superman is found in the input stream,
then the usingFile combined.conf which gets evaluated and parsed, and when this is
complete then the processor moves to robin which is captured as the last positional
argument.

Here is another example on how we can override and extend the flags. We will change the
flagA to 1024 and will append the value .local to the database flag.

Default value

There is two types of default flag values, a default value when the flag was not provided
by the user, and another default value for when the user provided the flag but not the
flag value. The flag configurations are defaultIs and emptyValueIs.

A default value can be configured for a flag by using the defaultIs flag configuration. It takes the value that the flag will have in case the flag is not provided by the user.

The combination of defaultIs and emptyValueIs makes it possible to define flags such as
booleans. So we could set up a flag such as --debug (Bool) that will take the value
False if missing and will take the value True if the user sent --debug without him
having to say --debug = True.

Common configurations

There are some common patterns that occurs while configuring flags. These patterns
can be put into a function for code reuse.

Boolean flag

A default behavior for boolean flag is that if the flag is missing then it's value is
False and if the flag is present, even with a missing flag value, then it's value is
True.

Dependent defaults

Creates a flag configuration that will define a default value for a flag based
on a condition. This condition is a function that takes in the current
FlagResults and returns Nothing if the there is no default value or the
default value (Just) if there is one.

If the function returns a value, and the user did not send the flag in the
input stream, then the default value associated with this function is used
as the default value for the flag.

The dependent default value is configured by using the defaultIf function.
It takes as arguments the default value getter function that given the
FlagResults tries to return a default value.

Global validation

A global validation rule is a function that will be evaluated with the FlagResults
after the processing stage and will determine if the current state is valid.

It is the last stage of flag processing. If there is a validation error then this error
is reported to the user. This validation is done by using the validate function that
takes a function that returns a Maybe String, Nothing being a passing result and Just
err being failing result with an err error message.

Flag operations

Flag operations allows the user to set the value of a flag based on the previous value set.
This is useful in situations where configuration files are used, so that a child configuration
file can extend the value of a flag set in a parent configuration file.

Operations are specified when setting a value for a flag. This is the syntax to set a flag:
--flag_name [operation] flag_value. If the [operation] is not set then the assign (=)
operation is implied.

Assign

This is the default operation. Sets the value of the flag, overwriting any previous value if
there was any. This is the default operation unless the user
changed it in the flag configuration.

Change flag default operation

By default a flag's default operation is the assign (=) operation. So if the user sends
a flag and it's value without explicitly using an operation this is the operation used.

Now if you want to change this behavior for a given flag you can do so by using the
operation flag configuration. This takes an operation as an argument and sets this
as the default operation for the flag: