use std::collections::{HashMap, HashSet};
pub fn format_with_comments(
value: &toml::Value,
comments: &HashMap<String, String>,
all_fields: &HashSet<String>,
optional_fields: &HashSet<String>,
path: &str,
) -> String {
match value {
toml::Value::Table(table) => {
let mut result = String::new();
let mut inline_keys = Vec::new();
let mut nested_tables = Vec::new();
let mut array_tables = Vec::new();
for (key, val) in table {
match val {
toml::Value::Table(_) => nested_tables.push(key),
toml::Value::Array(arr)
if !arr.is_empty() && matches!(arr[0], toml::Value::Table(_)) =>
{
array_tables.push(key);
}
_ => inline_keys.push(key),
}
}
for key in inline_keys {
let val = &table[key];
let current_path = build_path(path, key);
append_comment(&mut result, comments, ¤t_path);
result.push_str(&format!("{} = {}\n", key, format_value(val, comments)));
}
for field in all_fields {
let key = if path.is_empty() {
if field.contains('.') {
continue; }
field.as_str()
} else {
let prefix = format!("{}.", path);
if let Some(stripped) = field.strip_prefix(&prefix) {
stripped
} else {
continue; }
};
if !key.contains('.') && optional_fields.contains(field) && !table.contains_key(key)
{
append_comment(&mut result, comments, field);
result.push_str(&format!("# {} = ...\n", key));
}
}
for key in nested_tables {
let val = &table[key];
let current_path = build_path(path, key);
append_section_separator(&mut result);
append_comment(&mut result, comments, ¤t_path);
result.push_str(&format!("[{}]\n", current_path));
result.push_str(&format_with_comments(
val,
comments,
all_fields,
optional_fields,
¤t_path,
));
}
for key in array_tables {
let val = &table[key];
let current_path = build_path(path, key);
if let toml::Value::Array(arr) = val {
for item in arr {
append_section_separator(&mut result);
append_comment(&mut result, comments, ¤t_path);
result.push_str(&format!("[[{}]]\n", current_path));
result.push_str(&format_with_comments(
item,
comments,
all_fields,
optional_fields,
¤t_path,
));
}
}
}
result
}
_ => String::new(),
}
}
fn build_path(path: &str, key: &str) -> String {
if path.is_empty() {
key.to_string()
} else {
format!("{}.{}", path, key)
}
}
fn append_comment(result: &mut String, comments: &HashMap<String, String>, path: &str) {
if let Some(comment) = comments.get(path) {
let normalized = comment.replace("\n\n", "\n");
for line in normalized.lines() {
if line.is_empty() {
result.push_str("#\n");
} else {
result.push_str(&format!("# {}\n", line));
}
}
}
}
fn append_section_separator(result: &mut String) {
if !result.is_empty() {
result.push('\n');
}
}
fn format_value(value: &toml::Value, comments: &HashMap<String, String>) -> String {
match value {
toml::Value::String(s) => {
if s.contains('\n') {
format!("\"\"\"{}\"\"\"", s)
} else {
let escaped = s
.replace('\\', "\\\\")
.replace('\r', "\\r")
.replace('\t', "\\t")
.replace('"', "\\\"");
format!("\"{}\"", escaped)
}
}
toml::Value::Integer(i) => i.to_string(),
toml::Value::Float(f) => f.to_string(),
toml::Value::Boolean(b) => b.to_string(),
toml::Value::Array(arr) => {
if arr.len() < 5 && arr.iter().all(|v| is_scalar(v)) {
let items: Vec<String> = arr.iter().map(|v| format_value(v, comments)).collect();
format!("[{}]", items.join(", "))
} else if arr.iter().all(|v| is_scalar(v)) {
let items: Vec<String> = arr.iter().map(|v| format_value(v, comments)).collect();
format!("[\n {},\n]", items.join(",\n "))
} else {
format!(
"[{}]",
arr.iter()
.map(|v| format_value(v, comments))
.collect::<Vec<_>>()
.join(", ")
)
}
}
toml::Value::Table(table) => {
if table.len() < 5
&& table.values().all(|v| is_scalar(v))
&& !has_comments(table, comments)
{
let items: Vec<String> = table
.iter()
.map(|(k, v)| format!("{} = {}", k, format_value(v, comments)))
.collect();
format!("{{ {} }}", items.join(", "))
} else {
toml::to_string(value).unwrap().trim().to_string()
}
}
_ => toml::to_string(value).unwrap().trim().to_string(),
}
}
fn is_scalar(value: &toml::Value) -> bool {
!matches!(value, toml::Value::Table(_) | toml::Value::Array(_))
}
fn has_comments(
table: &toml::map::Map<String, toml::Value>,
comments: &HashMap<String, String>,
) -> bool {
table.keys().any(|k| comments.contains_key(k))
}