gdritter repos config-ini / 23cd132
Added Lens-based API and doctests; version bump Getty Ritter 7 years ago
5 changed file(s) with 451 addition(s) and 6 deletion(s). Collapse all Expand all
11 name: config-ini
2 version: 0.1.2.0
2 version: 0.2.0.0
33 synopsis: A library for simple INI-based configuration files.
44 homepage: https://github.com/aisamanra/config-ini
55 bug-reports: https://github.com/aisamanra/config-ini/issues
3434 library
3535 hs-source-dirs: src
3636 exposed-modules: Data.Ini.Config
37 , Data.Ini.Config.Lens
3738 , Data.Ini.Config.Raw
3839 ghc-options: -Wall
3940 build-depends: base >=4.7 && <4.10
4142 , unordered-containers >=0.2.7 && <0.3
4243 , transformers >=0.5.2 && <0.6
4344 , megaparsec >=5.1.2 && <5.2
45 -- , microlens
4446 default-language: Haskell2010
4547
4648 executable basic-example
6365 build-depends: base >=4.7 && <4.10
6466 , text
6567 , config-ini
68 default-language: Haskell2010
69
70 executable lens-example
71 if !flag(build-examples)
72 buildable: False
73 hs-source-dirs: examples/lens-example
74 main-is: Main.hs
75 ghc-options: -Wall
76 build-depends: base >=4.7 && <4.10
77 , text
78 , config-ini
79 , microlens-platform
6680 default-language: Haskell2010
6781
6882 test-suite test-ini-compat
89103 , unordered-containers
90104 , text
91105 , directory
106
107 test-suite test-doctest
108 type: exitcode-stdio-1.0
109 ghc-options: -Wall
110 default-language: Haskell2010
111 hs-source-dirs: test/doctest
112 main-is: Main.hs
113 build-depends: base
114 , doctest
115 , microlens
1 {-# LANGUAGE OverloadedStrings #-}
2 {-# LANGUAGE TemplateHaskell #-}
3
4 module Main where
5
6 import Data.Ini.Config.Lens
7 import Data.Text (Text)
8 import Lens.Micro.Platform (makeLenses)
9
10 data Config = Config
11 { _confUsername :: Text
12 , _confPort :: Int
13 , _confUseEncryption :: Bool
14 } deriving (Eq, Show)
15
16 makeLenses ''Config
17
18 defaultConfig :: Config
19 defaultConfig = Config
20 { _confUsername = "undefined"
21 , _confPort = 0
22 , _confUseEncryption = True
23 }
24
25 parseConfig :: IniLensParser Config ()
26 parseConfig = sectionL "network" $ do
27 confUsername .= field "user"
28 confPort .= fieldOf "port" number
29 confUseEncryption .=? fieldMbOf "encryption" flag
30
31 example :: Text
32 example = "[NETWORK]\n\
33 \user = gdritter\n\
34 \port = 8888\n"
35
36 main :: IO ()
37 main = do
38 print (parseIniFileL example defaultConfig parseConfig)
1 {-# LANGUAGE RankNTypes #-}
2 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
3
4 module Data.Ini.Config.Lens
5 (
6 -- $main
7 -- * Running Lens-Based Parsers
8 parseIniFileL
9 -- * Lens-Aware Parser Types
10 , IniLensParser
11 , SectionLensParser
12 -- * Lens-Aware Section-Level Parsing
13 , sectionL
14 , sectionOptL
15 -- * Lens-Aware Field-Level Parsing
16 , lensField
17 , (.=)
18 , lensFieldOpt
19 , (.=?)
20 -- ** Lens-Aware Field Parsing Aliases
21 , fieldL
22 , fieldOfL
23 , fieldMbL
24 , fieldMbOfL
25 , fieldOptL
26 , fieldOptOfL
27 , fieldDefL
28 , fieldDefOfL
29 , fieldFlagL
30 , fieldFlagDefL
31 -- * Reader Functions
32 , Lens
33 , updateLens
34 , module Data.Ini.Config
35 ) where
36
37 import Control.Applicative (Applicative(..), Alternative(..))
38 import Control.Monad.Trans.Class (lift)
39 import Control.Monad.Trans.Writer.Strict
40 import Data.Ini.Config
41 import Data.Monoid (Endo(..))
42 import Data.Text (Text)
43
44 -- $setup
45 -- >>> type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
46 --
47 -- >>> let lens get set f a = (`set` a) `fmap` f (get a)
48 --
49 -- >>> let _1 = lens fst (\ a (_, b) -> (a, b))
50 --
51 -- >>> let _2 = lens snd (\ b (a, _) -> (a, b))
52
53 -- | This is a "lens"-compatible type alias
54 type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
55
56 -- We need this to implement 'set' for lenses
57 newtype I a = I { fromI :: a }
58 instance Functor I where fmap f (I a) = I (f a)
59
60 set :: Lens s s a a -> a -> s -> s
61 set lens x a = fromI (lens (\ _ -> I x) a)
62
63 -- | This is a function compatible with the @fieldOf@ family of functions. It allows
64 -- you to parse a field and then create an update function with it.
65 updateLens :: (Text -> Either String a) -> Lens s s a a -> Text -> Either String (s -> s)
66 updateLens rd lens text = do
67 case rd text of
68 Left err -> Left err
69 Right r -> Right (\ st -> set lens r st)
70
71 newtype IniLensParser s a = IniLensParser (WriterT (Endo s) IniParser a)
72 deriving (Functor, Applicative, Alternative, Monad)
73
74 newtype SectionLensParser s a = SectionLensParser (WriterT (Endo s) SectionParser a)
75 deriving (Functor, Applicative, Alternative, Monad)
76
77 parseIniFileL :: Text -> s -> IniLensParser s () -> Either String s
78 parseIniFileL text def (IniLensParser mote) = do
79 (_, Endo update) <- parseIniFile text (runWriterT mote)
80 return (update def)
81
82 sectionL :: Text -> SectionLensParser s () -> IniLensParser s ()
83 sectionL name (SectionLensParser thunk) = IniLensParser $ do
84 ((), update) <- lift (section name (runWriterT thunk))
85 tell update
86 return ()
87
88 sectionOptL :: Text -> SectionLensParser s () -> IniLensParser s ()
89 sectionOptL name (SectionLensParser thunk) = IniLensParser $ do
90 updateMb <- lift (sectionMb name (runWriterT thunk))
91 case updateMb of
92 Nothing -> return ()
93 Just ((), update) -> tell update
94
95 toLens :: Lens s s a a -> SectionParser a -> SectionLensParser s ()
96 toLens lens mote = SectionLensParser $ do
97 rs <- lift mote
98 tell $ Endo (set lens rs)
99
100 -- | The 'lensField' function (or its operator form '.=') turns a lens and a
101 -- standard 'SectionParser' field into a 'SectionLensParser' that uses the
102 -- relevant lens to update an internal value to the result of the
103 -- 'SectionParser'.
104 --
105 -- >>> parseIniFileL"[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (lensField _1 (field "x"))
106 -- Right ("hello",False)
107 -- >>> parseIniFileL"[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (lensField _1 (field "y"))
108 -- Left "Missing field \"y\" in section \"MAIN\""
109 lensField :: Lens s s a a -> SectionParser a -> SectionLensParser s ()
110 lensField = toLens
111
112 -- | An infix alias for 'lensField'.
113 --
114 -- >>> parseIniFileL"[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (_1 .= field "x")
115 -- Right ("hello",False)
116 -- >>> parseIniFileL"[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (_1 .= field "y")
117 -- Left "Missing field \"y\" in section \"MAIN\""
118 (.=) :: Lens s s a a -> SectionParser a -> SectionLensParser s ()
119 (.=) = toLens
120
121 -- | The 'lensFieldOpt' function (or its operator form '.=?') turns a lens
122 -- and a standard 'SectionParser' field into a 'SectionLensParser' that
123 -- ignores values that are not present, but uses the lens to set a value
124 -- that is present.
125 --
126 -- >>> parseIniFileL"[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (lensFieldOpt _1 (fieldMb "x"))
127 -- Right ("hello",False)
128 -- >>> parseIniFileL"[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (lensFieldOpt _1 (fieldMb "y"))
129 -- Right ("def",False)
130 lensFieldOpt :: Lens s s a a -> SectionParser (Maybe a) -> SectionLensParser s ()
131 lensFieldOpt lens mote = SectionLensParser $ do
132 rsMb <- lift mote
133 case rsMb of
134 Just rs -> tell $ Endo (set lens rs)
135 Nothing -> return ()
136
137 -- | An infix alias for 'lensFieldOpt'.
138 --
139 -- >>> parseIniFileL"[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (_1 .=? fieldMb "x")
140 -- Right ("hello",False)
141 -- >>> parseIniFileL"[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (_1 .=? fieldMb "y")
142 -- Right ("def",False)
143 (.=?) :: Lens s s a a -> SectionParser (Maybe a) -> SectionLensParser s ()
144 (.=?) = lensFieldOpt
145
146 -- | A 'Lens'-aware variant of 'field': the 'Lens' argument names the
147 -- setter to use on the underlying value being modified.
148 --
149 -- >>> parseIniFileL "[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (fieldL "x" _1)
150 -- Right ("hello",False)
151 -- >>> parseIniFileL "[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (fieldL "y" _1)
152 -- Left "Missing field \"y\" in section \"MAIN\""
153 fieldL :: Text -> Lens s s Text Text -> SectionLensParser s ()
154 fieldL name lens = toLens lens $ field name
155
156 -- | A 'Lens'-aware variant of 'fieldOf': the 'Lens' argument names the
157 -- setter to use on the underlying value being modified.
158 --
159 -- >>> parseIniFileL "[MAIN]\nx = 72\n" (0, False) $ sectionL "MAIN" (fieldOfL "x" number _1)
160 -- Right (72,False)
161 -- >>> parseIniFileL "[MAIN]\nx = hello\n" (0, False) $ sectionL "MAIN" (fieldOfL "x" number _1)
162 -- Left "Line 2, in section \"MAIN\": Unable to parse \"hello\" as a value of type Integer"
163 -- >>> parseIniFileL "[MAIN]\nx = 72\n" (0, False) $ sectionL "MAIN" (fieldOfL "y" number _1)
164 -- Left "Missing field \"y\" in section \"MAIN\""
165 fieldOfL :: Text -> (Text -> Either String a) -> Lens s s a a -> SectionLensParser s ()
166 fieldOfL name rd lens = toLens lens $ fieldOf name rd
167
168
169 -- | A 'Lens'-aware variant of 'fieldMb': the 'Lens' argument names the
170 -- setter to use on the underlying value being modified.
171 --
172 -- >>> parseIniFileL "[MAIN]\nx = hello\n" (Just "def", False) $ sectionL "MAIN" (fieldMbL "x" _1)
173 -- Right (Just "hello",False)
174 -- >>> parseIniFileL "[MAIN]\nx = hello\n" (Just "def", False) $ sectionL "MAIN" (fieldMbL "y" _1)
175 -- Right (Nothing,False)
176 fieldMbL :: Text -> Lens s s (Maybe Text) (Maybe Text) -> SectionLensParser s ()
177 fieldMbL name lens = toLens lens $ fieldMb name
178
179
180 -- | A 'Lens'-aware variant of 'fieldMbOf': the 'Lens' argument names the
181 -- setter to use on the underlying value being modified.
182 --
183 -- >>> parseIniFileL "[MAIN]\nx = 72\n" (Just 0, False) $ sectionL "MAIN" (fieldMbOfL "x" number _1)
184 -- Right (Just 72,False)
185 -- >>> parseIniFileL "[MAIN]\nx = hello\n" (Just 0, False) $ sectionL "MAIN" (fieldMbOfL "x" number _1)
186 -- Left "Line 2, in section \"MAIN\": Unable to parse \"hello\" as a value of type Integer"
187 -- >>> parseIniFileL "[MAIN]\nx = 72\n" (Just 0, False) $ sectionL "MAIN" (fieldMbOfL "y" number _1)
188 -- Right (Nothing,False)
189 fieldMbOfL :: Text -> (Text -> Either String a) -> Lens s s (Maybe a) (Maybe a) -> SectionLensParser s ()
190 fieldMbOfL name rd lens = toLens lens $ fieldMbOf name rd
191
192 -- | A 'Lens'-aware variant of 'field' which does nothing if a key
193 -- is absent. The 'Lens' argument names the setter to use on the
194 -- underlying value being modified.
195 --
196 -- >>> parseIniFileL "[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (fieldOptL "x" _1)
197 -- Right ("hello",False)
198 -- >>> parseIniFileL "[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (fieldOptL "y" _1)
199 -- Right ("def",False)
200 fieldOptL :: Text -> Lens s s Text Text -> SectionLensParser s ()
201 fieldOptL name lens = SectionLensParser $ do
202 rsMb <- lift (fieldMb name)
203 case rsMb of
204 Nothing -> return ()
205 Just rs -> tell $ Endo (set lens rs)
206
207 -- | A 'Lens'-aware variant of 'fieldOf', which does nothing if a key
208 -- is absent. The 'Lens' argument names the
209 -- setter to use on the underlying value being modified.
210 --
211 -- >>> parseIniFileL "[MAIN]\nx = 72\n" (0, False) $ sectionL "MAIN" (fieldOptOfL "x" number _1)
212 -- Right (72,False)
213 -- >>> parseIniFileL "[MAIN]\nx = hello\n" (0, False) $ sectionL "MAIN" (fieldOptOfL "x" number _1)
214 -- Left "Line 2, in section \"MAIN\": Unable to parse \"hello\" as a value of type Integer"
215 -- >>> parseIniFileL "[MAIN]\nx = 72\n" (0, False) $ sectionL "MAIN" (fieldOptOfL "y" number _1)
216 -- Right (0,False)
217 fieldOptOfL :: Text -> (Text -> Either String a) -> Lens s s a a -> SectionLensParser s ()
218 fieldOptOfL name rd lens = SectionLensParser $ do
219 rsMb <- lift (fieldMbOf name rd)
220 case rsMb of
221 Nothing -> return ()
222 Just rs -> tell $ Endo (set lens rs)
223
224 -- | A 'Lens'-aware variant of 'fieldDef': the 'Lens' argument names the
225 -- setter to use on the underlying value being modified.
226 --
227 -- >>> parseIniFileL "[MAIN]\nx = hello\n" ("orig", False) $ sectionL "MAIN" (fieldDefL "x" "def" _1)
228 -- Right ("hello",False)
229 -- >>> parseIniFileL "[MAIN]\nx = hello\n" ("orig", False) $ sectionL "MAIN" (fieldDefL "y" "def" _1)
230 -- Right ("def",False)
231 fieldDefL :: Text -> Text -> Lens s s Text Text -> SectionLensParser s ()
232 fieldDefL name def lens = toLens lens $ fieldDef name def
233
234 -- | A 'Lens'-aware variant of 'fieldDefOf': the 'Lens' argument names the
235 -- setter to use on the underlying value being modified.
236 --
237 -- >>> parseIniFileL "[MAIN]\nx = 72\n" (0, False) $ sectionL "MAIN" (fieldDefOfL "x" number 99 _1)
238 -- Right (72,False)
239 -- >>> parseIniFileL "[MAIN]\nx = hello\n" (0, False) $ sectionL "MAIN" (fieldDefOfL "x" number 99 _1)
240 -- Left "Line 2, in section \"MAIN\": Unable to parse \"hello\" as a value of type Integer"
241 -- >>> parseIniFileL "[MAIN]\nx = 72\n" (0, False) $ sectionL "MAIN" (fieldDefOfL "y" number 99 _1)
242 -- Right (99,False)
243 fieldDefOfL :: Text -> (Text -> Either String a) -> a -> Lens s s a a -> SectionLensParser s ()
244 fieldDefOfL name rd def lens = toLens lens $ fieldDefOf name rd def
245
246 -- | A 'Lens'-aware variant of 'fieldFlag': the 'Lens' argument names the
247 -- setter to use on the underlying value being modified.
248 --
249 -- >>> parseIniFileL "[MAIN]\nx = yes\n" ("def", False) $ sectionL "MAIN" (fieldFlagL "x" _2)
250 -- Right ("def",True)
251 -- >>> parseIniFileL "[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (fieldFlagL "x" _2)
252 -- Left "Line 2, in section \"MAIN\": Unable to parse \"hello\" as a boolean"
253 -- >>> parseIniFileL "[MAIN]\nx = yes\n" ("def", False) $ sectionL "MAIN" (fieldFlagL "y" _2)
254 -- Left "Missing field \"y\" in section \"MAIN\""
255 fieldFlagL :: Text -> Lens s s Bool Bool -> SectionLensParser s ()
256 fieldFlagL name lens = toLens lens $ fieldFlag name
257
258 -- | A 'Lens'-aware variant of 'fieldFlagDef': the 'Lens' argument names the
259 -- setter to use on the underlying value being modified.
260 --
261 -- >>> parseIniFileL "[MAIN]\nx = yes\n" ("def", False) $ sectionL "MAIN" (fieldFlagDefL "x" False _2)
262 -- Right ("def",True)
263 -- >>> parseIniFileL "[MAIN]\nx = hello\n" ("def", False) $ sectionL "MAIN" (fieldFlagDefL "x" False _2)
264 -- Left "Line 2, in section \"MAIN\": Unable to parse \"hello\" as a boolean"
265 -- >>> parseIniFileL "[MAIN]\nx = yes\n" ("def", False) $ sectionL "MAIN" (fieldFlagDefL "y" False _2)
266 -- Right ("def",False)
267 fieldFlagDefL :: Text -> Bool -> Lens s s Bool Bool -> SectionLensParser s ()
268 fieldFlagDefL name def lens = toLens lens $ fieldFlagDef name def
269
270
271 -- $main
272 -- This module is designed to be used with lenses, so that we can
273 -- start with a default configuration and gradually update it,
274 -- rather than construct a new value from scratch. Among other
275 -- things, this makes it nicer to section our API but keep all
276 -- the configuration together. Consider the same example code
277 -- that appears in the documentation for the "Data.Ini.Config"
278 -- module, that parses this kind of configuration:
279 --
280 -- > [NETWORK]
281 -- > host = example.com
282 -- > port = 7878
283 -- >
284 -- > # here is a comment
285 -- > [LOCAL]
286 -- > user = terry
287 --
288 -- In that example, we split the configuration into a @NetworkConfig@
289 -- and a @LocalConfig@ type to mirror the configuration file's use of
290 -- @[LOCAL]@ and @[NETWORK]@ sections, but we might want to keep the
291 -- configuration data type as a single flag record, in which case our
292 -- parsing code becomes more awkward:
293 --
294 -- > data Config = Config
295 -- > { _cfHost :: String
296 -- > , _cfPort :: Int
297 -- > , _cfUser :: Text
298 -- > } deriving (Eq, Show)
299 -- >
300 -- > -- this is not ideal
301 -- > configParser :: IniParser Config
302 -- > configParser = do
303 -- > (host, port) <- section "NETWORK" $ do
304 -- > host <- fieldOf "host" string
305 -- > port <- fieldOf "port" number
306 -- > return (host, port)
307 -- > user <- section "LOCAL" $ field "user"
308 -- > return (Config host port user)
309 --
310 -- We could also use repeated invocations of 'section', but this
311 -- also makes our parsing code a lot uglier and involves unnecessary
312 -- repetition of the @\"NETWORK\"@ literal:
313 --
314 -- > -- this is kind of ugly
315 -- > configParser :: IniParser Config
316 -- > configParser = do
317 -- > host <- section "NETWORK" $ fieldOf "host" string
318 -- > port <- section "NETWORK" $ fieldOf "port" number
319 -- > user <- section "LOCAL" $ field "user"
320 -- > return (Config host port user)
321 --
322 -- Assuming that we generate lenses for the @Config@ type above,
323 -- then we can use the lens-based combinators in this module to
324 -- write terser parsing code by providing which lens to update
325 -- along with each field:
326 --
327 -- > configParser :: IniLensParser Config ()
328 -- > configParser = do
329 -- > section "NETWORK" $ do
330 -- > cfHost .= fieldOf "host" string
331 -- > cfPort .= fieldOf "port" number
332 -- > section "LOCAL" $ do
333 -- > cfUser .= field "user"
334 --
335 -- One downside to this approach is that you need an existing
336 -- value of the configuration type to update, which might mean
337 -- filling in a dummy value with nonsense data, even for fields
338 -- which are obligatory in the configuration, but on the other
339 -- hand, this can make some parsing code much more flexible and
340 -- terse.
137137 -- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (field "x")
138138 -- Right "hello"
139139 -- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (field "y")
140 -- Left "Missing field \"y\" in section \"main\""
140 -- Left "Missing field \"y\" in section \"MAIN\""
141141 field :: Text -> SectionParser Text
142142 field name = SectionParser $ vValue `fmap` rawField name
143143
173173 -- fail.
174174 --
175175 -- >>> parseIniFile "[MAIN]\nx = 72\n" $ section "MAIN" (fieldMbOf "x" number)
176 -- Right 72
176 -- Right (Just 72)
177177 -- >>> parseIniFile "[MAIN]\nx = hello\n" $ section "MAIN" (fieldMbOf "x" number)
178178 -- Left "Line 2, in section \"MAIN\": Unable to parse \"hello\" as a value of type Integer"
179179 -- >>> parseIniFile "[MAIN]\nx = 72\n" $ section "MAIN" (fieldMbOf "y" number)
226226 -- >>> parseIniFile "[MAIN]\nx = yes\n" $ section "MAIN" (fieldFlag "x")
227227 -- Right True
228228 -- >>> parseIniFile "[MAIN]\nx = yes\n" $ section "MAIN" (fieldFlag "y")
229 -- Left "Missing field \"y\" in section \"main\""
229 -- Left "Missing field \"y\" in section \"MAIN\""
230230 fieldFlag :: Text -> SectionParser Bool
231231 fieldFlag name = fieldOf name flag
232232
248248 -- with a human-readable error message if reading fails.
249249 --
250250 -- >>> readable "(5, 7)" :: Either String (Int, Int)
251 -- Right (5, 7)
251 -- Right (5,7)
252252 -- >>> readable "hello" :: Either String (Int, Int)
253253 -- Left "Unable to parse \"hello\" as a value of type (Int,Int)"
254254 readable :: forall a. (Read a, Typeable a) => Text -> Either String a
294294 -- >>> flag "F"
295295 -- Right False
296296 -- >>> flag "That's a secret!"
297 -- Left "Unable to parse \"that's a secret!\" as a boolean"
297 -- Left "Unable to parse \"That's a secret!\" as a boolean"
298298 flag :: Text -> Either String Bool
299299 flag s = case T.toLower s of
300300 "true" -> Right True
307307 "n" -> Right False
308308 _ -> Left ("Unable to parse " ++ show s ++ " as a boolean")
309309
310
311 -- $setup
312 --
313 -- >>> :{
314 -- data NetworkConfig = NetworkConfig
315 -- { netHost :: String, netPort :: Int }
316 -- deriving (Eq, Show)
317 -- >>> :}
318 --
319 -- >>> :{
320 -- data LocalConfig = LocalConfig
321 -- { localUser :: Text }
322 -- deriving (Eq, Show)
323 -- >>> :}
324 --
325 -- >>> :{
326 -- data Config = Config
327 -- { cfNetwork :: NetworkConfig, cfLocal :: Maybe LocalConfig }
328 -- deriving (Eq, Show)
329 -- >>> :}
330 --
331 -- >>> :{
332 -- let configParser = do
333 -- netCf <- section "NETWORK" $ do
334 -- host <- fieldOf "host" string
335 -- port <- fieldOf "port" number
336 -- return NetworkConfig { netHost = host, netPort = port }
337 -- locCf <- sectionMb "LOCAL" $
338 -- LocalConfig <$> field "user"
339 -- return Config { cfNetwork = netCf, cfLocal = locCf }
340 -- >>> :}
341 --
342 -- >>> :{
343 -- let example = "[NETWORK]\nhost = example.com\nport = 7878\n\n# here is a comment\n[LOCAL]\nuser = terry\n"
344 -- >>> :}
310345
311346 -- $main
312347 -- The 'config-ini' library exports some simple monadic functions to
1 module Main where
2
3 import Test.DocTest (doctest)
4
5 main :: IO ()
6 main = do
7 doctest [ "src/Data/Ini/Config.hs", "-XOverloadedStrings" ]
8 doctest [ "src/Data/Ini/Config/Lens.hs", "-XRankNTypes", "-XOverloadedStrings" ]