use super::template::*;
use crate::schema::{RenderResult, SchemaRenderer};
use indexmap::IndexMap;
use schematic_types::*;
use std::collections::BTreeMap;
use std::mem;
struct Section {
comment: String,
table: StructType,
}
pub struct TomlTemplateRenderer {
ctx: TemplateContext,
schemas: IndexMap<String, Schema>,
arrays: BTreeMap<String, Section>,
tables: BTreeMap<String, Section>,
}
impl TomlTemplateRenderer {
#[allow(clippy::should_implement_trait)]
pub fn default() -> Self {
TomlTemplateRenderer::new(TemplateOptions::default())
}
pub fn new(options: TemplateOptions) -> Self {
TomlTemplateRenderer {
ctx: TemplateContext::new(options),
schemas: IndexMap::default(),
arrays: BTreeMap::new(),
tables: BTreeMap::new(),
}
}
fn extract_sections(&mut self, doc: &mut StructType) {
for (name, field) in &mut doc.fields {
self.ctx.push_stack(name);
let comment = self.ctx.create_field_comment(field);
match &mut field.schema.ty {
SchemaType::Array(array) => {
let items_type = self
.ctx
.resolve_schema(&array.items_type, &self.schemas)
.to_owned();
if let SchemaType::Struct(mut table) = items_type.ty {
let key = self.ctx.get_stack_key();
field.hidden = true;
self.extract_sections(&mut table);
if !table.is_hidden() {
self.arrays.insert(
key,
Section {
comment,
table: *table,
},
);
}
}
}
SchemaType::Struct(table) => {
let key = self.ctx.get_stack_key();
field.hidden = true;
self.extract_sections(table);
if !table.is_hidden() {
self.tables.insert(
key,
Section {
comment,
table: (**table).to_owned(),
},
);
}
}
_ => {}
};
self.ctx.pop_stack();
}
}
}
impl SchemaRenderer<String> for TomlTemplateRenderer {
fn is_reference(&self, _name: &str) -> bool {
false
}
fn render_array(&mut self, array: &ArrayType, _schema: &Schema) -> RenderResult<String> {
let key = self.ctx.get_stack_key();
if !self.ctx.is_expanded(&key) {
return render_array(array);
}
let items_type = self.ctx.resolve_schema(&array.items_type, &self.schemas);
Ok(format!("[{}]", self.render_schema(&items_type)?))
}
fn render_boolean(&mut self, boolean: &BooleanType, _schema: &Schema) -> RenderResult<String> {
render_boolean(boolean)
}
fn render_enum(&mut self, enu: &EnumType, _schema: &Schema) -> RenderResult<String> {
render_enum(enu)
}
fn render_float(&mut self, float: &FloatType, _schema: &Schema) -> RenderResult<String> {
render_float(float)
}
fn render_integer(&mut self, integer: &IntegerType, _schema: &Schema) -> RenderResult<String> {
render_integer(integer)
}
fn render_literal(&mut self, literal: &LiteralType, _schema: &Schema) -> RenderResult<String> {
render_literal(literal)
}
fn render_null(&mut self, _schema: &Schema) -> RenderResult<String> {
render_null()
}
fn render_object(&mut self, object: &ObjectType, _schema: &Schema) -> RenderResult<String> {
let key = self.ctx.get_stack_key();
let value_type = self.ctx.resolve_schema(&object.value_type, &self.schemas);
if !self.ctx.is_expanded(&key) || value_type.is_struct() {
return render_object(object);
}
let comments = self.ctx.options.comments;
self.ctx.options.comments = false;
let value = self.render_schema(&value_type)?;
let mut key = self.render_schema(&object.key_type)?;
if key == EMPTY_STRING {
key = "example".into();
}
self.ctx.options.comments = comments;
Ok(format!("{{ {key} = {value} }}"))
}
fn render_reference(&mut self, reference: &str, _schema: &Schema) -> RenderResult<String> {
if let Some(schema) = self.schemas.get(reference) {
return self.render_schema_without_reference(&schema.to_owned());
}
render_reference(reference)
}
fn render_string(&mut self, string: &StringType, _schema: &Schema) -> RenderResult<String> {
render_string(string)
}
fn render_struct(&mut self, structure: &StructType, _schema: &Schema) -> RenderResult<String> {
let mut out = vec![];
for (name, field) in &structure.fields {
if field.flatten {
continue;
}
self.ctx.push_stack(name);
if !self.ctx.is_hidden(field) {
let prop = format!(
"{} = {}",
name,
self.render_schema(self.ctx.validate_schema_variant(
self.ctx.get_stack_value().as_ref(),
&field.schema
))?
);
out.push(self.ctx.create_field(field, prop));
}
self.ctx.pop_stack();
}
if out.is_empty() {
return Ok("{}".into());
}
Ok(out.join(self.ctx.gap()))
}
fn render_tuple(&mut self, tuple: &TupleType, _schema: &Schema) -> RenderResult<String> {
render_tuple(tuple, |schema| self.render_schema(schema))
}
fn render_union(&mut self, uni: &UnionType, _schema: &Schema) -> RenderResult<String> {
render_union(uni, |schema| self.render_schema(schema))
}
fn render_unknown(&mut self, _schema: &Schema) -> RenderResult<String> {
render_unknown()
}
fn render(&mut self, schemas: IndexMap<String, Schema>) -> RenderResult {
self.schemas = schemas;
let mut root = validate_root(&self.schemas)?;
let fake_schema = Schema::default();
if let SchemaType::Struct(doc) = &mut root.ty {
self.extract_sections(doc);
}
let mut sections = vec![self.render_schema_without_reference(&root)?];
for (key, value) in mem::take(&mut self.arrays) {
sections.push(format!(
"{}[[{key}]]\n{}",
value.comment,
self.render_struct(&value.table, &fake_schema)?
));
}
for (key, value) in mem::take(&mut self.tables) {
sections.push(format!(
"{}[{key}]\n{}",
value.comment,
self.render_struct(&value.table, &fake_schema)?
));
}
let mut template = sections.join("\n\n");
template = format!(
"{}{template}{}",
self.ctx.options.header, self.ctx.options.footer
);
template.push('\n');
Ok(template)
}
}