Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions embedded-cli-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ default = []
autocomplete = []
help = []

async = []

[dependencies]
convert_case = "0.6.0"
darling = "0.20.8"
Expand Down
109 changes: 79 additions & 30 deletions embedded-cli-macros/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,98 @@ pub fn impl_processor(vis: &Visibility, target: &TargetType) -> Result<TokenStre
let named_lifetime = target.named_lifetime();
let unnamed_lifetime = target.unnamed_lifetime();

let output = quote! {

impl #named_lifetime #ident #named_lifetime {
#vis fn processor<
W: _io::Write<Error = E>,
E: _io::Error,
F: FnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>,
>(
f: F,
) -> impl _cli::service::CommandProcessor<W, E> {
struct Processor<
let mut output = TokenStream::new();

if cfg!(feature = "async") {
output.extend(quote! {

impl #named_lifetime #ident #named_lifetime {
#vis fn processor_async<
W: _io::Write<Error = E>,
E: _io::Error,
F: FnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>,
> {
F: AsyncFnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>,
>(
f: F,
_ph: core::marker::PhantomData<(W, E)>,
) -> impl _cli::service::CommandProcessorAsync<W, E> {
struct Processor<
W: _io::Write<Error = E>,
E: _io::Error,
F: AsyncFnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>,
> {
f: F,
_ph: core::marker::PhantomData<(W, E)>,
}

impl<
W: _io::Write<Error = E>,
E: _io::Error,
F: AsyncFnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>,
> _cli::service::CommandProcessorAsync<W, E> for Processor<W, E, F>
{
async fn process<'a>(
&mut self,
cli: &mut _cli::cli::CliHandle<'_, W, E>,
raw: _cli::command::RawCommand<'a>,
) -> Result<(), _cli::service::ProcessError<'a, E>> {
let cmd = <#ident #unnamed_lifetime as _cli::service::FromRaw>::parse(raw)?;
(self.f)(cli, cmd).await?;
Ok(())
}
}

Processor {
f,
_ph: core::marker::PhantomData,
}
}
}
})
};
output.extend(
quote! {

impl<
impl #named_lifetime #ident #named_lifetime {
#vis fn processor<
W: _io::Write<Error = E>,
E: _io::Error,
F: FnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>,
>(
f: F,
) -> impl _cli::service::CommandProcessor<W, E> {
struct Processor<
W: _io::Write<Error = E>,
E: _io::Error,
F: FnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>,
> _cli::service::CommandProcessor<W, E> for Processor<W, E, F>
{
fn process<'a>(
&mut self,
cli: &mut _cli::cli::CliHandle<'_, W, E>,
raw: _cli::command::RawCommand<'a>,
) -> Result<(), _cli::service::ProcessError<'a, E>> {
let cmd = <#ident #unnamed_lifetime as _cli::service::FromRaw>::parse(raw)?;
(self.f)(cli, cmd)?;
Ok(())
> {
f: F,
_ph: core::marker::PhantomData<(W, E)>,
}

impl<
W: _io::Write<Error = E>,
E: _io::Error,
F: FnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>,
> _cli::service::CommandProcessor<W, E> for Processor<W, E, F>
{
fn process<'a>(
&mut self,
cli: &mut _cli::cli::CliHandle<'_, W, E>,
raw: _cli::command::RawCommand<'a>,
) -> Result<(), _cli::service::ProcessError<'a, E>> {
let cmd = <#ident #unnamed_lifetime as _cli::service::FromRaw>::parse(raw)?;
(self.f)(cli, cmd)?;
Ok(())
}
}
}

Processor {
f,
_ph: core::marker::PhantomData,
Processor {
f,
_ph: core::marker::PhantomData,
}
}
}
}
};
);

Ok(output)
}
4 changes: 4 additions & 0 deletions embedded-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ help = ["embedded-cli-macros/help"]
history = []
del_is_bs = []

async = ["embedded-cli-macros/async"]

[dependencies]
embedded-cli-macros = { version = "0.2.1", path = "../embedded-cli-macros", optional = true }

bitflags = "2.5.0"
embedded-io = "0.7.1"
ufmt = "0.2.0"
maybe-async-cfg2 = "0.3.0"
embedded-io-async = "0.7.0"

[dev-dependencies]
regex = "1.10.4"
Expand Down
8 changes: 8 additions & 0 deletions embedded-cli/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use embedded_io::{Error, Write};

use crate::{buffer::Buffer, cli::Cli, writer::EmptyWriter};

#[cfg(feature = "async")]
use crate::cli::CliAsync;

pub const DEFAULT_CMD_LEN: usize = 40;
pub const DEFAULT_HISTORY_LEN: usize = 100;
pub const DEFAULT_PROMPT: &str = "$ ";
Expand Down Expand Up @@ -41,6 +44,11 @@ where
Cli::from_builder(self)
}

#[cfg(feature = "async")]
pub fn build_async(self) -> Result<CliAsync<W, E, CommandBuffer, HistoryBuffer>, E> {
CliAsync::from_builder(self)
}

pub fn command_buffer<B: Buffer>(
self,
command_buffer: B,
Expand Down
36 changes: 25 additions & 11 deletions embedded-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ use crate::{
writer::{WriteExt, Writer},
};

#[cfg(feature = "async")]
use crate::service::CommandProcessorAsync;

#[cfg(feature = "autocomplete")]
use crate::autocomplete::Request;

Expand All @@ -28,6 +31,7 @@ use crate::{help::HelpRequest, service::HelpError};
use crate::history::History;

use embedded_io::{Error, Write};
use maybe_async_cfg2::maybe;

pub struct CliHandle<'a, W: Write<Error = E>, E: embedded_io::Error> {
new_prompt: Option<&'static str>,
Expand Down Expand Up @@ -77,6 +81,7 @@ enum NavigateInput {
Forward,
}

#[maybe(sync(keep_self), async(feature = "async"))]
pub struct Cli<W: Write<Error = E>, E: Error, CommandBuffer: Buffer, HistoryBuffer: Buffer> {
editor: Option<Editor<CommandBuffer>>,
#[cfg(feature = "history")]
Expand All @@ -88,6 +93,7 @@ pub struct Cli<W: Write<Error = E>, E: Error, CommandBuffer: Buffer, HistoryBuff
_ph: PhantomData<HistoryBuffer>,
}

#[maybe(sync(keep_self), async(feature = "async"))]
impl<W, E, CommandBuffer, HistoryBuffer> Debug for Cli<W, E, CommandBuffer, HistoryBuffer>
where
W: Write<Error = E>,
Expand All @@ -104,6 +110,11 @@ where
}
}

#[maybe(
sync(keep_self),
async(feature = "async"),
idents(CommandProcessor(sync, async = "CommandProcessorAsync"))
)]
impl<W, E, CommandBuffer, HistoryBuffer> Cli<W, E, CommandBuffer, HistoryBuffer>
where
W: Write<Error = E>,
Expand Down Expand Up @@ -157,23 +168,26 @@ where
/// command set and/or command processor.
/// In process callback you can change some outside state
/// so next calls will use different processor
pub fn process_byte<C: Autocomplete + Help, P: CommandProcessor<W, E>>(
pub async fn process_byte<C: Autocomplete + Help, P: CommandProcessor<W, E>>(
&mut self,
b: u8,
processor: &mut P,
) -> Result<(), E> {
if let (Some(mut editor), Some(mut input_generator)) =
(self.editor.take(), self.input_generator.take())
{
let result = input_generator
.accept(b)
.map(|input| match input {
let result = if let Some(input) = input_generator.accept(b) {
Some(match input {
Input::Control(control) => {
self.on_control_input::<C, _>(&mut editor, control, processor)
.await
}
Input::Char(text) => self.on_text_input(&mut editor, text),
})
.unwrap_or(Ok(()));
} else {
None
}
.unwrap_or(Ok(()));

self.editor = Some(editor);
self.input_generator = Some(input_generator);
Expand Down Expand Up @@ -244,7 +258,7 @@ where
Ok(())
}

fn on_control_input<C: Autocomplete + Help, P: CommandProcessor<W, E>>(
async fn on_control_input<C: Autocomplete + Help, P: CommandProcessor<W, E>>(
&mut self,
editor: &mut Editor<CommandBuffer>,
control: ControlInput,
Expand All @@ -259,7 +273,7 @@ where
let text = editor.text_mut();

let tokens = Tokens::new(text);
self.process_input::<C, _>(tokens, processor)?;
self.process_input::<C, _>(tokens, processor).await?;

editor.clear();

Expand Down Expand Up @@ -360,15 +374,15 @@ where
Ok(())
}

fn process_command<P: CommandProcessor<W, E>>(
async fn process_command<P: CommandProcessor<W, E>>(
&mut self,
command: RawCommand<'_>,
handler: &mut P,
) -> Result<(), E> {
let cli_writer = Writer::new(&mut self.writer);
let mut handle = CliHandle::new(cli_writer);

let res = handler.process(&mut handle, command);
let res = handler.process(&mut handle, command).await;

if let Some(prompt) = handle.new_prompt {
self.prompt = prompt;
Expand All @@ -386,7 +400,7 @@ where
}

#[allow(clippy::extra_unused_type_parameters)]
fn process_input<C: Help, P: CommandProcessor<W, E>>(
async fn process_input<C: Help, P: CommandProcessor<W, E>>(
&mut self,
tokens: Tokens<'_>,
handler: &mut P,
Expand All @@ -397,7 +411,7 @@ where
return self.process_help::<C>(request);
}

self.process_command(command, handler)?;
self.process_command(command, handler).await?;
};

Ok(())
Expand Down
42 changes: 42 additions & 0 deletions embedded-cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ use crate::{
token::Tokens,
};

#[cfg(feature = "async")]
use crate::service::CommandProcessorAsync;

#[cfg(feature = "autocomplete")]
use crate::autocomplete::{Autocompletion, Request};

Expand Down Expand Up @@ -90,6 +93,45 @@ impl<'a> RawCommand<'a> {
_ph: PhantomData,
}
}

#[cfg(feature = "async")]
pub fn processor_async<
W: Write<Error = E>,
E: embedded_io::Error,
F: AsyncFnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>,
>(
f: F,
) -> impl CommandProcessorAsync<W, E> {
struct Processor<
W: Write<Error = E>,
E: embedded_io::Error,
F: AsyncFnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>,
> {
f: F,
_ph: PhantomData<(W, E)>,
}

impl<
W: Write<Error = E>,
E: embedded_io::Error,
F: AsyncFnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>,
> CommandProcessorAsync<W, E> for Processor<W, E, F>
{
async fn process<'a>(
&mut self,
cli: &mut CliHandle<'_, W, E>,
raw: RawCommand<'a>,
) -> Result<(), ProcessError<'a, E>> {
(self.f)(cli, raw).await?;
Ok(())
}
}

Processor {
f,
_ph: PhantomData,
}
}
}

impl Autocomplete for RawCommand<'_> {
Expand Down
Loading