howudoin/lib.rs
1//! A progress reporting and consuming abstraction.
2//!
3//! `howudoin` intends to make producing and consuming progress reports simple and ergonomic.
4//!
5//! ```rust
6//! // initialise a consumer loop
7//! howudoin::init(howudoin::consumers::Noop::default());
8//!
9//! let rpt = howudoin::new().label("Progress").set_len(10);
10//!
11//! for _ in 0..10 {
12//! rpt.inc(); // increment the progress
13//! // check for cancellation
14//! if rpt.cancelled() {
15//! break;
16//! }
17//! }
18//!
19//! rpt.finish(); // finalise progress
20//!
21//! // fetch the tree of progress
22//! let progress = howudoin::fetch();
23//! ```
24//!
25//! Features:
26//! - Lightweight
27//! - Unobtrusive interface
28//! - Nestable reports
29//! - Automatic timers
30//! - Message accumulation
31//! - Cancellation
32//!
33//! ## Progress Reporting
34//!
35//! Producing a progress report can be done anywhere in code without any references.
36//!
37//! ```rust
38//! // creates a new report
39//! let rpt = howudoin::new();
40//! // creates a report below `rpt`
41//! let child = howudoin::new_with_parent(rpt.id());
42//! // creates a report at the root
43//! let rpt2 = howudoin::new_root();
44//!
45//! // progress reporting
46//! let rpt = rpt
47//! .label("Label") // set a label/name
48//! .set_len(1000); // progress is bounded
49//!
50//! rpt.desc("processing"); // progress message
51//! for i in 1_u32..=1000 {
52//! rpt.inc(); // increment progress position
53//! rpt.set_pos(i); // set progress position
54//! }
55//!
56//! rpt.finish(); // finished a report
57//! rpt.close(); // close a report from display
58//! ```
59//!
60//! ## Progress Display
61//!
62//! Progress display is abstracted from the producer.
63//! A display mechanism implements the [`Consume`] trait, and is sent to the consumer loop with
64//! [`init`].
65//! There exist a few predefined consumers in the [`consumers`] module, which are feature gated.
66//! Consumers are generally defined for mechanisms that are _invoked_.
67//!
68//! ```rust
69//! // initialise a term-line consumer
70//! howudoin::init(howudoin::consumers::TermLine::default());
71//! ```
72//!
73//! ## Progress Consumption
74//!
75//! Progress reports can also be _requested_ from the consumer loop.
76//! This pattern is used when a progress update is _requested_ from elsewhere (for
77//! example, a REST API).
78//!
79//! ```rust
80//! // initialise a no-op consumer
81//! howudoin::init(howudoin::consumers::Noop::default());
82//!
83//! // fetch the progress tree
84//! let progress = howudoin::fetch();
85//! ```
86//!
87//! ## Opt-in
88//!
89//! Progress reports are only sent to a consumer if the consumer loop has been initialised.
90//! In situations where the loop has not been initialised, progress reporting is a very cheap void
91//! operation.
92//! This means producers can be neatly separated from consumers.
93#![warn(missing_docs)]
94
95use flume::Sender;
96use report::Progress;
97use std::time::{Duration, Instant};
98
99pub mod consumers;
100pub mod flat_tree;
101pub mod report;
102mod rx;
103#[cfg(test)]
104mod tests;
105mod tx;
106
107/// A report identifier.
108///
109/// For a consumer instantiation, identifiers are distinct, increasing counters.
110/// Note that the counter will wrap around.
111pub type Id = usize;
112
113pub use rx::Controller;
114pub use tx::{cancel, cancelled, disable, fetch, init, new, new_root, new_with_parent, reset, Tx};
115
116#[derive(Debug)]
117enum Payload {
118 /// Add a new reporter, optionally under the parent.
119 AddReport(Option<Id>, Sender<Id>),
120 /// Add a new root report.
121 AddRootReport(Sender<Id>),
122 /// Fetch the progress history.
123 Fetch(Sender<Vec<report::Progress>>),
124 /// Set the label.
125 SetLabel(Id, String),
126 /// Set the description.
127 SetDesc(Id, String),
128 /// Set the progress length. If `None`, this progress is indeterminate.
129 SetLen(Id, Option<u64>),
130 /// Set whether to format the length and position as bytes.
131 SetFmtBytes(Id, bool),
132 /// Increment the progress position by a number of ticks.
133 Inc(Id, u64),
134 /// Set the progress position.
135 SetPos(Id, u64),
136 /// Add an accumulation message.
137 Accum(Id, report::Severity, String),
138 /// Reporter has finished, but should be kept displayed.
139 Finish(Id),
140 /// Reporter has finished and should be removed from display.
141 Close(Id),
142 /// Set cancellation flag to true.
143 Cancel,
144 /// Get the cancellation status.
145 Cancelled(Sender<bool>),
146 /// Reset the controller's state.
147 Reset,
148}
149
150/// A report consumer.
151///
152/// A consumer is required when initialising progress reporting.
153/// They can be defined on local types, and there are a few predined consumers in the [`consumers`]
154/// module.
155///
156/// When defining a consumer, it is recommended to take a look at the predefined ones and the
157/// [examples].
158///
159/// Note that a consumer is _invoked_. If a _requester_ of progress is required, [`fetch`] should
160/// be used.
161///
162/// [examples]: https://github.com/kdr-aus/how-u-doin/tree/main/examples
163pub trait Consume {
164 /// Set the debounce timeout.
165 ///
166 /// Defaults to 50 milliseconds.
167 /// This is the time waited for before invoking the [`Consume::rpt`] or [`Consume::closed`].
168 /// The debounce allows reports to be fully updated before being displayed, avoiding flogging
169 /// the consumer.
170 fn debounce(&self) -> Duration {
171 Duration::from_millis(50)
172 }
173
174 /// Invoked when reports are updated in some way.
175 ///
176 /// `rpt` is only invoked if there have been changes, and after [`Consume::debounce`].
177 /// The `report` has the meat of the progress, while there are identifiers to see where the
178 /// report lands in the tree.
179 fn rpt(&mut self, report: &report::Report, id: Id, parent: Option<Id>, controller: &Controller);
180
181 /// The report with `id` was closed, indicating it should be _removed from display_.
182 ///
183 /// The default implementation is to do nothing.
184 fn closed(&mut self, _id: Id) {}
185}