2016-01-20 2 views
5

Я использую optparse-applicative, и я хотел бы, чтобы иметь возможность анализировать аргументы командной строки, такие как:optparse-аппликативен вариант с несколькими значениями

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

т.е. два коммутаторов, оба из которых может принимать несколько аргументов ,

Так у меня есть тип данных для моих вариантов который выглядит следующим образом:

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

а затем Parser так:

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 

Этот подход не в том, что он даст ожидаемый результат когда для каждого коммутатора предоставляется только один аргумент, но если вы укажете второй аргумент, вы получите «Недопустимый аргумент» для этого второго аргумента.

Я задавался вопросом, могу ли я его убить, притворившись, что мне нужны четыре варианта: булевский переключатель (т. Е. -a); список строк; другой логический переключатель (т. е. -b); и еще один список строк. Таким образом, я изменил свой тип данных:

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

И затем изменил парсер так:

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

На этот раз с помощью many и argument комбинаторов вместо явного парсер для списка строк.

Но теперь первые many (argument str (metavar "FILE")) потребляет всех аргументов, в том числе следующих после -b.

Итак, как я могу написать этот парсер аргументов?

ответ

4

Помимо команд optparse-applicative следует за соглашением getopts: один аргумент в командной строке соответствует одному аргументу параметра. Это даже немного более строгим, поскольку getopts позволит несколько вариантов с тем же переключателем:

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

Таким образом, нет никакой «магии», который может помочь вам немедленно использовать программу как

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

после Options.Applicative.Parser не было написано с учетом этого; это также противоречит POSIX conventions, где опции принимают либо один аргумент, либо нет.

Однако, вы можете решить эту проблему с двух сторон: либо использовать -a несколько раз, как вы бы в getopts, или сообщить пользователю использовать кавычки:

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

Чтобы включить многократное использование опции вы должны использовать many (если они не являются обязательными) или some (если это не так).Вы даже можете комбинировать оба варианта:

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

, который позволяет использовать

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

Но предложенный ваш стиль не поддерживается какой-либо разборе библиотеки, я знаю, так как положение свободных аргументов будет неоднозначной , Если вы действительно хотите использовать -a 1 2 3 -b foo bar crux, вам нужно самостоятельно проанализировать аргументы.

+0

свободные аргументы не были бы двусмысленными, если бы аргументы '-a' были ограничены, чтобы не начинаться с' -'. – rampion

Смежные вопросы