gdritter repos config-ini / 0109688
Fix syntax highlighting Getty Ritter 7 years ago
1 changed file(s) with 9 addition(s) and 9 deletion(s). Collapse all Expand all
11 # `config-ini`
22
3 [![Hackage](https://img.shields.io/hackage/v/config-ini.svg)](https://hackage.haskell.org/package/config-ini)
3 [![Hackage](https://img.shields.io/hackage/v/config-ini.svg)](https://hackagehaskell.org/package/config-ini)
44
55 The `config-ini` library is a Haskell library for doing elementary INI file parsing in a quick and painless way.
66
2020
2121 The combinators provided here are designed to write quick and idiomatic parsers for basic INI files. Sections are parsed by `IniParser` computations, like `section` and its variations, while the fields within sections are parsed by `SectionParser` computations, like `field` and its variations. If we want to parse an INI file like the one above, treating the entire `LOCAL` section as optional, we can write it like this:
2222
23 ~~~.haskell
23 ~~~haskell
2424 data Config = Config
2525 { cfNetwork :: NetworkConfig
2626 , cfLocal :: Maybe LocalConfig
4848
4949 We can run our computation with `parseIniFile`, which, when run on our example file above, would produce the following:
5050
51 ~~~.haskell
51 ~~~haskell
5252 >>> parseIniFile example configParser
5353 Right (Config {cfNetwork = NetworkConfig {netHost = "example.com", netPort = 7878}, cfLocal = Just (LocalConfig {localUser = "terry"})})
5454 ~~~
5757
5858 The above example had an INI file split into two sections (`NETWORK` and `LOCAL`) and a data type with a corresponding structure (containing a `NetworkConfig` and `Maybe LocalConfig` field), which allowed each `section`-level parser to construct a chunk of the configuration and then combine them. This works well if our configuration file has the same structure as our data type, but that might not be what we want. Let's imagine we want to construct our `Config` type as a flat record like this:
5959
60 ~~~.haskell
60 ~~~haskell
6161 data Config = Config
6262 { _cfHost :: String
6363 , _cfPort :: Int
6767
6868 In this case, we can't construct a `Config` value until we've parsed all three fields in two distinct subsections. One way of doing this is to return the intermediate values from our `section` parsers and construct the `Config` value at the end, once we have all three of its fields:
6969
70 ~~~.haskell
70 ~~~haskell
7171 configParser :: IniParser Config
7272 configParser = do
7373 (host, port) <- section "NETWORK" $ do
8080
8181 This is unfortunately awkward and repetitive. An alternative is to flatten it out by repeating invocations of `section` like below, but this has its own problems, such as unnecessary repetition of the `"NETWORK"` string literal, unnecessarily repetitive lookups, and general verbosity:
8282
83 ~~~.haskell
83 ~~~haskell
8484 configParser :: IniParser Config
8585 configParser = do
8686 host <- section "NETWORK" $ fieldOf "host" string
9191
9292 In situations like these, you can instead use the `Data.Ini.Config.Bidir` module, which provides a slightly different abstraction: the functions exported by this module assume that you start with a default configuration value, and parsing a field allows you to _update_ that configuration with the value of a field. The monads exported by this module have an extra type parameter that represents the type of the value being updated. The easiest way to use this module is by combining lenses with the `.=` and `.=?` operators, which take a lens and a description of a field, and produce a `SectionSpec` value that uses the provided lens to update the underlying type when parsing:
9393
94 ~~~.haskell
94 ~~~haskell
9595 makeLenses ''Config
9696
9797 configParser :: IniSpec Config ()
105105
106106 In order to use this as a parser, we will need to provide an existing value of `Config` so we can apply our updates to it. We combine the `IniSpec` defined above with a default config
107107
108 ~~~.haskell
108 ~~~haskell
109109 configIni :: Ini Config
110110 configIni =
111111 let defConfig = Config "localhost" 8080 Nothing
117117
118118 This approach gives us other advantages, too. Each of the defined fields can be associated with some various pieces of metadata, marking them as optional for the purpose of parsing or associating a comment with them.
119119
120 ~~~.haskell
120 ~~~haskell
121121
122122 configParser' :: IniSpec Config ()
123123 configParser' = do