Jonathan Prieto-Cubides
Jonathan Prieto-Cubides

Reputation: 2847

Implementing another kind of flags in Haskell

We have the classic flags in the command line tools, those that enable something (without arguments, e.g --help or --version) and others kind of flags that accept arguments (e.g. --output-dir=/home/ or --input-file="in.a", whatever).

But this time, I would like to implement the following kind of.

$ myprogram --GCC-option="--stdlib 11" --debug

In a general way, the flag is like "--PROGRAM-option=ARGUMENT". Then, I keep from this flag, PROGRAM and ARGUMENT values, they are variables. In the example above, we have PROG=GCC and ARGUMENT=--stdlib 11.

How should can I implement this feature in Haskell? I have some experience parsing options in the classic way.

Upvotes: 0

Views: 141

Answers (1)

trevor cook
trevor cook

Reputation: 1600

In a recent project of mine I used an approach based on a 'Data.Tree' of option handling nodes. Of course, I haven't released this code so it's of very limited use, but I think the scheme might be helpful.

data OptHandle = Op { optSat :: String -> Bool
                    , opBuild :: [String] -> State Env [String]
                    }

The node fields: check to see if the argument satisfied the node; and incrementally built up an initial program environment based on the the remaining arguments (returning unused arguments to be processed by nodes lower in the tree.)

An option processing tree is then hard coded, such as below.

pgmOptTree :: [Tree OptHandle] 
pgmOptTree = [mainHelpT,pgmOptT,dbgT]

mainHelpT :: Tree OptHandle
mainHelpT = Node (Op sat bld) []
  where
    sat "--help" = True
    sat _ = False
    bld _ = do
      mySetEnvShowHelp
      return []

pgmOptT :: Tree OptHandle
pgmOptT = Node (Op sat bld) [dbgT]
  where
    sat = functionOn . someParse
    bld ss = do
       let (d,ss') = parsePgmOption ss
       mySetEnvPgmOpt d 
       return ss' 

You will also need a function which feeds the command line arguments to the tree, checking satisfiability of each node in a forest, executing the opBuild, and calling subforests. After running the handler in the state monad, you should be returned an initial starting environment which can be used to tell main the functionality you want to call.

The option handler I used was actually a little more complicated than this, as my program communicated with Bash to perform tab completions, and included help for most major options. I found the benefit to the approach was that I could more easily keep in sync three command line concerns: enabling tab completions which could inform users the next available commands; providing help for incomplete commands; and actually running the program for complete commands.

Maintaining a tree like this is nice because you can reuse nodes at different points, and add options that work with the others fairly easily.

Upvotes: 2

Related Questions