Skip to content
Closed
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
4 changes: 2 additions & 2 deletions embedded-cli-macros/src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct ServiceAttrs {
skip_from_raw: bool,
}

pub fn derive_command(input: DeriveInput) -> Result<TokenStream> {
pub fn derive_command(input: DeriveInput, is_async: bool) -> Result<TokenStream> {
let opts = ServiceAttrs::from_derive_input(&input)?;
let DeriveInput {
vis,
Expand Down Expand Up @@ -67,7 +67,7 @@ pub fn derive_command(input: DeriveInput) -> Result<TokenStream> {
} else {
parse::derive_from_raw(&target, &commands)?
};
let impl_processor = processor::impl_processor(&vis, &target)?;
let impl_processor = processor::impl_processor(&vis, &target, is_async)?;

let output = quote! {
#derive_autocomplete
Expand Down
4 changes: 2 additions & 2 deletions embedded-cli-macros/src/group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use self::command_group::CommandGroup;

mod command_group;

pub fn derive_command_group(input: DeriveInput) -> Result<TokenStream> {
pub fn derive_command_group(input: DeriveInput, is_async: bool) -> Result<TokenStream> {
let DeriveInput {
vis,
ident,
Expand Down Expand Up @@ -37,7 +37,7 @@ pub fn derive_command_group(input: DeriveInput) -> Result<TokenStream> {
let derive_autocomplete = derive_autocomplete(&target, &groups);
let derive_help = derive_help(&target, &groups);
let derive_from_raw = derive_from_raw(&target, &groups);
let impl_processor = processor::impl_processor(&vis, &target)?;
let impl_processor = processor::impl_processor(&vis, &target, is_async)?;

let output = quote! {
#derive_autocomplete
Expand Down
46 changes: 44 additions & 2 deletions embedded-cli-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mod utils;
pub fn derive_command(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input);

let output = match command::derive_command(input) {
let output = match command::derive_command(input, false) {
Ok(output) => output,
Err(e) => return e.write_errors().into(),
};
Expand All @@ -32,7 +32,49 @@ pub fn derive_command(input: TokenStream) -> TokenStream {
pub fn derive_command_group(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input);

let output = match group::derive_command_group(input) {
let output = match group::derive_command_group(input, false) {
Ok(output) => output,
Err(e) => return e.write_errors().into(),
};

// wrap with anonymous scope
quote! {
const _: () = {
extern crate embedded_cli as _cli;
use _cli::__private::io as _io;

#output
};
}
.into()
}

#[proc_macro_derive(CommandAsync, attributes(command, arg))]
pub fn derive_command_async(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input);

let output = match command::derive_command(input, true) {
Ok(output) => output,
Err(e) => return e.write_errors().into(),
};

// wrap with anonymous scope
quote! {
const _: () = {
extern crate embedded_cli as _cli;
use _cli::__private::io as _io;

#output
};
}
.into()
}

#[proc_macro_derive(CommandGroupAsync, attributes(group))]
pub fn derive_command_group_async(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input);

let output = match group::derive_command_group(input, true) {
Ok(output) => output,
Err(e) => return e.write_errors().into(),
};
Expand Down
110 changes: 80 additions & 30 deletions embedded-cli-macros/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,100 @@ use syn::Visibility;

use crate::utils::TargetType;

pub fn impl_processor(vis: &Visibility, target: &TargetType) -> Result<TokenStream> {
pub fn impl_processor(
vis: &Visibility,
target: &TargetType,
is_async: bool,
) -> Result<TokenStream> {
let ident = target.ident();
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 output = if is_async {
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: AsyncFnMut(&mut _cli::cli::CliHandle<'_, W, E>, #ident #unnamed_lifetime) -> Result<(), E>,
>(
f: F,
_ph: core::marker::PhantomData<(W, E)>,
) -> impl _cli::service::AsyncCommandProcessor<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::AsyncCommandProcessor<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,
}
}
}
}
} else {
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,
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions embedded-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ ufmt = "0.2.0"
[dev-dependencies]
regex = "1.10.4"
rstest = "0.19.0"
tokio = "1.52.3"
53 changes: 41 additions & 12 deletions embedded-cli/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
pub use crate::builder::CliBuilder;

use core::fmt::Debug;
use core::{
fmt::Debug,
future::Future,
pin::pin,
task::{Context, Poll, Waker},
};

#[cfg(not(feature = "history"))]
use core::marker::PhantomData;
Expand All @@ -12,7 +17,10 @@ use crate::{
command::RawCommand,
editor::Editor,
input::{ControlInput, Input, InputGenerator},
service::{Autocomplete, CommandProcessor, Help, ParseError, ProcessError},
service::{
AsyncCommandProcessor, Autocomplete, CommandProcessor, Help, ParseError, ProcessError,
SyncToAsyncAdapter,
},
token::Tokens,
utils,
writer::{WriteExt, Writer},
Expand Down Expand Up @@ -161,19 +169,40 @@ where
&mut self,
b: u8,
processor: &mut P,
) -> Result<(), E> {
let mut cx = Context::from_waker(Waker::noop());
match pin!(self.process_byte_async::<C, _>(b, &mut SyncToAsyncAdapter::new(processor)))
.poll(&mut cx)
{
Poll::Ready(res) => res,
Poll::Pending => unreachable!(),
}
}

/// 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
pub async fn process_byte_async<C: Autocomplete + Help, P: AsyncCommandProcessor<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 +273,7 @@ where
Ok(())
}

fn on_control_input<C: Autocomplete + Help, P: CommandProcessor<W, E>>(
async fn on_control_input<C: Autocomplete + Help, P: AsyncCommandProcessor<W, E>>(
&mut self,
editor: &mut Editor<CommandBuffer>,
control: ControlInput,
Expand All @@ -259,7 +288,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 @@ -354,15 +383,15 @@ where
Ok(())
}

fn process_command<P: CommandProcessor<W, E>>(
async fn process_command<P: AsyncCommandProcessor<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 @@ -380,7 +409,7 @@ where
}

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

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

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion embedded-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub mod writer;

/// Macro available if embedded-cli is built with `features = ["macros"]`.
#[cfg(feature = "embedded-cli-macros")]
pub use embedded_cli_macros::{Command, CommandGroup};
pub use embedded_cli_macros::{Command, CommandAsync, CommandGroup, CommandGroupAsync};

// Used by generated code. Not public API.
#[doc(hidden)]
Expand Down
Loading
Loading