From 4d4954dc9ef5ee4e0a9e5683a2427d390b62f227 Mon Sep 17 00:00:00 2001 From: Tommaso Clini Date: Wed, 10 Jun 2026 12:37:04 +0200 Subject: [PATCH 1/6] async closures version? --- embedded-cli-macros/src/processor.rs | 10 +++++----- embedded-cli/src/cli.rs | 25 ++++++++++++++----------- embedded-cli/src/command.rs | 10 +++++----- embedded-cli/src/service.rs | 4 ++-- examples/desktop/Cargo.toml | 1 + examples/desktop/src/main.rs | 6 ++++-- 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/embedded-cli-macros/src/processor.rs b/embedded-cli-macros/src/processor.rs index 78c5e43..80a86e1 100644 --- a/embedded-cli-macros/src/processor.rs +++ b/embedded-cli-macros/src/processor.rs @@ -16,14 +16,14 @@ pub fn impl_processor(vis: &Visibility, target: &TargetType) -> Result, 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, ) -> impl _cli::service::CommandProcessor { struct Processor< W: _io::Write, 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)>, @@ -32,16 +32,16 @@ pub fn impl_processor(vis: &Visibility, target: &TargetType) -> Result, 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>, > _cli::service::CommandProcessor for Processor { - fn process<'a>( + 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)?; + (self.f)(cli, cmd).await?; Ok(()) } } diff --git a/embedded-cli/src/cli.rs b/embedded-cli/src/cli.rs index f2d91b7..0deb817 100644 --- a/embedded-cli/src/cli.rs +++ b/embedded-cli/src/cli.rs @@ -157,7 +157,7 @@ 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>( + pub async fn process_byte>( &mut self, b: u8, processor: &mut P, @@ -165,15 +165,18 @@ where 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::(&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); @@ -244,7 +247,7 @@ where Ok(()) } - fn on_control_input>( + async fn on_control_input>( &mut self, editor: &mut Editor, control: ControlInput, @@ -259,7 +262,7 @@ where let text = editor.text_mut(); let tokens = Tokens::new(text); - self.process_input::(tokens, processor)?; + self.process_input::(tokens, processor).await?; editor.clear(); @@ -354,7 +357,7 @@ where Ok(()) } - fn process_command>( + async fn process_command>( &mut self, command: RawCommand<'_>, handler: &mut P, @@ -362,7 +365,7 @@ where 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; @@ -380,7 +383,7 @@ where } #[allow(clippy::extra_unused_type_parameters)] - fn process_input>( + async fn process_input>( &mut self, tokens: Tokens<'_>, handler: &mut P, @@ -391,7 +394,7 @@ where return self.process_help::(request); } - self.process_command(command, handler)?; + self.process_command(command, handler).await?; }; Ok(()) diff --git a/embedded-cli/src/command.rs b/embedded-cli/src/command.rs index bd6cf49..e411acf 100644 --- a/embedded-cli/src/command.rs +++ b/embedded-cli/src/command.rs @@ -56,14 +56,14 @@ impl<'a> RawCommand<'a> { pub fn processor< W: Write, E: embedded_io::Error, - F: FnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>, + F: AsyncFnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>, >( f: F, ) -> impl CommandProcessor { struct Processor< W: Write, E: embedded_io::Error, - F: FnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>, + F: AsyncFnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>, > { f: F, _ph: PhantomData<(W, E)>, @@ -72,15 +72,15 @@ impl<'a> RawCommand<'a> { impl< W: Write, E: embedded_io::Error, - F: FnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>, + F: AsyncFnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>, > CommandProcessor for Processor { - fn process<'a>( + async fn process<'a>( &mut self, cli: &mut CliHandle<'_, W, E>, raw: RawCommand<'a>, ) -> Result<(), ProcessError<'a, E>> { - (self.f)(cli, raw)?; + (self.f)(cli, raw).await?; Ok(()) } } diff --git a/embedded-cli/src/service.rs b/embedded-cli/src/service.rs index 870e9c3..1545ab4 100644 --- a/embedded-cli/src/service.rs +++ b/embedded-cli/src/service.rs @@ -118,7 +118,7 @@ pub trait FromRaw<'a>: Sized { } pub trait CommandProcessor, E: embedded_io::Error> { - fn process<'a>( + async fn process<'a>( &mut self, cli: &mut CliHandle<'_, W, E>, raw: RawCommand<'a>, @@ -131,7 +131,7 @@ where E: embedded_io::Error, F: for<'a> FnMut(&mut CliHandle<'_, W, E>, RawCommand<'a>) -> Result<(), ProcessError<'a, E>>, { - fn process<'a>( + async fn process<'a>( &mut self, cli: &mut CliHandle<'_, W, E>, command: RawCommand<'a>, diff --git a/examples/desktop/Cargo.toml b/examples/desktop/Cargo.toml index 500d60b..82851e1 100644 --- a/examples/desktop/Cargo.toml +++ b/examples/desktop/Cargo.toml @@ -8,4 +8,5 @@ embedded-cli = { path = "../../embedded-cli" } embedded-io = "0.7.1" rand = "0.8.5" termion = "3.0.0" +tokio = { version = "1.52.3", features = ["full"] } ufmt = "0.2.0" diff --git a/examples/desktop/src/main.rs b/examples/desktop/src/main.rs index ab165e0..2517382 100644 --- a/examples/desktop/src/main.rs +++ b/examples/desktop/src/main.rs @@ -166,7 +166,8 @@ fn on_status( Ok(()) } -fn main() { +#[tokio::main] +async fn main() { let stdout = stdout().into_raw_mode().unwrap(); let writer = Writer { stdout }; @@ -238,7 +239,7 @@ Use left and right to move inside input." for byte in bytes { cli.process_byte::, _>( byte, - &mut BaseCommand::processor(|cli, command| match command { + &mut BaseCommand::processor(async |cli, command| match command { BaseCommand::Led { id, command } => on_led(cli, &mut state, id, command), BaseCommand::Adc { id, command } => on_adc(cli, &mut state, id, command), BaseCommand::Status => on_status(cli, &mut state), @@ -248,6 +249,7 @@ Use left and right to move inside input." } }), ) + .await .unwrap(); } From 49e07fbd6fe4d6d635d286bf4c39dd4b200e0f58 Mon Sep 17 00:00:00 2001 From: Tommaso Clini Date: Mon, 6 Jul 2026 18:18:19 +0200 Subject: [PATCH 2/6] blocking compatibility --- embedded-cli/Cargo.toml | 1 + embedded-cli/src/cli.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/embedded-cli/Cargo.toml b/embedded-cli/Cargo.toml index 0af3143..84c9fd5 100644 --- a/embedded-cli/Cargo.toml +++ b/embedded-cli/Cargo.toml @@ -26,6 +26,7 @@ embedded-cli-macros = { version = "0.2.1", path = "../embedded-cli-macros", opti bitflags = "2.5.0" embedded-io = "0.7.1" ufmt = "0.2.0" +embassy-futures = "0.1.2" [dev-dependencies] regex = "1.10.4" diff --git a/embedded-cli/src/cli.rs b/embedded-cli/src/cli.rs index 0deb817..2826ab4 100644 --- a/embedded-cli/src/cli.rs +++ b/embedded-cli/src/cli.rs @@ -1,6 +1,6 @@ pub use crate::builder::CliBuilder; -use core::fmt::Debug; +use core::{fmt::Debug, future::Future, pin::pin, task::Context}; #[cfg(not(feature = "history"))] use core::marker::PhantomData; @@ -153,6 +153,14 @@ where Ok(cli) } + pub fn process_byte_blocking>( + &mut self, + b: u8, + processor: &mut P, + ) -> Result<(), E> { + embassy_futures::block_on(self.process_byte::(b, processor)) + } + /// Each call to process byte can be done with different /// command set and/or command processor. /// In process callback you can change some outside state From 2cafb8c69f45a7064011c53d57fa3bddfd3f000d Mon Sep 17 00:00:00 2001 From: Tommaso Clini Date: Mon, 6 Jul 2026 19:11:13 +0200 Subject: [PATCH 3/6] don't actually block --- embedded-cli/Cargo.toml | 1 - embedded-cli/src/cli.rs | 13 ++++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/embedded-cli/Cargo.toml b/embedded-cli/Cargo.toml index 84c9fd5..0af3143 100644 --- a/embedded-cli/Cargo.toml +++ b/embedded-cli/Cargo.toml @@ -26,7 +26,6 @@ embedded-cli-macros = { version = "0.2.1", path = "../embedded-cli-macros", opti bitflags = "2.5.0" embedded-io = "0.7.1" ufmt = "0.2.0" -embassy-futures = "0.1.2" [dev-dependencies] regex = "1.10.4" diff --git a/embedded-cli/src/cli.rs b/embedded-cli/src/cli.rs index 2826ab4..18608ac 100644 --- a/embedded-cli/src/cli.rs +++ b/embedded-cli/src/cli.rs @@ -1,6 +1,11 @@ pub use crate::builder::CliBuilder; -use core::{fmt::Debug, future::Future, pin::pin, task::Context}; +use core::{ + fmt::Debug, + future::Future, + pin::pin, + task::{Context, Poll, Waker}, +}; #[cfg(not(feature = "history"))] use core::marker::PhantomData; @@ -157,8 +162,10 @@ where &mut self, b: u8, processor: &mut P, - ) -> Result<(), E> { - embassy_futures::block_on(self.process_byte::(b, processor)) + ) -> Poll> { + let p = pin!(self.process_byte::(b, processor)); + let mut cx = Context::from_waker(Waker::noop()); + p.poll(&mut cx) } /// Each call to process byte can be done with different From 3a7cabac488c99da17c79187c25ee94a04d51fa1 Mon Sep 17 00:00:00 2001 From: Tommaso Clini Date: Tue, 7 Jul 2026 10:44:26 +0200 Subject: [PATCH 4/6] tokio as a dev dependency --- embedded-cli/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/embedded-cli/Cargo.toml b/embedded-cli/Cargo.toml index 0af3143..17bb545 100644 --- a/embedded-cli/Cargo.toml +++ b/embedded-cli/Cargo.toml @@ -30,3 +30,4 @@ ufmt = "0.2.0" [dev-dependencies] regex = "1.10.4" rstest = "0.19.0" +tokio = "1.52.3" From 377ab0f32f2fcc0329119e066327686cbaac3bbe Mon Sep 17 00:00:00 2001 From: Tommaso Clini Date: Fri, 10 Jul 2026 00:57:20 +0200 Subject: [PATCH 5/6] use of nb crate instead of Poll in blocking api --- embedded-cli/Cargo.toml | 1 + embedded-cli/src/cli.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/embedded-cli/Cargo.toml b/embedded-cli/Cargo.toml index 17bb545..292ece2 100644 --- a/embedded-cli/Cargo.toml +++ b/embedded-cli/Cargo.toml @@ -26,6 +26,7 @@ embedded-cli-macros = { version = "0.2.1", path = "../embedded-cli-macros", opti bitflags = "2.5.0" embedded-io = "0.7.1" ufmt = "0.2.0" +nb = "1.1.0" [dev-dependencies] regex = "1.10.4" diff --git a/embedded-cli/src/cli.rs b/embedded-cli/src/cli.rs index 18608ac..f81237e 100644 --- a/embedded-cli/src/cli.rs +++ b/embedded-cli/src/cli.rs @@ -158,14 +158,17 @@ where Ok(cli) } - pub fn process_byte_blocking>( + pub fn try_process_byte>( &mut self, b: u8, processor: &mut P, - ) -> Poll> { + ) -> nb::Result<(), E> { let p = pin!(self.process_byte::(b, processor)); let mut cx = Context::from_waker(Waker::noop()); - p.poll(&mut cx) + match p.poll(&mut cx) { + Poll::Ready(res) => res.map_err(nb::Error::Other), + Poll::Pending => Err(nb::Error::WouldBlock), + } } /// Each call to process byte can be done with different From 49d3747210f705657cab6688c9c7e5f39ed9b921 Mon Sep 17 00:00:00 2001 From: Tommaso Clini Date: Fri, 10 Jul 2026 00:59:48 +0200 Subject: [PATCH 6/6] change function name --- embedded-cli/src/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embedded-cli/src/cli.rs b/embedded-cli/src/cli.rs index f81237e..d0adbf6 100644 --- a/embedded-cli/src/cli.rs +++ b/embedded-cli/src/cli.rs @@ -158,7 +158,7 @@ where Ok(cli) } - pub fn try_process_byte>( + pub fn process_byte_sync>( &mut self, b: u8, processor: &mut P,