haskell – 解析命令行和REPL命令和选项

我正在编写一个既有命令行界面又有交互模式的程序.在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).显然还有’quit’命令在交互模式下非常重要,但是从CL使用它是没有意义的.

为了解决这个问题,我搜索了网络和hackage的命令行解析库.我发现最有趣的是cmdlib和optparse-applicative.但是,我对Haskell很新,即使我可以通过复制和修改库文档中的示例代码来创建工作程序,我还不太了解这些库的机制,因此无法解决我的问题.

我有这些问题:
如何为CL和REPL接口常用的命令和选项创建基本解析器,然后能够使用新命令和选项扩展基本解析器?
如何在输入错误或使用“–help”时阻止这些库退出程序?
我计划为我的程序添加完整的i18n支持.因此,我想阻止我选择的库打印任何消息,因为所有消息都需要翻译.怎么做到这一点?

所以我希望你能给我一些关于从哪里开始的提示. cmdlib或optparse-applicative(或其他一些库)是否支持我正在寻找的内容?或者我应该恢复到手工制作的解析器?

最佳答案 我想你可以使用我的库
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调用解析器.文档提供了如何检查已解析选项的示例,包括检查用户是否请求了帮助.

选项解析器本身不会打印任何输出,但可能很难国际化默认类型解析器生成的错误消息.如果对图书馆有任何改变,请告诉我.

点赞