Skip to content

Commit

Permalink
Add Buf_write.printf
Browse files Browse the repository at this point in the history
  • Loading branch information
SGrondin committed Dec 4, 2023
1 parent 211279e commit 3ae48b0
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 1 deletion.
28 changes: 28 additions & 0 deletions lib_eio/buf_write.ml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ type t =
; mutable bytes_written : int (* Total written bytes. Wraps. *)
; mutable state : state
; mutable wake_writer : unit -> unit
; mutable is_formatting : bool
; mutable formatter : Format.formatter option
}
(* Invariant: [write_pos >= scheduled_pos] *)

Expand Down Expand Up @@ -377,6 +379,8 @@ let of_buffer ?sw buffer =
; bytes_written = 0
; state = Active
; wake_writer = ignore
; is_formatting = false
; formatter = None
}
in
begin match sw with
Expand Down Expand Up @@ -416,6 +420,30 @@ let flush t =
Promise.await_exn p
)

let get_formatter = function
| { formatter = Some x; _ } -> x
| ({ formatter = None; _ } as t) ->
let formatter = Format.make_formatter
(fun buf off len -> write_gen t buf ~off ~len ~blit:Bigstringaf.blit_from_string)
(fun () ->
(* As per the Format module manual, an explicit flush writes to the
output channel and ensures that "all pending text is displayed"
and "these explicit flush calls [...] could dramatically impact efficiency".
Therefore it is clear that we need to call `flush t` instead of `flush_buffer t`. *)
if t.is_formatting then flush t)
in
t.formatter <- Some formatter;
formatter

let printf t =
let ppf = get_formatter t in
t.is_formatting <- true;
Format.kfprintf (fun ppf ->
assert t.is_formatting;
t.is_formatting <- false;
Format.pp_print_flush ppf ()
) ppf

let rec shift_buffers t written =
match Buffers.dequeue_exn t.scheduled with
| { Cstruct.len; _ } as iovec ->
Expand Down
14 changes: 13 additions & 1 deletion lib_eio/buf_write.mli
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
or application-specific output APIs.
A Buf_write serializer manages an internal buffer and a queue of output
buffers. The output bufferes may be a sub range of the serializer's
buffers. The output buffers may be a sub range of the serializer's
internal buffer or one that is user-provided. Buffered writes such as
{!string}, {!char}, {!cstruct}, etc., copy the source bytes into the
serializer's internal buffer. Unbuffered writes are done with
Expand Down Expand Up @@ -123,6 +123,18 @@ val cstruct : t -> Cstruct.t -> unit
It is safe to modify [cs] after this call returns.
For large cstructs, it may be more efficient to use {!schedule_cstruct}. *)

val printf : t -> ('a, Format.formatter, unit) format -> 'a
(** [printf t fmt x y z] formats the arguments according to the format string [fmt].
It supports all formatting and pretty-printing features of the Format module.
Accordingly, explicit flushes using [@.] or [%!] must perform a full (blocking) flush
so consider using [Fiber.fork] in such cases. *)

val get_formatter : t -> Format.formatter
(** [get_formatter t] returns the underlying formatter used by calls to [printf].
This function is useful to mutate formatter settings by calling, for example,
[Format.pp_set_margin] or [Format.pp_set_geometry].
Note that these formatter settings only affect calls to [printf]. *)

val write_gen
: t
-> blit:('a -> src_off:int -> Cstruct.buffer -> dst_off:int -> len:int -> unit)
Expand Down
38 changes: 38 additions & 0 deletions tests/buf_write.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,44 @@ With pausing
- : unit = ()
```

## Formatting

```ocaml
# Eio_mock.Backend.run @@ fun () ->
let f t =
let formatter = Write.get_formatter t in
Write.string t "Hello";
Format.pp_set_geometry formatter ~max_indent:4 ~margin:10;
(*
"@ " breakable space
"@[<v 6>" open vertical box, indentation: 6 (overriden by our geometry settings)
"%s" print string
"@ " breakable space
"%i" print int
"@." print newline + explicit flush
"%a" print arbitrary type
"@]" close box
"@ " breakable space
*)
Write.printf t "@ @[<v 6>%s@ %i@.%a@]@ "
"This is a test" 123
Eio.Net.Sockaddr.pp (`Tcp (Eio.Net.Ipaddr.V6.loopback, 8080));
Write.string t "-> Not from printf <-";
Write.printf t "@.Ok back to %s@." "printf";
Write.string t "Goodbye"
in
Write.with_flow flow f;;
+flow: wrote "Hello\n"
+ "This is a test\n"
+ " 123\n"
+flow: wrote "tcp:[::1]:8080\n"
+ "-> Not from printf <-\n"
+flow: wrote "Ok back to printf\n"
+flow: wrote "Goodbye"
- : unit = ()
```

## Flushing

```ocaml
Expand Down

0 comments on commit 3ae48b0

Please sign in to comment.