Skip to content

Commit

Permalink
parse struct doc and allow multiple line doc (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
yanganto committed May 7, 2023
1 parent d17eace commit 18ae054
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 31 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ members = [
]

[workspace.package]
version = "0.3.0"
version = "0.4.0"
edition = "2021"
authors = ["Antonio Yang <yanganto@gmail.com>"]
license = "MIT"
Expand Down
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ Deriving `TomlExample` on a struct will provide `to_example` function help gener
```rust
use toml_example::TomlExample;

/// Config is to arrange something or change the controls on a computer or other device
/// so that it can be used in a particular way
#[derive(TomlExample)]
struct Config {
/// Config.a should be a number
Expand All @@ -25,22 +27,25 @@ struct Config {
/// Config.d is a list of number
d: Vec<usize>,
}
let doc = Config::toml_example();

// doc is String toml example base on the docstring

// # Config.a should be a number
// a = 0
// # Config.b should be a string
// b = ""
// # Optional Config.c is a number
// # c = 0
// # Config.d is a list of number
// # d = [ 0, ]
let example = Config::toml_example();
```

Toml example base on the doc string of each field
```toml
# Config is to arrange something or change the controls on a computer or other device
# so that it can be used in a particular way

# Config.a should be a number
a = 0
# Config.b should be a string
b = ""
# Optional Config.c is a number
# c = 0
# Config.d is a list of number
# d = [ 0, ]
```

## Will do later
- use structure doc for example header
- nesting structure
- use `#[serde(default = "default_resource")]` for example
- function to write example file, `to_toml_example(file_name)`
Expand Down
49 changes: 33 additions & 16 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use proc_macro::TokenStream;
use proc_macro_error::{abort, proc_macro_error};
use quote::quote;
use syn::{
AngleBracketedGenericArguments, AttrStyle::Outer, Expr::Lit, ExprLit, Field, Fields::Named,
GenericArgument, Lit::Str, Meta::NameValue, MetaNameValue, PathArguments, PathSegment, Type,
TypePath,
AngleBracketedGenericArguments, AttrStyle::Outer, Attribute, Expr::Lit, ExprLit, Field,
Fields::Named, GenericArgument, Lit::Str, Meta::NameValue, MetaNameValue, PathArguments,
PathSegment, Type, TypePath,
};

fn default_value(ty: String) -> String {
Expand Down Expand Up @@ -56,11 +56,9 @@ fn parse_type(ty: &Type, default: &mut Option<String>, optional: &mut bool) {
}
}

fn get_default_and_doc_from_field(field: &Field) -> (Option<String>, Option<String>, bool) {
let mut doc = None;
let mut default = None;
let mut optional = false;
for attr in field.attrs.iter() {
fn parse_docs(attrs: &Vec<Attribute>) -> Vec<String> {
let mut docs = Vec::new();
for attr in attrs.iter() {
match (attr.style, &attr.meta) {
(Outer, NameValue(MetaNameValue { path, value, .. })) => {
for seg in path.segments.iter() {
Expand All @@ -69,16 +67,39 @@ fn get_default_and_doc_from_field(field: &Field) -> (Option<String>, Option<Stri
lit: Str(lit_str), ..
}) = value
{
doc = Some(lit_str.value());
docs.push(lit_str.value());
}
}
}
}
_ => (),
}
}
docs
}

fn get_default_and_doc_from_field(field: &Field) -> (Option<String>, Vec<String>, bool) {
let mut default = None;
let mut optional = false;
parse_type(&field.ty, &mut default, &mut optional);
(default.map(|s| s.to_string()), doc, optional)
(
default.map(|s| s.to_string()),
parse_docs(&field.attrs),
optional,
)
}

fn push_doc_string(example: &mut String, docs: Vec<String>, paragraph: bool) {
let has_docs = !docs.is_empty();
for doc in docs.into_iter() {
example.push('#');
example.push_str(&doc);
example.push('\n');
}

if has_docs && paragraph {
example.push('\n');
}
}

#[proc_macro_derive(TomlExample)]
Expand All @@ -87,6 +108,7 @@ pub fn derive_patch(item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::DeriveInput);
let struct_name = &input.ident;
let mut example = String::new();
push_doc_string(&mut example, parse_docs(&input.attrs), true);

let fields = if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &input.data {
fields
Expand All @@ -97,12 +119,7 @@ pub fn derive_patch(item: TokenStream) -> TokenStream {
for f in fields_named.named.iter() {
if let Some(field_name) = f.ident.as_ref().map(|i| i.to_string()) {
let (default, doc_str, optional) = get_default_and_doc_from_field(&f);

if let Some(doc_str) = doc_str {
example.push('#');
example.push_str(&doc_str);
example.push('\n');
}
push_doc_string(&mut example, doc_str, false);

if optional {
example.push_str("# ");
Expand Down
2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ license.workspace = true
readme.workspace = true

[dependencies]
toml-example-derive = { version = "=0.3", path = "../derive" }
toml-example-derive = { version = "=0.4", path = "../derive" }

[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }
Expand Down
27 changes: 27 additions & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,31 @@ c = [ 0, ]
);
assert!(toml::from_str::<Config>(Config::toml_example()).is_ok())
}

#[test]
fn sturct_doc() {
/// Config is to arrange something or change the controls on a computer or other device
/// so that it can be used in a particular way
#[derive(TomlExample, Deserialize, Default, PartialEq, Debug)]
#[allow(dead_code)]
struct Config {
/// Config.a should be a number
/// the number should be greater or equal zero
a: usize,
}
assert_eq!(
Config::toml_example(),
r#"# Config is to arrange something or change the controls on a computer or other device
# so that it can be used in a particular way
# Config.a should be a number
# the number should be greater or equal zero
a = 0
"#
);
assert_eq!(
toml::from_str::<Config>(Config::toml_example()).unwrap(),
Config::default()
)
}
}

0 comments on commit 18ae054

Please sign in to comment.