1#![deny(missing_docs)]
2#![deny(unsafe_code)]
3
4mod paragraphs;
11pub use paragraphs::*;
12mod frontend;
13pub use frontend::*;
14
15use std::{
16 collections::HashMap,
17 error::Error,
18 fmt::{self, Display, Formatter},
19 fs, io,
20 ops::{Deref, DerefMut},
21 path::Path,
22 sync::Arc,
23};
24
25use serde::{de::DeserializeOwned, Serialize};
26pub use serde_derive::{Deserialize, Serialize};
27
28enum MayBor<'a, T> {
30 Owned(T),
31 Borrowed(&'a mut T),
32}
33
34impl<'a, T> Deref for MayBor<'a, T> {
35 type Target = T;
36 fn deref(&self) -> &Self::Target {
37 use MayBor::*;
38 match self {
39 Owned(x) => x,
40 Borrowed(x) => x,
41 }
42 }
43}
44
45impl<'a, T> DerefMut for MayBor<'a, T> {
46 fn deref_mut(&mut self) -> &mut Self::Target {
47 use MayBor::*;
48 match self {
49 Owned(x) => x,
50 Borrowed(x) => x,
51 }
52 }
53}
54
55#[derive(Debug)]
57pub enum RuntimeError {
58 IO(io::Error),
60 Serialize(serde_yaml::Error),
62}
63
64impl From<io::Error> for RuntimeError {
65 fn from(e: io::Error) -> Self {
66 RuntimeError::IO(e)
67 }
68}
69
70impl From<serde_yaml::Error> for RuntimeError {
71 fn from(e: serde_yaml::Error) -> Self {
72 RuntimeError::Serialize(e)
73 }
74}
75
76impl Display for RuntimeError {
77 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
78 use RuntimeError::*;
79 match self {
80 IO(e) => write!(f, "{}", e),
81 Serialize(e) => write!(f, "{}", e),
82 }
83 }
84}
85
86impl Error for RuntimeError {}
87
88pub type RuntimeResult<T> = Result<T, RuntimeError>;
90
91pub enum Interrupt<N> {
99 Exit,
101 Skip,
105 Continue,
109 Jump(N),
111}
112
113pub use Interrupt::*;
114
115pub type ControlResult<T, N> = Result<T, Interrupt<N>>;
117
118pub type Control<N> = ControlResult<N, N>;
120
121pub fn next<T, N>(data: T) -> ControlResult<T, N> {
123 Ok(data)
124}
125
126pub fn exit<T, N>() -> ControlResult<T, N> {
128 Err(Exit)
129}
130
131pub trait Node: Clone {
142 type State;
144 fn next<F>(self, rt: &mut Runtime<Self, F>) -> Control<Self>
148 where
149 F: Frontend;
150}
151
152pub struct Runtime<'a, N, F = CliFrontend>
154where
155 N: Node,
156 F: Frontend,
157{
158 curr_node: N,
159 state: MayBor<'a, N::State>,
160 frontend: MayBor<'a, F>,
161 commands: HashMap<String, Arc<Box<CommandFn<N, F>>>>,
162}
163
164impl<'a, N, F> Runtime<'a, N, F>
165where
166 N: Node,
167 F: Frontend,
168{
169 fn borrow_state_frontend<G>(&mut self, f: G)
170 where
171 G: FnOnce(&mut N::State, &mut F),
172 {
173 f(&mut *self.state, &mut self.frontend)
174 }
175 pub fn push<M>(&mut self) -> Interrupt<N>
177 where
178 M: Node<State = N::State> + Default,
179 {
180 self.borrow_state_frontend(|state, frontend| {
181 run(M::default(), state, frontend);
182 });
183 Continue
184 }
185 pub fn push_node<M>(&mut self, node: M) -> Interrupt<N>
187 where
188 M: Node<State = N::State>,
189 {
190 self.borrow_state_frontend(|state, frontend| {
191 run(node, state, frontend);
192 });
193 Continue
194 }
195 pub fn save<P: AsRef<Path>>(&self, path: P) -> RuntimeResult<()>
197 where
198 N: Serialize,
199 N::State: Serialize,
200 {
201 fs::write(
202 path,
203 &serde_yaml::to_vec(&(&self.curr_node, self.state.deref()))?,
204 )?;
205 Ok(())
206 }
207 pub fn load<P: AsRef<Path>>(&mut self, path: P) -> RuntimeResult<()>
209 where
210 N: DeserializeOwned,
211 N::State: DeserializeOwned,
212 {
213 let (curr_node, state): (N, N::State) = serde_yaml::from_slice(&fs::read(path)?)?;
214 self.curr_node = curr_node;
215 self.state = MayBor::Owned(state);
216 Ok(())
217 }
218 #[must_use]
219 fn prompt_intercept(&mut self) -> ControlResult<String, N> {
220 loop {
221 let input = self.input();
222 match input.as_str().trim() {
223 "quit" => break exit(),
224 s => {
225 if let Some(f) = self.commands.get(s).cloned() {
226 match f(self) {
227 Continue => {}
228 e => break Err(e),
229 }
230 } else {
231 break next(s.to_string());
232 }
233 }
234 }
235 }
236 }
237 #[must_use]
239 pub fn prompt<S: Display>(&mut self, prompt: S) -> ControlResult<String, N> {
240 loop {
241 self.println(&prompt);
242 let input = self.prompt_intercept()?;
243 if !input.is_empty() {
244 break next(input);
245 }
246 }
247 }
248 #[must_use]
250 pub fn wait(&mut self) -> ControlResult<(), N> {
251 self.prompt_intercept()?;
252 next(())
253 }
254 #[must_use]
256 pub fn multiple_choice<M, C>(&mut self, message: M, choices: &[C]) -> ControlResult<usize, N>
257 where
258 M: Display,
259 C: Display,
260 {
261 loop {
262 self.println(&message);
263 for (i, choice) in choices.iter().enumerate() {
264 self.println(format!("{}) {}", i + 1, choice));
265 }
266 let resp = self.prompt_intercept()?;
267 match resp.parse::<usize>() {
268 Ok(num) if num > 0 && num <= choices.len() => break next(num - 1),
269 Err(_) => {
270 if let Some(i) = choices
271 .iter()
272 .position(|c| c.to_string().to_lowercase() == resp.to_lowercase())
273 {
274 break next(i);
275 } else {
276 self.println("Invalid choice")
277 }
278 }
279 _ => self.println("Invalid choice"),
280 }
281 }
282 }
283 #[must_use]
285 pub fn multiple_choice_named<M, S, C, I>(
286 &mut self,
287 message: M,
288 choices: I,
289 ) -> ControlResult<C, N>
290 where
291 M: Display,
292 S: Display,
293 I: IntoIterator<Item = (S, C)>,
294 {
295 let mut choices: Vec<(S, C)> = choices.into_iter().collect();
296 loop {
297 self.println(&message);
298 for (i, (text, _)) in choices.iter().enumerate() {
299 self.println(format!("{}) {}", i + 1, text));
300 }
301 let resp = self.prompt_intercept()?;
302 match resp.parse::<usize>() {
303 Ok(num) if num > 0 && num <= choices.len() => {
304 break next(choices.remove(num - 1).1)
305 }
306 _ => self.println("Invalid choice"),
307 }
308 }
309 }
310}
311
312impl<'a, N, F> Deref for Runtime<'a, N, F>
313where
314 N: Node,
315 F: Frontend,
316{
317 type Target = N::State;
318 fn deref(&self) -> &Self::Target {
319 &self.state
320 }
321}
322
323impl<'a, N, F> DerefMut for Runtime<'a, N, F>
324where
325 N: Node,
326 F: Frontend,
327{
328 fn deref_mut(&mut self) -> &mut Self::Target {
329 &mut self.state
330 }
331}
332
333impl<'a, N, F> AsRef<N::State> for Runtime<'a, N, F>
334where
335 N: Node,
336 F: Frontend,
337{
338 fn as_ref(&self) -> &N::State {
339 &self.state
340 }
341}
342
343impl<'a, N, F> AsMut<N::State> for Runtime<'a, N, F>
344where
345 N: Node,
346 F: Frontend,
347{
348 fn as_mut(&mut self) -> &mut N::State {
349 &mut self.state
350 }
351}
352
353pub trait CommandNames {
355 fn commands(&self) -> Vec<String>;
357}
358
359impl CommandNames for &str {
360 fn commands(&self) -> Vec<String> {
361 self.split_whitespace()
362 .flat_map(|s| s.split(','))
363 .flat_map(|s| s.split('|'))
364 .filter(|s| !s.is_empty())
365 .map(Into::into)
366 .collect()
367 }
368}
369
370impl CommandNames for String {
371 fn commands(&self) -> Vec<String> {
372 self.as_str().commands()
373 }
374}
375
376type CommandFn<N, F> = dyn Fn(&mut Runtime<N, F>) -> Interrupt<N>;
378
379pub struct RunBuilder<'a, N, F = CliFrontend>
381where
382 N: Node,
383 F: Frontend,
384{
385 commands: HashMap<String, Arc<Box<CommandFn<N, F>>>>,
386 state: Option<MayBor<'a, N::State>>,
387 start_node: Option<N>,
388 frontend: Option<MayBor<'a, F>>,
389}
390
391impl<'a, N, F> RunBuilder<'a, N, F>
392where
393 N: Node,
394 F: Frontend,
395{
396 pub fn command<C, G>(mut self, names: C, command: G) -> Self
405 where
406 C: CommandNames,
407 G: Fn(&mut Runtime<N, F>) -> Interrupt<N> + 'static,
408 {
409 let names = names.commands();
410 let command: Arc<Box<CommandFn<N, F>>> = Arc::new(Box::new(command));
411 for name in names {
412 self.commands.insert(name.to_string(), Arc::clone(&command));
413 }
414 self
415 }
416}
417
418impl<'a, N, F> Drop for RunBuilder<'a, N, F>
419where
420 N: Node,
421 F: Frontend,
422{
423 fn drop(&mut self) {
424 let mut runtime = Runtime {
425 state: self.state.take().expect("RunBuilder state is empty"),
426 curr_node: self
427 .start_node
428 .take()
429 .expect("RunBuilder start node is empty"),
430 commands: self.commands.drain().collect(),
431 frontend: self.frontend.take().expect("RunBuilder frontend is empty"),
432 };
433 loop {
434 match runtime.curr_node.clone().next(&mut runtime) {
435 Ok(node) | Err(Jump(node)) => runtime.curr_node = node,
436 Err(Exit) => break,
437 Err(Skip) | Err(Continue) => {}
438 }
439 }
440 }
441}
442
443pub fn run<'a, N, F>(start: N, state: &'a mut N::State, frontend: &'a mut F) -> RunBuilder<'a, N, F>
445where
446 N: Node,
447 F: Frontend,
448{
449 RunBuilder {
450 commands: HashMap::new(),
451 state: Some(MayBor::Borrowed(state)),
452 start_node: Some(start),
453 frontend: Some(MayBor::Borrowed(frontend)),
454 }
455}
456
457pub fn run_default<'a, N, F>() -> RunBuilder<'a, N, F>
459where
460 N: Node + Default,
461 N::State: Default,
462 F: Frontend + Default,
463{
464 RunBuilder {
465 commands: HashMap::new(),
466 state: Some(MayBor::Owned(N::State::default())),
467 start_node: Some(N::default()),
468 frontend: Some(MayBor::Owned(F::default())),
469 }
470}