use crate::SpecError;
pub trait OperatorView {
fn subject(&self) -> &str;
fn header_label(&self) -> &str;
fn render_body(&self);
fn render_summary(&self) {}
}
pub fn render<V: OperatorView>(view: &V) {
use crate::style::{glyph_snowflake, header, muted};
println!(
"{} {} {}",
glyph_snowflake(),
header(view.header_label()),
muted(view.subject()),
);
println!();
view.render_body();
println!();
view.render_summary();
}
pub type ViewResult<T = ()> = Result<T, Box<dyn std::error::Error>>;
pub fn pick_format<F, P>(
formats: Vec<F>,
predicate: P,
label: &'static str,
) -> Result<F, SpecError>
where
P: Fn(&F) -> bool,
{
formats
.into_iter()
.find(predicate)
.ok_or_else(|| SpecError::Interp {
phase: "operator-view".into(),
message: format!("missing format spec: {label}"),
})
}
#[cfg(test)]
mod tests {
use super::*;
struct Fixture {
subj: String,
body_called: std::cell::RefCell<bool>,
summary_called: std::cell::RefCell<bool>,
}
impl OperatorView for Fixture {
fn subject(&self) -> &str { &self.subj }
fn header_label(&self) -> &str { "fixture" }
fn render_body(&self) {
*self.body_called.borrow_mut() = true;
}
fn render_summary(&self) {
*self.summary_called.borrow_mut() = true;
}
}
#[test]
fn render_calls_body_and_summary() {
let f = Fixture {
subj: "x".into(),
body_called: std::cell::RefCell::new(false),
summary_called: std::cell::RefCell::new(false),
};
render(&f);
assert!(*f.body_called.borrow());
assert!(*f.summary_called.borrow());
}
#[test]
fn pick_format_finds_match() {
let formats = vec!["a".to_string(), "b".to_string(), "c".to_string()];
let result = pick_format(formats, |s| s == "b", "test").unwrap();
assert_eq!(result, "b");
}
#[test]
fn pick_format_errors_when_missing() {
let formats = vec!["a".to_string(), "b".to_string()];
let err = pick_format(formats, |s| s == "c", "test-thing")
.unwrap_err();
match err {
SpecError::Interp { phase, message } => {
assert_eq!(phase, "operator-view");
assert!(message.contains("test-thing"));
}
_ => panic!("expected Interp error, got {err:?}"),
}
}
}