Skip to content

Commit

Permalink
More fixes to get rid of implicit Display
Browse files Browse the repository at this point in the history
Add an explicit DisplayModel
  • Loading branch information
sourcefrog committed Dec 14, 2023
1 parent 2e0d7dc commit 76514b7
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 20 deletions.
2 changes: 1 addition & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Unreleased

- **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.
- **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`.

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.

Expand Down
14 changes: 11 additions & 3 deletions examples/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@
use std::time::Instant;

struct IntModel(usize);

impl nutmeg::Model for IntModel {
fn render(&mut self, _width: usize) -> String {
format!("count: {}", self.0)
}
}

fn main() {
let start = Instant::now();
let view = nutmeg::View::new(0u64, nutmeg::Options::default());
let view = nutmeg::View::new(IntModel(0), nutmeg::Options::default());
let n = 10_000_000;
for i in 0..n {
view.update(|model| *model = i);
view.update(|IntModel(count)| *count = i);
}
view.message(format!(
"{}ms to send {} updates; average {}ns/update",
"{}ms to send {} updates; average {}ns/update\n",
start.elapsed().as_millis(),
n,
start.elapsed().as_nanos() / n as u128,
Expand Down
10 changes: 6 additions & 4 deletions examples/print_holdoff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@ use std::thread;
use std::time;
use std::time::Duration;

use nutmeg::models::LinearModel;

fn main() -> io::Result<()> {
let options = nutmeg::Options::default()
.print_holdoff(Duration::from_millis(1000))
.update_interval(Duration::from_millis(0));
let mut view = nutmeg::View::new(0usize, options);
let mut view = nutmeg::View::new(LinearModel::new("Things", 50), options);
for _i in 0..5 {
for j in 0..4 {
writeln!(view, "message {j}")?;
thread::sleep(time::Duration::from_millis(100));
}
for j in 0..20 {
view.update(|state| {
view.update(|model| {
// Previous updates were applied even though
// they may not have been painted.
assert!(j == 0 || *state == (j - 1));
*state = j
assert!(j == 0 || model.done() == (j - 1));
model.set_done(j);
});
thread::sleep(time::Duration::from_millis(100));
}
Expand Down
28 changes: 20 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,10 +487,13 @@ impl<M: Model> View<M> {
///
/// ```
/// use nutmeg::{Options, View};
/// use nutmeg::models::LinearModel;
///
/// let view = View::new(10, Options::default());
/// view.update(|model| *model += 3);
/// assert_eq!(view.inspect_model(|m| *m), 13);
/// let mut model = LinearModel::new("Things done", 100);
/// model.set_done(10);
/// let view = View::new(model, Options::default());
/// view.update(|model| model.increment(3));
/// assert_eq!(view.inspect_model(|m| m.done()), 13);
/// ```
pub fn inspect_model<F, R>(&self, f: F) -> R
where
Expand Down Expand Up @@ -524,10 +527,15 @@ impl<M: Model> View<M> {
///
/// ```
/// use nutmeg::{Options, View};
/// use nutmeg::models::LinearModel;
///
/// let view = View::new(0, Options::default());
/// // ...
/// view.message(format!("{} splines reticulated\n", 42));
/// let view = View::new(LinearModel::new("Splines reticulated", 100), Options::default());
/// for i in 0..20 {
/// view.update(|model| model.increment(1));
/// if i == 12 {
/// view.message("Some quality splines here!\n");
/// }
/// }
/// ```
pub fn message<S: AsRef<str>>(&self, message: S) {
self.message_bytes(message.as_ref().as_bytes())
Expand All @@ -541,8 +549,9 @@ impl<M: Model> View<M> {
///
/// ```
/// use nutmeg::{Options, View};
/// use nutmeg::models::LinearModel;
///
/// let view = View::new("model content", Options::default());
/// let view = View::new(LinearModel::new("Things done", 100), Options::default());
/// view.message_bytes(b"hello crow\n");
/// ```
pub fn message_bytes<S: AsRef<[u8]>>(&self, message: S) {
Expand All @@ -563,8 +572,11 @@ impl<M: Model> View<M> {
///
/// ```
/// use nutmeg::{Destination, Options, View};
/// use nutmeg::models::DisplayModel;
///
/// let view = View::new(0, Options::default().destination(Destination::Capture));
/// let view = View::new(
/// DisplayModel("unchanging message"),
/// Options::default().destination(Destination::Capture));
/// let output = view.captured_output();
/// view.message("Captured message\n");
/// drop(view);
Expand Down
22 changes: 22 additions & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//! requirement to use them: they only implement the public [Model] interface.
use std::borrow::Cow;
use std::fmt::{Debug, Display};
use std::time::{Duration, Instant};

#[allow(unused)] // For docstrings
Expand Down Expand Up @@ -86,6 +87,7 @@ impl Model for StringPair {
/// progress.update(|model| model.increment(1));
/// }
/// ```
#[derive(Debug)]
pub struct LinearModel {
done: usize,
total: usize,
Expand All @@ -109,6 +111,16 @@ impl LinearModel {
self.total = total
}

/// Get the total number of things.
pub fn total(&self) -> usize {
self.total
}

/// Get the number of things done so far.
pub fn done(&self) -> usize {
self.done
}

/// Update the amount of work done.
///
/// This should normally be called from a callback passed to [View::update].
Expand Down Expand Up @@ -270,3 +282,13 @@ where
(self.render_fn)(&mut self.value)
}
}

/// A model that holds a single value and renders it using its `Display` implementation.
#[derive(Debug)]
pub struct DisplayModel<T: Display + Debug>(pub T);

impl<T: Display + Debug> Model for DisplayModel<T> {
fn render(&mut self, _width: usize) -> String {
format!("{}", self.0)
}
}
10 changes: 6 additions & 4 deletions tests/captured_in_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@
use std::io::Write;

use nutmeg::models::DisplayModel;

#[test]
fn view_stdout_captured() {
let mut view = nutmeg::View::new(String::new(), nutmeg::Options::default());
view.update(|model| *model = "stdout progress should be captured".into());
let mut view = nutmeg::View::new(DisplayModel("hello"), nutmeg::Options::default());
view.update(|DisplayModel(message)| *message = "stdout progress should be captured");
writeln!(view, "stdout message should be captured").unwrap();
}

#[test]
fn view_stderr_captured() {
let mut view = nutmeg::View::new(
String::new(),
DisplayModel("initial"),
nutmeg::Options::default().destination(nutmeg::Destination::Stderr),
);
view.update(|model| *model = "stderr progress should be captured".into());
view.update(|model| model.0 = "stderr progress should be captured");
writeln!(view, "stderr message should be captured").unwrap();
}

0 comments on commit 76514b7

Please sign in to comment.