use std::fmt;
use crate::meta::{Meta, FieldKind, LeafKind, Expr};
pub(crate) trait Formatter {
type ExprPrinter: fmt::Display + From<&'static Expr>;
fn buffer(&mut self) -> &mut String;
fn finish(self) -> String;
fn comment(&mut self, comment: impl fmt::Display);
fn disabled_field(&mut self, name: &'static str, value: Option<&'static Expr>);
fn start_nested(&mut self, name: &'static str, doc: &[&'static str]);
fn end_nested(&mut self);
fn start_main(&mut self) {}
fn end_main(&mut self) {}
fn env_comment(&mut self, env_key: &'static str) {
self.comment(format_args!(" Can also be specified via environment variable `{env_key}`."));
}
fn default_or_required_comment(&mut self, default_value: Option<&'static Expr>) {
match default_value {
None => self.comment(format_args!(" Required! This value must be specified.")),
Some(v) => self.comment(format_args!(" Default value: {}", Self::ExprPrinter::from(v))),
}
}
fn make_gap(&mut self, size: u8) {
if !self.buffer().is_empty() {
let num_trailing_newlines = self.buffer().chars()
.rev()
.take_while(|c| *c == '\n')
.count();
let newlines_needed = (size as usize + 1).saturating_sub(num_trailing_newlines);
let buffer = self.buffer();
for _ in 0..newlines_needed {
buffer.push('\n');
}
}
}
fn assert_single_trailing_newline(&mut self) {
let buffer = self.buffer();
if buffer.ends_with('\n') {
while buffer.ends_with("\n\n") {
buffer.pop();
}
} else {
buffer.push('\n');
}
}
}
#[non_exhaustive]
pub struct FormatOptions {
pub comments: bool,
pub env_keys: bool,
pub leaf_field_gap: Option<u8>,
pub nested_field_gap: u8,
}
impl FormatOptions {
fn leaf_field_gap(&self) -> u8 {
self.leaf_field_gap.unwrap_or(self.comments as u8)
}
}
impl Default for FormatOptions {
fn default() -> Self {
Self {
comments: true,
env_keys: true,
leaf_field_gap: None,
nested_field_gap: 1,
}
}
}
pub(crate) fn format(meta: &Meta, out: &mut impl Formatter, options: FormatOptions) {
if options.comments {
meta.doc.iter().for_each(|doc| out.comment(doc));
}
out.start_main();
format_impl(out, meta, &options);
out.end_main();
out.assert_single_trailing_newline();
}
fn format_impl(out: &mut impl Formatter, meta: &Meta, options: &FormatOptions) {
let leaf_fields = meta.fields.iter().filter_map(|f| match &f.kind {
FieldKind::Leaf { kind, env } => Some((f, kind, env)),
_ => None,
});
let mut emitted_anything = false;
for (i, (field, kind, env)) in leaf_fields.enumerate() {
emitted_anything = true;
if i > 0 {
out.make_gap(options.leaf_field_gap());
}
let mut emitted_something = false;
macro_rules! empty_sep_doc_line {
() => {
if emitted_something {
out.comment("");
}
};
}
if options.comments {
field.doc.iter().for_each(|doc| out.comment(doc));
emitted_something = !field.doc.is_empty();
if let Some(env) = env {
empty_sep_doc_line!();
out.env_comment(env);
}
}
match kind {
LeafKind::Optional => out.disabled_field(field.name, None),
LeafKind::Required { default } => {
if options.comments {
empty_sep_doc_line!();
out.default_or_required_comment(default.as_ref())
}
out.disabled_field(field.name, default.as_ref());
}
}
}
let nested_fields = meta.fields.iter().filter_map(|f| match &f.kind {
FieldKind::Nested { meta } => Some((f, meta)),
_ => None,
});
for (field, meta) in nested_fields {
if emitted_anything {
out.make_gap(options.nested_field_gap);
}
emitted_anything = true;
let comments = if options.comments { field.doc } else { &[] };
out.start_nested(field.name, comments);
format_impl(out, meta, options);
out.end_nested();
}
}