gedenkt
gedenkt

Reputation: 43

optparse-applicative: parsing list of pairs

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?

Upvotes: 4

Views: 759

Answers (2)

Huw
Huw

Reputation: 31

Many and some were a bit too eager in older versions of optparse, and would require the whole construct succeed after a single option before permitting further options.

I changed the logic for many and so they are lazier in how they consume options which are over many and some. You can see the changes and logic here .

Upvotes: 3

duplode
duplode

Reputation: 34398

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.)

Upvotes: 3

Related Questions