Skip to content

Commit 09fcc13

Browse files
committed
added chapter code for parsing an ini file
1 parent 3c61fa7 commit 09fcc13

File tree

3 files changed

+161
-0
lines changed

3 files changed

+161
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ cabal.sandbox.config
1616
*.hp
1717
.stack-work/
1818
chapter 19/shawty/dump.rdb
19+
.DS_Store

chapter24/parsing-ini-config-files/parsing-ini-config-files.cabal

+4
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@ executable parsing-ini-config-files
2121
, trifecta
2222
, parsers
2323
, raw-strings-qq
24+
, bytestring
25+
, hspec
26+
, containers
27+
, text
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
{-# LANGUAGE QuasiQuotes #-}
3+
4+
module Data.Ini where
5+
6+
import Control.Applicative
7+
import Data.ByteString (ByteString)
8+
import Data.Char (isAlpha)
9+
import Data.Map (Map)
10+
import qualified Data.Map as M
11+
import Data.Text (Text)
12+
import qualified Data.Text.IO as TIO
13+
import Test.Hspec
14+
import Text.RawString.QQ
15+
import Text.Trifecta
16+
17+
18+
headerEx :: String
19+
headerEx = "[blah]"
20+
21+
newtype Header =
22+
Header String deriving (Eq, Ord, Show)
23+
24+
parseBracketPair :: Parser a -> Parser a
25+
parseBracketPair p = char '[' *> p <* char ']'
26+
27+
parseHeader :: Parser Header
28+
parseHeader = parseBracketPair (Header <$> some letter)
29+
30+
assignmentEx :: String
31+
assignmentEx = "name=person"
32+
33+
type Name = String
34+
type Value = String
35+
type Assignments = Map Name Value
36+
37+
skipEOL :: Parser ()
38+
skipEOL = skipMany (oneOf "\n")
39+
40+
parseAssignment :: Parser (Name, Value)
41+
parseAssignment = do
42+
name <- some letter
43+
char '='
44+
value <- some letter
45+
skipEOL
46+
return (name, value)
47+
48+
commentEx :: String
49+
commentEx = "; comment on ini files looks like this"
50+
51+
commentEx' :: String
52+
commentEx' = "; also; like this;"
53+
54+
skipComments :: Parser ()
55+
skipComments =
56+
skipMany (do
57+
char ';' <|> char '#'
58+
skipMany (noneOf "\n")
59+
skipEOL
60+
)
61+
62+
sectionEx :: String
63+
sectionEx = [r|
64+
; ignore me
65+
[states]
66+
donut=happy
67+
|]
68+
69+
sectionEx' :: String
70+
sectionEx' = [r|
71+
; example 2
72+
[person]
73+
name=A
74+
sex=U
75+
76+
[school]
77+
name=donbosco
78+
principal=johnmulachira
79+
|]
80+
81+
data Section = Section Header Assignments deriving (Eq, Show)
82+
83+
newtype Config =
84+
Config (Map Header Assignments) deriving (Eq, Show)
85+
86+
skipWhiteSpace :: Parser ()
87+
skipWhiteSpace = skipMany (char ' ' <|> char '\n')
88+
89+
90+
parseSection :: Parser Section
91+
parseSection = do
92+
skipWhiteSpace
93+
skipComments
94+
h <- parseHeader
95+
skipEOL
96+
as <- some parseAssignment
97+
return $ Section h (M.fromList as)
98+
99+
100+
rollup :: Section -> Map Header Assignments -> Map Header Assignments
101+
rollup (Section h a) m = M.insert h a m
102+
103+
parseIni :: Parser Config
104+
parseIni = do
105+
sections <- some parseSection
106+
let mapOfSections = foldr rollup M.empty sections
107+
return (Config mapOfSections)
108+
109+
maybeSuccess :: Result a -> Maybe a
110+
maybeSuccess (Success a) = Just a
111+
maybeSuccess _ = Nothing
112+
113+
main :: IO ()
114+
main = hspec $ do
115+
describe "Assignment pairings" $
116+
it "can parse a simple assignment" $ do
117+
let m = parseString parseAssignment mempty assignmentEx
118+
r' = maybeSuccess m
119+
print m
120+
r' `shouldBe` Just ("name", "person")
121+
122+
describe "Header Parsing" $
123+
it "can parse a simple header" $ do
124+
let m = parseString parseHeader mempty headerEx
125+
r' = maybeSuccess m
126+
print m
127+
r' `shouldBe` Just (Header "blah")
128+
129+
describe "Comment parsing" $
130+
it "can parse a simple comment" $ do
131+
let p = skipComments >> parseHeader
132+
i = "; woot\n[blah]"
133+
m = parseString p mempty i
134+
r' = maybeSuccess m
135+
print m
136+
r' `shouldBe` Just (Header "blah")
137+
138+
describe "Section parsing" $
139+
it "can parse a section" $ do
140+
let m = parseString parseSection mempty sectionEx
141+
r' = maybeSuccess m
142+
states = M.fromList [("donut", "happy")]
143+
expected = Just (Section (Header "states") states)
144+
print m
145+
r' `shouldBe` expected
146+
147+
describe "INI parsing" $
148+
it "can parse multiple sections in an .ini file" $ do
149+
let m = parseString parseIni mempty sectionEx'
150+
r' = maybeSuccess m
151+
personSection = M.fromList [("name", "A"), ("sex", "U")]
152+
schoolSection = M.fromList [("name", "donbosco"), ("principal", "johnmulachira")]
153+
expected =
154+
Just $ Config $ M.fromList [(Header "person", personSection), (Header "school", schoolSection)]
155+
r' `shouldBe` expected
156+

0 commit comments

Comments
 (0)