use std::collections::HashMap;
use std::fmt::Display;
use std::io;
#[cfg(feature = "rustyline")]
use thiserror::Error;
use yansi::Paint;
use crate::{
input_handler::{InputResult, IO},
*,
};
#[derive(Clone)]
pub struct Shell<'a, T, M: Display, H, I: InputHandler> {
pub prompt: M,
pub commands: HashMap<&'a str, Command<T>>,
pub state: T,
pub handler: H,
pub description: String,
pub input_handler: I,
}
impl<'a, T, M: Display> Shell<'a, T, M, handler::DefaultHandler, IO> {
pub fn new(state: T, prompt: M) -> Self {
Shell {
prompt,
commands: HashMap::new(),
state,
handler: handler::DefaultHandler(),
description: String::new(),
input_handler: IO,
}
}
}
#[cfg(feature = "async")]
#[cfg_attr(nightly, doc(cfg(feature = "async")))]
impl<'a, T, M: Display> Shell<'a, T, M, handler::DefaultAsyncHandler, IO> {
pub fn new_async(state: T, prompt: M) -> Self {
Shell {
prompt,
commands: HashMap::new(),
state,
handler: handler::DefaultAsyncHandler(),
description: String::new(),
input_handler: IO,
}
}
}
impl<'a, T, M: Display, H: Handler<T>, I: InputHandler> Shell<'a, T, M, H, I> {
pub fn new_with_handler(
state: T,
prompt: M,
handler: H,
input_handler: I,
) -> Self {
Shell {
prompt,
commands: HashMap::new(),
state,
handler,
description: String::new(),
input_handler,
}
}
pub fn run(&mut self) -> io::Result<()> {
'_shell: loop {
let line =
match self.input_handler.read(&self.prompt.to_string())? {
InputResult::S(line) => line,
InputResult::Interrupted => continue '_shell,
InputResult::EOF => break '_shell,
};
match Self::unescape(line.trim()) {
Ok(line) => {
if self.handler.handle(
line,
&self.commands,
&mut self.state,
&self.description,
) {
break '_shell;
}
}
Err(e) => eprintln!("{}", Paint::red(e.to_string().as_str())),
}
}
Ok(())
}
}
#[cfg(feature = "async")]
#[cfg_attr(nightly, doc(cfg(feature = "async")))]
impl<'a, T: Send, M: Display, H: AsyncHandler<T>, I: InputHandler>
Shell<'a, T, M, H, I>
{
pub fn new_with_async_handler(
state: T,
prompt: M,
handler: H,
input_handler: I,
) -> Self {
Shell {
prompt,
commands: HashMap::new(),
state,
handler,
description: String::new(),
input_handler,
}
}
pub async fn run_async(&mut self) -> io::Result<()> {
'_shell: loop {
let line =
match self.input_handler.read(&self.prompt.to_string())? {
InputResult::S(line) => line,
InputResult::Interrupted => continue '_shell,
InputResult::EOF => break '_shell,
};
match Self::unescape(line.trim()) {
Ok(line) => {
if self
.handler
.handle_async(
line,
&self.commands,
&mut self.state,
&self.description,
)
.await
{
break '_shell;
}
}
Err(e) => eprintln!("{}", Paint::red(e.to_string())),
}
}
Ok(())
}
}
#[derive(Error, Debug)]
pub enum UnescapeError {
#[error("unhandled escape sequence \\{0}")]
UnhandledEscapeSequence(char),
#[error("unclosed quotes")]
UnclosedQuotes,
}
impl<'a, T, M: Display, H, I: InputHandler> Shell<'a, T, M, H, I> {
fn unescape(command: &str) -> Result<Vec<String>, UnescapeError> {
let mut vec = vec![String::new()];
let mut escape = false;
let mut string = false;
for c in command.chars() {
let segment = vec.last_mut().unwrap();
if escape {
match c {
'\\' => segment.push('\\'),
' ' if !string => segment.push(' '),
'n' => segment.push('\n'),
'r' => segment.push('\r'),
't' => segment.push('\t'),
'"' => segment.push('"'),
_ => return Err(UnescapeError::UnhandledEscapeSequence(c)),
}
escape = false;
} else {
match c {
'\\' => escape = true,
'"' => string = !string,
' ' if string => segment.push(c),
' ' if !string => vec.push(String::new()),
_ => segment.push(c),
}
}
}
if string {
return Err(UnescapeError::UnclosedQuotes);
}
if vec.len() == 1 && vec[0].is_empty() {
vec.clear();
}
Ok(vec)
}
}