4 | 4 |
{-# LANGUAGE ScopedTypeVariables #-}
|
5 | 5 |
{-# LANGUAGE ExistentialQuantification #-}
|
6 | 6 |
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
| 7 |
{-# LANGUAGE MultiWayIf #-}
|
7 | 8 |
|
8 | 9 |
module Data.Ini.Config.Bidir
|
9 | 10 |
(
|
|
11 | 12 |
|
12 | 13 |
-- * Parsing, Serializing, and Updating Files
|
13 | 14 |
-- $using
|
14 | |
parseIniFile
|
15 | |
, emitIniFile
|
| 15 |
-- parseIniFile
|
| 16 |
emitIniFile
|
16 | 17 |
, UpdatePolicy(..)
|
17 | 18 |
, UpdateCommentPolicy(..)
|
18 | 19 |
, defaultUpdatePolicy
|
19 | |
, updateIniFile
|
| 20 |
, Ini
|
| 21 |
, ini
|
| 22 |
, parseIni
|
| 23 |
, getIniText
|
| 24 |
, getIniValue
|
| 25 |
, updateIni
|
| 26 |
, setIniUpdatePolicy
|
| 27 |
|
20 | 28 |
-- * Bidirectional Parser Types
|
21 | 29 |
-- $types
|
22 | 30 |
, IniSpec
|
23 | 31 |
, SectionSpec
|
| 32 |
|
24 | 33 |
-- * Section-Level Parsing
|
25 | 34 |
-- $sections
|
26 | 35 |
, section
|
| 36 |
|
27 | 37 |
-- * Field-Level Parsing
|
28 | 38 |
-- $fields
|
29 | 39 |
, FieldDescription
|
|
34 | 44 |
, comment
|
35 | 45 |
, placeholderValue
|
36 | 46 |
, skipIfMissing
|
| 47 |
|
37 | 48 |
-- * FieldValues
|
38 | 49 |
-- $fieldvalues
|
39 | 50 |
, FieldValue(..)
|
|
44 | 55 |
, readable
|
45 | 56 |
, listWithSeparator
|
46 | 57 |
, pairWithSeparator
|
| 58 |
|
47 | 59 |
-- * Miscellaneous Helpers
|
48 | 60 |
-- $misc
|
49 | 61 |
, (&)
|
50 | 62 |
, Lens
|
| 63 |
|
51 | 64 |
) where
|
52 | 65 |
|
53 | 66 |
import Control.Monad.Trans.State.Strict (State, runState, modify)
|
|
67 | 80 |
|
68 | 81 |
import Data.Ini.Config.Raw
|
69 | 82 |
|
70 | |
-- * Utility functions
|
| 83 |
-- * Utility functions + lens stuffs
|
71 | 84 |
|
72 | 85 |
-- | This is a
|
73 | 86 |
-- <https://hackage.haskell.org/package/lens lens>-compatible
|
74 | 87 |
-- type alias
|
75 | 88 |
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
|
| 89 |
|
| 90 |
-- These are some inline reimplementations of "lens" operators. We
|
| 91 |
-- need the identity functor to implement 'set':
|
| 92 |
newtype I a = I { fromI :: a }
|
| 93 |
instance Functor I where fmap f (I x) = I (f x)
|
| 94 |
|
| 95 |
set :: Lens s t a b -> b -> s -> t
|
| 96 |
set lns x a = fromI (lns (const (I x)) a)
|
| 97 |
|
| 98 |
-- ... and we need the const functor to implement 'get':
|
| 99 |
newtype C a b = C { fromC :: a }
|
| 100 |
instance Functor (C a) where fmap _ (C x) = C x
|
| 101 |
|
| 102 |
get :: Lens s t a b -> s -> a
|
| 103 |
get lns a = fromC (lns C a)
|
76 | 104 |
|
77 | 105 |
lkp :: NormalizedText -> Seq (NormalizedText, a) -> Maybe a
|
78 | 106 |
lkp t = fmap snd . F.find (\ (t', _) -> t' == t)
|
|
92 | 120 |
infixl 1 &
|
93 | 121 |
#endif
|
94 | 122 |
|
| 123 |
-- * The 'Ini' type
|
| 124 |
|
| 125 |
-- | An 'Ini' is an abstract representation of an INI file, including
|
| 126 |
-- both its textual representation and the Haskell value it
|
| 127 |
-- represents.
|
| 128 |
data Ini s = Ini
|
| 129 |
{ iniSpec :: Spec s
|
| 130 |
, iniCurr :: s
|
| 131 |
, iniDef :: s
|
| 132 |
, iniLast :: Maybe RawIni
|
| 133 |
, iniPol :: UpdatePolicy
|
| 134 |
}
|
| 135 |
|
| 136 |
-- | Create a basic 'Ini' value from a default value and a spec.
|
| 137 |
ini :: s -> IniSpec s () -> Ini s
|
| 138 |
ini def (IniSpec spec) = Ini
|
| 139 |
{ iniSpec = runBidirM spec
|
| 140 |
, iniCurr = def
|
| 141 |
, iniDef = def
|
| 142 |
, iniLast = Nothing
|
| 143 |
, iniPol = defaultUpdatePolicy
|
| 144 |
}
|
| 145 |
|
| 146 |
-- | Get the underlying Haskell value associated with the 'Ini'.
|
| 147 |
getIniValue :: Ini s -> s
|
| 148 |
getIniValue = iniCurr
|
| 149 |
|
| 150 |
-- | Get the textual representation of an 'Ini' value. If this 'Ini'
|
| 151 |
-- value is the result of 'parseIni', then it will attempt to retain
|
| 152 |
-- the textual characteristics of the parsed version as much as
|
| 153 |
-- possible (e.g. by retaining comments, ordering, and whitespace in a
|
| 154 |
-- way that will minimize the overall diff footprint.) If the 'Ini'
|
| 155 |
-- value was created directly from a value and a specification, then
|
| 156 |
-- it will pretty-print an initial version of the file with the
|
| 157 |
-- comments and placeholder text specified in the spec.
|
| 158 |
getIniText :: Ini s -> Text
|
| 159 |
getIniText = printRawIni . getRawIni
|
| 160 |
|
| 161 |
-- | Get the underlying 'RawIni' value for the file.
|
| 162 |
getRawIni :: Ini s -> RawIni
|
| 163 |
getRawIni (Ini { iniLast = Just raw }) = raw
|
| 164 |
getRawIni (Ini { iniCurr = s
|
| 165 |
, iniSpec = spec
|
| 166 |
}) = emitIniFile s spec
|
| 167 |
|
| 168 |
-- | Parse a textual representation of an 'Ini' file. If the file is
|
| 169 |
-- malformed or if an obligatory field is not found, this will produce
|
| 170 |
-- a human-readable error message. If an optional field is not found,
|
| 171 |
-- then it will fall back on the existing value contained in the
|
| 172 |
-- provided 'Ini' structure.
|
| 173 |
parseIni :: Text -> Ini s -> Either String (Ini s)
|
| 174 |
parseIni t i@Ini { iniSpec = spec
|
| 175 |
, iniCurr = def
|
| 176 |
} = do
|
| 177 |
RawIni raw <- parseRawIni t
|
| 178 |
s <- parseSections def (Seq.viewl spec) raw
|
| 179 |
return $ i
|
| 180 |
{ iniCurr = s
|
| 181 |
, iniLast = Just (RawIni raw)
|
| 182 |
}
|
| 183 |
|
| 184 |
-- | Update the internal value of an 'Ini' file. If this 'Ini' value
|
| 185 |
-- is the result of 'parseIni', then the resulting 'Ini' value will
|
| 186 |
-- attempt to retain the textual characteristics of the parsed version
|
| 187 |
-- as much as possible (e.g. by retaining comments, ordering, and
|
| 188 |
-- whitespace in a way that will minimize the overall diff footprint.)
|
| 189 |
updateIni :: s -> Ini s -> Ini s
|
| 190 |
updateIni new i =
|
| 191 |
case doUpdateIni new i of
|
| 192 |
Left err -> error err
|
| 193 |
Right i' -> i'
|
| 194 |
|
| 195 |
setIniUpdatePolicy :: UpdatePolicy -> Ini s -> Ini s
|
| 196 |
setIniUpdatePolicy pol i = i { iniPol = pol }
|
| 197 |
|
95 | 198 |
-- * Type definitions
|
96 | 199 |
|
97 | |
-- | A value of type "FieldValue" packages up a parser and emitter
|
| 200 |
-- | A value of type 'FieldValue' packages up a parser and emitter
|
98 | 201 |
-- function into a single value. These are used for bidirectional
|
99 | 202 |
-- parsing and emitting of the value of a field.
|
100 | 203 |
data FieldValue a = FieldValue
|
|
103 | 206 |
-- the parser fails, then the string will be shown as an error
|
104 | 207 |
-- message to the user.
|
105 | 208 |
, fvEmit :: a -> Text
|
106 | |
-- ^ The serializer to use when serializing a value into an INI file.
|
| 209 |
-- ^ The function to use when serializing a value into an INI
|
| 210 |
-- file.
|
107 | 211 |
}
|
108 | 212 |
|
109 | 213 |
-- This is actually being used as a writer monad, but using a state
|
|
114 | 218 |
|
115 | 219 |
runBidirM :: BidirM s a -> Seq s
|
116 | 220 |
runBidirM = snd . flip runState Seq.empty
|
| 221 |
|
| 222 |
type Spec s = Seq (Section s)
|
117 | 223 |
|
118 | 224 |
-- | An 'IniSpec' value represents the structure of an entire
|
119 | 225 |
-- INI-format file in a declarative way. The @s@ parameter represents
|
|
129 | 235 |
newtype SectionSpec s a = SectionSpec (BidirM (Field s) a)
|
130 | 236 |
deriving (Functor, Applicative, Monad)
|
131 | 237 |
|
| 238 |
-- * Sections
|
| 239 |
|
132 | 240 |
-- | Define the specification of a top-level INI section.
|
133 | 241 |
section :: Text -> SectionSpec s () -> IniSpec s ()
|
134 | 242 |
section name (SectionSpec mote) = IniSpec $ do
|
|
141 | 249 |
isOptional (FieldMb _ fd) = fdSkipIfMissing fd
|
142 | 250 |
|
143 | 251 |
data Section s = Section NormalizedText (Seq (Field s)) Bool
|
| 252 |
|
| 253 |
-- * Fields
|
144 | 254 |
|
145 | 255 |
-- | A "Field" is a description of
|
146 | 256 |
data Field s
|
|
169 | 279 |
, fdSkipIfMissing :: Bool
|
170 | 280 |
}
|
171 | 281 |
|
| 282 |
-- ** Field operators
|
| 283 |
|
172 | 284 |
{- |
|
173 | 285 |
Associate a field description with a field. If this field
|
174 | 286 |
is not present when parsing, it will attempt to fall back
|
|
192 | 304 |
(.=?) :: Eq t => Lens s s (Maybe t) (Maybe t) -> FieldDescription t -> SectionSpec s ()
|
193 | 305 |
l .=? f = SectionSpec $ modify (Seq.|> fd)
|
194 | 306 |
where fd = FieldMb l f
|
| 307 |
|
| 308 |
-- ** Field metadata
|
195 | 309 |
|
196 | 310 |
{- |
|
197 | 311 |
Associate a multiline comment with a "FieldDescription". When
|
|
230 | 344 |
infixr 0 .=
|
231 | 345 |
infixr 0 .=?
|
232 | 346 |
|
| 347 |
-- ** Creating fields
|
| 348 |
|
233 | 349 |
-- | Create a description of a field by a combination of the name of
|
234 | 350 |
-- the field and a "FieldValue" describing how to parse and emit
|
235 | 351 |
-- values associated with that field.
|
|
245 | 361 |
-- | Create a description of a 'Bool'-valued field.
|
246 | 362 |
flag :: Text -> FieldDescription Bool
|
247 | 363 |
flag name = field name bool
|
| 364 |
|
| 365 |
-- ** FieldValues
|
248 | 366 |
|
249 | 367 |
-- | A "FieldValue" for parsing and serializing values according to
|
250 | 368 |
-- the logic of the "Read" and "Show" instances for that type,
|
|
320 | 438 |
, fvEmit = \ (x, y) -> fvEmit left x <> sep <> fvEmit right y
|
321 | 439 |
}
|
322 | 440 |
|
323 | |
-- | Provided an initial value and an 'IniSpec' describing the
|
324 | |
-- structure of an INI file, parse a 'Text' value as an INI file,
|
325 | |
-- update the initial value corresponding to the fields in the INI
|
326 | |
-- file, and then return the modified value.
|
327 | |
parseIniFile :: s -> IniSpec s () -> Text -> Either String s
|
328 | |
parseIniFile def (IniSpec mote) t =
|
329 | |
let spec = runBidirM mote
|
330 | |
in case parseIni t of
|
331 | |
Left err -> Left err
|
332 | |
Right (Ini ini) -> runSpec def (Seq.viewl spec) ini
|
| 441 |
-- * Parsing INI files
|
333 | 442 |
|
334 | 443 |
-- Are you reading this source code? It's not even that gross
|
335 | 444 |
-- yet. Just you wait. This is just the regular part. 'runSpec' is
|
336 | 445 |
-- easy: we walk the spec, and for each section, find the
|
337 | 446 |
-- corresponding section in the INI file and call runFields.
|
338 | |
runSpec :: s -> Seq.ViewL (Section s) -> Seq (NormalizedText, IniSection)
|
339 | |
-> Either String s
|
340 | |
runSpec s Seq.EmptyL _ = Right s
|
341 | |
runSpec s (Section name fs opt Seq.:< rest) ini
|
342 | |
| Just v <- lkp name ini = do
|
343 | |
s' <- runFields s (Seq.viewl fs) v
|
344 | |
runSpec s' (Seq.viewl rest) ini
|
345 | |
| opt = runSpec s (Seq.viewl rest) ini
|
| 447 |
parseSections
|
| 448 |
:: s
|
| 449 |
-> Seq.ViewL (Section s)
|
| 450 |
-> Seq (NormalizedText, IniSection)
|
| 451 |
-> Either String s
|
| 452 |
parseSections s Seq.EmptyL _ = Right s
|
| 453 |
parseSections s (Section name fs opt Seq.:< rest) i
|
| 454 |
| Just v <- lkp name i = do
|
| 455 |
s' <- parseFields s (Seq.viewl fs) v
|
| 456 |
parseSections s' (Seq.viewl rest) i
|
| 457 |
| opt = parseSections s (Seq.viewl rest) i
|
346 | 458 |
| otherwise = Left ("Unable to find section " ++ show name)
|
347 | |
|
348 | |
-- These are some inline reimplementations of "lens" operators. We
|
349 | |
-- need the identity functor to implement 'set':
|
350 | |
newtype I a = I { fromI :: a }
|
351 | |
instance Functor I where fmap f (I x) = I (f x)
|
352 | |
|
353 | |
set :: Lens s t a b -> b -> s -> t
|
354 | |
set lns x a = fromI (lns (const (I x)) a)
|
355 | |
|
356 | |
-- ... and we need the const functor to implement 'get':
|
357 | |
newtype C a b = C { fromC :: a }
|
358 | |
instance Functor (C a) where fmap _ (C x) = C x
|
359 | |
|
360 | |
get :: Lens s t a b -> s -> a
|
361 | |
get lns a = fromC (lns C a)
|
362 | 459 |
|
363 | 460 |
-- Now that we've got 'set', we can walk the field descriptions and
|
364 | 461 |
-- find them. There's some fiddly logic, but the high-level idea is
|
|
366 | 463 |
-- the provided parser and use the provided lens to add it to the
|
367 | 464 |
-- value. We have to decide what to do if it's not there, which
|
368 | 465 |
-- depends on lens metadata and whether it's an optional field or not.
|
369 | |
runFields :: s -> Seq.ViewL (Field s) -> IniSection -> Either String s
|
370 | |
runFields s Seq.EmptyL _ = Right s
|
371 | |
runFields s (Field l descr Seq.:< fs) sect
|
| 466 |
parseFields :: s -> Seq.ViewL (Field s) -> IniSection -> Either String s
|
| 467 |
parseFields s Seq.EmptyL _ = Right s
|
| 468 |
parseFields s (Field l descr Seq.:< fs) sect
|
372 | 469 |
| Just v <- lkp (fdName descr) (isVals sect) = do
|
373 | 470 |
value <- fvParse (fdValue descr) (T.strip (vValue v))
|
374 | |
runFields (set l value s) (Seq.viewl fs) sect
|
| 471 |
parseFields (set l value s) (Seq.viewl fs) sect
|
375 | 472 |
| fdSkipIfMissing descr =
|
376 | |
runFields s (Seq.viewl fs) sect
|
| 473 |
parseFields s (Seq.viewl fs) sect
|
377 | 474 |
| otherwise = Left ("Unable to find field " ++ show (fdName descr))
|
378 | |
runFields s (FieldMb l descr Seq.:< fs) sect
|
| 475 |
parseFields s (FieldMb l descr Seq.:< fs) sect
|
379 | 476 |
| Just v <- lkp (fdName descr) (isVals sect) = do
|
380 | 477 |
value <- fvParse (fdValue descr) (T.strip (vValue v))
|
381 | |
runFields (set l (Just value) s) (Seq.viewl fs) sect
|
| 478 |
parseFields (set l (Just value) s) (Seq.viewl fs) sect
|
382 | 479 |
| otherwise =
|
383 | |
runFields (set l Nothing s) (Seq.viewl fs) sect
|
| 480 |
parseFields (set l Nothing s) (Seq.viewl fs) sect
|
384 | 481 |
|
385 | 482 |
-- | Serialize a value as an INI file according to a provided
|
386 | 483 |
-- 'IniSpec'.
|
387 | |
emitIniFile :: s -> IniSpec s () -> Text
|
388 | |
emitIniFile s (IniSpec mote) =
|
389 | |
let spec = runBidirM mote in
|
390 | |
printIni $ Ini $ fmap (\ (Section name fs _) ->
|
391 | |
(name, toSection s (actualText name) fs)) spec
|
| 484 |
emitIniFile :: s -> Spec s -> RawIni
|
| 485 |
emitIniFile s spec =
|
| 486 |
RawIni $
|
| 487 |
fmap (\ (Section name fs _) ->
|
| 488 |
(name, toSection s (actualText name) fs)) spec
|
392 | 489 |
|
393 | 490 |
mkComments :: Seq Text -> Seq BlankLine
|
394 | 491 |
mkComments comments =
|
|
476 | 573 |
-- file, while newly added fields (for example, fields which have
|
477 | 574 |
-- been changed from a default value) will be added to the end of the
|
478 | 575 |
-- section in which they appear.
|
479 | |
updateIniFile :: s -> IniSpec s () -> Text -> UpdatePolicy -> Either String Text
|
480 | |
updateIniFile s (IniSpec mote) t pol =
|
481 | |
let spec = runBidirM mote
|
482 | |
in case parseIni t of
|
483 | |
Left err -> Left ("Error parsing existing INI file: " ++ err)
|
484 | |
Right (Ini ini) -> do
|
485 | |
ini' <- updateIniSections s ini spec pol
|
486 | |
return (printIni (Ini ini'))
|
487 | |
|
488 | |
updateIniSections :: s -> Seq (NormalizedText, IniSection)
|
489 | |
-> Seq (Section s)
|
490 | |
-> UpdatePolicy
|
491 | |
-> Either String (Seq (NormalizedText, IniSection))
|
492 | |
updateIniSections s sections fields pol = do
|
| 576 |
--doUpdateIni :: s -> s -> Spec s -> RawIni -> UpdatePolicy -> Either String (Ini s)
|
| 577 |
doUpdateIni :: s -> Ini s -> Either String (Ini s)
|
| 578 |
doUpdateIni s i@Ini { iniSpec = spec
|
| 579 |
, iniDef = def
|
| 580 |
, iniPol = pol
|
| 581 |
} = do -- spec (RawIni ini) pol = do
|
| 582 |
let RawIni ini' = getRawIni i
|
| 583 |
res <- updateSections s def ini' spec pol
|
| 584 |
return $ i
|
| 585 |
{ iniCurr = s
|
| 586 |
, iniLast = Just (RawIni res)
|
| 587 |
}
|
| 588 |
|
| 589 |
updateSections
|
| 590 |
:: s
|
| 591 |
-> s
|
| 592 |
-> Seq (NormalizedText, IniSection)
|
| 593 |
-> Seq (Section s)
|
| 594 |
-> UpdatePolicy
|
| 595 |
-> Either String (Seq (NormalizedText, IniSection))
|
| 596 |
updateSections s def sections fields pol = do
|
| 597 |
-- First, we process all the sections that actually appear in the
|
| 598 |
-- INI file in order
|
493 | 599 |
existingSections <- F.for sections $ \ (name, sec) -> do
|
494 | 600 |
let err = (Left ("Unexpected top-level section: " ++ show name))
|
495 | 601 |
Section _ spec _ <- maybe err Right
|
496 | 602 |
(F.find (\ (Section n _ _) -> n == name) fields)
|
497 | |
newVals <- updateIniSection s (isVals sec) spec pol
|
| 603 |
newVals <- updateFields s (isVals sec) spec pol
|
498 | 604 |
return (name, sec { isVals = newVals })
|
| 605 |
-- And then
|
499 | 606 |
let existingSectionNames = fmap fst existingSections
|
500 | 607 |
newSections <- F.for fields $
|
501 | |
\ (Section nm spec isOpt) ->
|
502 | |
if nm `elem` existingSectionNames
|
503 | |
then return mempty
|
504 | |
else return (Seq.singleton (nm, IniSection (actualText nm) mempty 0 0 mempty))
|
| 608 |
\ (Section nm spec _) ->
|
| 609 |
if | nm `elem` existingSectionNames -> return mempty
|
| 610 |
| otherwise ->
|
| 611 |
let rs = emitNewFields s def spec
|
| 612 |
in if Seq.null rs
|
| 613 |
then return mempty
|
| 614 |
else return $ Seq.singleton
|
| 615 |
( nm
|
| 616 |
, IniSection (actualText nm) rs 0 0 mempty
|
| 617 |
)
|
505 | 618 |
return (existingSections <> F.asum newSections)
|
506 | 619 |
|
507 | |
updateIniSection :: s -> Seq (NormalizedText, IniValue) -> Seq (Field s)
|
| 620 |
-- We won't emit a section if everything in the section is also
|
| 621 |
-- missing
|
| 622 |
emitNewFields :: s -> s -> Seq (Field s) -> Seq (NormalizedText, IniValue)
|
| 623 |
emitNewFields s def fields = go (Seq.viewl fields) where
|
| 624 |
go EmptyL = Seq.empty
|
| 625 |
go (Field l d :< fs)
|
| 626 |
-- If a field is not present but is also the same as the default,
|
| 627 |
-- then we can safely omit it
|
| 628 |
| get l s == get l def = go (Seq.viewl fs)
|
| 629 |
-- otherwise, we should add it to the result
|
| 630 |
| otherwise =
|
| 631 |
let new = ( fdName d
|
| 632 |
, IniValue
|
| 633 |
{ vLineNo = 0
|
| 634 |
, vName = actualText (fdName d)
|
| 635 |
, vValue = fvEmit (fdValue d) (get l s)
|
| 636 |
, vComments = mempty
|
| 637 |
, vCommentedOut = False
|
| 638 |
, vDelimiter = '='
|
| 639 |
}
|
| 640 |
)
|
| 641 |
in new <| go (Seq.viewl fs)
|
| 642 |
go (FieldMb l d :< fs) =
|
| 643 |
case get l s of
|
| 644 |
Nothing -> go (Seq.viewl fs)
|
| 645 |
Just v ->
|
| 646 |
let new = ( fdName d
|
| 647 |
, IniValue
|
| 648 |
{ vLineNo = 0
|
| 649 |
, vName = actualText (fdName d)
|
| 650 |
, vValue = fvEmit (fdValue d) v
|
| 651 |
, vComments = mempty
|
| 652 |
, vCommentedOut = False
|
| 653 |
, vDelimiter = '='
|
| 654 |
}
|
| 655 |
)
|
| 656 |
in new <| go (Seq.viewl fs)
|
| 657 |
|
| 658 |
|
| 659 |
updateFields :: s -> Seq (NormalizedText, IniValue) -> Seq (Field s)
|
508 | 660 |
-> UpdatePolicy -> Either String (Seq (NormalizedText, IniValue))
|
509 | |
updateIniSection s values fields pol = go (Seq.viewl values) fields
|
| 661 |
updateFields s values fields pol = go (Seq.viewl values) fields
|
510 | 662 |
where go ((t, val) :< vs) fs =
|
511 | 663 |
-- For each field, we need to fetch the description of the
|
512 | 664 |
-- field in the spec
|
|
565 | 717 |
-- were left out, but if we have any non-optional fields left
|
566 | 718 |
-- over, then we definitely need to include them.
|
567 | 719 |
go EmptyL fs = return (finish (Seq.viewl fs))
|
568 | |
finish (f@(Field l _) :< fs)
|
| 720 |
finish (f@(Field {}) :< fs)
|
569 | 721 |
| updateAddOptionalFields pol
|
570 | 722 |
, Just val <- mkValue (fieldName f) f '=' =
|
571 | 723 |
(fieldName f, val) <| finish (Seq.viewl fs)
|
|
604 | 756 |
Nothing -> Nothing
|
605 | 757 |
|
606 | 758 |
|
607 | |
-- DELETE ME LATER
|
608 | |
|
609 | |
lens :: (s -> a) -> (b -> s -> t) -> Lens s t a b
|
610 | |
lens gt st f a = (`st` a) `fmap` f (gt a)
|
611 | |
|
612 | |
_1 :: Lens (a, b) (a, b) a a
|
613 | |
_1 = lens fst (\ a (_, b) -> (a, b))
|
614 | |
|
615 | |
_2 :: Lens (a, b) (a, b) b b
|
616 | |
_2 = lens snd (\ b (a, _) -> (a, b))
|
617 | |
|
618 | |
|
619 | |
|
620 | 759 |
{- $main
|
621 | 760 |
|
622 | 761 |
This module presents an alternate API for parsing INI files. Unlike
|
|
704 | 843 |
& 'skipIfMissing'
|
705 | 844 |
cfPost '.=' 'field' \"port\" 'number'
|
706 | 845 |
& 'comment' [\"The port number\"]
|
707 | |
& 'defaultValue' 9999
|
708 | 846 |
'sectionOpt' \"LOCAL\" $ do
|
709 | 847 |
cfUser '.=?' 'field' \"user\" 'text'
|
710 | 848 |
@
|