{-# LANGUAGE ExistentialQuantification #-}
module Moo.Core where
import Control.Applicative ((<$>), (<*>))
import Control.Monad.Reader (ReaderT)
import qualified Data.Configurator as C
import Data.Configurator.Types (Config)
import qualified Data.Text as T
import Database.HDBC.PostgreSQL (connectPostgreSQL)
import Database.HDBC.Sqlite3 (connectSqlite3)
import System.Environment (getEnvironment)
import Database.Schema.Migrations ()
import Database.Schema.Migrations.Store (MigrationStore, StoreData)
import Database.Schema.Migrations.Backend
import Database.Schema.Migrations.Backend.HDBC
-- |The monad in which the application runs.
type AppT a = ReaderT AppState IO a
-- |The type of actions that are invoked to handle specific commands
type CommandHandler = StoreData -> AppT ()
-- |Application state which can be accessed by any command handler.
data AppState = AppState { _appOptions :: CommandOptions
, _appCommand :: Command
, _appRequiredArgs :: [String]
, _appOptionalArgs :: [String]
, _appStore :: MigrationStore
, _appDatabaseConnStr :: DbConnDescriptor
, _appDatabaseType :: String
, _appStoreData :: StoreData
}
type ShellEnvironment = [(String, String)]
data Configuration = Configuration
{ _connectionString :: String
, _databaseType :: String
, _migrationStorePath :: FilePath
}
loadConfiguration :: Maybe FilePath -> IO (Either String Configuration)
loadConfiguration pth = do
mCfg <- case pth of
Nothing -> fromShellEnvironment <$> getEnvironment
Just path -> fromConfigurator =<< C.load [C.Required path]
case mCfg of
Nothing -> do
case pth of
Nothing -> return $ Left "Missing required environment variables"
Just path -> return $ Left $ "Could not load configuration from " ++ path
Just cfg -> return $ Right cfg
fromShellEnvironment :: ShellEnvironment -> Maybe Configuration
fromShellEnvironment env = Configuration <$> connectionString
<*> databaseType
<*> migrationStorePath
where
connectionString = envLookup envDatabaseName
databaseType = envLookup envDatabaseType
migrationStorePath = envLookup envStoreName
envLookup = (\evar -> lookup evar env)
fromConfigurator :: Config -> IO (Maybe Configuration)
fromConfigurator conf = do
let configLookup = C.lookup conf . T.pack
connectionString <- configLookup envDatabaseName
databaseType <- configLookup envDatabaseType
migrationStorePath <- configLookup envStoreName
return $ Configuration <$> connectionString
<*> databaseType
<*> migrationStorePath
-- |CommandOptions are those options that can be specified at the command
-- prompt to modify the behavior of a command.
data CommandOptions = CommandOptions { _configFilePath :: Maybe String
, _test :: Bool
, _noAsk :: Bool
}
-- |A command has a name, a number of required arguments' labels, a
-- number of optional arguments' labels, and an action to invoke.
data Command = Command { _cName :: String
, _cRequired :: [String]
, _cOptional :: [String]
, _cAllowedOptions :: [String]
, _cDescription :: String
, _cHandler :: CommandHandler
}
-- |ConfigOptions are those options read from configuration file
data ConfigData = ConfigData { _dbTypeStr :: String
, _dbConnStr :: String
, _fileStorePath :: String
}
newtype DbConnDescriptor = DbConnDescriptor String
-- |The values of DBM_DATABASE_TYPE and their corresponding connection
-- factory functions.
databaseTypes :: [(String, String -> IO Backend)]
databaseTypes = [ ("postgresql", fmap hdbcBackend . connectPostgreSQL)
, ("sqlite3", fmap hdbcBackend . connectSqlite3)
]
envDatabaseType :: String
envDatabaseType = "DBM_DATABASE_TYPE"
envDatabaseName :: String
envDatabaseName = "DBM_DATABASE"
envStoreName :: String
envStoreName = "DBM_MIGRATION_STORE"