#[cfg(feature = "std")]
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::panic::{AssertUnwindSafe, catch_unwind};
use crate::completion::Completion;
use crate::context::Context;
use crate::source::SequenceRef;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Flow {
#[default]
Continue,
End,
}
pub enum Progress {
Done,
Wait(Completion),
Call(SequenceRef),
Return,
Goto(Option<SequenceRef>),
Resume(Box<dyn StepRun>),
}
impl core::fmt::Debug for Progress {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Done => f.write_str("Done"),
Self::Wait(c) => f.debug_tuple("Wait").field(c).finish(),
Self::Call(r) => f.debug_tuple("Call").field(r).finish(),
Self::Return => f.write_str("Return"),
Self::Goto(r) => f.debug_tuple("Goto").field(r).finish(),
Self::Resume(_) => f.write_str("Resume(..)"),
}
}
}
pub trait IntoProgress {
fn into_progress(self) -> Progress;
}
impl IntoProgress for () {
fn into_progress(self) -> Progress {
Progress::Done
}
}
impl IntoProgress for Progress {
fn into_progress(self) -> Progress {
self
}
}
impl IntoProgress for Completion {
fn into_progress(self) -> Progress {
Progress::Wait(self)
}
}
pub trait StepRun {
fn resume(&mut self, ctx: &mut Context<'_>) -> Progress;
}
pub trait Step {
fn summary(&self) -> String;
fn warning(&self) -> Option<String> {
None
}
fn flow(&self) -> Flow {
Flow::Continue
}
fn delegates_to(&self) -> Option<SequenceRef> {
None
}
fn references(&self) -> Vec<SequenceRef> {
self.delegates_to().into_iter().collect()
}
fn is_enabled(&self) -> bool {
true
}
fn start(&self, ctx: &mut Context<'_>) -> Progress;
}
#[derive(Clone, Debug)]
pub struct StepFacts {
pub summary: String,
pub warning: Option<String>,
pub flow: Flow,
pub delegates_to: Option<SequenceRef>,
pub references: Vec<SequenceRef>,
pub enabled: bool,
}
impl StepFacts {
pub(crate) fn enabled_of(step: &dyn Step) -> bool {
#[cfg(feature = "std")]
{
catch_unwind(AssertUnwindSafe(|| step.is_enabled())).unwrap_or(true)
}
#[cfg(not(feature = "std"))]
step.is_enabled()
}
#[must_use]
pub fn of(step: &dyn Step) -> Self {
#[cfg(feature = "std")]
{
catch_unwind(AssertUnwindSafe(|| Self::gather(step))).unwrap_or_else(|_| Self {
summary: "<step panicked describing itself>".to_owned(),
warning: Some("This step panicked while describing itself.".to_owned()),
flow: Flow::Continue,
delegates_to: None,
references: Vec::new(),
enabled: true,
})
}
#[cfg(not(feature = "std"))]
Self::gather(step)
}
fn gather(step: &dyn Step) -> Self {
Self {
summary: step.summary(),
warning: step.warning(),
flow: step.flow(),
delegates_to: step.delegates_to(),
references: step.references(),
enabled: step.is_enabled(),
}
}
}
#[cfg(test)]
mod tests {
use alloc::boxed::Box;
use super::*;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
struct Fine;
impl Step for Fine {
fn summary(&self) -> String {
"fine".into()
}
fn flow(&self) -> Flow {
Flow::Continue
}
fn start(&self, _ctx: &mut Context<'_>) -> Progress {
Progress::Done
}
}
#[cfg(feature = "std")]
struct PanicsDescribing;
#[cfg(feature = "std")]
impl Step for PanicsDescribing {
fn summary(&self) -> String {
panic!("half-authored")
}
fn flow(&self) -> Flow {
Flow::End
}
fn start(&self, _ctx: &mut Context<'_>) -> Progress {
Progress::Done
}
}
#[test]
fn step_is_object_safe() {
let steps: Vec<Box<dyn Step>> = vec![Box::new(Fine)];
assert_eq!(steps[0].summary(), "fine");
assert_eq!(steps[0].flow(), Flow::Continue);
assert!(steps[0].is_enabled());
assert!(steps[0].warning().is_none());
assert!(steps[0].delegates_to().is_none());
assert!(steps[0].references().is_empty());
}
#[test]
fn facts_snapshot_a_well_behaved_step() {
let facts = StepFacts::of(&Fine);
assert_eq!(facts.summary, "fine");
assert_eq!(facts.flow, Flow::Continue);
assert!(facts.references.is_empty());
assert!(facts.enabled);
}
#[test]
#[cfg(feature = "std")] fn facts_survive_a_panicking_accessor() {
let facts = StepFacts::of(&PanicsDescribing);
assert!(facts.warning.is_some());
assert_eq!(facts.flow, Flow::Continue);
}
}