Declarative INI data manipulation for OCaml.
Init provides bidirectional codecs for INI configuration files following Python's configparser semantics. This ensures configuration files are compatible with Python tools while providing a type-safe OCaml interface.
Quick Start
INI files consist of sections (in square brackets) containing key-value pairs. Here's a simple configuration file:
[server]
host = localhost
port = 8080
debug = false
[database]
connection_string = postgres://localhost/mydb
pool_size = 10To decode this, define an OCaml type and create a codec:
(* Define the OCaml types *)
type server = { host : string; port : int; debug : bool }
type database = { connection : string; pool_size : int }
type config = { server : server; database : database }
(* Create section codecs *)
let server_codec = Init.Section.(
obj (fun host port debug -> { host; port; debug })
|> mem "host" Init.string ~enc:(fun s -> s.host)
|> mem "port" Init.int ~enc:(fun s -> s.port)
|> mem "debug" Init.bool ~dec_absent:false ~enc:(fun s -> s.debug)
|> finish
)
let database_codec = Init.Section.(
obj (fun connection pool_size -> { connection; pool_size })
|> mem "connection_string" Init.string ~enc:(fun d -> d.connection)
|> mem "pool_size" Init.int ~dec_absent:5 ~enc:(fun d -> d.pool_size)
|> finish
)
(* Create the document codec *)
let config_codec = Init.Document.(
obj (fun server database -> { server; database })
|> section "server" server_codec ~enc:(fun c -> c.server)
|> section "database" database_codec ~enc:(fun c -> c.database)
|> finish
)
(* Use Init_bytesrw to decode *)
let config = Init_bytesrw.decode_string config_codec ini_stringRead the cookbook for more examples.
Related Libraries
Init_bytesrw- Byte-level I/O operations for string and file parsingInit_eio- Eio integration for async file and flow operations
Key Concepts
The INI Format
An INI file is a simple text format for configuration data:
- Sections are named groups enclosed in square brackets:
[section]. Section names are case-sensitive and can contain spaces. - Options (also called keys) are name-value pairs separated by
=or:. Option names are case-insensitive by default. - Values are strings. All data is stored as strings and converted to other types (int, bool, etc.) by codecs during decoding.
- Comments start with
#or;at the beginning of a line. - Multiline values are continued on indented lines.
The DEFAULT Section
The special [DEFAULT] section provides fallback values for all other sections. When an option is not found in a section, the parser looks in DEFAULT:
[DEFAULT]
base_dir = /opt/app
log_level = info
[production]
base_dir = /var/app
[development]
# Inherits base_dir from DEFAULT
log_level = debugValue Interpolation
Values can reference other values using interpolation syntax:
Basic interpolation (Python's ConfigParser default):
[paths]
base = /opt/app
data = %(base)s/data
logs = %(base)s/logsExtended interpolation (cross-section references):
[common]
base = /opt/app
[server]
data_dir = ${common:base}/dataSee Init_bytesrw.interpolation for configuration options.
Boolean Values
Following Python's configparser, these values are recognized as booleans (case-insensitive):
- True:
1,yes,true,on - False:
0,no,false,off
Use bool for Python-compatible parsing, or the stricter variants bool_01, bool_yesno, bool_truefalse, bool_onoff.
Case Sensitivity
By default (matching Python's behavior):
- Section names are case-sensitive:
[Server]and[server]are different sections. - Option names are case-insensitive:
Portandportrefer to the same option. Options are normalized to lowercase internally.
Library Architecture
The library is split into multiple packages:
- init (this module): Core types and codec combinators. No I/O dependencies.
- init.bytesrw (
Init_bytesrw): Parsing and encoding using bytesrw. - init.eio: File system integration with Eio.
Error Handling
All decode functions return ('a, string) result or ('a, Error.t) result. Common error cases:
Error.kind.Missing_section: Required section not found in file.Error.kind.Missing_option: Required option not found in section.Error.kind.Type_mismatch: Value could not be converted (e.g.,"abc"as an integer).Error.kind.Interpolation: Variable reference could not be resolved.Error.kind.Duplicate_section,Error.kind.Duplicate_option: In strict mode, duplicates are errors.
Cookbook Patterns
See the cookbook for detailed examples:
- Optional values and defaults
- Lists and comma-separated values
- Handling unknown options
- Interpolation (basic and extended)
- Layout-preserving round-trips
type 'a fmt = Format.formatter -> 'a -> unitThe type for formatters of values of type 'a.
Text Locations
Text locations track where elements appear in source files. This enables good error messages and layout-preserving round-trips.
module Textloc : sig ... endText locations.
Metadata
Metadata tracks source location and layout (whitespace/comments) for INI elements. This enables layout-preserving round-trips where your output matches your input formatting.
module Meta : sig ... endINI element metadata.
type 'a node = 'a * Meta.tThe type for values paired with metadata. Used internally to track source information for section and option names.
Paths
Paths identify locations within an INI document, like [server]/port. They are used in error messages to show where problems occurred.
module Path : sig ... endINI paths.
Errors
Error handling for INI parsing and codec operations. Errors include source location information when available.
module Error : sig ... endError handling.
module Repr : sig ... endCodecs
Codecs describe bidirectional mappings between INI text and OCaml values. A codec of type 'a t can:
- Decode: Parse INI text into an OCaml value of type
'a. - Encode: Serialize an OCaml value of type
'ato INI text.
Base codecs handle primitive types (string, int, bool, etc.). Compose them using Section and Document modules to build codecs for complex configuration types.
The type for INI codecs. A value of type 'a t describes how to decode INI data to type 'a and encode 'a to INI data.
val kind : 'a t -> stringkind c is a short description of what c represents (e.g., "integer", "server configuration"). Used in error messages.
val doc : 'a t -> stringdoc c is the documentation string for c.
with_doc ?kind ?doc c is c with updated kind and documentation.
val section_state : 'a t -> 'a Repr.section_state optionsection_state c returns the section codec state if c was created with Section.finish. Returns None for non-section codecs.
val document_state : 'a t -> 'a Repr.document_state optiondocument_state c returns the document codec state if c was created with Document.finish. Returns None for non-document codecs.
Base Codecs
Codecs for primitive INI value types. All INI values are strings internally; these codecs handle the conversion to/from OCaml types.
val string : string tstring is the identity codec. Values are returned as-is.
Example:
"hello world"decodes to"hello world".
val int : int tint decodes decimal integers.
Example:
"42"decodes to42"-100"decodes to-100"0xFF"fails (hex not supported)
Warning. Behavior depends on Sys.int_size for very large values.
val int32 : int32 tint32 decodes 32-bit integers.
val int64 : int64 tint64 decodes 64-bit integers.
val float : float tfloat decodes floating-point numbers.
Example:
"3.14"decodes to3.14"1e-10"decodes to1e-10
val bool : bool tbool decodes Python-compatible boolean values. Matching is case-insensitive.
True values: 1, yes, true, on
False values: 0, no, false, off
This matches Python's configparser.getboolean() behavior exactly.
Example:
"yes","YES","Yes"all decode totrue"0","no","OFF"all decode tofalse"maybe"fails with a type error
val bool_01 : bool tbool_01 decodes only "0" and "1".
Use this for stricter parsing when you want exactly these values.
val bool_yesno : bool tbool_yesno decodes "yes" and "no" (case-insensitive).
val bool_truefalse : bool tbool_truefalse decodes "true" and "false" (case-insensitive).
val bool_onoff : bool tbool_onoff decodes "on" and "off" (case-insensitive).
Combinators
Build complex codecs from simpler ones.
map ~dec ~enc c transforms codec c using dec for decoding and enc for encoding.
Example: A codec for URIs:
let uri = Init.map
~dec:Uri.of_string
~enc:Uri.to_string
Init.stringval enum :
?cmp:('a -> 'a -> int) ->
?kind:string ->
?doc:string ->
(string * 'a) list ->
'a tenum assoc creates a codec for enumerated values. String matching is case-insensitive.
Example:
type log_level = Debug | Info | Warn | Error
let log_level = Init.enum [
"debug", Debug;
"info", Info;
"warn", Warn;
"error", Error;
]option c wraps codec c to handle optional values. Empty strings decode to None; None encodes to empty string.
Note: For optional INI options (that may be absent), use Section.opt_mem instead. This codec is for values that are present but may be empty.
default v c uses v when decoding with c fails.
Example:
let port = Init.default 8080 Init.int
(* "abc" decodes to 8080 instead of failing *)list ?sep c decodes comma-separated (or sep-separated) values.
Example:
"a,b,c"withlist stringdecodes to["a"; "b"; "c"]"1, 2, 3"withlist intdecodes to[1; 2; 3]
Whitespace around elements is trimmed.
module Section : sig ... endmodule Document : sig ... end