use crate::component::{Component, RunError}; use crate::input::RawMode; /// A sequential composition of interactive components. /// /// Each step runs one `Component` to completion, then passes its result /// to a continuation that produces the next `Flow`. The whole thing resolves /// to a single `R` when complete. /// /// # Example /// /// ```rust,ignore /// use sly::flow::Flow; /// use sly::widgets::text_input::{text_input, TextInput}; /// use sly::widgets::confirm::confirm_str; /// /// let flow: Flow> = Flow::step( /// text_input(TextInput::new().prompt("Name: ")), /// |name| { /// Flow::step( /// confirm_str(&format!("Save as '{}'?", name), true), /// move |ok| { /// if ok { Flow::done(Some(name)) } else { Flow::done(None) } /// }, /// ) /// }, /// ); /// /// let result = flow.run(&raw_mode).unwrap(); /// ``` pub struct Flow { inner: Box Result>, } impl Flow { /// Run one component step, then pass its result to a continuation. pub fn step(component: Component, then: impl FnOnce(T) -> Flow + 'static) -> Self where S: 'static, T: 'static, { Flow { inner: Box::new(move |mode| { let result = component.run(mode)?; (then(result)).run(mode) }), } } /// A flow that immediately succeeds with `value`. pub fn done(value: R) -> Self { Flow { inner: Box::new(move |_mode| Ok(value)), } } /// A flow that immediately fails with `Cancelled`. pub fn cancel() -> Self { Flow { inner: Box::new(|_mode| Err(RunError::Cancelled)), } } /// Map the result value. pub fn map(self, f: impl FnOnce(R) -> R2 + 'static) -> Flow { Flow { inner: Box::new(move |mode| self.run(mode).map(f)), } } /// Run the entire flow to completion. pub fn run(self, mode: &RawMode) -> Result { (self.inner)(mode) } } // --------------------------------------------------------------------------- // Helper shorthands // --------------------------------------------------------------------------- use crate::widgets::confirm::confirm_str; use crate::widgets::selector::{Selector, SelectorItem, selector}; use crate::widgets::text_input::{TextInput, text_input}; /// Run a text-input prompt as a single flow step. pub fn ask(prompt: &str, input: TextInput) -> Flow { let prompt = prompt.to_owned(); let ti = input.prompt(&prompt); Flow::step(text_input(ti), Flow::done) } /// Run a selector as a single flow step. pub fn choose(items: Vec>) -> Flow { let sel = Selector::new(items); Flow::step(selector(sel), Flow::done) } /// Run a yes/no confirmation as a single flow step. pub fn confirm(message: &str, default: bool) -> Flow { Flow::step(confirm_str(message, default), Flow::done) } #[cfg(test)] mod tests { use super::*; // We test Flow by constructing flows that don't touch a real terminal. // We use `Flow::done` and `Flow::cancel` directly, and test that `map` // and chaining work correctly via the inner closure without running // actual IO components. /// A helper that builds a Flow from a pre-computed result. #[allow(dead_code)] fn immediate(r: Result) -> Flow { // We can't easily inject a raw mode, but we can test the logic of // done/cancel/map by relying on the fact that `done` and `cancel` // ignore the mode argument. match r { Ok(v) => Flow::done(v), Err(_) => Flow::cancel(), } } #[test] fn flow_done_produces_value() { // We can't call .run() without a RawMode, but we can verify the // structure: Flow::done wraps a closure that returns Ok(value). // Test indirectly via map. let f = Flow::done(42usize).map(|n| n * 2); // f is a Flow. We verify it's constructible. let _ = f; // just ensure it compiles without panicking } #[test] fn flow_map_transforms_result() { // Construct two immediate flows and chain them. let _f: Flow = Flow::done(10usize).map(|n| format!("val={}", n)); } #[test] fn flow_step_chains() { // Build a flow that chains two done steps. let _f: Flow = Flow::step( { use crate::component::Component; // A component whose run() we can't call in a unit test (needs RawMode), // so we just verify the type chain compiles. Component::new( (), |_| Block::text(vec![]), |_, _key| crate::component::Update::Finish("hello".to_string()), ) }, |s: String| Flow::done(format!("got: {}", s)), ); } #[test] fn flow_cancel_type_checks() { let _f: Flow = Flow::cancel(); } use crate::block::Block; }