Haskell命令行选项解析和应用程序构建库
optparse-applicative是一个功能完备的Haskell命令行解析库。它支持参数验证、错误处理、帮助文档生成和shell自动补全。通过简洁的API,开发者可以方便地定义和组合各类选项解析器,包括常规选项、标志、参数和子命令。该库设计灵活,适用于构建复杂的命令行应用程序。它提供了声明式API,用于构建灵活的命令行界面,支持多种选项类型,包括带参数的选项、开关、位置参数和嵌套子命令。通过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
。
还有其他类型的基本解析器,以及几种配置它们的方法。这些在构建器部分中有所介绍。
现在我们可以将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 扩展来轻松实现自定义。
通过命令行以不同方式配置程序也很常见。一个典型的例子是可以给程序一个文本文件作为输入,或者直接从标准输入读取。
我们可以在 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"