Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

haskell optparse-applicative: parsing a list of records that have multiple fields

If I have the following types and parser:

data Mode = 
     Mode1 
   | Mode2 
   deriving (Show, Eq, Read)

data ThingINeedMulitpleOf = 
   Thing { _name :: String, _mode :: Mode }
   deriving (Show, Eq)

thingParser :: Parser ThingINeedMulitpleOf
thingParser = Thing <$> strArgument (metavar "NAME")
                    <*> option auto (long "mode" <> metavar "MODE")

and I build a parser in the following way:

data Config = 
   Config ThingINeedMulitpleOf ThingINeedMulitpleOf
   deriving (Show, Eq)

loadConfig = execParser $ info (Config <$> thingParser <*> thingParser) fullDesc

then I can successfully parse my-exe Thing1 --mode Mode1 Thing2 --mode Mode2 but this is only useful if I want exactly two Things. I'm running into problems when trying to change the Config to support n Things, ie:

data Config = 
   Config [ThingINeedMulitpleOf]
   deriving (Show, Eq)

loadConfig = execParser $ info (Config <$> many thingParser) fullDesc

but I can now no longer parse my-exe Thing1 --mode Mode1 Thing2 --mode Mode2, giving me the error Invalid argument 'Thing1'

Interestingly, this works if the ThingINeedMulitpleOf only contains one field.

like image 214
tmortiboy Avatar asked Oct 25 '25 14:10

tmortiboy


2 Answers

If you use a subparser with the Alternative typeclass's some function, you can get the effect you're looking for, admittedly with a slightly different command line syntax.

thingSubparser :: Parser ThingINeedMulitpleOf
thingSubparser = subparser $ command "thing" (info thingParser mempty)

loadConfig :: IO Config
loadConfig = execParser $ info (Config <$> some thingSubparser) fullDesc

With this you can write command lines like:

my-exe thing test --mode Mode1 thing test2 --mode Mode2

which yield the Config object:

Config [Thing {_name = "test", _mode = Mode1},Thing {_name = "test2", _mode = Mode2}]

I'm not quite sure why you need the subparser and can't just use some against the thingParser, but if I had to guess it'd be because the command gives the parser a delimiter (the "thing" argument/command name).

like image 66
Daniel Chambers Avatar answered Oct 27 '25 04:10

Daniel Chambers


If you use a subparser with the Alternative typeclass's some function, you can get the effect you're looking for, admittedly with a slightly different command line syntax.

thingSubparser :: Parser ThingINeedMulitpleOf
thingSubparser = subparser $ command "thing" (info thingParser mempty)

loadConfig :: IO Config
loadConfig = execParser $ info (Config <$> some thingSubparser) fullDesc

With this you can write command lines like:

my-exe thing test --mode Mode1 thing test2 --mode Mode2

which yield the Config object:

Config [Thing {_name = "test", _mode = Mode1},Thing {_name = "test2", _mode = Mode2}]

I'm not quite sure why you need the subparser and can't just use some against the thingParser, but if I had to guess it'd be because the command gives the parser a delimiter (the "thing" argument/command name).

like image 22
Daniel Chambers Avatar answered Oct 27 '25 03:10

Daniel Chambers