···11+ISC License
22+33+Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>
44+55+Permission to use, copy, modify, and distribute this software for any
66+purpose with or without fee is hereby granted, provided that the above
77+copyright notice and this permission notice appear in all copies.
88+99+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1010+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1111+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1212+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1313+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1414+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1515+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+88
README.md
···11+# init - Declarative INI Data Manipulation for OCaml
22+33+Init provides bidirectional codecs for INI files following Python's
44+configparser semantics. Define your configuration types once, and Init
55+handles both parsing and serialization.
66+77+## Features
88+99+- **Python-compatible**: Follows configparser semantics for maximum compatibility
1010+- **Bidirectional codecs**: Define once, use for both decoding and encoding
1111+- **Type-safe**: Strongly-typed configuration with compile-time guarantees
1212+- **Multiline values**: Support for continuation lines via indentation
1313+- **Interpolation**: Basic `%(name)s` and extended `${section:name}` variable substitution
1414+- **DEFAULT section**: Automatic inheritance of default values
1515+- **Optional values**: Graceful handling of missing configuration options
1616+1717+## Installation
1818+1919+```bash
2020+opam install init
2121+```
2222+2323+For parsing/encoding support, also install the bytesrw sub-library:
2424+2525+```bash
2626+opam install init bytesrw
2727+```
2828+2929+For Eio file system integration:
3030+3131+```bash
3232+opam install init bytesrw eio bytesrw-eio
3333+```
3434+3535+## Quick Start
3636+3737+```ocaml
3838+(* Define your configuration type *)
3939+type server_config = {
4040+ host : string;
4141+ port : int;
4242+ debug : bool;
4343+}
4444+4545+(* Define the codec *)
4646+let server_codec = Init.Section.(
4747+ obj (fun host port debug -> { host; port; debug })
4848+ |> mem "host" Init.string ~enc:(fun c -> c.host)
4949+ |> mem "port" Init.int ~enc:(fun c -> c.port)
5050+ |> mem "debug" Init.bool ~dec_absent:false ~enc:(fun c -> c.debug)
5151+ |> finish
5252+)
5353+5454+let config_codec = Init.Document.(
5555+ obj (fun server -> server)
5656+ |> section "server" server_codec ~enc:Fun.id
5757+ |> finish
5858+)
5959+6060+(* Parse an INI file *)
6161+let () =
6262+ let ini = {|
6363+[server]
6464+host = localhost
6565+port = 8080
6666+debug = yes
6767+|} in
6868+ match Init_bytesrw.decode_string config_codec ini with
6969+ | Ok config ->
7070+ Printf.printf "Server: %s:%d (debug=%b)\n"
7171+ config.host config.port config.debug
7272+ | Error msg ->
7373+ Printf.printf "Error: %s\n" msg
7474+```
7575+7676+## Sub-libraries
7777+7878+- **init**: Core codec combinators (no dependencies)
7979+- **init.bytesrw**: Parsing and encoding using bytesrw
8080+- **init.eio**: Eio file system integration
8181+8282+## Documentation
8383+8484+See the [API documentation](https://ocaml.org/p/init/latest/doc/Init/index.html) for complete reference.
8585+8686+## License
8787+8888+ISC License. See [LICENSE](LICENSE) for details.
···11# This file is generated by dune, edit dune-project instead
22opam-version: "2.0"
33-version: "0.1.0"
43synopsis: "Declarative INI data manipulation for OCaml"
54description: """
65Init provides bidirectional codecs for INI files following Python's
···1312maintainer: ["Anil Madhavapeddy <anil@recoil.org>"]
1413authors: ["Anil Madhavapeddy <anil@recoil.org>"]
1514license: "ISC"
1616-homepage: "https://github.com/avsm/ocaml-init"
1717-bug-reports: "https://github.com/avsm/ocaml-init/issues"
1815depends: [
1916 "dune" {>= "3.0"}
2017 "ocaml" {>= "4.14.0"}
1818+ "alcotest" {with-test & >= "1.7.0"}
1919+ "str" {with-test}
2120 "odoc" {with-doc}
2221]
2322depopts: ["bytesrw" "eio" "bytesrw-eio"]
···3938 "@doc" {with-doc}
4039 ]
4140]
4242-dev-repo: "git+https://github.com/avsm/ocaml-init.git"
+66-93
src/bytesrw/init_bytesrw.ml
···1414 - DEFAULT section inheritance
1515 - Case-insensitive option lookup *)
16161717+module Result_syntax = struct
1818+ let ( let* ) = Result.bind
1919+end
2020+1721(* ---- Configuration ---- *)
18221923type interpolation =
···524528(* ---- Interpolation Pass ---- *)
525529526530let perform_interpolation state =
531531+ let open Result_syntax in
527532 let interpolate_value ~section iv =
528528- match interpolate state.config ~section ~defaults:state.defaults ~sections:state.sections iv.Init.Repr.raw with
529529- | Ok interpolated -> Ok { iv with Init.Repr.interpolated = interpolated }
530530- | Error e -> Error e
533533+ interpolate state.config ~section ~defaults:state.defaults ~sections:state.sections iv.Init.Repr.raw
534534+ |> Result.map (fun interpolated -> { iv with Init.Repr.interpolated = interpolated })
531535 in
532536 let interpolate_opts ~section opts =
533537 let rec loop acc = function
534538 | [] -> Ok (List.rev acc)
535539 | ((name, meta), iv) :: rest ->
536536- match interpolate_value ~section iv with
537537- | Ok iv' -> loop (((name, meta), iv') :: acc) rest
538538- | Error e -> Error e
540540+ let* iv' = interpolate_value ~section iv in
541541+ loop (((name, meta), iv') :: acc) rest
539542 in
540543 loop [] opts
541544 in
542542- (* Interpolate defaults *)
543543- match interpolate_opts ~section:None state.defaults with
544544- | Error e -> Error e
545545- | Ok defaults' ->
546546- state.defaults <- defaults';
547547- (* Interpolate sections *)
548548- let rec loop_sections acc = function
549549- | [] -> Ok (List.rev acc)
550550- | sec :: rest ->
551551- match interpolate_opts ~section:(Some (fst sec.Init.Repr.name)) sec.options with
552552- | Ok opts' -> loop_sections ({ sec with options = opts' } :: acc) rest
553553- | Error e -> Error e
554554- in
555555- match loop_sections [] state.sections with
556556- | Error e -> Error e
557557- | Ok sections' ->
558558- state.sections <- sections';
559559- Ok ()
545545+ let rec loop_sections acc = function
546546+ | [] -> Ok (List.rev acc)
547547+ | sec :: rest ->
548548+ let* opts' = interpolate_opts ~section:(Some (fst sec.Init.Repr.name)) sec.options in
549549+ loop_sections ({ sec with options = opts' } :: acc) rest
550550+ in
551551+ let* defaults' = interpolate_opts ~section:None state.defaults in
552552+ state.defaults <- defaults';
553553+ let* sections' = loop_sections [] state.sections in
554554+ state.sections <- sections';
555555+ Ok ()
560556561557(* ---- Line splitting ---- *)
562558···583579(* ---- Main Parse Functions ---- *)
584580585581let parse_string_internal ?(config=default_config) ?(locs=false) ?(layout=false) ?(file=Init.Textloc.file_none) s =
582582+ let open Result_syntax in
586583 let _ = locs in (* TODO: Use locs to control location tracking *)
587584 let _ = layout in (* TODO: Use layout to control whitespace preservation *)
588585 let state = make_state config file in
···592589 finalize_current_option state;
593590 Ok ()
594591 | line :: rest ->
595595- match process_line state line with
596596- | Ok () -> process rest
597597- | Error e -> Error e
592592+ let* () = process_line state line in
593593+ process rest
598594 in
599599- match process lines with
600600- | Error e -> Error e
601601- | Ok () ->
602602- (* Perform interpolation *)
603603- match perform_interpolation state with
604604- | Error e -> Error e
605605- | Ok () ->
606606- let doc = {
607607- Init.Repr.defaults = List.rev state.defaults;
608608- sections = List.rev_map (fun (sec : Init.Repr.ini_section) ->
609609- { sec with options = List.rev sec.options }
610610- ) state.sections;
611611- meta = Init.Meta.none;
612612- } in
613613- Ok doc
595595+ let* () = process lines in
596596+ let* () = perform_interpolation state in
597597+ Ok {
598598+ Init.Repr.defaults = List.rev state.defaults;
599599+ sections = List.rev_map (fun (sec : Init.Repr.ini_section) ->
600600+ { sec with options = List.rev sec.options }
601601+ ) state.sections;
602602+ meta = Init.Meta.none;
603603+ }
614604615605let parse_reader ?(config=default_config) ?(locs=false) ?(layout=false) ?(file=Init.Textloc.file_none) reader =
616606 let s = read_all_to_string reader in
···621611622612(* ---- Decoding ---- *)
623613624624-let decode' ?(config=default_config) ?(locs=false) ?(layout=false) ?(file=Init.Textloc.file_none) codec reader =
625625- match parse_reader ~config ~locs ~layout ~file reader with
626626- | Error e -> Error e
627627- | Ok doc ->
628628- match Init.document_state codec with
629629- | Some doc_state -> doc_state.decode doc
614614+let decode_doc codec doc =
615615+ match Init.document_state codec with
616616+ | Some doc_state -> doc_state.decode doc
617617+ | None ->
618618+ match Init.section_state codec with
619619+ | Some sec_state ->
620620+ (match doc.Init.Repr.sections with
621621+ | [sec] -> sec_state.decode sec
622622+ | [] -> Error (Init.Error.make (Init.Error.Codec "no sections in document"))
623623+ | _ -> Error (Init.Error.make (Init.Error.Codec "multiple sections; expected single section codec")))
630624 | None ->
631631- (* Maybe it's a section codec - try to decode from first/only section *)
632632- match Init.section_state codec with
633633- | Some sec_state ->
634634- (match doc.sections with
635635- | [sec] -> sec_state.decode sec
636636- | [] -> Error (Init.Error.make (Init.Error.Codec "no sections in document"))
637637- | _ -> Error (Init.Error.make (Init.Error.Codec "multiple sections; expected single section codec")))
638638- | None ->
639639- Error (Init.Error.make (Init.Error.Codec "codec is neither document nor section type"))
625625+ Error (Init.Error.make (Init.Error.Codec "codec is neither document nor section type"))
626626+627627+let decode' ?(config=default_config) ?(locs=false) ?(layout=false) ?(file=Init.Textloc.file_none) codec reader =
628628+ let open Result_syntax in
629629+ let* doc = parse_reader ~config ~locs ~layout ~file reader in
630630+ decode_doc codec doc
640631641632let decode ?config ?locs ?layout ?file codec reader =
642642- match decode' ?config ?locs ?layout ?file codec reader with
643643- | Ok v -> Ok v
644644- | Error e -> Error (Init.Error.to_string e)
633633+ decode' ?config ?locs ?layout ?file codec reader
634634+ |> Result.map_error Init.Error.to_string
645635646636let decode_string' ?(config=default_config) ?(locs=false) ?(layout=false) ?(file=Init.Textloc.file_none) codec s =
647647- match parse_string ~config ~locs ~layout ~file s with
648648- | Error e -> Error e
649649- | Ok doc ->
650650- match Init.document_state codec with
651651- | Some doc_state -> doc_state.decode doc
652652- | None ->
653653- match Init.section_state codec with
654654- | Some sec_state ->
655655- (match doc.sections with
656656- | [sec] -> sec_state.decode sec
657657- | [] -> Error (Init.Error.make (Init.Error.Codec "no sections in document"))
658658- | _ -> Error (Init.Error.make (Init.Error.Codec "multiple sections; expected single section codec")))
659659- | None ->
660660- Error (Init.Error.make (Init.Error.Codec "codec is neither document nor section type"))
637637+ let open Result_syntax in
638638+ let* doc = parse_string ~config ~locs ~layout ~file s in
639639+ decode_doc codec doc
661640662641let decode_string ?config ?locs ?layout ?file codec s =
663663- match decode_string' ?config ?locs ?layout ?file codec s with
664664- | Ok v -> Ok v
665665- | Error e -> Error (Init.Error.to_string e)
642642+ decode_string' ?config ?locs ?layout ?file codec s
643643+ |> Result.map_error Init.Error.to_string
666644667645(* ---- Encoding ---- *)
668646···713691 Error (Init.Error.make (Init.Error.Codec "codec is neither document nor section type"))
714692715693let encode' ?buf:_ codec value ~eod writer =
694694+ let open Result_syntax in
716695 let buffer = Buffer.create 1024 in
717717- match encode_to_buffer buffer codec value with
718718- | Error e -> Error e
719719- | Ok () ->
720720- let s = Buffer.contents buffer in
721721- Bytes.Writer.write_string writer s;
722722- if eod then Bytes.Writer.write_eod writer;
723723- Ok ()
696696+ let* () = encode_to_buffer buffer codec value in
697697+ Bytes.Writer.write_string writer (Buffer.contents buffer);
698698+ if eod then Bytes.Writer.write_eod writer;
699699+ Ok ()
724700725701let encode ?buf codec value ~eod writer =
726726- match encode' ?buf codec value ~eod writer with
727727- | Ok () -> Ok ()
728728- | Error e -> Error (Init.Error.to_string e)
702702+ encode' ?buf codec value ~eod writer
703703+ |> Result.map_error Init.Error.to_string
729704730705let encode_string' ?buf:_ codec value =
731706 let buffer = Buffer.create 1024 in
732732- match encode_to_buffer buffer codec value with
733733- | Error e -> Error e
734734- | Ok () -> Ok (Buffer.contents buffer)
707707+ encode_to_buffer buffer codec value
708708+ |> Result.map (fun () -> Buffer.contents buffer)
735709736710let encode_string ?buf codec value =
737737- match encode_string' ?buf codec value with
738738- | Ok s -> Ok s
739739- | Error e -> Error (Init.Error.to_string e)
711711+ encode_string' ?buf codec value
712712+ |> Result.map_error Init.Error.to_string
+3-5
src/eio/init_eio.ml
···3434let encode_path ?buf codec value path =
3535 Eio.Path.with_open_out ~create:(`Or_truncate 0o644) path @@ fun flow ->
3636 let writer = Bytesrw_eio.bytes_writer_of_flow flow in
3737- match Init_bytesrw.encode' ?buf codec value ~eod:true writer with
3838- | Ok () -> Ok ()
3939- | Error e -> Error e
3737+ Init_bytesrw.encode' ?buf codec value ~eod:true writer
40384139let encode_path_exn ?buf codec value path =
4240 match encode_path ?buf codec value path with
···5654 | Error e -> raise (err e)
57555856let encode_flow ?buf codec value ~eod flow =
5959- let writer = Bytesrw_eio.bytes_writer_of_flow flow in
6060- Init_bytesrw.encode' ?buf codec value ~eod writer
5757+ Bytesrw_eio.bytes_writer_of_flow flow
5858+ |> Init_bytesrw.encode' ?buf codec value ~eod
61596260let encode_flow_exn ?buf codec value ~eod flow =
6361 match encode_flow ?buf codec value ~eod flow with
+61-88
src/init.ml
···508508509509let default def c = {
510510 c with
511511- dec = (fun v ->
512512- match c.dec v with
513513- | Ok x -> Ok x
514514- | Error _ -> Ok def);
511511+ dec = (fun v -> Ok (Result.value ~default:def (c.dec v)));
515512}
516513517514let list ?(sep = ',') c = {
···539536 document = None;
540537}
541538539539+(* ---- Result helpers ---- *)
540540+541541+module Result_syntax = struct
542542+ let ( let* ) = Result.bind
543543+end
544544+542545(* ---- Section Codecs ---- *)
543546544547module Section = struct
···570573571574 let mem ?doc:_ ?dec_absent ?enc ?enc_omit name (c : 'a codec)
572575 (m : ('o, 'a -> 'dec) map) : ('o, 'dec) map =
576576+ let open Result_syntax in
573577 let lc_name = String.lowercase_ascii name in
574578 {
575579 m with
···580584 let decoded = match opt with
581585 | Some (_, v) -> c.dec v
582586 | None ->
583583- match dec_absent with
584584- | Some def -> Ok def
585585- | None -> Error (Error.make (Missing_option {
586586- section = fst sec.name; option = name }))
587587+ Option.to_result
588588+ ~none:(Error.make (Missing_option { section = fst sec.name; option = name }))
589589+ dec_absent
587590 in
588588- match decoded with
589589- | Ok a ->
590590- (match m.decode sec with
591591- | Ok f -> Ok (f a)
592592- | Error e -> Error e)
593593- | Error e -> Error e);
591591+ let* a = decoded in
592592+ let* f = m.decode sec in
593593+ Ok (f a));
594594 encode = (fun o ->
595595 let sec = m.encode o in
596596 match enc with
597597 | None -> sec
598598 | Some enc_fn ->
599599 let v = enc_fn o in
600600- let should_omit = match enc_omit with
601601- | Some f -> f v
602602- | None -> false
603603- in
600600+ let should_omit = Option.fold ~none:false ~some:(fun f -> f v) enc_omit in
604601 if should_omit then sec
605602 else
606603 let iv = c.enc v Meta.none in
···628625 if List.mem lc_n m.known then None
629626 else Some (n, v.Repr.interpolated)
630627 ) sec.Repr.options in
631631- match m.decode sec with
632632- | Ok f -> Ok (f unknown_opts)
633633- | Error e -> Error e);
628628+ m.decode sec |> Result.map (fun f -> f unknown_opts));
634629 encode = (fun o ->
635630 let sec = m.encode o in
636631 match enc with
637632 | None -> sec
638633 | Some enc_fn ->
639639- let unknown_opts = enc_fn o in
640634 let new_opts = List.map (fun (k, v) ->
641635 ((k, Meta.none), { Repr.raw = v; interpolated = v; meta = Meta.none })
642642- ) unknown_opts in
636636+ ) (enc_fn o) in
643637 { sec with options = new_opts @ sec.options });
644638 }
645639···702696 unknown = `Skip;
703697 }
704698699699+ let get_section_state sec_codec fn_name =
700700+ match sec_codec.section with
701701+ | Some s -> s
702702+ | None -> failwith (fn_name ^ ": codec must be a section codec")
703703+705704 let section ?doc:_ ?enc name (sec_codec : 'a codec)
706705 (m : ('o, 'a -> 'dec) map) : ('o, 'dec) map =
707707- let sec_state = match sec_codec.section with
708708- | Some s -> s
709709- | None -> failwith "section: codec must be a section codec"
710710- in
706706+ let open Result_syntax in
707707+ let sec_state = get_section_state sec_codec "section" in
711708 let lc_name = String.lowercase_ascii name in
712709 {
713710 m with
···715712 decode = (fun doc ->
716713 let sec = List.find_opt (fun s ->
717714 String.lowercase_ascii (fst s.Repr.name) = lc_name) doc.Repr.sections in
718718- match sec with
719719- | None -> Error (Error.make (Missing_section name))
720720- | Some sec ->
721721- match sec_state.decode sec with
722722- | Ok a ->
723723- (match m.decode doc with
724724- | Ok f -> Ok (f a)
725725- | Error e -> Error e)
726726- | Error e -> Error e);
715715+ let* sec = Option.to_result ~none:(Error.make (Missing_section name)) sec in
716716+ let* a = sec_state.decode sec in
717717+ let* f = m.decode doc in
718718+ Ok (f a));
727719 encode = (fun o ->
728720 let doc = m.encode o in
729721 match enc with
730722 | None -> doc
731723 | Some enc_fn ->
732732- let v = enc_fn o in
733733- let sec = sec_state.encode v in
734734- let sec = { sec with name = (name, Meta.none) } in
735735- { doc with sections = sec :: doc.sections });
724724+ let sec = sec_state.encode (enc_fn o) in
725725+ { doc with sections = { sec with name = (name, Meta.none) } :: doc.sections });
736726 }
737727738728 let opt_section ?doc:_ ?enc name (sec_codec : 'a codec)
739729 (m : ('o, 'a option -> 'dec) map) : ('o, 'dec) map =
740740- let sec_state = match sec_codec.section with
741741- | Some s -> s
742742- | None -> failwith "opt_section: codec must be a section codec"
743743- in
730730+ let open Result_syntax in
731731+ let sec_state = get_section_state sec_codec "opt_section" in
744732 let lc_name = String.lowercase_ascii name in
745733 {
746734 m with
···748736 decode = (fun doc ->
749737 let sec = List.find_opt (fun s ->
750738 String.lowercase_ascii (fst s.Repr.name) = lc_name) doc.Repr.sections in
751751- match sec with
752752- | None ->
753753- (match m.decode doc with
754754- | Ok f -> Ok (f None)
755755- | Error e -> Error e)
756756- | Some sec ->
757757- match sec_state.decode sec with
758758- | Ok a ->
759759- (match m.decode doc with
760760- | Ok f -> Ok (f (Some a))
761761- | Error e -> Error e)
762762- | Error e -> Error e);
739739+ let* value = match sec with
740740+ | None -> Ok None
741741+ | Some sec ->
742742+ let* a = sec_state.decode sec in
743743+ Ok (Some a)
744744+ in
745745+ let* f = m.decode doc in
746746+ Ok (f value));
763747 encode = (fun o ->
764748 let doc = m.encode o in
765749 match enc with
···769753 | None -> doc
770754 | Some v ->
771755 let sec = sec_state.encode v in
772772- let sec = { sec with name = (name, Meta.none) } in
773773- { doc with sections = sec :: doc.sections });
756756+ { doc with sections = { sec with name = (name, Meta.none) } :: doc.sections });
774757 }
775758776759 let defaults ?doc:_ ?enc (sec_codec : 'a codec)
777760 (m : ('o, 'a -> 'dec) map) : ('o, 'dec) map =
778778- let sec_state = match sec_codec.section with
779779- | Some s -> s
780780- | None -> failwith "defaults: codec must be a section codec"
781781- in
761761+ let open Result_syntax in
762762+ let sec_state = get_section_state sec_codec "defaults" in
782763 {
783764 m with
784765 known = "default" :: m.known;
···788769 options = doc.defaults;
789770 meta = Meta.none;
790771 } in
791791- match sec_state.decode fake_sec with
792792- | Ok a ->
793793- (match m.decode doc with
794794- | Ok f -> Ok (f a)
795795- | Error e -> Error e)
796796- | Error e -> Error e);
772772+ let* a = sec_state.decode fake_sec in
773773+ let* f = m.decode doc in
774774+ Ok (f a));
797775 encode = (fun o ->
798776 let doc = m.encode o in
799777 match enc with
···806784807785 let opt_defaults ?doc:_ ?enc (sec_codec : 'a codec)
808786 (m : ('o, 'a option -> 'dec) map) : ('o, 'dec) map =
809809- let sec_state = match sec_codec.section with
810810- | Some s -> s
811811- | None -> failwith "opt_defaults: codec must be a section codec"
812812- in
787787+ let open Result_syntax in
788788+ let sec_state = get_section_state sec_codec "opt_defaults" in
813789 {
814790 m with
815791 known = "default" :: m.known;
816792 decode = (fun doc ->
817817- if doc.defaults = [] then
818818- (match m.decode doc with
819819- | Ok f -> Ok (f None)
820820- | Error e -> Error e)
821821- else
822822- let fake_sec = {
823823- Repr.name = ("DEFAULT", Meta.none);
824824- options = doc.defaults;
825825- meta = Meta.none;
826826- } in
827827- match sec_state.decode fake_sec with
828828- | Ok a ->
829829- (match m.decode doc with
830830- | Ok f -> Ok (f (Some a))
831831- | Error e -> Error e)
832832- | Error e -> Error e);
793793+ let* value =
794794+ if doc.Repr.defaults = [] then Ok None
795795+ else
796796+ let fake_sec = {
797797+ Repr.name = ("DEFAULT", Meta.none);
798798+ options = doc.defaults;
799799+ meta = Meta.none;
800800+ } in
801801+ let* a = sec_state.decode fake_sec in
802802+ Ok (Some a)
803803+ in
804804+ let* f = m.decode doc in
805805+ Ok (f value));
833806 encode = (fun o ->
834807 let doc = m.encode o in
835808 match enc with