OCaml codecs for Python INI file handling compatible with ConfigParser
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 251 lines 5.8 kB view raw
1{0 Init Cookbook} 2 3This cookbook provides complete examples for common INI configuration patterns. 4Each example is self-contained and can be adapted to your use case. 5 6For runnable code, see [test/cookbook.ml] in the source repository. 7 8{1:optional_values Optional Values and Defaults} 9 10Handle missing options gracefully with defaults or optional fields. 11 12{[ 13type database_config = { 14 host : string; 15 port : int; (* Uses default if missing *) 16 password : string option; (* Optional field *) 17} 18 19let database_codec = Init.Section.( 20 obj (fun host port password -> { host; port; password }) 21 |> mem "host" Init.string ~enc:(fun c -> c.host) 22 (* dec_absent provides a default value when the option is missing *) 23 |> mem "port" Init.int ~dec_absent:5432 ~enc:(fun c -> c.port) 24 (* opt_mem decodes to None when the option is missing *) 25 |> opt_mem "password" Init.string ~enc:(fun c -> c.password) 26 |> finish 27) 28]} 29 30{1:lists Lists and Comma-Separated Values} 31 32Parse comma-separated lists of values. 33 34{[ 35type config = { 36 hosts : string list; 37 ports : int list; 38} 39 40let section_codec = Init.Section.( 41 obj (fun hosts ports -> { hosts; ports }) 42 |> mem "hosts" (Init.list Init.string) ~enc:(fun c -> c.hosts) 43 |> mem "ports" (Init.list Init.int) ~enc:(fun c -> c.ports) 44 |> finish 45) 46]} 47 48Configuration file: 49{v 50[cluster] 51hosts = node1.example.com, node2.example.com, node3.example.com 52ports = 8080, 8081, 8082 53v} 54 55{1:unknown_options Handling Unknown Options} 56 57Three strategies for dealing with options you didn't expect: 58 59{2 Skip Unknown (Default)} 60 61Silently ignore extra options: 62{[ 63let section_codec = Init.Section.( 64 obj (fun known_key -> known_key) 65 |> mem "known_key" Init.string ~enc:Fun.id 66 |> skip_unknown (* This is the default *) 67 |> finish 68) 69]} 70 71{2 Error on Unknown} 72 73Strict validation - reject unexpected options: 74{[ 75let section_codec = Init.Section.( 76 obj (fun known_key -> known_key) 77 |> mem "known_key" Init.string ~enc:Fun.id 78 |> error_unknown (* Reject unknown options *) 79 |> finish 80) 81]} 82 83{2 Keep Unknown} 84 85Capture unknown options for pass-through: 86{[ 87type config = { 88 known_key : string; 89 extra : (string * string) list; 90} 91 92let section_codec = Init.Section.( 93 obj (fun known_key extra -> { known_key; extra }) 94 |> mem "known_key" Init.string ~enc:(fun c -> c.known_key) 95 |> keep_unknown ~enc:(fun c -> c.extra) 96 |> finish 97) 98]} 99 100{1:interpolation Interpolation} 101 102{2 Basic Interpolation} 103 104Variable substitution using [%(name)s] syntax: 105 106{[ 107let paths_codec = Init.Section.( 108 obj (fun base data logs -> (base, data, logs)) 109 |> mem "base" Init.string ~enc:(fun (b,_,_) -> b) 110 |> mem "data" Init.string ~enc:(fun (_,d,_) -> d) 111 |> mem "logs" Init.string ~enc:(fun (_,_,l) -> l) 112 |> finish 113) 114]} 115 116Configuration file: 117{v 118[paths] 119base = /opt/myapp 120data = %(base)s/data 121logs = %(base)s/logs 122v} 123 124After interpolation: [data = /opt/myapp/data], [logs = /opt/myapp/logs]. 125 126{2 Extended Interpolation} 127 128Cross-section references using [$\{section:name\}] syntax: 129 130{[ 131let config = { Init_bytesrw.default_config with 132 interpolation = `Extended_interpolation } 133]} 134 135Configuration file: 136{v 137[common] 138base = /opt/myapp 139 140[server] 141data_dir = ${common:base}/data 142v} 143 144{2 No Interpolation} 145 146Disable interpolation for files with literal [%] characters: 147 148{[ 149let config = Init_bytesrw.raw_config 150(* Or: *) 151let config = { Init_bytesrw.default_config with 152 interpolation = `No_interpolation } 153]} 154 155{1:multifile Multi-File Configuration} 156 157Layer multiple configuration files, with later files overriding earlier ones: 158 159{[ 160(* Read base config, then override with environment-specific settings *) 161let load_config ~env = 162 let base = read_file "config/default.ini" in 163 let env_config = read_file (Printf.sprintf "config/%s.ini" env) in 164 (* Parse base first, then override with env_config *) 165 match Init_bytesrw.decode_string config_codec base with 166 | Error e -> Error e 167 | Ok base_config -> 168 (* Merge or override as needed *) 169 ... 170]} 171 172{1:roundtrip Layout-Preserving Round-Trips} 173 174Preserve formatting when modifying configuration files: 175 176{[ 177(* Decode with layout preservation *) 178let result = Init_bytesrw.decode_string 179 ~layout:true (* Preserve whitespace *) 180 ~locs:true (* Preserve locations *) 181 config_codec ini_text 182 183(* Modify and re-encode - formatting is preserved *) 184match result with 185| Ok config -> 186 let modified = { config with port = 9000 } in 187 Init_bytesrw.encode_string config_codec modified 188| Error e -> Error e 189]} 190 191{b Note:} Comments are NOT preserved during round-trips, matching Python's 192configparser behavior. 193 194{1:enums Enums and Custom Types} 195 196Parse enumerated values: 197 198{[ 199type log_level = Debug | Info | Warn | Error 200 201let log_level_codec = Init.enum [ 202 "debug", Debug; 203 "info", Info; 204 "warn", Warn; 205 "error", Error; 206] 207 208(* Aliases work too *) 209let environment_codec = Init.enum [ 210 "development", Development; 211 "dev", Development; (* Alias *) 212 "production", Production; 213 "prod", Production; (* Alias *) 214] 215]} 216 217{1:defaults The DEFAULT Section} 218 219The DEFAULT section provides fallback values for all other sections: 220 221{v 222[DEFAULT] 223timeout = 30 224 225[production] 226host = api.example.com 227port = 443 228 229[staging] 230host = staging.example.com 231port = 8443 232timeout = 60 233v} 234 235Both [production] and [staging] sections can access [timeout], but [staging] 236overrides the default value. 237 238{1:booleans Custom Boolean Formats} 239 240Different applications use different boolean representations: 241 242{[ 243(* Python-compatible: 1/yes/true/on or 0/no/false/off *) 244|> mem "flag" Init.bool 245 246(* Strict formats *) 247|> mem "enabled" Init.bool_01 (* Only 0 or 1 *) 248|> mem "active" Init.bool_yesno (* Only yes or no *) 249|> mem "debug" Init.bool_truefalse (* Only true or false *) 250|> mem "feature" Init.bool_onoff (* Only on or off *) 251]}