diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2021-05-28 20:43:24 +0100 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2021-05-28 21:06:45 +0100 |
commit | d6c0684fb5e0f902a9009fe02416b32fbe52ef57 (patch) | |
tree | e7604c506c336db6cfbd6cd96d34b1b3c81c3f0d /subplotlib-derive | |
parent | 55e81a7bf6d671f29602f6a09905b596454044ca (diff) | |
download | subplot-d6c0684fb5e0f902a9009fe02416b32fbe52ef57.tar.gz |
subplotlib-derive: Propagate documentation more cleanly
Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
Diffstat (limited to 'subplotlib-derive')
-rw-r--r-- | subplotlib-derive/src/lib.rs | 75 |
1 files changed, 73 insertions, 2 deletions
diff --git a/subplotlib-derive/src/lib.rs b/subplotlib-derive/src/lib.rs index e5b8ab6..160643d 100644 --- a/subplotlib-derive/src/lib.rs +++ b/subplotlib-derive/src/lib.rs @@ -1,6 +1,9 @@ use proc_macro::TokenStream; use proc_macro2::Span; -use syn::{parse_macro_input, parse_quote, Error, FnArg, Ident, ItemFn, Pat, ReturnType, Type}; +use syn::{ + parse_macro_input, parse_quote, Error, FnArg, Ident, ItemFn, Pat, PathArguments, ReturnType, + Type, +}; use quote::quote; @@ -42,6 +45,27 @@ fn ty_is_scenariocontext(ty: &Type) -> bool { } #[throws(Error)] +fn ty_as_path(ty: &Type) -> String { + if let Type::Path(p) = ty { + let mut ret = String::new(); + let mut colons = p.path.leading_colon.is_some(); + for seg in &p.path.segments { + if !matches!(seg.arguments, PathArguments::None) { + throw!(Error::new_spanned(seg, "unexpected path segment arguments")); + } + if colons { + ret.push_str("::"); + } + colons = true; + ret.push_str(&seg.ident.to_string()); + } + ret + } else { + throw!(Error::new_spanned(ty, "expected a type path")); + } +} + +#[throws(Error)] fn check_step_declaration(step: &ItemFn) { // Step functions must be declared very simply as: // fn stepfunctionname(context: &mut Context) @@ -174,6 +198,12 @@ fn process_step(mut input: ItemFn) -> proc_macro2::TokenStream { input.attrs.retain(|f| !f.path.is_ident("context")); + let docs: Vec<_> = input + .attrs + .iter() + .filter(|attr| attr.path.is_ident("doc")) + .collect(); + let fields = input .sig .inputs @@ -223,6 +253,7 @@ fn process_step(mut input: ItemFn) -> proc_macro2::TokenStream { #[allow(non_camel_case_types)] #[allow(unused)] #[derive(Default)] + #[doc(hidden)] pub struct Builder { #(#structfields),* } @@ -335,7 +366,44 @@ fn process_step(mut input: ItemFn) -> proc_macro2::TokenStream { } }; + let call_docs = { + let mut contextattrs = String::new(); + let outer_ctx = if ty_is_scenariocontext(&contexttype) { + None + } else { + Some(&contexttype) + }; + for context in outer_ctx.into_iter().chain(contexts.iter()) { + contextattrs.push_str(&format!("\n #[context({:?})]", ty_as_path(context)?)); + } + let func_args: Vec<_> = fields + .iter() + .map(|(ident, _)| format!("{}", ident)) + .collect(); + let func_args = func_args.join(", "); + format!( + r#" + Call [this step][self] function from another. + + If you want to call this step function from another, you will + need to do something like this: + + ```rust,ignore + #[step]{contextattrs} + fn defer_to_{stepname}(context: &ScenarioContext) {{ + //... + {stepname}::call(context, {func_args})?; + // ... + }} + ``` + "#, + stepname = stepname, + contextattrs = contextattrs, + func_args = func_args, + ) + }; let ret = quote! { + #(#docs)* #vis mod #stepname { use super::*; pub(crate) use super::#contexttype; @@ -345,17 +413,20 @@ fn process_step(mut input: ItemFn) -> proc_macro2::TokenStream { #[throws(StepError)] #[allow(unused)] // It's okay for step functions to not be used + #[doc(hidden)] #input + #[doc = #call_docs] pub fn call(___context___: &ScenarioContext, #(#inputargs),*) -> StepResult { #call_body } #[allow(unused_variables)] + #[doc(hidden)] pub fn register_contexts(scenario: &Scenario) { #register_fn_body } - } + } }; ret |