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.
                        
parseDescAndTagsis 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 aseitherReaderto construct aReadMaction. But the problem is that you would need to use it as the first argument toargumentinstead of thestrreader, and that’s the wrong place for it, sincesomeis 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-applicativeis designed for; it doesn’t offer aMonadinstance for parsers.Currently, your parser allows tags and descriptions to be interleaved, like this (supposing
isTag = (== ".") . take 1for 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: