function_docs 0.1.0

Provides macros for retrieving the doc comments on a function as a string.
Documentation
use itertools::Itertools;
use proc_macro::{Delimiter, TokenStream, TokenTree};

/// Marks a function for retrieval of doc comments using [`get_docs!()`].
///
/// # Examples
///
/// ```
/// # use function_docs::{function_docs, get_docs};
/// /// Adds two integers together and returns them.
/// #[function_docs]
/// fn add(a: i32, b: i32) -> i32 {
///     a + b
/// }
/// # fn main() {get_docs!(add);}
/// ```
#[proc_macro_attribute]
pub fn function_docs(_attr: TokenStream, mut item: TokenStream) -> TokenStream {
    item.extend(
        format!(
            "#[doc(hidden)]pub static ___FUNCTION_DOCS_{}: &str = {:?};",
            if let Some(x) =
                item.clone()
                    .into_iter()
                    .tuple_windows()
                    .find_map(|(x, y)| match (x, y) {
                        (TokenTree::Ident(a), TokenTree::Ident(b)) if a.to_string() == "fn" =>
                            Some(b),
                        _ => None,
                    })
            {
                x
            } else {
                return "compile_error!(\"function_docs only supports function docs\");"
                    .parse()
                    .unwrap();
            },
            item.clone()
                .into_iter()
                .tuple_windows()
                .filter_map(|(x, y)| match (x, y) {
                    (TokenTree::Punct(p), TokenTree::Group(g))
                        if p.as_char() == '#' && g.delimiter() == Delimiter::Bracket =>
                    {
                        let mut iter = g.stream().into_iter();
                        match (iter.next(), iter.next(), iter.next()) {
                            (
                                Some(TokenTree::Ident(i)),
                                Some(TokenTree::Punct(p)),
                                Some(TokenTree::Literal(s)),
                            ) if i.to_string() == "doc" && p.as_char() == '=' => Some(
                                s.to_string()
                                    .strip_prefix("\"")
                                    .unwrap()
                                    .strip_suffix("\"")
                                    .unwrap()
                                    .trim_start_matches(' ')
                                    .to_owned(),
                            ),
                            _ => None,
                        }
                    }
                    _ => None,
                })
                .collect::<Vec<String>>()
                .join("\n"),
        )
        .parse::<TokenStream>()
        .unwrap(),
    );
    item
}

/// Retrieves the doc comments on a function marked with [`#[function_docs]`](attr.function_docs.html).
///
/// # Examples
///
/// ```
/// # use function_docs::{function_docs, get_docs};
/// /// Adds two integers together and returns them.
/// #[function_docs]
/// fn add(a: i32, b: i32) -> i32 {
///     a + b
/// }
/// 
/// fn main() {
///     assert_eq!(get_docs!(add), "Adds two integers together and returns them.");
/// }
/// ```
#[proc_macro]
pub fn get_docs(item: TokenStream) -> TokenStream {
    let mut iter = item.into_iter();
    match (iter.next(), iter.next()) {
        (Some(TokenTree::Ident(i)), None) => {
            format!("___FUNCTION_DOCS_{}", i.to_string()).parse().unwrap()
        }
        _ => "compile_error!(\"expected ident\");".parse().unwrap(),
    }
}