{-# LANGUAGE ScopedTypeVariables, DeriveDataTypeable,
ExistentialQuantification, GADTs,
FlexibleInstances, UndecidableInstances,
TypeOperators #-}
module Test.Tasty.Options
(
IsOption(..)
, OptionSet
, setOption
, changeOption
, lookupOption
, singleOption
, OptionDescription(..)
, flagCLParser
, mkFlagCLParser
, mkOptionCLParser
, safeRead
, safeReadBool
) where
import qualified Data.Map as Map
import Data.Map (Map)
import Data.Char (toLower)
import Data.Tagged
import Data.Proxy
import Data.Typeable
import Data.Monoid
import Data.Foldable
import Prelude hiding (mod)
import Options.Applicative
#if MIN_VERSION_base(4,9,0)
import Data.Semigroup (Semigroup)
import qualified Data.Semigroup (Semigroup((<>)))
#endif
class Typeable v => IsOption v where
defaultValue :: v
parseValue :: String -> Maybe v
optionName :: Tagged v String
optionHelp :: Tagged v String
optionCLParser :: Parser v
optionCLParser = mkOptionCLParser mempty
data OptionValue = forall v . IsOption v => OptionValue v
newtype OptionSet = OptionSet (Map TypeRep OptionValue)
instance Monoid OptionSet where
mempty = OptionSet mempty
OptionSet a `mappend` OptionSet b =
OptionSet $ Map.unionWith (flip const) a b
#if MIN_VERSION_base(4,9,0)
instance Semigroup OptionSet where
(<>) = mappend
#endif
setOption :: IsOption v => v -> OptionSet -> OptionSet
setOption v (OptionSet s) =
OptionSet $ Map.insert (typeOf v) (OptionValue v) s
lookupOption :: forall v . IsOption v => OptionSet -> v
lookupOption (OptionSet s) =
case Map.lookup (typeOf (undefined :: v)) s of
Just (OptionValue x) | Just v <- cast x -> v
Just {} -> error "OptionSet: broken invariant (shouldn't happen)"
Nothing -> defaultValue
changeOption :: forall v . IsOption v => (v -> v) -> OptionSet -> OptionSet
changeOption f s = setOption (f $ lookupOption s) s
singleOption :: IsOption v => v -> OptionSet
singleOption v = setOption v mempty
data OptionDescription where
Option :: IsOption v => Proxy v -> OptionDescription
flagCLParser
:: forall v . IsOption v
=> Maybe Char
-> v
-> Parser v
flagCLParser mbShort = mkFlagCLParser (foldMap short mbShort)
mkFlagCLParser
:: forall v . IsOption v
=> Mod FlagFields v
-> v
-> Parser v
mkFlagCLParser mod v = flag' v
( long (untag (optionName :: Tagged v String))
<> help (untag (optionHelp :: Tagged v String))
<> mod
)
mkOptionCLParser :: forall v . IsOption v => Mod OptionFields v -> Parser v
mkOptionCLParser mod =
option parse
( long name
<> help (untag (optionHelp :: Tagged v String))
<> mod
)
where
name = untag (optionName :: Tagged v String)
parse = str >>=
maybe (readerError $ "Could not parse " ++ name) pure <$> parseValue
safeRead :: Read a => String -> Maybe a
safeRead s
| [(x, "")] <- reads s = Just x
| otherwise = Nothing
safeReadBool :: String -> Maybe Bool
safeReadBool s =
case (map toLower s) of
"true" -> Just True
"false" -> Just False
_ -> Nothing