| 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.
|