Haskell - 解析命令行和REPL命令及选项

4
我正在编写一个程序,它既有命令行界面又有交互模式。在CLI模式下,它执行一个命令,打印结果并退出。在交互模式下,它使用GNU readline重复读取命令,执行它们并打印结果(类似于REPL)。
无论命令来自命令行还是stdin,命令及其参数的语法几乎相同。我希望通过使用单个框架解析命令行和交互模式输入来最大化代码重用。
我的建议语法为(方括号表示可选部分,花括号表示重复):
  • From shell:

    program-name {[GLOBAL OPTION] ...} <command> [{<command arg>|<GLOBAL OPTION>|<LOCAL OPTION> ...}]  
    
  • In interactive mode:

    <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(或其他库)是否支持我正在寻找的功能?还是应该回到手工制作解析器?

在base中有一个非常简单的命令行解析工具:http://www.haskell.org/ghc/docs/latest/html/libraries/base/System-Console-GetOpt.html。如果您的选项作为列表给出,您可以有3个列表:一个用于常见命令,一个用于仅限REPL和一个用于仅限CL。`GetOpts`不会崩溃,它会返回错误消息,就像我认为任何好的库都应该做的一样。它也不会执行任何打印操作,因为打印与命令行解析无关。 - user2407038
这里有一份现有命令行选项解析库的评估。请查看此处:http://www.haskell.org/haskellwiki/Command_line_option_parsers - fizruk
1个回答

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调用解析器。文档中有如何检查已解析选项的示例,包括检查用户是否请求帮助。
选项解析器本身不会输出任何内容,但是默认类型解析器生成的错误消息可能难以国际化。如果对库进行任何更改,请告诉我。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接