From 22094de06ac1420ef0881d65f1692ab67c375704 Mon Sep 17 00:00:00 2001 From: Tommaso Clini Date: Thu, 30 Jul 2026 21:58:26 +0200 Subject: [PATCH 1/3] maybe async first try --- embedded-cli-macros/Cargo.toml | 2 + embedded-cli-macros/src/processor.rs | 104 ++++++++--- embedded-cli/Cargo.toml | 4 + embedded-cli/src/cli.rs | 60 ++++++- embedded-cli/src/command.rs | 40 +++++ embedded-cli/src/service.rs | 28 ++- examples/desktop_async/Cargo.toml | 12 ++ examples/desktop_async/README.md | 10 ++ examples/desktop_async/src/main.rs | 260 +++++++++++++++++++++++++++ 9 files changed, 484 insertions(+), 36 deletions(-) create mode 100644 examples/desktop_async/Cargo.toml create mode 100644 examples/desktop_async/README.md create mode 100644 examples/desktop_async/src/main.rs diff --git a/embedded-cli-macros/Cargo.toml b/embedded-cli-macros/Cargo.toml index 8a30f2e..9ba1da4 100644 --- a/embedded-cli-macros/Cargo.toml +++ b/embedded-cli-macros/Cargo.toml @@ -20,6 +20,8 @@ default = [] autocomplete = [] help = [] +async = [] + [dependencies] convert_case = "0.6.0" darling = "0.20.8" diff --git a/embedded-cli-macros/src/processor.rs b/embedded-cli-macros/src/processor.rs index 78c5e43..6fa0c2e 100644 --- a/embedded-cli-macros/src/processor.rs +++ b/embedded-cli-macros/src/processor.rs @@ -10,45 +10,91 @@ 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: F, - ) -> impl _cli::service::CommandProcessor { - struct Processor< + let output = if cfg!(feature = "async") { + quote! { + + impl #named_lifetime #ident #named_lifetime { + #vis fn 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)>, + ) -> impl _cli::service::CommandProcessor { + struct Processor< + W: _io::Write, + 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, + E: _io::Error, + F: AsyncFnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>, + > _cli::service::CommandProcessor for Processor + { + 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, + } } + } + } + } else { + quote! { - impl< + impl #named_lifetime #ident #named_lifetime { + #vis fn processor< + W: _io::Write, + E: _io::Error, + F: FnMut(&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>, - > _cli::service::CommandProcessor for Processor - { - 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, + E: _io::Error, + F: FnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>, + > _cli::service::CommandProcessor for Processor + { + 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, + } } } } diff --git a/embedded-cli/Cargo.toml b/embedded-cli/Cargo.toml index 945dd88..e7dd9bc 100644 --- a/embedded-cli/Cargo.toml +++ b/embedded-cli/Cargo.toml @@ -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" diff --git a/embedded-cli/src/cli.rs b/embedded-cli/src/cli.rs index c7a342c..75adb81 100644 --- a/embedded-cli/src/cli.rs +++ b/embedded-cli/src/cli.rs @@ -28,6 +28,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, E: embedded_io::Error> { new_prompt: Option<&'static str>, @@ -77,6 +78,10 @@ enum NavigateInput { Forward, } +#[maybe( + sync(cfg(not(feature = "async")), keep_self), + async(feature = "async", keep_self) +)] pub struct Cli, E: Error, CommandBuffer: Buffer, HistoryBuffer: Buffer> { editor: Option>, #[cfg(feature = "history")] @@ -88,6 +93,10 @@ pub struct Cli, E: Error, CommandBuffer: Buffer, HistoryBuff _ph: PhantomData, } +#[maybe( + sync(cfg(not(feature = "async")), keep_self), + async(feature = "async", keep_self) +)] impl Debug for Cli where W: Write, @@ -104,6 +113,10 @@ where } } +#[maybe( + sync(cfg(not(feature = "async")), keep_self), + async(feature = "async", keep_self) +)] impl Cli where W: Write, @@ -157,6 +170,7 @@ where /// command set and/or command processor. /// In process callback you can change some outside state /// so next calls will use different processor + #[cfg(not(feature = "async"))] pub fn process_byte>( &mut self, b: u8, @@ -183,6 +197,40 @@ where } } + /// 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 + /// so next calls will use different processor + #[cfg(feature = "async")] + pub async fn process_byte>( + &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 = 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), + }) + } else { + None + } + .unwrap_or(Ok(())); + + self.editor = Some(editor); + self.input_generator = Some(input_generator); + result + } else { + Ok(()) + } + } + /// Set new prompt to use in CLI /// /// Changes will apply immediately and current line @@ -244,7 +292,7 @@ where Ok(()) } - fn on_control_input>( + async fn on_control_input>( &mut self, editor: &mut Editor, control: ControlInput, @@ -259,7 +307,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(); @@ -360,7 +408,7 @@ where Ok(()) } - fn process_command>( + async fn process_command>( &mut self, command: RawCommand<'_>, handler: &mut P, @@ -368,7 +416,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; @@ -386,7 +434,7 @@ where } #[allow(clippy::extra_unused_type_parameters)] - fn process_input>( + async fn process_input>( &mut self, tokens: Tokens<'_>, handler: &mut P, @@ -397,7 +445,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..6a450f1 100644 --- a/embedded-cli/src/command.rs +++ b/embedded-cli/src/command.rs @@ -53,6 +53,7 @@ impl<'a> RawCommand<'a> { self.name } + #[cfg(not(feature = "async"))] pub fn processor< W: Write, E: embedded_io::Error, @@ -90,6 +91,45 @@ impl<'a> RawCommand<'a> { _ph: PhantomData, } } + + #[cfg(feature = "async")] + pub fn processor< + W: Write, + E: embedded_io::Error, + F: AsyncFnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>, + >( + f: F, + ) -> impl CommandProcessor { + struct Processor< + W: Write, + E: embedded_io::Error, + F: AsyncFnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>, + > { + f: F, + _ph: PhantomData<(W, E)>, + } + + impl< + W: Write, + E: embedded_io::Error, + F: AsyncFnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>, + > CommandProcessor for Processor + { + 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<'_> { diff --git a/embedded-cli/src/service.rs b/embedded-cli/src/service.rs index 870e9c3..4137629 100644 --- a/embedded-cli/src/service.rs +++ b/embedded-cli/src/service.rs @@ -1,4 +1,5 @@ use embedded_io::Write; +use maybe_async_cfg2::maybe; use crate::{arguments::FromArgumentError, cli::CliHandle, command::RawCommand}; @@ -117,14 +118,20 @@ pub trait FromRaw<'a>: Sized { fn parse(raw: RawCommand<'a>) -> Result>; } +#[maybe( + sync(cfg(not(feature = "async")), keep_self), + async(feature = "async", keep_self) +)] +#[allow(async_fn_in_trait)] pub trait CommandProcessor, E: embedded_io::Error> { - fn process<'a>( + async fn process<'a>( &mut self, cli: &mut CliHandle<'_, W, E>, raw: RawCommand<'a>, ) -> Result<(), ProcessError<'a, E>>; } +#[cfg(not(feature = "async"))] impl CommandProcessor for F where W: Write, @@ -139,3 +146,22 @@ where self(cli, command) } } + +#[cfg(feature = "async")] +impl CommandProcessor for F +where + W: Write, + E: embedded_io::Error, + F: for<'a> AsyncFnMut( + &mut CliHandle<'_, W, E>, + RawCommand<'a>, + ) -> Result<(), ProcessError<'a, E>>, +{ + async fn process<'a>( + &mut self, + cli: &mut CliHandle<'_, W, E>, + command: RawCommand<'a>, + ) -> Result<(), ProcessError<'a, E>> { + self(cli, command).await + } +} diff --git a/examples/desktop_async/Cargo.toml b/examples/desktop_async/Cargo.toml new file mode 100644 index 0000000..cc5b47f --- /dev/null +++ b/examples/desktop_async/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "desktop_async" +version = "0.1.0" +edition = "2021" + +[dependencies] +embedded-cli = { path = "../../embedded-cli", features = ["async"] } +embedded-io = "0.7.1" +rand = "0.8.5" +termion = "3.0.0" +tokio = { version = "1.53.1", features = ["full"] } +ufmt = "0.2.0" diff --git a/examples/desktop_async/README.md b/examples/desktop_async/README.md new file mode 100644 index 0000000..8963240 --- /dev/null +++ b/examples/desktop_async/README.md @@ -0,0 +1,10 @@ +# Desktop example + +This example used to quickly demonstrate CLI features without using actual device. + +# Running + +While inside `examples/desktop` directory: +```shell +cargo run +``` diff --git a/examples/desktop_async/src/main.rs b/examples/desktop_async/src/main.rs new file mode 100644 index 0000000..2517382 --- /dev/null +++ b/examples/desktop_async/src/main.rs @@ -0,0 +1,260 @@ +#![warn(rust_2018_idioms)] + +use embedded_cli::cli::{CliBuilder, CliHandle}; +use embedded_cli::codes; +use embedded_cli::Command; +use embedded_io::{ErrorType, Write}; +use std::convert::Infallible; +use std::io::{stdin, stdout, Stdout, Write as _}; +use termion::event::{Event, Key}; +use termion::input::TermRead; +use termion::raw::{IntoRawMode, RawTerminal}; + +use ufmt::{uwrite, uwriteln}; + +#[derive(Debug, Command)] +enum BaseCommand<'a> { + /// Control LEDs + Led { + /// LED id + #[arg(long)] + id: u8, + + #[command(subcommand)] + command: LedCommand, + }, + + /// Control ADC + Adc { + /// ADC id + #[arg(long)] + id: u8, + + #[command(subcommand)] + command: AdcCommand<'a>, + }, + + /// Show some status + Status, + + /// Stop CLI and exit + Exit, +} + +#[derive(Debug, Command)] +enum LedCommand { + /// Get current LED value + Get, + + /// Set LED value + Set { + /// LED brightness + value: u8, + }, +} + +#[derive(Debug, Command)] +enum AdcCommand<'a> { + /// Read ADC value + Read { + /// Print extra info + #[arg(short = 'V', long)] + verbose: bool, + + /// Sample count (16 by default) + #[arg(long)] + samples: Option, + + #[arg(long)] + sampler: &'a str, + }, +} + +pub struct Writer { + stdout: RawTerminal, +} + +impl ErrorType for Writer { + type Error = Infallible; +} + +impl Write for Writer { + fn write(&mut self, buf: &[u8]) -> Result { + self.stdout.write_all(buf).unwrap(); + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.stdout.flush().unwrap(); + Ok(()) + } +} + +struct AppState { + led_brightness: [u8; 4], + num_commands: usize, + should_exit: bool, +} + +fn on_led( + cli: &mut CliHandle<'_, Writer, Infallible>, + state: &mut AppState, + id: u8, + command: LedCommand, +) -> Result<(), Infallible> { + state.num_commands += 1; + + if id as usize > state.led_brightness.len() { + uwrite!(cli.writer(), "LED{} not found", id)?; + } else { + match command { + LedCommand::Get => { + uwrite!( + cli.writer(), + "Current LED{} brightness: {}", + id, + state.led_brightness[id as usize] + )?; + } + LedCommand::Set { value } => { + state.led_brightness[id as usize] = value; + uwrite!(cli.writer(), "Setting LED{} brightness to {}", id, value)?; + } + } + } + + Ok(()) +} + +fn on_adc( + cli: &mut CliHandle<'_, Writer, Infallible>, + state: &mut AppState, + id: u8, + command: AdcCommand<'_>, +) -> Result<(), Infallible> { + state.num_commands += 1; + + match command { + AdcCommand::Read { + verbose, + samples, + sampler, + } => { + let samples = samples.unwrap_or(16); + if verbose { + cli.writer().write_str("Performing sampling with ")?; + cli.writer().write_str(sampler)?; + uwriteln!(cli.writer(), "\nUsing {} samples", samples)?; + } + uwrite!( + cli.writer(), + "Current ADC{} readings: {}", + id, + rand::random::() + )?; + } + } + Ok(()) +} + +fn on_status( + cli: &mut CliHandle<'_, Writer, Infallible>, + state: &mut AppState, +) -> Result<(), Infallible> { + state.num_commands += 1; + uwriteln!(cli.writer(), "Received: {}", state.num_commands)?; + Ok(()) +} + +#[tokio::main] +async fn main() { + let stdout = stdout().into_raw_mode().unwrap(); + + let writer = Writer { stdout }; + + const CMD_BUFFER_SIZE: usize = 1024; + const HISTORY_BUFFER_SIZE: usize = 2048; + + // Creating static buffers for command history to avoid using stack memory + let (command_buffer, history_buffer) = unsafe { + static mut COMMAND_BUFFER: [u8; CMD_BUFFER_SIZE] = [0; CMD_BUFFER_SIZE]; + static mut HISTORY_BUFFER: [u8; HISTORY_BUFFER_SIZE] = [0; HISTORY_BUFFER_SIZE]; + #[allow(static_mut_refs)] + (COMMAND_BUFFER.as_mut(), HISTORY_BUFFER.as_mut()) + }; + + // Setup CLI with command buffer, history buffer, and a writer for output + let mut cli = CliBuilder::default() + .writer(writer) + .command_buffer(command_buffer) + .history_buffer(history_buffer) + .build() + .expect("Failed to build CLI"); + + // Setting the CLI prompt + let _ = cli.set_prompt("main$ "); + + // Create global state, that will be used for entire application + let mut state = AppState { + led_brightness: rand::random(), + num_commands: 0, + should_exit: false, + }; + + cli.write(|writer| { + uwrite!( + writer, + "Cli is running. Press 'Esc' to exit +Type \"help\" for a list of commands. +Use backspace and tab to remove chars and autocomplete. +Use up and down for history navigation. +Use left and right to move inside input." + )?; + Ok(()) + }) + .unwrap(); + + let stdin = stdin(); + for c in stdin.events() { + let evt = c.unwrap(); + let bytes = match evt { + Event::Key(Key::Esc) => break, + Event::Key(Key::Up) => vec![codes::ESCAPE, b'[', b'A'], + Event::Key(Key::Down) => vec![codes::ESCAPE, b'[', b'B'], + Event::Key(Key::Right) => vec![codes::ESCAPE, b'[', b'C'], + Event::Key(Key::Left) => vec![codes::ESCAPE, b'[', b'D'], + Event::Key(Key::BackTab) => vec![codes::TABULATION], + Event::Key(Key::Backspace) => vec![codes::BACKSPACE], + Event::Key(Key::Char(c)) => { + let mut buf = [0; 4]; + c.encode_utf8(&mut buf).as_bytes().to_vec() + } + _ => continue, + }; + // Process incoming byte + // Command type is specified for autocompletion and help + // Processor accepts closure where we can process parsed command + // we can use different command and processor with each call + // TODO: add example of login that uses different states + for byte in bytes { + cli.process_byte::, _>( + byte, + &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), + BaseCommand::Exit => { + state.should_exit = true; + cli.writer().write_str("Cli will shutdown now") + } + }), + ) + .await + .unwrap(); + } + + if state.should_exit { + break; + } + } +} From c2390f9e7a1581e19dd60d584f873982bf8282de Mon Sep 17 00:00:00 2001 From: Tommaso Clini Date: Fri, 31 Jul 2026 11:02:05 +0200 Subject: [PATCH 2/3] deduplicate process_byte function --- embedded-cli/src/cli.rs | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/embedded-cli/src/cli.rs b/embedded-cli/src/cli.rs index 75adb81..8979c0a 100644 --- a/embedded-cli/src/cli.rs +++ b/embedded-cli/src/cli.rs @@ -170,38 +170,6 @@ where /// command set and/or command processor. /// In process callback you can change some outside state /// so next calls will use different processor - #[cfg(not(feature = "async"))] - pub fn process_byte>( - &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 { - Input::Control(control) => { - self.on_control_input::(&mut editor, control, processor) - } - Input::Char(text) => self.on_text_input(&mut editor, text), - }) - .unwrap_or(Ok(())); - - self.editor = Some(editor); - self.input_generator = Some(input_generator); - result - } else { - Ok(()) - } - } - - /// 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 - /// so next calls will use different processor - #[cfg(feature = "async")] pub async fn process_byte>( &mut self, b: u8, From d0322c1e2e6382ba06998065a4d862dc4daf2010 Mon Sep 17 00:00:00 2001 From: Tommaso Clini Date: Sat, 1 Aug 2026 14:29:20 +0200 Subject: [PATCH 3/3] async api now does not replace sync api --- embedded-cli-macros/src/processor.rs | 19 +++++++++++-------- embedded-cli/src/builder.rs | 8 ++++++++ embedded-cli/src/cli.rs | 18 ++++++++---------- embedded-cli/src/command.rs | 10 ++++++---- embedded-cli/src/service.rs | 9 ++------- examples/desktop_async/src/main.rs | 4 ++-- 6 files changed, 37 insertions(+), 31 deletions(-) diff --git a/embedded-cli-macros/src/processor.rs b/embedded-cli-macros/src/processor.rs index 6fa0c2e..11fb006 100644 --- a/embedded-cli-macros/src/processor.rs +++ b/embedded-cli-macros/src/processor.rs @@ -10,17 +10,19 @@ pub fn impl_processor(vis: &Visibility, target: &TargetType) -> Result, E: _io::Error, F: AsyncFnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>, >( f: F, - ) -> impl _cli::service::CommandProcessor { + ) -> impl _cli::service::CommandProcessorAsync { struct Processor< W: _io::Write, E: _io::Error, @@ -34,7 +36,7 @@ pub fn impl_processor(vis: &Visibility, target: &TargetType) -> Result, E: _io::Error, F: AsyncFnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>, - > _cli::service::CommandProcessor for Processor + > _cli::service::CommandProcessorAsync for Processor { async fn process<'a>( &mut self, @@ -53,8 +55,9 @@ pub fn impl_processor(vis: &Visibility, target: &TargetType) -> Result Result Result, E> { + CliAsync::from_builder(self) + } + pub fn command_buffer( self, command_buffer: B, diff --git a/embedded-cli/src/cli.rs b/embedded-cli/src/cli.rs index 8979c0a..f32949a 100644 --- a/embedded-cli/src/cli.rs +++ b/embedded-cli/src/cli.rs @@ -18,6 +18,9 @@ use crate::{ writer::{WriteExt, Writer}, }; +#[cfg(feature = "async")] +use crate::service::CommandProcessorAsync; + #[cfg(feature = "autocomplete")] use crate::autocomplete::Request; @@ -78,10 +81,7 @@ enum NavigateInput { Forward, } -#[maybe( - sync(cfg(not(feature = "async")), keep_self), - async(feature = "async", keep_self) -)] +#[maybe(sync(keep_self), async(feature = "async"))] pub struct Cli, E: Error, CommandBuffer: Buffer, HistoryBuffer: Buffer> { editor: Option>, #[cfg(feature = "history")] @@ -93,10 +93,7 @@ pub struct Cli, E: Error, CommandBuffer: Buffer, HistoryBuff _ph: PhantomData, } -#[maybe( - sync(cfg(not(feature = "async")), keep_self), - async(feature = "async", keep_self) -)] +#[maybe(sync(keep_self), async(feature = "async"))] impl Debug for Cli where W: Write, @@ -114,8 +111,9 @@ where } #[maybe( - sync(cfg(not(feature = "async")), keep_self), - async(feature = "async", keep_self) + sync(keep_self), + async(feature = "async"), + idents(CommandProcessor(sync, async = "CommandProcessorAsync")) )] impl Cli where diff --git a/embedded-cli/src/command.rs b/embedded-cli/src/command.rs index 6a450f1..f566e8a 100644 --- a/embedded-cli/src/command.rs +++ b/embedded-cli/src/command.rs @@ -9,6 +9,9 @@ use crate::{ token::Tokens, }; +#[cfg(feature = "async")] +use crate::service::CommandProcessorAsync; + #[cfg(feature = "autocomplete")] use crate::autocomplete::{Autocompletion, Request}; @@ -53,7 +56,6 @@ impl<'a> RawCommand<'a> { self.name } - #[cfg(not(feature = "async"))] pub fn processor< W: Write, E: embedded_io::Error, @@ -93,13 +95,13 @@ impl<'a> RawCommand<'a> { } #[cfg(feature = "async")] - pub fn processor< + pub fn processor_async< W: Write, E: embedded_io::Error, F: AsyncFnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>, >( f: F, - ) -> impl CommandProcessor { + ) -> impl CommandProcessorAsync { struct Processor< W: Write, E: embedded_io::Error, @@ -113,7 +115,7 @@ impl<'a> RawCommand<'a> { W: Write, E: embedded_io::Error, F: AsyncFnMut(&mut CliHandle<'_, W, E>, RawCommand<'_>) -> Result<(), E>, - > CommandProcessor for Processor + > CommandProcessorAsync for Processor { async fn process<'a>( &mut self, diff --git a/embedded-cli/src/service.rs b/embedded-cli/src/service.rs index 4137629..1fd7a3d 100644 --- a/embedded-cli/src/service.rs +++ b/embedded-cli/src/service.rs @@ -118,11 +118,7 @@ pub trait FromRaw<'a>: Sized { fn parse(raw: RawCommand<'a>) -> Result>; } -#[maybe( - sync(cfg(not(feature = "async")), keep_self), - async(feature = "async", keep_self) -)] -#[allow(async_fn_in_trait)] +#[maybe(sync(keep_self), async(feature = "async", allow(async_fn_in_trait)))] pub trait CommandProcessor, E: embedded_io::Error> { async fn process<'a>( &mut self, @@ -131,7 +127,6 @@ pub trait CommandProcessor, E: embedded_io::Error> { ) -> Result<(), ProcessError<'a, E>>; } -#[cfg(not(feature = "async"))] impl CommandProcessor for F where W: Write, @@ -148,7 +143,7 @@ where } #[cfg(feature = "async")] -impl CommandProcessor for F +impl CommandProcessorAsync for F where W: Write, E: embedded_io::Error, diff --git a/examples/desktop_async/src/main.rs b/examples/desktop_async/src/main.rs index 2517382..705c961 100644 --- a/examples/desktop_async/src/main.rs +++ b/examples/desktop_async/src/main.rs @@ -188,7 +188,7 @@ async fn main() { .writer(writer) .command_buffer(command_buffer) .history_buffer(history_buffer) - .build() + .build_async() .expect("Failed to build CLI"); // Setting the CLI prompt @@ -239,7 +239,7 @@ Use left and right to move inside input." for byte in bytes { cli.process_byte::, _>( byte, - &mut BaseCommand::processor(async |cli, command| match command { + &mut BaseCommand::processor_async(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),