Include documentation in printed config files (#41)

* Start work on printing toml config with comments

* WIP: toml_config: extract default values for fields

* WIP: toml_config: handle single-level nested structs

* WIP: toml_config: improve comment handling, std type trait impls

* WIP: toml_config: add Private trait, improve comment handling, clean up

* toml_config: fix default value bug; improve tests

* Use toml_config in all applicable crates; add toml_config enum support

* toml_config: improve comments

* toml_config_derive: support enum comments

* Improve config comments for udp, cli_helpers, common

* Improve config comments

* Add tests for Config struct TomlConfig implementations

* Improve Config comments

* Improve Config comments

* ws, http: add config comments for tls cert and private key lines

* small fixes to toml_config and toml_config_derive

* Run cargo fmt

* Fix typo in several config comments

* Update README

* Update README
This commit is contained in:
Joakim Frostegård 2021-12-26 11:33:27 +01:00 committed by GitHub
parent d694785244
commit a208775104
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 581 additions and 92 deletions

View file

@ -0,0 +1,177 @@
use proc_macro2::{TokenStream, TokenTree};
use quote::quote;
use syn::{parse_macro_input, Attribute, Data, DataStruct, DeriveInput, Fields, Ident, Type};
#[proc_macro_derive(TomlConfig)]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let comment = extract_comment_string(input.attrs);
let ident = input.ident;
match input.data {
Data::Struct(struct_data) => {
let mut output_stream = quote! {
let mut output = String::new();
};
extract_from_struct(ident.clone(), struct_data, &mut output_stream);
proc_macro::TokenStream::from(quote! {
impl ::toml_config::TomlConfig for #ident {
fn default_to_string() -> String {
let mut output = String::new();
let comment: Option<String> = #comment;
if let Some(comment) = comment {
output.push_str(&comment);
output.push('\n');
}
let body = {
#output_stream
output
};
output.push_str(&body);
output
}
}
impl ::toml_config::__private::Private for #ident {
fn __to_string(&self, comment: Option<String>, field_name: String) -> String {
let mut output = String::new();
output.push('\n');
if let Some(comment) = comment {
output.push_str(&comment);
}
output.push_str(&format!("[{}]\n", field_name));
let body = {
#output_stream
output
};
output.push_str(&body);
output
}
}
})
}
Data::Enum(_) => proc_macro::TokenStream::from(quote! {
impl ::toml_config::__private::Private for #ident {
fn __to_string(&self, comment: Option<String>, field_name: String) -> String {
let mut output = String::new();
let wrapping_comment: Option<String> = #comment;
if let Some(comment) = wrapping_comment {
output.push_str(&comment);
}
if let Some(comment) = comment {
output.push_str(&comment);
}
let value = match ::toml_config::toml::ser::to_string(self) {
Ok(value) => value,
Err(err) => panic!("Couldn't serialize enum to toml: {:#}", err),
};
output.push_str(&format!("{} = {}\n", field_name, value));
output
}
}
}),
Data::Union(_) => panic!("Unions are not supported"),
}
}
fn extract_from_struct(
struct_ty_ident: Ident,
struct_data: DataStruct,
output_stream: &mut TokenStream,
) {
let fields = if let Fields::Named(fields) = struct_data.fields {
fields
} else {
panic!("Fields are not named");
};
output_stream.extend(::std::iter::once(quote! {
let struct_default = #struct_ty_ident::default();
}));
for field in fields.named.into_iter() {
let ident = field.ident.expect("Encountered unnamed field");
let ident_string = format!("{}", ident);
let comment = extract_comment_string(field.attrs);
if let Type::Path(path) = field.ty {
output_stream.extend(::std::iter::once(quote! {
{
let comment: Option<String> = #comment;
let field_default: #path = struct_default.#ident;
let s: String = ::toml_config::__private::Private::__to_string(
&field_default,
comment,
#ident_string.to_string()
);
output.push_str(&s);
}
}));
}
}
}
fn extract_comment_string(attrs: Vec<Attribute>) -> TokenStream {
let mut output = String::new();
for attr in attrs.into_iter() {
let path_ident = if let Some(path_ident) = attr.path.get_ident() {
path_ident
} else {
continue;
};
if format!("{}", path_ident) != "doc" {
continue;
}
for token_tree in attr.tokens {
match token_tree {
TokenTree::Literal(literal) => {
let mut comment = format!("{}", literal);
// Strip leading and trailing quotation marks
comment.remove(comment.len() - 1);
comment.remove(0);
// Add toml comment indicator
comment.insert(0, '#');
output.push_str(&comment);
output.push('\n');
}
_ => {}
}
}
}
if output.is_empty() {
quote! {
None
}
} else {
quote! {
Some(#output.to_string())
}
}
}