A simple TUI Library written in Rust
at main 164 lines 5.2 kB view raw
1use crate::component::{Component, RunError}; 2use crate::input::RawMode; 3 4/// A sequential composition of interactive components. 5/// 6/// Each step runs one `Component` to completion, then passes its result 7/// to a continuation that produces the next `Flow`. The whole thing resolves 8/// to a single `R` when complete. 9/// 10/// # Example 11/// 12/// ```rust,ignore 13/// use sly::flow::Flow; 14/// use sly::widgets::text_input::{text_input, TextInput}; 15/// use sly::widgets::confirm::confirm_str; 16/// 17/// let flow: Flow<Option<String>> = Flow::step( 18/// text_input(TextInput::new().prompt("Name: ")), 19/// |name| { 20/// Flow::step( 21/// confirm_str(&format!("Save as '{}'?", name), true), 22/// move |ok| { 23/// if ok { Flow::done(Some(name)) } else { Flow::done(None) } 24/// }, 25/// ) 26/// }, 27/// ); 28/// 29/// let result = flow.run(&raw_mode).unwrap(); 30/// ``` 31pub struct Flow<R: 'static> { 32 inner: Box<dyn FnOnce(&RawMode) -> Result<R, RunError>>, 33} 34 35impl<R: 'static> Flow<R> { 36 /// Run one component step, then pass its result to a continuation. 37 pub fn step<S, T>(component: Component<S, T>, then: impl FnOnce(T) -> Flow<R> + 'static) -> Self 38 where 39 S: 'static, 40 T: 'static, 41 { 42 Flow { 43 inner: Box::new(move |mode| { 44 let result = component.run(mode)?; 45 (then(result)).run(mode) 46 }), 47 } 48 } 49 50 /// A flow that immediately succeeds with `value`. 51 pub fn done(value: R) -> Self { 52 Flow { 53 inner: Box::new(move |_mode| Ok(value)), 54 } 55 } 56 57 /// A flow that immediately fails with `Cancelled`. 58 pub fn cancel() -> Self { 59 Flow { 60 inner: Box::new(|_mode| Err(RunError::Cancelled)), 61 } 62 } 63 64 /// Map the result value. 65 pub fn map<R2: 'static>(self, f: impl FnOnce(R) -> R2 + 'static) -> Flow<R2> { 66 Flow { 67 inner: Box::new(move |mode| self.run(mode).map(f)), 68 } 69 } 70 71 /// Run the entire flow to completion. 72 pub fn run(self, mode: &RawMode) -> Result<R, RunError> { 73 (self.inner)(mode) 74 } 75} 76 77// --------------------------------------------------------------------------- 78// Helper shorthands 79// --------------------------------------------------------------------------- 80 81use crate::widgets::confirm::confirm_str; 82use crate::widgets::selector::{Selector, SelectorItem, selector}; 83use crate::widgets::text_input::{TextInput, text_input}; 84 85/// Run a text-input prompt as a single flow step. 86pub fn ask(prompt: &str, input: TextInput) -> Flow<String> { 87 let prompt = prompt.to_owned(); 88 let ti = input.prompt(&prompt); 89 Flow::step(text_input(ti), Flow::done) 90} 91 92/// Run a selector as a single flow step. 93pub fn choose<T: Clone + 'static>(items: Vec<SelectorItem<T>>) -> Flow<T> { 94 let sel = Selector::new(items); 95 Flow::step(selector(sel), Flow::done) 96} 97 98/// Run a yes/no confirmation as a single flow step. 99pub fn confirm(message: &str, default: bool) -> Flow<bool> { 100 Flow::step(confirm_str(message, default), Flow::done) 101} 102 103#[cfg(test)] 104mod tests { 105 use super::*; 106 107 // We test Flow by constructing flows that don't touch a real terminal. 108 // We use `Flow::done` and `Flow::cancel` directly, and test that `map` 109 // and chaining work correctly via the inner closure without running 110 // actual IO components. 111 112 /// A helper that builds a Flow from a pre-computed result. 113 #[allow(dead_code)] 114 fn immediate<R: 'static>(r: Result<R, RunError>) -> Flow<R> { 115 // We can't easily inject a raw mode, but we can test the logic of 116 // done/cancel/map by relying on the fact that `done` and `cancel` 117 // ignore the mode argument. 118 match r { 119 Ok(v) => Flow::done(v), 120 Err(_) => Flow::cancel(), 121 } 122 } 123 124 #[test] 125 fn flow_done_produces_value() { 126 // We can't call .run() without a RawMode, but we can verify the 127 // structure: Flow::done wraps a closure that returns Ok(value). 128 // Test indirectly via map. 129 let f = Flow::done(42usize).map(|n| n * 2); 130 // f is a Flow<usize>. We verify it's constructible. 131 let _ = f; // just ensure it compiles without panicking 132 } 133 134 #[test] 135 fn flow_map_transforms_result() { 136 // Construct two immediate flows and chain them. 137 let _f: Flow<String> = Flow::done(10usize).map(|n| format!("val={}", n)); 138 } 139 140 #[test] 141 fn flow_step_chains() { 142 // Build a flow that chains two done steps. 143 let _f: Flow<String> = Flow::step( 144 { 145 use crate::component::Component; 146 // A component whose run() we can't call in a unit test (needs RawMode), 147 // so we just verify the type chain compiles. 148 Component::new( 149 (), 150 |_| Block::text(vec![]), 151 |_, _key| crate::component::Update::Finish("hello".to_string()), 152 ) 153 }, 154 |s: String| Flow::done(format!("got: {}", s)), 155 ); 156 } 157 158 #[test] 159 fn flow_cancel_type_checks() { 160 let _f: Flow<u32> = Flow::cancel(); 161 } 162 163 use crate::block::Block; 164}