1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
open Lwt.Syntax
type progress_display_mode = Auto | Always | Never
let progress_display_mode_enum =
[("auto", Auto); ("always", Always); ("never", Never)]
let progress_display_mode_encoding =
Data_encoding.string_enum progress_display_mode_enum
let animation =
[|
"|.....|";
"|o....|";
"|oo...|";
"|ooo..|";
"|.ooo.|";
"|..ooo|";
"|...oo|";
"|....o|";
"|.....|";
"|.....|";
"|.....|";
"|.....|";
|]
let init = String.make (String.length animation.(0)) ' '
let clean = String.make (String.length animation.(0)) '\b'
let animation = Array.map (fun x -> clean ^ x) animation
let number_of_frames = Array.length animation
let make_with_animation ppf ~make ~on_retry seed =
Format.fprintf ppf "%s%!" init ;
let rec loop n seed =
let start = Mtime_clock.counter () in
Format.fprintf ppf "%s%!" animation.(n mod number_of_frames) ;
let* r = make seed in
match r with
| Ok v -> Lwt.return v
| Error r ->
let time = Mtime_clock.count start in
let* v = on_retry time r in
loop (n + 1) v
in
let* result = loop 0 seed in
Format.fprintf ppf "%s%s\n%!" clean init ;
Lwt.return result
type 'a progress_fun =
| With_progress of {
step : (unit -> unit Lwt.t) -> 'a Lwt.t;
pp_print_step : Format.formatter -> int -> unit;
}
| Unknown_progress of {f : unit -> 'a Lwt.t; msg : string}
let generic_display_progress ?(every = 1) ?(out = Lwt_unix.stdout)
~progress_display_mode progress_fun =
if every <= 0 then
raise
(Invalid_argument "display_progress: negative or null repetition period") ;
let* print_progress =
match progress_display_mode with
| Auto -> Lwt_unix.isatty out
| Always -> Lwt.return_true
| Never -> Lwt.return_false
in
if not print_progress then
match progress_fun with
| With_progress {step; _} -> step (fun () -> Lwt.return_unit)
| Unknown_progress {f; _} -> f ()
else
let clear_line fmt = Format.fprintf fmt "\027[2K\r" in
let stream, notifier = Lwt_stream.create () in
let wrapped_notifier () =
notifier (Some ()) ;
Lwt.pause ()
in
let main_promise =
Lwt.finalize
(fun () ->
match progress_fun with
| With_progress {step; _} -> step wrapped_notifier
| Unknown_progress {f; _} -> f ())
(fun () ->
notifier None ;
Lwt.return_unit)
in
let oc = Unix.out_channel_of_descr (Lwt_unix.unix_file_descr out) in
let fmt = Format.formatter_of_out_channel oc in
let cpt = ref 0 in
let pp_cpt = ref 0 in
let rec tick_promise () =
let* () = Lwt_unix.sleep 1. in
let* () =
match progress_fun with
| Unknown_progress _ -> wrapped_notifier ()
| _ -> Lwt.return_unit
in
incr pp_cpt ;
tick_promise ()
in
let loop = tick_promise () in
let dot_array = [|""; "."; ".."; "..."|] in
let dots () = dot_array.(!pp_cpt mod 4) in
let pp () =
clear_line fmt ;
match progress_fun with
| With_progress {pp_print_step; _} ->
Format.fprintf fmt "%a%s%!" pp_print_step !cpt (dots ())
| Unknown_progress {msg; _} -> Format.fprintf fmt "%s%s%!" msg (dots ())
in
let pp_done () =
clear_line fmt ;
match progress_fun with
| With_progress {pp_print_step; _} ->
Format.fprintf fmt "%a Done@\n%!" pp_print_step !cpt
| Unknown_progress {msg; _} -> Format.fprintf fmt "%s Done@\n%!" msg
in
pp () ;
incr cpt ;
let printer =
Lwt_stream.iter_s
(fun () ->
if !cpt mod every = 0 then pp () ;
incr cpt ;
Lwt.return_unit)
stream
in
let* e = main_promise in
Lwt.cancel loop ;
let* () = printer in
decr cpt ;
pp_done () ;
Lwt.return e
let display_progress ?every ?out ~progress_display_mode ~pp_print_step step =
generic_display_progress
?every
?out
~progress_display_mode
(With_progress {step; pp_print_step})
let three_dots ?out ~progress_display_mode ~msg f =
generic_display_progress
?out
~progress_display_mode
(Unknown_progress {f; msg})