use std::{cell::RefCell, convert::Infallible, fmt::Debug, marker::PhantomData, rc::Rc}; use embedded_cli::{ arguments::{Arg as CliArg, ArgError}, cli::{Cli, CliBuilder, CliHandle}, command::RawCommand as CliRawCommand, service::{Autocomplete, CommandProcessor, Help, ParseError as CliParseError, ProcessError}, }; use embedded_io::ErrorType; use crate::terminal::Terminal; /// Helper trait to wrap parsed command or error with lifetime into owned command pub trait CommandConvert: Sized { fn convert(cmd: CliRawCommand<'_>) -> Result; } #[macro_export] macro_rules! impl_convert { ($from_ty:ty => $to_ty:ty, $var_name:ident, $conversion:block) => { impl embedded_cli::service::Autocomplete for $to_ty { #[cfg(feature = "autocomplete")] fn autocomplete( request: embedded_cli::autocomplete::Request<'_>, autocompletion: &mut embedded_cli::autocomplete::Autocompletion<'_>, ) { <$from_ty>::autocomplete(request, autocompletion) } } impl embedded_cli::service::Help for $to_ty { #[cfg(feature = "help")] fn command_count() -> usize { <$from_ty>::command_count() } #[cfg(feature = "help")] fn list_commands, E: embedded_io::Error>( writer: &mut embedded_cli::writer::Writer<'_, W, E>, ) -> Result<(), E> { <$from_ty>::list_commands(writer) } #[cfg(feature = "help")] fn command_help< W: embedded_io::Write, E: embedded_io::Error, F: FnMut(&mut embedded_cli::writer::Writer<'_, W, E>) -> Result<(), E>, >( parent: &mut F, command: embedded_cli::command::RawCommand<'_>, writer: &mut embedded_cli::writer::Writer<'_, W, E>, ) -> Result<(), embedded_cli::service::HelpError> { <$from_ty>::command_help(parent, command, writer) } } impl crate::wrapper::CommandConvert for $to_ty { fn convert( cmd: embedded_cli::command::RawCommand<'_>, ) -> Result { let $var_name = <$from_ty as embedded_cli::service::FromRaw>::parse(cmd)?; let cmd = $conversion; Ok(cmd) } } }; } #[derive(Clone, Debug, Eq, PartialEq)] pub enum Arg { DoubleDash, LongOption(String), ShortOption(char), Value(String), } #[derive(Clone, Debug, Eq, PartialEq)] pub struct RawCommand { pub name: String, pub args: Vec>, } impl_convert! {CliRawCommand<'_> => RawCommand, command, { match command { cmd => cmd.into(), } }} impl<'a> From> for RawCommand { fn from(value: CliRawCommand<'a>) -> Self { Self { name: value.name().to_string(), args: value .args() .args() .map(|arg| match arg { Ok(arg) => Ok(match arg { CliArg::DoubleDash => Arg::DoubleDash, CliArg::LongOption(name) => Arg::LongOption(name.to_string()), CliArg::ShortOption(name) => Arg::ShortOption(name), CliArg::Value(value) => Arg::Value(value.to_string()), }), Err(err) => Err(err), }) .collect(), } } } #[derive(Debug)] pub struct State { written: Vec, commands: Vec>, } impl Default for State { fn default() -> Self { Self { written: Default::default(), commands: Default::default(), } } } #[derive(Clone, Debug, Eq, PartialEq)] pub enum ParseError { MissingRequiredArgument { name: String }, ParseValueError { value: String, expected: String }, UnexpectedArgument { value: String }, UnexpectedLongOption { name: String }, UnexpectedShortOption { name: char }, UnknownCommand, Other, } impl<'a> From> for ParseError { fn from(value: CliParseError<'a>) -> Self { match value { CliParseError::MissingRequiredArgument { name } => { Self::MissingRequiredArgument { name: name.into() } } CliParseError::ParseValueError { value, expected } => Self::ParseValueError { value: value.into(), expected: expected.into(), }, CliParseError::UnexpectedArgument { value } => Self::UnexpectedArgument { value: value.into(), }, CliParseError::UnexpectedLongOption { name } => { Self::UnexpectedLongOption { name: name.into() } } CliParseError::UnexpectedShortOption { name } => { Self::UnexpectedShortOption { name: name.into() } } CliParseError::UnknownCommand => Self::UnknownCommand, _ => Self::Other, } } } pub struct CliWrapper { /// Actual cli object cli: Cli, Infallible, &'static mut [u8], &'static mut [u8]>, handler: Option< Box, Infallible>, T) -> Result<(), Infallible>>, >, state: Rc>>, terminal: Terminal, } struct App { handler: Option< Box, Infallible>, T) -> Result<(), Infallible>>, >, state: Rc>>, } impl CommandProcessor, Infallible> for App { fn process<'a>( &mut self, cli: &mut CliHandle<'_, Writer, Infallible>, command: CliRawCommand<'a>, ) -> Result<(), ProcessError<'a, Infallible>> { let command = T::convert(command); self.state.borrow_mut().commands.push(command.clone()); if let (Some(handler), Ok(command)) = (&mut self.handler, command) { handler(cli, command)?; } Ok(()) } } impl Default for CliWrapper { fn default() -> Self { Self::new() } } impl CliWrapper { pub fn builder() -> CliWrapperBuilder { CliWrapperBuilder { command_size: 80, history_size: 500, prompt: None, _ph: PhantomData, } } pub fn new() -> Self { Self::builder().build() } pub fn process_str(&mut self, text: &str) { let mut app = App { handler: self.handler.take(), state: self.state.clone(), }; for b in text.as_bytes() { self.cli.process_byte::(*b, &mut app).unwrap(); } self.handler = app.handler.take(); self.update_terminal(); } pub fn send_backspace(&mut self) { self.process_str("\x08") } pub fn send_down(&mut self) { self.process_str("\x1B[B") } pub fn send_enter(&mut self) { self.process_str("\n") } pub fn send_left(&mut self) { self.process_str("\x1B[D") } pub fn send_right(&mut self) { self.process_str("\x1B[C") } pub fn send_tab(&mut self) { self.process_str("\t") } pub fn send_up(&mut self) { self.process_str("\x1B[A") } pub fn set_handler( &mut self, handler: impl FnMut(&mut CliHandle<'_, Writer, Infallible>, T) -> Result<(), Infallible> + 'static, ) { self.handler = Some(Box::new(handler)); } pub fn set_prompt(&mut self, prompt: &'static str) { self.cli.set_prompt(prompt).unwrap(); self.update_terminal(); } pub fn received_commands(&self) -> Vec> { self.state.borrow().commands.to_vec() } pub fn terminal(&self) -> &Terminal { &self.terminal } pub fn write_str(&mut self, text: &str) { self.cli.write(|writer| writer.write_str(text)).unwrap(); self.update_terminal(); } fn update_terminal(&mut self) { for byte in self.state.borrow_mut().written.drain(..) { self.terminal.receive_byte(byte) } } } #[derive(Debug)] pub struct CliWrapperBuilder { command_size: usize, history_size: usize, prompt: Option<&'static str>, _ph: PhantomData, } impl CliWrapperBuilder { pub fn build(self) -> CliWrapper { let state = Rc::new(RefCell::new(State::default())); let writer = Writer { state: state.clone(), }; //TODO: impl Buffer for Vec so no need to leak let builder = CliBuilder::default() .writer(writer) .command_buffer(vec![0; self.command_size].leak()) .history_buffer(vec![0; self.history_size].leak()); let builder = if let Some(prompt) = self.prompt { builder.prompt(prompt) } else { builder }; let cli = builder.build().unwrap(); let terminal = Terminal::new(); let mut wrapper = CliWrapper { cli, handler: None, state, terminal, }; wrapper.update_terminal(); wrapper } pub fn prompt(mut self, prompt: &'static str) -> Self { self.prompt = Some(prompt); self } } pub struct Writer { state: Rc>>, } impl ErrorType for Writer { type Error = Infallible; } impl embedded_io::Write for Writer { fn write(&mut self, buf: &[u8]) -> Result { self.state.borrow_mut().written.extend_from_slice(buf); Ok(buf.len()) } fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) } }