bini-0.1.4: src/Data/Bini.hs
-- | Compressed binary configuration files in the (B)INI format.
--
--
-- Parsing example:
--
-- @
-- r <- readBiniFromFile \"loadouts.ini\"
-- case r of
-- Left s -> print s
-- Right bini -> writeFile \"loadouts.ini.txt\" (show bini)
-- @
--
-- Outputfile:
--
-- @
-- [Loadout]
-- nickname = msn_playerloadout
-- archetype = ge_fighter
-- equip = ge_gf1_engine_01
-- equip = shield01_mark01_lf, HpShield01
-- equip = ge_fighter_power01
-- ...
-- @
module Data.Bini
(-- * Reading
readBiniFromFile,
-- * Types
Bini(..),
Section(..),
Entry(..),
BiniVal(..)) where
import qualified Data.ByteString.Lazy as BL
import Data.Binary.Get
import Data.Word(Word32)
import Data.List(intercalate)
import qualified Data.ByteString.Lazy.Char8 as BLC
import Data.Binary.IEEE754(getFloat32le)
--helper
bstr2str :: BL.ByteString -> String
bstr2str = tail.init.show
-- |Get a String from the stringtable
getOffset :: BL.ByteString -> Int -> BL.ByteString
getOffset bstr offset = BL.takeWhile (/=0) s where
s = snd $ BL.splitAt (fromIntegral offset) bstr
--datatypes
-- |The abstract representation of the Bini
data Bini = Bini{ version :: Int, sections :: [Section] }
-- |The abstract representation of a section
data Section = Section{ name :: String, entries :: [Entry] }
-- |The abstract representation of an entry
data Entry = Entry{ varname :: String, values :: [BiniVal] }
-- |The abstract representation of a bini-value
data BiniVal = BiniInt Int | BiniFloat Float | BiniString String
instance Show Bini where
show (Bini v secs) = intercalate "\n" (map show secs)
instance Show Section where
show (Section name entries) = "["++name++"]\n"++intercalate "\n" (map show entries)++"\n"
instance Show Entry where
show (Entry name vals) = name++" = "++intercalate ", " (map show vals)
instance Show BiniVal where
show (BiniInt i) = show i
show (BiniFloat f) = show f
show (BiniString s) = s
-- |Parse a Binifile
readBiniFromFile :: String -> IO (Either String Bini)
readBiniFromFile path = do
content <- BL.readFile path
if "BINI"== bstr2str (BL.take 4 content) then do
let (isBini, version, strtableoff) = runGet parseHeader content
let (header_body, table) = BL.splitAt (fromIntegral strtableoff) content
let body = BL.drop 12 header_body
let secs = runGet (parseSections table) body
return $ Right $ Bini version secs
else return $ Left $ path++"is not a Bini-file!"
--------------------------------------------------------------------------------
---------------------- Parsing the Bini-file: ----------------------------------
--------------------------------------------------------------------------------
-- |Parse an entry
parseEntry :: BL.ByteString -> Get Entry
parseEntry ls = do
stroffset <- getWord16le
n_vals <- getWord8
let entryname = bstr2str $ ls `getOffset` fromIntegral stroffset
vals <- sequence [parseVal ls | x<-[1..(fromIntegral n_vals)]]
return (Entry entryname vals)
-- |Parse an entry-value
parseVal :: BL.ByteString -> Get BiniVal
parseVal ls = do
typ <- getWord8
case fromIntegral typ of
1 -> do
dat <- getWord32le
return $ BiniInt $ fromIntegral dat
2 -> do
dat <- getFloat32le
return $ BiniFloat dat
3 -> do
dat <- getWord32le
return $ BiniString $ bstr2str $ ls `getOffset` fromIntegral dat
parseSection :: BL.ByteString -> Get Section
parseSection table = do
stroffset <- getWord16le
num_e <- getWord16le
let secname = bstr2str $ table `getOffset` fromIntegral stroffset
let num_entries = fromIntegral num_e
entries <- sequence [parseEntry table | x<-[1..num_entries]]
return Section{ name = secname, entries = entries }
parseSections :: BL.ByteString -> Get [Section]
parseSections table = do
b <- isEmpty
if b then return []
else do
sec <- parseSection table
b2 <- isEmpty
if b2 then return [sec]
else do
secs <- parseSections table
return $ sec : secs
parseHeader :: Get (Word32, Int, Int)
parseHeader = do
isBini <- getWord32le
version <- getWord32le
str_table_offset <- getWord32le
return (isBini, fromIntegral version, fromIntegral str_table_offset)