Skip to content

Commit 76514b7

Browse files
committed
More fixes to get rid of implicit Display
Add an explicit DisplayModel
1 parent 2e0d7dc commit 76514b7

File tree

6 files changed

+66
-20
lines changed

6 files changed

+66
-20
lines changed

NEWS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Unreleased
44

5-
- **Breaking change:** `Render` no longer has a blanket implementation for `Display`: you can no longer use a string, integer, or some object that implements `Display` as a model directly. You must instead implement `Render` explicitly.
5+
- **Breaking change:** `Render` no longer has a blanket implementation for `Display`: you can no longer use a string, integer, or some object that implements `Display` as a model directly. You can instead implement `Render` explicitly, or opt in to this behavior using the new `nutmeg::models::DisplayModel`.
66

77
It seems that the `Display` implementation is often not a very satisfactory progress bar, and the presence of the blanket implementation causes confusing error messages when `Render` is not implemented correctly.
88

examples/bench.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,23 @@
44
55
use std::time::Instant;
66

7+
struct IntModel(usize);
8+
9+
impl nutmeg::Model for IntModel {
10+
fn render(&mut self, _width: usize) -> String {
11+
format!("count: {}", self.0)
12+
}
13+
}
14+
715
fn main() {
816
let start = Instant::now();
9-
let view = nutmeg::View::new(0u64, nutmeg::Options::default());
17+
let view = nutmeg::View::new(IntModel(0), nutmeg::Options::default());
1018
let n = 10_000_000;
1119
for i in 0..n {
12-
view.update(|model| *model = i);
20+
view.update(|IntModel(count)| *count = i);
1321
}
1422
view.message(format!(
15-
"{}ms to send {} updates; average {}ns/update",
23+
"{}ms to send {} updates; average {}ns/update\n",
1624
start.elapsed().as_millis(),
1725
n,
1826
start.elapsed().as_nanos() / n as u128,

examples/print_holdoff.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,24 @@ use std::thread;
1111
use std::time;
1212
use std::time::Duration;
1313

14+
use nutmeg::models::LinearModel;
15+
1416
fn main() -> io::Result<()> {
1517
let options = nutmeg::Options::default()
1618
.print_holdoff(Duration::from_millis(1000))
1719
.update_interval(Duration::from_millis(0));
18-
let mut view = nutmeg::View::new(0usize, options);
20+
let mut view = nutmeg::View::new(LinearModel::new("Things", 50), options);
1921
for _i in 0..5 {
2022
for j in 0..4 {
2123
writeln!(view, "message {j}")?;
2224
thread::sleep(time::Duration::from_millis(100));
2325
}
2426
for j in 0..20 {
25-
view.update(|state| {
27+
view.update(|model| {
2628
// Previous updates were applied even though
2729
// they may not have been painted.
28-
assert!(j == 0 || *state == (j - 1));
29-
*state = j
30+
assert!(j == 0 || model.done() == (j - 1));
31+
model.set_done(j);
3032
});
3133
thread::sleep(time::Duration::from_millis(100));
3234
}

src/lib.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -487,10 +487,13 @@ impl<M: Model> View<M> {
487487
///
488488
/// ```
489489
/// use nutmeg::{Options, View};
490+
/// use nutmeg::models::LinearModel;
490491
///
491-
/// let view = View::new(10, Options::default());
492-
/// view.update(|model| *model += 3);
493-
/// assert_eq!(view.inspect_model(|m| *m), 13);
492+
/// let mut model = LinearModel::new("Things done", 100);
493+
/// model.set_done(10);
494+
/// let view = View::new(model, Options::default());
495+
/// view.update(|model| model.increment(3));
496+
/// assert_eq!(view.inspect_model(|m| m.done()), 13);
494497
/// ```
495498
pub fn inspect_model<F, R>(&self, f: F) -> R
496499
where
@@ -524,10 +527,15 @@ impl<M: Model> View<M> {
524527
///
525528
/// ```
526529
/// use nutmeg::{Options, View};
530+
/// use nutmeg::models::LinearModel;
527531
///
528-
/// let view = View::new(0, Options::default());
529-
/// // ...
530-
/// view.message(format!("{} splines reticulated\n", 42));
532+
/// let view = View::new(LinearModel::new("Splines reticulated", 100), Options::default());
533+
/// for i in 0..20 {
534+
/// view.update(|model| model.increment(1));
535+
/// if i == 12 {
536+
/// view.message("Some quality splines here!\n");
537+
/// }
538+
/// }
531539
/// ```
532540
pub fn message<S: AsRef<str>>(&self, message: S) {
533541
self.message_bytes(message.as_ref().as_bytes())
@@ -541,8 +549,9 @@ impl<M: Model> View<M> {
541549
///
542550
/// ```
543551
/// use nutmeg::{Options, View};
552+
/// use nutmeg::models::LinearModel;
544553
///
545-
/// let view = View::new("model content", Options::default());
554+
/// let view = View::new(LinearModel::new("Things done", 100), Options::default());
546555
/// view.message_bytes(b"hello crow\n");
547556
/// ```
548557
pub fn message_bytes<S: AsRef<[u8]>>(&self, message: S) {
@@ -563,8 +572,11 @@ impl<M: Model> View<M> {
563572
///
564573
/// ```
565574
/// use nutmeg::{Destination, Options, View};
575+
/// use nutmeg::models::DisplayModel;
566576
///
567-
/// let view = View::new(0, Options::default().destination(Destination::Capture));
577+
/// let view = View::new(
578+
/// DisplayModel("unchanging message"),
579+
/// Options::default().destination(Destination::Capture));
568580
/// let output = view.captured_output();
569581
/// view.message("Captured message\n");
570582
/// drop(view);

src/models.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//! requirement to use them: they only implement the public [Model] interface.
88
99
use std::borrow::Cow;
10+
use std::fmt::{Debug, Display};
1011
use std::time::{Duration, Instant};
1112

1213
#[allow(unused)] // For docstrings
@@ -86,6 +87,7 @@ impl Model for StringPair {
8687
/// progress.update(|model| model.increment(1));
8788
/// }
8889
/// ```
90+
#[derive(Debug)]
8991
pub struct LinearModel {
9092
done: usize,
9193
total: usize,
@@ -109,6 +111,16 @@ impl LinearModel {
109111
self.total = total
110112
}
111113

114+
/// Get the total number of things.
115+
pub fn total(&self) -> usize {
116+
self.total
117+
}
118+
119+
/// Get the number of things done so far.
120+
pub fn done(&self) -> usize {
121+
self.done
122+
}
123+
112124
/// Update the amount of work done.
113125
///
114126
/// This should normally be called from a callback passed to [View::update].
@@ -270,3 +282,13 @@ where
270282
(self.render_fn)(&mut self.value)
271283
}
272284
}
285+
286+
/// A model that holds a single value and renders it using its `Display` implementation.
287+
#[derive(Debug)]
288+
pub struct DisplayModel<T: Display + Debug>(pub T);
289+
290+
impl<T: Display + Debug> Model for DisplayModel<T> {
291+
fn render(&mut self, _width: usize) -> String {
292+
format!("{}", self.0)
293+
}
294+
}

tests/captured_in_tests.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,21 @@
1111
1212
use std::io::Write;
1313

14+
use nutmeg::models::DisplayModel;
15+
1416
#[test]
1517
fn view_stdout_captured() {
16-
let mut view = nutmeg::View::new(String::new(), nutmeg::Options::default());
17-
view.update(|model| *model = "stdout progress should be captured".into());
18+
let mut view = nutmeg::View::new(DisplayModel("hello"), nutmeg::Options::default());
19+
view.update(|DisplayModel(message)| *message = "stdout progress should be captured");
1820
writeln!(view, "stdout message should be captured").unwrap();
1921
}
2022

2123
#[test]
2224
fn view_stderr_captured() {
2325
let mut view = nutmeg::View::new(
24-
String::new(),
26+
DisplayModel("initial"),
2527
nutmeg::Options::default().destination(nutmeg::Destination::Stderr),
2628
);
27-
view.update(|model| *model = "stderr progress should be captured".into());
29+
view.update(|model| model.0 = "stderr progress should be captured");
2830
writeln!(view, "stderr message should be captured").unwrap();
2931
}

0 commit comments

Comments
 (0)