1use std::borrow::Cow;
2use std::fmt::{Display, Formatter, Result, Write};
3
4use toml_datetime::*;
5
6use crate::document::Document;
7use crate::inline_table::DEFAULT_INLINE_KEY_DECOR;
8use crate::key::Key;
9use crate::repr::{Formatted, Repr, ValueRepr};
10use crate::table::{DEFAULT_KEY_DECOR, DEFAULT_KEY_PATH_DECOR, DEFAULT_TABLE_DECOR};
11use crate::value::{
12 DEFAULT_LEADING_VALUE_DECOR, DEFAULT_TRAILING_VALUE_DECOR, DEFAULT_VALUE_DECOR,
13};
14use crate::{Array, InlineTable, Item, Table, Value};
15
16pub(crate) trait Encode {
17 fn encode(
18 &self,
19 buf: &mut dyn Write,
20 input: Option<&str>,
21 default_decor: (&str, &str),
22 ) -> Result;
23}
24
25impl Encode for Key {
26 fn encode(
27 &self,
28 buf: &mut dyn Write,
29 input: Option<&str>,
30 default_decor: (&str, &str),
31 ) -> Result {
32 let decor = self.decor();
33 decor.prefix_encode(buf, input, default_decor.0)?;
34
35 if let Some(input) = input {
36 let repr = self
37 .as_repr()
38 .map(Cow::Borrowed)
39 .unwrap_or_else(|| Cow::Owned(self.default_repr()));
40 repr.encode(buf, input)?;
41 } else {
42 let repr = self.display_repr();
43 write!(buf, "{}", repr)?;
44 };
45
46 decor.suffix_encode(buf, input, default_decor.1)?;
47 Ok(())
48 }
49}
50
51impl<'k> Encode for &'k [&'k Key] {
52 fn encode(
53 &self,
54 buf: &mut dyn Write,
55 input: Option<&str>,
56 default_decor: (&str, &str),
57 ) -> Result {
58 for (i, key) in self.iter().enumerate() {
59 let first = i == 0;
60 let last = i + 1 == self.len();
61
62 let prefix = if first {
63 default_decor.0
64 } else {
65 DEFAULT_KEY_PATH_DECOR.0
66 };
67 let suffix = if last {
68 default_decor.1
69 } else {
70 DEFAULT_KEY_PATH_DECOR.1
71 };
72
73 if !first {
74 write!(buf, ".")?;
75 }
76 key.encode(buf, input, (prefix, suffix))?;
77 }
78 Ok(())
79 }
80}
81
82impl<T> Encode for Formatted<T>
83where
84 T: ValueRepr,
85{
86 fn encode(
87 &self,
88 buf: &mut dyn Write,
89 input: Option<&str>,
90 default_decor: (&str, &str),
91 ) -> Result {
92 let decor = self.decor();
93 decor.prefix_encode(buf, input, default_decor.0)?;
94
95 if let Some(input) = input {
96 let repr = self
97 .as_repr()
98 .map(Cow::Borrowed)
99 .unwrap_or_else(|| Cow::Owned(self.default_repr()));
100 repr.encode(buf, input)?;
101 } else {
102 let repr = self.display_repr();
103 write!(buf, "{}", repr)?;
104 };
105
106 decor.suffix_encode(buf, input, default_decor.1)?;
107 Ok(())
108 }
109}
110
111impl Encode for Array {
112 fn encode(
113 &self,
114 buf: &mut dyn Write,
115 input: Option<&str>,
116 default_decor: (&str, &str),
117 ) -> Result {
118 let decor = self.decor();
119 decor.prefix_encode(buf, input, default_decor.0)?;
120 write!(buf, "[")?;
121
122 for (i, elem) in self.iter().enumerate() {
123 let inner_decor;
124 if i == 0 {
125 inner_decor = DEFAULT_LEADING_VALUE_DECOR;
126 } else {
127 inner_decor = DEFAULT_VALUE_DECOR;
128 write!(buf, ",")?;
129 }
130 elem.encode(buf, input, inner_decor)?;
131 }
132 if self.trailing_comma() && !self.is_empty() {
133 write!(buf, ",")?;
134 }
135
136 self.trailing().encode_with_default(buf, input, "")?;
137 write!(buf, "]")?;
138 decor.suffix_encode(buf, input, default_decor.1)?;
139
140 Ok(())
141 }
142}
143
144impl Encode for InlineTable {
145 fn encode(
146 &self,
147 buf: &mut dyn Write,
148 input: Option<&str>,
149 default_decor: (&str, &str),
150 ) -> Result {
151 let decor = self.decor();
152 decor.prefix_encode(buf, input, default_decor.0)?;
153 write!(buf, "{{")?;
154 self.preamble().encode_with_default(buf, input, "")?;
155
156 let children = self.get_values();
157 let len = children.len();
158 for (i, (key_path, value)) in children.into_iter().enumerate() {
159 if i != 0 {
160 write!(buf, ",")?;
161 }
162 let inner_decor = if i == len - 1 {
163 DEFAULT_TRAILING_VALUE_DECOR
164 } else {
165 DEFAULT_VALUE_DECOR
166 };
167 key_path
168 .as_slice()
169 .encode(buf, input, DEFAULT_INLINE_KEY_DECOR)?;
170 write!(buf, "=")?;
171 value.encode(buf, input, inner_decor)?;
172 }
173
174 write!(buf, "}}")?;
175 decor.suffix_encode(buf, input, default_decor.1)?;
176
177 Ok(())
178 }
179}
180
181impl Encode for Value {
182 fn encode(
183 &self,
184 buf: &mut dyn Write,
185 input: Option<&str>,
186 default_decor: (&str, &str),
187 ) -> Result {
188 match self {
189 Value::String(repr) => repr.encode(buf, input, default_decor),
190 Value::Integer(repr) => repr.encode(buf, input, default_decor),
191 Value::Float(repr) => repr.encode(buf, input, default_decor),
192 Value::Boolean(repr) => repr.encode(buf, input, default_decor),
193 Value::Datetime(repr) => repr.encode(buf, input, default_decor),
194 Value::Array(array) => array.encode(buf, input, default_decor),
195 Value::InlineTable(table) => table.encode(buf, input, default_decor),
196 }
197 }
198}
199
200impl Display for Document {
201 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
202 let mut path = Vec::new();
203 let mut last_position = 0;
204 let mut tables = Vec::new();
205 visit_nested_tables(self.as_table(), &mut path, false, &mut |t, p, is_array| {
206 if let Some(pos) = t.position() {
207 last_position = pos;
208 }
209 tables.push((last_position, t, p.clone(), is_array));
210 Ok(())
211 })
212 .unwrap();
213
214 tables.sort_by_key(|&(id, _, _, _)| id);
215 let mut first_table = true;
216 for (_, table, path, is_array) in tables {
217 visit_table(
218 f,
219 self.original.as_deref(),
220 table,
221 &path,
222 is_array,
223 &mut first_table,
224 )?;
225 }
226 self.trailing()
227 .encode_with_default(f, self.original.as_deref(), "")
228 }
229}
230
231fn visit_nested_tables<'t, F>(
232 table: &'t Table,
233 path: &mut Vec<&'t Key>,
234 is_array_of_tables: bool,
235 callback: &mut F,
236) -> Result
237where
238 F: FnMut(&'t Table, &Vec<&'t Key>, bool) -> Result,
239{
240 callback(table, path, is_array_of_tables)?;
241
242 for kv in table.items.values() {
243 match kv.value {
244 Item::Table(ref t) if !t.is_dotted() => {
245 path.push(&kv.key);
246 visit_nested_tables(t, path, false, callback)?;
247 path.pop();
248 }
249 Item::ArrayOfTables(ref a) => {
250 for t in a.iter() {
251 path.push(&kv.key);
252 visit_nested_tables(t, path, true, callback)?;
253 path.pop();
254 }
255 }
256 _ => {}
257 }
258 }
259 Ok(())
260}
261
262fn visit_table(
263 buf: &mut dyn Write,
264 input: Option<&str>,
265 table: &Table,
266 path: &[&Key],
267 is_array_of_tables: bool,
268 first_table: &mut bool,
269) -> Result {
270 let children = table.get_values();
271 let is_visible_std_table = !(table.implicit && children.is_empty());
281
282 if path.is_empty() {
283 if !children.is_empty() {
285 *first_table = false;
286 }
287 } else if is_array_of_tables {
288 let default_decor = if *first_table {
289 *first_table = false;
290 ("", DEFAULT_TABLE_DECOR.1)
291 } else {
292 DEFAULT_TABLE_DECOR
293 };
294 table.decor.prefix_encode(buf, input, default_decor.0)?;
295 write!(buf, "[[")?;
296 path.encode(buf, input, DEFAULT_KEY_PATH_DECOR)?;
297 write!(buf, "]]")?;
298 table.decor.suffix_encode(buf, input, default_decor.1)?;
299 writeln!(buf)?;
300 } else if is_visible_std_table {
301 let default_decor = if *first_table {
302 *first_table = false;
303 ("", DEFAULT_TABLE_DECOR.1)
304 } else {
305 DEFAULT_TABLE_DECOR
306 };
307 table.decor.prefix_encode(buf, input, default_decor.0)?;
308 write!(buf, "[")?;
309 path.encode(buf, input, DEFAULT_KEY_PATH_DECOR)?;
310 write!(buf, "]")?;
311 table.decor.suffix_encode(buf, input, default_decor.1)?;
312 writeln!(buf)?;
313 }
314 for (key_path, value) in children {
316 key_path.as_slice().encode(buf, input, DEFAULT_KEY_DECOR)?;
317 write!(buf, "=")?;
318 value.encode(buf, input, DEFAULT_VALUE_DECOR)?;
319 writeln!(buf)?;
320 }
321 Ok(())
322}
323
324impl ValueRepr for String {
325 fn to_repr(&self) -> Repr {
326 to_string_repr(self, None, None)
327 }
328}
329
330pub(crate) fn to_string_repr(
331 value: &str,
332 style: Option<StringStyle>,
333 literal: Option<bool>,
334) -> Repr {
335 let (style, literal) = match (style, literal) {
336 (Some(style), Some(literal)) => (style, literal),
337 (_, Some(literal)) => (infer_style(value).0, literal),
338 (Some(style), _) => (style, infer_style(value).1),
339 (_, _) => infer_style(value),
340 };
341
342 let mut output = String::with_capacity(value.len() * 2);
343 if literal {
344 output.push_str(style.literal_start());
345 output.push_str(value);
346 output.push_str(style.literal_end());
347 } else {
348 output.push_str(style.standard_start());
349 for ch in value.chars() {
350 match ch {
351 '\u{8}' => output.push_str("\\b"),
352 '\u{9}' => output.push_str("\\t"),
353 '\u{a}' => match style {
354 StringStyle::NewlineTripple => output.push('\n'),
355 StringStyle::OnelineSingle => output.push_str("\\n"),
356 _ => unreachable!(),
357 },
358 '\u{c}' => output.push_str("\\f"),
359 '\u{d}' => output.push_str("\\r"),
360 '\u{22}' => output.push_str("\\\""),
361 '\u{5c}' => output.push_str("\\\\"),
362 c if c <= '\u{1f}' || c == '\u{7f}' => {
363 write!(output, "\\u{:04X}", ch as u32).unwrap();
364 }
365 ch => output.push(ch),
366 }
367 }
368 output.push_str(style.standard_end());
369 }
370
371 Repr::new_unchecked(output)
372}
373
374#[derive(Copy, Clone, Debug, PartialEq, Eq)]
375pub(crate) enum StringStyle {
376 NewlineTripple,
377 OnelineTripple,
378 OnelineSingle,
379}
380
381impl StringStyle {
382 fn literal_start(self) -> &'static str {
383 match self {
384 Self::NewlineTripple => "'''\n",
385 Self::OnelineTripple => "'''",
386 Self::OnelineSingle => "'",
387 }
388 }
389 fn literal_end(self) -> &'static str {
390 match self {
391 Self::NewlineTripple => "'''",
392 Self::OnelineTripple => "'''",
393 Self::OnelineSingle => "'",
394 }
395 }
396
397 fn standard_start(self) -> &'static str {
398 match self {
399 Self::NewlineTripple => "\"\"\"\n",
400 Self::OnelineTripple | Self::OnelineSingle => "\"",
404 }
405 }
406
407 fn standard_end(self) -> &'static str {
408 match self {
409 Self::NewlineTripple => "\"\"\"",
410 Self::OnelineTripple | Self::OnelineSingle => "\"",
414 }
415 }
416}
417
418fn infer_style(value: &str) -> (StringStyle, bool) {
419 let mut out = String::with_capacity(value.len() * 2);
429 let mut ty = StringStyle::OnelineSingle;
430 let mut max_found_singles = 0;
432 let mut found_singles = 0;
433 let mut prefer_literal = false;
434 let mut can_be_pretty = true;
435
436 for ch in value.chars() {
437 if can_be_pretty {
438 if ch == '\'' {
439 found_singles += 1;
440 if found_singles >= 3 {
441 can_be_pretty = false;
442 }
443 } else {
444 if found_singles > max_found_singles {
445 max_found_singles = found_singles;
446 }
447 found_singles = 0
448 }
449 match ch {
450 '\t' => {}
451 '\\' => {
452 prefer_literal = true;
453 }
454 '\n' => ty = StringStyle::NewlineTripple,
455 c if c <= '\u{1f}' || c == '\u{7f}' => can_be_pretty = false,
458 _ => {}
459 }
460 out.push(ch);
461 } else {
462 if ch == '\n' {
465 ty = StringStyle::NewlineTripple;
466 }
467 }
468 }
469 if found_singles > 0 && value.ends_with('\'') {
470 can_be_pretty = false;
472 }
473 if !prefer_literal {
474 can_be_pretty = false;
475 }
476 if !can_be_pretty {
477 debug_assert!(ty != StringStyle::OnelineTripple);
478 return (ty, false);
479 }
480 if found_singles > max_found_singles {
481 max_found_singles = found_singles;
482 }
483 debug_assert!(max_found_singles < 3);
484 if ty == StringStyle::OnelineSingle && max_found_singles >= 1 {
485 ty = StringStyle::OnelineTripple;
487 }
488 (ty, true)
489}
490
491impl ValueRepr for i64 {
492 fn to_repr(&self) -> Repr {
493 Repr::new_unchecked(self.to_string())
494 }
495}
496
497impl ValueRepr for f64 {
498 fn to_repr(&self) -> Repr {
499 to_f64_repr(*self)
500 }
501}
502
503fn to_f64_repr(f: f64) -> Repr {
504 let repr = match (f.is_sign_negative(), f.is_nan(), f == 0.0) {
505 (true, true, _) => "-nan".to_owned(),
506 (false, true, _) => "nan".to_owned(),
507 (true, false, true) => "-0.0".to_owned(),
508 (false, false, true) => "0.0".to_owned(),
509 (_, false, false) => {
510 if f % 1.0 == 0.0 {
511 format!("{}.0", f)
512 } else {
513 format!("{}", f)
514 }
515 }
516 };
517 Repr::new_unchecked(repr)
518}
519
520impl ValueRepr for bool {
521 fn to_repr(&self) -> Repr {
522 Repr::new_unchecked(self.to_string())
523 }
524}
525
526impl ValueRepr for Datetime {
527 fn to_repr(&self) -> Repr {
528 Repr::new_unchecked(self.to_string())
529 }
530}