1use proc_macro2::TokenStream;
4use quote::quote;
5use unsynn::*;
6
7use crate::parse::{FnSig, SyncDocArg, SyncDocInner};
8use crate::path_utils::make_manifest_relative_path;
9
10pub fn syncdoc_impl(
11 args: TokenStream,
12 item: TokenStream,
13) -> core::result::Result<TokenStream, TokenStream> {
14 let syncdoc_args = match parse_syncdoc_args(&mut args.to_token_iter()) {
16 Ok(args) => args,
17 Err(e) => {
18 return Ok(quote! {
20 compile_error!(#e);
21 #item
22 });
23 }
24 };
25
26 let mut item_iter = item.to_token_iter();
28 let func = match parse_simple_function(&mut item_iter) {
29 Ok(func) => func,
30 Err(e) => {
31 return Ok(quote! {
32 compile_error!(#e);
33 #item
34 });
35 }
36 };
37
38 Ok(generate_documented_function(syncdoc_args, func))
39}
40
41pub fn module_doc_impl(args: TokenStream) -> core::result::Result<TokenStream, TokenStream> {
46 let call_site = proc_macro2::Span::call_site();
47 let source_file = call_site
48 .local_file()
49 .ok_or_else(|| {
50 let error = "Could not determine source file location";
51 quote! { compile_error!(#error) }
52 })?
53 .to_string_lossy()
54 .to_string();
55
56 let base_path = if args.is_empty() {
58 crate::config::get_docs_path(&source_file).map_err(|e| {
60 let error = format!("Failed to get docs path from config: {}", e);
61 quote! { compile_error!(#error) }
62 })?
63 } else {
64 let mut args_iter = args.into_token_iter();
66 match parse_syncdoc_args(&mut args_iter) {
67 Ok(parsed_args) => parsed_args.base_path,
68 Err(e) => {
69 let error = format!("Failed to parse module_doc args: {}", e);
70 return Err(quote! { compile_error!(#error) });
71 }
72 }
73 };
74
75 let module_path = crate::path_utils::extract_module_path(&source_file);
77 let doc_path = if module_path.is_empty() {
78 let file_stem = std::path::Path::new(&source_file)
80 .file_stem()
81 .and_then(|s| s.to_str())
82 .unwrap_or("module");
83 format!("{}/{}.md", base_path, file_stem)
84 } else {
85 format!("{}/{}.md", base_path, module_path)
86 };
87
88 let local_file = call_site.local_file().ok_or_else(|| {
90 let error = "Could not find local file";
91 quote! { compile_error!(#error) }
92 })?;
93 let rel_doc_path = make_manifest_relative_path(&doc_path, &local_file);
94
95 Ok(quote! {
97 include_str!(#rel_doc_path)
98 })
99}
100
101#[derive(Debug)]
102struct SyncDocArgs {
103 base_path: String,
104 name: Option<String>,
105 cfg_attr: Option<String>,
106}
107
108struct SimpleFunction {
109 attrs: Vec<TokenStream>,
110 vis: Option<TokenStream>,
111 const_kw: Option<TokenStream>,
112 async_kw: Option<TokenStream>,
113 unsafe_kw: Option<TokenStream>,
114 extern_kw: Option<TokenStream>,
115 fn_name: proc_macro2::Ident,
116 generics: Option<TokenStream>,
117 params: TokenStream,
118 ret_type: Option<TokenStream>,
119 where_clause: Option<TokenStream>,
120 body: TokenStream,
121}
122
123fn parse_syncdoc_args(input: &mut TokenIter) -> core::result::Result<SyncDocArgs, String> {
124 match input.parse::<SyncDocInner>() {
125 Ok(parsed) => {
126 let mut args = SyncDocArgs {
127 base_path: String::new(),
128 name: None,
129 cfg_attr: None,
130 };
131
132 if let Some(arg_list) = parsed.args {
133 for arg in arg_list.0 {
134 match arg.value {
135 SyncDocArg::Path(path_arg) => {
136 args.base_path = path_arg.value.as_str().to_string();
137 }
138 SyncDocArg::Name(name_arg) => {
139 args.name = Some(name_arg.value.as_str().to_string());
140 }
141 SyncDocArg::CfgAttr(cfg_attr_arg) => {
142 args.cfg_attr = Some(cfg_attr_arg.value.as_str().to_string());
143 }
144 }
145 }
146 }
147
148 if args.base_path.is_empty() || args.cfg_attr.is_none() {
149 if args.base_path.is_empty() {
151 let call_site = proc_macro2::Span::call_site();
153 let source_file = call_site
154 .local_file()
155 .ok_or("Could not determine source file location")?
156 .to_string_lossy()
157 .to_string();
158
159 let base_path = crate::config::get_docs_path(&source_file)
160 .map_err(|e| format!("Failed to get docs path from config: {}", e))?;
161
162 let module_path = crate::path_utils::extract_module_path(&source_file);
164 args.base_path = if module_path.is_empty() {
165 base_path
166 } else {
167 format!("{}/{}", base_path, module_path)
168 };
169 }
170
171 if let Ok(cfg) = crate::config::get_cfg_attr() {
173 args.cfg_attr = cfg;
174 }
175 }
176
177 Ok(args)
178 }
179 Err(e) => Err(format!("Failed to parse syncdoc args: {}", e)),
180 }
181}
182
183fn parse_simple_function(input: &mut TokenIter) -> core::result::Result<SimpleFunction, String> {
184 match input.parse::<FnSig>() {
185 Ok(parsed) => {
186 let attrs = if let Some(attr_list) = parsed.attributes {
188 attr_list
189 .0
190 .into_iter()
191 .map(|attr| {
192 let mut tokens = TokenStream::new();
193 unsynn::ToTokens::to_tokens(&attr, &mut tokens);
194 tokens
195 })
196 .collect()
197 } else {
198 Vec::new()
199 };
200
201 let vis = parsed.visibility.map(|v| {
203 let mut tokens = TokenStream::new();
204 quote::ToTokens::to_tokens(&v, &mut tokens);
205 tokens
206 });
207
208 let const_kw = parsed.const_kw.map(|k| {
210 let mut tokens = TokenStream::new();
211 unsynn::ToTokens::to_tokens(&k, &mut tokens);
212 tokens
213 });
214
215 let async_kw = parsed.async_kw.map(|k| {
217 let mut tokens = TokenStream::new();
218 unsynn::ToTokens::to_tokens(&k, &mut tokens);
219 tokens
220 });
221
222 let unsafe_kw = parsed.unsafe_kw.map(|k| {
224 let mut tokens = TokenStream::new();
225 unsynn::ToTokens::to_tokens(&k, &mut tokens);
226 tokens
227 });
228
229 let extern_kw = parsed.extern_kw.map(|k| {
231 let mut tokens = TokenStream::new();
232 unsynn::ToTokens::to_tokens(&k, &mut tokens);
233 tokens
234 });
235
236 let fn_name = parsed.name;
237
238 let generics = parsed.generics.map(|g| {
239 let mut tokens = TokenStream::new();
240 unsynn::ToTokens::to_tokens(&g, &mut tokens);
241 tokens
242 });
243
244 let mut params = TokenStream::new();
245 unsynn::ToTokens::to_tokens(&parsed.params, &mut params);
246
247 let ret_type = parsed.return_type.map(|rt| {
248 let mut tokens = TokenStream::new();
249 unsynn::ToTokens::to_tokens(&rt, &mut tokens);
250 tokens
251 });
252
253 let where_clause = parsed.where_clause.map(|wc| {
254 let mut tokens = TokenStream::new();
255 unsynn::ToTokens::to_tokens(&wc, &mut tokens);
256 tokens
257 });
258
259 let mut body = TokenStream::new();
260 unsynn::ToTokens::to_tokens(&parsed.body, &mut body);
261
262 Ok(SimpleFunction {
263 attrs,
264 vis,
265 const_kw,
266 async_kw,
267 unsafe_kw,
268 extern_kw,
269 fn_name,
270 generics,
271 params,
272 ret_type,
273 where_clause,
274 body,
275 })
276 }
277 Err(e) => Err(format!("Failed to parse function: {}", e)),
278 }
279}
280
281fn generate_documented_function(args: SyncDocArgs, func: SimpleFunction) -> TokenStream {
282 let SimpleFunction {
283 attrs,
284 vis,
285 const_kw,
286 async_kw,
287 unsafe_kw,
288 extern_kw,
289 fn_name,
290 generics,
291 params,
292 ret_type,
293 where_clause,
294 body,
295 } = func;
296
297 let doc_file_name = args.name.unwrap_or_else(|| fn_name.to_string());
299 let doc_path = if args.base_path.ends_with(".md") {
300 args.base_path
302 } else {
303 format!("{}/{}.md", args.base_path, doc_file_name)
305 };
306
307 let call_site = proc_macro2::Span::call_site();
309 let local_file = call_site.local_file().expect("Could not find local file");
310 let rel_doc_path = make_manifest_relative_path(&doc_path, &local_file);
311
312 let vis_tokens = vis.unwrap_or_default();
314 let const_tokens = const_kw.unwrap_or_default();
315 let async_tokens = async_kw.unwrap_or_default();
316 let unsafe_tokens = unsafe_kw.unwrap_or_default();
317 let extern_tokens = extern_kw.unwrap_or_default();
318 let generics_tokens = generics.unwrap_or_default();
319 let ret_tokens = ret_type.unwrap_or_default();
320 let where_tokens = where_clause.unwrap_or_default();
321
322 let doc_attr = if let Some(cfg_value) = args.cfg_attr {
324 let cfg_ident = proc_macro2::Ident::new(&cfg_value, proc_macro2::Span::call_site());
325 quote! { #[cfg_attr(#cfg_ident, doc = include_str!(#rel_doc_path))] }
326 } else {
327 quote! { #[doc = include_str!(#rel_doc_path)] }
328 };
329
330 quote! {
331 #(#attrs)*
332 #doc_attr
333 #vis_tokens #const_tokens #async_tokens #unsafe_tokens #extern_tokens fn #fn_name #generics_tokens #params #ret_tokens #where_tokens #body
334 }
335}
336
337pub fn inject_doc_attr(
339 doc_path: String,
340 cfg_attr: Option<String>,
341 item: TokenStream,
342) -> TokenStream {
343 let call_site = proc_macro2::Span::call_site();
345 let local_file = call_site.local_file().expect("Could not find local file");
346 let rel_doc_path = make_manifest_relative_path(&doc_path, &local_file);
347 if let Some(cfg_value) = cfg_attr {
348 let cfg_ident = proc_macro2::Ident::new(&cfg_value, proc_macro2::Span::call_site());
349 quote! {
350 #[cfg_attr(#cfg_ident, doc = include_str!(#rel_doc_path))]
351 #item
352 }
353 } else {
354 quote! {
355 #[doc = include_str!(#rel_doc_path)]
356 #item
357 }
358 }
359}