Search code examples
haskelloptparse-applicative

subcommand help using optparse applicative


I am having two issues with optparse-applicative: (I put the issues together, since it is likely there is one underlying mis-conception).

  1. It is saying that the usage is : (COMMAND | COMMAND)

    crime cli - process CRIME template and deploy commands

    Usage: (COMMAND | COMMAND) The CLI for CRIME

I would like it to actually list the name of the program (not interactive) and also list the COMMANDS, but I don't grok how to add the meta data for a particular subcommand.

  1. When I try to get help for a particular subcommand, I am getting the message about missing required arguments. (

stack exec commandline-exe -- DeployStack --help

> Invalid option `--help'
> 
> Usage: <interactive> DeployStack (-d|--deployment DEPLOYMENT)
>                                  (-t|--template TEMPLATE) 
>                                  [-p|--provider PROVIDER]   Deploy a Stack

Here is how I am building the help:

-- For information about how this was done see: https://stackoverflow.com/questions/36339103/using-optparse-applicative-with-multiple-subcommands-and-global-options
-- and https://hackage.haskell.org/package/optparse-applicative
module CLIParser
  ( Actions(..)
  , actions
  ) where

import           Data.Semigroup      ((<>))
import           Options.Applicative
import           Text.Show.Functions

data Actions
  = CreateTemplate
      { name :: String
      , path :: String
      }
  | DeployStack
      { deployment :: String
      , template   :: String
      , provider   :: String
      }
  deriving (Show)

actions :: Parser Actions
actions =
  subparser
    (command
       "CreateTemplate"
       (info templateCreator (progDesc "Create Template")) <>
     commandGroup "Template commands:") <|>
  subparser
    (command "DeployStack" (info deployStack (progDesc "Deploy a Stack")) <>
     commandGroup "Deploy commands:")

templateCreator :: Parser Actions
templateCreator = CreateTemplate <$> nameArg <*> pathArg

deployStack :: Parser Actions
deployStack = DeployStack <$> deployArg <*> templateArg <*> providerArg

nameArg :: Parser String
nameArg =
  strOption
    (long "name" <> metavar "NAME" <> short 'n' <> help "Name to give template")

pathArg :: Parser String
pathArg =
  strOption
    (long "path" <> metavar "PATH" <> short 'p' <> help "Path to template file")

deployArg :: Parser String
deployArg =
  strOption
    (long "deployment" <>
     metavar "DEPLOYMENT" <> short 'd' <> help "Name of deployment")

templateArg :: Parser String
templateArg =
  strOption
    (long "template" <>
     metavar "TEMPLATE" <> short 't' <> help "Template for deployement")

providerArg :: Parser String
providerArg =
  strOption
    (long "provider" <>
     metavar "PROVIDER" <>
     short 'p' <> showDefault <> value "AWS" <> help "Cloud Provider (e.g. AWS)")

** Main.hs **

module Main where

import CLIParser
import Options.Applicative
import System.IO

options :: ParserInfo Actions
options = info (actions <**> helper)
  ( fullDesc
    <> progDesc "The CLI for CRIME"
    <> header "crime cli - process CRIME template and deploy commands" )

main :: IO ()
main = execParser options >>= display


display :: Show a => a -> IO ()
display x = print x

Solution

  • The solution to getting the USAGE (COMMAND | COMMAND) is to use metavars in the command subgroup.

    To get the help on each command, use hsubcommand not command.

    Solutions shown below:

    D:\CRIME\commandLine>stack exec crimeCLI -- --help
    crime cli - process CRIME template and deploy commands
    
    Usage: crimeCLI.EXE (Template COMMAND | Deployment COMMAND)
      (e.g. stack exec crimeCLI -- TemplateCreate -name NAME -path PATHS)
    
    Available options:
      -h,--help                Show this help text
    
    Template commands:
      TemplateCreate           Create Template
      TemplateDetails          Show Details about a Template
      TemplateList             List all Templates for this user
    
    Deploy commands:
      DeployStack              Deploy a Stack to a Provider
      DeployRemove             Remove a deployed stack from a Provider
    

    and here is the code:

    -- For information about how this was done see: https://stackoverflow.com/questions/36339103/using-optparse-applicative-with-multiple-subcommands-and-global-options
    -- and https://hackage.haskell.org/package/optparse-applicative
    module CLIParser
      ( Actions(..)
      , actions
      ) where
    
    import           Data.Semigroup      ((<>))
    import           Options.Applicative
    import           Text.Show.Functions
    
    data Actions
      = TemplateCreate
          { name :: String
          , path :: String
          }
      | TemplateDelete
          { name :: String
          , path :: String
          }
      | TemplateDetails
          { name :: String
          }
      | TemplateList
      | DeployStack
          { deployment :: String
          , template   :: String
          , provider   :: String
          }
      | DeployRemove
          { deployment :: String
          , template   :: String
          , provider   :: String
          }
      deriving (Show)
    
    actions :: Parser Actions
    actions =
      hsubparser
        (command
           "TemplateCreate"
           (info templateCreator (progDesc "Create Template")) <>
         command
           "TemplateDetails"
           (info templateDetails (progDesc "Show Details about a Template")) <>
         command
           "TemplateList"
           (info templateList (progDesc "List all Templates for this user")) <>
         commandGroup "Template commands:" <> (metavar "Template COMMAND")) <|>
      hsubparser
        (command
           "DeployStack"
           (info deployStack (progDesc "Deploy a Stack to a Provider")) <>
         command
           "DeployRemove"
           (info deployRemove (progDesc "Remove a deployed stack from a Provider")) <>
         commandGroup "Deploy commands:"  <> (metavar "Deployment COMMAND"))
    
    templateCreator :: Parser Actions
    templateCreator = TemplateCreate <$> nameArg <*> pathArg
    
    templateDetails :: Parser Actions
    templateDetails = TemplateDetails <$> nameArg
    
    templateList :: Parser Actions
    templateList = pure TemplateList
    
    deployStack :: Parser Actions
    deployStack = DeployStack <$> deployArg <*> templateArg <*> providerArg
    
    deployRemove :: Parser Actions
    deployRemove = DeployRemove <$> deployArg <*> templateArg <*> providerArg
    
    nameArg :: Parser String
    nameArg =
      strOption
        (long "name" <> metavar "NAME" <> short 'n' <> help "Name to give template")
    
    pathArg :: Parser String
    pathArg =
      strOption
        (long "path" <> metavar "PATH" <> short 'p' <> help "Path to template file")
    
    deployArg :: Parser String
    deployArg =
      strOption
        (long "deployment" <>
         metavar "DEPLOYMENT" <> short 'd' <> help "Name of deployment")
    
    templateArg :: Parser String
    templateArg =
      strOption
        (long "template" <>
         metavar "TEMPLATE" <> short 't' <> help "Template for deployement")
    
    providerArg :: Parser String
    providerArg =
      strOption
        (long "provider" <>
         metavar "PROVIDER" <>
         short 'p' <> showDefault <> value "AWS" <> help "Cloud Provider (e.g. AWS)")