seacat-1.0.0.0: src/Web/Seacat/Configuration.hs
{-# LANGUAGE FlexibleContexts #-}
-- |Configuration file handling. This module re-exports `ConfgParser`
-- and `get` from `Data.ConfigFile`, as pretty much all applications
-- will want to use those in conjunction with this module.
module Web.Seacat.Configuration ( ConfigParser
, loadConfigFile
, reloadConfigFile
, applyUserConfig
, defaults
, get
, get'
, conf
, conf') where
import Control.Applicative ((<$>))
import Control.Monad.Error.Class (MonadError)
import Data.ConfigFile
import Data.Either.Utils (forceEither)
import System.IO.Error (catchIOError)
import Web.Routes.PathInfo (PathInfo)
import Web.Seacat.RequestHandler.Types (RequestProcessor, askConf)
-- |Load a configuration file by name.
-- All errors (syntax, file access, etc) are squashed together,
-- returning a Nothing if anything fails. This is probably ok, given
-- the simplicity of the format, however it may be useful later on to
-- distinguish between syntax errors (and where they are) and access
-- errors, giving the user a better error message than just "oops,
-- something went wrong"
--
-- String interpolation is turned on (with a depth of 10). See the
-- `Data.ConfigParser` documentation for details on that.
--
-- This sets all of the configuration values expected by the main
-- application to their defaults if they weren't present already, but
-- other values are left as they are in the file.
loadConfigFile :: FilePath -> IO (Maybe ConfigParser)
loadConfigFile filename = (Just <$> loadConfigFileUnsafe filename) `catchIOError` const (return Nothing)
-- |Load a configuration file unsafely. This may throw an IO
-- exception.
loadConfigFileUnsafe :: FilePath -> IO ConfigParser
loadConfigFileUnsafe filename = do
let base = emptyCP { accessfunc = interpolatingAccess 10 }
cp <- readfile base filename
return . merge defaults $ forceEither cp
-- |Reload and reapply the configuration file. If an error is raised,
-- fall back to the original configuration. Specifically, this merges
-- the new configuration with the old, so if the new configuration has
-- removed a required setting, this won't cause a problem.
reloadConfigFile :: ConfigParser -- ^ The original configuration
-> FilePath -- ^ The config file to load
-> IO ConfigParser -- ^ The new configuration
reloadConfigFile cfg filename = ((cfg `merge`) <$> loadConfigFileUnsafe filename) `catchIOError` const (return cfg)
-- |Default configuration values:
--
-- - Listen on *:3000
-- - Use http://localhost:3000 as the basis for all URLs
-- - Use /tmp as the basis for all file look-ups
-- - Use a sqlite database called seacat.sqlite with 10 connections.
defaults :: ConfigParser
defaults = forceEither . readstring emptyCP $ unlines
[ "[server]"
, "host = *"
, "port = 3000"
, "web_root = http://localhost:3000"
, "file_root = /tmp"
, "[database]"
, "backend = sqlite"
, "connection_string = seacat.sqlite"
, "pool_size = 10"
]
-- |Apply the supplied configuration to the standard
-- configuration. This overrides the values in the original
-- configuration with the ones in the user configuration, if provided,
-- preserving any missing values.
applyUserConfig :: ConfigParser -- ^ The standard configuration
-> Maybe ConfigParser -- ^ Optional application-specific configuration
-> ConfigParser
applyUserConfig cfg (Just usercfg) = cfg `merge` usercfg
applyUserConfig cfg _ = cfg
-- |Get a value from the configuration unsafely (throws an
-- `IOException` on fail).
get' :: Get_C a => ConfigParser -> SectionSpec -> OptionSpec -> a
get' cp ss os = forceEither $ get cp ss os
-- |Get a value from the configuration in a handler. I found as I was
-- using Seacat that my handlers all started with a block of the form,
--
-- > cfg <- askConf
-- > let foo = get cfg "section" "foo"
-- > let bar = get cfg "section" "bar"
-- > let baz = get cfg "section" "baz"
--
-- This simplifies that by getting rid of the need to use `askConf`
-- manually.
conf :: (Get_C a, MonadError CPError m, PathInfo r) => SectionSpec -> OptionSpec -> RequestProcessor r (m a)
conf ss os = askConf >>= \config -> return $ get config ss os
-- |Get a value from the configuration in a handler unsafely. Like
-- `conf`, but throws an `IOException` if the value can't be found.
conf' :: (Get_C a, PathInfo r) => SectionSpec -> OptionSpec -> RequestProcessor r a
conf' ss os = askConf >>= \config -> return $ get' config ss os