struct CommandAttr {
name: Option<String>,
capability: Option<String>,
platforms: Option<Vec<String>>,
errors: Option<Vec<String>>,
devices: Option<Vec<String>>,
}
impl Parse for CommandAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut attr = CommandAttr {
name: None,
capability: None,
platforms: None,
errors: None,
devices: None,
};
if input.is_empty() {
return Ok(attr);
}
loop {
let key: Ident = input.parse()?;
if key == "name" {
let _: Token![=] = input.parse()?;
let name: LitStr = input.parse()?;
attr.name = Some(name.value());
} else if key == "capability" {
let _: Token![=] = input.parse()?;
let cap: LitStr = input.parse()?;
attr.capability = Some(cap.value());
} else if key == "platform" {
let content;
let _: syn::token::Paren = syn::parenthesized!(content in input);
let mut platforms = Vec::new();
loop {
let ident: Ident = content.parse()?;
platforms.push(ident.to_string());
if content.parse::<Token![,]>().is_err() {
break;
}
}
if platforms.is_empty() {
return Err(syn::Error::new(
key.span(),
"platform(...) requires at least one platform",
));
}
attr.platforms = Some(platforms);
} else if key == "error" {
let content;
let _: syn::token::Paren = syn::parenthesized!(content in input);
if content.is_empty() {
return Err(syn::Error::new(
key.span(),
"error(...) requires at least one error code",
));
}
let mut errors = Vec::new();
loop {
let code: LitStr = content.parse()?;
errors.push(code.value());
if content.parse::<Token![,]>().is_err() {
break;
}
}
attr.errors = Some(errors);
} else if key == "device" {
let content;
let _: syn::token::Paren = syn::parenthesized!(content in input);
if content.is_empty() {
return Err(syn::Error::new(
key.span(),
"device(...) requires at least one capability token",
));
}
let mut devices = Vec::new();
loop {
if content.peek(LitStr) {
let token: LitStr = content.parse()?;
devices.push(token.value());
} else {
let ident: Ident = content.parse()?;
devices.push(ident.to_string());
}
if content.parse::<Token![,]>().is_err() {
break;
}
}
attr.devices = Some(devices);
} else {
return Err(syn::Error::new(
key.span(),
"unsupported `#[command]` key; supported keys: `name`, `capability`, `platform`, `error`, `device`",
));
}
if input.parse::<Token![,]>().is_err() {
break;
}
}
Ok(attr)
}
}
fn extract_state_inner(ty: &Type) -> Option<Type> {
let Type::Path(type_path) = ty else {
return None;
};
let segment = type_path.path.segments.last()?;
if segment.ident == "State"
&& let PathArguments::AngleBracketed(args) = &segment.arguments
&& let Some(GenericArgument::Type(inner_ty)) = args.args.first()
{
return Some(inner_ty.clone());
}
None
}
fn extract_result_inner(ty: &Type) -> Option<TokenStream2> {
let Type::Path(type_path) = ty else {
return None;
};
let segment = type_path.path.segments.last()?;
if segment.ident != "Result" {
return None;
}
let PathArguments::AngleBracketed(args) = &segment.arguments else {
return None;
};
let GenericArgument::Type(inner_ty) = args.args.first()? else {
return None;
};
Some(quote! { #inner_ty })
}
fn command_doc_comment(func: &ItemFn) -> String {
func.attrs
.iter()
.filter_map(|attr| {
if attr.path().is_ident("doc")
&& let syn::Meta::NameValue(nv) = &attr.meta
&& let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}) = &nv.value
{
return Some(s.value().trim().to_string());
}
None
})
.collect::<Vec<_>>()
.join("\n")
}