使用 OptParse-Applicative 将用户选项解析为自定义数据类型

Parsing user options into custom data types with OptParse-Applicative

我正在尝试构建一个 CLI 美食日记应用程序。

这是我希望在其中解析用户输入的数据类型。

data JournalCommand =
  JournalSearch Query DataTypes Ingridents BrandOwnder PageNumber
  | JournalReport Query DataTypes Ingridents BrandOwnder PageNumber ResultNumber
  | JournalDisplay FromDate ToDate ResultNumber
  | JournalStoreSearch Query DataTypes Ingridents BrandOwnder PageNumber ResultNumber StoreFlag
  | JournalStoreCustom CustomEntry OnDate StoreFlag
  | JournalDelete FromDate ToDate ResultNumber
  | JournalEdit CustomEntry ResultNumber
  deriving (Show, Eq)

因为有很多重叠,我总共有 8 个 Parser a 类型的函数。

像这样的函数

-- | Search Query
aQueryParser :: Parser String
aQueryParser = strOption
               ( long "search"
                 <> short 's'
                 <> help "Search for a term in the database"
               )

如果最终有这样的功能的想法

runJournal :: JournalCommand -> MT SomeError IO ()
runJournal = \case
             JournalSearch q d i b p
                     -> runSearch q d i b p
             JournalReport q d i b p r
                     -> runSearchAndReport q d i b p r
            ...
            ...

其中 MT 是一些可以处理 error + IO 的 monad 转换器。还不确定。

问题是:如何设置 parseArgs 函数

parseArgs :: IO JournalCommand
parseArgs = execParser ...

parser函数

parser :: Parser JournalCommand
parser = ...

这样我就可以将用户输入解析为 JournalCommand,然后 return 将数据解析为相关函数。

我知道我可以fmap这样的数据类型

data JournalDisplay { jdFromDate     :: UTCTime
                    , jdToDate       :: UTCTime
                    , jdResultNumber :: Maybe Int
                    }

作为

JournalDisplay
<$>
fromDateParser
<*>
toDateParser
<*>
optional resultNumberParser

但我不确定如何使用我的原始数据结构来做到这一点。

我想我需要一个像这样的列表 [Mod CommandFields JournalCommand],我可以通过连接 Mod 列表将其传递给 subparser 函数。我不太确定。

在 optparse-applicative 中有 Parser 类型,还有 ParserInfo type which represents a "completed" parser holding extra information like header, footer, description, etc... and which is ready to be run with execParser。 我们通过添加额外信息作为修饰符的 info 函数从 ParserParserInfo

现在,当用子命令编写解析器时,每个子命令必须有自己的 ParserInfo 值(意味着它可以有自己的本地帮助和描述)。

我们将每个 ParserInfo 值传递给 command function (along with the name we want the subcommand to have) and then we combine the [Mod CommandFields JournalCommand] list using mconcat and pass the result to subparser。这将为我们提供 top-level Parser。我们需要再次使用 info 来提供 top-level 描述并得到最终的 ParserInfo.

使用您的类型的简化版本的示例:

data JournalCommand =
    JournalSearch String String
  | JournalReport String
  deriving (Show, Eq)

journalParserInfo :: O.ParserInfo JournalCommand
journalParserInfo = 
    let searchParserInfo :: O.ParserInfo JournalCommand
        searchParserInfo = 
            O.info
            (JournalSearch 
                <$> strArgument (metavar "ARG1" <> help "This is arg 1")
                <*> strArgument (metavar "ARG2" <> help "This is arg 2"))
            (O.fullDesc <> O.progDesc "desc 1")
        reportParserInfo :: O.ParserInfo JournalCommand
        reportParserInfo = 
            O.info
            (JournalReport 
                <$> strArgument (metavar "ARG3" <> help "This is arg 3"))
            (O.fullDesc <> O.progDesc "desc 2")
        toplevel :: O.Parser JournalCommand
        toplevel = O.subparser (mconcat [ 
                command "search" searchParserInfo, 
                command "journal" reportParserInfo 
            ])
     in O.info toplevel (O.fullDesc <> O.progDesc "toplevel desc")