OCaml Library Collection
/

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 = 10

To 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_string

Read the cookbook for more examples.

  • Init_bytesrw - Byte-level I/O operations for string and file parsing
  • Init_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 = debug

Value 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/logs

Extended interpolation (cross-section references):

[common]
base = /opt/app

[server]
data_dir = ${common:base}/data

See 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: Port and port refer 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:

Cookbook Patterns

See the cookbook for detailed examples:

type 'a fmt = Format.formatter -> 'a -> unit

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

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

INI element metadata.

type 'a node = 'a * Meta.t

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

INI paths.

Errors

Error handling for INI parsing and codec operations. Errors include source location information when available.

module Error : sig ... end

Error handling.

module Repr : sig ... end

Codecs

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 'a to 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.

type 'a t

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

kind c is a short description of what c represents (e.g., "integer", "server configuration"). Used in error messages.

val doc : 'a t -> string

doc c is the documentation string for c.

val with_doc : ?kind:string -> ?doc:string -> 'a t -> 'a t

with_doc ?kind ?doc c is c with updated kind and documentation.

val section_state : 'a t -> 'a Repr.section_state option

section_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 option

document_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 t

string is the identity codec. Values are returned as-is.

Example:

  • "hello world" decodes to "hello world".
val int : int t

int decodes decimal integers.

Example:

  • "42" decodes to 42
  • "-100" decodes to -100
  • "0xFF" fails (hex not supported)

Warning. Behavior depends on Sys.int_size for very large values.

val int32 : int32 t

int32 decodes 32-bit integers.

val int64 : int64 t

int64 decodes 64-bit integers.

val float : float t

float decodes floating-point numbers.

Example:

  • "3.14" decodes to 3.14
  • "1e-10" decodes to 1e-10
val bool : bool t

bool 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 to true
  • "0", "no", "OFF" all decode to false
  • "maybe" fails with a type error
val bool_01 : bool t

bool_01 decodes only "0" and "1".

Use this for stricter parsing when you want exactly these values.

val bool_yesno : bool t

bool_yesno decodes "yes" and "no" (case-insensitive).

val bool_truefalse : bool t

bool_truefalse decodes "true" and "false" (case-insensitive).

val bool_onoff : bool t

bool_onoff decodes "on" and "off" (case-insensitive).

Combinators

Build complex codecs from simpler ones.

val map : ?kind:string -> ?doc:string -> dec:('a -> 'b) -> enc:('b -> 'a) -> 'a t -> 'b t

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.string
val enum : ?cmp:('a -> 'a -> int) -> ?kind:string -> ?doc:string -> (string * 'a) list -> 'a t

enum 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;
]
  • parameter cmp

    Comparison function for encoding (default: polymorphic compare).

val option : ?kind:string -> ?doc:string -> 'a t -> 'a option t

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.

val default : 'a -> 'a t -> 'a t

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 *)
val list : ?sep:char -> 'a t -> 'a list t

list ?sep c decodes comma-separated (or sep-separated) values.

Example:

  • "a,b,c" with list string decodes to ["a"; "b"; "c"]
  • "1, 2, 3" with list int decodes to [1; 2; 3]

Whitespace around elements is trimmed.

  • parameter sep

    Separator character (default: ',').

module Section : sig ... end
module Document : sig ... end