是否可以使用带有多个参数的 optparse-applicative 选项?
Is it possible to have a optparse-applicative option with several parameters?
我刚发现我精心制作的解析器无法解析我抛给它的任何字符串:
roi :: Parser (Maybe ROI)
roi = optional $ option (ROI <$> auto <*> auto <*> auto <*> auto)
$ long "roi" <> metavar "ROI" <> help "Only process selected region of interest"
其中 ROI = ROI Int Int Int Int
如果这很重要,它嵌套在更高的解析器中
options :: Parser Opts
options = Opts <$> input <*> output <*> roi <*> startT <*> endT
其中 Opts
是合适的 ADT。
现在我假设 roi
解析器将解析诸如 --roi 1 2 3 4
的表达式,但它失败并显示 Invalid argument '128'
并给我使用信息。
--roi 1
而是解析但 returns Just (ROI 1 1 1 1)
有没有办法让它工作?
我认为选项不应该使用多个参数。至少我不确定您将如何实施。我建议简单地放弃这个想法,将你的 ROI 选项放入一个参数中,使用像 --roi 1,2,3,4
.
这样的语法
您只需为此实现一个自定义 reader,下面是您如何实现的示例:
module Main where
import Options.Applicative
data ROI = ROI Int Int Int Int
deriving Show
-- didn't remember what this function was called, don't use this
splitOn :: Eq a => a -> [a] -> [[a]]
splitOn sep (x:xs) | sep==x = [] : splitOn sep xs
| otherwise = let (xs':xss) = splitOn sep xs in (x:xs'):xss
splitOn _ [] = [[]]
roiReader :: ReadM ROI
roiReader = do
o <- str
-- no error checking, don't actually do this
let [a,b,c,d] = map read $ splitOn ',' o
return $ ROI a b c d
roiParser :: Parser ROI
roiParser = option roiReader (long "roi")
main :: IO ()
main = execParser opts >>= print where
opts = info (helper <*> roiParser) fullDesc
option
的类型是:
option :: ReadM a -> Mod OptionFields a -> Parser a
ReadM
,反过来就是"A newtype over 'ReaderT String Except', used by option readers"。由于 option
在幕后使用 ReaderT
,当您将它与 ReadM
的 Applicative
实例一起使用时,就像您在此处所做的那样...
ROI <$> auto <*> auto <*> auto <*> auto
...相同且完整的输入字符串被提供给四个 auto
解析器中的每一个,因为这就是 reader/function 应用实例的工作方式。
如果您希望将以空格分隔的值解析为单个 ROI
,则需要编写自定义解析器。这是围绕 eitherReader
构建的一个不是特别整洁的尝试。请注意,这将要求值包含在引号内 (--roi "1 2 3 4"
),以便将它们作为单个字符串接收。 Cubic 的回答提出了另一种方法,即使用逗号分隔值 (--roi 1,2,3,4
).
import Text.Read (readEither)
-- etc.
roi :: Parser (Maybe ROI)
roi = optional
$ option (eitherReader $ \inp -> case traverse readEither (words inp) of
Right [x, y, z, w] -> Right (ROI x y z w)
Right _ -> Left "ROI requires exactly 4 values"
Left _ -> Left "ROI requires integer values")
$ long "roi" <> metavar "ROI" <> help "Only process selected region of interest"
成功和失败模式:
GHCi> execParserPure defaultPrefs (info roi mempty) ["--roi","1 2 3 4"]
Success (Just (ROI 1 2 3 4))
GHCi> execParserPure defaultPrefs (info roi mempty) ["--roi","1 2 3"]
Failure (ParserFailure (option --roi: ROI requires exactly 4 values
Usage: <program> [--roi ROI],ExitFailure 1,80))
GHCi> execParserPure defaultPrefs (info roi mempty) ["--roi","1 2 foo 4"]
Failure (ParserFailure (option --roi: ROI requires integer values
Usage: <program> [--roi ROI],ExitFailure 1,80))
我刚发现我精心制作的解析器无法解析我抛给它的任何字符串:
roi :: Parser (Maybe ROI)
roi = optional $ option (ROI <$> auto <*> auto <*> auto <*> auto)
$ long "roi" <> metavar "ROI" <> help "Only process selected region of interest"
其中 ROI = ROI Int Int Int Int
如果这很重要,它嵌套在更高的解析器中
options :: Parser Opts
options = Opts <$> input <*> output <*> roi <*> startT <*> endT
其中 Opts
是合适的 ADT。
现在我假设 roi
解析器将解析诸如 --roi 1 2 3 4
的表达式,但它失败并显示 Invalid argument '128'
并给我使用信息。
--roi 1
而是解析但 returns Just (ROI 1 1 1 1)
有没有办法让它工作?
我认为选项不应该使用多个参数。至少我不确定您将如何实施。我建议简单地放弃这个想法,将你的 ROI 选项放入一个参数中,使用像 --roi 1,2,3,4
.
您只需为此实现一个自定义 reader,下面是您如何实现的示例:
module Main where
import Options.Applicative
data ROI = ROI Int Int Int Int
deriving Show
-- didn't remember what this function was called, don't use this
splitOn :: Eq a => a -> [a] -> [[a]]
splitOn sep (x:xs) | sep==x = [] : splitOn sep xs
| otherwise = let (xs':xss) = splitOn sep xs in (x:xs'):xss
splitOn _ [] = [[]]
roiReader :: ReadM ROI
roiReader = do
o <- str
-- no error checking, don't actually do this
let [a,b,c,d] = map read $ splitOn ',' o
return $ ROI a b c d
roiParser :: Parser ROI
roiParser = option roiReader (long "roi")
main :: IO ()
main = execParser opts >>= print where
opts = info (helper <*> roiParser) fullDesc
option
的类型是:
option :: ReadM a -> Mod OptionFields a -> Parser a
ReadM
,反过来就是"A newtype over 'ReaderT String Except', used by option readers"。由于 option
在幕后使用 ReaderT
,当您将它与 ReadM
的 Applicative
实例一起使用时,就像您在此处所做的那样...
ROI <$> auto <*> auto <*> auto <*> auto
...相同且完整的输入字符串被提供给四个 auto
解析器中的每一个,因为这就是 reader/function 应用实例的工作方式。
如果您希望将以空格分隔的值解析为单个 ROI
,则需要编写自定义解析器。这是围绕 eitherReader
构建的一个不是特别整洁的尝试。请注意,这将要求值包含在引号内 (--roi "1 2 3 4"
),以便将它们作为单个字符串接收。 Cubic 的回答提出了另一种方法,即使用逗号分隔值 (--roi 1,2,3,4
).
import Text.Read (readEither)
-- etc.
roi :: Parser (Maybe ROI)
roi = optional
$ option (eitherReader $ \inp -> case traverse readEither (words inp) of
Right [x, y, z, w] -> Right (ROI x y z w)
Right _ -> Left "ROI requires exactly 4 values"
Left _ -> Left "ROI requires integer values")
$ long "roi" <> metavar "ROI" <> help "Only process selected region of interest"
成功和失败模式:
GHCi> execParserPure defaultPrefs (info roi mempty) ["--roi","1 2 3 4"]
Success (Just (ROI 1 2 3 4))
GHCi> execParserPure defaultPrefs (info roi mempty) ["--roi","1 2 3"]
Failure (ParserFailure (option --roi: ROI requires exactly 4 values
Usage: <program> [--roi ROI],ExitFailure 1,80))
GHCi> execParserPure defaultPrefs (info roi mempty) ["--roi","1 2 foo 4"]
Failure (ParserFailure (option --roi: ROI requires integer values
Usage: <program> [--roi ROI],ExitFailure 1,80))