use proc_macro2::Span;
use syn::{spanned::Spanned, Attribute, Expr, ExprLit, Lit, Meta};
use crate::{fields_parse::ParserType, rename_rules::RenameRule, Result};
pub(crate) struct CommandAttrs {
pub prefix: Option<(String, Span)>,
pub description: Option<(String, Span)>,
pub rename_rule: Option<(RenameRule, Span)>,
pub rename: Option<(String, Span)>,
pub parser: Option<(ParserType, Span)>,
pub separator: Option<(String, Span)>,
pub command_separator: Option<(String, Span)>,
pub hide: Option<Span>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum DescriptionSource {
DocComment,
Explicit,
}
impl CommandAttrs {
pub fn from_attributes(attributes: &[Attribute]) -> Result<Self> {
let mut this = Self {
prefix: None,
description: None,
rename_rule: None,
rename: None,
parser: None,
separator: None,
command_separator: None,
hide: None,
};
let mut desc_source: Option<DescriptionSource> = None;
for attr in attributes {
if attr.path().is_ident("doc") {
if desc_source != Some(DescriptionSource::Explicit) {
if let Meta::NameValue(nv) = &attr.meta {
if let Expr::Lit(ExprLit {
lit: Lit::Str(s), ..
}) = &nv.value
{
let text = s.value();
let text = text.strip_prefix(' ').unwrap_or(&text);
match &mut this.description {
Some((existing, _)) => {
existing.push('\n');
existing.push_str(text);
}
slot @ None => {
*slot = Some((text.to_owned(), s.span()));
}
}
desc_source = Some(DescriptionSource::DocComment);
}
}
}
continue;
}
if !attr.path().is_ident("command") {
continue;
}
attr.parse_nested_meta(|meta| {
let span = meta.path.span();
if meta.path.is_ident("prefix") {
let value: Lit = meta.value()?.parse()?;
if let Lit::Str(s) = value {
insert_syn(&mut this.prefix, s.value(), span)?;
}
} else if meta.path.is_ident("description") {
let value: Lit = meta.value()?.parse()?;
if let Lit::Str(s) = value {
if desc_source == Some(DescriptionSource::Explicit) {
return Err(syn::Error::new(span, "duplicate `description` attribute"));
}
this.description = Some((s.value(), span));
desc_source = Some(DescriptionSource::Explicit);
}
} else if meta.path.is_ident("rename_rule") {
let value: Lit = meta.value()?.parse()?;
if let Lit::Str(s) = value {
let rule = RenameRule::parse(&s.value()).map_err(|_| {
syn::Error::new(span, format!("invalid rename rule `{}`", s.value()))
})?;
insert_syn(&mut this.rename_rule, rule, span)?;
}
} else if meta.path.is_ident("rename") {
let value: Lit = meta.value()?.parse()?;
if let Lit::Str(s) = value {
insert_syn(&mut this.rename, s.value(), span)?;
}
} else if meta.path.is_ident("parse_with") {
let value: Lit = meta.value()?.parse()?;
if let Lit::Str(s) = value {
let parser = ParserType::parse(&s.value()).map_err(|_| {
syn::Error::new(
span,
format!("invalid parse_with value `{}`", s.value()),
)
})?;
insert_syn(&mut this.parser, parser, span)?;
}
} else if meta.path.is_ident("separator") {
let value: Lit = meta.value()?.parse()?;
if let Lit::Str(s) = value {
insert_syn(&mut this.separator, s.value(), span)?;
}
} else if meta.path.is_ident("command_separator") {
let value: Lit = meta.value()?.parse()?;
if let Lit::Str(s) = value {
insert_syn(&mut this.command_separator, s.value(), span)?;
}
} else if meta.path.is_ident("hide") {
if this.hide.is_some() {
return Err(syn::Error::new(span, "duplicate `hide` attribute"));
}
this.hide = Some(span);
} else {
return Err(syn::Error::new(
span,
"unknown command attribute (expected one of: `prefix`, `description`, \
`rename_rule`, `rename`, `parse_with`, `separator`, \
`command_separator`, `hide`)",
));
}
Ok(())
})?;
}
Ok(this)
}
}
fn insert_syn<T>(
slot: &mut Option<(T, Span)>,
value: T,
span: Span,
) -> std::result::Result<(), syn::Error> {
if slot.is_some() {
return Err(syn::Error::new(span, "duplicate attribute"));
}
*slot = Some((value, span));
Ok(())
}