-- Copyright: 2009 Dino Morelli
-- License: BSD3 (see LICENSE)
-- Author: Dino Morelli <dino@ui3.info>

{-# LANGUAGE FlexibleContexts #-}

module Erbu.Conf
   ( ConfMap, parseToMap
   )
   where

import Control.Monad.Error
import Data.Map hiding ( map )
import Data.Maybe ( catMaybes )
import Text.ParserCombinators.Parsec


-- These need to go into a shared library
eol :: GenParser Char st Char
eol = newline <|> (eof >> return '\n')

tillEol :: GenParser Char st String
tillEol = manyTill (noneOf "\n") eol


-- A blank line
blank :: GenParser Char st ()
blank = char '\n' >> return ()


-- A comment (line starting with # as its first character)
comment :: GenParser Char st ()
comment = char '#' >> tillEol >> return ()


-- A blank line or comment is discardable
discardable :: GenParser Char st (Maybe a)
discardable = do
   try (blank <|> comment)
   return Nothing


-- A key (beginning of line up to the first =)
key :: GenParser Char st String
key = many1 $ noneOf " =\n"


-- A key=value item on a single line
oneLiner :: GenParser Char st (Maybe (String, String))
oneLiner = do
   k <- key
   char '='
   v <- noneOf "\n"
   vs <- tillEol
   return $ Just (k, v:vs)


-- A value line for a multiline item (always indented)
multiLine :: GenParser Char st String
multiLine = do
   many1 space
   tillEol


-- Match the key and value of a multiline item
multiLiner :: GenParser Char st (Maybe (String, String))
multiLiner = do
   k <- key
   char '='
   newline
   ls <- many1 multiLine
   return $ Just (k, unlines ls)


-- Bring the above together into a complete config file
conf :: GenParser Char st [Maybe (String, String)]
conf = many (try multiLiner <|> discardable <|> oneLiner)


type ConfMap = Map String String


{- |
   Parse config file data into a simple (Map String String).
   This is an action in MonadError String. Upon failure, error
   message text from Parsec is thrown.

   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 :: (MonadError String m) => String -> String -> m ConfMap
parseToMap heading entireConf = either
   (throwError . show)
   (return . fromList . catMaybes)
   $ parse conf heading entireConf

