Skip to content

Commit 3ae48b0

Browse files
committed
Add Buf_write.printf
1 parent 211279e commit 3ae48b0

File tree

3 files changed

+79
-1
lines changed

3 files changed

+79
-1
lines changed

lib_eio/buf_write.ml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ type t =
163163
; mutable bytes_written : int (* Total written bytes. Wraps. *)
164164
; mutable state : state
165165
; mutable wake_writer : unit -> unit
166+
; mutable is_formatting : bool
167+
; mutable formatter : Format.formatter option
166168
}
167169
(* Invariant: [write_pos >= scheduled_pos] *)
168170

@@ -377,6 +379,8 @@ let of_buffer ?sw buffer =
377379
; bytes_written = 0
378380
; state = Active
379381
; wake_writer = ignore
382+
; is_formatting = false
383+
; formatter = None
380384
}
381385
in
382386
begin match sw with
@@ -416,6 +420,30 @@ let flush t =
416420
Promise.await_exn p
417421
)
418422

423+
let get_formatter = function
424+
| { formatter = Some x; _ } -> x
425+
| ({ formatter = None; _ } as t) ->
426+
let formatter = Format.make_formatter
427+
(fun buf off len -> write_gen t buf ~off ~len ~blit:Bigstringaf.blit_from_string)
428+
(fun () ->
429+
(* As per the Format module manual, an explicit flush writes to the
430+
output channel and ensures that "all pending text is displayed"
431+
and "these explicit flush calls [...] could dramatically impact efficiency".
432+
Therefore it is clear that we need to call `flush t` instead of `flush_buffer t`. *)
433+
if t.is_formatting then flush t)
434+
in
435+
t.formatter <- Some formatter;
436+
formatter
437+
438+
let printf t =
439+
let ppf = get_formatter t in
440+
t.is_formatting <- true;
441+
Format.kfprintf (fun ppf ->
442+
assert t.is_formatting;
443+
t.is_formatting <- false;
444+
Format.pp_print_flush ppf ()
445+
) ppf
446+
419447
let rec shift_buffers t written =
420448
match Buffers.dequeue_exn t.scheduled with
421449
| { Cstruct.len; _ } as iovec ->

lib_eio/buf_write.mli

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
or application-specific output APIs.
4747
4848
A Buf_write serializer manages an internal buffer and a queue of output
49-
buffers. The output bufferes may be a sub range of the serializer's
49+
buffers. The output buffers may be a sub range of the serializer's
5050
internal buffer or one that is user-provided. Buffered writes such as
5151
{!string}, {!char}, {!cstruct}, etc., copy the source bytes into the
5252
serializer's internal buffer. Unbuffered writes are done with
@@ -123,6 +123,18 @@ val cstruct : t -> Cstruct.t -> unit
123123
It is safe to modify [cs] after this call returns.
124124
For large cstructs, it may be more efficient to use {!schedule_cstruct}. *)
125125

126+
val printf : t -> ('a, Format.formatter, unit) format -> 'a
127+
(** [printf t fmt x y z] formats the arguments according to the format string [fmt].
128+
It supports all formatting and pretty-printing features of the Format module.
129+
Accordingly, explicit flushes using [@.] or [%!] must perform a full (blocking) flush
130+
so consider using [Fiber.fork] in such cases. *)
131+
132+
val get_formatter : t -> Format.formatter
133+
(** [get_formatter t] returns the underlying formatter used by calls to [printf].
134+
This function is useful to mutate formatter settings by calling, for example,
135+
[Format.pp_set_margin] or [Format.pp_set_geometry].
136+
Note that these formatter settings only affect calls to [printf]. *)
137+
126138
val write_gen
127139
: t
128140
-> blit:('a -> src_off:int -> Cstruct.buffer -> dst_off:int -> len:int -> unit)

tests/buf_write.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,44 @@ With pausing
146146
- : unit = ()
147147
```
148148

149+
## Formatting
150+
151+
```ocaml
152+
# Eio_mock.Backend.run @@ fun () ->
153+
let f t =
154+
let formatter = Write.get_formatter t in
155+
Write.string t "Hello";
156+
Format.pp_set_geometry formatter ~max_indent:4 ~margin:10;
157+
(*
158+
"@ " breakable space
159+
"@[<v 6>" open vertical box, indentation: 6 (overriden by our geometry settings)
160+
"%s" print string
161+
"@ " breakable space
162+
"%i" print int
163+
"@." print newline + explicit flush
164+
"%a" print arbitrary type
165+
"@]" close box
166+
"@ " breakable space
167+
*)
168+
Write.printf t "@ @[<v 6>%s@ %i@.%a@]@ "
169+
"This is a test" 123
170+
Eio.Net.Sockaddr.pp (`Tcp (Eio.Net.Ipaddr.V6.loopback, 8080));
171+
172+
Write.string t "-> Not from printf <-";
173+
Write.printf t "@.Ok back to %s@." "printf";
174+
Write.string t "Goodbye"
175+
in
176+
Write.with_flow flow f;;
177+
+flow: wrote "Hello\n"
178+
+ "This is a test\n"
179+
+ " 123\n"
180+
+flow: wrote "tcp:[::1]:8080\n"
181+
+ "-> Not from printf <-\n"
182+
+flow: wrote "Ok back to printf\n"
183+
+flow: wrote "Goodbye"
184+
- : unit = ()
185+
```
186+
149187
## Flushing
150188

151189
```ocaml

0 commit comments

Comments
 (0)