Streaming opam file codec for OCaml
0
fork

Configure Feed

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

opam: extract Codec submodule

Moves the codec combinators (type 'a t, bool/int/string/ident/list/option/
map/enum/filtered/constraint_, and the File record-codec builder) out of
opam.ml into a new codec.ml. opam.ml becomes a thin facade that re-exports
Codec and defines [type 'a t = 'a Codec.t] and [module File = Codec.File]
as ergonomic aliases.

Users reach combinators via either the canonical [Opam.Codec.string] path
(uniform with Sexp.Codec) or the ergonomic [Opam.string] / [Opam.t] aliases
(backward-compatible).

+306 -325
+1 -1
dune-project
··· 11 11 (package 12 12 (name opam) 13 13 (synopsis "Streaming opam file codec for OCaml") 14 - (tags (org:blacksun codec format opam)) 14 + (tags (org:blacksun codec.text)) 15 15 (description 16 16 "A type-safe codec library for opam files using a combinator-based 17 17 approach inspired by Jsont and the ocaml-toml package. The core library
+229
lib/codec.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2026 Thomas Gazagnaire <thomas@gazagnaire.org> 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Opam codec combinators. Exposed as [Opam.Codec]. *) 7 + 8 + type 'a fmt = Format.formatter -> 'a -> unit 9 + type 'a t = { kind : string; dec : Value.t -> 'a; enc : 'a -> Value.t } 10 + 11 + let kind c = c.kind 12 + 13 + let kind_name = function 14 + | Value.Bool _ -> "bool" 15 + | Value.Int _ -> "int" 16 + | Value.String _ -> "string" 17 + | Value.Ident _ -> "identifier" 18 + | Value.List _ -> "list" 19 + | Value.Group _ -> "group" 20 + | Value.Option _ -> "option" 21 + | Value.Relop _ -> "relational" 22 + | Value.Prefix_relop _ -> "prefix-relational" 23 + | Value.Logop _ -> "logical" 24 + | Value.Pfxop _ -> "prefix" 25 + | Value.Env_binding _ -> "env-binding" 26 + 27 + let err_expected exp v = 28 + Opam_error.sort_mismatch Loc.Meta.none ~expected:exp ~found:(kind_name v) 29 + 30 + let bool = 31 + { 32 + kind = "bool"; 33 + dec = (function Value.Bool b -> b | v -> err_expected "bool" v); 34 + enc = (fun b -> Value.Bool b); 35 + } 36 + 37 + let int = 38 + { 39 + kind = "int"; 40 + dec = (function Value.Int i -> i | v -> err_expected "int" v); 41 + enc = (fun i -> Value.Int i); 42 + } 43 + 44 + let string = 45 + { 46 + kind = "string"; 47 + dec = (function Value.String s -> s | v -> err_expected "string" v); 48 + enc = (fun s -> Value.String s); 49 + } 50 + 51 + let ident = 52 + { 53 + kind = "identifier"; 54 + dec = (function Value.Ident s -> s | v -> err_expected "identifier" v); 55 + enc = (fun s -> Value.Ident s); 56 + } 57 + 58 + let list elt = 59 + { 60 + kind = "list of " ^ elt.kind; 61 + dec = 62 + (function 63 + | Value.List xs -> List.map elt.dec xs 64 + | v -> err_expected "list" v); 65 + enc = (fun xs -> Value.List (List.map elt.enc xs)); 66 + } 67 + 68 + let option elt = 69 + { 70 + kind = "optional " ^ elt.kind; 71 + dec = (fun v -> Some (elt.dec v)); 72 + enc = (function Some v -> elt.enc v | None -> Value.List []); 73 + } 74 + 75 + let map ?kind:k ~dec ~enc c = 76 + let kind = match k with Some k -> k | None -> c.kind in 77 + { kind; dec = (fun v -> dec (c.dec v)); enc = (fun x -> c.enc (enc x)) } 78 + 79 + let enum ?kind:k cases = 80 + let kind = match k with Some k -> k | None -> "enum" in 81 + let rev = List.map (fun (s, v) -> (v, s)) cases in 82 + { 83 + kind; 84 + dec = 85 + (function 86 + | Value.Ident s | Value.String s -> ( 87 + match List.assoc_opt s cases with 88 + | Some v -> v 89 + | None -> 90 + Loc.Error.raise [] Loc.Meta.none (Opam_error.Unknown_enum_value s) 91 + ) 92 + | v -> err_expected "identifier or string" v); 93 + enc = 94 + (fun x -> 95 + match List.find_opt (fun (v, _) -> Stdlib.compare v x = 0) rev with 96 + | Some (_, s) -> Value.Ident s 97 + | None -> failwith "Opam.enum: value not in case list"); 98 + } 99 + 100 + let filtered inner = 101 + { 102 + kind = inner.kind ^ " (filtered)"; 103 + dec = 104 + (function 105 + | Value.Option (v, fs) -> (inner.dec v, fs) 106 + | v -> (inner.dec v, [])); 107 + enc = 108 + (function x, [] -> inner.enc x | x, fs -> Value.Option (inner.enc x, fs)); 109 + } 110 + 111 + let rec flatten_logop op = function 112 + | Value.Logop (op', a, b) when op' = op -> 113 + flatten_logop op a @ flatten_logop op b 114 + | v -> [ v ] 115 + 116 + let rec encode_constraint = function 117 + | [] -> failwith "Opam.constraint_: empty constraint" 118 + | [ (op, ver) ] -> Value.Prefix_relop (op, Value.String ver) 119 + | (op, ver) :: rest -> 120 + Value.Logop 121 + (`And, Value.Prefix_relop (op, Value.String ver), encode_constraint rest) 122 + 123 + let constraint_ = 124 + let dec_atom = function 125 + | Value.Prefix_relop (op, Value.String s) -> (op, s) 126 + | v -> err_expected "version constraint atom" v 127 + in 128 + { 129 + kind = "version constraint"; 130 + dec = 131 + (function 132 + | Value.Logop (`And, _, _) as v -> 133 + List.map dec_atom (flatten_logop `And v) 134 + | v -> [ dec_atom v ]); 135 + enc = encode_constraint; 136 + } 137 + 138 + (* ---- File / record codecs ---- *) 139 + 140 + module File = struct 141 + type 'a codec = 'a t 142 + 143 + type ('o, 'dec) builder = { 144 + kind : string; 145 + dec : Value.item list -> 'dec; 146 + encs : ('o -> Value.item list -> Value.item list) list; 147 + } 148 + 149 + type 'a t = { 150 + file_kind : string; 151 + file_dec : Value.file -> 'a; 152 + file_enc : 'a -> Value.file; 153 + } 154 + 155 + let kind (t : 'a t) = t.file_kind 156 + 157 + let obj ?kind:k ctor = 158 + let kind = match k with Some k -> k | None -> "opam record" in 159 + { kind; dec = (fun _ -> ctor); encs = [] } 160 + 161 + let lookup_value name items = 162 + let rec loop = function 163 + | [] -> None 164 + | Value.Variable (n, v) :: _ when n = name -> Some v 165 + | _ :: rest -> loop rest 166 + in 167 + loop items 168 + 169 + let push_field_ctx kind name f = 170 + try f () 171 + with Opam_error.Error e -> 172 + Opam_error.push_object (kind, Loc.Meta.none) (name, Loc.Meta.none) e 173 + 174 + let field ?dec_absent ~enc name (codec : 'a codec) 175 + (b : ('o, 'a -> 'dec) builder) : ('o, 'dec) builder = 176 + let kind = b.kind in 177 + let dec items = 178 + let f = b.dec items in 179 + match lookup_value name items with 180 + | Some v -> 181 + let decoded = push_field_ctx kind name (fun () -> codec.dec v) in 182 + f decoded 183 + | None -> ( 184 + match dec_absent with 185 + | Some default -> f default 186 + | None -> 187 + push_field_ctx kind name (fun () -> 188 + Opam_error.missing_field Loc.Meta.none name)) 189 + in 190 + let enc_step (o : 'o) acc = 191 + Value.Variable (name, codec.enc (enc o)) :: acc 192 + in 193 + { kind; dec; encs = enc_step :: b.encs } 194 + 195 + let opt ~enc name (codec : 'a codec) (b : ('o, 'a option -> 'dec) builder) : 196 + ('o, 'dec) builder = 197 + let kind = b.kind in 198 + let dec items = 199 + let f = b.dec items in 200 + match lookup_value name items with 201 + | Some v -> 202 + let decoded = 203 + push_field_ctx kind name (fun () -> Some (codec.dec v)) 204 + in 205 + f decoded 206 + | None -> f None 207 + in 208 + let enc_step (o : 'o) acc = 209 + match enc o with 210 + | None -> acc 211 + | Some v -> Value.Variable (name, codec.enc v) :: acc 212 + in 213 + { kind; dec; encs = enc_step :: b.encs } 214 + 215 + let finish (b : ('o, 'o) builder) : 'o t = 216 + let encs_in_order = List.rev b.encs in 217 + let file_dec (f : Value.file) = b.dec f.contents in 218 + let file_enc (o : 'o) = 219 + let items = List.fold_right (fun f acc -> f o acc) encs_in_order [] in 220 + Value.file items 221 + in 222 + { file_kind = b.kind; file_dec; file_enc } 223 + 224 + let of_file_exn (t : 'a t) f = t.file_dec f 225 + let to_file (t : 'a t) x = t.file_enc x 226 + 227 + let of_file t f = 228 + try Ok (of_file_exn t f) with Opam_error.Error e -> Error e 229 + end
+53
lib/codec.mli
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2026 Thomas Gazagnaire <thomas@gazagnaire.org> 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Opam codec combinators. Internal — exposed publicly as [Opam.Codec]. *) 7 + 8 + type 'a fmt = Format.formatter -> 'a -> unit 9 + 10 + type 'a t 11 + (** Codec for a single {!Value.t}. *) 12 + 13 + val kind : 'a t -> string 14 + val bool : bool t 15 + val int : int t 16 + val string : string t 17 + val ident : string t 18 + val list : 'a t -> 'a list t 19 + val option : 'a t -> 'a option t 20 + val map : ?kind:string -> dec:('a -> 'b) -> enc:('b -> 'a) -> 'a t -> 'b t 21 + val enum : ?kind:string -> (string * 'a) list -> 'a t 22 + val filtered : 'a t -> ('a * Value.t list) t 23 + val constraint_ : (Value.relop * string) list t 24 + 25 + (** File / record codecs. *) 26 + module File : sig 27 + type 'a codec = 'a t 28 + type 'a t 29 + type ('o, 'dec) builder 30 + 31 + val obj : ?kind:string -> 'dec -> ('o, 'dec) builder 32 + 33 + val field : 34 + ?dec_absent:'a -> 35 + enc:('o -> 'a) -> 36 + string -> 37 + 'a codec -> 38 + ('o, 'a -> 'dec) builder -> 39 + ('o, 'dec) builder 40 + 41 + val opt : 42 + enc:('o -> 'a option) -> 43 + string -> 44 + 'a codec -> 45 + ('o, 'a option -> 'dec) builder -> 46 + ('o, 'dec) builder 47 + 48 + val finish : ('o, 'o) builder -> 'o t 49 + val kind : 'a t -> string 50 + val of_file : 'a t -> Value.file -> ('a, Opam_error.t) result 51 + val of_file_exn : 'a t -> Value.file -> 'a 52 + val to_file : 'a t -> 'a -> Value.file 53 + end
+1 -1
lib/dune
··· 1 1 (library 2 2 (name opam) 3 3 (public_name opam) 4 - (modules opam value lexer parser printer opam_error) 4 + (modules opam value codec lexer parser printer opam_error) 5 5 (libraries 6 6 fmt 7 7 (re_export loc)))
+4 -221
lib/opam.ml
··· 8 8 module Path = Loc.Path 9 9 module Error = Opam_error 10 10 module Value = Value 11 + module Codec = Codec 11 12 module Lexer = Lexer 12 13 module Parser = Parser 13 14 module Printer = Printer 14 15 15 16 exception Error = Opam_error.Error 16 17 17 - type 'a fmt = Format.formatter -> 'a -> unit 18 - type 'a t = { kind : string; dec : Value.t -> 'a; enc : 'a -> Value.t } 18 + type 'a t = 'a Codec.t 19 + type 'a fmt = 'a Codec.fmt 19 20 20 - let kind c = c.kind 21 - 22 - let kind_name = function 23 - | Value.Bool _ -> "bool" 24 - | Value.Int _ -> "int" 25 - | Value.String _ -> "string" 26 - | Value.Ident _ -> "identifier" 27 - | Value.List _ -> "list" 28 - | Value.Group _ -> "group" 29 - | Value.Option _ -> "option" 30 - | Value.Relop _ -> "relational" 31 - | Value.Prefix_relop _ -> "prefix-relational" 32 - | Value.Logop _ -> "logical" 33 - | Value.Pfxop _ -> "prefix" 34 - | Value.Env_binding _ -> "env-binding" 35 - 36 - let err_expected exp v = 37 - Opam_error.sort_mismatch Loc.Meta.none ~expected:exp ~found:(kind_name v) 38 - 39 - let bool = 40 - { 41 - kind = "bool"; 42 - dec = (function Value.Bool b -> b | v -> err_expected "bool" v); 43 - enc = (fun b -> Value.Bool b); 44 - } 45 - 46 - let int = 47 - { 48 - kind = "int"; 49 - dec = (function Value.Int i -> i | v -> err_expected "int" v); 50 - enc = (fun i -> Value.Int i); 51 - } 52 - 53 - let string = 54 - { 55 - kind = "string"; 56 - dec = (function Value.String s -> s | v -> err_expected "string" v); 57 - enc = (fun s -> Value.String s); 58 - } 59 - 60 - let ident = 61 - { 62 - kind = "identifier"; 63 - dec = (function Value.Ident s -> s | v -> err_expected "identifier" v); 64 - enc = (fun s -> Value.Ident s); 65 - } 66 - 67 - let list elt = 68 - { 69 - kind = "list of " ^ elt.kind; 70 - dec = 71 - (function 72 - | Value.List xs -> List.map elt.dec xs 73 - | v -> err_expected "list" v); 74 - enc = (fun xs -> Value.List (List.map elt.enc xs)); 75 - } 76 - 77 - let option elt = 78 - { 79 - kind = "optional " ^ elt.kind; 80 - dec = (fun v -> Some (elt.dec v)); 81 - enc = (function Some v -> elt.enc v | None -> Value.List []); 82 - } 83 - 84 - let map ?kind:k ~dec ~enc c = 85 - let kind = match k with Some k -> k | None -> c.kind in 86 - { kind; dec = (fun v -> dec (c.dec v)); enc = (fun x -> c.enc (enc x)) } 87 - 88 - let enum ?kind:k cases = 89 - let kind = match k with Some k -> k | None -> "enum" in 90 - let rev = List.map (fun (s, v) -> (v, s)) cases in 91 - { 92 - kind; 93 - dec = 94 - (function 95 - | Value.Ident s | Value.String s -> ( 96 - match List.assoc_opt s cases with 97 - | Some v -> v 98 - | None -> 99 - Loc.Error.raise [] Loc.Meta.none (Opam_error.Unknown_enum_value s) 100 - ) 101 - | v -> err_expected "identifier or string" v); 102 - enc = 103 - (fun x -> 104 - match List.find_opt (fun (v, _) -> Stdlib.compare v x = 0) rev with 105 - | Some (_, s) -> Value.Ident s 106 - | None -> failwith "Opam.enum: value not in case list"); 107 - } 108 - 109 - let filtered inner = 110 - { 111 - kind = inner.kind ^ " (filtered)"; 112 - dec = 113 - (function 114 - | Value.Option (v, fs) -> (inner.dec v, fs) 115 - | v -> (inner.dec v, [])); 116 - enc = 117 - (function x, [] -> inner.enc x | x, fs -> Value.Option (inner.enc x, fs)); 118 - } 119 - 120 - let rec flatten_logop op = function 121 - | Value.Logop (op', a, b) when op' = op -> 122 - flatten_logop op a @ flatten_logop op b 123 - | v -> [ v ] 124 - 125 - let rec encode_constraint = function 126 - | [] -> failwith "Opam.constraint_: empty constraint" 127 - | [ (op, ver) ] -> Value.Prefix_relop (op, Value.String ver) 128 - | (op, ver) :: rest -> 129 - Value.Logop 130 - (`And, Value.Prefix_relop (op, Value.String ver), encode_constraint rest) 131 - 132 - let constraint_ = 133 - let dec_atom = function 134 - | Value.Prefix_relop (op, Value.String s) -> (op, s) 135 - | v -> err_expected "version constraint atom" v 136 - in 137 - { 138 - kind = "version constraint"; 139 - dec = 140 - (function 141 - | Value.Logop (`And, _, _) as v -> 142 - List.map dec_atom (flatten_logop `And v) 143 - | v -> [ dec_atom v ]); 144 - enc = encode_constraint; 145 - } 146 - 147 - (* ---- File / record codecs ---- *) 148 - 149 - module File = struct 150 - type 'a codec = 'a t 151 - 152 - type ('o, 'dec) builder = { 153 - kind : string; 154 - dec : Value.item list -> 'dec; 155 - encs : ('o -> Value.item list -> Value.item list) list; 156 - } 157 - 158 - type 'a t = { 159 - file_kind : string; 160 - file_dec : Value.file -> 'a; 161 - file_enc : 'a -> Value.file; 162 - } 163 - 164 - let kind (t : 'a t) = t.file_kind 165 - 166 - let obj ?kind:k ctor = 167 - let kind = match k with Some k -> k | None -> "opam record" in 168 - { kind; dec = (fun _ -> ctor); encs = [] } 169 - 170 - let lookup_value name items = 171 - let rec loop = function 172 - | [] -> None 173 - | Value.Variable (n, v) :: _ when n = name -> Some v 174 - | _ :: rest -> loop rest 175 - in 176 - loop items 177 - 178 - let push_field_ctx kind name f = 179 - try f () 180 - with Opam_error.Error e -> 181 - Opam_error.push_object (kind, Loc.Meta.none) (name, Loc.Meta.none) e 182 - 183 - let field ?dec_absent ~enc name (codec : 'a codec) 184 - (b : ('o, 'a -> 'dec) builder) : ('o, 'dec) builder = 185 - let kind = b.kind in 186 - let dec items = 187 - let f = b.dec items in 188 - match lookup_value name items with 189 - | Some v -> 190 - let decoded = push_field_ctx kind name (fun () -> codec.dec v) in 191 - f decoded 192 - | None -> ( 193 - match dec_absent with 194 - | Some default -> f default 195 - | None -> 196 - push_field_ctx kind name (fun () -> 197 - Opam_error.missing_field Loc.Meta.none name)) 198 - in 199 - let enc_step (o : 'o) acc = 200 - Value.Variable (name, codec.enc (enc o)) :: acc 201 - in 202 - { kind; dec; encs = enc_step :: b.encs } 203 - 204 - let opt ~enc name (codec : 'a codec) (b : ('o, 'a option -> 'dec) builder) : 205 - ('o, 'dec) builder = 206 - let kind = b.kind in 207 - let dec items = 208 - let f = b.dec items in 209 - match lookup_value name items with 210 - | Some v -> 211 - let decoded = 212 - push_field_ctx kind name (fun () -> Some (codec.dec v)) 213 - in 214 - f decoded 215 - | None -> f None 216 - in 217 - let enc_step (o : 'o) acc = 218 - match enc o with 219 - | None -> acc 220 - | Some v -> Value.Variable (name, codec.enc v) :: acc 221 - in 222 - { kind; dec; encs = enc_step :: b.encs } 223 - 224 - let finish (b : ('o, 'o) builder) : 'o t = 225 - let encs_in_order = List.rev b.encs in 226 - let file_dec (f : Value.file) = b.dec f.contents in 227 - let file_enc (o : 'o) = 228 - let items = List.fold_right (fun f acc -> f o acc) encs_in_order [] in 229 - Value.file items 230 - in 231 - { file_kind = b.kind; file_dec; file_enc } 232 - 233 - let of_file_exn (t : 'a t) f = t.file_dec f 234 - let to_file (t : 'a t) x = t.file_enc x 235 - 236 - let of_file t f = 237 - try Ok (of_file_exn t f) with Opam_error.Error e -> Error e 238 - end 21 + module File = Codec.File 239 22 240 23 let decode_string fc s = 241 24 try
+16 -101
lib/opam.mli
··· 5 5 6 6 (** Type-safe codecs for opam files. 7 7 8 - Two layers of codecs: 9 - - {{!t} ['a t]} maps a single {!Value.t} to/from an OCaml value. 10 - - {{!File.t} ['a File.t]} maps a whole {!Value.file} to/from an OCaml record 11 - by composing field codecs. 8 + The raw AST lives in {!Value}, the codec combinators in {!Codec}. The 9 + top-level {!t} alias lets you write [string Opam.t] instead of 10 + [string Opam.Codec.t]. 12 11 13 12 {2 Quick Start} 14 13 ··· 16 15 type pkg = { name : string; version : string; depends : string list } 17 16 18 17 let pkg_codec : pkg Opam.File.t = 19 - Opam.File.( 18 + Opam.Codec.File.( 20 19 obj (fun name version depends -> { name; version; depends }) 21 - |> field "name" Opam.string ~enc:(fun p -> p.name) 22 - |> field "version" Opam.string ~enc:(fun p -> p.version) 20 + |> field "name" Opam.Codec.string ~enc:(fun p -> p.name) 21 + |> field "version" Opam.Codec.string ~enc:(fun p -> p.version) 23 22 |> field "depends" 24 - Opam.(list string) 23 + Opam.Codec.(list string) 25 24 ~enc:(fun p -> p.depends) 26 25 ~dec_absent:[] 27 26 |> finish) ··· 31 30 32 31 For raw value parsing without codecs, see {!Opam_bytesrw}. *) 33 32 34 - (** {1:re_exports Re-exports} *) 33 + (** {1 Re-exports} *) 35 34 36 35 module Loc = Loc 37 36 module Meta = Loc.Meta 38 37 module Path = Loc.Path 39 38 module Error = Opam_error 40 39 module Value = Value 40 + module Codec = Codec 41 41 module Lexer = Lexer 42 42 module Parser = Parser 43 43 module Printer = Printer 44 44 45 45 exception Error of Opam_error.t 46 46 47 - (** {1:codec Value codecs} *) 47 + type 'a t = 'a Codec.t 48 + (** [Opam.t] is [Opam.Codec.t]. Ergonomic alias so users can write 49 + [string Opam.t] instead of [string Opam.Codec.t]. *) 48 50 49 - type 'a t 50 - (** A codec for a single opam {!Value.t}. *) 51 + type 'a fmt = 'a Codec.fmt 51 52 52 - type 'a fmt = Format.formatter -> 'a -> unit 53 + module File = Codec.File 54 + (** [Opam.File.t] is [Opam.Codec.File.t]. *) 53 55 54 - val kind : 'a t -> string 55 - (** [kind c] is a short human-readable name used in error messages. *) 56 - 57 - val bool : bool t 58 - (** Codec for opam booleans. *) 59 - 60 - val int : int t 61 - (** Codec for opam integers. *) 62 - 63 - val string : string t 64 - (** Codec for opam strings (quoted). *) 65 - 66 - val ident : string t 67 - (** Codec for opam identifiers (unquoted). *) 68 - 69 - val list : 'a t -> 'a list t 70 - (** [list c] is the codec for a homogeneous opam list. *) 71 - 72 - val option : 'a t -> 'a option t 73 - (** [option c] wraps decoded values in [Some]; encodes [None] as the empty list. 74 - For absent record fields, see {!File.opt}. *) 75 - 76 - val map : ?kind:string -> dec:('a -> 'b) -> enc:('b -> 'a) -> 'a t -> 'b t 77 - (** [map ~dec ~enc c] transforms a codec [c] from ['a] to ['b]. *) 78 - 79 - val enum : ?kind:string -> (string * 'a) list -> 'a t 80 - (** [enum cases] is a codec restricted to the [(label, value)] cases. *) 81 - 82 - (** {1:opam Opam-specific value codecs} *) 83 - 84 - val filtered : 'a t -> ('a * Value.t list) t 85 - (** [filtered c] decodes [v {f1 f2 ...}] as a pair of underlying value and 86 - filter expressions, or just [v] (with [[]]). *) 87 - 88 - val constraint_ : (Value.relop * string) list t 89 - (** [constraint_] decodes a version constraint expression like 90 - [>= "1.0" & < "2.0"] as a list of [(operator, version)] pairs. *) 91 - 92 - (** {1:file File codecs} *) 93 - 94 - module File : sig 95 - type 'a codec = 'a t 96 - 97 - type 'a t 98 - (** A codec for a {!Value.file}. *) 99 - 100 - type ('o, 'dec) builder 101 - 102 - val obj : ?kind:string -> 'dec -> ('o, 'dec) builder 103 - (** [obj ctor] starts a record builder. [ctor] is the constructor expecting 104 - one argument per [field] / [opt] call (in declaration order). *) 105 - 106 - val field : 107 - ?dec_absent:'a -> 108 - enc:('o -> 'a) -> 109 - string -> 110 - 'a codec -> 111 - ('o, 'a -> 'dec) builder -> 112 - ('o, 'dec) builder 113 - (** [field name c b] requires field [name] of value-codec [c] in the record. 114 - [dec_absent] supplies a default if the field is missing. Without 115 - [dec_absent], the field is required. *) 116 - 117 - val opt : 118 - enc:('o -> 'a option) -> 119 - string -> 120 - 'a codec -> 121 - ('o, 'a option -> 'dec) builder -> 122 - ('o, 'dec) builder 123 - (** [opt name c b] adds an optional field. *) 124 - 125 - val finish : ('o, 'o) builder -> 'o t 126 - (** [finish b] turns a complete builder into a file-level codec. *) 127 - 128 - val kind : 'a t -> string 129 - (** [kind t] is the codec's kind label. *) 130 - 131 - val of_file : 'a t -> Value.file -> ('a, Error.t) result 132 - (** [of_file t f] decodes file [f] with codec [t]. *) 133 - 134 - val of_file_exn : 'a t -> Value.file -> 'a 135 - (** [of_file_exn t f] is like {!of_file} but raises {!Error}. *) 136 - 137 - val to_file : 'a t -> 'a -> Value.file 138 - (** [to_file t x] encodes record [x] as a file. *) 139 - end 140 - 141 - (** {1:io Decoding and encoding strings} *) 56 + (** {1 String I/O} *) 142 57 143 58 val decode_string : 'a File.t -> string -> ('a, Error.t) result 144 59 (** [decode_string c s] parses [s] and decodes it with [c]. *)
+1 -1
opam.opam
··· 10 10 maintainer: ["Thomas Gazagnaire <thomas@gazagnaire.org>"] 11 11 authors: ["Thomas Gazagnaire <thomas@gazagnaire.org>"] 12 12 license: "ISC" 13 - tags: ["org:blacksun" "codec" "format" "opam"] 13 + tags: ["org:blacksun" "codec.text"] 14 14 homepage: "https://tangled.org/gazagnaire.org/ocaml-opam" 15 15 bug-reports: "https://tangled.org/gazagnaire.org/ocaml-opam/issues" 16 16 depends: [
+1
test/test_opam.ml
··· 4 4 ---------------------------------------------------------------------------*) 5 5 6 6 open Opam 7 + open Opam.Codec 7 8 8 9 type pkg = { 9 10 opam_version : string;