packages feed

NoSlow-0.2: NoSlow/Util/Opts.hs

module NoSlow.Util.Opts ( OptM, update, exit, error, parse, parseAll,
                          printError,
                          noArg, reqArg, readArg, helpArg, listArg,
                          selArg, matchName, matchLib, matchGroup )
where

import NoSlow.Util.Tag

import System.Console.GetOpt
import System.Exit
import System.Environment       ( getProgName )
import System.IO                ( stderr, hPutStrLn )

import Data.List ( foldl' )
import Data.Monoid
import Prelude hiding ( error, read )

data Result a = Ok a
              | Exit (IO ()) ExitCode
              | Error String

instance Functor Result where
  fmap f r = r >>= return . f

instance Monad Result where
  return = Ok

  Ok x     >>= f = f x
  Exit p c >>= _ = Exit p c
  Error s  >>= _ = Error s

newtype OptM a = OptM (a -> Result a)

instance Monoid (OptM a) where
  mempty             = OptM Ok
  mappend (OptM f) o = OptM (apply o . f)

result :: Result a -> OptM a
result = OptM . const

update :: (a -> a) -> OptM a
update f = OptM $ Ok . f

exit :: IO () -> ExitCode -> OptM a
exit p = result . Exit p

error :: String -> OptM a
error = result . Error

errorMsg :: String -> OptM a -> OptM a
errorMsg msg (OptM f) = OptM $ \x -> case f x of
                                       Error _ -> Error msg
                                       r       -> r

check :: Bool -> OptM a -> OptM a
check True  o = o
check False _ = error "invalid argument"

read :: Read a => (a -> OptM b) -> String -> OptM b
read f s = case reads s of
             [(x,"")] -> f x
             _        -> error "invalid argument"

require :: (a -> Bool) -> (a -> OptM b) -> a -> OptM b
require f g x = check (f x) (g x)

apply :: OptM a -> Result a -> Result a
apply (OptM f) r = r >>= f

chain :: Result a -> (a -> OptM b) -> OptM b
chain r o = OptM $ \x -> do { a <- r; run (o a) x }
                             
run :: OptM a -> a -> Result a
run (OptM f) x = f x

parse :: a -> [OptDescr (OptM a)] -> [String] -> IO (a, [String])
parse a options args
  = case getOpt Permute options args of
      (os, args', []) ->
        case run (mconcat os) a of
          Ok r     -> return (r, args')
          Exit p c -> do { p; exitWith c }
          Error s  -> printError s
      (_, _, (s : _)) -> printError s

parseAll :: a -> [OptDescr (OptM a)] -> [String] -> IO a
parseAll a options args
  = do
      (r, args') <- parse a options args
      case args' of
        []    -> return r
        s : _ -> printError $ "extra argument `" ++ s ++ "'" 

printError :: String -> IO a
printError msg
  = do
      prog <- getProgName
      hPutStrLn stderr $ "Error: " ++ msg
      hPutStrLn stderr $ "Run \"" ++ prog
                         ++ " --help\" for usage information"
      exitWith (ExitFailure 1)

noArg :: (a -> a) -> ArgDescr (OptM a)
noArg = NoArg . update

reqArg :: (String -> a -> a) -> String -> ArgDescr (OptM a)
reqArg f = ReqArg (update . f)

readArg :: Read a => String -> (a -> Bool) -> (a -> b -> b)
                       -> String -> ArgDescr (OptM b)
readArg s f g = ReqArg (errorMsg msg . (read $ require f $ update . g))
  where
    msg = "invalid " ++ s

helpArg :: IO () -> ArgDescr (OptM a)
helpArg p = NoArg (exit p ExitSuccess)

listArg :: (String -> Result a) -> ([a] -> b -> b)
              -> String -> ArgDescr (OptM b)
listArg f g = ReqArg $ \s -> chain (mapM f (split ',' s)) (update . g)

selArg :: (String -> Result (Tag -> Bool))
       -> ((Tag -> Bool) -> a -> a) -> String -> ArgDescr (OptM a)
selArg f g = listArg f (g . foldr or (const False))
  where
    or f g t = f t || g t

matchName :: String -> Result (Tag -> Bool)
matchName s = Ok $ \t -> tagName t == s

matchLib :: String -> Result (Tag -> Bool)
matchLib "" = Ok $ const False
matchLib s  = case split '/' s of
               [lib]     -> Ok $ \t -> tagLibrary t == lib
               [lib,sub] -> Ok $ \t -> tagLibrary t == lib
                                       && tagSubsystem t == sub
               _         -> Error $ "Invalid library " ++ s

matchGroup :: String -> Result (Tag -> Bool)
matchGroup grp = Ok $ \t -> tagGroup t == grp