#![deny(missing_docs)]
#![deny(unsafe_code)]
mod paragraphs;
pub use paragraphs::*;
mod frontend;
pub use frontend::*;
use std::{
collections::HashMap,
error::Error,
fmt::{self, Display, Formatter},
fs, io,
ops::{Deref, DerefMut},
path::Path,
sync::Arc,
};
use serde::{de::DeserializeOwned, Serialize};
pub use serde_derive::{Deserialize, Serialize};
enum MayBor<'a, T> {
Owned(T),
Borrowed(&'a mut T),
}
impl<'a, T> Deref for MayBor<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
use MayBor::*;
match self {
Owned(x) => x,
Borrowed(x) => x,
}
}
}
impl<'a, T> DerefMut for MayBor<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
use MayBor::*;
match self {
Owned(x) => x,
Borrowed(x) => x,
}
}
}
#[derive(Debug)]
pub enum RuntimeError {
IO(io::Error),
Serialize(serde_yaml::Error),
}
impl From<io::Error> for RuntimeError {
fn from(e: io::Error) -> Self {
RuntimeError::IO(e)
}
}
impl From<serde_yaml::Error> for RuntimeError {
fn from(e: serde_yaml::Error) -> Self {
RuntimeError::Serialize(e)
}
}
impl Display for RuntimeError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
use RuntimeError::*;
match self {
IO(e) => write!(f, "{}", e),
Serialize(e) => write!(f, "{}", e),
}
}
}
impl Error for RuntimeError {}
pub type RuntimeResult<T> = Result<T, RuntimeError>;
pub enum Interrupt<N> {
Exit,
Skip,
Continue,
Jump(N),
}
pub use Interrupt::*;
pub type ControlResult<T, N> = Result<T, Interrupt<N>>;
pub type Control<N> = ControlResult<N, N>;
pub fn next<T, N>(data: T) -> ControlResult<T, N> {
Ok(data)
}
pub fn exit<T, N>() -> ControlResult<T, N> {
Err(Exit)
}
pub trait Node: Clone {
type State;
fn next<F>(self, rt: &mut Runtime<Self, F>) -> Control<Self>
where
F: Frontend;
}
pub struct Runtime<'a, N, F = CliFrontend>
where
N: Node,
F: Frontend,
{
curr_node: N,
state: MayBor<'a, N::State>,
frontend: MayBor<'a, F>,
commands: HashMap<String, Arc<Box<CommandFn<N, F>>>>,
}
impl<'a, N, F> Runtime<'a, N, F>
where
N: Node,
F: Frontend,
{
fn borrow_state_frontend<G>(&mut self, f: G)
where
G: FnOnce(&mut N::State, &mut F),
{
f(&mut *self.state, &mut self.frontend)
}
pub fn push<M>(&mut self) -> Interrupt<N>
where
M: Node<State = N::State> + Default,
{
self.borrow_state_frontend(|state, frontend| {
run(M::default(), state, frontend);
});
Continue
}
pub fn push_node<M>(&mut self, node: M) -> Interrupt<N>
where
M: Node<State = N::State>,
{
self.borrow_state_frontend(|state, frontend| {
run(node, state, frontend);
});
Continue
}
pub fn save<P: AsRef<Path>>(&self, path: P) -> RuntimeResult<()>
where
N: Serialize,
N::State: Serialize,
{
fs::write(
path,
&serde_yaml::to_vec(&(&self.curr_node, self.state.deref()))?,
)?;
Ok(())
}
pub fn load<P: AsRef<Path>>(&mut self, path: P) -> RuntimeResult<()>
where
N: DeserializeOwned,
N::State: DeserializeOwned,
{
let (curr_node, state): (N, N::State) = serde_yaml::from_slice(&fs::read(path)?)?;
self.curr_node = curr_node;
self.state = MayBor::Owned(state);
Ok(())
}
#[must_use]
fn prompt_intercept(&mut self) -> ControlResult<String, N> {
loop {
let input = self.input();
match input.as_str().trim() {
"quit" => break exit(),
s => {
if let Some(f) = self.commands.get(s).cloned() {
match f(self) {
Continue => {}
e => break Err(e),
}
} else {
break next(s.to_string());
}
}
}
}
}
#[must_use]
pub fn prompt<S: Display>(&mut self, prompt: S) -> ControlResult<String, N> {
loop {
self.println(&prompt);
let input = self.prompt_intercept()?;
if !input.is_empty() {
break next(input);
}
}
}
#[must_use]
pub fn wait(&mut self) -> ControlResult<(), N> {
self.prompt_intercept()?;
next(())
}
#[must_use]
pub fn multiple_choice<M, C>(&mut self, message: M, choices: &[C]) -> ControlResult<usize, N>
where
M: Display,
C: Display,
{
loop {
self.println(&message);
for (i, choice) in choices.iter().enumerate() {
self.println(format!("{}) {}", i + 1, choice));
}
let resp = self.prompt_intercept()?;
match resp.parse::<usize>() {
Ok(num) if num > 0 && num <= choices.len() => break next(num - 1),
Err(_) => {
if let Some(i) = choices
.iter()
.position(|c| c.to_string().to_lowercase() == resp.to_lowercase())
{
break next(i);
} else {
self.println("Invalid choice")
}
}
_ => self.println("Invalid choice"),
}
}
}
#[must_use]
pub fn multiple_choice_named<M, S, C, I>(
&mut self,
message: M,
choices: I,
) -> ControlResult<C, N>
where
M: Display,
S: Display,
I: IntoIterator<Item = (S, C)>,
{
let mut choices: Vec<(S, C)> = choices.into_iter().collect();
loop {
self.println(&message);
for (i, (text, _)) in choices.iter().enumerate() {
self.println(format!("{}) {}", i + 1, text));
}
let resp = self.prompt_intercept()?;
match resp.parse::<usize>() {
Ok(num) if num > 0 && num <= choices.len() => {
break next(choices.remove(num - 1).1)
}
_ => self.println("Invalid choice"),
}
}
}
}
impl<'a, N, F> Deref for Runtime<'a, N, F>
where
N: Node,
F: Frontend,
{
type Target = N::State;
fn deref(&self) -> &Self::Target {
&self.state
}
}
impl<'a, N, F> DerefMut for Runtime<'a, N, F>
where
N: Node,
F: Frontend,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.state
}
}
impl<'a, N, F> AsRef<N::State> for Runtime<'a, N, F>
where
N: Node,
F: Frontend,
{
fn as_ref(&self) -> &N::State {
&self.state
}
}
impl<'a, N, F> AsMut<N::State> for Runtime<'a, N, F>
where
N: Node,
F: Frontend,
{
fn as_mut(&mut self) -> &mut N::State {
&mut self.state
}
}
pub trait CommandNames {
fn commands(&self) -> Vec<String>;
}
impl CommandNames for &str {
fn commands(&self) -> Vec<String> {
self.split_whitespace()
.flat_map(|s| s.split(','))
.flat_map(|s| s.split('|'))
.filter(|s| !s.is_empty())
.map(Into::into)
.collect()
}
}
impl CommandNames for String {
fn commands(&self) -> Vec<String> {
self.as_str().commands()
}
}
type CommandFn<N, F> = dyn Fn(&mut Runtime<N, F>) -> Interrupt<N>;
pub struct RunBuilder<'a, N, F = CliFrontend>
where
N: Node,
F: Frontend,
{
commands: HashMap<String, Arc<Box<CommandFn<N, F>>>>,
state: Option<MayBor<'a, N::State>>,
start_node: Option<N>,
frontend: Option<MayBor<'a, F>>,
}
impl<'a, N, F> RunBuilder<'a, N, F>
where
N: Node,
F: Frontend,
{
pub fn command<C, G>(mut self, names: C, command: G) -> Self
where
C: CommandNames,
G: Fn(&mut Runtime<N, F>) -> Interrupt<N> + 'static,
{
let names = names.commands();
let command: Arc<Box<CommandFn<N, F>>> = Arc::new(Box::new(command));
for name in names {
self.commands.insert(name.to_string(), Arc::clone(&command));
}
self
}
}
impl<'a, N, F> Drop for RunBuilder<'a, N, F>
where
N: Node,
F: Frontend,
{
fn drop(&mut self) {
let mut runtime = Runtime {
state: self.state.take().expect("RunBuilder state is empty"),
curr_node: self
.start_node
.take()
.expect("RunBuilder start node is empty"),
commands: self.commands.drain().collect(),
frontend: self.frontend.take().expect("RunBuilder frontend is empty"),
};
loop {
match runtime.curr_node.clone().next(&mut runtime) {
Ok(node) | Err(Jump(node)) => runtime.curr_node = node,
Err(Exit) => break,
Err(Skip) | Err(Continue) => {}
}
}
}
}
pub fn run<'a, N, F>(start: N, state: &'a mut N::State, frontend: &'a mut F) -> RunBuilder<'a, N, F>
where
N: Node,
F: Frontend,
{
RunBuilder {
commands: HashMap::new(),
state: Some(MayBor::Borrowed(state)),
start_node: Some(start),
frontend: Some(MayBor::Borrowed(frontend)),
}
}
pub fn run_default<'a, N, F>() -> RunBuilder<'a, N, F>
where
N: Node + Default,
N::State: Default,
F: Frontend + Default,
{
RunBuilder {
commands: HashMap::new(),
state: Some(MayBor::Owned(N::State::default())),
start_node: Some(N::default()),
frontend: Some(MayBor::Owned(F::default())),
}
}