initial example-config generator

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk 2024-09-08 22:17:02 +00:00
parent 370db5ccea
commit ba253738dc
3 changed files with 107 additions and 1 deletions

View file

@ -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 {

98
src/macros/config.rs Normal file
View file

@ -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<TokenStream> {
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<String> {
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<String> {
let Type::Path(TypePath {
path,
..
}) = &field.ty
else {
return None;
};
path.segments
.iter()
.next()
.map(|segment| segment.ident.to_string())
}

View file

@ -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<T> = std::result::Result<T, Error>;
@ -47,6 +48,11 @@ pub fn implement(args: TokenStream, input: TokenStream) -> TokenStream {
attribute_macro::<ItemFn, _>(args, input, implement::implement)
}
#[proc_macro_attribute]
pub fn config_example_generator(args: TokenStream, input: TokenStream) -> TokenStream {
attribute_macro::<ItemStruct, _>(args, input, config::example_generator)
}
fn attribute_macro<I, F>(args: TokenStream, input: TokenStream, func: F) -> TokenStream
where
F: Fn(I, &[Meta]) -> Result<TokenStream>,