123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206(*————————————————————————————————————————————————————————————————————————————
Copyright (c) 2020–2021 Craig Ferguson <me@craigfe.io>
Distributed under the MIT license. See terms at the end of this file.
————————————————————————————————————————————————————————————————————————————*)open!Importtype'areporter='a->unitmoduletypeS=sigtype'areportertype'alinetype('a,'b)multitypeconfigvalwith_reporter:?config:config->'aline->('areporter->'b)->'b(** [with_reporters line f] begins rendering [line] and calls [f] with the
reporting function. Once [f] returns, the display is finalised. {b Note:}
attempting to use the reporting function after [f] has returned will raise
a [Finalised] exception. *)valwith_reporters:?config:config->('a,'b)multi->'a->'b(** [with_reporters bars f] begins rendering [bars] and passes the
corresponding reporting functions to [f]. Once [f] returns, the display is
finalised. *)(** {2 Examples}
- Reading a file into memory and displaying a single progress bar:
{[
let read_file path buffer =
let total = file_size path and in_channel = open_in path in
try
with_reporter (counter ~total ()) @@ fun report ->
let rec aux offset =
let bytes_read = really_read buffer offset in
report bytes_read;
aux (offset + bytes_read)
in
aux 0
with End_of_file -> close_in in_channel
]}
- Sending data to multiple clients, with one progress bar each:
{[
let multi_bar_rendering () =
with_reporters
Multi.(line bar_a ++ line bar_b ++ line bar_c)
(fun report_a report_b report_c ->
for i = 1 to 1000 do
report_a (transfer_bytes client_a);
report_b (transfer_bytes client_b);
report_c (transfer_bytes client_c)
done)
]} *)(** {2 Logging during rendering} *)valinterject_with:(unit->'a)->'a(** [interject_with f] executes the function [f] while temporarily suspending
the rendering of any active progress bar display. This can be useful when
printing to [stdout] / [stderr], to avoid any interference from the
rendering of progress bars. If using the [Logs] library, consider using
{!reporter} and {!instrument_reporter} instead.
{b Note}:
{i the caller must ensure that the terminal cursor is left in an
appropriate position to resume rendering. In practice, this means that
any printing to the terminal should be terminated with a newline
character and flushed.} *)(** Extensions to the {{:https://erratique.ch/software/logs} [Logs]} library
designed to cooperate with progress bar rendering: *)vallogs_reporter:?pp_header:(Logs.level*stringoption)Fmt.t->?app:Format.formatter->?dst:Format.formatter->unit->Logs.reporter(** [reporter] is like [Logs_fmt.reporter] but produces a reporter that
{{!Progress.interject_with} suspends} any ongoing progress bar rendering
while displaying log entries, ensuring that log entries in the terminal
are never overwritten by the renderer. *)valinstrument_logs_reporter:Logs.reporter->Logs.reporter(** [instrument_reporter r] wraps the synchronous reporter [r] to ensure that
any progress bar rendering is suspended while messages are being
constructed for [r].
{b Note}:
{i to ensure that log entries are not overwritten by the [Progress]
renderer, [r] must flush any log entries to the terminal synchronously:
as soon as they are reported. This is true of the [Logs] reporters
built by {!Logs.format_reporter} and {!Logs_fmt.reporter}. An
asynchronous reporter should use {!interject_with} to delimit its
flushing action instead.} *)(** {2 Manual lifecycle management}
Functions for explicitly starting and stopping the process of rendering a
bar; useful when the code doing the progress reporting cannot be
conveniently delimited inside {!with_reporter}. All {!Display}s must be
properly {{!Display.finalise} finalised}, and it is not possible to
interleave rendering of displays. *)moduleReporter:sigtype-'at(** The (abstract) type of reporter functions used by the manual lifecycle
management functions in {!Display}. An ['a t] is conceptually an
['a -> unit] function, but can be explicitly {!finalise}d. *)valreport:'at->'a->unitvalfinalise:_t->unit(** [finalise t] terminates rendering of the line associated with reporter
[t]. Attempting to {!report} to a finalised reporter will raise an
exception. *)(** A heterogeneous list type, used by {!Display} for returning a list of
reporters corresponding to multi-line progress displays. *)type(_,_)list=|[]:('a,'a)list|(::):'a*('b,'c)list->('a->'b,'c)listendmoduleDisplay:sigtype('a,'b)t(** The type of active progress bar displays. The type parameters ['a] and
['b] track the types of the reporting functions supplied by {!reporters}
(see {!Multi.t} for details).*)valstart:?config:config->('a,'b)multi->('a,'b)t(** Initiate rendering of a progress bar display. Raises [Failure] if there
is already an active progress bar display. *)valreporters:('a,unit)t->('a,unit)Reporter.list(** [reporters d] is the list of initial reporting functions belonging to
display [d].
{b Note}
{i this list does not include any reporters added {i during} progress
bar rendering via {!add_line}.} *)valtick:_t->unit(** [tick d] re-renders the contents of display [d] without reporting any
specific values. This function can be used to update spinners,
durations, etc. when there is no actual progress to report. *)valadd_line:?above:int->(_,_)t->'aline->'aReporter.t(** Add a line to an ongoing display, and get its reporting function. By
default, the line is added to the {i bottom} of the display
([above = 0]); the [~above] argument can be passed to add the line above
some number of existing lines. *)valremove_line:(_,_)t->_Reporter.t->unit(** Remove a line from an ongoing display, identified by the reporting
function that was returned by [add_line]. Lines may be removed either
before they are finalised (for example if some task has been cancelled)
or after being finalised. In both cases, the line will be removed from
the display, thus retrieving some space in the terminal. Attempting to
remove a line that has already been removed from the display will raise
[Failure]. Also raises [Failure] if the display has already been
finalised. *)valpause:(_,_)t->unit(** Suspends the rendering of any active progress bar display. It can be
useful to compose with the [Logs] library and avoid interference when
printing to [stdout] / [stderr] from the rendering of progress bars. *)valresume:(_,_)t->unit(** Resume the rendering of progress bar display. *)valfinalise:(_,_)t->unit(** Terminate the given progress bar display. Raises [Failure] if the
display has already been finalised. *)endendmoduletypeRenderer=sigmoduletypeS=SmoduleMake(_:Platform.S):Swithtype'areporter:='areporterandtype'aline:='aLine.tandtype('a,'b)multi:=('a,'b)Multi.tandtypeconfig:=Config.user_suppliedend(*————————————————————————————————————————————————————————————————————————————
Copyright (c) 2020–2021 Craig Ferguson <me@craigfe.io>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
————————————————————————————————————————————————————————————————————————————*)