content: {-# LANGUAGE CPP #-}
  {-# LANGUAGE OverloadedStrings #-}
  -- | Functionality for using YAML as configuration files
  --
  -- In particular, merging environment variables with yaml values
  --
  -- 'loadYamlSettings' is a high-level API for loading YAML and merging environment variables.
  -- A yaml value of @_env:ENV_VAR:default@ or just @_env:ENV_VAR@ will lookup the environment variable @ENV_VAR@.
  --
  -- On a historical note, this code was taken directly from the yesod web framework's configuration module.
  module Data.Yaml.Config
      ( -- * High-level
        loadYamlSettings
      , loadYamlSettingsArgs
        -- ** EnvUsage
      , EnvUsage
      , ignoreEnv
      , useEnv
      , requireEnv
      , useCustomEnv
      , requireCustomEnv
        -- * Lower level
      , 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
  #if MIN_VERSION_aeson(2,0,0)
  import qualified Data.Aeson.Key as K
  import qualified Data.Aeson.KeyMap as H
  import Data.Aeson.KeyMap (KeyMap)
  #else
  import qualified Data.HashMap.Strict as H
  #endif
  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
  
  #if MIN_VERSION_aeson(2,0,0)
  fromText :: T.Text -> Key
  fromText = K.fromText
  #else
  fromText :: T.Text -> T.Text
  fromText = id
  
  type KeyMap a = H.HashMap T.Text a
  #endif
  
  newtype MergedValue = MergedValue { getMergedValue :: Value }
  
  instance Semigroup MergedValue where
      MergedValue x <> MergedValue y = MergedValue $ mergeValues x y
  
  -- | Left biased
  mergeValues :: Value -> Value -> Value
  mergeValues (Object x) (Object y) = Object $ H.unionWith mergeValues x y
  mergeValues x _ = x
  
  -- | Override environment variable placeholders in the given @Value@ with
  -- values from the environment.
  --
  -- If the first argument is @True@, then all placeholders _must_ be provided by
  -- the actual environment. Otherwise, default values from the @Value@ will be
  -- used.
  --
  -- @since 0.8.16
  applyEnvValue :: Bool -- ^ require an environment variable to be present?
                -> KeyMap Text -> Value -> Value
  applyEnvValue requireEnv' env =
      goV
    where
      goV (Object o) = Object $ goV <$> o
      goV (Array a) = Array (goV <$> a)
      goV (String t1) = fromMaybe (String t1) $ do
          t2 <- T.stripPrefix "_env:" t1
          let (name, t3) = T.break (== ':') t2
              mdef = fmap parseValue $ T.stripPrefix ":" t3
          Just $ case H.lookup (fromText name) env of
              Just val ->
                  -- If the default value parses as a String, we treat the
                  -- environment variable as a raw value and do not parse it.
                  -- This means that things like numeric passwords just work.
                  -- However, for originally numerical or boolean values (e.g.,
                  -- port numbers), we still perform a normal YAML parse.
                  --
                  -- For details, see:
                  -- https://github.com/yesodweb/yesod/issues/1061
                  case mdef of
                      Just (String _) -> String val
                      _ -> parseValue val
              Nothing ->
                  case mdef of
                      Just val | not requireEnv' -> val
                      _ -> Null
      goV v = v
  
      parseValue val = either
        (const (String val))
        id
        (Y.decodeThrow $ encodeUtf8 val)
  
  -- | Get the actual environment as a @HashMap@ from @Text@ to @Text@.
  --
  -- @since 0.8.16
  getCurrentEnv :: IO (KeyMap Text)
  getCurrentEnv = fmap (H.fromList . map (fromText . pack *** pack)) getEnvironment
  
  -- | A convenience wrapper around 'applyEnvValue' and 'getCurrentEnv'
  --
  -- @since 0.8.16
  applyCurrentEnv :: Bool -- ^ require an environment variable to be present?
                  -> Value -> IO Value
  applyCurrentEnv requireEnv' orig = flip (applyEnvValue requireEnv') orig <$> getCurrentEnv
  
  -- | Defines how we want to use the environment variables when loading a config
  -- file. Use the smart constructors provided by this module.
  --
  -- @since 0.8.16
  data EnvUsage = IgnoreEnv
                | UseEnv
                | RequireEnv
                | UseCustomEnv (KeyMap Text)
                | RequireCustomEnv (KeyMap Text)
  
  -- | Do not use any environment variables, instead relying on defaults values
  -- in the config file.
  --
  -- @since 0.8.16
  ignoreEnv :: EnvUsage
  ignoreEnv = IgnoreEnv
  
  -- | Use environment variables when available, otherwise use defaults.
  --
  -- @since 0.8.16
  useEnv :: EnvUsage
  useEnv = UseEnv
  
  -- | Do not use default values from the config file, but instead take all
  -- overrides from the environment. If a value is missing, loading the file will
  -- throw an exception.
  --
  -- @since 0.8.16
  requireEnv :: EnvUsage
  requireEnv = RequireEnv
  
  -- | Same as 'useEnv', but instead of the actual environment, use the provided
  -- @HashMap@ as the environment.
  --
  -- @since 0.8.16
  useCustomEnv :: KeyMap Text -> EnvUsage
  useCustomEnv = UseCustomEnv
  
  -- | Same as 'requireEnv', but instead of the actual environment, use the
  -- provided @HashMap@ as the environment.
  --
  -- @since 0.8.16
  requireCustomEnv :: KeyMap Text -> EnvUsage
  requireCustomEnv = RequireCustomEnv
  
  -- | Load the settings from the following three sources:
  --
  -- * Run time config files
  --
  -- * Run time environment variables
  --
  -- * The default compile time config file
  --
  -- For example, to load up settings from @config/foo.yaml@ and allow overriding
  -- from the actual environment, you can use:
  --
  -- > loadYamlSettings ["config/foo.yaml"] [] useEnv
  --
  -- Alternatively you can configure settings through the Yaml @Value@ directly in Haskell.
  -- For example, to load up settings from only from environment variables, you can use:
  --
  -- > loadYamlSettings [] [object ["connectionString" .= ("_env:DATABASE_URL" :: String)]] requireEnv
  --
  -- @since 0.8.16
  loadYamlSettings
      :: FromJSON settings
      => [FilePath] -- ^ run time config files to use, earlier files have precedence
      -> [Value] -- ^ any other values to use, usually from compile time config. overridden by files
      -> EnvUsage
      -> IO settings
  loadYamlSettings runTimeFiles compileValues envUsage = do
      runValues <- forM runTimeFiles $ \fp -> do
          eres <- YI.decodeFileEither fp
          case eres of
              Left e -> throwIO (Y.LoadSettingsException fp e)
              Right value -> return value
  
      value' <-
          case nonEmpty $ map MergedValue $ runValues ++ compileValues of
              Nothing -> error "loadYamlSettings: No configuration provided"
              Just ne -> return $ getMergedValue $ sconcat ne
      value <-
          case envUsage of
              IgnoreEnv            -> return $ applyEnvValue   False mempty value'
              UseEnv               ->          applyCurrentEnv False        value'
              RequireEnv           ->          applyCurrentEnv True         value'
              UseCustomEnv env     -> return $ applyEnvValue   False env    value'
              RequireCustomEnv env -> return $ applyEnvValue   True  env    value'
  
      case Y.parseEither parseJSON value of
          Left s -> error $ "Could not convert to expected type: " ++ s
          Right settings -> return settings
  
  -- | Same as @loadYamlSettings@, but get the list of runtime config files from
  -- the command line arguments.
  --
  -- @since 0.8.17
  loadYamlSettingsArgs
      :: FromJSON settings
      => [Value] -- ^ any other values to use, usually from compile time config. overridden by files
      -> EnvUsage -- ^ use environment variables
      -> IO settings
  loadYamlSettingsArgs values env = do
      args <- getArgs
      loadYamlSettings args values env
  