config-ini
The config-ini
library is a Haskell library for doing elementary INI file parsing in a quick and painless way. You can find the source code on Github and the full documentation for the library on Hackage.
There are two ways of using the library: one of them involves a traditional monadic DSL which parses field-by-field, and the other is a powerful bidirectional DSL which allows you to use the same declarative specification to parse, serialize, and diff-minimally update INI files.
Consider the following basic INI file:
[NETWORK]
host = example.com
port = 7878
# here is a comment
[LOCAL]
user = terry
We want to parse this into a Haskell data structure defined using this type, with its associated lenses:
data Config = Config
{ _cfHost :: String
, _cfPort :: Int
, _cfUser :: Maybe Text
} deriving (Eq, Show)
makeLenses ''Config
Using config-ini
's basic API, we can extract the fields using basic functions like fieldOf
and section
, which lookup and deserialize values from the INI file, and construct a Config
value from those:
configParser :: IniParser Config
configParser = do
(host, port) <- section "NETWORK" $ do
host <- fieldOf "host" string
port <- fieldOf "port" number
pure (host, port)
user <- sectionMb "LOCAL" $ field "user"
return Config
{ _cfHost = host
, _cfPort = port
, _cfUser = user
}
In order to use config-ini
's bidirectional API, we instead have to use the generated lenses and associate them with descriptions of how to look up those individual fields, like this:
configSpec :: IniSpec Config ()
configSpec = do
section "NETWORK" $ do
cfHost .= field "host" string
cfPort .= field "port" number
section "LOCAL" $ do
cfUser .=? field "user"
This defines a specification that we can use to create a parser, but in order to actually construct a value, we need a default Config
value to supply to it, which we pass along with the IniSpec
to the ini
function, which gives us a value of type Ini
, from which we can derive a parser:
configIni :: Ini Config
configIni =
let defConfig = Config "localhost" 8080 Nothing
in ini defConfig configSpec
myParseIni :: Text -> Either String Config
myParseIni t = fmap getIniValue (parseIni t configIni)
However, we can also serialize the Ini
value, which takes the default value and turns it into a textual INI file. We can also use the Ini
value to parse a file into a new Ini
value, update it, and then re-serialize: the Ini
value will contain all of the "structural" information about the file, like whitespace and comments and ordering, which means that the update is diff-minimal: all unchanged values will remain in the same order, changed value will be modified in-place with retained comments, and new values will appear grouped together at the end of their sections.