我正在编写一个既有命令行界面又有交互模式的程序。在CLI模式下,它执行一个命令,打印结果并退出。在交互模式下,它使用GNU readline重复读取命令,执行它们并打印结果(以REPL的精神)。
命令及其参数的语法几乎相同,无论它们来自命令行还是frmo stdin。我想通过使用单个框架来解析命令行和交互模式输入来最大化代码重用。
我建议的语法是(方括号表示可选部分,括号重复),如下所示:
来自shell:
program-name {[GLOBAL OPTION] ...} <command> [{<command arg>|<GLOBAL OPTION>|<LOCAL OPTION> ...}]
在互动模式下:
<command> [{<command arg>|<GLOBAL OPTION>|<LOCAL OPTION> ...}]
本地选项仅对一个特定命令有效(不同的命令可能为一个选项指定不同的含义)。
我的问题是CL和交互式界面之间存在一些差异: 某些全局选项仅在命令行中有效(例如--help, - version或--config-file)。显然还有“退出”命令在交互模式下非常重要,但在CL中使用它是没有意义的。
为了解决这个问题,我搜索了网络和hackage的命令行解析库。我发现的最有趣的是cmdlib和optparse-applicative。但是,我对Haskell很新,即使我可以通过复制和修改库文档中的示例代码来创建一个工作程序,我还没有完全理解这些库的机制,因此无法解决我的问题。
我记住这些问题:
如何为CL和REPL接口常用的命令和选项创建基本解析器,然后能够使用新命令和选项扩展基本解析器?
如何防止这些库在输入错误时退出我的程序,或者当&#39; - help&#39;使用?
我计划为我的程序添加完整的i18n支持。因此,我想阻止我选择的库打印任何消息,因为所有消息都需要翻译。怎么做到这一点?
所以我希望你能给我一些关于从这里去的地方的提示。 cmdlib或optparse-applicative(或其他一些库)是否支持我正在寻找的内容?或者我应该恢复到手工制作的解析器?
答案 0 :(得分:2)
我认为您可以使用我的库http://hackage.haskell.org/package/options来执行此操作。子命令功能与您正在寻找的命令标志解析行为完全匹配。
在两个不相交的选项集之间共享子命令会有点棘手,但辅助类型类应该能够做到。粗略的示例代码:
-- A type for options shared between CLI and interactive modes.
data CommonOptions = CommonOptions
{ optSomeOption :: Bool
}
instance Options CommonOptions where ...
-- A type for options only available in CLI mode (such as --version or --config-file)
data CliOptions = CliOptions
{ common :: CommonOptions
, version :: Bool
, configFile :: String
}
instance Options CliOptions where ...
-- if a command takes only global options, it can use this subcommand option type.
data NoOptions = NoOptions
instance Options NoOptions where
defineOptions = pure NoOptions
-- typeclass to let commands available in both modes access common options
class HasCommonOptions a where
getCommonOptions :: a -> CommonOptions
instance HasCommonOptions CommonOptions where
getCommonOptions = id
instance HasCommonOptions CliOptions where
getCommonOptions = common
commonCommands :: HasCommonOptions a => [Subcommand a (IO ())]
commonCommands = [... {- your commands here -} ...]
cliCommands :: HasCommonOptions a => [Subcommand a (IO ())]
cliCommands = commonCommands ++ [cmdRepl]
interactiveCommands :: HasCommonOptions a => [Subcommand a (IO ())]
interactiveCommands = commonCommands ++ [cmdQuit]
cmdRepl :: HasCommonOptions a => Subcommand a (IO ())
cmdRepl = subcommand "repl" $ \opts NoOptions -> do
{- run your interactive REPL here -}
cmdQuit :: Subcommand a (IO ())
cmdQuit = subcommand "quit" (\_ NoOptions -> exitSuccess)
我怀疑像runSubcommand
这样的辅助函数不够专业,因此一旦从REPL提示中拆分输入字符串,就会想要用parseSubcommand
调用解析器。这些文档提供了如何检查已解析选项的示例,包括检查用户是否请求了帮助。
选项解析器本身不会打印任何输出,但可能很难将默认类型解析器生成的错误消息国际化。如果对图书馆有任何改变,请告知我们。