Bytesrw adapter for Eio
ocaml codec
0
fork

Configure Feed

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

Merge branch 'main' of https://git.recoil.org/gazagnaire.org/ocaml-bytesrw-eio

# Conflicts:
# .ocamlformat
# README.md
# bytesrw-eio.opam
# dune-project
# test/dune
# test/test_bytesrw_eio.ml

+193 -28
+1 -1
.ocamlformat
··· 1 - version = 0.28.1 1 + version = 0.29.0
+22 -5
README.md
··· 26 26 27 27 ```ocaml 28 28 (* Specify custom slice length for reading *) 29 - let reader = Bytesrw_eio.bytes_reader_of_flow ~slice_length:4096 flow in 29 + let reader flow = Bytesrw_eio.bytes_reader_of_flow ~slice_length:4096 flow 30 30 31 31 (* Specify custom slice length for writing *) 32 - let writer = Bytesrw_eio.bytes_writer_of_flow ~slice_length:4096 flow in 33 - () 32 + let writer flow = Bytesrw_eio.bytes_writer_of_flow ~slice_length:4096 flow 33 + ``` 34 + 35 + For random-access file reading: 36 + 37 + ```ocaml 38 + (* Read from a specific offset in a file *) 39 + let reader file = Bytesrw_eio.pread_reader ~offset:1024 file 34 40 ``` 35 41 36 42 ## Installation 37 43 44 + Install with opam: 45 + 46 + ```sh 47 + $ opam install bytesrw-eio 38 48 ``` 39 - opam install bytesrw-eio 49 + 50 + If opam cannot find the package, it may not yet be released in the public 51 + `opam-repository`. Add the overlay repository, then install it: 52 + 53 + ```sh 54 + $ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git 55 + $ opam update 56 + $ opam install bytesrw-eio 40 57 ``` 41 58 42 59 ## Documentation ··· 48 65 odig doc bytesrw-eio 49 66 ``` 50 67 51 - ## License 68 + ## Licence 52 69 53 70 ISC
+3
bytesrw-eio.opam
··· 6 6 maintainer: ["Anil Madhavapeddy <anil@recoil.org>"] 7 7 authors: ["Anil Madhavapeddy"] 8 8 license: "ISC" 9 + tags: ["org:blacksun" "eio"] 9 10 homepage: "https://tangled.org/anil.recoil.org/ocaml-bytesrw-eio" 10 11 bug-reports: "https://tangled.org/anil.recoil.org/ocaml-bytesrw-eio/issues" 11 12 depends: [ ··· 15 16 "eio" {>= "1.0"} 16 17 "odoc" {with-doc} 17 18 "alcotest" {with-test & >= "1.7.0"} 19 + "mdx" {with-test} 18 20 "eio_main" {with-test} 19 21 ] 20 22 build: [ ··· 33 35 ] 34 36 dev-repo: "git+https://tangled.org/anil.recoil.org/ocaml-bytesrw-eio" 35 37 x-maintenance-intent: ["(latest)"] 38 + x-quality-test: "2026-04-15"
+1
bytesrw-eio.opam.template
··· 1 + x-quality-test: "2026-04-15"
+7
dune
··· 1 + (env 2 + (dev 3 + (flags :standard %{dune-warnings}))) 4 + 5 + (mdx 6 + (files README.md) 7 + (libraries bytesrw-eio eio))
+3
dune-project
··· 1 1 (lang dune 3.21) 2 + (using mdx 0.4) 2 3 (name bytesrw-eio) 3 4 4 5 (generate_opam_files true) ··· 11 12 (package 12 13 (name bytesrw-eio) 13 14 (synopsis "Bytesrw readers and writers for Eio") 15 + (tags (org:blacksun eio)) 14 16 (description "Provides Bytesrw.Bytes.Reader and Writer adapters for Eio Flows") 15 17 (depends 16 18 (ocaml (>= 5.0)) ··· 18 20 (eio (>= 1.0)) 19 21 (odoc :with-doc) 20 22 (alcotest (and :with-test (>= 1.7.0))) 23 + (mdx :with-test) 21 24 (eio_main :with-test)))
+85
lib/bytesrw_eio.ml
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Bytesrw adapters for Eio 7 + 8 + This module provides adapters to create {!Bytesrw.Bytes.Reader.t} and 9 + {!Bytesrw.Bytes.Writer.t} from Eio flows, mirroring the API of 10 + {!Bytesrw_unix} for Eio's effect-based I/O. *) 11 + 12 + open Bytesrw 13 + 14 + (** Create a [Bytes.Reader.t] from an Eio source flow. 15 + 16 + Reads directly from the flow without intermediate buffering. 17 + 18 + @param slice_length 19 + Maximum bytes per slice (default: 65536, which is 20 + {!Bytes.Slice.unix_io_buffer_size}) *) 21 + let bytes_reader_of_flow ?(slice_length = Bytes.Slice.unix_io_buffer_size) 22 + (flow : _ Eio.Flow.source) : Bytes.Reader.t = 23 + let buf_size = Bytes.Slice.check_length slice_length in 24 + let read () = 25 + let cstruct = Cstruct.create buf_size in 26 + match Eio.Flow.single_read flow cstruct with 27 + | 0 -> Bytes.Slice.eod 28 + | count -> 29 + let data_cs = Cstruct.sub cstruct 0 count in 30 + let buf = Cstruct.to_bytes data_cs in 31 + Bytes.Slice.make buf ~first:0 ~length:count 32 + | exception End_of_file -> Bytes.Slice.eod 33 + in 34 + Bytes.Reader.make ~slice_length read 35 + 36 + (** Create a [Bytes.Reader.t] from an Eio file at [offset]. 37 + 38 + Uses pread for random-access reading without loading the whole file. 39 + 40 + @param slice_length Maximum bytes per slice (default: 65536). 41 + @param length Number of bytes to read (default: to end of file). *) 42 + let pread_reader ?(slice_length = Bytes.Slice.unix_io_buffer_size) ?length 43 + ~offset (file : _ Eio.File.ro) : Bytes.Reader.t = 44 + let stat = Eio.File.stat file in 45 + let file_length = Optint.Int63.to_int stat.size in 46 + let total = match length with Some n -> n | None -> file_length - offset in 47 + let pos = ref 0 in 48 + let buf_size = Bytes.Slice.check_length slice_length in 49 + let buf = Bytes.create buf_size in 50 + let read () = 51 + if !pos >= total then Bytes.Slice.eod 52 + else 53 + let len = min buf_size (total - !pos) in 54 + let cs = Cstruct.create len in 55 + Eio.File.pread_exact file 56 + ~file_offset:(Optint.Int63.of_int (offset + !pos)) 57 + [ cs ]; 58 + Cstruct.blit_to_bytes cs 0 buf 0 len; 59 + pos := !pos + len; 60 + Bytes.Slice.make buf ~first:0 ~length:len 61 + in 62 + Bytes.Reader.make ~slice_length read 63 + 64 + (** Create a [Bytes.Writer.t] from an Eio sink flow. 65 + 66 + Writes directly to the flow without intermediate buffering. 67 + 68 + @param slice_length 69 + Suggested slice length for upstream (default: 65536, which is 70 + {!Bytes.Slice.unix_io_buffer_size}) *) 71 + let bytes_writer_of_flow ?(slice_length = Bytes.Slice.unix_io_buffer_size) 72 + (flow : _ Eio.Flow.sink) : Bytes.Writer.t = 73 + let rec write slice = 74 + if Bytes.Slice.is_eod slice then () 75 + else begin 76 + let bytes = Bytes.Slice.bytes slice in 77 + let first = Bytes.Slice.first slice in 78 + let length = Bytes.Slice.length slice in 79 + let cstruct = Cstruct.of_bytes ~off:first ~len:length bytes in 80 + match Eio.Flow.single_write flow [ cstruct ] with 81 + | count when count = length -> () 82 + | count -> write (Option.get (Bytes.Slice.drop count slice)) 83 + end 84 + in 85 + Bytes.Writer.make ~slice_length write
+48
lib/bytesrw_eio.mli
··· 1 + (*--------------------------------------------------------------------------- 2 + Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 + SPDX-License-Identifier: ISC 4 + ---------------------------------------------------------------------------*) 5 + 6 + (** Bytesrw adapters for Eio 7 + 8 + This module provides adapters to create {!Bytesrw.Bytes.Reader.t} and 9 + {!Bytesrw.Bytes.Writer.t} from Eio flows, mirroring the API of 10 + [Bytesrw_unix] for Eio's effect-based I/O. 11 + 12 + Unlike the Buf_read/Buf_write wrappers, these adapters read and write 13 + directly to the flow, allowing bytesrw to handle its own buffering. *) 14 + 15 + (** {1 Readers} *) 16 + 17 + val bytes_reader_of_flow : 18 + ?slice_length:int -> _ Eio.Flow.source -> Bytesrw.Bytes.Reader.t 19 + (** [bytes_reader_of_flow flow] creates a reader from an Eio source flow. 20 + 21 + Reads directly from the flow without intermediate buffering. 22 + 23 + @param slice_length Maximum bytes per slice (default: 65536). *) 24 + 25 + (** {1 Random-access readers} *) 26 + 27 + val pread_reader : 28 + ?slice_length:int -> 29 + ?length:int -> 30 + offset:int -> 31 + _ Eio.File.ro -> 32 + Bytesrw.Bytes.Reader.t 33 + (** [pread_reader ~offset file] creates a reader from [file] starting at 34 + [offset] bytes. Reads via pread on demand — does not load the file into 35 + memory. 36 + 37 + @param slice_length Maximum bytes per slice (default: 65536). 38 + @param length Number of bytes to read (default: to end of file). *) 39 + 40 + (** {1 Writers} *) 41 + 42 + val bytes_writer_of_flow : 43 + ?slice_length:int -> _ Eio.Flow.sink -> Bytesrw.Bytes.Writer.t 44 + (** [bytes_writer_of_flow flow] creates a writer from an Eio sink flow. 45 + 46 + Writes directly to the flow without intermediate buffering. 47 + 48 + @param slice_length Suggested slice length for upstream (default: 65536). *)
+4
lib/dune
··· 1 + (library 2 + (name bytesrw_eio) 3 + (public_name bytesrw-eio) 4 + (libraries bytesrw eio))
+1 -1
test/dune
··· 1 1 (test 2 - (name test_bytesrw_eio) 2 + (name test) 3 3 (libraries bytesrw-eio bytesrw eio eio_main alcotest))
+1
test/test.ml
··· 1 + let () = Alcotest.run "bytesrw_eio" [ Test_bytesrw_eio.suite ]
+15 -21
test/test_bytesrw_eio.ml
··· 192 192 193 193 Alcotest.(check string) "round-trip data matches" test_data read_data 194 194 195 - let () = 196 - Alcotest.run "Bytesrw_eio" 195 + let suite = 196 + ( "bytesrw_eio", 197 197 [ 198 - ( "reader", 199 - [ 200 - Alcotest.test_case "basic read" `Quick test_reader_basic; 201 - Alcotest.test_case "custom slice length" `Quick 202 - test_reader_custom_slice_length; 203 - Alcotest.test_case "empty flow" `Quick test_reader_empty; 204 - Alcotest.test_case "multiple reads data isolation" `Quick 205 - test_reader_multiple_reads; 206 - ] ); 207 - ( "writer", 208 - [ 209 - Alcotest.test_case "basic write" `Quick test_writer_basic; 210 - Alcotest.test_case "custom slice length" `Quick 211 - test_writer_custom_slice_length; 212 - Alcotest.test_case "eod write" `Quick test_writer_eod; 213 - Alcotest.test_case "partial slice" `Quick test_writer_partial_slice; 214 - ] ); 215 - ("integration", [ Alcotest.test_case "round-trip" `Quick test_roundtrip ]); 216 - ] 198 + Alcotest.test_case "reader basic read" `Quick test_reader_basic; 199 + Alcotest.test_case "reader custom slice length" `Quick 200 + test_reader_custom_slice_length; 201 + Alcotest.test_case "reader empty flow" `Quick test_reader_empty; 202 + Alcotest.test_case "reader multiple reads data isolation" `Quick 203 + test_reader_multiple_reads; 204 + Alcotest.test_case "writer basic write" `Quick test_writer_basic; 205 + Alcotest.test_case "writer custom slice length" `Quick 206 + test_writer_custom_slice_length; 207 + Alcotest.test_case "writer eod write" `Quick test_writer_eod; 208 + Alcotest.test_case "writer partial slice" `Quick test_writer_partial_slice; 209 + Alcotest.test_case "round-trip" `Quick test_roundtrip; 210 + ] )
+2
test/test_bytesrw_eio.mli
··· 1 + val suite : string * unit Alcotest.test_case list 2 + (** Test suite. *)