{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
module Data.Yaml.Config
(
loadYamlSettings
, loadYamlSettingsArgs
, EnvUsage
, ignoreEnv
, useEnv
, requireEnv
, useCustomEnv
, requireCustomEnv
, applyCurrentEnv
, getCurrentEnv
, applyEnvValue
) where
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>))
import Data.Monoid
#endif
import Data.Semigroup
import Data.List.NonEmpty (nonEmpty)
import Data.Aeson
import qualified Data.HashMap.Strict as H
import Data.Text (Text, pack)
import System.Environment (getArgs, getEnvironment)
import Control.Arrow ((***))
import Control.Monad (forM)
import Control.Exception (throwIO)
import Data.Text.Encoding (encodeUtf8)
import qualified Data.Yaml as Y
import qualified Data.Yaml.Include as YI
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
newtype MergedValue = MergedValue { MergedValue -> Value
getMergedValue :: Value }
instance Semigroup MergedValue where
MergedValue Value
x <> :: MergedValue -> MergedValue -> MergedValue
<> MergedValue Value
y = Value -> MergedValue
MergedValue (Value -> MergedValue) -> Value -> MergedValue
forall a b. (a -> b) -> a -> b
$ Value -> Value -> Value
mergeValues Value
x Value
y
mergeValues :: Value -> Value -> Value
mergeValues :: Value -> Value -> Value
mergeValues (Object Object
x) (Object Object
y) = Object -> Value
Object (Object -> Value) -> Object -> Value
forall a b. (a -> b) -> a -> b
$ (Value -> Value -> Value) -> Object -> Object -> Object
forall k v.
(Eq k, Hashable k) =>
(v -> v -> v) -> HashMap k v -> HashMap k v -> HashMap k v
H.unionWith Value -> Value -> Value
mergeValues Object
x Object
y
mergeValues Value
x Value
_ = Value
x
applyEnvValue :: Bool
-> H.HashMap Text Text -> Value -> Value
applyEnvValue :: Bool -> HashMap Text Text -> Value -> Value
applyEnvValue Bool
requireEnv' HashMap Text Text
env =
Value -> Value
goV
where
goV :: Value -> Value
goV (Object Object
o) = Object -> Value
Object (Object -> Value) -> Object -> Value
forall a b. (a -> b) -> a -> b
$ Value -> Value
goV (Value -> Value) -> Object -> Object
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o
goV (Array Array
a) = Array -> Value
Array (Value -> Value
goV (Value -> Value) -> Array -> Array
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Array
a)
goV (String Text
t1) = Value -> Maybe Value -> Value
forall a. a -> Maybe a -> a
fromMaybe (Text -> Value
String Text
t1) (Maybe Value -> Value) -> Maybe Value -> Value
forall a b. (a -> b) -> a -> b
$ do
Text
t2 <- Text -> Text -> Maybe Text
T.stripPrefix Text
"_env:" Text
t1
let (Text
name, Text
t3) = (Char -> Bool) -> Text -> (Text, Text)
T.break (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
':') Text
t2
mdef :: Maybe Value
mdef = (Text -> Value) -> Maybe Text -> Maybe Value
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> Value
parseValue (Maybe Text -> Maybe Value) -> Maybe Text -> Maybe Value
forall a b. (a -> b) -> a -> b
$ Text -> Text -> Maybe Text
T.stripPrefix Text
":" Text
t3
Value -> Maybe Value
forall a. a -> Maybe a
Just (Value -> Maybe Value) -> Value -> Maybe Value
forall a b. (a -> b) -> a -> b
$ case Text -> HashMap Text Text -> Maybe Text
forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
H.lookup Text
name HashMap Text Text
env of
Just Text
val ->
case Maybe Value
mdef of
Just (String Text
_) -> Text -> Value
String Text
val
Maybe Value
_ -> Text -> Value
parseValue Text
val
Maybe Text
Nothing ->
case Maybe Value
mdef of
Just Value
val | Bool -> Bool
not Bool
requireEnv' -> Value
val
Maybe Value
_ -> Value
Null
goV Value
v = Value
v
parseValue :: Text -> Value
parseValue Text
val = (SomeException -> Value)
-> (Value -> Value) -> Either SomeException Value -> Value
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either
(Value -> SomeException -> Value
forall a b. a -> b -> a
const (Text -> Value
String Text
val))
Value -> Value
forall a. a -> a
id
(ByteString -> Either SomeException Value
forall (m :: * -> *) a.
(MonadThrow m, FromJSON a) =>
ByteString -> m a
Y.decodeThrow (ByteString -> Either SomeException Value)
-> ByteString -> Either SomeException Value
forall a b. (a -> b) -> a -> b
$ Text -> ByteString
encodeUtf8 Text
val)
getCurrentEnv :: IO (H.HashMap Text Text)
getCurrentEnv :: IO (HashMap Text Text)
getCurrentEnv = ([(String, String)] -> HashMap Text Text)
-> IO [(String, String)] -> IO (HashMap Text Text)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ([(Text, Text)] -> HashMap Text Text
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
H.fromList ([(Text, Text)] -> HashMap Text Text)
-> ([(String, String)] -> [(Text, Text)])
-> [(String, String)]
-> HashMap Text Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((String, String) -> (Text, Text))
-> [(String, String)] -> [(Text, Text)]
forall a b. (a -> b) -> [a] -> [b]
map (String -> Text
pack (String -> Text)
-> (String -> Text) -> (String, String) -> (Text, Text)
forall (a :: * -> * -> *) b c b' c'.
Arrow a =>
a b c -> a b' c' -> a (b, b') (c, c')
*** String -> Text
pack)) IO [(String, String)]
getEnvironment
applyCurrentEnv :: Bool
-> Value -> IO Value
applyCurrentEnv :: Bool -> Value -> IO Value
applyCurrentEnv Bool
requireEnv' Value
orig = (HashMap Text Text -> Value -> Value)
-> Value -> HashMap Text Text -> Value
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Bool -> HashMap Text Text -> Value -> Value
applyEnvValue Bool
requireEnv') Value
orig (HashMap Text Text -> Value) -> IO (HashMap Text Text) -> IO Value
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO (HashMap Text Text)
getCurrentEnv
data EnvUsage = IgnoreEnv
| UseEnv
| RequireEnv
| UseCustomEnv (H.HashMap Text Text)
| RequireCustomEnv (H.HashMap Text Text)
ignoreEnv :: EnvUsage
ignoreEnv :: EnvUsage
ignoreEnv = EnvUsage
IgnoreEnv
useEnv :: EnvUsage
useEnv :: EnvUsage
useEnv = EnvUsage
UseEnv
requireEnv :: EnvUsage
requireEnv :: EnvUsage
requireEnv = EnvUsage
RequireEnv
useCustomEnv :: H.HashMap Text Text -> EnvUsage
useCustomEnv :: HashMap Text Text -> EnvUsage
useCustomEnv = HashMap Text Text -> EnvUsage
UseCustomEnv
requireCustomEnv :: H.HashMap Text Text -> EnvUsage
requireCustomEnv :: HashMap Text Text -> EnvUsage
requireCustomEnv = HashMap Text Text -> EnvUsage
RequireCustomEnv
loadYamlSettings
:: FromJSON settings
=> [FilePath]
-> [Value]
-> EnvUsage
-> IO settings
loadYamlSettings :: [String] -> [Value] -> EnvUsage -> IO settings
loadYamlSettings [String]
runTimeFiles [Value]
compileValues EnvUsage
envUsage = do
[Value]
runValues <- [String] -> (String -> IO Value) -> IO [Value]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM [String]
runTimeFiles ((String -> IO Value) -> IO [Value])
-> (String -> IO Value) -> IO [Value]
forall a b. (a -> b) -> a -> b
$ \String
fp -> do
Either ParseException Value
eres <- String -> IO (Either ParseException Value)
forall a. FromJSON a => String -> IO (Either ParseException a)
YI.decodeFileEither String
fp
case Either ParseException Value
eres of
Left ParseException
e -> ParseException -> IO Value
forall e a. Exception e => e -> IO a
throwIO (String -> ParseException -> ParseException
Y.LoadSettingsException String
fp ParseException
e)
Right Value
value -> Value -> IO Value
forall (m :: * -> *) a. Monad m => a -> m a
return Value
value
Value
value' <-
case [MergedValue] -> Maybe (NonEmpty MergedValue)
forall a. [a] -> Maybe (NonEmpty a)
nonEmpty ([MergedValue] -> Maybe (NonEmpty MergedValue))
-> [MergedValue] -> Maybe (NonEmpty MergedValue)
forall a b. (a -> b) -> a -> b
$ (Value -> MergedValue) -> [Value] -> [MergedValue]
forall a b. (a -> b) -> [a] -> [b]
map Value -> MergedValue
MergedValue ([Value] -> [MergedValue]) -> [Value] -> [MergedValue]
forall a b. (a -> b) -> a -> b
$ [Value]
runValues [Value] -> [Value] -> [Value]
forall a. [a] -> [a] -> [a]
++ [Value]
compileValues of
Maybe (NonEmpty MergedValue)
Nothing -> String -> IO Value
forall a. HasCallStack => String -> a
error String
"loadYamlSettings: No configuration provided"
Just NonEmpty MergedValue
ne -> Value -> IO Value
forall (m :: * -> *) a. Monad m => a -> m a
return (Value -> IO Value) -> Value -> IO Value
forall a b. (a -> b) -> a -> b
$ MergedValue -> Value
getMergedValue (MergedValue -> Value) -> MergedValue -> Value
forall a b. (a -> b) -> a -> b
$ NonEmpty MergedValue -> MergedValue
forall a. Semigroup a => NonEmpty a -> a
sconcat NonEmpty MergedValue
ne
Value
value <-
case EnvUsage
envUsage of
EnvUsage
IgnoreEnv -> Value -> IO Value
forall (m :: * -> *) a. Monad m => a -> m a
return (Value -> IO Value) -> Value -> IO Value
forall a b. (a -> b) -> a -> b
$ Bool -> HashMap Text Text -> Value -> Value
applyEnvValue Bool
False HashMap Text Text
forall a. Monoid a => a
mempty Value
value'
EnvUsage
UseEnv -> Bool -> Value -> IO Value
applyCurrentEnv Bool
False Value
value'
EnvUsage
RequireEnv -> Bool -> Value -> IO Value
applyCurrentEnv Bool
True Value
value'
UseCustomEnv HashMap Text Text
env -> Value -> IO Value
forall (m :: * -> *) a. Monad m => a -> m a
return (Value -> IO Value) -> Value -> IO Value
forall a b. (a -> b) -> a -> b
$ Bool -> HashMap Text Text -> Value -> Value
applyEnvValue Bool
False HashMap Text Text
env Value
value'
RequireCustomEnv HashMap Text Text
env -> Value -> IO Value
forall (m :: * -> *) a. Monad m => a -> m a
return (Value -> IO Value) -> Value -> IO Value
forall a b. (a -> b) -> a -> b
$ Bool -> HashMap Text Text -> Value -> Value
applyEnvValue Bool
True HashMap Text Text
env Value
value'
case (Value -> Parser settings) -> Value -> Either String settings
forall a b. (a -> Parser b) -> a -> Either String b
Y.parseEither Value -> Parser settings
forall a. FromJSON a => Value -> Parser a
parseJSON Value
value of
Left String
s -> String -> IO settings
forall a. HasCallStack => String -> a
error (String -> IO settings) -> String -> IO settings
forall a b. (a -> b) -> a -> b
$ String
"Could not convert to expected type: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
s
Right settings
settings -> settings -> IO settings
forall (m :: * -> *) a. Monad m => a -> m a
return settings
settings
loadYamlSettingsArgs
:: FromJSON settings
=> [Value]
-> EnvUsage
-> IO settings
loadYamlSettingsArgs :: [Value] -> EnvUsage -> IO settings
loadYamlSettingsArgs [Value]
values EnvUsage
env = do
[String]
args <- IO [String]
getArgs
[String] -> [Value] -> EnvUsage -> IO settings
forall settings.
FromJSON settings =>
[String] -> [Value] -> EnvUsage -> IO settings
loadYamlSettings [String]
args [Value]
values EnvUsage
env