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

{-# 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'