1use std::borrow::Cow;
2use std::fmt::{Display, Formatter, Result, Write};
3
4use toml_datetime::Datetime;
5
6use crate::inline_table::DEFAULT_INLINE_KEY_DECOR;
7use crate::key::Key;
8use crate::repr::{Formatted, Repr, ValueRepr};
9use crate::table::{
10 DEFAULT_KEY_DECOR, DEFAULT_KEY_PATH_DECOR, DEFAULT_ROOT_DECOR, DEFAULT_TABLE_DECOR,
11};
12use crate::value::{
13 DEFAULT_LEADING_VALUE_DECOR, DEFAULT_TRAILING_VALUE_DECOR, DEFAULT_VALUE_DECOR,
14};
15use crate::DocumentMut;
16use crate::{Array, InlineTable, Item, Table, Value};
17
18pub(crate) fn encode_key(this: &Key, buf: &mut dyn Write, input: Option<&str>) -> Result {
19 if let Some(input) = input {
20 let repr = this
21 .as_repr()
22 .map(Cow::Borrowed)
23 .unwrap_or_else(|| Cow::Owned(this.default_repr()));
24 repr.encode(buf, input)?;
25 } else {
26 let repr = this.display_repr();
27 write!(buf, "{repr}")?;
28 };
29
30 Ok(())
31}
32
33fn encode_key_path(
34 this: &[Key],
35 buf: &mut dyn Write,
36 input: Option<&str>,
37 default_decor: (&str, &str),
38) -> Result {
39 let leaf_decor = this.last().expect("always at least one key").leaf_decor();
40 for (i, key) in this.iter().enumerate() {
41 let dotted_decor = key.dotted_decor();
42
43 let first = i == 0;
44 let last = i + 1 == this.len();
45
46 if first {
47 leaf_decor.prefix_encode(buf, input, default_decor.0)?;
48 } else {
49 write!(buf, ".")?;
50 dotted_decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?;
51 }
52
53 encode_key(key, buf, input)?;
54
55 if last {
56 leaf_decor.suffix_encode(buf, input, default_decor.1)?;
57 } else {
58 dotted_decor.suffix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.1)?;
59 }
60 }
61 Ok(())
62}
63
64pub(crate) fn encode_key_path_ref(
65 this: &[&Key],
66 buf: &mut dyn Write,
67 input: Option<&str>,
68 default_decor: (&str, &str),
69) -> Result {
70 let leaf_decor = this.last().expect("always at least one key").leaf_decor();
71 for (i, key) in this.iter().enumerate() {
72 let dotted_decor = key.dotted_decor();
73
74 let first = i == 0;
75 let last = i + 1 == this.len();
76
77 if first {
78 leaf_decor.prefix_encode(buf, input, default_decor.0)?;
79 } else {
80 write!(buf, ".")?;
81 dotted_decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?;
82 }
83
84 encode_key(key, buf, input)?;
85
86 if last {
87 leaf_decor.suffix_encode(buf, input, default_decor.1)?;
88 } else {
89 dotted_decor.suffix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.1)?;
90 }
91 }
92 Ok(())
93}
94
95pub(crate) fn encode_formatted<T: ValueRepr>(
96 this: &Formatted<T>,
97 buf: &mut dyn Write,
98 input: Option<&str>,
99 default_decor: (&str, &str),
100) -> Result {
101 let decor = this.decor();
102 decor.prefix_encode(buf, input, default_decor.0)?;
103
104 if let Some(input) = input {
105 let repr = this
106 .as_repr()
107 .map(Cow::Borrowed)
108 .unwrap_or_else(|| Cow::Owned(this.default_repr()));
109 repr.encode(buf, input)?;
110 } else {
111 let repr = this.display_repr();
112 write!(buf, "{repr}")?;
113 };
114
115 decor.suffix_encode(buf, input, default_decor.1)?;
116 Ok(())
117}
118
119pub(crate) fn encode_array(
120 this: &Array,
121 buf: &mut dyn Write,
122 input: Option<&str>,
123 default_decor: (&str, &str),
124) -> Result {
125 let decor = this.decor();
126 decor.prefix_encode(buf, input, default_decor.0)?;
127 write!(buf, "[")?;
128
129 for (i, elem) in this.iter().enumerate() {
130 let inner_decor;
131 if i == 0 {
132 inner_decor = DEFAULT_LEADING_VALUE_DECOR;
133 } else {
134 inner_decor = DEFAULT_VALUE_DECOR;
135 write!(buf, ",")?;
136 }
137 encode_value(elem, buf, input, inner_decor)?;
138 }
139 if this.trailing_comma() && !this.is_empty() {
140 write!(buf, ",")?;
141 }
142
143 this.trailing().encode_with_default(buf, input, "")?;
144 write!(buf, "]")?;
145 decor.suffix_encode(buf, input, default_decor.1)?;
146
147 Ok(())
148}
149
150pub(crate) fn encode_table(
151 this: &InlineTable,
152 buf: &mut dyn Write,
153 input: Option<&str>,
154 default_decor: (&str, &str),
155) -> Result {
156 let decor = this.decor();
157 decor.prefix_encode(buf, input, default_decor.0)?;
158 write!(buf, "{{")?;
159 this.preamble().encode_with_default(buf, input, "")?;
160
161 let children = this.get_values();
162 let len = children.len();
163 for (i, (key_path, value)) in children.into_iter().enumerate() {
164 if i != 0 {
165 write!(buf, ",")?;
166 }
167 let inner_decor = if i == len - 1 {
168 DEFAULT_TRAILING_VALUE_DECOR
169 } else {
170 DEFAULT_VALUE_DECOR
171 };
172 encode_key_path_ref(&key_path, buf, input, DEFAULT_INLINE_KEY_DECOR)?;
173 write!(buf, "=")?;
174 encode_value(value, buf, input, inner_decor)?;
175 }
176
177 write!(buf, "}}")?;
178 decor.suffix_encode(buf, input, default_decor.1)?;
179
180 Ok(())
181}
182
183pub(crate) fn encode_value(
184 this: &Value,
185 buf: &mut dyn Write,
186 input: Option<&str>,
187 default_decor: (&str, &str),
188) -> Result {
189 match this {
190 Value::String(repr) => encode_formatted(repr, buf, input, default_decor),
191 Value::Integer(repr) => encode_formatted(repr, buf, input, default_decor),
192 Value::Float(repr) => encode_formatted(repr, buf, input, default_decor),
193 Value::Boolean(repr) => encode_formatted(repr, buf, input, default_decor),
194 Value::Datetime(repr) => encode_formatted(repr, buf, input, default_decor),
195 Value::Array(array) => encode_array(array, buf, input, default_decor),
196 Value::InlineTable(table) => encode_table(table, buf, input, default_decor),
197 }
198}
199
200impl Display for DocumentMut {
201 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
202 let decor = self.decor();
203 decor.prefix_encode(f, None, DEFAULT_ROOT_DECOR.0)?;
204
205 let mut path = Vec::new();
206 let mut last_position = 0;
207 let mut tables = Vec::new();
208 visit_nested_tables(self.as_table(), &mut path, false, &mut |t, p, is_array| {
209 if let Some(pos) = t.position() {
210 last_position = pos;
211 }
212 tables.push((last_position, t, p.clone(), is_array));
213 Ok(())
214 })
215 .unwrap();
216
217 tables.sort_by_key(|&(id, _, _, _)| id);
218 let mut first_table = true;
219 for (_, table, path, is_array) in tables {
220 visit_table(f, None, table, &path, is_array, &mut first_table)?;
221 }
222 decor.suffix_encode(f, None, DEFAULT_ROOT_DECOR.1)?;
223 self.trailing().encode_with_default(f, None, "")
224 }
225}
226
227fn visit_nested_tables<'t, F>(
228 table: &'t Table,
229 path: &mut Vec<Key>,
230 is_array_of_tables: bool,
231 callback: &mut F,
232) -> Result
233where
234 F: FnMut(&'t Table, &Vec<Key>, bool) -> Result,
235{
236 if !table.is_dotted() {
237 callback(table, path, is_array_of_tables)?;
238 }
239
240 for (key, value) in table.items.iter() {
241 match value {
242 Item::Table(ref t) => {
243 let key = key.clone();
244 path.push(key);
245 visit_nested_tables(t, path, false, callback)?;
246 path.pop();
247 }
248 Item::ArrayOfTables(ref a) => {
249 for t in a.iter() {
250 let key = key.clone();
251 path.push(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 encode_key_path(path, 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 encode_key_path(path, 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 encode_key_path_ref(&key_path, buf, input, DEFAULT_KEY_DECOR)?;
317 write!(buf, "=")?;
318 encode_value(value, 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) = infer_style(value, style, literal);
336
337 let mut output = String::with_capacity(value.len() * 2);
338 if literal {
339 output.push_str(style.literal_start());
340 output.push_str(value);
341 output.push_str(style.literal_end());
342 } else {
343 output.push_str(style.standard_start());
344 for ch in value.chars() {
345 match ch {
346 '\u{8}' => output.push_str("\\b"),
347 '\u{9}' => output.push_str("\\t"),
348 '\u{a}' => match style {
349 StringStyle::NewlineTriple => output.push('\n'),
350 StringStyle::OnelineSingle => output.push_str("\\n"),
351 StringStyle::OnelineTriple => unreachable!(),
352 },
353 '\u{c}' => output.push_str("\\f"),
354 '\u{d}' => output.push_str("\\r"),
355 '\u{22}' => output.push_str("\\\""),
356 '\u{5c}' => output.push_str("\\\\"),
357 c if c <= '\u{1f}' || c == '\u{7f}' => {
358 write!(output, "\\u{:04X}", ch as u32).unwrap();
359 }
360 ch => output.push(ch),
361 }
362 }
363 output.push_str(style.standard_end());
364 }
365
366 Repr::new_unchecked(output)
367}
368
369#[derive(Copy, Clone, Debug, PartialEq, Eq)]
370pub(crate) enum StringStyle {
371 NewlineTriple,
372 OnelineTriple,
373 OnelineSingle,
374}
375
376impl StringStyle {
377 fn literal_start(self) -> &'static str {
378 match self {
379 Self::NewlineTriple => "'''\n",
380 Self::OnelineTriple => "'''",
381 Self::OnelineSingle => "'",
382 }
383 }
384 fn literal_end(self) -> &'static str {
385 match self {
386 Self::NewlineTriple => "'''",
387 Self::OnelineTriple => "'''",
388 Self::OnelineSingle => "'",
389 }
390 }
391
392 fn standard_start(self) -> &'static str {
393 match self {
394 Self::NewlineTriple => "\"\"\"\n",
395 Self::OnelineTriple | Self::OnelineSingle => "\"",
399 }
400 }
401
402 fn standard_end(self) -> &'static str {
403 match self {
404 Self::NewlineTriple => "\"\"\"",
405 Self::OnelineTriple | Self::OnelineSingle => "\"",
409 }
410 }
411}
412
413fn infer_style(
414 value: &str,
415 style: Option<StringStyle>,
416 literal: Option<bool>,
417) -> (StringStyle, bool) {
418 match (style, literal) {
419 (Some(style), Some(literal)) => (style, literal),
420 (None, Some(literal)) => (infer_all_style(value).0, literal),
421 (Some(style), None) => {
422 let literal = infer_literal(value);
423 (style, literal)
424 }
425 (None, None) => infer_all_style(value),
426 }
427}
428
429fn infer_literal(value: &str) -> bool {
430 #[cfg(feature = "parse")]
431 {
432 use winnow::stream::ContainsToken as _;
433 (value.contains('"') | value.contains('\\'))
434 && value
435 .chars()
436 .all(|c| crate::parser::strings::LITERAL_CHAR.contains_token(c))
437 }
438 #[cfg(not(feature = "parse"))]
439 {
440 false
441 }
442}
443
444fn infer_all_style(value: &str) -> (StringStyle, bool) {
445 let mut ty = StringStyle::OnelineSingle;
453 let mut max_found_singles = 0;
455 let mut found_singles = 0;
456 let mut prefer_literal = false;
457 let mut can_be_pretty = true;
458
459 for ch in value.chars() {
460 if can_be_pretty {
461 if ch == '\'' {
462 found_singles += 1;
463 if found_singles >= 3 {
464 can_be_pretty = false;
465 }
466 } else {
467 if found_singles > max_found_singles {
468 max_found_singles = found_singles;
469 }
470 found_singles = 0;
471 }
472 match ch {
473 '\t' => {}
474 '"' => {
475 prefer_literal = true;
476 }
477 '\\' => {
478 prefer_literal = true;
479 }
480 '\n' => ty = StringStyle::NewlineTriple,
481 c if c <= '\u{1f}' || c == '\u{7f}' => can_be_pretty = false,
484 _ => {}
485 }
486 } else {
487 if ch == '\n' {
490 ty = StringStyle::NewlineTriple;
491 }
492 }
493 }
494 if found_singles > 0 && value.ends_with('\'') {
495 can_be_pretty = false;
497 }
498 if !prefer_literal {
499 can_be_pretty = false;
500 }
501 if !can_be_pretty {
502 debug_assert!(ty != StringStyle::OnelineTriple);
503 return (ty, false);
504 }
505 if found_singles > max_found_singles {
506 max_found_singles = found_singles;
507 }
508 debug_assert!(max_found_singles < 3);
509 if ty == StringStyle::OnelineSingle && max_found_singles >= 1 {
510 ty = StringStyle::OnelineTriple;
512 }
513 (ty, true)
514}
515
516impl ValueRepr for i64 {
517 fn to_repr(&self) -> Repr {
518 Repr::new_unchecked(self.to_string())
519 }
520}
521
522impl ValueRepr for f64 {
523 fn to_repr(&self) -> Repr {
524 to_f64_repr(*self)
525 }
526}
527
528fn to_f64_repr(f: f64) -> Repr {
529 let repr = match (f.is_sign_negative(), f.is_nan(), f == 0.0) {
530 (true, true, _) => "-nan".to_owned(),
531 (false, true, _) => "nan".to_owned(),
532 (true, false, true) => "-0.0".to_owned(),
533 (false, false, true) => "0.0".to_owned(),
534 (_, false, false) => {
535 if f % 1.0 == 0.0 {
536 format!("{f}.0")
537 } else {
538 format!("{f}")
539 }
540 }
541 };
542 Repr::new_unchecked(repr)
543}
544
545impl ValueRepr for bool {
546 fn to_repr(&self) -> Repr {
547 Repr::new_unchecked(self.to_string())
548 }
549}
550
551impl ValueRepr for Datetime {
552 fn to_repr(&self) -> Repr {
553 Repr::new_unchecked(self.to_string())
554 }
555}
556
557#[cfg(test)]
558mod test {
559 use super::*;
560 use proptest::prelude::*;
561
562 proptest! {
563 #[test]
564 #[cfg(feature = "parse")]
565 fn parseable_string(string in "\\PC*") {
566 let string = Value::from(string);
567 let encoded = string.to_string();
568 let _: Value = encoded.parse().unwrap_or_else(|err| {
569 panic!("error: {err}
570
571string:
572```
573{string}
574```
575")
576 });
577 }
578 }
579
580 proptest! {
581 #[test]
582 #[cfg(feature = "parse")]
583 fn parseable_key(string in "\\PC*") {
584 let string = Key::new(string);
585 let encoded = string.to_string();
586 let _: Key = encoded.parse().unwrap_or_else(|err| {
587 panic!("error: {err}
588
589string:
590```
591{string}
592```
593")
594 });
595 }
596 }
597}