module Data.Ini.Config.Raw
( Ini(..)
, IniSection(..)
, IniValue(..)
, parseIni
) where
import Control.Monad (void)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import Data.Text (Text)
import qualified Data.Text as T
import Text.Megaparsec
import Text.Megaparsec.Text
-- | An 'Ini' value is a mapping from section names to
-- 'IniSection' values.
newtype Ini
= Ini { fromIni :: HashMap Text IniSection }
deriving (Eq, Show)
-- | An 'IniSection' consists of a name, a mapping of key-value pairs,
-- and metadata about where the section starts and ends in the file.
data IniSection = IniSection
{ isName :: Text
, isVals :: HashMap Text IniValue
, isStartLine :: Int
, isEndLine :: Int
} deriving (Eq, Show)
-- | An 'IniValue' represents a key-value mapping, and also stores the
-- line number where it appears.
data IniValue = IniValue
{ vLineNo :: Int
, vName :: Text
, vValue :: Text
} deriving (Eq, Show)
-- | Parse a 'Text' value into an 'Ini' value.
parseIni :: Text -> Either String Ini
parseIni t = case runParser pIni "ini file" t of
Left err -> Left (parseErrorPretty err)
Right v -> Right v
pIni :: Parser Ini
pIni = do
sBlanks
vs <- many (pSection <?> "section")
void eof
return $ Ini $ HM.fromList [ (T.toLower (isName v), v)
| v <- vs
]
sBlanks :: Parser ()
sBlanks = skipMany (void eol <|> sComment)
sComment :: Parser ()
sComment = do
void (oneOf ";#")
void (manyTill anyChar eol)
pSection :: Parser IniSection
pSection = do
start <- getCurrentLine
void (char '[')
name <- T.pack `fmap` some (noneOf "[]")
void (char ']')
sBlanks
vals <- many (pPair <?> "key-value pair")
end <- getCurrentLine
sBlanks
return IniSection
{ isName = T.strip name
, isVals = HM.fromList [ (vName v, v) | v <- vals ]
, isStartLine = start
, isEndLine = end
}
pPair :: Parser IniValue
pPair = do
pos <- getCurrentLine
key <- T.pack `fmap` some (noneOf "[]=:")
void (oneOf ":=")
val <- T.pack `fmap` manyTill anyChar eol
sBlanks
return IniValue
{ vLineNo = pos
, vName = T.strip key
, vValue = T.strip val
}
getCurrentLine :: Parser Int
getCurrentLine = (fromIntegral . unPos . sourceLine) `fmap` getPosition