packages feed

iyql-0.0.5.1: src/main/haskell/Yql/Data/Cfg.hs

-- Copyright (c) 2010, Diego Souza
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
--   * Redistributions of source code must retain the above copyright notice,
--     this list of conditions and the following disclaimer.
--   * Redistributions in binary form must reproduce the above copyright notice,
--     this list of conditions and the following disclaimer in the documentation
--     and/or other materials provided with the distribution.
--   * Neither the name of the <ORGANIZATION> nor the names of its contributors
--     may be used to endorse or promote products derived from this software
--     without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

module Yql.Data.Cfg
       ( -- * Types
         Cfg()
         -- * Reading
       , cfg 
       , tryCfg
       , tryCfgs
       , tryUsrCfg
       , tryUsrCfgs
       , usrCfg
       , parseCfg
       , basedir
         -- * Building
       , empty
       , fromList
       ) where

import Prelude hiding (catch)
import Data.Char
import Data.List
import Control.Exception
import System.FilePath
import System.Directory
import System.IO

newtype Cfg = Cfg [(String,String)]
            deriving (Show,Eq,Ord)

-- | Returns an empty configuration
empty :: Cfg
empty = fromList []

-- | Reads configuration from a list
fromList :: [(String,String)] -> Cfg
fromList = Cfg

-- | Read a config entry or return a default value in case there is an
-- error.
tryCfg :: Cfg -> String -> String -> String
tryCfg (Cfg db) key def = case (lookup key db)
                          of Nothing  -> def
                             Just rst -> rst

-- | Read a config entry that is allowed to be defined multiple times.
tryCfgs :: Cfg -> String -> [String] -> [String]
tryCfgs (Cfg ((k,v):es)) key def 
  | k==key             = v : tryCfgs (Cfg es) key []
  | otherwise          = tryCfgs (Cfg es) key def
tryCfgs (Cfg []) _ def = def

-- | Read a config from the user configuration file.
tryUsrCfg :: String -> String -> IO String
tryUsrCfg key def = usrCfg >>= \db -> return $ tryCfg db key def

-- | Read a config from the user configuration file.
tryUsrCfgs :: String -> [String] -> IO [String]
tryUsrCfgs key def = usrCfg >>= \db -> return $ tryCfgs db key def

-- | Read a config entry from the configuration file.
cfg :: FilePath -> IO Cfg
cfg file = catch readCfg (\(SomeException _) -> return (Cfg []))
  where readCfg = do available <- doesFileExist file
                     if (available)
                       then bracket (openFile file ReadMode) hClose (\h -> fmap parseCfg (hGetContents h >>= evaluate))
                       else return (Cfg [])

-- | The configuration file is as follows:
--    1. Lines starting with -- are considered comments;
--    2. Empty lines are also discarded;
--    3. Entries are simply pairs, separated by colon (:);
parseCfg :: String -> Cfg
parseCfg = Cfg . map (parseEntry . stripComments) . filter properEntries . lines
  where properEntries xs0 
          | "--" `isPrefixOf` xs = False
          | null xs              = False
          | otherwise            = True
            where xs = dropWhile isSpace xs0
        
        parseEntry xs = let (key,val) = break (==':') (dropWhile isSpace xs)
                        in (key,dropWhile isSpace (drop 1 val))
        
        stripComments []          = []
        stripComments ('-':'-':_) = []
        stripComments (x:xs)      = x : stripComments xs

-- | Base directory where configuration files are read from
basedir :: IO FilePath
basedir = fmap (\home -> joinPath [home,".iyql"]) getHomeDirectory

-- | File configuration file
usrCfg :: IO Cfg
usrCfg = do home <- basedir
            cfg (joinPath [home,"cfg"])