2016-01-20 14 views
5

sto usando optparse-applicative e vorrei poter analizzare la linea di comando quali:opzione optparse-applicativa con più valori

$ ./program -a file1 file2 -b filea fileb 

Per esempio, due interruttori, entrambi i quali possono richiedere più argomenti .

Così ho un tipo di dati per le mie opzioni che assomiglia a questo:

data MyOptions = MyOptions { 
    aFiles :: [String] 
    , bFiles :: [String] } 

E poi un Parser come questo:

config :: Parser MyOptions 
config = MyOptions 
     <$> option (str >>= parseStringList) 
      (short 'a' <> long "aFiles") 
     <*> option (str >>= parseStringList) 
      (short 'b' <> long "bFiles") 

parseStringList :: Monad m => String -> m [String] 
parseStringList = return . words 

Questo approccio non riesce a che darà il risultato previsto quando viene fornito un solo argomento per ogni switch, ma se si fornisce un secondo argomento si ottiene "Argomento non valido" per quel secondo argomento.

Mi chiedevo se potevo escluderlo fingendo di volere quattro opzioni: un interruttore booleano (ad esempio -a); una lista di stringhe; un altro interruttore booleano (ad esempio -b); e un altro elenco di stringhe. Così ho cambiato il mio tipo di dati:

data MyOptions = MyOptions { 
    isA :: Bool 
    , aFiles :: [String] 
    , isB :: Bool 
    , bFiles :: [String] } 

e poi modificato il parser in questo modo:

config :: Parser MyOptions 
config = MyOptions 
     <$> switch 
      (short 'a' <> long "aFiles") 
     <*> many (argument str (metavar "FILE")) 
     <*> switch 
      (short 'b' <> long "bFiles") 
     <*> many (argument str (metavar "FILE")) 

questa volta usando il many e argument combinatori invece di un parser esplicita per un elenco di stringhe.

Ma ora il primo many (argument str (metavar "FILE")) consuma tutte degli argomenti, compresi quelli che seguono l'interruttore -b.

Quindi, come posso scrivere questo parser degli argomenti?

risposta

4

Oltre ai comandi, optparse-applicative segue la convenzione getopts: un singolo argomento sulla riga di comando corrisponde a un argomento di opzione singolo. E 'ancora un po' più rigorosa, poiché getopts permetterà opzioni multiple con lo stesso interruttore:

./program-with-getopts -i input1 -i input2 -i input3 

Quindi non c'è nessuna "magia" che può aiutare immediatamente di utilizzare il programma come

./program-with-magic -a 1 2 3 -b foo bar crux 

dal Options.Applicative.Parser non è stato scritto con questo in mente; contraddice anche lo POSIX conventions, in cui le opzioni accettano un argomento o nessuno.

Tuttavia, è possibile affrontare questo problema da due parti: o utilizzare -a più volte, come si farebbe in getopts, o dire all'utente di utilizzare le virgolette:

./program-as-above -a "1 2 3" -b "foo bar crux" 
# works already with your program! 

Per abilitare l'utilizzo multiplo di un'opzione devi usare many (se sono opzionali) o some (se non lo sono).È anche possibile combinare entrambe le varianti:

multiString desc = concat <$> some single 
    where single = option (str >>= parseStringList) desc 

config :: Parser MyOptions 
config = MyOptions 
    <$> multiString (short 'a' <> long "aFiles" <> help "Use quotes/multiple") 
    <*> multiString (short 'b' <> long "bFiles" <> help "Use quotes/multiple") 

che consente di utilizzare

./program-with-posix-style -a 1 -a "2 3" -b foo -b "foo bar" 

Ma il vostro stile proposto non è supportato da qualsiasi libreria di analisi che conosco, in quanto la posizione di argomenti liberi sarebbe ambiguo . Se vuoi veramente usare -a 1 2 3 -b foo bar crux, devi analizzare tu stesso gli argomenti.

+0

gli argomenti liberi non sarebbero ambigui se gli argomenti di '-a' fossero vincolati a non iniziare con' -', però. – rampion