gpt4 book ai didi

haskell - 如何使用 'optparse-applicative' 创建和区分全局选项?

转载 作者:行者123 更新时间:2023-12-04 12:27:07 24 4
gpt4 key购买 nike

在我的 Haskell 可执行文件中,使用 optparse-applicative 创建, 我想要 --version 的全局选项与全局一起--help可从所有子命令获得的选项。然而 example provided (见下文)用于添加 --version带有子命令的 CLI 选项会导致 --version不一致可用的选项

$ cli create --version
Invalid option `--version'

Usage: cli create NAME
Create a thing

$ cli delete --version
0.0

并且永远不会出现在子命令的帮助中
$ 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全局可用并适用于所有子命令:
$ 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

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

事实上,理想情况下,我希望能够在帮助输出中清楚地对选项进行分组:
$ 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 来实现这一点? ?
{-#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"))

最佳答案

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

$ ./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

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

如果您还希望在每个子命令中都可以使用全局选项,那么您将遇到一些问题。
  • 据我所知,没有办法影响帮助文本输出以便将它们单独分组到各个命令帮助文本中。
  • 您将需要一些自定义 subparser - 类似功能,可添加您的全局选项并将它们与命令之前的任何全局选项合并。

  • 对于#2,重组示例以支持这一点的一种方法可能是以下几条:

    首先,标准样板和导入:
    {-# LANGUAGE ScopedTypeVariables #-}
    {-# LANGUAGE TupleSections #-}
    {-# LANGUAGE ApplicativeDo #-}

    import Data.Monoid
    import Data.Semigroup ((<>))
    import Options.Applicative
    import Options.Applicative.Types
    Opts被明确分成 optGlobalsoptCommand ,如果有更多可用选项,则可以轻松地一次处理所有全局选项:
    data Opts = Opts
    { optGlobals :: !GlobalOpts
    , optCommand :: !Command
    }
    data GlobalOpts = GlobalOpts { optGlobalFlag :: Bool }
    GlobalOpts应该是 SemigroupMonoid ,因为我们需要合并在各个不同点(命令之前,命令之后等)看到的选项。也应该可以,对 mysubparser 进行适当的修改。下面,要求仅在命令之后给出全局选项并省略此要求。
    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键入以表示不同的可能命令:
    data Command
    = Create String
    | Delete

    真正的魔力: mysubparser包裹 hsubparser添加全局选项并处理合并它们。它将全局选项的解析器作为参数:
    mysubparser :: forall a b. Monoid a
    => Parser a
    -> Mod CommandFields b
    -> Parser (a, b)
    mysubparser globals cmds = do

    首先,它运行全局解析器(以捕获在命令之前给出的任何全局变量):
      g1 <- globals

    然后它使用 hsubparser获取命令解析器,并将其修改为也解析全局选项:
      (g2, r) <- addGlobals $ hsubparser cmds

    最后,它合并两个全局选项集,并返回解析后的全局选项和命令解析器结果:
      pure (g1 <> g2, r)
    where
    addGlobals辅助功能:
            addGlobals :: forall c. Parser c -> Parser (a, c)

    如果 NilP已给出,我们只使用 mempty获取默认选项集:
            addGlobals (NilP x) = NilP $ (mempty,) <$> x

    重要的情况:如果我们有一个 OptP围绕 Option使用 CommandReader , globals解析器被添加到每个命令解析器中:
            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 合并选项集视情况而定:
            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很容易:
    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应该是一个非常通用/可重用的组件。

    这表现出更接近您想要的行为:
    $ ./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

    关于haskell - 如何使用 'optparse-applicative' 创建和区分全局选项?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53769310/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com