I have a basic command add
that takes 2 kind of arguments: a word or a tag. A tag is just a word starting by +
. A word is just a String
. It can contain at least one argument (I use some
for this).
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)
I want to add another rule: the add
command should receive at least one word (but 0 or more tags). For this, I need to check after the first parsing the word list. If it's empty, I would like to fail as if the add
commands received no argument, but I can't figure out how to do.
parseDescAndTags
is currently a pure function, so there’s no way for it to cause parsing to fail. Just to get this out of the way, I should also note that in this code:The operator
<$>
is declaredinfixl 4
, so it’s left-associative, and your expression is therefore equivalent to:You happen to be using
<$>
in the “function reader” functor,(->) a
, which is equivalent to composition(.)
:If you want to use
ReadM
, you need to use functions such aseitherReader
to construct aReadM
action. But the problem is that you would need to use it as the first argument toargument
instead of thestr
reader, and that’s the wrong place for it, sincesome
is on the outside and you want to fail parsing based on the accumulated results of the whole option.Unfortunately that kind of context-sensitive parsing is not what
optparse-applicative
is designed for; it doesn’t offer aMonad
instance for parsers.Currently, your parser allows tags and descriptions to be interleaved, like this (supposing
isTag = (== ".") . take 1
for illustration):Producing
"some description text"
for the description and[".tag1", ".tag2"]
as the tags. Is that what you want, or can you use a simpler format instead, like requiring all tags at the end?If so, the result is simple: parse at least one non-tag with
some
, then any number of tags withmany
:As an alternative, you can parse command-line options with
optparse-applicative
, but do any more complex validation on your options records after running the parser. Then if you want to print the help text manually, you can use: