Declarative CSV codecs
0
fork

Configure Feed

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

Fix xmlt lint: err_ helpers, names, failf, ocamlformat

- xmlt: 12 err_* helpers for consistent error messages
- xmlt: shorter test names, .ocamlformat, failf
- sexpt: rename get_mem→mem, get_nth→nth (drop redundant prefix)

+68 -50
+12
lib/csvt.ml
··· 442 442 w (String.concat "," (Array.to_list fields)); 443 443 w "\n") 444 444 rows 445 + 446 + module Private = struct 447 + type nonrec header = header 448 + type nonrec 'a resolved = 'a resolved 449 + 450 + let resolve = resolve 451 + let decode_row = decode_row 452 + let decode_field = decode_field 453 + let encode_field = encode_field 454 + let encode_header = encode_header 455 + let encode_row = encode_row 456 + end
+33 -33
lib/csvt.mli
··· 81 81 dec:(string -> ('a, string) result) -> enc:('a -> string) -> unit -> 'a t 82 82 (** [col_map ~dec ~enc ()] creates a custom field codec. *) 83 83 84 - (** {1:decode Decode and Encode} *) 85 - 86 - val decode_field : 'a t -> string -> ('a, string) result 87 - (** [decode_field t s] decodes string [s] using CSV type [t]. *) 88 - 89 - val encode_field : 'a t -> 'a -> string 90 - (** [encode_field t v] encodes value [v] to a string using CSV type [t]. *) 91 - 92 84 (** {1:introspect Introspection} *) 93 85 94 86 val col_names : 'a t -> string list ··· 140 132 (** [finish m] completes the row codec. *) 141 133 end 142 134 143 - (** {1:header Header Resolution} *) 144 - 145 - type header = string array 146 - (** A CSV header: array of column names. *) 147 - 148 - type 'a resolved 149 - (** A codec resolved against a specific header. Column name lookups are 150 - performed once, giving O(1) per-row decoding. *) 151 - 152 - val resolve : 'a t -> header -> ('a resolved, error) result 153 - (** [resolve t header] resolves column names to indices. *) 154 - 155 - val decode_row : 'a resolved -> int -> string array -> ('a, error) result 156 - (** [decode_row resolved row_num fields] decodes a single CSV row. *) 157 - 158 135 (** {1:update Update} *) 159 136 160 137 val update_col : 161 138 string -> 162 139 'a t -> 163 140 ('a -> 'a) -> 164 - header -> 141 + string array -> 165 142 string array -> 166 143 (string array, error) result 167 144 (** [update_col name codec f header row] decodes column [name] from [row] using 168 145 [codec], applies [f] to the typed value, re-encodes, and returns the updated 169 146 row. Other columns are unchanged. *) 170 147 171 - val delete_col : string -> header -> string array -> header * string array 148 + val delete_col : 149 + string -> string array -> string array -> string array * string array 172 150 (** [delete_col name header row] removes the column [name] from both the header 173 151 and the row. Returns the new header and row. If [name] is not found, returns 174 152 both unchanged. *) 175 153 176 - (** {1:encode Encoding} *) 177 - 178 - val encode_header : 'a t -> header 179 - (** [encode_header t] returns the CSV header for encoding. *) 180 - 181 - val encode_row : 'a t -> 'a -> string array 182 - (** [encode_row t v] encodes a value as a CSV row. *) 183 - 184 154 (** {1:file File Operations} *) 185 155 186 156 val decode_channel : 'a t -> in_channel -> ('a list, error) result ··· 206 176 207 177 val encode : 'a t -> 'a list -> Bytesrw.Bytes.Writer.t -> unit 208 178 (** [encode t rows w] encodes [rows] as CSV and writes them to writer [w]. *) 179 + 180 + (** {1:private_ Private} 181 + 182 + {b For internal use by sibling libraries only.} These functions operate on 183 + raw string arrays and are not part of the public codec API. *) 184 + module Private : sig 185 + type header = string array 186 + (** A CSV header: array of column names. *) 187 + 188 + type 'a resolved 189 + (** A codec resolved against a specific header. *) 190 + 191 + val resolve : 'a t -> header -> ('a resolved, error) result 192 + (** [resolve t header] resolves column names to indices. *) 193 + 194 + val decode_row : 'a resolved -> int -> string array -> ('a, error) result 195 + (** [decode_row resolved row_num fields] decodes a single CSV row. *) 196 + 197 + val decode_field : 'a t -> string -> ('a, string) result 198 + (** [decode_field t s] decodes string [s] using CSV type [t]. *) 199 + 200 + val encode_field : 'a t -> 'a -> string 201 + (** [encode_field t v] encodes value [v] to a string using CSV type [t]. *) 202 + 203 + val encode_header : 'a t -> header 204 + (** [encode_header t] returns the CSV header for encoding. *) 205 + 206 + val encode_row : 'a t -> 'a -> string array 207 + (** [encode_row t v] encodes a value as a CSV row. *) 208 + end
+23 -17
test/test_csvt.ml
··· 236 236 (* {1 Encoding tests} *) 237 237 238 238 let test_encode_header () = 239 - let h = Csvt.encode_header point_codec in 239 + let h = Csvt.Private.encode_header point_codec in 240 240 Alcotest.(check int) "ncols" 3 (Array.length h); 241 241 Alcotest.(check string) "col0" "x" h.(0); 242 242 Alcotest.(check string) "col1" "y" h.(1); 243 243 Alcotest.(check string) "col2" "label" h.(2) 244 244 245 245 let test_encode_row () = 246 - let row = Csvt.encode_row point_codec { x = 1.5; y = 2.5; label = "test" } in 246 + let row = 247 + Csvt.Private.encode_row point_codec { x = 1.5; y = 2.5; label = "test" } 248 + in 247 249 Alcotest.(check int) "ncols" 3 (Array.length row); 248 250 Alcotest.(check string) "label" "test" row.(2) 249 251 250 252 let test_encode_record () = 251 253 let row = 252 - Csvt.encode_row record_codec 254 + Csvt.Private.encode_row record_codec 253 255 { id = 42; name = "bob"; score = 99.9; active = true } 254 256 in 255 257 Alcotest.(check string) "id" "42" row.(0); ··· 603 605 (* string *) 604 606 Alcotest.(check (result string string)) 605 607 "string dec" (Ok "hello") 606 - (Csvt.decode_field Csvt.string "hello"); 608 + (Csvt.Private.decode_field Csvt.string "hello"); 607 609 Alcotest.(check string) 608 610 "string enc" "hello" 609 - (Csvt.encode_field Csvt.string "hello"); 611 + (Csvt.Private.encode_field Csvt.string "hello"); 610 612 (* int *) 611 613 Alcotest.(check (result int string)) 612 614 "int dec" (Ok 42) 613 - (Csvt.decode_field Csvt.int "42"); 614 - Alcotest.(check string) "int enc" "42" (Csvt.encode_field Csvt.int 42); 615 + (Csvt.Private.decode_field Csvt.int "42"); 616 + Alcotest.(check string) "int enc" "42" (Csvt.Private.encode_field Csvt.int 42); 615 617 (* float *) 616 - (match Csvt.decode_field Csvt.float "3.14" with 618 + (match Csvt.Private.decode_field Csvt.float "3.14" with 617 619 | Ok f -> Alcotest.(check (float 1e-6)) "float dec" 3.14 f 618 620 | Error e -> Alcotest.failf "float dec: %s" e); 619 - Alcotest.(check string) "float enc" "3.14" (Csvt.encode_field Csvt.float 3.14); 621 + Alcotest.(check string) 622 + "float enc" "3.14" 623 + (Csvt.Private.encode_field Csvt.float 3.14); 620 624 (* bool *) 621 625 Alcotest.(check (result bool string)) 622 626 "bool dec" (Ok true) 623 - (Csvt.decode_field Csvt.bool "yes"); 624 - Alcotest.(check string) "bool enc" "true" (Csvt.encode_field Csvt.bool true); 627 + (Csvt.Private.decode_field Csvt.bool "yes"); 628 + Alcotest.(check string) 629 + "bool enc" "true" 630 + (Csvt.Private.encode_field Csvt.bool true); 625 631 (* option *) 626 632 Alcotest.(check (result (option int) string)) 627 633 "option dec none" (Ok None) 628 - (Csvt.decode_field (Csvt.option Csvt.int) "NULL"); 634 + (Csvt.Private.decode_field (Csvt.option Csvt.int) "NULL"); 629 635 Alcotest.(check (result (option int) string)) 630 636 "option dec some" (Ok (Some 7)) 631 - (Csvt.decode_field (Csvt.option Csvt.int) "7"); 637 + (Csvt.Private.decode_field (Csvt.option Csvt.int) "7"); 632 638 Alcotest.(check string) 633 639 "option enc none" "NULL" 634 - (Csvt.encode_field (Csvt.option Csvt.int) None); 640 + (Csvt.Private.encode_field (Csvt.option Csvt.int) None); 635 641 Alcotest.(check string) 636 642 "option enc some" "7" 637 - (Csvt.encode_field (Csvt.option Csvt.int) (Some 7)) 643 + (Csvt.Private.encode_field (Csvt.option Csvt.int) (Some 7)) 638 644 639 645 (* {1 Query tests (get_col)} *) 640 646 ··· 662 668 663 669 let test_get_col_encode () = 664 670 let codec = Csvt.get_col "v" Csvt.int in 665 - let h = Csvt.encode_header codec in 671 + let h = Csvt.Private.encode_header codec in 666 672 Alcotest.(check (array string)) "header" [| "v" |] h; 667 - let row = Csvt.encode_row codec 42 in 673 + let row = Csvt.Private.encode_row codec 42 in 668 674 Alcotest.(check (array string)) "row" [| "42" |] row 669 675 670 676 let test_get_col_col_names () =