处理 CLI 参数语义错误的编程模式或库(即惯用方式)?
Programming pattern or library (i.e. idiomatic way) to handle CLI arguments semantic errors?
我有一个 Haskell 应用程序,它使用 optparse-applicative
库进行 CLI 参数解析。我的 CLI 参数数据类型包含 FilePath
s(文件和目录)、Double
s 等。optparse-applicative
可以处理解析错误,但我想确保某些文件和目录存在(或不存在),数字为 >= 0
等
可以做的是实现一堆像下面这样的辅助函数:
exitIfM :: IO Bool -> Text -> IO ()
exitIfM predicateM errorMessage = whenM predicateM $ putTextLn errorMessage >> exitFailure
exitIfNotM :: IO Bool -> Text -> IO ()
exitIfNotM predicateM errorMessage = unlessM predicateM $ putTextLn errorMessage >> exitFailure
然后我这样使用它:
body :: Options -> IO ()
body (Options path1 path2 path3 count) = do
exitIfNotM (doesFileExist path1) ("File " <> (toText ledgerPath) <> " does not exist")
exitIfNotM (doesDirectoryExist path2) ("Directory " <> (toText skKeysPath) <> " does not exist")
exitIfM (doesFileExist path3) ("File " <> (toText nodeExe) <> " already exist")
exitIf (count <= 0) ("--counter should be positive")
这对我来说太特别而且丑陋了。此外,我编写的几乎每个应用程序都需要类似的功能。当我想在实际处理数据类型之前做一堆检查时,是否有一些惯用的方法来处理这种编程模式?涉及的样板文件越少越好:)
与其在构建之后验证选项记录,不如使用applicative functor composition来组合参数解析和验证:
import Control.Monad
import Data.Functor.Compose
import Control.Lens ((<&>)) -- flipped fmap
import Control.Applicative.Lift (runErrors,failure) -- form transformers
import qualified Options.Applicative as O
import System.Directory -- from directory
data Options = Options { path :: FilePath, count :: Int } deriving Show
main :: IO ()
main = do
let pathOption = Compose (Compose (O.argument O.str (O.metavar "FILE") <&> \file ->
do exists <- doesPathExist file
pure $ if exists
then pure file
else failure ["Could not find file."]))
countOption = Compose (Compose (O.argument O.auto (O.metavar "INT") <&> \i ->
do pure $ if i < 10
then pure i
else failure ["Incorrect number."]))
Compose (Compose parsy) = Options <$> pathOption <*> countOption
io <- O.execParser $ O.info parsy mempty
errs <- io
case runErrors errs of
Left msgs -> print msgs
Right r -> print r
组合解析器的类型为 Compose (Compose Parser IO) (Errors [String]) Options
。 IO
层用于执行文件存在性检查,而 Errors
是来自 transformers 的 validation-like 应用程序,它会累积错误消息。 运行 解析器产生一个 IO
动作,当 运行 时产生一个 Errors [String] Options
值。
代码有点冗长,但这些参数解析器可以打包到库中并重复使用。
一些示例构成了回复:
Λ :main "/tmp" 2
Options {path = "/tmp", count = 2}
Λ :main "/tmpx" 2
["Could not find file."]
Λ :main "/tmpx" 22
["Could not find file.","Incorrect number."]
我有一个 Haskell 应用程序,它使用 optparse-applicative
库进行 CLI 参数解析。我的 CLI 参数数据类型包含 FilePath
s(文件和目录)、Double
s 等。optparse-applicative
可以处理解析错误,但我想确保某些文件和目录存在(或不存在),数字为 >= 0
等
可以做的是实现一堆像下面这样的辅助函数:
exitIfM :: IO Bool -> Text -> IO ()
exitIfM predicateM errorMessage = whenM predicateM $ putTextLn errorMessage >> exitFailure
exitIfNotM :: IO Bool -> Text -> IO ()
exitIfNotM predicateM errorMessage = unlessM predicateM $ putTextLn errorMessage >> exitFailure
然后我这样使用它:
body :: Options -> IO ()
body (Options path1 path2 path3 count) = do
exitIfNotM (doesFileExist path1) ("File " <> (toText ledgerPath) <> " does not exist")
exitIfNotM (doesDirectoryExist path2) ("Directory " <> (toText skKeysPath) <> " does not exist")
exitIfM (doesFileExist path3) ("File " <> (toText nodeExe) <> " already exist")
exitIf (count <= 0) ("--counter should be positive")
这对我来说太特别而且丑陋了。此外,我编写的几乎每个应用程序都需要类似的功能。当我想在实际处理数据类型之前做一堆检查时,是否有一些惯用的方法来处理这种编程模式?涉及的样板文件越少越好:)
与其在构建之后验证选项记录,不如使用applicative functor composition来组合参数解析和验证:
import Control.Monad
import Data.Functor.Compose
import Control.Lens ((<&>)) -- flipped fmap
import Control.Applicative.Lift (runErrors,failure) -- form transformers
import qualified Options.Applicative as O
import System.Directory -- from directory
data Options = Options { path :: FilePath, count :: Int } deriving Show
main :: IO ()
main = do
let pathOption = Compose (Compose (O.argument O.str (O.metavar "FILE") <&> \file ->
do exists <- doesPathExist file
pure $ if exists
then pure file
else failure ["Could not find file."]))
countOption = Compose (Compose (O.argument O.auto (O.metavar "INT") <&> \i ->
do pure $ if i < 10
then pure i
else failure ["Incorrect number."]))
Compose (Compose parsy) = Options <$> pathOption <*> countOption
io <- O.execParser $ O.info parsy mempty
errs <- io
case runErrors errs of
Left msgs -> print msgs
Right r -> print r
组合解析器的类型为 Compose (Compose Parser IO) (Errors [String]) Options
。 IO
层用于执行文件存在性检查,而 Errors
是来自 transformers 的 validation-like 应用程序,它会累积错误消息。 运行 解析器产生一个 IO
动作,当 运行 时产生一个 Errors [String] Options
值。
代码有点冗长,但这些参数解析器可以打包到库中并重复使用。
一些示例构成了回复:
Λ :main "/tmp" 2
Options {path = "/tmp", count = 2}
Λ :main "/tmpx" 2
["Could not find file."]
Λ :main "/tmpx" 22
["Could not find file.","Incorrect number."]