Home > OS >  (Sub)command synonyms for optparse-applicative
(Sub)command synonyms for optparse-applicative

Time:11-24

I would like to add synonyms for the subcommands in my Haskell command line tool. For example summarise and summarize should yield the same result. Of course I could just add an entirely separate command summarize, that appears as an own element in the output of --help. But maybe there is a more elegant way.

Here is a self-contained example in a stack script opt_ex.hs:

#!/usr/bin/env stack
-- stack --resolver lts-18.17 script --package optparse-applicative

import Options.Applicative
import Data.Semigroup ((<>))

data Options = CmdGreet GreetArgs | CmdGroot GreetArgs

newtype GreetArgs = GreetArgs String

main :: IO ()
main = do
    cmdOpts <- customExecParser (prefs showHelpOnEmpty) (info optParser fullDesc)
    runCmd cmdOpts

optParser :: Parser Options
optParser = subparser (
    command "greet" (info (CmdGreet <$> sample) (progDesc "Print greeting 1")) <>
    command "groot" (info (CmdGroot <$> sample) (progDesc "Print greeting 2"))
    )

runCmd :: Options -> IO ()
runCmd o = case o of
    CmdGreet opts -> greet opts
    CmdGroot opts -> groot opts

greet :: GreetArgs -> IO ()
greet (GreetArgs h) = putStrLn $ "Hello, "    h    "!"

groot :: GreetArgs -> IO ()
groot (GreetArgs h) = putStrLn $ "Howdy, "    h    "!"

sample :: Parser GreetArgs
sample = GreetArgs <$> strArgument ( metavar "TARGET" )

You can run this with ./opt_ex.hs greet John to get Hello, John! and with ./opt_ex.hs groot John to get Howdy, John!. Running ./opt_ex.hs will give you the following overview:

Usage: opt_ex.hs COMMAND

Available commands:
  greet                    Print greeting 1
  groot                    Print greeting 2

What would be the most elegant way, to add a command gruut in this example, which behaves exactly like greet, but produces the least amount of overhead, both in the code and for the user?

Ideally I would like ./opt_ex.hs to yield something like this:

Usage: opt_ex.hs COMMAND

Available commands:
  greet|gruut              Print greeting 1
  groot                    Print greeting 2

CodePudding user response:

I don't think you can do this. It works fine for options, because the definition of OptField contains a list of OptName, and adds to that list when you use (<>). But the definition of CommandFields, the thing returned by command, is

data CommandFields a = CommandFields
  { cmdCommands :: [(String, ParserInfo a)]
  , cmdGroup :: Maybe String }

Each String name is thus associated with a different ParserInfo. Of course, you can define a variable containing any ParserInfo you like, and reuse it across two commands, so you won't have to repeat the ParserInfo. But as far as optparse-applicative is concerned, those two commands are distinct, so it will list them separately in the help text. For your example, this would look like

optParser = let greeting1 = info (CmdGreet <$> sample) (progDesc "Print greeting 1")
            in subparser $
               command "greet" greeting1 <>
               command "gruut" greeting1 <>
               command "groot" (info (CmdGroot <$> sample) (progDesc "Print greeting 2"))

and indeed, when run we see both commands listed:

Usage: optparse COMMAND

Available commands:
  greet                    Print greeting 1
  gruut                    Print greeting 1
  groot                    Print greeting 2
  • Related