summaryrefslogtreecommitdiff
path: root/subplotlib-derive
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2021-05-28 20:43:24 +0100
committerDaniel Silverstone <dsilvers@digital-scurf.org>2021-05-28 21:06:45 +0100
commitd6c0684fb5e0f902a9009fe02416b32fbe52ef57 (patch)
treee7604c506c336db6cfbd6cd96d34b1b3c81c3f0d /subplotlib-derive
parent55e81a7bf6d671f29602f6a09905b596454044ca (diff)
downloadsubplot-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.rs75
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