use std::any::type_name;
use documented::{Documented, DocumentedFields};
use toml_edit::{ArrayOfTables, Decor, Item, RawString, Table};
use tracing::warn;
use crate::error::{ConfigError, Result};
pub fn append_docs_as_toml_comments(decor: &mut Decor, docs: &str) {
let old_prefix = decor.prefix().and_then(RawString::as_str);
let last_line = old_prefix.and_then(|prefix| prefix.lines().last());
let comments = docs
.lines()
.map(|l| {
if l.is_empty() {
"#\n".into()
} else {
format!("# {l}\n")
}
})
.collect();
let new_prefix = match (old_prefix, last_line) {
(None | Some(""), None) => comments,
(None, Some(_)) => unreachable!(),
(Some(_), None) => unreachable!(),
(Some(prefix), Some("")) => format!("{prefix}{comments}"),
(Some(prefix), Some(_)) => format!("{prefix}#\n{comments}"),
};
decor.set_prefix(new_prefix);
}
pub fn annotate_toml_table<T>(table: &mut Table, is_root: bool) -> Result<()>
where
T: Documented + DocumentedFields,
{
if !is_root {
append_docs_as_toml_comments(table.decor_mut(), T::DOCS);
}
for (mut key_mut, value_item) in table.iter_mut() {
let key_str = key_mut.get();
match T::get_field_docs(key_str) {
Ok(docs) => {
match value_item {
Item::None => {
return Err(ConfigError::UnexpectedTomlItem(key_str.into()));
}
Item::Value(_) => append_docs_as_toml_comments(key_mut.leaf_decor_mut(), docs),
Item::Table(sub_table) => {
append_docs_as_toml_comments(sub_table.decor_mut(), docs)
}
Item::ArrayOfTables(array) => {
let first_table = array
.iter_mut()
.next()
.expect("Array of table should not be empty");
append_docs_as_toml_comments(first_table.decor_mut(), docs);
}
}
}
Err(_) => {
warn!(
"Field '{}' found in TOML but not in struct '{}' for documentation lookup, or it's a complex case like flatten.",
key_str,
type_name::<T>()
);
}
}
}
Ok(())
}
pub fn annotate_toml_array_of_tables<T>(array: &mut ArrayOfTables) -> Result<()>
where
T: Documented + DocumentedFields,
{
if let Some(first_table) = array.iter_mut().next() {
annotate_toml_table::<T>(first_table, false)
.map_err(|err| ConfigError::AnnotateFirstTable(err.to_string()))?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use toml_edit::Decor;
use super::*;
use crate::config::Config;
#[test]
fn test_append_docs_as_toml_comments() {
let mut decor = Decor::new("", "");
append_docs_as_toml_comments(&mut decor, "Test documentation");
let prefix = decor.prefix().and_then(|p| p.as_str()).unwrap();
assert!(prefix.contains("# Test documentation"));
}
#[test]
fn test_append_docs_multiline() {
let mut decor = Decor::new("", "");
append_docs_as_toml_comments(&mut decor, "Line 1\nLine 2\nLine 3");
let prefix = decor.prefix().and_then(|p| p.as_str()).unwrap();
assert!(prefix.contains("# Line 1"));
assert!(prefix.contains("# Line 2"));
assert!(prefix.contains("# Line 3"));
}
#[test]
fn test_append_docs_empty_lines() {
let mut decor = Decor::new("", "");
append_docs_as_toml_comments(&mut decor, "Line 1\n\nLine 2");
let prefix = decor.prefix().and_then(|p| p.as_str()).unwrap();
assert!(prefix.contains("#\n"));
}
#[test]
fn test_annotate_toml_document() {
let config = Config::default_config::<&str>(&[]);
let doc = config.to_annotated_document();
assert!(doc.is_ok());
let doc = doc.unwrap();
assert!(doc.to_string().contains("#"));
}
}