From 2e8c3c02401ed37d1eaeb99cf1021cbf2ff61a42 Mon Sep 17 00:00:00 2001 From: faervan Date: Tue, 9 Sep 2025 23:17:40 +0200 Subject: [PATCH 1/7] test: add recursive nesting test --- lib/src/lib.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 8318c92..be44975 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -932,6 +932,34 @@ port = 80 assert!(toml::from_str::(&Node::toml_example()).is_ok()); } + #[test] + fn recursive_nesting() { + #[derive(TomlExample, Default, Debug, Deserialize, PartialEq)] + struct Outer { + #[toml_example(nesting)] + _middle: Middle, + } + #[derive(TomlExample, Default, Debug, Deserialize, PartialEq)] + struct Middle { + #[toml_example(nesting)] + _inner: Inner, + } + #[derive(TomlExample, Default, Debug, Deserialize, PartialEq)] + struct Inner { + _value: usize, + } + let example = Outer::toml_example(); + assert_eq!(toml::from_str::(&example).unwrap(), Outer::default()); + assert_eq!( + example, + r#"[_middle] +[_middle._inner] +_value = 0 + +"# + ); + } + #[test] fn require() { #[derive(TomlExample, Deserialize, Default, PartialEq, Debug)] From d719119a3860ab03ff05ed08891bb1702d535055 Mon Sep 17 00:00:00 2001 From: faervan Date: Sat, 13 Sep 2025 23:36:08 +0200 Subject: [PATCH 2/7] feat: support recursive nesting --- Cargo.toml | 1 + derive/src/lib.rs | 77 ++++++++++++++++++++++++++++++++++++----------- lib/src/traits.rs | 2 +- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index df09b8c..a742b86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "lib", "derive", ] +resolver = "2" [workspace.package] version = "0.15.0" diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 49e949c..c6c7320 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -66,6 +66,39 @@ impl ParsedField { } fn label(&self) -> String { + let label = match self.nesting_format { + Some(NestingFormat::Section(NestingType::Dict)) => { + if self.flatten { + self.default_key() + } else { + format!("{}.{}", self.name, self.default_key()) + } + } + Some(NestingFormat::Prefix) => String::new(), + _ => { + if self.flatten { + String::new() + } else { + self.name.to_string() + } + } + }; + if label.is_empty() { + r#""""#.to_string() + } else { + format!( + " + if label.is_empty() {{ + \"{label}\".to_string() + }} else {{ + label.to_string() + \".\" + \"{label}\" + }} + " + ) + } + } + + fn label_format(&self) -> (&str, &str) { match self.nesting_format { Some(NestingFormat::Section(NestingType::Vec)) => { if self.flatten { @@ -78,22 +111,15 @@ impl ParsedField { ) ) } - self.prefix() + &format!("[[{}]]", self.name) - } - Some(NestingFormat::Section(NestingType::Dict)) => { - self.prefix() - + &if self.flatten { - format!("[{}]", self.default_key()) - } else { - format!("[{}.{}]", self.name, self.default_key()) - } + ("[[", "]]") } - Some(NestingFormat::Prefix) => "".to_string(), + Some(NestingFormat::Section(NestingType::Dict)) => ("[", "]"), + Some(NestingFormat::Prefix) => ("", ""), _ => { if self.flatten { - self.prefix() + ("", "") } else { - self.prefix() + &format!("[{}]", self.name) + ("[", "]") } } } @@ -453,10 +479,15 @@ impl Intermediate { Ok(quote! { impl toml_example::TomlExample for #struct_name { fn toml_example() -> String { - #struct_name::toml_example_with_prefix("", "") + #struct_name::toml_example_with_prefix("", ("", ""), "") } - fn toml_example_with_prefix(label: &str, prefix: &str) -> String { - #struct_doc.to_string() + label + &#field_example_stream + fn toml_example_with_prefix(label: &str, label_format: (&str, &str), prefix: &str) + -> String { + #struct_doc.to_string() + + label_format.0 + + label + + label_format.1 + + &#field_example_stream } } }) @@ -502,10 +533,22 @@ impl Intermediate { field.push_doc_to_string(example); if let Some(ref field_type) = field.ty { example.push_str("\"##.to_string()"); + let (before, after) = field.label_format(); + let label_format = format!( + "(\"{}{before}\", \"{after}{nesting_section_newline}\")", + if field.optional && field.nesting_format != Some(NestingFormat::Prefix) + { + "# " + } else { + "" + } + ); example.push_str(&format!( - " + &{field_type}::toml_example_with_prefix(\"{}{}\", \"{}\")", + " + &{field_type}::toml_example_with_prefix(\ + &{}, {}, \"{}\"\ + )", field.label(), - nesting_section_newline, + label_format, field.prefix() )); example.push_str(" + &r##\""); diff --git a/lib/src/traits.rs b/lib/src/traits.rs index 2193b82..db373cd 100644 --- a/lib/src/traits.rs +++ b/lib/src/traits.rs @@ -5,7 +5,7 @@ use std::path::Path; pub trait TomlExample { /// structure to toml example fn toml_example() -> String; - fn toml_example_with_prefix(label: &str, prefix: &str) -> String; + fn toml_example_with_prefix(label: &str, label_format: (&str, &str), prefix: &str) -> String; fn to_toml_example>(file_name: P) -> std::io::Result<()> { let mut file = File::create(file_name)?; file.write_all(Self::toml_example().as_bytes())?; From fa541d70f745f06eeb8e5b965257e8f381f81331 Mon Sep 17 00:00:00 2001 From: faervan Date: Sun, 14 Sep 2025 00:03:31 +0200 Subject: [PATCH 3/7] docs: add faervan to authors --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a742b86..717fd7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ resolver = "2" [workspace.package] version = "0.15.0" edition = "2021" -authors = ["Antonio Yang "] +authors = ["Antonio Yang ", "faervan "] license = "MIT" description = "A lib help generate toml example" repository = "https://github.com/yanganto/toml-example" From 92505b5951f4a5a1ddca92b84a7115568c6a7519 Mon Sep 17 00:00:00 2001 From: faervan Date: Sun, 14 Sep 2025 01:08:31 +0200 Subject: [PATCH 4/7] test: more complex nest and flattening --- lib/src/lib.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index be44975..6054cd6 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -956,6 +956,63 @@ port = 80 [_middle._inner] _value = 0 +"# + ); + } + + #[test] + fn recursive_nesting_and_flatten() { + #[derive(TomlExample, Default, Debug, Deserialize, PartialEq)] + struct Outer { + #[toml_example(nesting)] + middle: Middle, + #[toml_example(default = false)] + /// Some toggle + flag: bool, + } + #[derive(TomlExample, Default, Debug, Deserialize, PartialEq)] + struct Middle { + #[serde(flatten)] + #[toml_example(nesting)] + /// Values of [Inner] are flattened into [Middle] + inner: Inner, + } + #[derive(TomlExample, Default, Debug, Deserialize, PartialEq)] + struct Inner { + #[toml_example(nesting)] + /// [Extra] is flattened into [Middle] + extra: Extra, + /// `value` is defined below `extra`, but shown above + value: usize, + } + #[derive(TomlExample, Debug, Deserialize, PartialEq)] + #[toml_example(default)] + struct Extra { + name: String, + } + impl Default for Extra { + fn default() -> Self { + Self { + name: String::from("ferris"), + } + } + } + let example = Outer::toml_example(); + assert_eq!(toml::from_str::(&example).unwrap(), Outer::default()); + assert_eq!( + example, + r#"# Some toggle +flag = false + +[middle] +# Values of [Inner] are flattened into [Middle] +# `value` is defined below `extra`, but shown above +value = 0 + +# [Extra] is flattened into [Middle] +[middle.extra] +name = "ferris" + "# ); } From 59ddad180133bc61755ad56a2153b6f4a23d5796 Mon Sep 17 00:00:00 2001 From: faervan Date: Sun, 14 Sep 2025 01:09:44 +0200 Subject: [PATCH 5/7] fix: flatten inside nesting --- derive/src/lib.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index c6c7320..7dacc8d 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -84,7 +84,7 @@ impl ParsedField { } }; if label.is_empty() { - r#""""#.to_string() + String::from("label") } else { format!( " @@ -483,11 +483,12 @@ impl Intermediate { } fn toml_example_with_prefix(label: &str, label_format: (&str, &str), prefix: &str) -> String { - #struct_doc.to_string() - + label_format.0 - + label - + label_format.1 - + &#field_example_stream + let wrapped_label = if label_format.0.is_empty() { + String::new() + } else { + label_format.0.to_string() + label + label_format.1 + }; + #struct_doc.to_string() + &wrapped_label + &#field_example_stream } } }) From 3576e218c53c3d907f1cf5ef3d227950d693db2f Mon Sep 17 00:00:00 2001 From: faervan Date: Sun, 14 Sep 2025 01:26:22 +0200 Subject: [PATCH 6/7] test: don't put flattened items in nested field section --- lib/src/lib.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 6054cd6..0c268a7 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -1124,6 +1124,30 @@ ab3 = "B" assert_eq!(ItemWrapper::toml_example(), Item::toml_example()); } + #[test] + fn flatten_order() { + #[derive(TomlExample)] + struct Outer { + #[toml_example(nesting)] + _nested: Item, + #[toml_example(flatten, nesting)] + _flattened: Item, + } + #[derive(TomlExample)] + struct Item { + _value: String, + } + assert_eq!( + Outer::toml_example(), + r#"_value = "" + +[_nested] +_value = "" + +"# + ); + } + #[test] fn multi_attr_escaping() { #[derive(TomlExample, Deserialize, PartialEq)] From 18242a198234a1a41cc525084dfcb23930a08c2b Mon Sep 17 00:00:00 2001 From: faervan Date: Sun, 14 Sep 2025 01:26:45 +0200 Subject: [PATCH 7/7] fix: flattened items in nested field section --- derive/src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 7dacc8d..2cbfbb0 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -517,18 +517,19 @@ impl Intermediate { let (example, nesting_section_newline) = if field.nesting_format == Some(NestingFormat::Prefix) { (&mut field_example, "") - } else { + } else if field.flatten { ( - &mut nesting_field_example, - if field.flatten - && field.nesting_format - == Some(NestingFormat::Section(NestingType::None)) + &mut field_example, + if field.nesting_format + == Some(NestingFormat::Section(NestingType::None)) { "" } else { "\n" }, ) + } else { + (&mut nesting_field_example, "\n") }; field.push_doc_to_string(example);