You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
4.3 KiB
152 lines
4.3 KiB
{-# LANGUAGE OverloadedStrings #-} |
|
{-# LANGUAGE QuasiQuotes #-} |
|
|
|
module Data.Ini where |
|
|
|
import Control.Applicative |
|
import Data.ByteString (ByteString) |
|
import Data.Char (isAlpha) |
|
import Data.Map (Map) |
|
import qualified Data.Map as M |
|
import Data.Text (Text) |
|
import qualified Data.Text.IO as TIO |
|
import Test.Hspec |
|
import Text.RawString.QQ |
|
import Text.Trifecta |
|
|
|
headerEx :: ByteString |
|
headerEx = "[blah]" |
|
|
|
newtype Header = Header String deriving (Eq, Ord, Show) |
|
|
|
parseBracketPair :: Parser a -> Parser a |
|
parseBracketPair p = char '[' *> p <* char ']' |
|
|
|
parseHeader :: Parser Header |
|
parseHeader = parseBracketPair (Header <$> some letter) |
|
|
|
assignmentEx :: ByteString |
|
assignmentEx = "woot=1" |
|
|
|
type Name = String |
|
type Value = String |
|
type Assignments = Map Name Value |
|
|
|
parseAssignment :: Parser (Name, Value) |
|
parseAssignment = do |
|
name <- some letter |
|
_ <- char '=' |
|
val <- some (noneOf "\n") |
|
skipEOL -- important! |
|
return (name, val) |
|
|
|
-- | Skip end of lien and whitespace beyond. |
|
skipEOL :: Parser () |
|
skipEOL = skipMany (oneOf "\n") |
|
|
|
commentEx :: ByteString |
|
commentEx = "; last modified 11 November 2017 by John Doe" |
|
|
|
commentEx' :: ByteString |
|
commentEx' = "; blah\n; woot\n \n; hah" |
|
|
|
-- | Skip comments starting at the beginning of the line |
|
skipComments :: Parser () |
|
skipComments = skipMany $ do |
|
_ <- char ';' <|> char '#' |
|
skipMany (noneOf "\n") |
|
skipEOL |
|
|
|
sectionEx :: ByteString |
|
sectionEx = "; ignore me\n[languages]\nGael=Haskell" |
|
|
|
sectionEx' :: ByteString |
|
sectionEx' = [r| |
|
; ignore me |
|
[languages] |
|
Gael=Haskell |
|
|] |
|
|
|
sectionEx'' :: ByteString |
|
sectionEx'' = [r| |
|
; comment |
|
[section] |
|
host=wikipedia.org |
|
alias=claw |
|
|
|
[whatisit] |
|
red=intoothandclaw |
|
|] |
|
|
|
data Section = Section Header Assignments deriving (Eq, Show) |
|
|
|
newtype Config = Config (Map Header Assignments) deriving (Eq, Show) |
|
|
|
skipWhitespace :: Parser () |
|
skipWhitespace = skipMany (char ' ' <|> char '\n') |
|
|
|
parseSection :: Parser Section |
|
parseSection = do |
|
skipWhitespace |
|
skipComments |
|
h <- parseHeader |
|
skipEOL |
|
assignments <- some parseAssignment |
|
return $ Section h (M.fromList assignments) |
|
|
|
rollup :: Section -> Map Header Assignments -> Map Header Assignments |
|
rollup (Section h a) m = M.insert h a m |
|
|
|
parseIni :: Parser Config |
|
parseIni = do |
|
sections <- some parseSection |
|
let mapOfSections = foldr rollup M.empty sections |
|
return $ Config mapOfSections |
|
|
|
maybeSuccess :: Result a -> Maybe a |
|
maybeSuccess (Success a) = Just a |
|
maybeSuccess _ = Nothing |
|
|
|
main :: IO () |
|
main = hspec $ do |
|
describe "Assignment Parsing" $ |
|
it "can parse a simple assignment" $ do |
|
let m = parseByteString parseAssignment mempty assignmentEx |
|
r' = maybeSuccess m |
|
print m |
|
r' `shouldBe` Just ("woot", "1") |
|
describe "Header Parsing" $ |
|
it "can parse a simple header" $ do |
|
let m = parseByteString parseHeader mempty headerEx |
|
r' = maybeSuccess m |
|
print m |
|
r' `shouldBe` Just (Header "blah") |
|
describe "Comment parsing" $ |
|
it "Skips comment before header" $ do |
|
let p = skipComments >> parseHeader |
|
i = "; woot\n[blah]" |
|
m = parseByteString p mempty i |
|
r' = maybeSuccess m |
|
print m |
|
r' `shouldBe` Just (Header "blah") |
|
describe "Section parsing" $ |
|
it "can parse a simple section" $ do |
|
let m = parseByteString parseSection mempty sectionEx |
|
r' = maybeSuccess m |
|
languages = M.fromList [("Gael", "Haskell")] |
|
expected' = Just (Section (Header "languages") languages) |
|
print m |
|
r' `shouldBe` expected' |
|
describe "INI parsing" $ |
|
it "Can parse multiple sections" $ do |
|
let m = parseByteString parseIni mempty sectionEx'' |
|
r' = maybeSuccess m |
|
sectionValues = M.fromList |
|
[ ("alias", "claw") , ("host", "wikipedia.org")] |
|
whatisitValues = M.fromList [("red", "intoothandclaw")] |
|
expected' = Just (Config (M.fromList [ (Header "section" |
|
, sectionValues) |
|
, (Header "whatisit" |
|
, whatisitValues)])) |
|
print m |
|
r' `shouldBe` expected' |