use std::fmt::{self, Write};
use crate::{
Config,
template::{self, Formatter},
meta::Expr,
};
#[non_exhaustive]
pub struct FormatOptions {
pub indent: u8,
pub general: template::FormatOptions,
}
impl Default for FormatOptions {
fn default() -> Self {
Self {
indent: 2,
general: Default::default(),
}
}
}
pub fn template<C: Config>(options: FormatOptions) -> String {
let mut out = Json5Formatter::new(&options);
template::format(&C::META, &mut out, options.general);
out.finish()
}
struct Json5Formatter {
indent: u8,
buffer: String,
depth: u8,
}
impl Json5Formatter {
fn new(options: &FormatOptions) -> Self {
Self {
indent: options.indent,
buffer: String::new(),
depth: 0,
}
}
fn emit_indentation(&mut self) {
let num_spaces = self.depth as usize * self.indent as usize;
write!(self.buffer, "{: <1$}", "", num_spaces).unwrap();
}
fn dec_depth(&mut self) {
self.depth = self
.depth
.checked_sub(1)
.expect("formatter bug: ended too many nested");
}
}
impl Formatter for Json5Formatter {
type ExprPrinter = PrintExpr;
fn buffer(&mut self) -> &mut String {
&mut self.buffer
}
fn comment(&mut self, comment: impl fmt::Display) {
self.emit_indentation();
writeln!(self.buffer, "//{comment}").unwrap();
}
fn disabled_field(&mut self, name: &str, value: Option<&'static Expr>) {
match value.map(PrintExpr) {
None => self.comment(format_args!("{name}: ,")),
Some(v) => self.comment(format_args!("{name}: {v},")),
};
}
fn start_nested(&mut self, name: &'static str, doc: &[&'static str]) {
doc.iter().for_each(|doc| self.comment(doc));
self.emit_indentation();
writeln!(self.buffer, "{name}: {{").unwrap();
self.depth += 1;
}
fn end_nested(&mut self) {
self.dec_depth();
self.emit_indentation();
self.buffer.push_str("},\n");
}
fn start_main(&mut self) {
self.buffer.push_str("{\n");
self.depth += 1;
}
fn end_main(&mut self) {
self.dec_depth();
self.buffer.push_str("}\n");
}
fn finish(self) -> String {
assert_eq!(self.depth, 0, "formatter bug: lingering nested objects");
self.buffer
}
}
struct PrintExpr(&'static Expr);
impl From<&'static Expr> for PrintExpr {
fn from(expr: &'static Expr) -> Self {
Self(expr)
}
}
impl fmt::Display for PrintExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
json5::to_string(&self.0)
.expect("string serialization to JSON5 failed")
.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::{template, FormatOptions};
use crate::test_utils::{self, include_format_output};
use pretty_assertions::assert_str_eq;
#[test]
fn default() {
let out = template::<test_utils::example1::Conf>(FormatOptions::default());
assert_str_eq!(&out, include_format_output!("1-default.json5"));
}
#[test]
fn no_comments() {
let mut options = FormatOptions::default();
options.general.comments = false;
let out = template::<test_utils::example1::Conf>(options);
assert_str_eq!(&out, include_format_output!("1-no-comments.json5"));
}
#[test]
fn immediately_nested() {
let out = template::<test_utils::example2::Conf>(Default::default());
assert_str_eq!(&out, include_format_output!("2-default.json5"));
}
}