parseDescAndTags目前是一个纯函数,所以没有办法导致解析失败。只是为了解决这个问题,我还应该注意在这段代码中:
Add <$> parseDescAndTags <$> partition isTag <$> some (argument str (metavar "DESC"))
运算符<$> 被声明为infixl 4,所以它是左结合的,因此你的表达式等价于:
((Add <$> parseDescAndTags) <$> partition isTag) <$> some (argument str (metavar "DESC"))
你恰好在“函数阅读器”函子中使用了<$>,(->) a,相当于组合(.):
Add . parseDescAndTags . partition isTag <$> some (argument str (metavar "DESC"))
如果要使用ReadM,则需要使用eitherReader等函数来构造ReadM动作。但问题是您需要将它用作argument 而不是str 阅读器的第一个参数,这是错误的地方,因为some 在外部并且您希望根据 whole 选项的累积结果解析失败。
不幸的是,这种上下文相关的解析不是optparse-applicative 所设计的;它没有为解析器提供Monad 实例。
目前,您的解析器允许标签和描述交错,像这样(假设isTag = (== ".") . take 1 用于说明):
add some .tag1 description .tag2 text
生成"some description text" 作为描述,[".tag1", ".tag2"] 作为标签。这是你想要的,还是你可以使用更简单的格式,比如最后要求所有标签?
add some description text .tag1 .tag2
如果是这样,结果很简单:用some 解析至少一个非标签,然后用many 解析任意数量的标签:
addCommand :: Mod CommandFields Arg
addCommand = command "add" (info parser infoMod)
where
infoMod = progDesc "Add a new task"
parser = Add <$> addOpts
addOpts = AddOpts
<$> (unwords <$> some (argument nonTag (metavar "DESC")))
<*> many (argument tag (metavar "TAG"))
nonTag = eitherReader
$ \ str -> if isTag str
then Left ("unexpected tag: '" <> str <> "'")
else Right str
tag = eitherReader
$ \ str -> if isTag str
then Right $ drop 1 str
else Left ("not a tag: '" <> str <> "'")
作为替代方案,您可以使用optparse-applicative 解析 命令行选项,但在运行解析器后对您的选项记录执行任何更复杂的验证。然后如果你想手动打印帮助文本,你可以使用:
printHelp :: ParserPrefs -> ParserInfo a -> IO a
printHelp parserPrefs parserInfo = @987654322@ $ @987654323@
$ @987654324@ parserPrefs parserInfo @987654325@ mempty