I'm trying to parse a list of pairs with optparse-applicative. Parsing a single pair works, but parsing arbitrarily many using the many
combinator fails.
import Options.Applicative
pairParser = (,) <$> argument str (metavar "s1")
<*> argument str (metavar "s2")
testParser p = getParseResult . execParserPure (prefs idm)
(info (helper <*> p) fullDesc)
main = do
print $ testParser pairParser ["one", "two"]
print $ testParser (many pairParser) []
print $ testParser (many pairParser) ["one", "two"]
print $ testParser (many pairParser) ["one", "two", "three", "four"]
Output:
Just ("one","two") <- good
Just [] <- still good
Nothing <- does not work
Nothing <- also does not work
Any ideas?
Disclaimer: I have no experience of doing advanced optparse-applicative tricks, so I might be missing something obvious. Readers: please point it out if that's the case.
Your problem is that what many
does is (in a hand-wavy description) to apply the parser to each chunk of the input, with the chunks in this case consisting of individual arguments, and then collect the results. So many pairParser
applies pairParser
to ["one"]
and then to ["two"]
, and both parses fail. That being so, you might either replace execParserPure
with a function that chunks the arguments in an appropriate way and adjust the rest of the program accordingly, or (what I suspect is the easier choice) abandon pairParser
and just post-process the parsed arguments, as in:
pairArgs :: [a] -> [(a, a)]
pairArgs = noLeftover . foldr pairNext (Nothing, [])
where
noLeftover (m, ps) = case m of
Nothing -> ps
_ -> []
pairNext x (m, ps) = case m of
Just y -> (Nothing, (x, y) : ps)
Nothing -> (Just x, ps)
manyPairsParser :: Parser [(String, String)]
manyPairsParser = pairArgs <$> many (argument str (metavar "s1 s2.."))
GHCi> testParser manyPairsParser []
Just []
GHCi> testParser manyPairsParser ["foo"]
Just []
GHCi> testParser manyPairsParser ["foo","bar"]
Just [("foo","bar")]
GHCi> testParser manyPairsParser ["foo","bar","baz"]
Just []
GHCi> testParser manyPairsParser ["foo","bar","baz","quux"]
Just [("foo","bar"),("baz","quux")]
(Note that in the demo above I'm handling failure by returning an empty list of pairs, and considering that an odd number of arguments should lead to failure. You'll need to do some adjustments if you want a different behaviour.)