OptParse
opt-parse is an advanced command line parser for Curry. It features support for options with and without values (i.e. flags), positional arguments and commands that can define their own sub-parsers. It borrows heavily from Paolo Capriotti’s Haskell package optparse-applicative and Curry’s GetOpt module.
You use opt-parse by declaring a parser specification and then running that parser specification on a command line. A parser specification is made up from individual parsers for options, flags, position arguments and commands. Each individual parser results in an arbitrary value, though all parsers in a parser specification must result in values of the same type.
A simple command line parser example might look like this:
cmdParser = optParser $
option (\s -> readInt s)
( long "number"
<> short "n"
<> metavar "NUMBER"
<> help "The number." )
<.> arg (\s -> readInt s)
( metavar "NEXT-NUMBER"
<> help "The next number." )
main = do
args <- getArgs
parseResult <- return $ parse (intercalate " " args) cmdParser "test"
putStrLn $ case parseResult of
Left err -> err
Right v -> show vThis defines a parser that supports a number option and
requires a single positional argument. Both values are parsed into an
integer. The parse function is called with the command line
as a single string, the parser specification and the name of the current
program. It results in either a Left if there was a parse
error or a Right with the list of parse results. Running
test --help prints out usage information:
test NEXT-NUMBER
-n, --number NUMBER The number.
NEXT-NUMBER The next number.
If we run test --number=5 2, we get the list of parse
results:
[2, 5]
metavar and help are modifiers that can be
applied to any argument parser, command, option, flag or positional. The
help text is what is printed in the detailed usage output,
the metavar is the placeholder to be printed for the
argument’s value in the usage output. The optional modifier
can also be applied to all argument types, although flags and options
are already optional by default.
The long and short modifiers are specific
to options and flags.
Right now, the result of our parser is a list of the individual parse results. Usually, we want our parse result to be a single value, for example a Curry data type such as this:
data Options = Options
{ number :: Int
, nextNumber :: Int }To parse a command line to an Options value, we return
functions from our individual parsers instead of integers:
cmdParser = optParser $
option (\s a -> a { number = readInt s })
( long "number"
<> short "n"
<> metavar "NUMBER"
<> help "The number." )
<.> arg (\s a -> a { nextNumber = readInt s })
( metavar "NEXT-NUMBER"
<> help "The next number." )The result of a successful parse will now be a list of functions that
change an Options value. We can fold this list onto a
default Options:
applyParse :: [Options -> Options] -> Options
applyParse fs = foldl (flip apply) defaultOpts fs
where
defaultOpts = Options 0 0
main = do
args <- getArgs
parseResult <- return $ parse (intercalate " " args) cmdParser "test"
putStrLn $ case parseResult of
Left err -> err
Right v -> show $ applyParse vExecuting test --number=5 1 results in:
(Options 5 1)
Positional arguments can be created via arg and
rest. arg is a normal positional argument
which can be optional or mandatory. rest is a positional
argument that consumes the rest of the command line as-is. Positional
arguments are expected in the order they occur in the parser
definition.
flag can be used to create flag arguments. A flag
argument expects no value.
In addition to options, flags and positional arguments, opt-parse also includes support for commands. A command is a positional argument that dispatches to sub-parsers depending on its value. If we have a calculator program that supports addition and multiplication, we could model its command line interface using commands:
data Options = Options
{ operation :: Int -> Int -> Int
, operandA :: Int
, operandB :: Int }
cmdParser = optParser $
commands (metavar "OPERATION")
( command "add" (help "Adds two numbers.") (\a -> a { operation = (+) })
( arg (\s a -> a { operandA = readInt s }
( metavar "OPERAND-A"
<> help "The first operand." )
<.> arg (\s a -> a { operandB = readInt s }
( metavar "OPERAND-B"
<> help "The second operand." ) )
<|> command "mult" (help "Multiplies two numbers.") (\a -> a { operation = (*) })
( arg (\s a -> a { operandA = readInt s }
( metavar "OPERAND-A"
<> help "The first operand." )
<.> arg (\s a -> a { operandB = readInt s }
( metavar "OPERAND-B"
<> help "The second operand." ) ) )The corresponding usage output for test run with no
further arguments is:
test OPERATION
Options for OPERATION
add Adds two numbers.
mult Multiplies two numbers.
If we choose an operation, e.g. add, the output is:
test add OPERAND-A OPERAND-B
OPERAND-A The first operand.
OPERAND-B The second operand.