sshtun-1.0.0: src/Sshtun/Conf.hs
-- Copyright: 2012 Dino Morelli
-- License: BSD3 (see LICENSE)
-- Author: Dino Morelli <dino@ui3.info>
{-# LANGUAGE FlexibleContexts #-}
{- |
Simple module for loading config files
-}
module Sshtun.Conf
( Conf (..)
, parseConf
)
where
import Control.Monad.Error
import Data.Map hiding ( map )
import Data.Maybe ( catMaybes )
import Prelude hiding ( lookup )
import System.Log
import Text.Regex ( matchRegex, mkRegex )
type ConfMap = Map String String
data Conf = Conf
{ logFile :: String
, logPriority :: Priority
, switchUrl :: Maybe String
, switchPollInterval :: Int
, localDaemonUser :: String
, sshPort :: Int
, localPort :: Int
, remotePort :: Int
, remoteUser :: String
, remoteHost :: String
, addlSshArgs :: String
, tunnelRetryDelay :: Int
}
deriving Show
emptyConf :: Conf
emptyConf = Conf
{ logFile = ""
, logPriority = DEBUG
, switchUrl = Nothing
, switchPollInterval = 0
, localDaemonUser = ""
, sshPort = 0
, localPort = 0
, remotePort = 0
, remoteUser = ""
, remoteHost = ""
, addlSshArgs = ""
, tunnelRetryDelay = 0
}
extractConfItem :: MonadError String m =>
ConfMap -> (String, String -> m b) -> m b
extractConfItem cm (k, f) =
maybe (throwError $ "Missing config field: " ++ k)
f $ lookup k cm
parseConf :: Monad m => String -> m (Either String Conf)
parseConf entireConf = runErrorT $ do
mutatorActions <- mapM (extractConfItem cm) exts
foldM (\c f -> f c) emptyConf mutatorActions
where
cm = parseToMap entireConf
exts =
[ ( "logFile"
, (\s -> return (\c -> return $ c { logFile = s }))
)
, ( "logPriority"
, (\s -> return (\c -> do
p <- strToPriority s
return $ c { logPriority = p }
))
)
, ( "switchUrl"
, (\s -> return (\c ->
return $ c { switchUrl = parseSwitchUrl s }))
)
, ( "switchPollInterval"
, (\s -> return (\c -> do
n <- readE ("switchPollInterval, unable to parse: " ++ s) s
return $ c { switchPollInterval = n }
))
)
, ( "localDaemonUser"
, (\s -> return (\c -> return $ c { localDaemonUser = s }))
)
, ( "sshPort"
, (\s -> return (\c -> do
n <- readE ("sshPort, unable to parse: " ++ s) s
return $ c { sshPort = n }
))
)
, ( "localPort"
, (\s -> return (\c -> do
n <- readE ("localPort, unable to parse: " ++ s) s
return $ c { localPort = n }
))
)
, ( "remotePort"
, (\s -> return (\c -> do
n <- readE ("remotePort, unable to parse: " ++ s) s
return $ c { remotePort = n }
))
)
, ( "remoteUser"
, (\s -> return (\c -> return $ c { remoteUser = s }))
)
, ( "remoteHost"
, (\s -> return (\c -> return $ c { remoteHost = s }))
)
, ( "addlSshArgs"
, (\s -> return (\c -> return $ c { addlSshArgs = s }))
)
, ( "tunnelRetryDelay"
, (\s -> return (\c -> do
n <- readE ("tunnelRetryDelay, unable to parse: " ++ s) s
return $ c { tunnelRetryDelay = n }
))
)
]
parseSwitchUrl :: String -> Maybe String
parseSwitchUrl "" = Nothing
parseSwitchUrl s = Just s
readE :: (MonadError e m, Read a) => e -> String -> m a
readE msg s = case reads s of
((x, ""):[]) -> return x
_ -> throwError msg
{- |
Parse config file data into a simple (Map String String).
For example, this:
> --- file start ---
> foo=one
> # a comment
>
> bar
> baz-blorp=2
> --- file end ---
becomes:
> fromList [("foo","one"),("bar",""),("baz-blorp","2")]
Comments (prefixed with #) and blank lines in the config file
are discarded.
-}
parseToMap :: String -> ConfMap
parseToMap entireConf =
fromList $ map listToPair
$ catMaybes $ map (matchRegex re) $ lines entireConf
where
listToPair [k, v] = (k, v)
listToPair _ = undefined -- Should never happen
re = mkRegex "^([^#][^=]*)=?(.*)"
strToPriority :: MonadError String m => String -> m Priority
strToPriority "DEBUG" = return DEBUG
strToPriority "INFO" = return INFO
strToPriority "NOTICE" = return NOTICE
strToPriority "WARNING" = return WARNING
strToPriority "ERROR" = return ERROR
strToPriority "CRITICAL" = return CRITICAL
strToPriority "ALERT" = return ALERT
strToPriority "EMERGENCY" = return EMERGENCY
strToPriority x = throwError $ "Invalid logPriority: " ++ x