use std::collections::BTreeMap;
use crate::path::{path_child_segments, path_matches_pattern};
use crate::{ConfigMetadata, FieldMetadata, MergeStrategy};
pub(super) fn render_toml_comments(path: &str, metadata: &ConfigMetadata, output: &mut String) {
for field in toml_comment_fields(path, metadata) {
for line in toml_comment_doc_lines(field) {
output.push_str("# ");
output.push_str(&line);
output.push('\n');
}
}
for line in toml_effective_comment_lines(path, metadata) {
output.push_str("# ");
output.push_str(&line);
output.push('\n');
}
}
pub(super) fn render_toml_array_table_item_comments(
path: &str,
metadata: &ConfigMetadata,
output: &mut String,
) {
for field in toml_comment_fields(path, metadata) {
for line in toml_comment_doc_lines(field) {
output.push_str("# ");
output.push_str(&line);
output.push('\n');
}
}
for line in toml_effective_comment_lines(path, metadata) {
output.push_str("# ");
output.push_str(&line);
output.push('\n');
}
}
pub(super) fn render_toml_inline_array_item_comments(
path: &str,
metadata: &ConfigMetadata,
output: &mut String,
) {
let mut wildcard = None::<&FieldMetadata>;
let mut indexed = BTreeMap::<Vec<TomlArrayCommentSegment>, &FieldMetadata>::new();
for field in metadata.fields() {
let Some(segments) = path_child_segments(&field.path, path) else {
continue;
};
if segments.as_slice() == ["*"] {
wildcard = Some(field);
continue;
}
if segments.is_empty() {
continue;
}
if let Some(segments) = parse_toml_comment_segments(&segments) {
indexed.insert(segments, field);
}
}
if let Some(field) = wildcard {
for line in toml_comment_lines(field) {
output.push_str("# [*] ");
output.push_str(&line);
output.push('\n');
}
}
for (segments, field) in indexed {
let marker = segments
.iter()
.map(|segment| match segment {
TomlArrayCommentSegment::Wildcard => "[*]".to_owned(),
TomlArrayCommentSegment::Index(index) => format!("[{index}]"),
})
.collect::<String>();
let mut lines = toml_comment_doc_lines(field);
let effective = metadata
.effective_field_for(&field.path)
.unwrap_or_else(|| field.clone());
lines.extend(toml_non_doc_comment_lines(&effective));
for line in lines {
output.push_str("# ");
output.push_str(&marker);
output.push(' ');
output.push_str(&line);
output.push('\n');
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum TomlArrayCommentSegment {
Wildcard,
Index(usize),
}
fn parse_toml_comment_segments(segments: &[&str]) -> Option<Vec<TomlArrayCommentSegment>> {
segments
.iter()
.map(|segment| {
if *segment == "*" {
Some(TomlArrayCommentSegment::Wildcard)
} else {
segment
.parse::<usize>()
.ok()
.map(TomlArrayCommentSegment::Index)
}
})
.collect()
}
fn toml_comment_sort_key(path: &str) -> (usize, usize) {
let segments = path.split('.').filter(|segment| !segment.is_empty());
let total = segments.clone().count();
let specificity = segments.filter(|segment| *segment != "*").count();
(specificity, total)
}
fn toml_comment_fields<'a>(path: &str, metadata: &'a ConfigMetadata) -> Vec<&'a FieldMetadata> {
let mut fields = metadata
.fields()
.iter()
.filter(|field| field.path == path || path_matches_pattern(path, &field.path))
.collect::<Vec<_>>();
fields.sort_by(|left, right| {
toml_comment_sort_key(&left.path)
.cmp(&toml_comment_sort_key(&right.path))
.then_with(|| left.path.cmp(&right.path))
});
fields
}
fn toml_comment_lines(field: &FieldMetadata) -> Vec<String> {
let mut lines = Vec::new();
lines.extend(toml_comment_doc_lines(field));
lines.extend(toml_non_doc_comment_lines(field));
lines
}
fn toml_comment_doc_lines(field: &FieldMetadata) -> Vec<String> {
field
.doc
.as_ref()
.map(|doc| {
doc.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
})
.unwrap_or_default()
}
fn toml_non_doc_comment_lines(field: &FieldMetadata) -> Vec<String> {
let mut lines = Vec::new();
if let Some(env) = &field.env {
lines.push(format!("env: {env}"));
}
if !field.aliases.is_empty() {
lines.push(format!("aliases: {}", field.aliases.join(", ")));
}
if field.has_default {
lines.push("default: provided by serde".to_owned());
}
if field.merge_explicit || field.merge != MergeStrategy::Merge {
lines.push(format!("merge: {}", field.merge));
}
if !field.validations.is_empty() {
lines.push(format!(
"validate: {}",
field
.validations
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ")
));
}
if field.secret {
lines.push("secret: true".to_owned());
}
if let Some(note) = &field.deprecated {
lines.push(format!("deprecated: {note}"));
}
lines
}
fn toml_effective_comment_lines(path: &str, metadata: &ConfigMetadata) -> Vec<String> {
metadata
.effective_field_for(path)
.map(|field| toml_non_doc_comment_lines(&field))
.unwrap_or_default()
}