use crate::code::Code;
use crate::code::CodeError as CError;
use crate::code::Invokable;
use io::Stream;
use log::*;
use logos::Logos;
use parser::Token;
use snafu::Snafu;
use std::collections::HashSet;
use std::io::{Read, Write};
pub mod code;
mod io;
mod parser;
#[derive(Debug, Snafu)]
pub enum ShellError<'a> {
#[snafu(display("Code already exists: {}", name))]
CodeAlreadyExists { name: &'a str },
#[snafu(display("Code does not exist: {}", name))]
CodeDoesNotExist { name: &'a str },
#[snafu(display("An error occured in Code. {}", err))]
CodeError { err: CError<'a> },
}
pub type ShellResult<'a, T> = Result<T, ShellError<'a>>;
pub trait ReadWrite: Read + Write {}
impl<T> ReadWrite for T where T: Read + Write {}
pub struct Shell<'a> {
codes: HashSet<Code<'a>>,
pub stdout: Box<dyn ReadWrite>,
pub stderr: Box<dyn ReadWrite>,
}
impl<'a> Shell<'a> {
pub fn new() -> Self {
debug!("Initializing Shell...");
Self {
codes: HashSet::new(),
stdout: Box::new(Stream::new()),
stderr: Box::new(Stream::new()),
}
}
pub fn register(&mut self, name: &'a str, invokable: Box<dyn Invokable>) -> ShellResult<()> {
debug!("Registering code...");
trace!("name: {}", name);
match self.codes.iter().any(|c| c.name == name) {
true => {
error!("Code already exists: {}", name);
Err(ShellError::CodeAlreadyExists { name })
}
false => match Code::new(name, invokable) {
Ok(c) => {
debug!("Inserting code...");
self.codes.insert(c);
Ok(())
}
Err(e) => {
let err = Err(ShellError::CodeError { err: e });
error!("An error occured initializing code: {:?}", err);
err
}
},
}
}
pub fn unregister(&mut self, name: &'a str) -> ShellResult<()> {
debug!("Unregistering code...");
trace!("name: {}", name);
if !self.codes.iter().any(|c| c.name == name) {
error!("Code with name does not exist: {}", name);
return Err(ShellError::CodeAlreadyExists { name });
}
debug!("Removing code...");
self.codes.retain(|c| !(c.name != name));
Ok(())
}
pub fn filter_names(
&'a self,
query: &'a str,
starts_with: bool,
) -> Box<dyn Iterator<Item = &'a str> + 'a> {
debug!("Filtering code names...");
trace!("query: {}", query);
trace!("starts with: {}", starts_with);
debug!("Generating code iterator...");
Box::new(
self.codes
.iter()
.filter(move |c| match starts_with {
true => {
let do_filter = c.name.starts_with(query);
trace!("`{}` starts with `{}`: {}", c.name, query, do_filter);
do_filter
}
false => {
let do_filter = c.name.contains(query);
trace!("`{}` contains `{}`: {}", c.name, query, do_filter);
do_filter
}
})
.map(|c| {
debug!("Mapping `{}` to &str...", c.name);
c.name
}),
)
}
pub fn run(&mut self, input: &'a str) {
debug!("Running input...");
trace!("\ninput\n-----\n{}", input);
debug!("Initializing lexer for input...");
let lex = Token::lexer(input);
debug!("Iterating tokens in lexer...");
for token in lex {
trace!("token: {:?}", token);
match token {
Token::Code((name, args)) => match self.codes.iter().find(|c| c.name == name) {
Some(c) => {
debug!("Invoking code...");
trace!("name: {}", name);
trace!("args: {}", args);
c.invokable.invoke(
&args[..],
Box::new(&mut self.stdout as &mut dyn Write),
Box::new(&mut self.stderr as &mut dyn Write),
);
}
None => warn!("Could not find Code."), },
_ => debug!("Token is not Code: {:?}", token),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::*;
use std::iter::FromIterator;
struct ClHello;
impl Invokable for ClHello {
fn invoke(
&self,
args: &str,
mut stdout: Box<&mut dyn Write>,
mut stderr: Box<&mut dyn Write>,
) {
match args.is_empty() {
true => {
stderr
.write(b"Args are empty.")
.expect("Could not write to stderr.");
stdout
.write(b"Hello, world!")
.expect("Could not write to stdout.");
}
false => {
let msg: String = format!("Hello, {}!", args);
stdout
.write(msg.as_bytes())
.expect("Could not write to stdout.");
}
}
}
}
struct SvFoo;
impl Invokable for SvFoo {
fn invoke(
&self,
_args: &str,
mut _stdout: Box<&mut dyn Write>,
mut _stderr: Box<&mut dyn Write>,
) {
unimplemented!()
}
}
struct SvFoobar;
impl Invokable for SvFoobar {
fn invoke(
&self,
_args: &str,
mut _stdout: Box<&mut dyn Write>,
mut _stderr: Box<&mut dyn Write>,
) {
unimplemented!()
}
}
#[fixture]
fn invokable() -> Box<dyn Invokable> {
Box::new(ClHello)
}
#[fixture]
fn shell<'a>(invokable: Box<dyn Invokable>) -> Shell<'a> {
let mut shell = Shell::new();
shell
.codes
.insert(Code::new("cl_hello", invokable).expect("Could not initialize Code ch_hello."));
shell
}
#[rstest(
name,
expect_failure,
case("cl_foo", false),
case("cl_hello", true),
case("foo bar", true)
)]
fn register<'a>(
mut shell: Shell<'a>,
invokable: Box<dyn Invokable>,
name: &'a str,
expect_failure: bool,
) {
match expect_failure {
true => {
assert!(shell.register(name, invokable).is_err());
}
false => {
assert!(shell.register(name, invokable).is_ok());
}
}
}
#[rstest(name, expect_failure, case("cl_foo", true), case("cl_hello", false))]
fn unregister<'a>(mut shell: Shell<'a>, name: &'a str, expect_failure: bool) {
match expect_failure {
true => assert!(shell.unregister(name).is_err()),
false => assert!(shell.unregister(name).is_ok()),
}
}
#[rstest(input, case("cl_hello"), case("cl_hello Eray"))]
fn run_out<'a>(mut shell: Shell<'a>, input: &'a str) {
shell.run(input);
let stdout = {
let ref mut stdout = shell.stdout;
let mut stdout_bytes: Vec<u8> = vec![];
stdout
.read_to_end(&mut stdout_bytes)
.expect("Could not read stdout.");
String::from_iter(stdout_bytes.into_iter().map(|b| b as char))
};
match input.contains("Eray") {
true => {
assert_eq!(stdout, "Hello, Eray!");
}
false => {
let stderr = {
let ref mut stderr = shell.stderr;
let mut stderr_bytes: Vec<u8> = vec![];
stderr
.read_to_end(&mut stderr_bytes)
.expect("Could not read stdout.");
String::from_iter(stderr_bytes.into_iter().map(|b| b as char))
};
assert_eq!(stdout, "Hello, world!");
assert_eq!(stderr, "Args are empty.");
}
}
}
#[rstest]
fn run_script_file<'a>(mut shell: Shell<'a>) {
let script = include_str!("../resources/test/example_script_2.txt");
shell.run(script);
let (stdout, stderr) = (
{
let ref mut stdout = shell.stdout;
let mut stdout_bytes: Vec<u8> = vec![];
stdout
.read_to_end(&mut stdout_bytes)
.expect("Could not read stdout.");
String::from_iter(stdout_bytes.into_iter().map(|b| b as char))
},
{
let ref mut stderr = shell.stderr;
let mut stderr_bytes: Vec<u8> = vec![];
stderr
.read_to_end(&mut stderr_bytes)
.expect("Could not read stdout.");
String::from_iter(stderr_bytes.into_iter().map(|b| b as char))
},
);
assert_eq!(stdout, "Hello, world!Hello, Eray!");
assert_eq!(stderr, "Args are empty.");
}
#[rstest]
fn filter_names(mut shell: Shell) {
shell
.register("sv_foo", Box::new(SvFoo))
.expect("Could not register sv_foo.");
shell
.register("sv_foobar", Box::new(SvFoobar))
.expect("Could not register sv_foobar.");
let sv_foo_names: HashSet<&str> = shell.filter_names("sv_foo", true).collect();
assert_eq!(
sv_foo_names,
HashSet::from_iter(["sv_foo", "sv_foobar"].iter().cloned())
);
let foo_names: HashSet<&str> = shell.filter_names("foo", false).collect();
assert_eq!(
foo_names,
HashSet::from_iter(["sv_foo", "sv_foobar"].iter().cloned())
);
}
}