diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index 126b3123..64e1c9ba 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -5,6 +5,7 @@ use std::{ path::PathBuf, }; +use conduit_macros::config_example_generator; use either::{ Either, Either::{Left, Right}, @@ -27,6 +28,7 @@ pub mod check; pub mod proxy; /// all the config options for conduwuit +#[config_example_generator] #[derive(Clone, Debug, Deserialize)] #[allow(clippy::struct_excessive_bools)] pub struct Config { diff --git a/src/macros/config.rs b/src/macros/config.rs new file mode 100644 index 00000000..6d29c21f --- /dev/null +++ b/src/macros/config.rs @@ -0,0 +1,98 @@ +use std::fmt::Write; + +use proc_macro::TokenStream; +use quote::ToTokens; +use syn::{Expr, ExprLit, Field, Fields, FieldsNamed, ItemStruct, Lit, Meta, MetaNameValue, Type, TypePath}; + +use crate::{utils::is_cargo_build, Result}; + +#[allow(clippy::needless_pass_by_value)] +pub(super) fn example_generator(input: ItemStruct, args: &[Meta]) -> Result { + if is_cargo_build() { + generate_example(&input, args)?; + } + + Ok(input.to_token_stream().into()) +} + +#[allow(clippy::needless_pass_by_value)] +#[allow(unused_variables)] +fn generate_example(input: &ItemStruct, _args: &[Meta]) -> Result<()> { + if let Fields::Named(FieldsNamed { + named, + .. + }) = &input.fields + { + for field in named { + let Some(ident) = &field.ident else { + continue; + }; + + let Some(doc) = get_doc_comment(field) else { + continue; + }; + + let Some(type_name) = get_type_name(field) else { + continue; + }; + + //println!("{:?} {type_name:?}\n{doc}", ident.to_string()); + } + } + + Ok(()) +} + +fn get_doc_comment(field: &Field) -> Option { + let mut out = String::new(); + for attr in &field.attrs { + let Meta::NameValue(MetaNameValue { + path, + value, + .. + }) = &attr.meta + else { + continue; + }; + + if !path + .segments + .iter() + .next() + .is_some_and(|s| s.ident == "doc") + { + continue; + } + + let Expr::Lit(ExprLit { + lit, + .. + }) = &value + else { + continue; + }; + + let Lit::Str(token) = &lit else { + continue; + }; + + writeln!(&mut out, "# {}", token.value()).expect("wrote to output string buffer"); + } + + (!out.is_empty()).then_some(out) +} + +fn get_type_name(field: &Field) -> Option { + let Type::Path(TypePath { + path, + .. + }) = &field.ty + else { + return None; + }; + + path.segments + .iter() + .next() + .map(|segment| segment.ident.to_string()) +} diff --git a/src/macros/mod.rs b/src/macros/mod.rs index d32cda71..1aa1e24f 100644 --- a/src/macros/mod.rs +++ b/src/macros/mod.rs @@ -1,5 +1,6 @@ mod admin; mod cargo; +mod config; mod debug; mod implement; mod refutable; @@ -9,7 +10,7 @@ mod utils; use proc_macro::TokenStream; use syn::{ parse::{Parse, Parser}, - parse_macro_input, Error, Item, ItemConst, ItemEnum, ItemFn, Meta, + parse_macro_input, Error, Item, ItemConst, ItemEnum, ItemFn, ItemStruct, Meta, }; pub(crate) type Result = std::result::Result; @@ -47,6 +48,11 @@ pub fn implement(args: TokenStream, input: TokenStream) -> TokenStream { attribute_macro::(args, input, implement::implement) } +#[proc_macro_attribute] +pub fn config_example_generator(args: TokenStream, input: TokenStream) -> TokenStream { + attribute_macro::(args, input, config::example_generator) +} + fn attribute_macro(args: TokenStream, input: TokenStream, func: F) -> TokenStream where F: Fn(I, &[Meta]) -> Result,