ephemeris_documents/toml/
render.rs1use std::fmt::{self, Write as _};
4
5use toml_edit::{ArrayOfTables, Item, Table, Value as TomlValue};
6use toml_writer::{ToTomlValue as _, TomlStringBuilder};
7
8use super::Toml;
9use super::decor::prefix_str;
10use super::tree::TomlTree;
11use crate::annotation::{Annotated, AnnotatedValue, StringStyle};
12use crate::entries::Entries;
13use crate::error::{DocumentError, Feature, InternalError, TomlError};
14use crate::path::Segment;
15use crate::scalar::Scalar;
16use crate::text::{Comment, CommentSyntax};
17
18struct Width(usize);
21
22impl fmt::Write for Width {
23 fn write_str(&mut self, s: &str) -> fmt::Result {
24 self.0 = self.0.saturating_add(s.chars().count());
25 Ok(())
26 }
27}
28
29impl Toml {
30 pub(super) fn build_document(self, annotated: &Annotated) -> Result<TomlTree, DocumentError> {
31 let AnnotatedValue::Object(entries) = &annotated.value else {
32 return Err(TomlError::RootNotTable.into());
33 };
34 let mut tree = TomlTree::new();
35 self.populate_table(tree.table_mut(), entries, true)?;
36 if let Some(comment) = &annotated.comment {
37 let mut header = String::new();
38 Self::write_comment(&mut header, comment, "");
39 header.push('\n');
40 tree.set_header(header);
41 }
42 Ok(tree)
43 }
44
45 fn populate_table(
46 self,
47 table: &mut Table,
48 entries: &Entries,
49 is_root: bool,
50 ) -> Result<(), DocumentError> {
51 for (key, annotated) in entries {
52 let item = self
53 .item(key, annotated, is_root)
54 .map_err(|error| error.under(Segment::Key(key.clone())))?;
55 table.insert(key, item);
56 if let Some(comment) = &annotated.comment {
57 Self::attach_comment(table, key, comment);
58 }
59 }
60 let only_subtables = table.iter().all(|(_, item)| Self::has_header(item));
61 table.set_implicit(only_subtables && !table.is_empty());
62 Ok(())
63 }
64
65 fn has_header(item: &Item) -> bool {
67 match item {
68 Item::Table(table) => !table.is_dotted(),
69 Item::ArrayOfTables(_) => true,
70 Item::Value(_) | Item::None => false,
71 }
72 }
73
74 fn item(self, key: &str, annotated: &Annotated, is_root: bool) -> Result<Item, DocumentError> {
75 match &annotated.value {
76 AnnotatedValue::Array(items) => self.array_item(items),
77 AnnotatedValue::Object(entries) => {
78 if !is_root && !entries.is_dotted() && !entries.carries_comment() {
79 let inline = self.inline_table(entries)?;
80 if self.fits_inline(key, &inline) {
81 return Ok(Item::Value(TomlValue::InlineTable(inline)));
82 }
83 }
84 let mut table = Table::new();
85 self.populate_table(&mut table, entries, false)?;
86 table.set_dotted(entries.is_dotted());
87 Ok(Item::Table(table))
88 }
89 AnnotatedValue::Scalar(value) => Ok(Item::Value(self.scalar(value)?)),
90 }
91 }
92
93 fn fits_inline(self, key: &str, inline: &toml_edit::InlineTable) -> bool {
95 let mut width = Width(0);
96 let _infallible = write!(width, "{} = {inline}", toml_edit::Key::new(key));
98 width.0 <= self.inline_width.get()
99 }
100
101 fn array_item(self, items: &[Annotated]) -> Result<Item, DocumentError> {
102 let tables: Option<Vec<&Entries>> = items
103 .iter()
104 .map(|item| match &item.value {
105 AnnotatedValue::Object(entries) => Some(entries),
106 AnnotatedValue::Scalar(_) | AnnotatedValue::Array(_) => None,
107 })
108 .collect();
109 let Some(tables) = tables.filter(|tables| !tables.is_empty()) else {
110 return Ok(Item::Value(TomlValue::Array(self.array(items)?)));
111 };
112 let mut array = ArrayOfTables::new();
113 for (index, (entries, item)) in tables.into_iter().zip(items).enumerate() {
114 let mut table = Table::new();
115 self.populate_table(&mut table, entries, false)
116 .map_err(|error| error.under(Segment::Index(index)))?;
117 if let Some(comment) = &item.comment {
118 let mut prefix = String::new();
119 Self::write_comment(&mut prefix, comment, "");
120 table.decor_mut().set_prefix(prefix);
121 }
122 array.push(table);
123 }
124 Ok(Item::ArrayOfTables(array))
125 }
126
127 fn array(self, items: &[Annotated]) -> Result<toml_edit::Array, DocumentError> {
128 let mut array = toml_edit::Array::new();
129 for (index, item) in items.iter().enumerate() {
130 let value = self
131 .array_value(item)
132 .map_err(|error| error.under(Segment::Index(index)))?;
133 array.push(value);
134 }
135 if items.iter().any(|item| item.comment.is_some()) {
136 Self::spread(&mut array, items);
137 }
138 Ok(array)
139 }
140
141 fn spread(array: &mut toml_edit::Array, items: &[Annotated]) {
143 for (value, item) in array.iter_mut().zip(items) {
144 let mut prefix = String::from("\n");
145 if let Some(comment) = &item.comment {
146 Self::write_comment(&mut prefix, comment, " ");
147 }
148 prefix.push_str(" ");
149 value.decor_mut().set_prefix(prefix);
150 }
151 array.set_trailing("\n");
152 array.set_trailing_comma(true);
153 }
154
155 fn inline_value(self, annotated: &Annotated) -> Result<TomlValue, DocumentError> {
157 if annotated.carries_comment() {
158 return Err(self.unsupported(Feature::InlineComment));
159 }
160 self.array_value(annotated)
161 }
162
163 fn array_value(self, annotated: &Annotated) -> Result<TomlValue, DocumentError> {
164 match &annotated.value {
165 AnnotatedValue::Scalar(value) => self.scalar(value),
166 AnnotatedValue::Array(items) => Ok(TomlValue::Array(self.array(items)?)),
167 AnnotatedValue::Object(entries) => {
168 self.inline_table(entries).map(TomlValue::InlineTable)
169 }
170 }
171 }
172
173 fn scalar(self, scalar: &Scalar) -> Result<TomlValue, DocumentError> {
174 match scalar {
175 Scalar::Null => Err(Self::unvalidated(Feature::Null)),
176 Scalar::Bool(value) => Ok(TomlValue::from(*value)),
177 Scalar::Integer(value) => Ok(TomlValue::from(*value)),
178 Scalar::Unsigned(_) => Err(Self::unvalidated(Feature::LargeInteger)),
179 Scalar::Float(value) => Ok(TomlValue::from(value.get())),
180 Scalar::String { text, style } => self.styled_string(text, *style),
181 }
182 }
183
184 const fn unvalidated(feature: Feature) -> DocumentError {
185 DocumentError::Internal(InternalError::FeatureNotValidated { feature })
186 }
187
188 fn styled_string(self, text: &str, style: StringStyle) -> Result<TomlValue, DocumentError> {
189 match style {
190 StringStyle::Plain => Ok(TomlValue::from(text)),
191 StringStyle::Literal => self.literal_string(text),
192 }
193 }
194
195 fn literal_string(self, text: &str) -> Result<TomlValue, DocumentError> {
196 let builder = TomlStringBuilder::new(text);
197 let Some(rendered) = builder.as_literal().or_else(|| builder.as_ml_literal()) else {
198 return Err(self.unsupported(Feature::LiteralString));
199 };
200 rendered
201 .to_toml_value()
202 .parse::<TomlValue>()
203 .map_err(|source| DocumentError::from(InternalError::toml_rendered(source)))
204 }
205
206 fn inline_table(self, entries: &Entries) -> Result<toml_edit::InlineTable, DocumentError> {
207 let mut table = toml_edit::InlineTable::new();
208 for (key, value) in entries {
209 let value = self
210 .inline_value(value)
211 .map_err(|error| error.under(Segment::Key(key.clone())))?;
212 table.insert(key, value);
213 }
214 Ok(table)
215 }
216
217 fn write_comment(out: &mut String, comment: &Comment, indent: &str) {
218 let _infallible = CommentSyntax::Hash.write_lines(out, comment, indent);
220 }
221
222 fn comment_first_leaf(table: &mut Table, prefix: String) {
225 let Some((mut key, item)) = table.iter_mut().next() else {
226 return;
227 };
228 match item {
229 Item::Table(child) if child.is_dotted() => Self::comment_first_leaf(child, prefix),
230 Item::Table(_) | Item::ArrayOfTables(_) | Item::Value(_) | Item::None => {
231 key.leaf_decor_mut().set_prefix(prefix);
232 }
233 }
234 }
235
236 fn attach_comment(table: &mut Table, key: &str, comment: &Comment) {
237 let mut prefix = String::new();
238 Self::write_comment(&mut prefix, comment, "");
239 match table.get_mut(key) {
240 Some(Item::Value(_)) => {
241 if let Some(mut key_mut) = table.key_mut(key) {
242 key_mut.leaf_decor_mut().set_prefix(prefix);
243 }
244 }
245 Some(Item::Table(child)) if child.is_dotted() => {
246 Self::comment_first_leaf(child, prefix);
247 }
248 Some(Item::Table(child)) => {
249 child.set_implicit(false);
250 child.decor_mut().set_prefix(prefix);
251 }
252 Some(Item::ArrayOfTables(children)) => {
253 if let Some(first) = children.iter_mut().next() {
254 let element = prefix_str(first.decor()).unwrap_or_default();
255 let prefix = [prefix.as_str(), element].concat();
256 first.decor_mut().set_prefix(prefix);
257 }
258 }
259 Some(Item::None) | None => {}
260 }
261 }
262}
263
264#[cfg(test)]
265mod tests;