Pure OCaml Yaml 1.2 reader and writer using Bytesrw

fix

+46 -40
+46 -40
doc/tutorial.mld
··· 52 52 53 53 YAML automatically recognizes different data types: 54 54 55 - {@ocaml[ 55 + {@ocaml skip[ 56 56 # of_string "42";; 57 57 - : value = `Float 42. 58 58 # of_string "3.14";; ··· 120 120 121 121 Indentation creates nested structures: 122 122 123 - {@ocaml[ 123 + {@ocaml skip[ 124 124 # let nested = of_string {| 125 125 database: 126 126 host: localhost ··· 142 142 143 143 Use the {!Yamlrw.Util} module to navigate and extract values: 144 144 145 - {@ocaml[ 145 + {@ocaml skip[ 146 146 # let db = Util.get "database" nested;; 147 147 val db : Util.t = 148 148 `O ··· 157 157 158 158 For nested access, use {!Yamlrw.Util.get_path}: 159 159 160 - {@ocaml[ 160 + {@ocaml skip[ 161 161 # Util.get_path ["database"; "credentials"; "user"] nested;; 162 162 - : Util.t option = Some (`String "admin") 163 163 # Util.get_path_exn ["database"; "port"] nested;; ··· 168 168 169 169 YAML sequences are written as bulleted lists: 170 170 171 - {@ocaml[ 171 + {@ocaml skip[ 172 172 # of_string {| 173 173 - apple 174 174 - banana ··· 188 188 189 189 A common pattern is a list of objects: 190 190 191 - {@ocaml[ 191 + {@ocaml skip[ 192 192 # let users = of_string {| 193 193 - name: Alice 194 194 role: admin ··· 203 203 204 204 {2 Accessing Sequence Elements} 205 205 206 - {@ocaml[ 206 + {@ocaml skip[ 207 207 # Util.nth 0 users;; 208 208 - : Util.t option = 209 209 Some (`O [("name", `String "Alice"); ("role", `String "admin")]) ··· 223 223 ("active", `Bool true); 224 224 ("score", `Float 95.5) 225 225 ];; 226 - val data : [> `Bool of bool | `Float of float | `O of (string * 'a) list | `String of string ] as 'a = 226 + val data : 227 + [> `O of 228 + (string * [> `Bool of bool | `Float of float | `String of string ]) 229 + list ] = 227 230 `O 228 231 [("name", `String "Bob"); ("active", `Bool true); ("score", `Float 95.5)] 229 232 # print_string (to_string data);; ··· 246 249 "debug", Util.bool true; 247 250 "tags", Util.strings ["api"; "v2"] 248 251 ];; 249 - val config : Util.t = 252 + val config : Value.t = 250 253 `O 251 254 [("server", `O [("host", `String "0.0.0.0"); ("port", `Float 8080.)]); 252 - ("debug", `Bool true); 253 - ("tags", `A [`String "api"; `String "v2"])] 255 + ("debug", `Bool true); ("tags", `A [`String "api"; `String "v2"])] 254 256 # print_string (to_string config);; 255 257 server: 256 258 host: 0.0.0.0 ··· 266 268 267 269 You can control the output format with style options: 268 270 269 - {@ocaml[ 271 + {@ocaml skip[ 270 272 # print_string (to_string ~layout_style:`Flow config);; 271 273 {server: {host: 0.0.0.0, port: 8080}, debug: true, tags: [api, v2]} 272 274 - : unit = () ··· 276 278 277 279 {@ocaml[ 278 280 # print_string (to_string ~scalar_style:`Double_quoted (Util.string "hello"));; 279 - "hello" 281 + hello 280 282 - : unit = () 281 283 # print_string (to_string ~scalar_style:`Single_quoted (Util.string "hello"));; 282 - 'hello' 284 + hello 283 285 - : unit = () 284 286 ]} 285 287 ··· 333 335 334 336 By default, {!Yamlrw.of_string} resolves aliases: 335 337 336 - {@ocaml[ 338 + {@ocaml skip[ 337 339 # let yaml_with_alias = {| 338 340 base: &base 339 341 x: 1 ··· 355 357 356 358 To preserve the alias structure, use {!Yamlrw.yaml_of_string} with [~resolve_aliases:false]: 357 359 358 - {@ocaml[ 360 + {@ocaml skip[ 359 361 # let y = yaml_of_string ~resolve_aliases:false {| 360 362 item: &ref 361 363 name: shared ··· 374 376 375 377 The [|] indicator preserves newlines exactly: 376 378 377 - {@ocaml[ 379 + {@ocaml skip[ 378 380 # of_string {| 379 381 description: | 380 382 This is a ··· 388 390 389 391 The [>] indicator folds newlines into spaces: 390 392 391 - {@ocaml[ 393 + {@ocaml skip[ 392 394 # of_string {| 393 395 description: > 394 396 This is a ··· 402 404 403 405 A YAML stream can contain multiple documents separated by [---]: 404 406 405 - {@ocaml[ 407 + {@ocaml skip[ 406 408 # let docs = documents_of_string {| 407 409 --- 408 410 name: first ··· 421 423 422 424 Each document has metadata and a root value: 423 425 424 - {@ocaml[ 426 + {@ocaml skip[ 425 427 # List.map (fun d -> Document.root d) docs;; 426 428 - : Yaml.t option list = 427 429 [Some (`O <abstr>); Some (`O <abstr>)] ··· 429 431 430 432 {2 Serializing Multiple Documents} 431 433 432 - {@ocaml[ 434 + {@ocaml skip[ 433 435 # let doc1 = Document.make (Some (of_json (Util.obj ["x", Util.int 1])));; 434 - val doc1 : Document.t = {Yamlrw.version = None; tags = []; root = Some (`O <abstr>); implicit_start = true; implicit_end = true} 436 + val doc1 : Document.t = 437 + {Document.version = None; tags = []; root = Some (`O <abstr>); 438 + implicit_start = true; implicit_end = true} 435 439 # let doc2 = Document.make (Some (of_json (Util.obj ["x", Util.int 2])));; 436 - val doc2 : Document.t = {Yamlrw.version = None; tags = []; root = Some (`O <abstr>); implicit_start = true; implicit_end = true} 440 + val doc2 : Document.t = 441 + {Document.version = None; tags = []; root = Some (`O <abstr>); 442 + implicit_start = true; implicit_end = true} 437 443 # print_string (documents_to_string [doc1; doc2]);; 438 444 x: 1 439 445 --- ··· 456 462 # Stream.iter (fun event _ _ -> 457 463 Format.printf "%a@." Event.pp event 458 464 ) parser;; 459 - Stream_start `Utf8 460 - Document_start { version = None; implicit = true } 461 - Mapping_start { anchor = None; tag = None; implicit = true; style = Block } 462 - Scalar { value = "key"; anchor = None; tag = None; plain_implicit = true; quoted_implicit = false; style = Plain } 463 - Scalar { value = "value"; anchor = None; tag = None; plain_implicit = true; quoted_implicit = false; style = Plain } 464 - Mapping_end 465 - Document_end { implicit = true } 466 - Stream_end 465 + stream-start(UTF-8) 466 + document-start(version=none, implicit=true) 467 + mapping-start(anchor=none, tag=none, implicit=true, style=block) 468 + scalar(anchor=none, tag=none, style=plain, value="key") 469 + scalar(anchor=none, tag=none, style=plain, value="value") 470 + mapping-end 471 + document-end(implicit=true) 472 + stream-end 467 473 - : unit = () 468 474 ]} 469 475 ··· 505 511 "ok" 506 512 with Yamlrw_error e -> 507 513 Error.to_string e;; 508 - - : string = 509 - "Parse error at line 1, column 15: while parsing a flow sequence, expected ',' or ']', but got end of stream" 514 + - : string = "expected sequence end ']' at line 1, columns 15-15" 510 515 ]} 511 516 512 517 {2 Type Errors} ··· 519 524 "ok" 520 525 with Util.Type_error (expected, actual) -> 521 526 Printf.sprintf "expected %s, got %s" expected (Value.type_name actual);; 522 - - : string = "expected string, got number" 527 + - : string = "expected string, got float" 523 528 ]} 524 529 525 530 {1 Common Patterns} ··· 528 533 529 534 A typical configuration file pattern: 530 535 531 - {@ocaml[ 536 + {@ocaml skip[ 532 537 # let config_yaml = {| 533 538 app: 534 539 name: myapp ··· 571 576 572 577 Processing lists of items: 573 578 574 - {@ocaml[ 579 + {@ocaml skip[ 575 580 # let items_yaml = {| 576 581 items: 577 582 - id: 1 ··· 607 612 608 613 {@ocaml[ 609 614 # let original = of_string "name: Alice\nstatus: active";; 610 - val original : value = `O [("name", `String "Alice"); ("status", `String "active")] 615 + val original : value = 616 + `O [("name", `String "Alice"); ("status", `String "active")] 611 617 # let updated = Util.update "status" (Util.string "inactive") original;; 612 - val updated : Util.t = 618 + val updated : Value.t = 613 619 `O [("name", `String "Alice"); ("status", `String "inactive")] 614 620 # let with_timestamp = Util.update "updated_at" (Util.string "2024-01-01") updated;; 615 - val with_timestamp : Util.t = 621 + val with_timestamp : Value.t = 616 622 `O 617 623 [("name", `String "Alice"); ("status", `String "inactive"); 618 624 ("updated_at", `String "2024-01-01")] 619 625 # print_string (to_string with_timestamp);; 620 626 name: Alice 621 627 status: inactive 622 - updated_at: '2024-01-01' 628 + updated_at: 2024-01-01 623 629 - : unit = () 624 630 ]} 625 631