complete the example-config generator macro
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
parent
b08c1241a8
commit
3396542168
|
@ -1,11 +1,19 @@
|
|||
use std::fmt::Write;
|
||||
use std::{fmt::Write as _, fs::File, io::Write as _};
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::ToTokens;
|
||||
use syn::{Expr, ExprLit, Field, Fields, FieldsNamed, ItemStruct, Lit, Meta, MetaNameValue, Type, TypePath};
|
||||
use syn::{
|
||||
parse::Parser, punctuated::Punctuated, Error, Expr, ExprLit, Field, Fields, FieldsNamed, ItemStruct, Lit, Meta,
|
||||
MetaList, MetaNameValue, Type, TypePath,
|
||||
};
|
||||
|
||||
use crate::{utils::is_cargo_build, Result};
|
||||
|
||||
const UNDOCUMENTED: &str = "# This item is undocumented. Please contribute documentation for it.";
|
||||
const HEADER: &str = "## Conduwuit Configuration\n##\n## THIS FILE IS GENERATED. Changes to documentation and \
|
||||
defaults must\n## be made within the code found at src/core/config/\n";
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub(super) fn example_generator(input: ItemStruct, args: &[Meta]) -> Result<TokenStream> {
|
||||
if is_cargo_build() {
|
||||
|
@ -18,6 +26,12 @@ pub(super) fn example_generator(input: ItemStruct, args: &[Meta]) -> Result<Toke
|
|||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[allow(unused_variables)]
|
||||
fn generate_example(input: &ItemStruct, _args: &[Meta]) -> Result<()> {
|
||||
let mut file = File::create("conduwuit-example.toml")
|
||||
.map_err(|e| Error::new(Span::call_site(), format!("Failed to open config file for generation: {e}")))?;
|
||||
|
||||
file.write_all(HEADER.as_bytes())
|
||||
.expect("written to config file");
|
||||
|
||||
if let Fields::Named(FieldsNamed {
|
||||
named,
|
||||
..
|
||||
|
@ -28,21 +42,143 @@ fn generate_example(input: &ItemStruct, _args: &[Meta]) -> Result<()> {
|
|||
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());
|
||||
let doc = get_doc_comment(field)
|
||||
.unwrap_or_else(|| UNDOCUMENTED.into())
|
||||
.trim_end()
|
||||
.to_owned();
|
||||
|
||||
let doc = if doc.ends_with('#') {
|
||||
format!("{doc}\n")
|
||||
} else {
|
||||
format!("{doc}\n#\n")
|
||||
};
|
||||
|
||||
let default = get_doc_default(field)
|
||||
.or_else(|| get_default(field))
|
||||
.unwrap_or_default();
|
||||
|
||||
let default = if !default.is_empty() {
|
||||
format!(" {default}")
|
||||
} else {
|
||||
default
|
||||
};
|
||||
|
||||
file.write_fmt(format_args!("\n{doc}"))
|
||||
.expect("written to config file");
|
||||
|
||||
file.write_fmt(format_args!("#{ident} ={default}\n"))
|
||||
.expect("written to config file");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_default(field: &Field) -> Option<String> {
|
||||
for attr in &field.attrs {
|
||||
let Meta::List(MetaList {
|
||||
path,
|
||||
tokens,
|
||||
..
|
||||
}) = &attr.meta
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !path
|
||||
.segments
|
||||
.iter()
|
||||
.next()
|
||||
.is_some_and(|s| s.ident == "serde")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(arg) = Punctuated::<Meta, syn::Token![,]>::parse_terminated
|
||||
.parse(tokens.clone().into())
|
||||
.ok()?
|
||||
.iter()
|
||||
.next()
|
||||
.cloned()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match arg {
|
||||
Meta::NameValue(MetaNameValue {
|
||||
value: Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(str),
|
||||
..
|
||||
}),
|
||||
..
|
||||
}) => {
|
||||
match str.value().as_str() {
|
||||
"HashSet::new" | "Vec::new" | "RegexSet::empty" => Some("[]".to_owned()),
|
||||
"true_fn" => return Some("true".to_owned()),
|
||||
_ => return None,
|
||||
};
|
||||
},
|
||||
Meta::Path {
|
||||
..
|
||||
} => return Some("false".to_owned()),
|
||||
_ => return None,
|
||||
};
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn get_doc_default(field: &Field) -> Option<String> {
|
||||
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;
|
||||
};
|
||||
|
||||
let value = token.value();
|
||||
if !value.trim().starts_with("default:") {
|
||||
continue;
|
||||
}
|
||||
|
||||
return value
|
||||
.split_once(':')
|
||||
.map(|(_, v)| v)
|
||||
.map(str::trim)
|
||||
.map(ToOwned::to_owned);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn get_doc_comment(field: &Field) -> Option<String> {
|
||||
let mut out = String::new();
|
||||
for attr in &field.attrs {
|
||||
|
@ -76,7 +212,12 @@ fn get_doc_comment(field: &Field) -> Option<String> {
|
|||
continue;
|
||||
};
|
||||
|
||||
writeln!(&mut out, "# {}", token.value()).expect("wrote to output string buffer");
|
||||
let value = token.value();
|
||||
if value.trim().starts_with("default:") {
|
||||
continue;
|
||||
}
|
||||
|
||||
writeln!(&mut out, "#{value}").expect("wrote to output string buffer");
|
||||
}
|
||||
|
||||
(!out.is_empty()).then_some(out)
|
||||
|
|
Loading…
Reference in New Issue