packages feed

doc-review-0.7: src/Config/GetOpt.hs

module Config.GetOpt
    ( Opts
    , MkCfg
    , optSpec
    , noArgs
    , contentDirDesc
    , Err
    )
where

import Data.Monoid ( mappend )
import Control.Monad ( liftM, ap, guard )
import Config.Types ( ActionSpec(..), preUsage, CommandResult(..)
                    , isHelpRequest )
import Config.Format ( wrap, indent )
import Data.Char ( isSpace )
import System.Console.GetOpt

type Opts cfg = [OptDescr (Err (cfg -> cfg))]
type MkCfg cfg = [String] -> Err cfg

contentDirDesc :: (FilePath -> a) -> OptDescr a
contentDirDesc f = Option "" ["content-dir"] (ReqArg f "DIR")
                   "Where to find the marked up HTML files. Defaults to \
                   \the current working directory."

optSpec :: String -> (cfg -> a) -> Opts cfg -> MkCfg cfg -> String
        -> ActionSpec a
optSpec name mkAction osOpts osCfg desc =
    ActionSpec
    { asName = name
    , asDescr = desc
    , asParse = parseOptSpec
    }
    where
      actUsage = preUsage [usageInfo (unlines $ wrap 78 $ desc) osOpts]

      strip = reverse . dropWhile isSpace . reverse

      parseOptSpec args =
          let (os, args', es) = getOpt Permute osOpts args
              flgs = if null es
                     then sequenceE os
                     else Err $ map strip es ++ toErrors (sequenceE os)
              result xs = case (flgs, osCfg xs) of
                            (Err es1, Err es2) -> Err $ es1 ++ es2
                            (_, cfg)           ->
                                foldr ($) `liftM` cfg `ap` flgs
              handle (Err msgs)  =
                  CommandFailed $ actUsage `mappend` preUsage
                                    ("Errors parsing arguments:":indent 2 msgs)
              handle (Val cfg) =
                  CommandOk $ mkAction cfg
              accepted actArgs =
                  if isHelpRequest actArgs
                  then HelpRequest actUsage
                  else handle $ result actArgs
          in case args' of
               (k:actArgs) -> guard (k == name) >> return (accepted actArgs)
               _                       -> Nothing


noArgs :: a -> [String] -> Err a
noArgs x [] = return x
noArgs _ args = fail $ "Unexpected arguments: " ++ show args

-- Error monad (like either)
data Err a = Err [String] | Val a
instance Monad Err where
    return        = Val
    fail          = Err . return
    (Err s) >>= _ = Err s
    (Val x) >>= f = f x

sequenceE :: [Err a] -> Err [a]
sequenceE xs = case errs xs of
                 ([], as) -> Val as
                 (es, _)  -> Err es

-- Partition errors and successes
errs :: [Err a] -> ([String], [a])
errs (x:xs) = let (es, as) = errs xs
              in case x of
                   Err e -> (e ++ es, as)
                   Val x' -> (es, x':as)
errs [] = ([], [])

toErrors :: Err a -> [String]
toErrors (Err es) = es
toErrors _        = []