Optparse适用:连续解析(ReadM)

时间:2020-07-30 16:33:00

标签: haskell monads applicative optparse-applicative

我有一个基本命令add,它接受​​两种参数:单词或标记。标签只是一个以+开头的单词。单词只是String。它可以包含至少一个参数(为此我使用some)。

data Arg = Add AddOpts

data AddOpts = AddOpts
  { desc :: String,
    tags :: [String]
  }
  deriving (Show)

addCommand :: Mod CommandFields Arg
addCommand = command "add" (info parser infoMod)
  where
    infoMod = progDesc "Add a new task"
    parser = Add <$> parseDescAndTags <$> partition isTag <$> some (argument str (metavar "DESC"))
    parseDescAndTags (_, []) = FAIL HERE
    parseDescAndTags (tags, desc) = AddOpts (unwords desc) (map tail tags)

我想添加另一条规则:add命令应至少接收一个单词(但0个或多个标签)。为此,我需要在第一次解析单词列表后进行检查。如果为空,我会失败,好像add命令未收到任何参数,但我不知道该怎么做。

1 个答案:

答案 0 :(得分:1)

size当前是一个纯函数,因此无法导致解析失败。只是为了避免这种情况,我还应注意以下代码:

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")) ,则需要使用诸如eitherReader之类的函数来构造ReadM动作。但是问题在于,您需要将它用作ReadM的第一个参数,而不是argument的读者,这是错误的地方,因为str外部,并且您想根据整个选项的累加结果进行解析。

不幸的是,some并不是为这种上下文敏感的解析而设计的;它不为解析器提供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 <> "'") parse 命令行选项,但是在运行解析器后对选项记录进行任何更复杂的 validation 。然后,如果要手动打印帮助文本,可以使用:

optparse-applicative