{-# LANGUAGE BangPatterns, OverloadedStrings, RecordWildCards,
    ScopedTypeVariables, TupleSections #-}

-- |
-- Module:      Data.Configurator
-- Copyright:   (c) 2011 MailRank, Inc.
-- License:     BSD3
-- Maintainer:  Bryan O'Sullivan <bos@serpentine.com>
-- Stability:   experimental
-- Portability: portable
--
-- A simple (yet powerful) library for working with configuration
-- files.

module Data.Configurator
    (
    -- * Configuration file format
    -- $format

    -- ** Binding a name to a value
    -- $binding

    -- *** Value types
    -- $types

    -- *** String interpolation
    -- $interp

    -- ** Grouping directives
    -- $group

    -- ** Importing files
    -- $import

    -- * Types
      Worth(..)
    -- * Loading configuration data
    , autoReload
    , autoReloadGroups
    , autoConfig
    , empty
    -- * Lookup functions
    , lookup
    , lookupDefault
    , require
    -- * Notification of configuration changes
    -- $notify
    , prefix
    , exact
    , subscribe
    -- * Low-level loading functions
    , load
    , loadGroups
    , reload
    , subconfig
    , addToConfig
    , addGroupsToConfig
    -- * Helper functions
    , display
    , getMap
    ) where

import Control.Applicative ((<$>))
import Control.Concurrent (ThreadId, forkIO, threadDelay)
import Control.Exception (SomeException, evaluate, handle, throwIO, try)
import Control.Monad (foldM, forM, forM_, join, when, msum)
import Data.Configurator.Instances ()
import Data.Configurator.Parser (interp, topLevel)
import Data.Configurator.Types.Internal
import Data.IORef (atomicModifyIORef, newIORef, readIORef)
import Data.List (tails)
import Data.Maybe (fromMaybe, isJust)
import Data.Monoid (mconcat)
import Data.Ratio (denominator, numerator)
import Data.Text.Lazy.Builder (fromString, fromText, toLazyText)
import Data.Text.Lazy.Builder.Int (decimal)
import Data.Text.Lazy.Builder.RealFloat (realFloat)
import Prelude hiding (lookup)
import System.Environment (getEnv)
import System.IO (hPutStrLn, stderr)
import System.IO.Unsafe (unsafePerformIO)
import System.Posix.Types (EpochTime, FileOffset)
import System.PosixCompat.Files (fileSize, getFileStatus, modificationTime)
import qualified Control.Exception as E
import qualified Data.Attoparsec.Text as T
import qualified Data.Attoparsec.Text.Lazy as L
import qualified Data.HashMap.Lazy as H
import qualified Data.Text as T
import qualified Data.Text.Lazy as L
import qualified Data.Text.Lazy.IO as L

loadFiles :: [Worth Path] -> IO (H.HashMap (Worth Path) [Directive])
loadFiles :: [Worth Text] -> IO (HashMap (Worth Text) [Directive])
loadFiles = forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM HashMap (Worth Text) [Directive]
-> Worth Text -> IO (HashMap (Worth Text) [Directive])
go forall k v. HashMap k v
H.empty
 where
   go :: HashMap (Worth Text) [Directive]
-> Worth Text -> IO (HashMap (Worth Text) [Directive])
go HashMap (Worth Text) [Directive]
seen Worth Text
path = do
     let rewrap :: b -> Worth b
rewrap b
n = forall a b. a -> b -> a
const b
n forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Worth Text
path
         wpath :: Text
wpath = forall a. Worth a -> a
worth Worth Text
path
     Worth Text
path' <- forall {b}. b -> Worth b
rewrap forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> Text -> HashMap Text Value -> IO Text
interpolate Text
"" Text
wpath forall k v. HashMap k v
H.empty
     [Directive]
ds    <- Worth FilePath -> IO [Directive]
loadOne (Text -> FilePath
T.unpack forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Worth Text
path')
     let !seen' :: HashMap (Worth Text) [Directive]
seen'    = forall k v.
(Eq k, Hashable k) =>
k -> v -> HashMap k v -> HashMap k v
H.insert Worth Text
path [Directive]
ds HashMap (Worth Text) [Directive]
seen
         notSeen :: Worth Text -> Bool
notSeen Worth Text
n = Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Maybe a -> Bool
isJust forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
H.lookup Worth Text
n forall a b. (a -> b) -> a -> b
$ HashMap (Worth Text) [Directive]
seen
     forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM HashMap (Worth Text) [Directive]
-> Worth Text -> IO (HashMap (Worth Text) [Directive])
go HashMap (Worth Text) [Directive]
seen' forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
filter Worth Text -> Bool
notSeen forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> [Directive] -> [Worth Text]
importsOf Text
wpath forall a b. (a -> b) -> a -> b
$ [Directive]
ds

-- | Create a 'Config' from the contents of the named files. Throws an
-- exception on error, such as if files do not exist or contain errors.
--
-- File names have any environment variables expanded prior to the
-- first time they are opened, so you can specify a file name such as
-- @\"$(HOME)/myapp.cfg\"@.
load :: [Worth FilePath] -> IO Config
load :: [Worth FilePath] -> IO Config
load [Worth FilePath]
files = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Text -> BaseConfig -> Config
Config Text
"") forall a b. (a -> b) -> a -> b
$ Maybe AutoConfig -> [(Text, Worth FilePath)] -> IO BaseConfig
load' forall a. Maybe a
Nothing (forall a b. (a -> b) -> [a] -> [b]
map (\Worth FilePath
f -> (Text
"", Worth FilePath
f)) [Worth FilePath]
files)

-- | Create a 'Config' from the contents of the named files, placing them
-- into named prefixes.  If a prefix is non-empty, it should end in a
-- dot.
loadGroups :: [(Name, Worth FilePath)] -> IO Config
loadGroups :: [(Text, Worth FilePath)] -> IO Config
loadGroups [(Text, Worth FilePath)]
files = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Text -> BaseConfig -> Config
Config Text
"") forall a b. (a -> b) -> a -> b
$ Maybe AutoConfig -> [(Text, Worth FilePath)] -> IO BaseConfig
load' forall a. Maybe a
Nothing [(Text, Worth FilePath)]
files

load' :: Maybe AutoConfig -> [(Name, Worth FilePath)] -> IO BaseConfig
load' :: Maybe AutoConfig -> [(Text, Worth FilePath)] -> IO BaseConfig
load' Maybe AutoConfig
auto [(Text, Worth FilePath)]
paths0 = do
  let second :: (t -> b) -> (a, t) -> (a, b)
second t -> b
f (a
x,t
y) = (a
x, t -> b
f t
y)
      paths :: [(Text, Worth Text)]
paths          = forall a b. (a -> b) -> [a] -> [b]
map (forall {t} {b} {a}. (t -> b) -> (a, t) -> (a, b)
second (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap FilePath -> Text
T.pack)) [(Text, Worth FilePath)]
paths0
  HashMap (Worth Text) [Directive]
ds <- [Worth Text] -> IO (HashMap (Worth Text) [Directive])
loadFiles (forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd [(Text, Worth Text)]
paths)
  IORef [(Text, Worth Text)]
p <- forall a. a -> IO (IORef a)
newIORef [(Text, Worth Text)]
paths
  IORef (HashMap Text Value)
m <- forall a. a -> IO (IORef a)
newIORef forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< [(Text, Worth Text)]
-> HashMap (Worth Text) [Directive] -> IO (HashMap Text Value)
flatten [(Text, Worth Text)]
paths HashMap (Worth Text) [Directive]
ds
  IORef (HashMap Pattern [ChangeHandler])
s <- forall a. a -> IO (IORef a)
newIORef forall k v. HashMap k v
H.empty
  forall (m :: * -> *) a. Monad m => a -> m a
return BaseConfig {
                cfgAuto :: Maybe AutoConfig
cfgAuto = Maybe AutoConfig
auto
              , cfgPaths :: IORef [(Text, Worth Text)]
cfgPaths = IORef [(Text, Worth Text)]
p
              , cfgMap :: IORef (HashMap Text Value)
cfgMap = IORef (HashMap Text Value)
m
              , cfgSubs :: IORef (HashMap Pattern [ChangeHandler])
cfgSubs = IORef (HashMap Pattern [ChangeHandler])
s
              }

-- | Gives a 'Config' corresponding to just a single group of the original
-- 'Config'.  The subconfig can be used just like the original 'Config', but
-- see the documentation for 'reload'.
subconfig :: Name -> Config -> Config
subconfig :: Text -> Config -> Config
subconfig Text
g (Config Text
root BaseConfig
cfg) = Text -> BaseConfig -> Config
Config ([Text] -> Text
T.concat [Text
root, Text
g, Text
"."]) BaseConfig
cfg

-- | Forcibly reload a 'Config'. Throws an exception on error, such as
-- if files no longer exist or contain errors.  If the provided 'Config' is
-- a 'subconfig', this will reload the entire top-level configuration, not just
-- the local section.
reload :: Config -> IO ()
reload :: Config -> IO ()
reload (Config Text
_ cfg :: BaseConfig
cfg@BaseConfig{Maybe AutoConfig
IORef [(Text, Worth Text)]
IORef (HashMap Text Value)
IORef (HashMap Pattern [ChangeHandler])
cfgSubs :: IORef (HashMap Pattern [ChangeHandler])
cfgMap :: IORef (HashMap Text Value)
cfgPaths :: IORef [(Text, Worth Text)]
cfgAuto :: Maybe AutoConfig
cfgSubs :: BaseConfig -> IORef (HashMap Pattern [ChangeHandler])
cfgMap :: BaseConfig -> IORef (HashMap Text Value)
cfgPaths :: BaseConfig -> IORef [(Text, Worth Text)]
cfgAuto :: BaseConfig -> Maybe AutoConfig
..}) = BaseConfig -> IO ()
reloadBase BaseConfig
cfg

reloadBase :: BaseConfig -> IO ()
reloadBase :: BaseConfig -> IO ()
reloadBase cfg :: BaseConfig
cfg@BaseConfig{Maybe AutoConfig
IORef [(Text, Worth Text)]
IORef (HashMap Text Value)
IORef (HashMap Pattern [ChangeHandler])
cfgSubs :: IORef (HashMap Pattern [ChangeHandler])
cfgMap :: IORef (HashMap Text Value)
cfgPaths :: IORef [(Text, Worth Text)]
cfgAuto :: Maybe AutoConfig
cfgSubs :: BaseConfig -> IORef (HashMap Pattern [ChangeHandler])
cfgMap :: BaseConfig -> IORef (HashMap Text Value)
cfgPaths :: BaseConfig -> IORef [(Text, Worth Text)]
cfgAuto :: BaseConfig -> Maybe AutoConfig
..} = do
  [(Text, Worth Text)]
paths <- forall a. IORef a -> IO a
readIORef IORef [(Text, Worth Text)]
cfgPaths
  HashMap Text Value
m' <- [(Text, Worth Text)]
-> HashMap (Worth Text) [Directive] -> IO (HashMap Text Value)
flatten [(Text, Worth Text)]
paths forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< [Worth Text] -> IO (HashMap (Worth Text) [Directive])
loadFiles (forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd [(Text, Worth Text)]
paths)
  HashMap Text Value
m <- forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef IORef (HashMap Text Value)
cfgMap forall a b. (a -> b) -> a -> b
$ \HashMap Text Value
m -> (HashMap Text Value
m', HashMap Text Value
m)
  BaseConfig
-> HashMap Text Value
-> HashMap Text Value
-> HashMap Pattern [ChangeHandler]
-> IO ()
notifySubscribers BaseConfig
cfg HashMap Text Value
m HashMap Text Value
m' forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< forall a. IORef a -> IO a
readIORef IORef (HashMap Pattern [ChangeHandler])
cfgSubs

-- | Add additional files to a 'Config', causing it to be reloaded to add
-- their contents.
addToConfig :: [Worth FilePath] -> Config -> IO ()
addToConfig :: [Worth FilePath] -> Config -> IO ()
addToConfig [Worth FilePath]
paths0 Config
cfg = [(Text, Worth FilePath)] -> Config -> IO ()
addGroupsToConfig (forall a b. (a -> b) -> [a] -> [b]
map (\Worth FilePath
x -> (Text
"",Worth FilePath
x)) [Worth FilePath]
paths0) Config
cfg

-- | Add additional files to named groups in a 'Config', causing it to be
-- reloaded to add their contents.  If the prefixes are non-empty, they should
-- end in dots.
addGroupsToConfig :: [(Name, Worth FilePath)] -> Config -> IO ()
addGroupsToConfig :: [(Text, Worth FilePath)] -> Config -> IO ()
addGroupsToConfig [(Text, Worth FilePath)]
paths0 (Config Text
root cfg :: BaseConfig
cfg@BaseConfig{Maybe AutoConfig
IORef [(Text, Worth Text)]
IORef (HashMap Text Value)
IORef (HashMap Pattern [ChangeHandler])
cfgSubs :: IORef (HashMap Pattern [ChangeHandler])
cfgMap :: IORef (HashMap Text Value)
cfgPaths :: IORef [(Text, Worth Text)]
cfgAuto :: Maybe AutoConfig
cfgSubs :: BaseConfig -> IORef (HashMap Pattern [ChangeHandler])
cfgMap :: BaseConfig -> IORef (HashMap Text Value)
cfgPaths :: BaseConfig -> IORef [(Text, Worth Text)]
cfgAuto :: BaseConfig -> Maybe AutoConfig
..}) = do
  let fix :: (Text, f FilePath) -> (Text, f Text)
fix (Text
x,f FilePath
y) = (Text
root Text -> Text -> Text
`T.append` Text
x, forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap FilePath -> Text
T.pack f FilePath
y)
      paths :: [(Text, Worth Text)]
paths     = forall a b. (a -> b) -> [a] -> [b]
map forall {f :: * -> *}.
Functor f =>
(Text, f FilePath) -> (Text, f Text)
fix [(Text, Worth FilePath)]
paths0
  forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef IORef [(Text, Worth Text)]
cfgPaths forall a b. (a -> b) -> a -> b
$ \[(Text, Worth Text)]
prev -> ([(Text, Worth Text)]
prev forall a. [a] -> [a] -> [a]
++ [(Text, Worth Text)]
paths, ())
  BaseConfig -> IO ()
reloadBase BaseConfig
cfg

-- | Defaults for automatic 'Config' reloading when using
-- 'autoReload'.  The 'interval' is one second, while the 'onError'
-- action ignores its argument and does nothing.
autoConfig :: AutoConfig
autoConfig :: AutoConfig
autoConfig = AutoConfig {
               interval :: Int
interval = Int
1
             , onError :: SomeException -> IO ()
onError = forall a b. a -> b -> a
const forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return ()
             }

-- | Load a 'Config' from the given 'FilePath's, and start a reload
-- thread.
--
-- At intervals, a thread checks for modifications to both the
-- original files and any files they refer to in @import@ directives,
-- and reloads the 'Config' if any files have been modified.
--
-- If the initial attempt to load the configuration files fails, an
-- exception is thrown.  If the initial load succeeds, but a
-- subsequent attempt fails, the 'onError' handler is invoked.
--
-- File names have any environment variables expanded prior to the
-- first time they are opened, so you can specify a file name such as
-- @\"$(HOME)/myapp.cfg\"@.
autoReload :: AutoConfig
           -- ^ Directions for when to reload and how to handle
           -- errors.
           -> [Worth FilePath]
           -- ^ Configuration files to load.
           -> IO (Config, ThreadId)
autoReload :: AutoConfig -> [Worth FilePath] -> IO (Config, ThreadId)
autoReload AutoConfig
auto [Worth FilePath]
paths = AutoConfig -> [(Text, Worth FilePath)] -> IO (Config, ThreadId)
autoReloadGroups AutoConfig
auto (forall a b. (a -> b) -> [a] -> [b]
map (\Worth FilePath
x -> (Text
"", Worth FilePath
x)) [Worth FilePath]
paths)

autoReloadGroups :: AutoConfig
                 -> [(Name, Worth FilePath)]
                 -> IO (Config, ThreadId)
autoReloadGroups :: AutoConfig -> [(Text, Worth FilePath)] -> IO (Config, ThreadId)
autoReloadGroups AutoConfig{Int
SomeException -> IO ()
onError :: SomeException -> IO ()
interval :: Int
onError :: AutoConfig -> SomeException -> IO ()
interval :: AutoConfig -> Int
..} [(Text, Worth FilePath)]
_
    | Int
interval forall a. Ord a => a -> a -> Bool
< Int
1    = forall a. HasCallStack => FilePath -> a
error FilePath
"autoReload: negative interval"
autoReloadGroups AutoConfig
_ [] = forall a. HasCallStack => FilePath -> a
error FilePath
"autoReload: no paths to load"
autoReloadGroups auto :: AutoConfig
auto@AutoConfig{Int
SomeException -> IO ()
onError :: SomeException -> IO ()
interval :: Int
onError :: AutoConfig -> SomeException -> IO ()
interval :: AutoConfig -> Int
..} [(Text, Worth FilePath)]
paths = do
  BaseConfig
cfg <- Maybe AutoConfig -> [(Text, Worth FilePath)] -> IO BaseConfig
load' (forall a. a -> Maybe a
Just AutoConfig
auto) [(Text, Worth FilePath)]
paths
  let files :: [Worth FilePath]
files = forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd [(Text, Worth FilePath)]
paths
      loop :: [Maybe Meta] -> IO b
loop [Maybe Meta]
meta = do
        Int -> IO ()
threadDelay (forall a. Ord a => a -> a -> a
max Int
interval Int
1 forall a. Num a => a -> a -> a
* Int
1000000)
        [Maybe Meta]
meta' <- [Worth FilePath] -> IO [Maybe Meta]
getMeta [Worth FilePath]
files
        if [Maybe Meta]
meta' forall a. Eq a => a -> a -> Bool
== [Maybe Meta]
meta
          then [Maybe Meta] -> IO b
loop [Maybe Meta]
meta
          else (BaseConfig -> IO ()
reloadBase BaseConfig
cfg forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`E.catch` SomeException -> IO ()
onError) forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> [Maybe Meta] -> IO b
loop [Maybe Meta]
meta'
  ThreadId
tid <- IO () -> IO ThreadId
forkIO forall a b. (a -> b) -> a -> b
$ forall {b}. [Maybe Meta] -> IO b
loop forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< [Worth FilePath] -> IO [Maybe Meta]
getMeta [Worth FilePath]
files
  forall (m :: * -> *) a. Monad m => a -> m a
return (Text -> BaseConfig -> Config
Config Text
"" BaseConfig
cfg, ThreadId
tid)

-- | Save both a file's size and its last modification date, so we
-- have a better chance of detecting a modification on a crappy
-- filesystem with timestamp resolution of 1 second or worse.
type Meta = (FileOffset, EpochTime)

getMeta :: [Worth FilePath] -> IO [Maybe Meta]
getMeta :: [Worth FilePath] -> IO [Maybe Meta]
getMeta [Worth FilePath]
paths = forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
t a -> (a -> m b) -> m (t b)
forM [Worth FilePath]
paths forall a b. (a -> b) -> a -> b
$ \Worth FilePath
path ->
   forall e a. Exception e => (e -> IO a) -> IO a -> IO a
handle (\(SomeException
_::SomeException) -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ do
     FileStatus
st <- FilePath -> IO FileStatus
getFileStatus (forall a. Worth a -> a
worth Worth FilePath
path)
     forall (m :: * -> *) a. Monad m => a -> m a
return (FileStatus -> COff
fileSize FileStatus
st, FileStatus -> CTime
modificationTime FileStatus
st)

-- | Look up a name in the given 'Config'.  If a binding exists, and
-- the value can be 'convert'ed to the desired type, return the
-- converted value, otherwise 'Nothing'.
lookup :: Configured a => Config -> Name -> IO (Maybe a)
lookup :: forall a. Configured a => Config -> Text -> IO (Maybe a)
lookup (Config Text
root BaseConfig{Maybe AutoConfig
IORef [(Text, Worth Text)]
IORef (HashMap Text Value)
IORef (HashMap Pattern [ChangeHandler])
cfgSubs :: IORef (HashMap Pattern [ChangeHandler])
cfgMap :: IORef (HashMap Text Value)
cfgPaths :: IORef [(Text, Worth Text)]
cfgAuto :: Maybe AutoConfig
cfgSubs :: BaseConfig -> IORef (HashMap Pattern [ChangeHandler])
cfgMap :: BaseConfig -> IORef (HashMap Text Value)
cfgPaths :: BaseConfig -> IORef [(Text, Worth Text)]
cfgAuto :: BaseConfig -> Maybe AutoConfig
..}) Text
name =
    (forall (m :: * -> *) a. Monad m => m (m a) -> m a
join forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a. Configured a => Value -> Maybe a
convert forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
H.lookup (Text
root Text -> Text -> Text
`T.append` Text
name)) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. IORef a -> IO a
readIORef IORef (HashMap Text Value)
cfgMap

-- | Look up a name in the given 'Config'.  If a binding exists, and
-- the value can be 'convert'ed to the desired type, return the
-- converted value, otherwise throw a 'KeyError'.
require :: Configured a => Config -> Name -> IO a
require :: forall a. Configured a => Config -> Text -> IO a
require Config
cfg Text
name = do
  Maybe a
val <- forall a. Configured a => Config -> Text -> IO (Maybe a)
lookup Config
cfg Text
name
  case Maybe a
val of
    Just a
v -> forall (m :: * -> *) a. Monad m => a -> m a
return a
v
    Maybe a
_      -> forall e a. Exception e => e -> IO a
throwIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> KeyError
KeyError forall a b. (a -> b) -> a -> b
$ Text
name

-- | Look up a name in the given 'Config'.  If a binding exists, and
-- the value can be converted to the desired type, return it,
-- otherwise return the default value.
lookupDefault :: Configured a =>
                 a
              -- ^ Default value to return if 'lookup' or 'convert'
              -- fails.
              -> Config -> Name -> IO a
lookupDefault :: forall a. Configured a => a -> Config -> Text -> IO a
lookupDefault a
def Config
cfg Text
name = forall a. a -> Maybe a -> a
fromMaybe a
def forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. Configured a => Config -> Text -> IO (Maybe a)
lookup Config
cfg Text
name

-- | Perform a simple dump of a 'Config' to @stdout@.
display :: Config -> IO ()
display :: Config -> IO ()
display (Config Text
root BaseConfig{Maybe AutoConfig
IORef [(Text, Worth Text)]
IORef (HashMap Text Value)
IORef (HashMap Pattern [ChangeHandler])
cfgSubs :: IORef (HashMap Pattern [ChangeHandler])
cfgMap :: IORef (HashMap Text Value)
cfgPaths :: IORef [(Text, Worth Text)]
cfgAuto :: Maybe AutoConfig
cfgSubs :: BaseConfig -> IORef (HashMap Pattern [ChangeHandler])
cfgMap :: BaseConfig -> IORef (HashMap Text Value)
cfgPaths :: BaseConfig -> IORef [(Text, Worth Text)]
cfgAuto :: BaseConfig -> Maybe AutoConfig
..}) = forall a. Show a => a -> IO ()
print forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text
root,) forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< forall a. IORef a -> IO a
readIORef IORef (HashMap Text Value)
cfgMap

-- | Fetch the 'H.HashMap' that maps names to values.
getMap :: Config -> IO (H.HashMap Name Value)
getMap :: Config -> IO (HashMap Text Value)
getMap = forall a. IORef a -> IO a
readIORef forall b c a. (b -> c) -> (a -> b) -> a -> c
. BaseConfig -> IORef (HashMap Text Value)
cfgMap forall b c a. (b -> c) -> (a -> b) -> a -> c
. Config -> BaseConfig
baseCfg

flatten :: [(Name, Worth Path)]
        -> H.HashMap (Worth Path) [Directive]
        -> IO (H.HashMap Name Value)
flatten :: [(Text, Worth Text)]
-> HashMap (Worth Text) [Directive] -> IO (HashMap Text Value)
flatten [(Text, Worth Text)]
roots HashMap (Worth Text) [Directive]
files = forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM HashMap Text Value -> (Text, Worth Text) -> IO (HashMap Text Value)
doPath forall k v. HashMap k v
H.empty [(Text, Worth Text)]
roots
 where
  doPath :: HashMap Text Value -> (Text, Worth Text) -> IO (HashMap Text Value)
doPath HashMap Text Value
m (Text
pfx, Worth Text
f) = case forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
H.lookup Worth Text
f HashMap (Worth Text) [Directive]
files of
        Maybe [Directive]
Nothing -> forall (m :: * -> *) a. Monad m => a -> m a
return HashMap Text Value
m
        Just [Directive]
ds -> forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM (Text
-> Text
-> HashMap Text Value
-> Directive
-> IO (HashMap Text Value)
directive Text
pfx (forall a. Worth a -> a
worth Worth Text
f)) HashMap Text Value
m [Directive]
ds

  directive :: Text
-> Text
-> HashMap Text Value
-> Directive
-> IO (HashMap Text Value)
directive Text
pfx Text
_ HashMap Text Value
m (Bind Text
name (String Text
value)) = do
      Text
v <- Text -> Text -> HashMap Text Value -> IO Text
interpolate Text
pfx Text
value HashMap Text Value
m
      forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! forall k v.
(Eq k, Hashable k) =>
k -> v -> HashMap k v -> HashMap k v
H.insert (Text -> Text -> Text
T.append Text
pfx Text
name) (Text -> Value
String Text
v) HashMap Text Value
m
  directive Text
pfx Text
_ HashMap Text Value
m (Bind Text
name Value
value) =
      forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! forall k v.
(Eq k, Hashable k) =>
k -> v -> HashMap k v -> HashMap k v
H.insert (Text -> Text -> Text
T.append Text
pfx Text
name) Value
value HashMap Text Value
m
  directive Text
pfx Text
f HashMap Text Value
m (Group Text
name [Directive]
xs) = forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM (Text
-> Text
-> HashMap Text Value
-> Directive
-> IO (HashMap Text Value)
directive Text
pfx' Text
f) HashMap Text Value
m [Directive]
xs
      where pfx' :: Text
pfx' = [Text] -> Text
T.concat [Text
pfx, Text
name, Text
"."]
  directive Text
pfx Text
f HashMap Text Value
m (Import Text
path) =
      let f' :: Text
f' = Text -> Text -> Text
relativize Text
f Text
path
      in  case forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
H.lookup (forall {b}. b -> Worth b
Required (Text -> Text -> Text
relativize Text
f Text
path)) HashMap (Worth Text) [Directive]
files of
            Just [Directive]
ds -> forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM (Text
-> Text
-> HashMap Text Value
-> Directive
-> IO (HashMap Text Value)
directive Text
pfx Text
f') HashMap Text Value
m [Directive]
ds
            Maybe [Directive]
_       -> forall (m :: * -> *) a. Monad m => a -> m a
return HashMap Text Value
m

interpolate :: T.Text -> T.Text -> H.HashMap Name Value -> IO T.Text
interpolate :: Text -> Text -> HashMap Text Value -> IO Text
interpolate Text
pfx Text
s HashMap Text Value
env
    | Text
"$" Text -> Text -> Bool
`T.isInfixOf` Text
s =
      case forall a. Parser a -> Text -> Either FilePath a
T.parseOnly Parser [Interpolate]
interp Text
s of
        Left FilePath
err   -> forall e a. Exception e => e -> IO a
throwIO forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> ConfigError
ParseError FilePath
"" FilePath
err
        Right [Interpolate]
xs -> (Text -> Text
L.toStrict forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> Text
toLazyText forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Monoid a => [a] -> a
mconcat) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM Interpolate -> IO Builder
interpret [Interpolate]
xs
    | Bool
otherwise = forall (m :: * -> *) a. Monad m => a -> m a
return Text
s
 where
  lookupEnv :: Text -> Maybe Value
lookupEnv Text
name = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, MonadPlus m) =>
t (m a) -> m a
msum forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (forall a b c. (a -> b -> c) -> b -> a -> c
flip forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
H.lookup HashMap Text Value
env) [Text]
fullnames
    where fullnames :: [Text]
fullnames = forall a b. (a -> b) -> [a] -> [b]
map (Text -> [Text] -> Text
T.intercalate Text
".")     -- ["a.b.c.x","a.b.x","a.x","x"]
                    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map (forall a. [a] -> [a]
reverse forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text
nameforall a. a -> [a] -> [a]
:)) -- [["a","b","c","x"],["a","b","x"],["a","x"],["x"]]
                    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [[a]]
tails                   -- [["c","b","a"],["b","a"],["a"],[]]
                    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
reverse                 -- ["c","b","a"]
                    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Bool
T.null)   -- ["a","b","c"]
                    forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> Text -> [Text]
T.split (forall a. Eq a => a -> a -> Bool
==Char
'.')         -- ["a","b","c",""]
                    forall a b. (a -> b) -> a -> b
$ Text
pfx                     -- "a.b.c."

  interpret :: Interpolate -> IO Builder
interpret (Literal Text
x)   = forall (m :: * -> *) a. Monad m => a -> m a
return (Text -> Builder
fromText Text
x)
  interpret (Interpolate Text
name) =
      case Text -> Maybe Value
lookupEnv Text
name of
        Just (String Text
x) -> forall (m :: * -> *) a. Monad m => a -> m a
return (Text -> Builder
fromText Text
x)
        Just (Number Rational
r)
            | forall a. Ratio a -> a
denominator Rational
r forall a. Eq a => a -> a -> Bool
== Integer
1 -> forall (m :: * -> *) a. Monad m => a -> m a
return (forall a. Integral a => a -> Builder
decimal forall a b. (a -> b) -> a -> b
$ forall a. Ratio a -> a
numerator Rational
r)
            | Bool
otherwise -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a. RealFloat a => a -> Builder
realFloat (forall a. Fractional a => Rational -> a
fromRational Rational
r :: Double)
                           -- TODO: Use a dedicated Builder for Rationals instead of
                           -- using realFloat on a Double.
        Just Value
_          -> forall a. HasCallStack => FilePath -> a
error FilePath
"type error"
        Maybe Value
_ -> do
          Either SomeException FilePath
e <- forall e a. Exception e => IO a -> IO (Either e a)
try forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> IO FilePath
getEnv forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> FilePath
T.unpack forall a b. (a -> b) -> a -> b
$ Text
name
          case Either SomeException FilePath
e of
            Left (SomeException
_::SomeException) ->
                forall e a. Exception e => e -> IO a
throwIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath -> ConfigError
ParseError FilePath
"" forall a b. (a -> b) -> a -> b
$ FilePath
"no such variable " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> FilePath
show Text
name
            Right FilePath
x -> forall (m :: * -> *) a. Monad m => a -> m a
return (FilePath -> Builder
fromString FilePath
x)

importsOf :: Path -> [Directive] -> [Worth Path]
importsOf :: Text -> [Directive] -> [Worth Text]
importsOf Text
path (Import Text
ref : [Directive]
xs) = forall {b}. b -> Worth b
Required (Text -> Text -> Text
relativize Text
path Text
ref)
                                 forall a. a -> [a] -> [a]
: Text -> [Directive] -> [Worth Text]
importsOf Text
path [Directive]
xs
importsOf Text
path (Group Text
_ [Directive]
ys : [Directive]
xs) = Text -> [Directive] -> [Worth Text]
importsOf Text
path [Directive]
ys forall a. [a] -> [a] -> [a]
++ Text -> [Directive] -> [Worth Text]
importsOf Text
path [Directive]
xs
importsOf Text
path (Directive
_ : [Directive]
xs)          = Text -> [Directive] -> [Worth Text]
importsOf Text
path [Directive]
xs
importsOf Text
_    [Directive]
_                 = []

relativize :: Path -> Path -> Path
relativize :: Text -> Text -> Text
relativize Text
parent Text
child
  | Text -> Char
T.head Text
child forall a. Eq a => a -> a -> Bool
== Char
'/' = Text
child
  | Bool
otherwise           = forall a b. (a, b) -> a
fst (Text -> Text -> (Text, Text)
T.breakOnEnd Text
"/" Text
parent) Text -> Text -> Text
`T.append` Text
child

loadOne :: Worth FilePath -> IO [Directive]
loadOne :: Worth FilePath -> IO [Directive]
loadOne Worth FilePath
path = do
  Either SomeException Text
es <- forall e a. Exception e => IO a -> IO (Either e a)
try forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> IO Text
L.readFile forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Worth a -> a
worth forall a b. (a -> b) -> a -> b
$ Worth FilePath
path
  case Either SomeException Text
es of
    Left (SomeException
err::SomeException) -> case Worth FilePath
path of
                                   Required FilePath
_ -> forall e a. Exception e => e -> IO a
throwIO SomeException
err
                                   Worth FilePath
_          -> forall (m :: * -> *) a. Monad m => a -> m a
return []
    Right Text
s -> do
            Either FilePath [Directive]
p <- forall a. a -> IO a
evaluate (forall r. Result r -> Either FilePath r
L.eitherResult forall a b. (a -> b) -> a -> b
$ forall a. Parser a -> Text -> Result a
L.parse Parser [Directive]
topLevel Text
s)
                 forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`E.catch` \(ConfigError
e::ConfigError) ->
                 forall e a. Exception e => e -> IO a
throwIO forall a b. (a -> b) -> a -> b
$ case ConfigError
e of
                             ParseError FilePath
_ FilePath
err -> FilePath -> FilePath -> ConfigError
ParseError (forall a. Worth a -> a
worth Worth FilePath
path) FilePath
err
            case Either FilePath [Directive]
p of
              Left FilePath
err -> forall e a. Exception e => e -> IO a
throwIO (FilePath -> FilePath -> ConfigError
ParseError (forall a. Worth a -> a
worth Worth FilePath
path) FilePath
err)
              Right [Directive]
ds -> forall (m :: * -> *) a. Monad m => a -> m a
return [Directive]
ds

-- | Subscribe for notifications.  The given action will be invoked
-- when any change occurs to a configuration property matching the
-- supplied pattern.
subscribe :: Config -> Pattern -> ChangeHandler -> IO ()
subscribe :: Config -> Pattern -> ChangeHandler -> IO ()
subscribe (Config Text
root BaseConfig{Maybe AutoConfig
IORef [(Text, Worth Text)]
IORef (HashMap Text Value)
IORef (HashMap Pattern [ChangeHandler])
cfgSubs :: IORef (HashMap Pattern [ChangeHandler])
cfgMap :: IORef (HashMap Text Value)
cfgPaths :: IORef [(Text, Worth Text)]
cfgAuto :: Maybe AutoConfig
cfgSubs :: BaseConfig -> IORef (HashMap Pattern [ChangeHandler])
cfgMap :: BaseConfig -> IORef (HashMap Text Value)
cfgPaths :: BaseConfig -> IORef [(Text, Worth Text)]
cfgAuto :: BaseConfig -> Maybe AutoConfig
..}) Pattern
pat ChangeHandler
act = do
  HashMap Pattern [ChangeHandler]
m' <- forall a b. IORef a -> (a -> (a, b)) -> IO b
atomicModifyIORef IORef (HashMap Pattern [ChangeHandler])
cfgSubs forall a b. (a -> b) -> a -> b
$ \HashMap Pattern [ChangeHandler]
m ->
        let m' :: HashMap Pattern [ChangeHandler]
m' = forall k v.
(Eq k, Hashable k) =>
(v -> v -> v) -> k -> v -> HashMap k v -> HashMap k v
H.insertWith forall a. [a] -> [a] -> [a]
(++) (Text -> Pattern -> Pattern
localPattern Text
root Pattern
pat) [ChangeHandler
act] HashMap Pattern [ChangeHandler]
m in (HashMap Pattern [ChangeHandler]
m', HashMap Pattern [ChangeHandler]
m')
  forall a. a -> IO a
evaluate HashMap Pattern [ChangeHandler]
m' forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return ()

localPattern :: Name -> Pattern -> Pattern
localPattern :: Text -> Pattern -> Pattern
localPattern Text
pfx (Exact  Text
s) = Text -> Pattern
Exact  (Text
pfx Text -> Text -> Text
`T.append` Text
s)
localPattern Text
pfx (Prefix Text
s) = Text -> Pattern
Prefix (Text
pfx Text -> Text -> Text
`T.append` Text
s)

notifySubscribers :: BaseConfig -> H.HashMap Name Value -> H.HashMap Name Value
                  -> H.HashMap Pattern [ChangeHandler] -> IO ()
notifySubscribers :: BaseConfig
-> HashMap Text Value
-> HashMap Text Value
-> HashMap Pattern [ChangeHandler]
-> IO ()
notifySubscribers BaseConfig{Maybe AutoConfig
IORef [(Text, Worth Text)]
IORef (HashMap Text Value)
IORef (HashMap Pattern [ChangeHandler])
cfgSubs :: IORef (HashMap Pattern [ChangeHandler])
cfgMap :: IORef (HashMap Text Value)
cfgPaths :: IORef [(Text, Worth Text)]
cfgAuto :: Maybe AutoConfig
cfgSubs :: BaseConfig -> IORef (HashMap Pattern [ChangeHandler])
cfgMap :: BaseConfig -> IORef (HashMap Text Value)
cfgPaths :: BaseConfig -> IORef [(Text, Worth Text)]
cfgAuto :: BaseConfig -> Maybe AutoConfig
..} HashMap Text Value
m HashMap Text Value
m' HashMap Pattern [ChangeHandler]
subs = forall k v a. (k -> v -> a -> a) -> a -> HashMap k v -> a
H.foldrWithKey forall {t :: * -> *} {b}.
Foldable t =>
Pattern -> t ChangeHandler -> IO b -> IO b
go (forall (m :: * -> *) a. Monad m => a -> m a
return ()) HashMap Pattern [ChangeHandler]
subs
 where
  changedOrGone :: [(Text, Maybe Value)]
changedOrGone = forall k v a. (k -> v -> a -> a) -> a -> HashMap k v -> a
H.foldrWithKey Text -> Value -> [(Text, Maybe Value)] -> [(Text, Maybe Value)]
check [] HashMap Text Value
m
      where check :: Text -> Value -> [(Text, Maybe Value)] -> [(Text, Maybe Value)]
check Text
n Value
v [(Text, Maybe Value)]
nvs = case forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
H.lookup Text
n HashMap Text Value
m' of
                              Just Value
v' | Value
v forall a. Eq a => a -> a -> Bool
/= Value
v'   -> (Text
n,forall a. a -> Maybe a
Just Value
v')forall a. a -> [a] -> [a]
:[(Text, Maybe Value)]
nvs
                                      | Bool
otherwise -> [(Text, Maybe Value)]
nvs
                              Maybe Value
_                   -> (Text
n,forall a. Maybe a
Nothing)forall a. a -> [a] -> [a]
:[(Text, Maybe Value)]
nvs
  new :: [(Text, Value)]
new = forall k v a. (k -> v -> a -> a) -> a -> HashMap k v -> a
H.foldrWithKey forall {b}. Text -> b -> [(Text, b)] -> [(Text, b)]
check [] HashMap Text Value
m'
      where check :: Text -> b -> [(Text, b)] -> [(Text, b)]
check Text
n b
v [(Text, b)]
nvs = case forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
H.lookup Text
n HashMap Text Value
m of
                              Maybe Value
Nothing -> (Text
n,b
v)forall a. a -> [a] -> [a]
:[(Text, b)]
nvs
                              Maybe Value
_       -> [(Text, b)]
nvs
  notify :: p -> t -> t -> (t -> t -> IO ()) -> IO ()
notify p
p t
n t
v t -> t -> IO ()
a = t -> t -> IO ()
a t
n t
v forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`E.catch` forall b a. b -> (a -> b) -> Maybe a -> b
maybe forall a. Show a => a -> IO ()
report AutoConfig -> SomeException -> IO ()
onError Maybe AutoConfig
cfgAuto
    where report :: a -> IO ()
report a
e = Handle -> FilePath -> IO ()
hPutStrLn Handle
stderr forall a b. (a -> b) -> a -> b
$
                     FilePath
"*** a ChangeHandler threw an exception for " forall a. [a] -> [a] -> [a]
++
                     forall a. Show a => a -> FilePath
show (p
p,t
n) forall a. [a] -> [a] -> [a]
++ FilePath
": " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> FilePath
show a
e
  go :: Pattern -> t ChangeHandler -> IO b -> IO b
go p :: Pattern
p@(Exact Text
n) t ChangeHandler
acts IO b
next = (forall a b. a -> b -> a
const IO b
next forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<<) forall a b. (a -> b) -> a -> b
$ do
    let v' :: Maybe Value
v' = forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
H.lookup Text
n HashMap Text Value
m'
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
H.lookup Text
n HashMap Text Value
m forall a. Eq a => a -> a -> Bool
/= Maybe Value
v') forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall {p} {t} {t}.
(Show p, Show t) =>
p -> t -> t -> (t -> t -> IO ()) -> IO ()
notify Pattern
p Text
n Maybe Value
v') forall a b. (a -> b) -> a -> b
$ t ChangeHandler
acts
  go p :: Pattern
p@(Prefix Text
n) t ChangeHandler
acts IO b
next = (forall a b. a -> b -> a
const IO b
next forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<<) forall a b. (a -> b) -> a -> b
$ do
    let matching :: [(Text, b)] -> [(Text, b)]
matching = forall a. (a -> Bool) -> [a] -> [a]
filter (Text -> Text -> Bool
T.isPrefixOf Text
n forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst)
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (forall {b}. [(Text, b)] -> [(Text, b)]
matching [(Text, Value)]
new) forall a b. (a -> b) -> a -> b
$ \(Text
n',Value
v) -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall {p} {t} {t}.
(Show p, Show t) =>
p -> t -> t -> (t -> t -> IO ()) -> IO ()
notify Pattern
p Text
n' (forall a. a -> Maybe a
Just Value
v)) t ChangeHandler
acts
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (forall {b}. [(Text, b)] -> [(Text, b)]
matching [(Text, Maybe Value)]
changedOrGone) forall a b. (a -> b) -> a -> b
$ \(Text
n',Maybe Value
v) -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall {p} {t} {t}.
(Show p, Show t) =>
p -> t -> t -> (t -> t -> IO ()) -> IO ()
notify Pattern
p Text
n' Maybe Value
v) t ChangeHandler
acts

-- | A completely empty configuration.
empty :: Config
empty :: Config
empty = Text -> BaseConfig -> Config
Config Text
"" forall a b. (a -> b) -> a -> b
$ forall a. IO a -> a
unsafePerformIO forall a b. (a -> b) -> a -> b
$ do
          IORef [(Text, Worth Text)]
p <- forall a. a -> IO (IORef a)
newIORef []
          IORef (HashMap Text Value)
m <- forall a. a -> IO (IORef a)
newIORef forall k v. HashMap k v
H.empty
          IORef (HashMap Pattern [ChangeHandler])
s <- forall a. a -> IO (IORef a)
newIORef forall k v. HashMap k v
H.empty
          forall (m :: * -> *) a. Monad m => a -> m a
return BaseConfig {
                       cfgAuto :: Maybe AutoConfig
cfgAuto = forall a. Maybe a
Nothing
                     , cfgPaths :: IORef [(Text, Worth Text)]
cfgPaths = IORef [(Text, Worth Text)]
p
                     , cfgMap :: IORef (HashMap Text Value)
cfgMap = IORef (HashMap Text Value)
m
                     , cfgSubs :: IORef (HashMap Pattern [ChangeHandler])
cfgSubs = IORef (HashMap Pattern [ChangeHandler])
s
                     }
{-# NOINLINE empty #-}

-- $format
--
-- A configuration file consists of a series of directives and
-- comments, encoded in UTF-8.  A comment begins with a \"@#@\"
-- character, and continues to the end of a line.
--
-- Files and directives are processed from first to last, top to
-- bottom.

-- $binding
--
-- A binding associates a name with a value.
--
-- > my_string = "hi mom! \u2603"
-- > your-int-33 = 33
-- > his_bool = on
-- > HerList = [1, "foo", off]
--
-- A name must begin with a Unicode letter, which is followed by zero
-- or more of a Unicode alphanumeric code point, hyphen \"@-@\", or
-- underscore \"@_@\".
--
-- Bindings are created or overwritten in the order in which they are
-- encountered.  It is legitimate for a name to be bound multiple
-- times, in which case the last value wins.
--
-- > a = 1
-- > a = true
-- > # value of a is now true, not 1

-- $types
--
-- The configuration file format supports the following data types:
--
-- * Booleans, represented as @on@ or @off@, @true@ or @false@.  These
--   are case sensitive, so do not try to use @True@ instead of
--   @true@!
--
-- * Integers, represented in base 10.
--
-- * Unicode strings, represented as text (possibly containing escape
--   sequences) surrounded by double quotes.
--
-- * Heterogeneous lists of values, represented as an opening square
--   bracket \"@[@\", followed by a series of comma-separated values,
--   ending with a closing square bracket \"@]@\".
--
-- The following escape sequences are recognised in a text string:
--
-- * @\\n@ - newline
--
-- * @\\r@ - carriage return
--
-- * @\\t@ - horizontal tab
--
-- * @\\\\@ - backslash
--
-- * @\\\"@ - double quote
--
-- * @\\u@/xxxx/ - Unicode character from the basic multilingual
--   plane, encoded as four hexadecimal digits
--
-- * @\\u@/xxxx/@\\u@/xxxx/ - Unicode character from an astral plane,
--   as two hexadecimal-encoded UTF-16 surrogates

-- $interp
--
-- Strings support interpolation, so that you can dynamically
-- construct a string based on data in your configuration or the OS
-- environment.
--
-- If a string value contains the special sequence \"@$(foo)@\" (for
-- any name @foo@), then the name @foo@ will be looked up in the
-- configuration data and its value substituted.  If that name cannot
-- be found, it will be looked up in the OS environment.
--
-- For security reasons, it is an error for a string interpolation
-- fragment to contain a name that cannot be found in either the
-- current configuration or the environment.
--
-- To represent a single literal \"@$@\" character in a string, double
-- it: \"@$$@\".

-- $group
--
-- It is possible to group a number of directives together under a
-- single prefix:
--
-- > my-group
-- > {
-- >   a = 1
-- >
-- >   # groups support nesting
-- >   nested {
-- >     b = "yay!"
-- >   }
-- > }
--
-- The name of a group is used as a prefix for the items in the
-- group. For instance, the value of \"@a@\" above can be retrieved
-- using 'lookup' by supplying the name \"@my-group.a@\", and \"@b@\"
-- will be named \"@my-group.nested.b@\".

-- $import
--
-- To import the contents of another configuration file, use the
-- @import@ directive.
--
-- > import "$(HOME)/etc/myapp.cfg"
--
-- Absolute paths are imported as is.  Relative paths are resolved with
-- respect to the file they are imported from.  It is an error for an
-- @import@ directive to name a file that does not exist, cannot be read,
-- or contains errors.
--
-- If an @import@ appears inside a group, the group's naming prefix
-- will be applied to all of the names imported from the given
-- configuration file.
--
-- Supposing we have a file named \"@foo.cfg@\":
--
-- > bar = 1
--
-- And another file that imports it into a group:
--
-- > hi {
-- >   import "foo.cfg"
-- > }
--
-- This will result in a value named \"@hi.bar@\".

-- $notify
--
-- To more efficiently support an application's need to dynamically
-- reconfigure, a subsystem may ask to be notified when a
-- configuration property is changed as a result of a reload, using
-- the 'subscribe' action.