Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using optparse-applicative with multiple subcommands and global options

I am writing a commandline program that takes multiple subcommands, which take flags/arguments.

The program should also take some 'global-flags' that are applicable to all subcommands. For examples:

myProgram --configfile=~/.customrc UPLOADFILE --binary myfile.x
myProgram --configfile=~/.customrc SEARCH --regex "[a-z]+"

in this example, the subcommands are UPLOADFILE and SEARCH, and configfile is relevant to both subcommands, and binary and regex applicable to the specific subcommands.

I feel this must be possible with this library, but I am struggling to work out what to put where! I am relatively new to Haskell, trying to get my head around applicatives, and it making my brain hurt :)

There is an example of subcommands here in the documentation for the module, but I can't seem to work out how to get the global-flags to work.

If someone could point me towards a small working example, or insight into how I shoud structure code to do this, I would be very grateful, I am finding these higher order functions slightly magical!

Many thanks for your time. Best wishes,

Mike

like image 500
user2633351 Avatar asked Mar 31 '16 16:03

user2633351


1 Answers

The documentation you linked has this to say:

Commands are useful to implement command line programs with multiple functions, each with its own set of options, and possibly some global options that apply to all of them.

Although it does not say precisely how those options should be applied. If you look at the types, however, you can gain some insight. The example indicates to use subparser, whose type is Mod CommandFields a -> Parser a. This probably doesn't say much (especially the left hand side) but crucially this function just produces a Parser - so you can combine it with other parsers as you normally would:

data Subcommand 
  = Upload { binary :: String } 
  | Search { regex  :: String } deriving Show 

data Options = Options 
  { configFile :: FilePath 
  , subcommand :: Subcommand 
  } deriving Show 

commandO = Options <$> configFileO <*> subcommandO 

configFileO = strOption
   ( long "configfile"
  <> help "Filepath of configuration file" 
   )

subcommandO :: Parser Subcommand
subcommandO = subparser ...

Defining subcommands themselves is fairly straightforward - I just copied the example from the docs and renamed some things in accordance with your specific example:

subcommandO = 
  subparser
    ( command "UPLOADFILE" (info uploadO
        ( progDesc "Upload a file" ))
   <> command "SEARCH" (info searchO
        ( progDesc "Search in a file" ))
   )

uploadO = Upload <$> 
  ( strOption
     ( long "binary"
    <> help "Binary file to upload" 
     )
  )

searchO = Upload <$> 
  ( strOption
     ( long "regex"
    <> help "Regular expression to search for" 
     )
  )

main = execParser opt >>= print where 
  opt = info (helper <*> commandO)
     ( fullDesc
    <> progDesc "Example for multiple subcommands"
    <> header "myProgram" )

Running this program gives the following:

>:main --configfile=~/.customrc UPLOADFILE --binary myfile.x
Options {configFile = "~/.customrc", subcommand = Upload {binary = "myfile.x"}}

>:main --configfile=~/.customrc SEARCH --regex "[a-z]+"
Options {configFile = "~/.customrc", subcommand = Upload {binary = "[a-z]+"}}

>:main --help
myProgram

Usage: <interactive> --configfile ARG COMMAND
  Example for multiple subcommands

Available options:
  -h,--help                Show this help text
  --configfile ARG         Filepath of configuration file

Available commands:
  UPLOADFILE               Upload a file
  SEARCH                   Search in a file
*** Exception: ExitSuccess
like image 139
user2407038 Avatar answered Oct 16 '22 02:10

user2407038