readme_sync/
docs_parser.rs

1use std::borrow::Cow;
2
3use thiserror::Error;
4
5use crate::Config;
6
7/// Parsed documentation text chunk.
8#[derive(Clone, Debug, Eq, PartialEq)]
9pub struct DocsItem {
10    /// Parsed or generated text.
11    pub text: Cow<'static, str>,
12    /// Source file span.
13    pub span: Option<DocsSpan>,
14}
15
16/// Documentation text chunk span start and end.
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct DocsSpan {
19    /// Chunk start line and column.
20    pub start: DocsLineColumn,
21    /// Chunk end line and column.
22    pub end: DocsLineColumn,
23}
24
25/// Documentation line and column.
26#[derive(Clone, Copy, Debug, Eq, PartialEq)]
27pub struct DocsLineColumn {
28    /// The line number within the file (0-indexed).
29    pub line: usize,
30    /// The column within the line (0-indexed).
31    pub column: usize,
32}
33
34impl From<&syn::LitStr> for DocsItem {
35    fn from(lit_str: &syn::LitStr) -> Self {
36        let text = Cow::from(lit_str.value());
37        let span = Some(DocsSpan::from(lit_str.span()));
38        Self { text, span }
39    }
40}
41
42impl From<&'static str> for DocsItem {
43    fn from(text: &'static str) -> Self {
44        Self {
45            text: Cow::from(text),
46            span: None,
47        }
48    }
49}
50
51impl From<proc_macro2::Span> for DocsSpan {
52    fn from(span: proc_macro2::Span) -> Self {
53        let start = span.start();
54        let end = span.end();
55        Self {
56            start: DocsLineColumn {
57                line: start.line - 1,
58                column: start.column,
59            },
60            end: DocsLineColumn {
61                line: end.line - 1,
62                column: end.column,
63            },
64        }
65    }
66}
67
68/// Builds documentation from the specified attribute.
69pub fn build_attr_docs(
70    attr: &syn::Attribute,
71    config: &Config<'_>,
72) -> Result<impl Iterator<Item = DocsItem>, BuildAttrDocsError> {
73    Ok(build_meta_docs(&attr.meta, config)?)
74}
75
76/// Builds documentation from the specified compile-time structured attribute.
77pub fn build_meta_docs(
78    meta: &syn::Meta,
79    config: &Config<'_>,
80) -> Result<impl Iterator<Item = DocsItem>, BuildMetaDocsError> {
81    use std::vec::Vec;
82
83    if meta.path().is_ident("doc") {
84        match meta {
85            syn::Meta::NameValue(syn::MetaNameValue { value, .. }) => match value {
86                syn::Expr::Lit(syn::ExprLit {
87                    lit: syn::Lit::Str(lit_str),
88                    attrs,
89                }) if attrs.is_empty() => {
90                    Ok(std::vec![DocsItem::from(lit_str), DocsItem::from("\n")].into_iter())
91                }
92                _ => Err(BuildMetaDocsError::NonStringDocInput(meta.clone())),
93            },
94            _ => Ok(Vec::new().into_iter()),
95        }
96    } else if meta.path().is_ident("cfg_attr") {
97        match meta {
98            syn::Meta::List(meta_list) => {
99                let mut it = meta_list
100                    .parse_args::<PunctuatedMetaArgs>()
101                    .map_err(|_| BuildMetaDocsError::CfgNonMetaAttribute(meta_list.clone()))?
102                    .0
103                    .into_iter();
104                let predicate = it
105                    .next()
106                    .ok_or_else(|| BuildMetaDocsError::CfgAttrWithoutPredicate(meta.clone()))?;
107                let mut it = it.peekable();
108                let _ = it
109                    .peek()
110                    .ok_or_else(|| BuildMetaDocsError::CfgAttrWithoutAttribute(meta.clone()))?;
111
112                let predicate_result = eval_cfg_predicate(&predicate, config)?;
113                if predicate_result {
114                    let doc: Result<Vec<DocsItem>, BuildMetaDocsError> = it
115                        .map(|nested_meta| build_meta_docs(&nested_meta, config))
116                        .try_fold(Vec::new(), |mut acc, doc| {
117                            acc.extend(doc?);
118                            Ok(acc)
119                        });
120                    let doc = doc?;
121                    Ok(doc.into_iter())
122                } else {
123                    Ok(Vec::new().into_iter())
124                }
125            }
126            _ => Err(BuildMetaDocsError::NonListCfgAttrInput(meta.clone())),
127        }
128    } else {
129        Ok(Vec::new().into_iter())
130    }
131}
132
133/// Evaluates configuration predicate.
134pub fn eval_cfg_predicate(
135    meta: &syn::Meta,
136    config: &Config<'_>,
137) -> Result<bool, EvalCfgPredicateError> {
138    use std::string::ToString;
139
140    let ident = meta
141        .path()
142        .get_ident()
143        .ok_or_else(|| EvalCfgPredicateError::NonIdentPath(meta.clone()))?;
144    match meta {
145        syn::Meta::Path(_) => Ok(config.idents.contains(ident.to_string().as_str())),
146        syn::Meta::List(meta_list) => {
147            let it = meta_list
148                .parse_args::<PunctuatedMetaArgs>()
149                .map_err(|_| EvalCfgPredicateError::CfgNonMetaAttribute(meta_list.clone()))?
150                .0
151                .into_iter();
152
153            if ident == "all" {
154                for nested_meta in it {
155                    match eval_cfg_predicate(&nested_meta, config) {
156                        Ok(true) => {}
157                        value => {
158                            return value;
159                        }
160                    }
161                }
162                Ok(true)
163            } else if ident == "any" {
164                for nested_meta in it {
165                    match eval_cfg_predicate(&nested_meta, config) {
166                        Ok(false) => {}
167                        value => {
168                            return value;
169                        }
170                    }
171                }
172                Ok(false)
173            } else if ident == "not" {
174                let mut iter = it;
175                if let (Some(first), None) = (iter.next(), iter.next()) {
176                    Ok(!eval_cfg_predicate(&first, config)?)
177                } else {
178                    Err(EvalCfgPredicateError::NonSingleNotInput(meta.clone()))
179                }
180            } else {
181                Err(EvalCfgPredicateError::InvalidPredicateFn(meta.clone()))
182            }
183        }
184        syn::Meta::NameValue(syn::MetaNameValue { value, .. }) => match value {
185            syn::Expr::Lit(syn::ExprLit {
186                lit: syn::Lit::Str(lit_str),
187                attrs,
188            }) if attrs.is_empty() => Ok(config.name_values.contains(&(
189                Cow::from(ident.to_string().as_str()),
190                Cow::from(lit_str.value()),
191            ))),
192            _ => Err(EvalCfgPredicateError::NonStringOptionValue(meta.clone())),
193        },
194    }
195}
196
197struct PunctuatedMetaArgs(syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>);
198
199impl syn::parse::Parse for PunctuatedMetaArgs {
200    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
201        Ok(Self(syn::punctuated::Punctuated::<
202            syn::Meta,
203            syn::Token![,],
204        >::parse_terminated(input)?))
205    }
206}
207
208/// An error which can occur when building documentation from attribute.
209#[derive(Clone, Debug, Error)]
210pub enum BuildAttrDocsError {
211    /// Attribute parser error.
212    #[error("Attribute parser error: {0}")]
213    SynError(#[from] syn::Error),
214    /// Meta parser error.
215    #[error("Meta parser error: {0}")]
216    MetaError(#[from] BuildMetaDocsError),
217}
218
219/// An error which can occur when building documentation from meta-attribute.
220#[derive(Clone, Debug, Eq, Error, PartialEq)]
221pub enum BuildMetaDocsError {
222    /// Non-string doc attribute input.
223    #[error("Non-string doc attribute input: `{0:?}`.")]
224    NonStringDocInput(syn::Meta),
225    /// Non-list `cfg_attr` attribute input.
226    #[error("Non-list `cfg_attr` attribute input: `{0:?}`.")]
227    NonListCfgAttrInput(syn::Meta),
228    /// `cfg_attr` without predicate argument.
229    #[error("`cfg_attr` should contain predicate argument: `{0:?}`.")]
230    CfgAttrWithoutPredicate(syn::Meta),
231    /// `cfg_attr` has no attributes.
232    #[error("`cfg_attr` should contain at least one attribute: `{0:?}`.")]
233    CfgAttrWithoutAttribute(syn::Meta),
234    /// `cfg_attr` non-meta attribute.
235    #[error("`cfg_attr` attribute should be a meta attribute: `{0:?}`.")]
236    CfgNonMetaAttribute(syn::MetaList),
237    /// Predicate evaluation error.
238    #[error(transparent)]
239    PredicateError(#[from] EvalCfgPredicateError),
240}
241
242/// An error which can occur when evaluating configuration predicate.
243#[derive(Clone, Debug, Eq, Error, PartialEq)]
244pub enum EvalCfgPredicateError {
245    /// Non-indentifier predicatge path.
246    #[error("Predicate path should be an identifier: `{0:?}`.")]
247    NonIdentPath(syn::Meta),
248    /// Non-single `not()` input.
249    #[error("Predicate `not()` accepts only a single inner predicate: `{0:?}`.")]
250    NonSingleNotInput(syn::Meta),
251    /// Unknown predicate function.
252    #[error("Unknown predicate function: `{0:?}`.")]
253    InvalidPredicateFn(syn::Meta),
254    /// Non-string option value.
255    #[error("Predicatge option values can only be a string or raw string literal: `{0:?}`.")]
256    NonStringOptionValue(syn::Meta),
257    /// Non-meta attribute.
258    #[error("Predicate should be a meta attribute: `{0:?}`.")]
259    CfgNonMetaAttribute(syn::MetaList),
260}