diff --git a/lib_eio/buf_write.ml b/lib_eio/buf_write.ml index 7085b9d77..a3f2b2f65 100644 --- a/lib_eio/buf_write.ml +++ b/lib_eio/buf_write.ml @@ -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] *) @@ -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 @@ -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 -> diff --git a/lib_eio/buf_write.mli b/lib_eio/buf_write.mli index 2a9b875f9..7e6b66e97 100644 --- a/lib_eio/buf_write.mli +++ b/lib_eio/buf_write.mli @@ -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 @@ -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) diff --git a/tests/buf_write.md b/tests/buf_write.md index 692f45780..de453ce9f 100644 --- a/tests/buf_write.md +++ b/tests/buf_write.md @@ -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 + "@[" 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 "@ @[%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