{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
module System.Envy
(
FromEnv (..)
, ToEnv (..)
, Var (..)
, EnvList (..)
, EnvVar (..)
, Parser (..)
, decodeEnv
, decodeWithDefaults
, decode
, showEnv
, setEnvironment
, setEnvironment'
, unsetEnvironment
, unsetEnvironment'
, makeEnv
, env
, envMaybe
, (.=)
, (.!=)
, ReadShowVar (..)
, DefConfig (..)
, Option (..)
, defOption
, runEnv
, gFromEnvCustom
) where
import Control.Applicative
import Control.Monad.Except
import Control.Monad.Fail
import Control.Exception
import Data.Functor.Identity
import Data.Maybe
import Data.Monoid
import Data.Char
import Data.Time
import GHC.Generics
import Data.Typeable
import System.Environment.Blank
import Text.Read (readMaybe)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Data.Text (Text)
import Data.Word
import Data.Int
import qualified Data.ByteString.Char8 as B8
import qualified Data.ByteString.Lazy.Char8 as BL8
newtype Parser a = Parser { runParser :: ExceptT String IO a }
deriving ( Functor, Monad, Applicative, MonadError String
, MonadIO, Alternative, MonadPlus )
instance MonadFail Parser where
fail = Parser . throwError
data EnvVar = EnvVar {
variableName :: String,
variableValue :: String
}
deriving (Show, Eq)
evalParser :: Parser a -> IO (Either String a)
evalParser = runExceptT . runParser
runEnv :: Parser a -> IO (Either String a)
runEnv = runExceptT . runParser
env :: Var a
=> String
-> Parser a
env key = do
result <- liftIO (getEnv key)
case result of
Nothing -> throwError $ "Variable not found for: " ++ key
Just dv ->
case fromVar dv of
Nothing -> throwError $ ("Parse failure: could not parse variable "
++ show key ++ " into type "
++ show (typeOf dv))
Just x -> return x
envMaybe :: Var a
=> String
-> Parser (Maybe a)
envMaybe key = do
val <- liftIO (getEnv key)
return $ case val of
Nothing -> Nothing
Just x -> fromVar x
(.!=) :: Parser (Maybe a)
-> a
-> Parser a
(.!=) parser def = fromMaybe def <$> parser
(.=) :: Var a
=> String
-> a
-> EnvVar
(.=) variableName value = EnvVar variableName (toVar value)
class FromEnv a where
fromEnv :: Maybe a -> Parser a
default fromEnv :: (Generic a, GFromEnv (Rep a)) => Maybe a -> Parser a
fromEnv oa = gFromEnvCustom defOption oa
gFromEnvCustom :: forall a. (Generic a, GFromEnv (Rep a))
=> Option
-> Maybe a
-> Parser a
gFromEnvCustom opts oa = to <$> gFromEnv opts (from <$> oa)
class GFromEnv f where
gFromEnv :: Option -> Maybe (f a) -> Parser (f a)
class DefConfig a where defConfig :: a
data Option = Option {
dropPrefixCount :: Int
, customPrefix :: String
} deriving Show
defOption :: Option
defOption = Option 0 mempty
instance (GFromEnv a, GFromEnv b) => GFromEnv (a :*: b) where
gFromEnv opts ox = let (oa, ob) = case ox of
(Just (a :*: b)) -> (Just a, Just b)
_ -> (Nothing, Nothing) in
liftA2 (:*:) (gFromEnv opts oa) (gFromEnv opts ob)
instance GFromEnv a => GFromEnv (C1 i a) where
gFromEnv opts (Just (M1 x))= M1 <$> gFromEnv opts (Just x)
gFromEnv opts _ = M1 <$> gFromEnv opts Nothing
instance GFromEnv a => GFromEnv (D1 i a) where
gFromEnv opts (Just (M1 x)) = M1 <$> gFromEnv opts (Just x)
gFromEnv opts _ = M1 <$> gFromEnv opts Nothing
instance (Selector s, Var a) => GFromEnv (S1 s (K1 i a)) where
gFromEnv opts ox =
let p = case ox of
Just (M1 (K1 def)) -> envMaybe envName .!= def
_ -> env envName in
M1 . K1 <$> p
where
envName = toEnvName opts $ selName (SelectorProxy :: SelectorProxy s Proxy ())
toEnvName :: Option -> String -> String
toEnvName Option{..} xs =
let name = snake (drop dropPrefixCount xs)
in if customPrefix == mempty
then name
else map toUpper customPrefix ++ "_" ++ name
applyFirst :: (Char -> Char) -> String -> String
applyFirst _ [] = []
applyFirst f [x] = [f x]
applyFirst f (x:xs) = f x: xs
snakeCase :: String -> String
snakeCase = u . applyFirst toLower
where u [] = []
u (x:xs) | isUpper x = '_' : toLower x : snakeCase xs
| otherwise = x : u xs
snake :: String -> String
snake = map toUpper . snakeCase
data SelectorProxy (s :: Meta) (f :: * -> *) a = SelectorProxy
class ToEnv a where
toEnv :: a -> EnvList a
data EnvList a = EnvList [EnvVar] deriving (Show)
makeEnv :: [EnvVar] -> EnvList a
makeEnv = EnvList
class Typeable a => Var a where
toVar :: a -> String
fromVar :: String -> Maybe a
instance Var Text where toVar = T.unpack; fromVar = Just . T.pack
instance Var TL.Text where toVar = TL.unpack; fromVar = Just . TL.pack
instance Var BL8.ByteString where toVar = BL8.unpack; fromVar = Just . BL8.pack
instance Var B8.ByteString where toVar = B8.unpack; fromVar = Just . B8.pack
instance Var Int where toVar = show; fromVar = readMaybe
instance Var Int8 where toVar = show; fromVar = readMaybe
instance Var Int16 where toVar = show; fromVar = readMaybe
instance Var Int32 where toVar = show; fromVar = readMaybe
instance Var Int64 where toVar = show; fromVar = readMaybe
instance Var Integer where toVar = show; fromVar = readMaybe
instance Var UTCTime where toVar = show; fromVar = readMaybe
instance Var Day where toVar = show; fromVar = readMaybe
instance Var Word8 where toVar = show; fromVar = readMaybe
instance Var Bool where toVar = show; fromVar = readMaybe
instance Var Double where toVar = show; fromVar = readMaybe
instance Var Word16 where toVar = show; fromVar = readMaybe
instance Var Word32 where toVar = show; fromVar = readMaybe
instance Var Word64 where toVar = show; fromVar = readMaybe
instance Var String where toVar = id; fromVar = Just
instance Var () where toVar = const "()"; fromVar = const $ Just ()
instance Var a => Var (Maybe a) where
toVar = maybe "" toVar
fromVar "" = Nothing
fromVar s = Just <$> fromVar s
deriving instance (Var a, Typeable a) => Var (Last a)
deriving instance (Var a, Typeable a) => Var (First a)
deriving instance (Var a, Typeable a) => Var (Identity a)
newtype ReadShowVar a = ReadShowVar { unReadShowVar :: a }
instance (Typeable a, Show a, Read a) => Var (ReadShowVar a) where
toVar = show . unReadShowVar
fromVar = fmap ReadShowVar . readMaybe
decodeEnv :: FromEnv a => IO (Either String a)
decodeEnv = evalParser (fromEnv Nothing)
decode :: FromEnv a => IO (Maybe a)
decode = fmap eitherToMaybe decodeEnv
where
eitherToMaybe (Left _) = Nothing
eitherToMaybe (Right x) = Just x
decodeWithDefaults :: FromEnv a => a -> IO a
decodeWithDefaults def = (\(Right x) -> x) <$> evalParser (fromEnv (Just def))
wrapIOException :: IO a -> IO (Either String a)
wrapIOException action = try action >>= \case
Left (ex :: IOException) -> return $ Left $ show ex
Right x -> return $ Right x
setEnvironment :: EnvList a -> IO (Either String ())
setEnvironment (EnvList envVars) = wrapIOException $ mapM_ set envVars
where set var = setEnv (variableName var) (variableValue var) True
setEnvironment' :: ToEnv a => a -> IO (Either String ())
setEnvironment' = setEnvironment . toEnv
unsetEnvironment :: EnvList a -> IO (Either String ())
unsetEnvironment (EnvList envVars) = wrapIOException $ mapM_ unset envVars
where unset var = unsetEnv (variableName var)
unsetEnvironment' :: ToEnv a => a -> IO (Either String ())
unsetEnvironment' = unsetEnvironment . toEnv
showEnv :: IO ()
showEnv = mapM_ print =<< getEnvironment