4

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?

fho
  • 6,787
  • 26
  • 71
gedenkt
  • 43
  • 5

2 Answers2

3

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

duplode
  • 33,731
  • 7
  • 79
  • 150
  • Thanks! I find the behaviour of `many` really confusing, as I expected it to work more like Parsec ... Does there exist a more Parsec-style command line parser? – gedenkt Jul 21 '15 at 08:39
  • @gedenkt Not that I'm aware of. Libraries such as `optparse-applicative` are streamlined and specialised to handle the more usual argument-and-option-passing use cases. BTW, another thing you might try (though I haven't tested it) is specifying a custom format for your pairs, as in `{item1,item2}` instead of `item1 item2`. I believe the FluxCapacitor example in the [readme](https://hackage.haskell.org/package/optparse-applicative-0.11.0.2) suggests a way to achieve that with `optparse-applicative`. – duplode Jul 21 '15 at 22:51
3

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 .

Huw
  • 31
  • 1