use doc_data::*;
use once_cell::sync::{Lazy};
use proc_macro::{TokenStream };
use proc_macro2::{TokenTree, Span};
use quote::quote;
use std::{
sync::{
atomic::{AtomicUsize, Ordering},
},
};
use strum::IntoEnumIterator;
use syn::{
Attribute,
AttributeArgs,
Error,
Field,
Fields,
FieldsNamed,
FieldsUnnamed,
Ident,
Item,
ItemEnum,
ItemStruct,
parse_str,
parse_macro_input,
Path,
parse::{Parse, ParseStream},
Result,
Variant,
Visibility,
};
static INVOCATIONS: AtomicUsize = AtomicUsize::new(0);
fn count_invocation() -> usize {
INVOCATIONS.fetch_add(1, Ordering::SeqCst)
}
fn paths_eq(
p_0: &Path,
p_1: &Path
) -> bool {
format!("{}", quote!{#p_0}) == format!("{}", quote!{#p_1})
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
struct ParsedOuterAttrs {
pub vis_opt: Option<Visibility>,
pub outer_attrs: Vec<Attribute>,
pub item_opt: Option<Item>,
}
impl ParsedOuterAttrs{
#[allow(clippy::declare_interior_mutable_const)]
pub const DOC_PATH: Lazy<Path> = Lazy::new(|| parse_str::<Path>("doc").expect("must parse doc path"));
#[allow(dead_code)]
pub const DOC_ATTR_LINE_START: &'static str = "= \"";
pub const DOC_ATTR_LINE_END: &'static str = "\"";
pub const LINE_JOINER: &'static str = "\n";
pub fn extract_doc_data(
&self,
_count: usize,
) -> anyhow::Result<()> {
type AttrsCollectionType = Vec<(Option<Ident>, Vec<(HelperAttr, Attribute)>, Vec<Attribute>)>;
let extract_attrs_and_ident_into_map_fold_fn = |
attrs: &Vec<Attribute>,
ident: &Option<Ident>,
m: &mut AttrsCollectionType,
| -> anyhow::Result<()> {
let mut helper_attrs_and_attributes
: Vec<(HelperAttr, Attribute)> = Vec::with_capacity(attrs.len());
let mut other_attrs
: Vec<Attribute> = Vec::with_capacity(attrs.len());
for attr in attrs.iter() {
if let Some(_helper_attr) = HelperAttr::iter()
.find(|helper_attr| paths_eq(&attr.path, &helper_attr.into()))
{
helper_attrs_and_attributes.push(
(HelperAttr::from_attribute(attr)?, attr.clone())
);
} else {
other_attrs.push(attr.clone());
}
}
if !helper_attrs_and_attributes.is_empty() {
m.push((ident.clone(), helper_attrs_and_attributes, other_attrs));
}
Ok(())
};
let record_doc_from_attrs_collection = |
attrs_collection: AttrsCollectionType
| -> anyhow::Result<()> {
let _attrs_collection_len = attrs_collection.len();
for (
_i, (_ident_opt, helper_attrs_and_attributes, other_attributes)
) in attrs_collection.iter().enumerate() {
let helper_attrs = helper_attrs_and_attributes.iter().map(|(h,_)| h.clone()).collect();
let content: String = Self::get_outer_doc_comments_string(other_attributes);
record_doc_from_helper_attributes_and_str(
true, &content,
&helper_attrs,
)?;
}
Ok(())
};
if let Some(ref item) = self.item_opt {
match item {
Item::Enum(ItemEnum{ref variants, ..}) => {
let mut attrs_collection: AttrsCollectionType = Vec::with_capacity(variants.len());
for Variant{ref attrs, ref ident, ..} in variants.iter() {
extract_attrs_and_ident_into_map_fold_fn( attrs, &Some(ident.clone()), &mut attrs_collection)?
}
record_doc_from_attrs_collection(attrs_collection)
},
Item::Struct(ItemStruct{ ref fields, ..}) => {
match fields {
Fields::Named(FieldsNamed{ ref named, .. }) => {
let mut attrs_collection: AttrsCollectionType = Vec::with_capacity(named.len());
for Field{ref attrs, ref ident, ..} in named.iter() {
extract_attrs_and_ident_into_map_fold_fn( attrs, ident, &mut attrs_collection)?
}
record_doc_from_attrs_collection(attrs_collection)
},
Fields::Unnamed(FieldsUnnamed{ ref unnamed, .. }) => {
let mut attrs_collection: AttrsCollectionType = Vec::with_capacity(unnamed.len());
for Field{ref attrs, ref ident, ..} in unnamed.iter() {
extract_attrs_and_ident_into_map_fold_fn( attrs, ident, &mut attrs_collection)?
}
record_doc_from_attrs_collection(attrs_collection)
},
_ => Ok(())
}
},
_ => {
Ok(())
}
}
} else {
Ok(())
}
}
#[allow(dead_code)]
pub fn extract_comment_text(s: &str) -> String {
if s.starts_with(Self::DOC_ATTR_LINE_START) && s.ends_with(Self::DOC_ATTR_LINE_END) {
s[Self::DOC_ATTR_LINE_START.len()..(s.len() - Self::DOC_ATTR_LINE_END.len())].to_string()
} else {
String::new()
}
}
#[allow(clippy::borrow_interior_mutable_const)]
pub fn get_doc_comments_lines(
outer_attrs: &[Attribute]
) -> Vec<String> {
outer_attrs.iter()
.filter_map(|Attribute{path, ref tokens, ..}| {
if paths_eq(path, &*Self::DOC_PATH) {
match tokens.clone().into_iter().nth(1) {
Some(TokenTree::Literal(literal)) => {
Some(
literal
.to_string()
.trim_end_matches(Self::DOC_ATTR_LINE_END)
.trim_start_matches(Self::DOC_ATTR_LINE_END)
.to_string()
)
},
_ => None
}
} else {
None
}
})
.collect()
}
pub fn get_outer_doc_comments_string(attrs: &[Attribute]) -> String {
Self::get_doc_comments_lines(attrs).join(ParsedOuterAttrs::LINE_JOINER)
}
}
impl Parse for ParsedOuterAttrs {
fn parse(input: ParseStream) -> Result<Self> {
Ok(Self{
outer_attrs: input.call(Attribute::parse_outer)?,
vis_opt: input.parse().ok(),
item_opt: input.parse().ok()
})
}
}
#[allow(clippy::ptr_arg)]
fn record_doc_from_helper_attributes_and_str(
do_save: bool,
doc_comment_string: &str,
helper_attributes: &Vec<HelperAttr>,
) -> Result<()> {
let mut chapter_blurb_opt = None;
let mut name_opt = None;
let mut number_opt = None;
let mut name_path_opt = None;
let mut number_path_opt = None;
for helper_attribute in helper_attributes.iter() {
match helper_attribute {
HelperAttr::ChapterName(ref chapter_name) => {
name_opt = Some(chapter_name.to_string());
},
HelperAttr::ChapterBlurb(ref chapter_blurb) => {
chapter_blurb_opt = Some(chapter_blurb.to_string());
},
HelperAttr::ChapterNameSlug(ref chapter_names) => {
name_path_opt = Some(chapter_names.to_vec());
},
HelperAttr::ChapterNum(ref chapter_number) => {
number_opt = Some(*chapter_number);
},
HelperAttr::ChapterNumSlug(ref chapter_numbers) => {
number_path_opt = Some(chapter_numbers.to_vec());
},
}
}
let generate_documentable = |
name_opt: &Option<String>
| -> Documentable {
let documentable = Documentable::Doc(
name_opt.as_ref().cloned().unwrap_or_default(),
doc_comment_string.to_string()
);
documentable
};
let docs = &*doc_data::DOCS;
let mut docs_write_lock = docs.write().expect("Must get write lock on global docs");
let write_res = match (number_path_opt, name_path_opt) {
(Some(ref path_numbers), Some(ref path_names)) => {
if name_opt.is_none() {
name_opt = path_names.get(path_numbers.len()-1).cloned();
}
let documentable = generate_documentable(&name_opt);
docs_write_lock.add_path(
&chapter_blurb_opt,
&name_opt,
Some(documentable),
Some(true),
path_names,
path_numbers,
)
},
(Some(ref path_numbers), None) => {
let documentable = generate_documentable(&name_opt);
docs_write_lock.add_path(
&chapter_blurb_opt,
&name_opt,
Some(documentable),
Some(true),
&[],
path_numbers,
)
},
(None, Some(ref path_names)) => {
if name_opt.is_none() {
name_opt = path_names.last().cloned();
}
let documentable = generate_documentable(&name_opt);
docs_write_lock.add_path(
&chapter_blurb_opt,
&name_opt,
Some(documentable),
Some(true),
path_names,
&[],
)
},
(None, None) => {
let documentable = generate_documentable(&name_opt);
docs_write_lock.add_entry(
documentable,
name_opt,
number_opt,
Some(true),
)
}
};
drop(docs_write_lock);
match write_res {
Ok(()) => {
if do_save {
match doc_data::save_global_docs_to_path(
None,
None,
) {
Ok(()) => {
Ok(())
}
Err(doc_save_error) => {
Err(Error::new(
Span::mixed_site(),
format!("{:#?}", doc_save_error)
))
}
}
} else {
Ok(())
}
},
Err(error) => {
Err(Error::new(
Span::mixed_site(),
format!("{:#?}", error),
))
}
}
}
#[proc_macro_derive(
user_doc_item,
attributes(
chapter_blurb,
chapter_name,
chapter_name_slug,
chapter_num,
chapter_num_slug,
)
)]
pub fn user_doc_item(
item: TokenStream
) -> TokenStream {
let count = count_invocation();
let parsed_outer_attrs = parse_macro_input!(item as ParsedOuterAttrs);
match parsed_outer_attrs.extract_doc_data(count) {
Ok(()) => TokenStream::new(),
Err(extraction_error) => Error::new(
Span::call_site(),
format!(
"Could not extract doc data during derive macro invocation:\n{:#?}",
extraction_error
)
).into_compile_error().into()
}
}
#[proc_macro_attribute]
pub fn user_doc_fn(
own_attr: TokenStream,
item: TokenStream,
) -> TokenStream {
let _count:usize = count_invocation();
let it = item.clone();
let own_attribute_args = parse_macro_input!(own_attr as AttributeArgs);
let helper_attributes_res: Result<Vec<HelperAttr>> = HelperAttr::from_attribute_args(&own_attribute_args);
let parsed_outer_attrs = parse_macro_input!(item as ParsedOuterAttrs);
match helper_attributes_res {
Ok(helper_attributes) => {
match record_doc_from_helper_attributes_and_str(
true, &ParsedOuterAttrs::get_outer_doc_comments_string(&parsed_outer_attrs.outer_attrs),
&helper_attributes,
) {
Ok(()) => it,
Err(err) => err.into_compile_error().into()
}
},
Err(err) => err.into_compile_error().into()
}
}