packages feed

hsenv-0.3: src/Util/Args/Args.hs

{-# LANGUAGE Arrows #-}
module Util.Args.Args (parseArgs) where

import Util.Args.ArgDescr (ArgDescr(..), KnownArgs)
import Util.Args.ArgArrow (ArgArrow, runArgArrow, askArgs, getKnownArgs, addKnownArg)
import Util.Args.RawArgs (Args(..), parseArguments)
import Control.Arrow ((>>>), returnA, arr)
import System.Environment (getArgs)
import System.IO (stderr, hPutStrLn)
import System.Exit (exitFailure, exitSuccess)
import Data.Maybe (catMaybes)
import Util.Args.Usage (usage)

-- cli arg parsing result
data ArgParseResult a = Usage
                      | Help
                      | Error String
                      | Version
                      | OK a

-- wraps a cli arg parsing computation to add few standard
-- arguments: --help (and -h) and --version
-- and allows to distinguish them in the result
helperArgArrow :: ArgArrow a b -> ArgArrow a (ArgParseResult b)
helperArgArrow arrow = proc x -> do
  addKnownArg knargs -< ()
  args <- askArgs -< ()
  if "help" `elem` switches args || 'h' `elem` shortSwitches args then
    returnA -< Help
   else if "usage" `elem` switches args then
    returnA -< Usage
   else if "version" `elem` switches args then
    returnA -< Version
   else
    arrow >>> arr OK -< x
    where knargs = [versionOpt, helpOpt]
          helpOpt = SwitchDescr "help" "Show this help message" (Just 'h')
          versionOpt = SwitchDescr "version" "Show version string" Nothing

-- prints a msg to stderr and exits program with return value 1
failWith :: String -> IO a
failWith s = hPutStrLn stderr s >> exitFailure

-- validates provided cli arguments against known argument descriptions
-- handles unknown arguments, and currently forbids positional arguments
validateArguments :: Args -> KnownArgs -> IO ()
validateArguments args knArgs
    | not $ null $ positionals args = failWith "Positional arguments are not allowed"
    | otherwise =
        either failWith return $ do
          mapM_ (validate "short switch" "-" knShortSwitches (:"")) $ shortSwitches args
          mapM_ (validate "switch" "--" knSwitches id) $ switches args
          mapM_ (validate "option" "--" knKeys id . fst) $ valArgs args
    where knShortSwitches = catMaybes $ flip map knArgs $ \x -> case x of
                               SwitchDescr _ _ c -> c
                               _ -> Nothing
          knSwitches = catMaybes $ flip map knArgs $ \x -> case x of
                               SwitchDescr name _ _ -> Just name
                               _ -> Nothing
          knKeys = catMaybes $ flip map knArgs $ \x -> case x of
                               ValArg name _ _ _ -> Just name
                               _ -> Nothing
          validate xName prefix knownXs showX x =
              if x `elem` knownXs then
                  Right ()
              else
                  Left $ "Unknown " ++ xName ++ " '" ++ prefix ++ showX x ++ "'"

-- takes a cli arg parsing computation, version string and usage msg footer
-- runs the computation on program cli arguments
-- and returns the result. if arguments validation fails,
-- prints usage message and exits with failure
parseArgs :: ArgArrow () a -> String -> String -> IO a
parseArgs arrgArr version outro = do
  args <- getArgs
  case parseArguments args of
    Left s -> failWith s
    Right parsedArgs -> do
      validateArguments parsedArgs $ getKnownArgs arrgArr'
      result <- runArgArrow arrgArr' parsedArgs
      case result of
        OK a -> return a
        Error s -> failWith s
        Version -> putStrLn version >> exitSuccess
        _ -> usage arrgArr' outro >>= putStr >> exitSuccess
  where arrgArr' = helperArgArrow arrgArr