Fix syntax highlighting
Getty Ritter
7 years ago
1 | 1 | # `config-ini` |
2 | 2 | |
3 |
[![Hackage](https://img.shields.io/hackage/v/config-ini.svg)](https://hackage |
|
3 | [![Hackage](https://img.shields.io/hackage/v/config-ini.svg)](https://hackagehaskell.org/package/config-ini) | |
4 | 4 | |
5 | 5 | The `config-ini` library is a Haskell library for doing elementary INI file parsing in a quick and painless way. |
6 | 6 | |
20 | 20 | |
21 | 21 | 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: |
22 | 22 | |
23 |
~~~ |
|
23 | ~~~haskell | |
24 | 24 | data Config = Config |
25 | 25 | { cfNetwork :: NetworkConfig |
26 | 26 | , cfLocal :: Maybe LocalConfig |
48 | 48 | |
49 | 49 | We can run our computation with `parseIniFile`, which, when run on our example file above, would produce the following: |
50 | 50 | |
51 |
~~~ |
|
51 | ~~~haskell | |
52 | 52 | >>> parseIniFile example configParser |
53 | 53 | Right (Config {cfNetwork = NetworkConfig {netHost = "example.com", netPort = 7878}, cfLocal = Just (LocalConfig {localUser = "terry"})}) |
54 | 54 | ~~~ |
57 | 57 | |
58 | 58 | 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: |
59 | 59 | |
60 |
~~~ |
|
60 | ~~~haskell | |
61 | 61 | data Config = Config |
62 | 62 | { _cfHost :: String |
63 | 63 | , _cfPort :: Int |
67 | 67 | |
68 | 68 | 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: |
69 | 69 | |
70 |
~~~ |
|
70 | ~~~haskell | |
71 | 71 | configParser :: IniParser Config |
72 | 72 | configParser = do |
73 | 73 | (host, port) <- section "NETWORK" $ do |
80 | 80 | |
81 | 81 | 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: |
82 | 82 | |
83 |
~~~ |
|
83 | ~~~haskell | |
84 | 84 | configParser :: IniParser Config |
85 | 85 | configParser = do |
86 | 86 | host <- section "NETWORK" $ fieldOf "host" string |
91 | 91 | |
92 | 92 | 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: |
93 | 93 | |
94 |
~~~ |
|
94 | ~~~haskell | |
95 | 95 | makeLenses ''Config |
96 | 96 | |
97 | 97 | configParser :: IniSpec Config () |
105 | 105 | |
106 | 106 | 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 |
107 | 107 | |
108 |
~~~ |
|
108 | ~~~haskell | |
109 | 109 | configIni :: Ini Config |
110 | 110 | configIni = |
111 | 111 | let defConfig = Config "localhost" 8080 Nothing |
117 | 117 | |
118 | 118 | 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. |
119 | 119 | |
120 |
~~~ |
|
120 | ~~~haskell | |
121 | 121 | |
122 | 122 | configParser' :: IniSpec Config () |
123 | 123 | configParser' = do |