1

我有一个带有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命令没有收到参数一样,但我不知道该怎么做。

4

1 回答 1

1

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它在外面,你想根据整个选项的累积结果进行解析失败。

不幸的是,这种上下文相关的解析不是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 = handleParseResult $ Failure
  $ parserFailure parserPrefs parserInfo ShowHelpText mempty
于 2020-07-30T18:16:27.967 回答