{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE CPP #-} {-| Module : System.ReadEnvVar Copyright : (c) Dennis Gosnell, 2016 License : BSD-style (see LICENSE file) Maintainer : cdep.illabout@gmail.com Stability : experimental Portability : POSIX This Haskell module exports functions for safely reading environment variables. The @lookupEnv*@ functions are for reading in 'String'-like environment variables (like hostnames, passwords, etc.), while the @readEnv*@ functions are for reading in Haskell datatypes (like 'Int', 'Double', etc). Most of these functions run in 'MonadIO'. This means that they can be used from any monad that implements 'MonadIO'. This makes it easier to run in a monad transformer stack. If you're not familiar with 'MonadIO', you can just think of all the functions as being 'IO' actions. The 'lookupEnv', 'lookupEnvDef', and 'lookupEnvEx' functions all use 'IsString' to generalize the return type. This makes it more general than "System.Environment"\'s 'Env.lookupEnv'. It makes it possible to read in things other than 'String's, like or . -} module System.ReadEnvVar ( lookupEnv , lookupEnvDef , readEnv , readEnvDef -- * Unsafe functions , EnvVarDoesNotExistException(..) , lookupEnvEx , readEnvEx , EnvVarCannotBeReadException(..) , readEnvEx' -- * Re-exports , setEnv ) where #if __GLASGOW_HASKELL__ < 710 -- We don't need this import for GHC 7.10 as it exports all required functions -- from Prelude import Control.Applicative #endif import Control.Exception (Exception) import Control.Monad.Catch (MonadThrow(throwM)) import Control.Monad.IO.Class (MonadIO(liftIO)) import Data.Data (Data) import Data.Maybe (fromMaybe) import Data.String (IsString(fromString)) import Data.Typeable (Typeable) import System.Environment (setEnv) import qualified System.Environment as Env import Text.Read (readMaybe) -- | Lookup a value from an environment variable and read it in with -- 'readMaybe'. If the environment variable doesn't exist, or it can't be -- 'read', return the default value. Like 'readEnv' but with a default -- value. -- -- Read an environment variable that exists: -- -- >>> setEnv "TEST_ENV_VAR1" "1000" -- >>> readEnvDef "TEST_ENV_VAR1" 5 :: IO Int -- 1000 -- -- Try reading an environment variable that does not exist. Returns the -- default value: -- -- >>> readEnvDef "THIS_ENV_VAR_WILL_NOT_EXIST" 5 :: IO Int -- 5 -- -- Try reading an environment variable that cannot be 'read'. Returns the -- default value: -- -- >>> setEnv "BAD_ENV_VAR" "not an int" -- >>> readEnvDef "BAD_ENV_VAR" 10 :: IO Int -- 10 -- -- Note that this __DOES NOT__ read string values as one might expect: -- -- >>> setEnv "TEST_ENV_VAR2" "some string 1" -- >>> readEnvDef "TEST_ENV_VAR2" "def val" :: IO String -- "def val" -- -- It will read string values as if they were Haskell strings: -- -- >>> setEnv "TEST_ENV_VAR3" "\"some string 1\"" -- >>> readEnvDef "TEST_ENV_VAR3" "def val" :: IO String -- "some string 1" readEnvDef :: (MonadIO m, Read a) => String -- ^ environment variable to lookup -> a -- ^ default value to return if the environment variable -- either does not exist, or cannot be 'read' -> m a readEnvDef envVar def = do maybeEnv <- readEnv envVar return $ fromMaybe def maybeEnv -- | Lookup a value from an environment variable and read it in with -- 'readMaybe'. -- -- Read an environment variable that exists: -- -- >>> setEnv "TEST_ENV_VAR" "2000" -- >>> readEnv "TEST_ENV_VAR" :: IO (Maybe Int) -- Just 2000 -- -- Try reading an environment variable that does not exist. Returns 'Nothing': -- -- >>> readEnv "THIS_ENV_VAR_WILL_NOT_EXIST" :: IO (Maybe Int) -- Nothing -- -- Try reading an environment variable that cannot be 'read'. Returns -- 'Nothing': -- -- >>> setEnv "BAD_ENV_VAR" "not an int" -- >>> readEnv "BAD_ENV_VAR" :: IO (Maybe Int) -- Nothing -- -- Note that this __DOES NOT__ read string values as one might expect: -- -- >>> setEnv "TEST_ENV_VAR2" "some string 1" -- >>> readEnv "TEST_ENV_VAR2" :: IO (Maybe String) -- Nothing -- -- It will read string values as if they were Haskell strings: -- -- >>> setEnv "TEST_ENV_VAR3" "\"some string 1\"" -- >>> readEnv "TEST_ENV_VAR3" :: IO (Maybe String) -- Just "some string 1" readEnv :: (MonadIO m, Read a) => String -- ^ environment variable to lookup -> m (Maybe a) readEnv envVar = do maybeEnv <- liftIO (Env.lookupEnv envVar) return $ maybeEnv >>= readMaybe -- | Like 'lookupEnv' but take a default value. -- -- Lookup an environment variable that exists: -- -- >>> setEnv "TEST_ENV_VAR" "foo" -- >>> lookupEnvDef "TEST_ENV_VAR" "bar" :: IO String -- "foo" -- -- Lookup an environment variable that doesn't exist. Return the default -- value: -- -- >>> lookupEnvDef "THIS_ENV_VAR_WILL_NOT_EXIST" "bar" :: IO String -- "bar" lookupEnvDef :: (IsString a, MonadIO m) => String -- ^ environment variable to lookup -> a -- ^ default value to return if environment variable not defined -> m a lookupEnvDef envVar defaultValue = do maybeEnv <- liftIO $ Env.lookupEnv envVar return $ maybe defaultValue fromString maybeEnv -- | Like "System.Environment"\'s 'Env.lookupEnv', but using 'IsString' to make -- it more general. -- -- Lookup an environment variable that exists: -- -- >>> setEnv "TEST_ENV_VAR" "foo" -- >>> lookupEnv "TEST_ENV_VAR" :: IO (Maybe String) -- Just "foo" -- -- Lookup an environment variable that doesn't exist. Return 'Nothing': -- -- >>> lookupEnv "THIS_ENV_VAR_WILL_NOT_EXIST" :: IO (Maybe String) -- Nothing lookupEnv :: (IsString a, MonadIO m) => String -> m (Maybe a) lookupEnv envVar = do maybeEnv <- liftIO $ Env.lookupEnv envVar return $ fmap fromString maybeEnv -- | 'Exception' thrown by 'lookupEnvEx' and 'readEnvEx' when the -- environment variable being read doesn't exist. data EnvVarDoesNotExistException = -- | The 'String' is the name of the environment variable that does not -- exist. EnvVarDoesNotExistException String deriving (Data, Eq, Ord, Read, Show, Typeable) instance Exception EnvVarDoesNotExistException -- | Like 'lookupEnv', but instead of returning a 'Maybe', throw an -- 'EnvVarDoesNotExistException' if the environment variable does not exist. -- The exception is thrown with 'throwM' from 'MonadThrow'. -- -- Lookup an environment variable that exists: -- -- >>> setEnv "TEST_ENV_VAR" "foo" -- >>> lookupEnvEx "TEST_ENV_VAR" :: IO String -- "foo" -- -- Lookup an environment variable that doesn't exist. Throws -- 'EnvVarDoesNotExistException'. -- -- >>> lookupEnvEx "THIS_ENV_VAR_WILL_NOT_EXIST" :: IO String -- *** Exception: EnvVarDoesNotExistException "THIS_ENV_VAR_WILL_NOT_EXIST" lookupEnvEx :: (IsString a, MonadIO m, MonadThrow m) => String -> m a lookupEnvEx envVar = do maybeEnv <- liftIO (Env.lookupEnv envVar) case maybeEnv of Nothing -> throwM $ EnvVarDoesNotExistException envVar Just env -> return $ fromString env -- | Lookup a value from an environment variable and read it in with -- 'readMaybe'. Throw an 'EnvVarDoesNotExistException' if the environment -- variable does not exist. The exception is thrown with 'throwM' from -- 'MonadThrow'. -- -- Read an environment variable that exists: -- -- >>> setEnv "TEST_ENV_VAR" "2000" -- >>> readEnvEx "TEST_ENV_VAR" :: IO (Maybe Int) -- Just 2000 -- -- Try reading an environment variable that does not exist. Throws -- 'EnvVarDoesNotExistException': -- -- >>> readEnvEx "THIS_ENV_VAR_WILL_NOT_EXIST" :: IO (Maybe Int) -- *** Exception: EnvVarDoesNotExistException "THIS_ENV_VAR_WILL_NOT_EXIST" -- -- Try reading an environment variable that cannot be 'read'. Returns -- 'Nothing': -- -- >>> setEnv "BAD_ENV_VAR" "not an int" -- >>> readEnvEx "BAD_ENV_VAR" :: IO (Maybe Int) -- Nothing -- -- Note that this __DOES NOT__ read string values as one might expect: -- -- >>> setEnv "TEST_ENV_VAR2" "some string 1" -- >>> readEnvEx "TEST_ENV_VAR2" :: IO (Maybe String) -- Nothing -- -- It will read string values as if they were Haskell strings: -- -- >>> setEnv "TEST_ENV_VAR3" "\"some string 1\"" -- >>> readEnvEx "TEST_ENV_VAR3" :: IO (Maybe String) -- Just "some string 1" readEnvEx :: (MonadIO m, Read a, MonadThrow m) => String -- ^ environment variable to lookup -> m (Maybe a) readEnvEx envVar = do maybeEnv <- liftIO (Env.lookupEnv envVar) case maybeEnv of Nothing -> throwM $ EnvVarDoesNotExistException envVar Just env -> return $ readMaybe env -- | 'Exception' thrown by 'readEnvEx'' when the environment variable -- cannot be 'read'. data EnvVarCannotBeReadException = -- | The first 'String' is the name of the environment variable that cannot -- be 'read'. EnvVarCannotBeReadException String deriving (Data, Eq, Ord, Read, Show, Typeable) instance Exception EnvVarCannotBeReadException -- | Just like 'readEnvEx', but also throw an exception when the environment -- variable cannot be 'read'. -- -- This can throw both 'EnvVarCannotBeReadException' and -- 'EnvVarDoesNotExistException' with 'throwM'. -- -- Read an environment variable that exists: -- -- >>> setEnv "TEST_ENV_VAR" "2000" -- >>> readEnvEx' "TEST_ENV_VAR" :: IO Int -- 2000 -- -- Try reading an environment variable that does not exist. Throws -- 'EnvVarDoesNotExistException': -- -- >>> readEnvEx' "THIS_ENV_VAR_WILL_NOT_EXIST" :: IO Int -- *** Exception: EnvVarDoesNotExistException "THIS_ENV_VAR_WILL_NOT_EXIST" -- -- Try reading an environment variable that cannot be 'read'. Throws -- 'EnvVarCannotBeReadException': -- -- >>> setEnv "BAD_ENV_VAR" "not an int" -- >>> readEnvEx' "BAD_ENV_VAR" :: IO Int -- *** Exception: EnvVarCannotBeReadException "BAD_ENV_VAR" -- -- Note that this __DOES NOT__ read string values as one might expect: -- -- >>> setEnv "TEST_ENV_VAR2" "some string 1" -- >>> readEnvEx' "TEST_ENV_VAR2" :: IO String -- *** Exception: EnvVarCannotBeReadException "TEST_ENV_VAR2" -- -- It will read string values as if they were Haskell strings: -- -- >>> setEnv "TEST_ENV_VAR3" "\"some string 1\"" -- >>> readEnvEx' "TEST_ENV_VAR3" :: IO String -- "some string 1" readEnvEx' :: (MonadIO m, Read a, MonadThrow m) => String -- ^ environment variable to lookup -> m a readEnvEx' envVar = do maybeEnv <- readEnvEx envVar case maybeEnv of Nothing -> throwM $ EnvVarCannotBeReadException envVar Just env -> return env