# optparse-declarative [![Hackage](https://matrix.hackage.haskell.org/api/v2/packages/optparse-declarative/badge)](http://hackage.haskell.org/package/optparse-declarative) [![GitHub Actions: test](https://github.com/tanakh/optparse-declarative/workflows/test/badge.svg)](https://github.com/tanakh/optparse-declarative/actions?query=workflow%3Atest) [![Join the chat at https://gitter.im/optparse-declarative/community](https://badges.gitter.im/optparse-declarative/community.svg)](https://gitter.im/optparse-declarative/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) `optparse-declarative` is a declarative and easy-to-use command-line option parser. # Install ```bash $ cabal install optparse-declarative ``` # Usage ## Writing a simple command First, you need to enable `DataKinds` extension. Then import `Options.Declarative` module. ```hs {-# LANGUAGE DataKinds #-} import Options.Declarative ``` Next, define command line options as a **type of the function**. For example, this is a simple greeting program with `-g` option that takes a message of type `String` and an unnamed command-line argument that specifies a name: ```hs greet :: Flag "g" '["greet"] "STRING" "greeting message" (Def "Hello" String) -> Arg "NAME" String -> Cmd "Greeting command" () greet msg name = liftIO $ putStrLn $ get msg ++ ", " ++ get name ++ "!" ``` There are two types of options, `Flag` and `Arg`. `Flag` represents a named argument (e.g., `--greet "Hola"`), and `Arg` an unnamed argument (e.g., `John` of `greet --greet Hola John`). The last argument of `Flag` and `Arg` is the type of the value of the argument; in this example, they are both `String`. You can specify any type for the value as long as the type is an instance of `ArgRead` typeclass, in which the conversion function from `String` to the specified type is defined. `Options.Declarative` provides following instances of `ArgRead` typeclass. - Int - Integer - Bool - Double - String - (ArgRead a) => Maybe a Users can add a new instance of `ArgRead` to support any user-defined type. Please see Section "How to add a new instance of `ArgRead`" for details. If you wish to specify a default value for allowing users to omit a value, use the modifier `Def` with the default value as the second type argument (and the third type argument is the type of the value). You need to specify the default value in `String` instead of the final value of the target type; the string will be converted to the final value via `ArgRead` typeclass. In the example above, the variable `msg` has a very complex type (`Flag "g" '["greet"] "STRING" "greeting message" (Def "Hello" String)`). In order to get the value of the target type (in this case, that is `String`), you can use `get` function. The whole type of command is `Cmd`. `Cmd` is an instance of `MonadIO` and it has some extra information. Finally, you can run the whole program by `run_`. ```hs main :: IO () main = run_ greet ``` Here is an example session with the program shown above. ```bash $ ghc simple.hs $ ./simple simple: not enough arguments Try 'simple --help' for more information. $ ./simple --help Usage: simple [OPTION...] NAME Options: -g STRING --greet=STRING greeting message -? --help display this help and exit $ ./simple World Hello, World! $ ./simple --greet=Goodbye World Goodbye, World! ``` Note that only the final option is used when multiple options of the same name are given. This behavior emulates the behavior of a naive program that uses GNU Getopt. ```bash $ ./simple --greet=Hello --greet=Goodbye World Goodbye, World! ``` There is another way of interpreting multiple options of the same name. Suppose if you need to get multiple values from the same option. Say, you wish to get `["Hello", "Goodbye"]` from the command-line option `--greet=Hello --greeet=Goodbye`. Then, you can use the type `[]` to indicate that it accepts multiple values. The first line of the function `greet` in the example above would be changed as this: ```hs greet :: Flag "g" '["greet"] "STRING" "greeting message" [String] ``` The value returned by `get` will be a value of type `[String]`. See the complete working example at `example/list.hs` for details. ## Writing multiple subcommands You can write (nested) subcommands. You don't know what subcommands are? Imagine `git` command. `git` has subcommands such as `git add`, `git commit`, `git log`, etc. `git` has nested subcommands such as `git remote add`, `git remote rm`, etc. `optparse-declarative` provides an easy way to provide such possibly nested subcommands. Just group subcommands by `Group`, then you get a subcommand parser. Here is an example with two subcommands `greet` and `connect`: ```hs {-# LANGUAGE DataKinds #-} import Options.Declarative main :: IO () main = run_ $ Group "Test program for library" [ subCmd "greet" greet , subCmd "connect" connect ] greet :: Flag "g" '["greet"] "STRING" "greeting message" (Def "Hello" String) -> Flag "" '["decolate"] "" "decolate message" Bool -> Arg "NAME" String -> Cmd "Greeting command" () greet msg deco name = do let f x | get deco = "*** " ++ x ++ " ***" | otherwise = x liftIO $ putStrLn $ f $ get msg ++ ", " ++ get name ++ "!" connect :: Flag "h" '["host"] "HOST" "host name" (Def "localhost" String) -> Flag "p" '["port"] "PORT" "port number" (Def "8080" Int ) -> Cmd "Connect command" () connect host port = do let addr = get host ++ ":" ++ show (get port) liftIO $ putStrLn $ "connect to " ++ addr ``` This is a sample session for the program above: ```bash $ ./subcmd --help Usage: subcmd [OPTION...] [ARGS...] Options: -? --help display this help and exit Commands: greet Greeting command port Server command $ ./subcmd connect --port=1234 connect to localhost:1234 ``` If you wish to specify the program name or the version number, use `run` instead of `run_`. The first argument of `run` is a program name (of type `String`). The second argument is a version number (of type `Maybe String`). ```hs main :: IO () main = run "program_name" (Just "1.3.2") $ Group "Test program for library" [ subCmd "greet" greet , subCmd "connect" connect ] ``` For more examples, please see `example` directory. ## Default options `optparse-declarative` provides a few default options. For example, `--help` is defined automatically so users do not have to write it by their own. If run with `run` and the version number, `--version` is defined automatically. Also, `--verbosity` option (`-v` for short) is defined by default. `getVerbosity` returns the verbosity level in `Int`. `-v` gives 1, `-vv` gives 2, `-vvv` gives 3. Alternatively, `--verbose=3` would yield 3. ## How to add a new instance of `ArgRead` Users need to create an instance of `ArgRead` for supporting a new type for the command line argument. Here is the definition of class `ArgRead`. ```hs class ArgRead a where -- | Type of the argument type Unwrap a :: * type Unwrap a = a -- | Get the argument's value unwrap :: a -> Unwrap a default unwrap :: a ~ Unwrap a => a -> Unwrap a unwrap = id -- | Argument parser argRead :: [String] -> Maybe a default argRead :: Read a => [String] -> Maybe a argRead ss = getLast $ mconcat $ Last . readMaybe <$> ss -- | Indicate this argument is mandatory needArg :: Proxy a -> Bool needArg _ = True ``` Suppose you are adding a support for your type `T`. We explain which function to define explicitly, depending on the property of `T`. If `T` is the type of the final value you take out of a command line, you do not have to define `Unwrap`. If `T` is a wrapper like `Def`, define `type Unwrap T = `. For `Def x y`, `type Unwrap (Def x y) = y`. If you defined `Unwrap`, define `unwrap` that takes an actual value out of the wrapped value. `argRead` is the main function that converts String into a value. If the type is an instance of `Read` and you are satisfied with how `read` converts a `String` into value, there is no need to define your own `argRead`. Otherwise, you define a function that converts a `String` into a value of the target type. When parsing is successful, return `Just`. When it fails, return `Nothing`. If the input is `[]`, it indicates the option does not have an argument; otherwise the input is a list of a single `String`. Last but not least, define `needArg _ = False` when the option allows us to omit the associated value; consider a boolean option like `--help`.