gdritter repos config-ini / aaf4a65
Some renaming and moving around Getty Ritter 7 years ago
15 changed file(s) with 195 addition(s) and 195 deletion(s). Collapse all Expand all
3333 library
3434 hs-source-dirs: src
3535 exposed-modules: Data.Ini.Config
36 , Data.Ini.Raw
36 , Data.Ini.Config.Raw
3737 ghc-options: -Wall
3838 build-depends: base >=4.7 && <4.10
3939 , text >=1.2.2 && <1.3
4545 executable basic-example
4646 if !flag(build-examples)
4747 buildable: False
48 hs-source-dirs: basic-example
48 hs-source-dirs: examples/basic-example
4949 main-is: Main.hs
5050 ghc-options: -Wall
5151 build-depends: base >=4.7 && <4.10
5656 executable config-example
5757 if !flag(build-examples)
5858 buildable: False
59 hs-source-dirs: config-example
59 hs-source-dirs: examples/config-example
6060 main-is: Main.hs
6161 ghc-options: -Wall
6262 build-depends: base >=4.7 && <4.10
7777 , unordered-containers
7878 , text
7979
80 test-suite test-suite
80 test-suite test-prewritten
8181 type: exitcode-stdio-1.0
8282 ghc-options: -Wall
8383 default-language: Haskell2010
84 hs-source-dirs: test/general
84 hs-source-dirs: test/prewritten
8585 main-is: Main.hs
8686 build-depends: base
8787 , config-ini
1 module Data.Ini.Config.Raw
2 ( Ini(..)
3 , IniSection(..)
4 , IniValue(..)
5 , parseIni
6 ) where
7
8 import Control.Monad (void)
9 import Data.HashMap.Strict (HashMap)
10 import qualified Data.HashMap.Strict as HM
11 import Data.Text (Text)
12 import qualified Data.Text as T
13 import Text.Megaparsec
14 import Text.Megaparsec.Text
15
16 -- | An 'Ini' value is a mapping from section names to
17 -- 'IniSection' values.
18 newtype Ini
19 = Ini { fromIni :: HashMap Text IniSection }
20 deriving (Eq, Show)
21
22 -- | An 'IniSection' consists of a name, a mapping of key-value pairs,
23 -- and metadata about where the section starts and ends in the file.
24 data IniSection = IniSection
25 { isName :: Text
26 , isVals :: HashMap Text IniValue
27 , isStartLine :: Int
28 , isEndLine :: Int
29 } deriving (Eq, Show)
30
31 -- | An 'IniValue' represents a key-value mapping, and also stores the
32 -- line number where it appears.
33 data IniValue = IniValue
34 { vLineNo :: Int
35 , vName :: Text
36 , vValue :: Text
37 } deriving (Eq, Show)
38
39 -- | Parse a 'Text' value into an 'Ini' value.
40 parseIni :: Text -> Either String Ini
41 parseIni t = case runParser pIni "ini file" t of
42 Left err -> Left (parseErrorPretty err)
43 Right v -> Right v
44
45 pIni :: Parser Ini
46 pIni = sBlanks *> (go `fmap` (many (pSection <?> "section") <* eof))
47 where go vs = Ini $ HM.fromList [ (T.toLower (isName v), v)
48 | v <- vs
49 ]
50
51 sBlanks :: Parser ()
52 sBlanks = skipMany (void eol <|> sComment)
53
54 sComment :: Parser ()
55 sComment = do
56 void (oneOf ";#")
57 void (manyTill anyChar eol)
58
59 pSection :: Parser IniSection
60 pSection = do
61 start <- getCurrentLine
62 void (char '[')
63 name <- T.pack `fmap` some (noneOf "[]")
64 void (char ']')
65 sBlanks
66 vals <- many (pPair <?> "key-value pair")
67 end <- getCurrentLine
68 sBlanks
69 return IniSection
70 { isName = T.strip name
71 , isVals = HM.fromList [ (vName v, v) | v <- vals ]
72 , isStartLine = start
73 , isEndLine = end
74 }
75
76 pPair :: Parser IniValue
77 pPair = do
78 pos <- getCurrentLine
79 key <- T.pack `fmap` some (noneOf "[]=:")
80 void (oneOf ":=")
81 val <- T.pack `fmap` manyTill anyChar eol
82 sBlanks
83 return IniValue
84 { vLineNo = pos
85 , vName = T.strip key
86 , vValue = T.strip val
87 }
88
89 getCurrentLine :: Parser Int
90 getCurrentLine = (fromIntegral . unPos . sourceLine) `fmap` getPosition
3333
3434 import Control.Monad.Trans.Except
3535 import qualified Data.HashMap.Strict as HM
36 import Data.Ini.Raw
36 import Data.Ini.Config.Raw
3737 import Data.String (IsString(..))
3838 import Data.Text (Text)
3939 import qualified Data.Text as T
+0
-90
src/Data/Ini/Raw.hs less more
1 module Data.Ini.Raw
2 ( Ini(..)
3 , IniSection(..)
4 , IniValue(..)
5 , parseIni
6 ) where
7
8 import Control.Monad (void)
9 import Data.HashMap.Strict (HashMap)
10 import qualified Data.HashMap.Strict as HM
11 import Data.Text (Text)
12 import qualified Data.Text as T
13 import Text.Megaparsec
14 import Text.Megaparsec.Text
15
16 -- | An 'Ini' value is a mapping from section names to
17 -- 'IniSection' values.
18 newtype Ini
19 = Ini { fromIni :: HashMap Text IniSection }
20 deriving (Eq, Show)
21
22 -- | An 'IniSection' consists of a name, a mapping of key-value pairs,
23 -- and metadata about where the section starts and ends in the file.
24 data IniSection = IniSection
25 { isName :: Text
26 , isVals :: HashMap Text IniValue
27 , isStartLine :: Int
28 , isEndLine :: Int
29 } deriving (Eq, Show)
30
31 -- | An 'IniValue' represents a key-value mapping, and also stores the
32 -- line number where it appears.
33 data IniValue = IniValue
34 { vLineNo :: Int
35 , vName :: Text
36 , vValue :: Text
37 } deriving (Eq, Show)
38
39 -- | Parse a 'Text' value into an 'Ini' value.
40 parseIni :: Text -> Either String Ini
41 parseIni t = case runParser pIni "ini file" t of
42 Left err -> Left (parseErrorPretty err)
43 Right v -> Right v
44
45 pIni :: Parser Ini
46 pIni = sBlanks *> (go `fmap` (many (pSection <?> "section") <* eof))
47 where go vs = Ini $ HM.fromList [ (T.toLower (isName v), v)
48 | v <- vs
49 ]
50
51 sBlanks :: Parser ()
52 sBlanks = skipMany (void eol <|> sComment)
53
54 sComment :: Parser ()
55 sComment = do
56 void (oneOf ";#")
57 void (manyTill anyChar eol)
58
59 pSection :: Parser IniSection
60 pSection = do
61 start <- getCurrentLine
62 void (char '[')
63 name <- T.pack `fmap` some (noneOf "[]")
64 void (char ']')
65 sBlanks
66 vals <- many (pPair <?> "key-value pair")
67 end <- getCurrentLine
68 sBlanks
69 return IniSection
70 { isName = T.strip name
71 , isVals = HM.fromList [ (vName v, v) | v <- vals ]
72 , isStartLine = start
73 , isEndLine = end
74 }
75
76 pPair :: Parser IniValue
77 pPair = do
78 pos <- getCurrentLine
79 key <- T.pack `fmap` some (noneOf "[]=:")
80 void (oneOf ":=")
81 val <- T.pack `fmap` manyTill anyChar eol
82 sBlanks
83 return IniValue
84 { vLineNo = pos
85 , vName = T.strip key
86 , vValue = T.strip val
87 }
88
89 getCurrentLine :: Parser Int
90 getCurrentLine = (fromIntegral . unPos . sourceLine) `fmap` getPosition
+0
-42
test/general/Main.hs less more
1 module Main where
2
3 import Data.List
4 import Data.Ini.Raw
5 import Data.HashMap.Strict (HashMap)
6 import Data.Text (Text)
7 import qualified Data.Text.IO as T
8 import System.Directory
9 import System.Exit
10
11 dir :: FilePath
12 dir = "test/general/cases"
13
14 main :: IO ()
15 main = do
16 files <- getDirectoryContents dir
17 let inis = [ f | f <- files
18 , ".ini" `isSuffixOf` f
19 ]
20 mapM_ runTest inis
21
22 toMaps :: Ini -> HashMap Text (HashMap Text Text)
23 toMaps (Ini m) = fmap (fmap vValue . isVals) m
24
25 runTest :: FilePath -> IO ()
26 runTest iniF = do
27 let hsF = take (length iniF - 4) iniF ++ ".hs"
28 ini <- T.readFile (dir ++ "/" ++ iniF)
29 hs <- readFile (dir ++ "/" ++ hsF)
30 case parseIni ini of
31 Left err -> do
32 putStrLn ("Error parsing " ++ iniF)
33 putStrLn err
34 exitFailure
35 Right x
36 | toMaps x == read hs -> do
37 putStrLn ("Passed: " ++ iniF)
38 | otherwise -> do
39 putStrLn ("Parses do not match for " ++ iniF)
40 putStrLn ("Expected: " ++ hs)
41 putStrLn ("Actual: " ++ show (toMaps x))
42 exitFailure
+0
-11
test/general/cases/basic.hs less more
1 fromList
2 [ ( "s1"
3 , fromList
4 [ ( "foo", "bar" )
5 , ( "baz", "quux" )
6 ]
7 )
8 , ( "s2"
9 , fromList [ ( "argl", "bargl" ) ]
10 )
11 ]
+0
-12
test/general/cases/basic.ini less more
1 # a thorough test
2 # leading comments
3 [S1]
4 # test with equals
5 foo = bar
6 # test with colon
7 baz : quux
8
9 [S2]
10 ; comments with semicolons
11 argl = bargl
12 ; trailing comments
+0
-18
test/general/cases/unicode.hs less more
1 fromList
2 [ ( "中文"
3 , fromList
4 [ ("鸡丁", "宫保")
5 , ("豆腐", "麻婆")
6 ]
7 )
8 , ( "русский"
9 , fromList [ ( "хорошо", "очень" ) ]
10 )
11 , ( "العَرَبِيَّة‎‎"
12 , fromList
13 [ ("واحد", "١")
14 , ("اثنان", "٢")
15 , ("ثلاثة", "٣")
16 ]
17 )
18 ]
+0
-15
test/general/cases/unicode.ini less more
1 # some unicode tests, for good measure
2
3 [中文]
4 # 也有漢字在這個注释
5 鸡丁 = 宫保
6 豆腐 : 麻婆
7
8 [русский]
9 ; и это комментарии
10 хорошо = очень
11
12 [العَرَبِيَّة‎‎]
13 واحد = ١
14 اثنان = ٢
15 ثلاثة = ٣
44 import Data.HashMap.Strict (HashMap)
55 import qualified Data.HashMap.Strict as HM
66 import qualified Data.Ini as I1
7 import qualified Data.Ini.Raw as I2
7 import qualified Data.Ini.Config.Raw as I2
88 import Data.Text (Text)
99 import qualified Data.Text as T
1010
1 module Main where
2
3 import Data.List
4 import Data.Ini.Config.Raw
5 import Data.HashMap.Strict (HashMap)
6 import Data.Text (Text)
7 import qualified Data.Text.IO as T
8 import System.Directory
9 import System.Exit
10
11 dir :: FilePath
12 dir = "test/prewritten/cases"
13
14 main :: IO ()
15 main = do
16 files <- getDirectoryContents dir
17 let inis = [ f | f <- files
18 , ".ini" `isSuffixOf` f
19 ]
20 mapM_ runTest inis
21
22 toMaps :: Ini -> HashMap Text (HashMap Text Text)
23 toMaps (Ini m) = fmap (fmap vValue . isVals) m
24
25 runTest :: FilePath -> IO ()
26 runTest iniF = do
27 let hsF = take (length iniF - 4) iniF ++ ".hs"
28 ini <- T.readFile (dir ++ "/" ++ iniF)
29 hs <- readFile (dir ++ "/" ++ hsF)
30 case parseIni ini of
31 Left err -> do
32 putStrLn ("Error parsing " ++ iniF)
33 putStrLn err
34 exitFailure
35 Right x
36 | toMaps x == read hs -> do
37 putStrLn ("Passed: " ++ iniF)
38 | otherwise -> do
39 putStrLn ("Parses do not match for " ++ iniF)
40 putStrLn ("Expected: " ++ hs)
41 putStrLn ("Actual: " ++ show (toMaps x))
42 exitFailure
1 fromList
2 [ ( "s1"
3 , fromList
4 [ ( "foo", "bar" )
5 , ( "baz", "quux" )
6 ]
7 )
8 , ( "s2"
9 , fromList [ ( "argl", "bargl" ) ]
10 )
11 ]
1 # a thorough test
2 # leading comments
3 [S1]
4 # test with equals
5 foo = bar
6 # test with colon
7 baz : quux
8
9 [S2]
10 ; comments with semicolons
11 argl = bargl
12 ; trailing comments
1 fromList
2 [ ( "中文"
3 , fromList
4 [ ("鸡丁", "宫保")
5 , ("豆腐", "麻婆")
6 ]
7 )
8 , ( "русский"
9 , fromList [ ( "хорошо", "очень" ) ]
10 )
11 , ( "العَرَبِيَّة‎‎"
12 , fromList
13 [ ("واحد", "١")
14 , ("اثنان", "٢")
15 , ("ثلاثة", "٣")
16 ]
17 )
18 ]
1 # some unicode tests, for good measure
2
3 [中文]
4 # 也有漢字在這個注释
5 鸡丁 = 宫保
6 豆腐 : 麻婆
7
8 [русский]
9 ; и это комментарии
10 хорошо = очень
11
12 [العَرَبِيَّة‎‎]
13 واحد = ١
14 اثنان = ٢
15 ثلاثة = ٣