Streaming opam file codec for OCaml
0
fork

Configure Feed

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

opam: satisfy merlint after Codec extraction

- Add pp to Codec.t and re-export at Opam top level (E415 wants the
main type to have a pretty-printer).
- Add test_codec.ml + .mli stub exercising kind on the basic combinators
(E605 requires test files for every library module).
- Flesh out codec.mli with per-value doc comments (E400).

+87 -3
+1
lib/codec.ml
··· 9 9 type 'a t = { kind : string; dec : Value.t -> 'a; enc : 'a -> Value.t } 10 10 11 11 let kind c = c.kind 12 + let pp ppf c = Fmt.string ppf c.kind 12 13 13 14 let kind_name = function 14 15 | Value.Bool _ -> "bool"
+45 -3
lib/codec.mli
··· 3 3 SPDX-License-Identifier: ISC 4 4 ---------------------------------------------------------------------------*) 5 5 6 - (** Opam codec combinators. Internal — exposed publicly as [Opam.Codec]. *) 6 + (** Opam codec combinators. Internal — exposed publicly as {!Opam.Codec}. *) 7 7 8 8 type 'a fmt = Format.formatter -> 'a -> unit 9 9 10 10 type 'a t 11 - (** Codec for a single {!Value.t}. *) 11 + (** [t] is the codec for a single {!Value.t}. *) 12 12 13 13 val kind : 'a t -> string 14 + (** [kind c] is the kind label for [c]. *) 15 + 16 + val pp : 'a t Fmt.t 17 + (** [pp] prints a codec's kind. *) 18 + 14 19 val bool : bool t 20 + (** [bool] is the opam boolean codec. *) 21 + 15 22 val int : int t 23 + (** [int] is the opam integer codec. *) 24 + 16 25 val string : string t 26 + (** [string] is the opam string codec (quoted strings). *) 27 + 17 28 val ident : string t 29 + (** [ident] is the opam identifier codec (unquoted). *) 30 + 18 31 val list : 'a t -> 'a list t 32 + (** [list c] is the homogeneous-list codec over [c]. *) 33 + 19 34 val option : 'a t -> 'a option t 35 + (** [option c] wraps decoded values in [Some]; [None] encodes as the empty 36 + list. *) 37 + 20 38 val map : ?kind:string -> dec:('a -> 'b) -> enc:('b -> 'a) -> 'a t -> 'b t 39 + (** [map ~dec ~enc c] transforms codec [c] through the given functions. *) 40 + 21 41 val enum : ?kind:string -> (string * 'a) list -> 'a t 42 + (** [enum cases] is a codec restricted to the given labels. *) 43 + 22 44 val filtered : 'a t -> ('a * Value.t list) t 45 + (** [filtered c] decodes [v {f1 f2 ...}] as a pair of value and filters. *) 46 + 23 47 val constraint_ : (Value.relop * string) list t 48 + (** [constraint_] decodes a version constraint expression. *) 24 49 25 - (** File / record codecs. *) 50 + (** File-level codecs (record-shaped opam files). *) 26 51 module File : sig 27 52 type 'a codec = 'a t 53 + (** [codec] is an alias for the value codec. *) 54 + 28 55 type 'a t 56 + (** [t] is a file-level codec. *) 57 + 29 58 type ('o, 'dec) builder 59 + (** [builder] is the intermediate state while composing field codecs. *) 30 60 31 61 val obj : ?kind:string -> 'dec -> ('o, 'dec) builder 62 + (** [obj ctor] starts a record builder with constructor [ctor]. *) 32 63 33 64 val field : 34 65 ?dec_absent:'a -> ··· 37 68 'a codec -> 38 69 ('o, 'a -> 'dec) builder -> 39 70 ('o, 'dec) builder 71 + (** [field name c b] adds a required field. *) 40 72 41 73 val opt : 42 74 enc:('o -> 'a option) -> ··· 44 76 'a codec -> 45 77 ('o, 'a option -> 'dec) builder -> 46 78 ('o, 'dec) builder 79 + (** [opt name c b] adds an optional field. *) 47 80 48 81 val finish : ('o, 'o) builder -> 'o t 82 + (** [finish b] seals the builder. *) 83 + 49 84 val kind : 'a t -> string 85 + (** [kind t] is the codec's kind label. *) 86 + 50 87 val of_file : 'a t -> Value.file -> ('a, Opam_error.t) result 88 + (** [of_file t f] decodes [f] using [t]. *) 89 + 51 90 val of_file_exn : 'a t -> Value.file -> 'a 91 + (** [of_file_exn t f] is like {!of_file} but raises {!Opam_error.Error}. *) 92 + 52 93 val to_file : 'a t -> 'a -> Value.file 94 + (** [to_file t x] encodes [x] as a file. *) 53 95 end
+2
lib/opam.ml
··· 18 18 type 'a t = 'a Codec.t 19 19 type 'a fmt = 'a Codec.fmt 20 20 21 + let pp = Codec.pp 22 + 21 23 module File = Codec.File 22 24 23 25 let decode_string fc s =
+3
lib/opam.mli
··· 50 50 51 51 type 'a fmt = 'a Codec.fmt 52 52 53 + val pp : 'a t Fmt.t 54 + (** [pp] prints a codec's kind. *) 55 + 53 56 module File = Codec.File 54 57 (** [Opam.File.t] is [Opam.Codec.File.t]. *) 55 58
+1
test/dune
··· 3 3 (modules 4 4 test 5 5 test_value 6 + test_codec 6 7 test_lexer 7 8 test_parser 8 9 test_printer
+1
test/test.ml
··· 7 7 Alcotest.run "opam" 8 8 [ 9 9 Test_value.suite; 10 + Test_codec.suite; 10 11 Test_lexer.suite; 11 12 Test_parser.suite; 12 13 Test_printer.suite;
+32
test/test_codec.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2026 Thomas Gazagnaire <thomas@gazagnaire.org> 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Tests for {!Opam.Codec}. The heavy integration tests live in 7 + {!Test_opam}; these verify the combinators in isolation. *) 8 + 9 + let test_bool_kind () = 10 + Alcotest.(check string) "bool kind" "bool" (Opam.Codec.kind Opam.Codec.bool) 11 + 12 + let test_int_kind () = 13 + Alcotest.(check string) "int kind" "int" (Opam.Codec.kind Opam.Codec.int) 14 + 15 + let test_list_kind () = 16 + let c = Opam.Codec.list Opam.Codec.int in 17 + Alcotest.(check string) "list kind" "list of int" (Opam.Codec.kind c) 18 + 19 + let test_map_kind () = 20 + let c = Opam.Codec.map ~dec:succ ~enc:pred Opam.Codec.int in 21 + Alcotest.(check string) "map inherits kind" "int" (Opam.Codec.kind c); 22 + let c' = Opam.Codec.map ~kind:"pos_int" ~dec:succ ~enc:pred Opam.Codec.int in 23 + Alcotest.(check string) "map override kind" "pos_int" (Opam.Codec.kind c') 24 + 25 + let suite = 26 + ( "codec", 27 + [ 28 + Alcotest.test_case "bool kind" `Quick test_bool_kind; 29 + Alcotest.test_case "int kind" `Quick test_int_kind; 30 + Alcotest.test_case "list kind" `Quick test_list_kind; 31 + Alcotest.test_case "map kind" `Quick test_map_kind; 32 + ] )
+2
test/test_codec.mli
··· 1 + val suite : string * unit Alcotest.test_case list 2 + (** [suite] is the test suite for {!Opam.Codec}. *)