In my Haskell executable, created using optparse-applicative, I would like to have a global option for --version alongside the global --help option that is available from all subcommands. However the example provided (see below) for adding a --version option to to a CLI with subcommands results in a --version option that is inconsistently available
$ cli create --version
Invalid option `--version'
Usage: cli create NAME
  Create a thing
$ cli delete --version
0.0
and never shows up in help for subcommands
$ cli create -h
Usage: cli create NAME
  Create a thing
Available options:
  NAME                     Name of the thing to create
  -h,--help                Show this help text
$ cli delete -h
Usage: cli delete 
  Delete the thing
Available options:
  -h,--help                Show this help text
The behavior I would like is for --version to be available globally and to all subcommands:
$ cli create -h
Usage: cli create NAME
  Create a thing
Available options:
  NAME                     Name of the thing to create
  --version                Show version
  -h,--help                Show this help text
$ cli delete -h
Usage: cli delete 
  Delete the thing
Available options:
  --version                Show version
  -h,--help                Show this help text
$ cli create --version
0.0
$ cli delete --version
0.0
It's not clear from the documentation how to achieve this.
In fact, I'd ideally like to be able to clearly group options in the help output:
$ cli create -h
Usage: cli create NAME
  Create a thing
Arguments:
  NAME                     Name of the thing to create
Global options:
  --version                Show version
  -h,--help                Show this help text
$ cli delete -h
Usage: cli delete 
  Delete the thing
Global options:
  --version                Show version
  -h,--help                Show this help text
Is there a way to achieve this using optparse-applicative?
{-#LANGUAGE ScopedTypeVariables#-}
import Data.Semigroup ((<>))
import Options.Applicative
data Opts = Opts
    { optGlobalFlag :: !Bool
    , optCommand :: !Command
    }
data Command
    = Create String
    | Delete
main :: IO ()
main = do
    (opts :: Opts) <- execParser optsParser
    case optCommand opts of
        Create name -> putStrLn ("Created the thing named " ++ name)
        Delete -> putStrLn "Deleted the thing!"
    putStrLn ("global flag: " ++ show (optGlobalFlag opts))
  where
    optsParser :: ParserInfo Opts
    optsParser =
        info
            (helper <*> versionOption <*> programOptions)
            (fullDesc <> progDesc "optparse subcommands example" <>
             header
                 "optparse-sub-example - a small example program for optparse-applicative with subcommands")
    versionOption :: Parser (a -> a)
    versionOption = infoOption "0.0" (long "version" <> help "Show version")
    programOptions :: Parser Opts
    programOptions =
        Opts <$> switch (long "global-flag" <> help "Set a global flag") <*>
        hsubparser (createCommand <> deleteCommand)
    createCommand :: Mod CommandFields Command
    createCommand =
        command
            "create"
            (info createOptions (progDesc "Create a thing"))
    createOptions :: Parser Command
    createOptions =
        Create <$>
        strArgument (metavar "NAME" <> help "Name of the thing to create")
    deleteCommand :: Mod CommandFields Command
    deleteCommand =
        command
            "delete"
            (info (pure Delete) (progDesc "Delete the thing"))
				
                        
As far as I know, this (in particular, the categorized help text) isn't really easy to do with
optparse-applicative, since it isn't quite the pattern that they were planning for with global arguments. If you are okay with usingprogram --global-options command --local-options(which is a fairly standard pattern) instead ofprogram command --global-and-local-options, then you can use the approach shown in the linked example:(Note: I would advise going with this approach, since "global options before the command" is fairly standard).
If you also want the global options to be available in every subcommand, you will have a few issues.
subparser-like function that adds your global options & merges them with any global options before the command.For #2, one way to restructure the example to support this might be something along these lines:
To start with, standard boilerplate and imports:
Optsare explicitly split intooptGlobalsandoptCommand, making it easy to deal with all of the global options at once if more are available:GlobalOptsshould be aSemigroupand aMonoid, since we need to merge options seen at various different points (before the command, after the command, etc.). It should also be possible, with suitable alterations tomysubparserbelow, to require global options to be given only after commands and omit this requirement.As before, a
Commandtype to represent the different possible commands:The real magic:
mysubparserwrapshsubparserto add global options and deal with merging them. It takes the parser for global options as an argument:To start with, it runs the global parser (to catch any globals given before a command):
It then uses
hsubparserto get a command parser, and modifies it to also parse global options:Finally, it merges the two global option sets, and returns the parsed global options and the command parser result:
The
addGlobalshelper function:If
NilPwas given, we just usememptyto get the default option set:The important case: if we have an
OptParound anOptionthat uses aCommandReader, theglobalsparser is added to every command parser:In all other cases, either just use the default option set, or merge option sets from recursive
Parsers as appropriate:Modifications to the
mainfunction are fairly minimal, and mostly related to using the newGlobalOpts. Once a parser forGlobalOptsis available, passing it tomysubparseris quite easy:Notice that
mysubparsershould be a quite generic/reusable component.This exhibits behavior closer to what you wanted: