no_incode_comments/
lib.rs1use proc_macro::TokenStream;
2use quote::quote;
3use syn::parse::{Parse, ParseStream};
4use syn::punctuated::Punctuated;
5use syn::token::Comma;
6use syn::{parse_macro_input, Expr, Item, ItemFn, ItemStruct, Lit, Meta};
7
8use std::collections::HashMap;
9use std::fs;
10
11struct ExternalDocArgs {
36 args: Punctuated<Meta, Comma>,
37}
38
39impl Parse for ExternalDocArgs {
40 fn parse(input: ParseStream) -> syn::Result<Self> {
41 Ok(ExternalDocArgs {
42 args: Punctuated::parse_terminated(input)?,
43 })
44 }
45}
46
47#[proc_macro_attribute]
48pub fn external_doc(attr: TokenStream, item: TokenStream) -> TokenStream {
49 let args = parse_macro_input!(attr as ExternalDocArgs).args;
50 let input = parse_macro_input!(item as Item);
51
52 let mut doc_path = None;
53 let mut doc_key = None;
54
55 for arg in args {
56 match arg {
57 Meta::NameValue(nv) if nv.path.is_ident("path") => {
58 if let Expr::Lit(expr_lit) = nv.value {
59 if let Lit::Str(lit_str) = expr_lit.lit {
60 doc_path = Some(lit_str.value());
61 }
62 }
63 }
64 Meta::NameValue(nv) if nv.path.is_ident("key") => {
65 if let Expr::Lit(expr_lit) = nv.value {
66 if let Lit::Str(lit_str) = expr_lit.lit {
67 doc_key = Some(lit_str.value());
68 }
69 }
70 }
71 _ => {}
72 }
73 }
74
75 let doc_path = doc_path.expect("Must specify Markdown path");
76 let doc_key = doc_key.expect("Must specify key");
77
78 let markdown = fs::read_to_string(&doc_path);
79
80 let doc_comment = if !markdown.is_err() {
81 let mut docs_map = HashMap::new();
82 let mut current_key = String::new();
83 let mut current_lines = Vec::new();
84
85 for line in markdown.unwrap().lines() {
86 if let Some(stripped) = line.strip_prefix("# ") {
87 if !current_key.is_empty() {
88 docs_map.insert(current_key.clone(), current_lines.join("\n"));
89 }
90 current_key = stripped.trim().to_string();
91 current_lines = Vec::new();
92 } else if !current_key.is_empty() {
93 current_lines.push(line.trim_end().to_string());
94 }
95 }
96 if !current_key.is_empty() {
97 docs_map.insert(current_key.clone(), current_lines.join("\n"));
98 }
99
100 docs_map
101 .get(&doc_key)
102 .map(String::as_str)
103 .unwrap_or("No documentation found for item.")
104 .to_string()
105 } else {
106 "No documentation found for item.".to_string()
107 };
108
109 let doc_lines: Vec<_> = doc_comment
110 .lines()
111 .map(|line| quote! { #[doc = #line] })
112 .collect();
113
114 let output = match input {
115 Item::Fn(item_fn) => {
116 quote! {
117 #(#doc_lines)*
118 #item_fn
119 }
120 }
121 Item::Struct(item_struct) => {
122 quote! {
123 #(#doc_lines)*
124 #item_struct
125 }
126 }
127 _ => panic!("#[external_doc] can only be applied to functions or structs"),
128 };
129
130 output.into()
131}