Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(*): Add option for custom parse fn in AtatUrc #205

Merged
merged 1 commit into from
Apr 22, 2024
Merged
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
3 changes: 2 additions & 1 deletion atat/src/digest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
None,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParseError {
Incomplete,
NoMatch,
Expand All @@ -31,8 +32,8 @@
pub trait Parser {
/// Parse a URC, if it exists.
///
/// - if no URC exists, return [ParseError::NoMatch]

Check warning on line 35 in atat/src/digest.rs

View workflow job for this annotation

GitHub Actions / clippy

item in documentation is missing backticks

warning: item in documentation is missing backticks --> atat/src/digest.rs:35:37 | 35 | /// - if no URC exists, return [ParseError::NoMatch] | ^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown = note: `-W clippy::doc-markdown` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::doc_markdown)]` help: try | 35 | /// - if no URC exists, return [`ParseError::NoMatch`] | ~~~~~~~~~~~~~~~~~~~~~
/// - if a URC exists but is incomplete, return [ParseError::Incomplete]

Check warning on line 36 in atat/src/digest.rs

View workflow job for this annotation

GitHub Actions / clippy

item in documentation is missing backticks

warning: item in documentation is missing backticks --> atat/src/digest.rs:36:54 | 36 | /// - if a URC exists but is incomplete, return [ParseError::Incomplete] | ^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown help: try | 36 | /// - if a URC exists but is incomplete, return [`ParseError::Incomplete`] | ~~~~~~~~~~~~~~~~~~~~~~~~
/// - if a URC exists and is complete, return it and its length
fn parse(buf: &[u8]) -> Result<(&[u8], usize), ParseError>;
}
Expand Down Expand Up @@ -351,7 +352,7 @@
recognize(nom::bytes::complete::take_until("\r\n"))(buf)
}

fn take_until_including<T, Input, Error: ParseError<Input>>(
pub fn take_until_including<T, Input, Error: ParseError<Input>>(
tag: T,
) -> impl Fn(Input) -> IResult<Input, (Input, Input), Error>
where
Expand Down Expand Up @@ -404,7 +405,7 @@
T: nom::InputLength + Clone + nom::InputTake + nom::InputIter,
{
move |i| {
let (i, (prefix_data, _, error_msg)) = tuple((

Check warning on line 408 in atat/src/digest.rs

View workflow job for this annotation

GitHub Actions / clippy

matching over `()` is more explicit

warning: matching over `()` is more explicit --> atat/src/digest.rs:408:35 | 408 | let (i, (prefix_data, _, error_msg)) = tuple(( | ^ help: use `()` instead of `_`: `()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#ignored_unit_patterns = note: `-W clippy::ignored-unit-patterns` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::ignored_unit_patterns)]`
recognize(take_until_including(token.clone())),
not(tag("\r")),
recognize(take_until_including("\r\n")),
Expand Down Expand Up @@ -465,19 +466,19 @@
}

fn trim_ascii_whitespace(x: &[u8]) -> &[u8] {
let from = match x.iter().position(|x| !x.is_ascii_whitespace()) {
Some(i) => i,
None => return &x[0..0],
};

Check warning on line 472 in atat/src/digest.rs

View workflow job for this annotation

GitHub Actions / clippy

this could be rewritten as `let...else`

warning: this could be rewritten as `let...else` --> atat/src/digest.rs:469:9 | 469 | / let from = match x.iter().position(|x| !x.is_ascii_whitespace()) { 470 | | Some(i) => i, 471 | | None => return &x[0..0], 472 | | }; | |__________^ help: consider writing: `let Some(from) = x.iter().position(|x| !x.is_ascii_whitespace()) else { return &x[0..0] };` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else = note: `-W clippy::manual-let-else` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::manual_let_else)]`
let to = x.iter().rposition(|x| !x.is_ascii_whitespace()).unwrap();
&x[from..=to]
}

pub fn trim_start_ascii_space(x: &[u8]) -> &[u8] {

Check warning on line 477 in atat/src/digest.rs

View workflow job for this annotation

GitHub Actions / clippy

this function could have a `#[must_use]` attribute

warning: this function could have a `#[must_use]` attribute --> atat/src/digest.rs:477:5 | 477 | pub fn trim_start_ascii_space(x: &[u8]) -> &[u8] { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add the attribute: `#[must_use] pub fn trim_start_ascii_space(x: &[u8]) -> &[u8]` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#must_use_candidate = note: `-W clippy::must-use-candidate` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::must_use_candidate)]`
match x.iter().position(|&x| x != b' ') {
Some(offset) => &x[offset..],
None => &x[0..0],
}

Check warning on line 481 in atat/src/digest.rs

View workflow job for this annotation

GitHub Actions / clippy

use Option::map_or_else instead of an if let/else

warning: use Option::map_or_else instead of an if let/else --> atat/src/digest.rs:478:9 | 478 | / match x.iter().position(|&x| x != b' ') { 479 | | Some(offset) => &x[offset..], 480 | | None => &x[0..0], 481 | | } | |_________^ help: try: `x.iter().position(|&x| x != b' ').map_or_else(|| &x[0..0], |offset| &x[offset..])` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else = note: `-W clippy::option-if-let-else` implied by `-W clippy::nursery` = help: to override `-W clippy::nursery` add `#[allow(clippy::option_if_let_else)]`
}
}
#[cfg(test)]
Expand Down
116 changes: 113 additions & 3 deletions atat/src/ingress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
UrcChannel,
};

#[derive(Debug, PartialEq)]

Check warning on line 6 in atat/src/ingress.rs

View workflow job for this annotation

GitHub Actions / clippy

you are deriving `PartialEq` and can implement `Eq`

warning: you are deriving `PartialEq` and can implement `Eq` --> atat/src/ingress.rs:6:17 | 6 | #[derive(Debug, PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq = note: `-W clippy::derive-partial-eq-without-eq` implied by `-W clippy::nursery` = help: to override `-W clippy::nursery` add `#[allow(clippy::derive_partial_eq_without_eq)]`
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
ResponseSlotBusy,
Expand Down Expand Up @@ -99,12 +99,12 @@
const URC_SUBSCRIBERS: usize,
> Ingress<'a, D, Urc, RES_BUF_SIZE, URC_CAPACITY, URC_SUBSCRIBERS>
{
pub fn new(
digester: D,
buf: &'a mut [u8],
res_slot: &'a ResponseSlot<RES_BUF_SIZE>,
urc_channel: &'a UrcChannel<Urc, URC_CAPACITY, URC_SUBSCRIBERS>,
) -> Self {

Check warning on line 107 in atat/src/ingress.rs

View workflow job for this annotation

GitHub Actions / clippy

docs for function which may panic missing `# Panics` section

warning: docs for function which may panic missing `# Panics` section --> atat/src/ingress.rs:102:5 | 102 | / pub fn new( 103 | | digester: D, 104 | | buf: &'a mut [u8], 105 | | res_slot: &'a ResponseSlot<RES_BUF_SIZE>, 106 | | urc_channel: &'a UrcChannel<Urc, URC_CAPACITY, URC_SUBSCRIBERS>, 107 | | ) -> Self { | |_____________^ | note: first possible panic found here --> atat/src/ingress.rs:113:28 | 113 | urc_publisher: urc_channel.0.publisher().unwrap(), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc = note: `-W clippy::missing-panics-doc` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::missing_panics_doc)]`
Self {
digester,
buf,
Expand Down Expand Up @@ -176,7 +176,7 @@
match &resp {
Ok(r) => {
if r.is_empty() {
debug!("Received OK ({}/{})", swallowed, self.pos,)

Check warning on line 179 in atat/src/ingress.rs

View workflow job for this annotation

GitHub Actions / clippy

consider adding a `;` to the last statement for consistent formatting

warning: consider adding a `;` to the last statement for consistent formatting --> atat/src/ingress.rs:179:33 | 179 | ... debug!("Received OK ({}/{})", swallowed, self.pos,) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add a `;` here: `debug!("Received OK ({}/{})", swallowed, self.pos,);` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned = note: `-W clippy::semicolon-if-nothing-returned` implied by `-W clippy::pedantic` = help: to override `-W clippy::pedantic` add `#[allow(clippy::semicolon_if_nothing_returned)]`
} else {
debug!(
"Received response ({}/{}): {:?}",
Expand Down Expand Up @@ -212,7 +212,7 @@
Ok(())
}

async fn advance(&mut self, commit: usize) {

Check warning on line 215 in atat/src/ingress.rs

View workflow job for this annotation

GitHub Actions / clippy

future cannot be sent between threads safely

warning: future cannot be sent between threads safely --> atat/src/ingress.rs:215:5 | 215 | async fn advance(&mut self, commit: usize) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future returned by `advance` is not `Send` | note: future is not `Send` as this value is used across an await --> atat/src/ingress.rs:252:61 | 242 | if let Some(urc) = Urc::parse(urc_line) { | -------------------- has type `core::option::Option<<Urc as traits::AtatUrc>::Response>` which is not `Send` ... 252 | self.urc_publisher.publish(urc).await; | ^^^^^ await occurs here, with `Urc::parse(urc_line)` maybe used later = note: `<Urc as traits::AtatUrc>::Response` doesn't implement `core::marker::Send` note: future is not `Send` as this value is used across an await --> atat/src/ingress.rs:252:61 | 215 | async fn advance(&mut self, commit: usize) { | --------- has type `&mut ingress::Ingress<'_, D, Urc, RES_BUF_SIZE, URC_CAPACITY, URC_SUBSCRIBERS>` which is not `Send` ... 252 | self.urc_publisher.publish(urc).await; | ^^^^^ await occurs here, with `&mut self` maybe used later = note: `D` doesn't implement `core::marker::Send` note: future is not `Send` as it awaits another future which is not `Send` --> atat/src/ingress.rs:252:29 | 252 | ... self.urc_publisher.publish(urc).await; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here on type `embassy_sync::pubsub::publisher::PublisherWaitFuture<'_, '_, embassy_sync::pubsub::PubSubChannel<embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex, <Urc as traits::AtatUrc>::Response, URC_CAPACITY, URC_SUBSCRIBERS, 1>, <Urc as traits::AtatUrc>::Response>`, which is not `Send` = note: `<Urc as traits::AtatUrc>::Response` doesn't implement `core::marker::Sync` = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send = note: `-W clippy::future-not-send` implied by `-W clippy::nursery` = help: to override `-W clippy::nursery` add `#[allow(clippy::future_not_send)]`
self.pos += commit;
assert!(self.pos <= self.buf.len());

Expand Down Expand Up @@ -260,7 +260,7 @@
match &resp {
Ok(r) => {
if r.is_empty() {
debug!("Received OK ({}/{})", swallowed, self.pos,)

Check warning on line 263 in atat/src/ingress.rs

View workflow job for this annotation

GitHub Actions / clippy

consider adding a `;` to the last statement for consistent formatting

warning: consider adding a `;` to the last statement for consistent formatting --> atat/src/ingress.rs:263:33 | 263 | ... debug!("Received OK ({}/{})", swallowed, self.pos,) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add a `;` here: `debug!("Received OK ({}/{})", swallowed, self.pos,);` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned
} else {
debug!(
"Received response ({}/{}): {:?}",
Expand Down Expand Up @@ -302,8 +302,8 @@
#[cfg(test)]
mod tests {
use crate::{
self as atat, atat_derive::AtatUrc, response_slot::ResponseSlot, AtDigester, Response,
UrcChannel,
self as atat, atat_derive::AtatUrc, digest::parser::take_until_including,
response_slot::ResponseSlot, AtDigester, Response, UrcChannel,
};

use super::*;
Expand All @@ -314,6 +314,115 @@
ConnectOk,
#[at_urc(b"CONNECT FAIL")]
ConnectFail,
#[at_urc(b"CUSTOM", parse = custom_parse_fn)]
CustomParse,

#[at_urc(b"+CREG", parse = custom_cxreg_parse)]
Creg,
}

/// Example custom parse function, that validates the number of arguments in
/// the URC.
///
/// NOTE: This will not work correctly with arguments containing quoted
/// commas, like e.g. HTTP responses or arbitrary data URCs.
fn custom_parse_fn<'a, T, Error: nom::error::ParseError<&'a [u8]> + core::fmt::Debug>(
token: T,
) -> impl Fn(&'a [u8]) -> nom::IResult<&'a [u8], (&'a [u8], usize), Error>
where
&'a [u8]: nom::Compare<T> + nom::FindSubstring<T>,
T: nom::InputLength + Clone + nom::InputTake + nom::InputIter,
{
const N_ARGS: usize = 3;

move |i| {
let (i, (urc, len)) = atat::digest::parser::urc_helper(token.clone())(i)?;

let index = urc.iter().position(|&x| x == b':').unwrap_or(urc.len());

let (_, cnt) = nom::multi::many0_count(nom::sequence::tuple((
nom::character::complete::space0::<_, Error>,
take_until_including(","),
)))(&urc[index + 1..])?;

if cnt + 1 != N_ARGS {
return Err(nom::Err::Error(Error::from_error_kind(
urc,
nom::error::ErrorKind::Count,
)));
}

Ok((i, (urc, len)))
}
}

/// Example custom parse function, that validates the number of arguments in
/// the URC.
///
/// "+CxREG?" response will always have atleast 2 arguments, both being
/// integers.
///
/// "+CxREG:" URC will always have at least 1 integer argument, and the
/// second argument, if present, will be a string.
fn custom_cxreg_parse<'a, T, Error: nom::error::ParseError<&'a [u8]> + core::fmt::Debug>(
token: T,
) -> impl Fn(&'a [u8]) -> nom::IResult<&'a [u8], (&'a [u8], usize), Error>
where
&'a [u8]: nom::Compare<T> + nom::FindSubstring<T>,
T: nom::InputLength + Clone + nom::InputTake + nom::InputIter + nom::AsBytes,
{
move |i| {
let (i, (urc, len)) = atat::digest::parser::urc_helper(token.clone())(i)?;

let index = urc.iter().position(|&x| x == b':').unwrap_or(urc.len());
let arguments = &urc[index + 1..];

// Parse the first argument
let (rem, _) = nom::sequence::tuple((
nom::character::complete::space0,
nom::number::complete::u8,
nom::branch::alt((nom::combinator::eof, nom::bytes::complete::tag(","))),
))(arguments)?;

if !rem.is_empty() {
// If we have more arguments, we want to make sure this is a quoted string for the URC case.
nom::sequence::tuple((
nom::character::complete::space0,
nom::sequence::delimited(
nom::bytes::complete::tag("\""),
nom::bytes::complete::escaped(
nom::character::streaming::none_of("\"\\"),
'\\',
nom::character::complete::one_of("\"\\"),
),
nom::bytes::complete::tag("\""),
),
nom::branch::alt((nom::combinator::eof, nom::bytes::complete::tag(","))),
))(rem)?;
}

Ok((i, (urc, len)))
}
}

#[test]
fn test_custom_parse_cxreg() {
let creg_resp = b"\r\n+CREG: 2,5,\"9E9A\",\"019624BD\",2\r\n";
let creg_urc_min = b"\r\n+CREG: 0\r\n";
let creg_urc_full = b"\r\n+CREG: 5,\"9E9A\",\"0196BDB0\",2\r\n";

assert!(
custom_cxreg_parse::<&[u8], nom::error::Error<&[u8]>>(&b"+CREG"[..])(creg_resp)
.is_err()
);
assert!(
custom_cxreg_parse::<&[u8], nom::error::Error<&[u8]>>(&b"+CREG"[..])(creg_urc_min)
.is_ok()
);
assert!(
custom_cxreg_parse::<&[u8], nom::error::Error<&[u8]>>(&b"+CREG"[..])(creg_urc_full)
.is_ok()
);
}

#[test]
Expand All @@ -328,12 +437,13 @@
let mut sub = urc_channel.subscribe().unwrap();

let buf = ingress.write_buf();
let data = b"\r\nCONNECT OK\r\n\r\nCONNECT FAIL\r\n\r\nOK\r\n";
let data = b"\r\nCONNECT OK\r\n\r\nCONNECT FAIL\r\n\r\nCUSTOM: 1,5, true\r\n\r\nOK\r\n";
buf[..data.len()].copy_from_slice(data);
ingress.try_advance(data.len()).unwrap();

assert_eq!(Urc::ConnectOk, sub.try_next_message_pure().unwrap());
assert_eq!(Urc::ConnectFail, sub.try_next_message_pure().unwrap());
assert_eq!(Urc::CustomParse, sub.try_next_message_pure().unwrap());

let response = res_slot.try_get().unwrap();
let response: &Response<100> = &response.borrow();
Expand Down
39 changes: 27 additions & 12 deletions atat_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ pub fn derive_atat_resp(input: TokenStream) -> TokenStream {
/// Automatically derive [`atat::AtatUrc`] trait
///
/// [`atat::AtatUrc`]: ../atat/trait.AtatUrc.html
///
/// ### Field attribute (`#[at_urc(..)]`)
/// The `AtatUrc` derive macro comes with a required field attribute
/// `#[at_urc(..)]`, that is used to specify the URC token to match for.
///
/// The first argument is required, and must be either a string or a byte
/// literal, specifying the URC token to match for.
///
/// Allowed optionals for `at_urc` are:
/// - `parse`: **function** Function that should be used to parse for the URC
/// instead of using default `atat::digest::parser::urc_helper` function. The
/// passed functions needs to have a valid non signature.
#[proc_macro_derive(AtatUrc, attributes(at_urc))]
pub fn derive_atat_urc(input: TokenStream) -> TokenStream {
urc::atat_urc(input)
Expand All @@ -77,10 +89,11 @@ pub fn derive_atat_urc(input: TokenStream) -> TokenStream {
/// Furthermore it automatically implements [`atat::AtatLen`], based on the data
/// type given in the container attribute.
///
/// **NOTE**: When using this derive macro with struct or tuple variants in the enum, one
/// should take extra care to avoid large size variations of the variants, as the
/// resulting `AtatLen` of the enum will be the length of the representation
/// (see `#[at_enum(..)]`) together with the largest sum of field values in the variant.
/// **NOTE**: When using this derive macro with struct or tuple variants in the
/// enum, one should take extra care to avoid large size variations of the
/// variants, as the resulting `AtatLen` of the enum will be the length of the
/// representation (see `#[at_enum(..)]`) together with the largest sum of field
/// values in the variant.
///
/// Eg.
/// ```ignore
Expand All @@ -102,8 +115,8 @@ pub fn derive_atat_urc(input: TokenStream) -> TokenStream {
/// `LargeSizeVariations::VariantOne`
///
/// ### Container attribute (`#[at_enum(..)]`)
/// The `AtatEnum` derive macro comes with an option of annotating the struct with
/// a container attribute `#[at_enum(..)]`.
/// The `AtatEnum` derive macro comes with an option of annotating the struct
/// with a container attribute `#[at_enum(..)]`.
///
/// The container attribute only allows specifying a single parameter, that is
/// non-optional if the container attribute is present. The parameter allows
Expand All @@ -116,10 +129,10 @@ pub fn derive_atat_urc(input: TokenStream) -> TokenStream {
///
/// ### Field attribute (`#[at_arg(..)]`)
/// The `AtatEnum` derive macro comes with an optional field attribute
/// `#[at_arg(..)]`, that can be specified o some or all of the fields.
/// `#[at_arg(..)]`, that can be specified for some or all of the fields.
///
/// Allowed options for `at_arg` are:
/// - `value` **integer** The value of the serialized field
/// - `value`: **integer** The value of the serialized field
#[proc_macro_derive(AtatEnum, attributes(at_enum, at_arg))]
pub fn derive_atat_enum(input: TokenStream) -> TokenStream {
enum_::atat_enum(input)
Expand Down Expand Up @@ -153,10 +166,12 @@ pub fn derive_atat_enum(input: TokenStream) -> TokenStream {
/// 'AT'). Can also be set to '' (empty).
/// - `termination`: **string** Overwrite the line termination of the command
/// (default '\r\n'). Can also be set to '' (empty).
/// - `quote_escape_strings`: **bool** Whether to escape strings in commands (default true).
/// - `parse`: **function** Function that should be used to parse the response instead of using
/// default `atat::serde_at::from_slice` function. The passed functions needs to have a signature
/// `Result<Response, E>` where `Response` is the type of the response passed in the `at_cmd`
/// - `quote_escape_strings`: **bool** Whether to escape strings in commands
/// (default true).
/// - `parse`: **function** Function that should be used to parse the response
/// instead of using default `atat::serde_at::from_slice` function. The
/// passed functions needs to have a signature `Result<Response, E>` where
/// `Response` is the type of the response passed in the `at_cmd`
///
/// ### Field attribute (`#[at_arg(..)]`)
/// The `AtatCmd` derive macro comes with an optional field attribute
Expand Down
60 changes: 44 additions & 16 deletions atat_derive/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub struct ArgAttributes {
#[derive(Clone)]
pub struct UrcAttributes {
pub code: LitByteStr,
pub parse: Option<Path>,
}

/// Parsed attributes of `#[at_enum(..)]`
Expand Down Expand Up @@ -78,8 +79,7 @@ pub fn parse_field_attr(attributes: &[Attribute]) -> Result<FieldAttributes> {
for attr in attributes {
if attr.path().is_ident("at_arg") {
attrs.at_arg = Some(attr.parse_args()?);
}
if attr.path().is_ident("at_urc") {
} else if attr.path().is_ident("at_urc") {
attrs.at_urc = Some(attr.parse_args()?);
}
}
Expand Down Expand Up @@ -227,9 +227,9 @@ impl Parse for ArgAttributes {

impl Parse for UrcAttributes {
fn parse(input: ParseStream) -> Result<Self> {
let code = match input.parse::<syn::Lit>()? {
Lit::ByteStr(b) => b,
Lit::Str(s) => LitByteStr::new(s.value().as_bytes(), input.span()),
let code = match input.parse::<syn::Lit>() {
Ok(Lit::ByteStr(b)) => b,
Ok(Lit::Str(s)) => LitByteStr::new(s.value().as_bytes(), input.span()),
_ => {
return Err(Error::new(
input.span(),
Expand All @@ -238,13 +238,26 @@ impl Parse for UrcAttributes {
}
};

Ok(Self { code })
let mut at_urc = Self { code, parse: None };

while input.parse::<syn::token::Comma>().is_ok() {
let optional = input.parse::<syn::MetaNameValue>()?;
if optional.path.is_ident("parse") {
match optional.value {
Expr::Path(ExprPath { path, .. }) => {
at_urc.parse = Some(path);
}
_ => return Err(Error::new(input.span(), "expected function for 'parse'")),
}
}
}

Ok(at_urc)
}
}

impl Parse for CmdAttributes {
fn parse(input: ParseStream) -> Result<Self> {
let call_site = Span::call_site();
let cmd = input.parse::<syn::LitStr>()?;
let _comma = input.parse::<syn::token::Comma>()?;
let response_ident = input.parse::<Path>()?;
Expand Down Expand Up @@ -274,7 +287,7 @@ impl Parse for CmdAttributes {
}
_ => {
return Err(Error::new(
call_site,
Span::call_site(),
"expected integer value for 'timeout_ms'",
))
}
Expand All @@ -288,7 +301,7 @@ impl Parse for CmdAttributes {
}
_ => {
return Err(Error::new(
call_site,
Span::call_site(),
"expected integer value for 'attempts'",
))
}
Expand All @@ -298,7 +311,12 @@ impl Parse for CmdAttributes {
Expr::Path(ExprPath { path, .. }) => {
at_cmd.parse = Some(path);
}
_ => return Err(Error::new(call_site, "expected function for 'parse'")),
_ => {
return Err(Error::new(
Span::call_site(),
"expected function for 'parse'",
))
}
}
} else if optional.path.is_ident("reattempt_on_parse_err") {
match optional.value {
Expand All @@ -309,7 +327,7 @@ impl Parse for CmdAttributes {
}
_ => {
return Err(Error::new(
call_site,
Span::call_site(),
"expected bool value for 'reattempt_on_parse_err'",
))
}
Expand All @@ -321,7 +339,12 @@ impl Parse for CmdAttributes {
}) => {
at_cmd.abortable = Some(v.value);
}
_ => return Err(Error::new(call_site, "expected bool value for 'abortable'")),
_ => {
return Err(Error::new(
Span::call_site(),
"expected bool value for 'abortable'",
))
}
}
} else if optional.path.is_ident("value_sep") {
match optional.value {
Expand All @@ -330,7 +353,12 @@ impl Parse for CmdAttributes {
}) => {
at_cmd.value_sep = v.value;
}
_ => return Err(Error::new(call_site, "expected bool value for 'value_sep'")),
_ => {
return Err(Error::new(
Span::call_site(),
"expected bool value for 'value_sep'",
))
}
}
} else if optional.path.is_ident("cmd_prefix") {
match optional.value {
Expand All @@ -341,7 +369,7 @@ impl Parse for CmdAttributes {
}
_ => {
return Err(Error::new(
call_site,
Span::call_site(),
"expected string value for 'cmd_prefix'",
))
}
Expand All @@ -355,7 +383,7 @@ impl Parse for CmdAttributes {
}
_ => {
return Err(Error::new(
call_site,
Span::call_site(),
"expected string value for 'termination'",
))
}
Expand All @@ -369,7 +397,7 @@ impl Parse for CmdAttributes {
}
_ => {
return Err(Error::new(
call_site,
Span::call_site(),
"expected bool value for 'quote_escape_strings'",
))
}
Expand Down
Loading
Loading