optparse-applicative
optparse-applicative是一个Haskell库,用于解析命令行选项,并提供强大的applicative接口来组合它们。
optparse-applicative负责读取和验证传递给命令行的参数,处理和报告错误,生成使用说明行、全面的帮助屏幕,并支持上下文相关的bash、zsh和fish补全。
目录
简介
optparse-applicative的核心类型是Parser
data Parser a
instance Functor Parser
instance Applicative Parser
instance Alternative Parser
Parser a
类型的值表示一组选项的规范,当命令行参数成功解析时,将生成类型为a
的值。
如果你熟悉像parsec、attoparsec这样的解析器组合库,或者JSON解析器aeson,你会对optparse-applicative感到非常熟悉。
如果不熟悉,别担心!你只需要学习一些基本的解析器,以及如何将它们作为Applicative
和Alternative
的实例进行组合。
快速入门
这里有一个解析器的简单示例。
import Options.Applicative
data Sample = Sample
{ hello :: String
, quiet :: Bool
, enthusiasm :: Int }
sample :: Parser Sample
sample = Sample
<$> strOption
( long "hello"
<> metavar "TARGET"
<> help "Target for the greeting" )
<*> switch
( long "quiet"
<> short 'q'
<> help "Whether to be quiet" )
<*> option auto
( long "enthusiasm"
<> help "How enthusiastically to greet"
<> showDefault
<> value 1
<> metavar "INT" )
解析器使用applicative风格从一组基本组合子构建而成。在这个例子中,hello被定义为带有String
参数的选项,而quiet是一个布尔标志(称为开关),enthusiasm则借助Read
类型类被解析为Int
类型。
解析器可以这样使用:
main :: IO ()
main = greet =<< execParser opts
where
opts = info (sample <**> helper)
( fullDesc
<> progDesc "Print a greeting for TARGET"
<> header "hello - a test for optparse-applicative" )
greet :: Sample -> IO ()
greet (Sample h False n) = putStrLn $ "Hello, " ++ h ++ replicate n '!'
greet _ = return ()
greet
函数是程序的入口点,而opts
是程序的完整描述,用于生成帮助文本。helper
组合子接受任何解析器,并为其添加help
选项。
这个例子中的hello
选项是必需的,因为它没有默认值,所以在没有任何参数的情况下运行程序将显示适当的错误消息和简短的选项摘要:
Missing: --hello TARGET
Usage: hello --hello TARGET [-q|--quiet] [--enthusiasm INT]
Print a greeting for TARGET
使用--help
选项运行程序将显示完整的帮助文本,其中包含带有描述的详细选项列表
hello - a test for optparse-applicative
Usage: hello --hello TARGET [-q|--quiet] [--enthusiasm INT]
Print a greeting for TARGET
Available options:
--hello TARGET Target for the greeting
-q,--quiet Whether to be quiet
--enthusiasm INT How enthusiastically to greet (default: 1)
-h,--help Show this help text
基础知识
解析器
optparse-applicative通过其Builder接口提供了许多原始解析器,对应于不同的posix风格选项。这些在下面的专门章节中有详细说明,现在,让我们看几个更多的例子,以了解如何定义解析器。
这里是一个带参数的必需选项的解析器:
target :: Parser String
target = strOption
( long "hello"
<> metavar "TARGET"
<> help "Target for the greeting" )
可以看到,我们正在定义一个String
参数的选项解析器,带有长选项名"hello"、元变量"TARGET"和给定的帮助文本。这意味着上面定义的target
解析器将要求命令行上有如下选项:
--hello world
元变量和帮助文本将出现在生成的帮助文本中,但不会影响解析器的行为。
传递给选项的属性被称为修饰符,它们使用[半群]操作(<>)
进行组合。
像target
这样带参数的选项被称为常规选项,非常常见。另一种类型的选项是标志,其中最简单的是布尔开关,例如:
quiet :: Parser Bool
quiet = switch ( long "quiet" <> short 'q' <> help "Whether to be quiet" )
这里我们使用了short
修饰符来指定选项的单字母名称。这意味着这个开关可以用--quiet
或-q
来设置。
标志与常规选项不同,它们没有参数。它们只是返回一个预定义的值。对于上面的简单开关,如果用户输入了标志,则返回True
,否则返回False
。
还有其他类型的基本解析器,以及几种配置它们的方法。这些在构建器部分中有所介绍。
Applicative
现在我们可以将target
和quiet
组合成一个接受两个选项并返回组合值的单一解析器。给定一个类型
data Options = Options
{ optTarget :: String
, optQuiet :: Bool }
现在只需使用 Applicative
的 apply 运算符 (<*>)
来组合之前定义的两个解析器
opts :: Parser Options
opts = Options <$> target <*> quiet
无论序列中哪个解析器出现在前面,选项仍将按照它们在命令行中出现的顺序进行解析。具有这种特性的解析器有时被称为排列解析器。
在我们的例子中,像这样的命令行:
--target world -q
将会得到与以下命令行相同的结果:
-q --target world
正是这种特性使我们采用 Applicative 接口而不是 Monadic 接口,因为所有选项必须并行考虑,不能依赖于其他选项的输出。
但请注意,排序顺序仍然有一定的意义,因为它会影响生成的帮助文本。可以通过 lambda 抽象、使用 Arrow 表示法 或利用 GHC 8 的 ApplicativeDo 扩展来轻松实现自定义。
Alternative
通过命令行以不同方式配置程序也很常见。一个典型的例子是可以给程序一个文本文件作为输入,或者直接从标准输入读取。
我们可以在 Haskell 中使用和类型轻松有效地对此进行建模:
data Input
= FileInput FilePath
| StdInput
run :: Input -> IO ()
run = ...
现在我们可以为和类型的组成部分定义两个基本解析器:
fileInput :: Parser Input
fileInput = FileInput <$> strOption
( long "file"
<> short 'f'
<> metavar "FILENAME"
<> help "Input file" )
stdInput :: Parser Input
stdInput = flag' StdInput
( long "stdin"
<> help "Read from stdin" )
由于 Parser
类型构造函数是 Alternative
的实例,我们可以使用选择运算符 (<|>)
组合这些解析器
input :: Parser Input
input = fileInput <|> stdInput
现在 --file "foo.txt"
将被解析为 FileInput "foo.txt"
,--stdin
将被解析为 StdInput
,但包含两个选项的命令行,如
--file "foo.txt" --stdin
将被拒绝。
由于具有 Applicative
和 Alternative
实例,optparse-applicative 解析器还可以与标准组合子组合。例如:optional :: Alternative f => f a -> f (Maybe a)
意味着用户不需要为受影响的 Parser
提供输入。例如,如果用户未提供 output
选项,以下解析器将返回 Nothing
而不是失败:
optional $ strOption
( long "output"
<> metavar "DIRECTORY" )
运行解析器
在我们可以运行 Parser
之前,我们需要将其包装到一个 ParserInfo
结构中,该结构指定了一些仅适用于顶级解析器的属性,例如描述程序功能的标题,以在帮助屏幕中显示。
info
函数将帮助完成这一步骤。在 快速入门 中我们看到
opts :: ParserInfo Sample
opts = info (sample <**> helper)
( fullDesc
<> progDesc "Print a greeting for TARGET"
<> header "hello - a test for optparse-applicative" )
我们在 opts
后添加的 helper
解析器只是创建了一个显示帮助文本的虚拟 --help
选项。除此之外,我们只是用有意义的值设置了 ParserInfo
结构的一些字段。现在我们有了一个 ParserInfo
,终于可以运行解析器了。最简单的方法是在 main
中调用 execParser
函数:
main :: IO ()
main = do
options <- execParser opts
...
execParser
函数处理了所有事情,包括从命令行获取参数、向用户显示错误和帮助屏幕,以及以适当的退出代码退出。
在需要对解析器行为进行更精细控制的情况下,或者如果你想在纯代码中使用它,还有其他运行 ParserInfo
的方法。这些将在 自定义解析和错误处理 中介绍。
构建器
构建器允许你使用方便的基于组合子的语法定义解析器。我们已经看到了构建器的实际应用示例,如 strOption
和 switch
,我们用它们来为"hello"示例定义 opts
解析器。
构建器总是接受一个 修饰符 参数,它本质上是作用于选项的函数组合,设置属性值或添加功能。
构建器通过从头开始构建选项,并最终将其提升为单一选项解析器,准备使用普通的 Applicative
和 Alternative
组合子与其他解析器组合。
有关构建器和修饰符的完整列表,请参阅 Options.Applicative.Builder
的 haddock 文档。
optparse-applicative
中有四种不同类型的选项:常规选项、标志、参数和命令。接下来,我们将逐一介绍这些选项,并描述可用于创建它们的构建器。
常规选项
常规选项是接受单个参数、解析它并返回一个值的选项。
常规选项可以有一个默认值,如果在命令行中未找到该选项,则使用默认值作为结果。没有默认值的选项被视为必需选项,如果未找到则会产生错误。
常规选项可以有长名称或短(单字符)名称,这决定了选项何时匹配以及如何提取参数。
具有长名称的选项(比如"output")可以在命令行上指定为
--output filename.txt
或
--output=filename.txt
而短名称选项(比如"o")可以指定为
-o filename.txt
或
-ofilename.txt
选项可以有多个名称,通常是一个长名称和一个短名称,尽管你可以自由创建具有任意长短名称组合的选项。
返回字符串的常规选项最为常见,可以使用 strOption
构建器创建。例如,
strOption
( long "output"
<> short 'o'
<> metavar "FILE"
<> value "out.txt"
<> help "Write output to FILE" )
创建了一个带有字符串参数的常规选项(可以在帮助文本和文档中称为 FILE
),默认值为 "out.txt",长名称为 "output",短名称为 "o"。
常规 option
可以返回任何类型的对象,并接受一个读取器参数,指定如何解析参数。一个常见的读取器是 auto
,它需要返回类型的 Read
实例,并用它来解析参数。例如:
lineCount :: Parser Int
lineCount = option auto
( long "lines"
<> short 'n'
<> metavar "K"
<> help "Output the last K lines" )
指定一个带有 Int
参数的常规选项。我们在这里添加了显式类型注解,因为没有它解析器的输出类型将是多态的。通常不需要添加类型注解,因为类型通常可以从使用解析器的上下文中推断出来。
关于读取器的更多信息可以在下面找到。
标志
标志就像常规选项,但它不接受任何参数,它在命令行中要么存在要么不存在。
标志有一个默认值和一个激活值。如果在命令行中找到标志,则返回激活值,否则使用默认值。例如:
data Verbosity = Normal | Verbose
flag Normal Verbose
( long "verbose"
<> short 'v'
<> help "启用详细模式" )
这是一个返回 Verbosity
值的标志解析器。
简单的布尔标志可以使用 switch
构建器来指定,如下所示:
switch
( long "keep-tmp-files"
<> help "保留所有中间临时文件" )
还有一个 flag'
构建器,它没有默认值。这在我们之前的 --stdin
标志示例中演示过,通常用作替代选项的一侧。
flag'
构建器的另一个有趣用途是计算命令行中出现的实例数量,例如,可以按比例指定详细程度设置;以下解析器将计算命令行中 -v
的实例数。
length <$> many (flag' () (short 'v'))
标志可以在单个连字符后一起使用,所以 -vvv
和 -v -v -v
对于上面的解析器都会产生 3。
参数
参数解析器指定一个位置命令行参数。
argument
构建器接受一个读取器参数,并创建一个解析器,每次传递一个命令行参数且读取器成功时都会返回解析后的值。例如
argument str (metavar "FILE")
创建一个接受任何字符串的参数。要接受任意数量的参数,可以将 argument
构建器与 many
或 some
组合器结合使用:
some (argument str (metavar "FILES"))
注意,默认情况下,以 -
开头的参数被视为选项,不会被 argument
解析器考虑。
然而,解析器总是接受一个特殊参数:--
。当在命令行中找到 --
时,所有后续的单词都会被 argument
解析器考虑,无论它们是否以 -
开头。
参数使用与常规选项相同的读取器。
命令
当在命令行中遇到某个特定字符串时,可以使用命令来指定要使用的子解析器。
命令对于实现具有多个功能的命令行程序很有用,每个功能都有自己的选项集,可能还有一些适用于所有功能的全局选项。典型的例子是版本控制系统如 git
,或构建工具如 cabal
。
请注意,出现在命令中的所有解析器需要具有相同的类型。因此,最好使用一个与命令本身结构相同的和类型。例如,对于上面的解析器,你可以定义一个类似这样的类型:
data Options = Options
{ optCommand :: Command
, ... }
data Command
= Add AddOptions
| Commit CommitOptions
...
然后可以使用 subparser
构建器(或 hsubparser
,它们相同但每个命令都有一个额外的 --help
选项)创建命令,并可以使用 command
修饰符添加命令。例如,
subparser
( command "add" (info addCommand ( progDesc "向仓库添加文件" ))
<> command "commit" (info commitCommand ( progDesc "记录仓库的更改" ))
)
每个命令都接受一个完整的 ParserInfo
结构,在生成帮助文本时将用于提取该命令的描述。
或者,你可以直接从解析器返回一个 IO
动作,并使用 Control.Monad
中的 join
执行它。
start :: String -> IO ()
stop :: IO ()
opts :: Parser (IO ())
opts = subparser
( command "start" (info (start <$> argument str idm) idm)
<> command "stop" (info (pure stop) idm) )
main :: IO ()
main = join $ execParser (info opts idm)
修饰符
修饰符是 Semigroup
和 Monoid
类型类的实例,所以它们可以使用组合函数 mappend
(或简单地 (<>)
)进行组合。由于不同的构建器接受不同的修饰符集,修饰符有一个类型参数,指定哪些构建器支持它。
例如,
command :: String -> ParserInfo a -> Mod CommandFields a
只能与命令一起使用,因为 Mod
的 CommandFields
类型参数将阻止它被传递给其他类型选项的构建器。
许多修饰符在这个类型参数上是多态的,这意味着它们可以与任何构建器一起使用。
自定义解析和错误处理
解析器运行器
解析器通过 execParser
系列函数运行 — 从最容易使用到最灵活,这些函数是:
execParser :: ParserInfo a -> IO a
customExecParser :: ParserPrefs -> ParserInfo a -> IO a
execParserPure :: ParserPrefs -> ParserInfo a -> [String] -> ParserResult a
当使用 IO
函数时,获取命令行参数以及处理退出代码和失败将自动完成。当使用 execParserPure
时,可以使用以下函数:
handleParseResult :: ParserResult a -> IO a
overFailure :: (ParserHelp -> ParserHelp) -> ParserResult a -> ParserResult a
来正确设置退出代码并显示帮助信息;以及在失败时修改帮助信息(例如添加额外信息)。
选项读取器
选项和参数需要一种方法来解释命令行上传递的字符串到所需的类型。str
和 auto
读取器是最常见的方式,但也可以创建一个不使用 Read
类型类或返回 String
的自定义读取器,并用它来解析选项。自定义读取器是 ReadM
单子中的一个值。
我们提供了 eitherReader :: (String -> Either String a) -> ReadM a
便利函数来帮助创建这些值,其中 Left
将保存解析失败的错误信息。
data FluxCapacitor = ...
parseFluxCapacitor :: ReadM FluxCapacitor
parseFluxCapacitor = eitherReader $ \s -> ...
option parseFluxCapacitor ( long "flux-capacitor" )
也可以直接使用 ReadM
,使用 readerAsk
获取命令行字符串,并在 ReadM
单子中使用 readerAbort
或 readerError
退出并显示错误信息。
eitherReader
的一个很好的特性是它与 attoparsec 解析器很好地组合在一起
import qualified Data.Attoparsec.Text as A
attoReadM :: A.Parser a -> ReadM a
attoReadM p = eitherReader (A.parseOnly p . T.pack)
偏好设置
PrefsMod
可用于自定义使用文本的外观和控制其显示时机;关闭子解析器的回溯;以及开启消歧。
要使用这些修改,将它们提供给 prefs
构建器,然后将生成的偏好设置传递给接受 ParserPrefs
参数的解析器运行器之一,如 customExecParser
。
消歧
可以配置 optparse-applicative 来自动消除长选项前缀的歧义。例如,给定一个带有 --filename
和 --filler
选项的程序 foo
,输入
$ foo --fil test.txt
会失败,而输入
$ foo --file test.txt
会成功,并正确识别 "file"
为 filename
选项的无歧义前缀。
选项消歧默认是关闭的。要启用它,请按上述说明使用 disambiguate
PrefsMod
修饰符。
以下是一个最小示例:
import Options.Applicative
sample :: Parser ()
sample = () <$
switch (long "filename") <*
switch (long "filler")
main :: IO ()
main = customExecParser p opts
where
opts = info (helper <*> sample) idm
p = prefs disambiguate
注意。如果一个选项名是另一个选项的前缀,那么当启用消歧时它将永远不会被匹配。更多详情请参见 #419。
自定义帮助屏幕
optparse-applicative 有许多组合器可以帮助自定义使用文本,并确定何时显示它。
progDesc
、header
和 footer
函数可用于指定程序的简要描述或标语,以及围绕生成的选项和命令描述的详细信息。
内部实际上我们使用 prettyprinter 库,如果需要额外的自定义,可以提供文本或 prettyprinter Doc
元素。
要显示使用文本,用户可以在应用 helper
组合器到 Parser
后输入 --help
。
作者还可以使用 showHelpOnError
或 showHelpOnEmpty
偏好设置,在任何解析器失败时显示帮助文本,或在主程序或其子命令解析开始时命令不完整时显示帮助文本。
即使错误不显示帮助文本,也会显示一个特定的错误消息,指出缺少什么或无法解析什么。
myParser :: Parser ()
myParser = ...
main :: IO ()
main = customExecParser p opts
where
opts = info (myParser <**> helper) idm
p = prefs showHelpOnEmpty
命令组
对于具有多个子命令的程序,一个可能有用的实验性功能是命令组分离。
data Sample
= Hello [String]
| Goodbye
deriving (Eq, Show)
hello :: Parser Sample
hello = Hello <$> many (argument str (metavar "TARGET..."))
sample :: Parser Sample
sample = subparser
( command "hello" (info hello (progDesc "Print greeting"))
<> command "goodbye" (info (pure Goodbye) (progDesc "Say goodbye"))
)
<|> subparser
( command "bonjour" (info hello (progDesc "Print greeting"))
<> command "au-revoir" (info (pure Goodbye) (progDesc "Say goodbye"))
<> commandGroup "French commands:"
<> hidden
)
这将在逻辑上分隔两个子解析器的使用文本(如果不使用 commandGroup
修饰符,这些通常会一起出现)。hidden
修饰符抑制了第二个子解析器的元变量在简要使用行中显示,这在某些情况下是可取的。
在这个例子中,我们本质上为我们的解析器创建了同义词,但可以用它来分离常用命令和罕见命令,或安全命令和危险命令。
前面示例的使用文本如下:
Usage: commands COMMAND
Available options:
-h,--help Show this help text
Available commands:
hello Print greeting
goodbye Say goodbye
French commands:
bonjour Print greeting
au-revoir Say goodbye
Bash、Zsh 和 Fish 补全
optparse-applicative
内置支持 bash、zsh 和 fish shell 中命令行选项和参数的补全。任何使用 execParser
系列函数运行的解析器都会自动扩展几个(隐藏的)选项用于补全系统:
-
--bash-completion-script
:这需要程序的完整路径作为参数,并打印一个 bash 脚本,当源入 bash 会话时,将安装必要的机制使 bash 补全工作。为了快速测试,你可以运行类似(对于PATH
上名为foo
的程序):$ source <(foo --bash-completion-script `which foo`)
通常,
--bash-completion-script
的输出应该随程序一起发布,并在安装过程中复制到适当的目录(通常是/etc/bash_completion.d/
); -
--zsh-completion-script
:类似于 zsh; -
--fish-completion-script
:类似于 fish shell; -
--bash-completion-index
、--bash-completion-word
:补全脚本用于获取给定命令行可能补全列表的内部选项; -
--bash-completion-enriched
:一个标志,告诉补全系统随可能的补全一起发出描述。这用于为zsh
和fish
提供补全的帮助。
动作和补全器
默认情况下,选项和命令始终会被补全。因此,例如,如果程序 foo
有一个长名称为 output
的选项,输入
$ foo --ou<TAB>
将自动补全为 --output
。
参数(无论是常规选项的还是顶级的)默认不会被补全。要为参数启用补全,在常规选项或参数上使用以下修饰符之一:
completeWith
:指定从中选择的可能补全列表;action
:指定补全"动作"。动作动态确定可能补全的列表。常见的动作是"file"和"directory";完整的动作列表可以在 [bash 文档]中找到;completer
:补全器是一个函数String -> IO [String]
,返回给定字符串的所有可能补全。你可以使用这个修饰符为参数指定自定义补全。
补全修饰符可以多次使用:结果补全将调用所有修饰符并合并结果。
内部机制
当使用 execParser
运行解析器时,解析器会扩展 bashCompletionParser
,它定义了上述选项。
当触发补全时,补全脚本会使用特殊的 --bash-completion-index
和 --bash-completion-word
选项调用可执行文件。
因此,原始解析器在完成模式下运行,即在不同的 monad 上调用 runParser
,该 monad 会跟踪解析器的当前状态,并在处理完所有参数后退出。
完成 monad 在失败时返回解析器的最后状态(如果没有选项匹配),或与选项关联的补全器(如果在获取该选项的参数时失败)。
从中我们生成可能的补全列表,并将它们打印到标准输出。然后补全脚本读取这些内容,并将其放入 COMPREPLY
变量(或其他 shell 的适当替代变量)中。
Arrow 接口
也可以使用 Arrow 语法 来组合基本解析器。
当保存解析结果的结构深度嵌套,或字段顺序与应用解析器的顺序不同时,这特别有用。
使用 Options.Applicative.Arrows
模块中的函数,可以这样写,例如:
data Options = Options
{ optArgs :: [String]
, optVerbose :: Bool }
opts :: Parser Options
opts = runA $ proc () -> do
verbosity <- asA (option auto (short 'v' <> value 0)) -< ()
let verbose = verbosity > 0
args <- asA (many (argument str idm)) -< ()
returnA -< Options args verbose
其中解析器使用 asA
转换为箭头,结果组合的箭头使用 runA
转回 Parser
。
查看 tests/Examples/Cabal.hs
可以看到一个稍微复杂一点的例子,展示了如何使用箭头语法定义解析器。
请注意,提供 Arrow
接口只是为了方便。基于 Applicative
的 API 同样具有表现力,尽管在某些情况下使用起来可能会比较麻烦。
Applicative do
有些人可能会发现使用 do 表示法更容易使用 optparse-applicative。然而,由于 Parser
不是 Monad
的实例,这只能在最新版本的 GHC 中使用 ApplicativeDo 扩展来实现。例如,以这种方式指定的解析器可能是:
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ApplicativeDo #-}
data Options = Options
{ optArgs :: [String]
, optVerbose :: Bool }
opts :: Parser Options
opts = do
optVerbose <- switch (short 'v')
optArgs <- many (argument str idm)
pure Options {..}
这里我们还使用了 RecordWildCards 扩展来使解析器规范更简洁。如果编译错误提到找不到 Monad
实例,很可能是因为指定的 Parser
无法完全用 Applicative
实现(注意,在 GHC 8.0.1 中有一些关于 ApplicativeDo 的 desugaring 错误,特别是使用 ($)
的函数应用可能不起作用,pure
值应该用括号括起来)。
常见问题
-
单子式解析?
如果使用单子式风格,就无法遍历解析器并生成用法字符串,也无法允许选项以任意顺序解析。因此,optparse-applicative 有意不支持以这种方式编写
Parser
,Parser
类型也没有Monad
实例。 -
重叠的标志和选项/带可选参数的选项?
不支持这种情况,因为它可能导致解析歧义。
例如,如果我们支持并有一个可选值选项 "--foo" 和一个标志 "--bar",那么 "--foo --bar" 是带值 "--bar" 的选项,还是默认值加上开启的标志?如果不是开关而是多个位置字符串参数,第一个字符串是选项的值还是第一个位置参数?
建议改用
Parser
的Alternative
实例,并为标志、选项和默认值(使用不同的标志和选项名称)创建一个 flag'。 -
ReadM
错误时回溯?解析器结构在解析时预先确定。这意味着如果
ReadM
失败,整个解析也必须失败,我们不能考虑任何替代方案,因为无法保证剩余结构会适合。这种情况的一个偶尔令人困惑的副作用是,不同构造函数的两个位置参数不能在解析器级别组合;相反,这必须在ReadM
级别完成。例如:import Options.Applicative data S3orFile = S3 BucketKey | File FilePath s3Read, fileRead :: ReadM S3orFile s3Read = S3 <$> ... fileRead = File <$> ... correct :: Parser S3orFile correct = argument (s3Read <|> fileRead) idm incorrect :: Parser S3orFile incorrect = argument s3Read idm <|> argument fileRead idm
工作原理
应用函子 Parser
本质上是一个异构的 Option
列表或树,使用存在类型实现。
因此,所有选项都是静态已知的(即在解析之前,不一定在运行时之前),可以遍历它们以生成帮助文本。实际上,在显示解析器的用法文本时,我们使用中间树结构。
当我们检查用户输入时,会检查每个参数以确定它是选项、标志还是位置参数。然后搜索解析树以查找匹配项,如果找到,则用值本身替换树的那个叶子。处理完所有输入后,我们检查是否可以生成完整值,如果不能则发出错误。
有关基于简化实现的更详细解释,请参阅这篇博文。