synthez_core/parse/attr.rs
1//! Batteries for parsing a single attribute.
2
3use crate::Spanning;
4
5/// Lookups for the standard Rust `#[doc]` attributes in the given
6/// [`syn::Attribute`]s and parses their text as [`String`] with a little
7/// normalization.
8///
9/// # Errors
10///
11/// - If parsing text from `#[doc]` attribute fails.
12/// - If `#[doc]` doesn't contain text.
13pub fn doc_string(
14 attrs: &[syn::Attribute],
15) -> syn::Result<Option<Spanning<String>>> {
16 let mut span = None;
17 let mut out = String::new();
18
19 for a in super::attrs::filter_by_name("doc", attrs) {
20 if let syn::Meta::NameValue(item) = &a.meta {
21 if let syn::Expr::Lit(expr) = &item.value {
22 if let syn::Lit::Str(lit) = &expr.lit {
23 if span.is_none() {
24 span = Some(lit.span());
25 }
26
27 let s = lit.value();
28 let s = s.strip_prefix(' ').unwrap_or(&s).trim_end();
29 if s.ends_with('\\') {
30 out.push_str(s.trim_end_matches('\\'));
31 out.push(' ');
32 } else {
33 out.push_str(s);
34 out.push('\n');
35 }
36 } else {
37 return Err(syn::Error::new_spanned(
38 &expr.lit,
39 "`#[doc]` attribute can contain string literals only",
40 ));
41 }
42 } else {
43 return Err(syn::Error::new_spanned(
44 &item.value,
45 "`#[doc]` attribute should be in `#[doc = value] format`",
46 ));
47 }
48 }
49 }
50 if !out.is_empty() {
51 out.truncate(out.trim_end().len());
52 }
53
54 Ok(span.map(|s| Spanning::new(out, s)))
55}
56
57/// Lookups for the standard Rust `#[doc]` attributes in the given
58/// [`syn::Attribute`]s and parses their text as [`syn::LitStr`] with a little
59/// normalization.
60///
61/// # Errors
62///
63/// - If parsing text from `#[doc]` attribute fails.
64/// - If `#[doc]` doesn't contain text.
65///
66/// [`syn::LitStr`]: struct@syn::LitStr
67pub fn doc(attrs: &[syn::Attribute]) -> syn::Result<Option<syn::LitStr>> {
68 doc_string(attrs).map(|opt| opt.map(Into::into))
69}