首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >我如何创建,并区分,使用‘opt解译-应用’的全局选项?

我如何创建,并区分,使用‘opt解译-应用’的全局选项?
EN

Stack Overflow用户
提问于 2018-12-13 20:05:35
回答 1查看 493关注 0票数 7

在使用optparse-applicative创建的Haskell可执行文件中,我希望在所有子命令可用的全局--help选项之外,为--version提供一个全局选项。但是,向带有子命令的CLI添加--version选项的--version(见下文)会导致一个不一致可用的--version选项。

代码语言:javascript
复制
$ cli create --version
Invalid option `--version'

Usage: cli create NAME
  Create a thing

$ cli delete --version
0.0

也从不出现在“帮助”子命令中

代码语言:javascript
复制
$ 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

我希望--version在全局和所有子命令中都可用:

代码语言:javascript
复制
$ 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

从文档中还不清楚如何实现这一点。

实际上,理想情况下,我希望能够在帮助输出中清楚地分组选项:

代码语言:javascript
复制
$ 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

是否有一种使用optparse-applicative实现这一目标的方法?

代码语言:javascript
复制
{-#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"))
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-12-20 20:11:14

据我所知,这一点(特别是分类帮助文本)对于optparse-applicative来说并不容易,因为这并不是他们计划使用全局参数的模式。如果您可以使用program --global-options command --local-options (这是一个相当标准的模式)而不是program command --global-and-local-options,那么您可以使用链接示例中所示的方法:

代码语言:javascript
复制
$ ./optparse-sub-example
optparse-sub-example - a small example program for optparse-applicative with
subcommands

Usage: optparse [--version] [--global-flag] COMMAND
  optparse subcommands example

Available options:
  -h,--help                Show this help text
  --version                Show version
  --global-flag            Set a global flag

Available commands:
  create                   Create a thing
  delete                   Delete the thing

$ ./optparse-sub-example --version create
0.0
$ ./optparse-sub-example --version delete
0.0
$ ./optparse-sub-example --global-flag create HI
Created the thing named HI
global flag: True
$ ./optparse-sub-example --global-flag delete
Deleted the thing!
global flag: True

(注:我建议采用这种方法,因为“命令之前的全局选项”相当标准)。

如果您还希望全局选项在每个子命令中可用,那么您将遇到一些问题。

  1. 据我所知,没有办法影响帮助文本输出,以便将它们单独分组到单独的命令帮助文本中。
  2. 您将需要一些自定义的subparser-like函数来添加全局选项&在命令之前将它们与任何全局选项合并。

对于#2,重构示例以支持这一点的一种方法可能是这样的:

首先,标准样板和进口:

代码语言:javascript
复制
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ApplicativeDo #-}

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

Opts被明确划分为optGlobalsoptCommand,如果有更多的选项可用,那么可以轻松地同时处理所有的全局选项:

代码语言:javascript
复制
data Opts = Opts
    { optGlobals :: !GlobalOpts 
    , optCommand :: !Command
    }
data GlobalOpts = GlobalOpts { optGlobalFlag :: Bool }

GlobalOpts应该是一个Semigroup和一个Monoid,因为我们需要合并在不同点上看到的选项(在命令之前、命令之后等等)。在对下面的mysubparser进行适当修改后,也应该可以要求只在命令之后提供全局选项,而忽略了这一要求。

代码语言:javascript
复制
instance Semigroup GlobalOpts where
  -- Code for merging option parser results from the multiple parsers run
  -- at various different places. Note that this may be run with the default
  -- values returned by one parser (from a location with no options present)
  -- and the true option values from another, so it may be important
  -- to distinguish between "the default value" and "no option" (since "no
  -- option" shouldn't override another value provided earlier, while
  -- "user-supplied value that happens to match the default" probably should).
  --
  -- In this case this doesn't matter, since the flag being provided anywhere
  -- should be enough for it to be considered true.
  (GlobalOpts f1) <> (GlobalOpts f2) = GlobalOpts (f1 || f2)
instance Monoid GlobalOpts where
  -- Default values for the various options. These should probably match the
  -- defaults used in the option declarations.
  mempty = GlobalOpts False

如前所述,表示不同可能的命令的Command类型:

代码语言:javascript
复制
data Command
    = Create String
    | Delete

真正的神奇之处是:mysubparser封装hsubparser以添加全局选项并处理合并它们。它将全局选项的解析器作为参数:

代码语言:javascript
复制
mysubparser :: forall a b. Monoid a
            => Parser a
            -> Mod CommandFields b
            -> Parser (a, b)
mysubparser globals cmds = do

首先,它运行全局解析器(捕捉命令之前给出的全局数据):

代码语言:javascript
复制
  g1 <- globals

然后使用hsubparser获取命令解析器,并对其进行修改以解析全局选项:

代码语言:javascript
复制
  (g2, r) <- addGlobals $ hsubparser cmds

最后,它合并两个全局选项集,并返回解析的全局选项和命令解析器结果:

代码语言:javascript
复制
  pure (g1 <> g2, r)
  where 

addGlobals助手函数:

代码语言:javascript
复制
        addGlobals :: forall c. Parser c -> Parser (a, c)

如果给出了NilP,我们只需使用mempty获得默认选项集:

代码语言:javascript
复制
        addGlobals (NilP x) = NilP $ (mempty,) <$> x

重要的情况是:如果在使用OptPOption周围有一个CommandReader,则将globals解析器添加到每个命令解析器中:

代码语言:javascript
复制
        addGlobals (OptP (Option (CmdReader n cs g) ps)) =
          OptP (Option (CmdReader n cs $ fmap go . g) ps)
          where go pi = pi { infoParser = (,) <$> globals <*> infoParser pi }

在所有其他情况下,只需使用默认选项集,或酌情使用递归Parser中的合并选项集:

代码语言:javascript
复制
        addGlobals (OptP o) = OptP ((mempty,) <$> o)
        addGlobals (AltP p1 p2) = AltP (addGlobals p1) (addGlobals p2)
        addGlobals (MultP p1 p2) =
          MultP ((\(g2, f) -> \(g1, x) -> (g1 <> g2, f x)) <$> addGlobals p1)
                (addGlobals p2)
        addGlobals (BindP p k) = BindP (addGlobals p) $ \(g1, x) ->
                                   BindP (addGlobals $ k x) $ \(g2, x') ->
                                     pure (g1 <> g2, x')

main函数的修改非常小,并且大多与使用新的GlobalOpts有关。一旦GlobalOpts的解析器可用,将其传递给mysubparser非常容易:

代码语言:javascript
复制
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 (optGlobals opts)))
  where
    optsParser :: ParserInfo Opts
    optsParser =
        info
            (helper <*> 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")
    globalOpts :: Parser GlobalOpts
    globalOpts = versionOption <*>
      (GlobalOpts <$> switch (long "global-flag" <> help "Set a global flag"))
    programOptions :: Parser Opts
    programOptions =
      uncurry Opts <$> mysubparser globalOpts (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"))

注意,mysubparser应该是一个相当通用/可重用的组件。

这显示了更接近您想要的行为:

代码语言:javascript
复制
$ ./optparse-sub-example create --global-flag HI
Created the thing named HI
global flag: True
$ ./optparse-sub-example --global-flag create HI
Created the thing named HI
global flag: True
$ ./optparse-sub-example --global-flag delete
Deleted the thing!
global flag: True
$ ./optparse-sub-example delete --global-flag
Deleted the thing!
global flag: True
$ ./optparse-sub-example delete
Deleted the thing!
global flag: False
$ ./optparse-sub-example delete --version
0.0
$ ./optparse-sub-example create --version
0.0
票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53769310

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档