Разбор аргументов командной строки в Haskell с использованием getOpt

Я пытаюсь научить себя Haskell. В качестве примера программы я пишу пасьянс «Паук».

Я пытаюсь написать парсер командной строки, используя System.Console.GetOpt. Я знаю, что есть более простые способы разбора аргументов для этой программы, но я хочу узнать, как использовать модуль GetOpt, потому что я предполагаю, что его сложность понадобится позже в других программах, которые я буду писать.

Я пытаюсь добавить опцию «--help», которая просто печатает сообщение об использовании, а затем завершает работу. Я также хотел бы печатать сообщения об использовании, если какой-либо из аргументов опции «—games» или опции «—suits» не является допустимым целым числом (игры >= 1 и ‹= 1000, костюмы == 1, 2, или 4). Я буду передавать полученный тип данных Options в другие части моей программы.

Я также получаю сообщение об ошибке, что progName не входит в область действия. Разве оператор case в parseArgs не входит в область действия блока do?

Вот мой код, собранный из примеров в "Real World Haskell" и Вики Haskell:

module Main (main) where

import System.Console.GetOpt
import System.Environment(getArgs, getProgName)

data Options = Options {
    optGames :: Int
  , optSuits :: Int
  , optVerbose :: Bool
  } deriving Show

defaultOptions = Options {
    optGames  = 1
  , optSuits = 4
  , optVerbose = False
  }

options :: [OptDescr (Options -> Options)]
options =
  [ Option ['g'] ["games"]
      (ReqArg (\g opts -> opts { optGames = (read g) }) "GAMES")
      "number of games"
  , Option ['s'] ["suits"]
      (ReqArg (\s opts -> opts { optSuits = (read s) }) "SUITS")
      "number of suits"
  , Option ['v'] ["verbose"]
      (NoArg (\opts -> opts { optVerbose = True }))
      "verbose output"
  ]

parseArgs :: IO Options
parseArgs = do
  argv <- getArgs
  progName <- getProgName
  case getOpt RequireOrder options argv of
    (opts, [], []) -> return (foldl (flip id) defaultOptions opts)
    (_, _, errs) -> ioError (userError (concat errs ++ helpMessage))
  where
    header = "Usage: " ++ progName ++ " [OPTION...]"
    helpMessage = usageInfo header options

main :: IO ()
main = do
  options <- parseArgs
  putStrLn $ show options

person Ralph    schedule 28.05.2012    source источник
comment
Область действия связана с тем, что предложение where присоединено к parseArgs, где progName не входит в область действия (оно связано в блоке выполнения). Если вы сделаете отступ where немного больше, он прикрепится к case, и progName окажется в области видимости.   -  person Daniel Fischer    schedule 28.05.2012
comment
(Если есть вопрос, отличный от того, почему я получаю эту ошибку, вы должны указать этот вопрос явно.)   -  person Daniel Wagner    schedule 28.05.2012
comment
Спасибо. Я устанавливал отступ whereclause до тех пор, пока он не стал на один уровень отступа больше, чем во втором case ((_, _, errs)), и это сработало, но тогда я предполагаю, что значения header и helpMessage не будут входить в область действия первого case case. Вместо этого я переместил их, чтобы они были let значениями блока do, и удалил where.   -  person Ralph    schedule 29.05.2012


Ответы (1)


Вот решение, которое я придумал:

module Main (main) where

import Control.Monad
import Control.Monad.Error
import System.Console.GetOpt
import System.Environment(getArgs, getProgName)

data Options = Options {
    optGames :: Int
  , optSuits :: Int
  , optVerbose :: Bool
  } deriving Show

defaultOptions = Options {
    optGames  = 1
  , optSuits = 4
  , optVerbose = False
  }

options :: [OptDescr (Options -> Either String Options)]
options =
  [ Option ['g'] ["games"]
      (ReqArg (\g opts ->
        case reads g of
          [(games, "")] | games >= 1 && games <= 1000 -> Right opts { optGames = games }
          _ -> Left "--games must be a number between 1 and 1000"
        ) "GAMES")
      "number of games"
  , Option ['s'] ["suits"]
      (ReqArg (\s opts ->
        case reads s of
          [(suits, "")] | suits `elem` [1, 2, 4] -> Right opts { optSuits = suits }
          _ -> Left "--suits must be 1, 2, or 4"
        ) "SUITS")
      "number of suits"
  , Option ['v'] ["verbose"]
      (NoArg (\opts -> Right opts { optVerbose = True }))
      "verbose output"
  ]

parseArgs :: IO Options
parseArgs = do
  argv <- getArgs
  progName <- getProgName
  let header = "Usage: " ++ progName ++ " [OPTION...]"
  let helpMessage = usageInfo header options
  case getOpt RequireOrder options argv of
    (opts, [], []) ->
      case foldM (flip id) defaultOptions opts of
        Right opts -> return opts
        Left errorMessage -> ioError (userError (errorMessage ++ "\n" ++ helpMessage))
    (_, _, errs) -> ioError (userError (concat errs ++ helpMessage))

main :: IO ()
main = do
  options <- parseArgs
  putStrLn $ show options

Как я могу улучшить это?

person Ralph    schedule 30.05.2012