@hackage commander-cli0.10.0.0

A command line argument/option parser library

Commander CLI

Hackage Build Status

This library is meant to allow Haskell programs to quickly and easily construct command line interfaces which are easy to use, especially as a Haskell user. To learn, I suggest viewing/playing with the task-manager application which comes with this repository. Here, we'll display a simpler example:

main = command_ . toplevel @"argument-taker" . arg @"example-argument" $ raw . putStrLn

When you run this program with argument-taker help, you will see:

usage:
name: argument-taker
|
+- subprogram: help
|
`- argument: example-argument :: [Char]

The meaning of this is that every path in the tree is a unique command. The one we've used is the help command. If we run this program with argument-taker hello we will see:

hello

Okay, so we've made a program with hardly any scaffolding that gives us a decent help message, and pipes through our argument correctly. Naturally, we might want to expand on the documentation of this program, as its not quite obvious enough what it does.

main = command_ . toplevel @"argument-taker" . arg @"example-argument" $ (description @"Takes the argument and prints it" . raw . putStrLn)

Printing out the documentation again with argument-taker help, we see:

usage:
name: argument-taker
|
+- subprogram: help
|
`- argument: example-argument :: [Char]
   |
   `- description: Takes the argument and prints it

Okay, so we can expand the documentation. But what if I have an option to pass to the same program? Well, we can pass an option like so:

main = command_ . toplevel @"argument-taker" $
  opt @"m" @"mode" \mode ->
    arg @"example-argument" $ \arg ->
      description @"Takes the argument and prints it or not, depending on the mode" . raw $ do
        if mode == "Print" then putStrLn arg else pure ()

Now, when we run argument-taker help we will see:

usage:
name: argument-taker
|
+- subprogram: help
|
`- option: -m <mode :: [Char]>
   |
   `- argument: example-argument :: [Char]
      |
      `- description: Takes the argument and prints it or not, depending on the mode

Design

The library is based around the following classes:

class Unrender r where
  unrender :: Text -> Maybe r

This class is what you will use to define the parsing of a type from text and can use any parsing library or whatever you want. Next, we have the class

class HasProgram p where
  data ProgramT p m a
  run :: ProgramT p IO a -> CommanderT State IO a
  hoist :: (forall x. m x -> n x) -> ProgramT p m a -> ProgramT p n a
  documentation :: Forest String

Instances of this class will define a syntactic element, a new instance of the data family ProgramT, as well as its semantics in terms of the CommanderT monad, which is something like a free backtracking monad. Users should not have to make instances of this class, as the common CLI elements are already defined as instances. Of course, you can if you want to, and it can be profitable to do so.