1 | use std::borrow::Cow; |
2 | use std::fmt::{Display, Formatter, Result, Write}; |
3 | |
4 | use toml_datetime::*; |
5 | |
6 | use crate::document::Document; |
7 | use crate::inline_table::DEFAULT_INLINE_KEY_DECOR; |
8 | use crate::key::Key; |
9 | use crate::repr::{Formatted, Repr, ValueRepr}; |
10 | use crate::table::{DEFAULT_KEY_DECOR, DEFAULT_KEY_PATH_DECOR, DEFAULT_TABLE_DECOR}; |
11 | use crate::value::{ |
12 | DEFAULT_LEADING_VALUE_DECOR, DEFAULT_TRAILING_VALUE_DECOR, DEFAULT_VALUE_DECOR, |
13 | }; |
14 | use crate::{Array, InlineTable, Item, Table, Value}; |
15 | |
16 | pub(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 | |
25 | impl 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 | |
51 | impl<'k> Encode for &'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 | |
82 | impl<'k> Encode for &'k [&'k Key] { |
83 | fn encode( |
84 | &self, |
85 | buf: &mut dyn Write, |
86 | input: Option<&str>, |
87 | default_decor: (&str, &str), |
88 | ) -> Result { |
89 | for (i, key) in self.iter().enumerate() { |
90 | let first = i == 0; |
91 | let last = i + 1 == self.len(); |
92 | |
93 | let prefix = if first { |
94 | default_decor.0 |
95 | } else { |
96 | DEFAULT_KEY_PATH_DECOR.0 |
97 | }; |
98 | let suffix = if last { |
99 | default_decor.1 |
100 | } else { |
101 | DEFAULT_KEY_PATH_DECOR.1 |
102 | }; |
103 | |
104 | if !first { |
105 | write!(buf, "." )?; |
106 | } |
107 | key.encode(buf, input, (prefix, suffix))?; |
108 | } |
109 | Ok(()) |
110 | } |
111 | } |
112 | |
113 | impl<T> Encode for Formatted<T> |
114 | where |
115 | T: ValueRepr, |
116 | { |
117 | fn encode( |
118 | &self, |
119 | buf: &mut dyn Write, |
120 | input: Option<&str>, |
121 | default_decor: (&str, &str), |
122 | ) -> Result { |
123 | let decor = self.decor(); |
124 | decor.prefix_encode(buf, input, default_decor.0)?; |
125 | |
126 | if let Some(input) = input { |
127 | let repr = self |
128 | .as_repr() |
129 | .map(Cow::Borrowed) |
130 | .unwrap_or_else(|| Cow::Owned(self.default_repr())); |
131 | repr.encode(buf, input)?; |
132 | } else { |
133 | let repr = self.display_repr(); |
134 | write!(buf, " {}" , repr)?; |
135 | }; |
136 | |
137 | decor.suffix_encode(buf, input, default_decor.1)?; |
138 | Ok(()) |
139 | } |
140 | } |
141 | |
142 | impl Encode for Array { |
143 | fn encode( |
144 | &self, |
145 | buf: &mut dyn Write, |
146 | input: Option<&str>, |
147 | default_decor: (&str, &str), |
148 | ) -> Result { |
149 | let decor = self.decor(); |
150 | decor.prefix_encode(buf, input, default_decor.0)?; |
151 | write!(buf, "[" )?; |
152 | |
153 | for (i, elem) in self.iter().enumerate() { |
154 | let inner_decor; |
155 | if i == 0 { |
156 | inner_decor = DEFAULT_LEADING_VALUE_DECOR; |
157 | } else { |
158 | inner_decor = DEFAULT_VALUE_DECOR; |
159 | write!(buf, "," )?; |
160 | } |
161 | elem.encode(buf, input, inner_decor)?; |
162 | } |
163 | if self.trailing_comma() && !self.is_empty() { |
164 | write!(buf, "," )?; |
165 | } |
166 | |
167 | self.trailing().encode_with_default(buf, input, "" )?; |
168 | write!(buf, "]" )?; |
169 | decor.suffix_encode(buf, input, default_decor.1)?; |
170 | |
171 | Ok(()) |
172 | } |
173 | } |
174 | |
175 | impl Encode for InlineTable { |
176 | fn encode( |
177 | &self, |
178 | buf: &mut dyn Write, |
179 | input: Option<&str>, |
180 | default_decor: (&str, &str), |
181 | ) -> Result { |
182 | let decor = self.decor(); |
183 | decor.prefix_encode(buf, input, default_decor.0)?; |
184 | write!(buf, " {{" )?; |
185 | self.preamble().encode_with_default(buf, input, "" )?; |
186 | |
187 | let children = self.get_values(); |
188 | let len = children.len(); |
189 | for (i, (key_path, value)) in children.into_iter().enumerate() { |
190 | if i != 0 { |
191 | write!(buf, "," )?; |
192 | } |
193 | let inner_decor = if i == len - 1 { |
194 | DEFAULT_TRAILING_VALUE_DECOR |
195 | } else { |
196 | DEFAULT_VALUE_DECOR |
197 | }; |
198 | key_path |
199 | .as_slice() |
200 | .encode(buf, input, DEFAULT_INLINE_KEY_DECOR)?; |
201 | write!(buf, "=" )?; |
202 | value.encode(buf, input, inner_decor)?; |
203 | } |
204 | |
205 | write!(buf, " }}" )?; |
206 | decor.suffix_encode(buf, input, default_decor.1)?; |
207 | |
208 | Ok(()) |
209 | } |
210 | } |
211 | |
212 | impl Encode for Value { |
213 | fn encode( |
214 | &self, |
215 | buf: &mut dyn Write, |
216 | input: Option<&str>, |
217 | default_decor: (&str, &str), |
218 | ) -> Result { |
219 | match self { |
220 | Value::String(repr: &Formatted) => repr.encode(buf, input, default_decor), |
221 | Value::Integer(repr: &Formatted) => repr.encode(buf, input, default_decor), |
222 | Value::Float(repr: &Formatted) => repr.encode(buf, input, default_decor), |
223 | Value::Boolean(repr: &Formatted) => repr.encode(buf, input, default_decor), |
224 | Value::Datetime(repr: &Formatted) => repr.encode(buf, input, default_decor), |
225 | Value::Array(array: &Array) => array.encode(buf, input, default_decor), |
226 | Value::InlineTable(table: &InlineTable) => table.encode(buf, input, default_decor), |
227 | } |
228 | } |
229 | } |
230 | |
231 | impl Display for Document { |
232 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { |
233 | let mut path = Vec::new(); |
234 | let mut last_position = 0; |
235 | let mut tables = Vec::new(); |
236 | visit_nested_tables(self.as_table(), &mut path, false, &mut |t, p, is_array| { |
237 | if let Some(pos) = t.position() { |
238 | last_position = pos; |
239 | } |
240 | tables.push((last_position, t, p.clone(), is_array)); |
241 | Ok(()) |
242 | }) |
243 | .unwrap(); |
244 | |
245 | tables.sort_by_key(|&(id, _, _, _)| id); |
246 | let mut first_table = true; |
247 | for (_, table, path, is_array) in tables { |
248 | visit_table( |
249 | f, |
250 | self.original.as_deref(), |
251 | table, |
252 | &path, |
253 | is_array, |
254 | &mut first_table, |
255 | )?; |
256 | } |
257 | self.trailing() |
258 | .encode_with_default(f, self.original.as_deref(), "" ) |
259 | } |
260 | } |
261 | |
262 | fn visit_nested_tables<'t, F>( |
263 | table: &'t Table, |
264 | path: &mut Vec<Key>, |
265 | is_array_of_tables: bool, |
266 | callback: &mut F, |
267 | ) -> Result |
268 | where |
269 | F: FnMut(&'t Table, &Vec<Key>, bool) -> Result, |
270 | { |
271 | if !table.is_dotted() { |
272 | callback(table, path, is_array_of_tables)?; |
273 | } |
274 | |
275 | for kv in table.items.values() { |
276 | match kv.value { |
277 | Item::Table(ref t) => { |
278 | let mut key = kv.key.clone(); |
279 | if t.is_dotted() { |
280 | // May have newlines and generally isn't written for standard tables |
281 | key.decor_mut().clear(); |
282 | } |
283 | path.push(key); |
284 | visit_nested_tables(t, path, false, callback)?; |
285 | path.pop(); |
286 | } |
287 | Item::ArrayOfTables(ref a) => { |
288 | for t in a.iter() { |
289 | let key = kv.key.clone(); |
290 | path.push(key); |
291 | visit_nested_tables(t, path, true, callback)?; |
292 | path.pop(); |
293 | } |
294 | } |
295 | _ => {} |
296 | } |
297 | } |
298 | Ok(()) |
299 | } |
300 | |
301 | fn visit_table( |
302 | buf: &mut dyn Write, |
303 | input: Option<&str>, |
304 | table: &Table, |
305 | path: &[Key], |
306 | is_array_of_tables: bool, |
307 | first_table: &mut bool, |
308 | ) -> Result { |
309 | let children = table.get_values(); |
310 | // We are intentionally hiding implicit tables without any tables nested under them (ie |
311 | // `table.is_empty()` which is in contrast to `table.get_values().is_empty()`). We are |
312 | // trusting the user that an empty implicit table is not semantically meaningful |
313 | // |
314 | // This allows a user to delete all tables under this implicit table and the implicit table |
315 | // will disappear. |
316 | // |
317 | // However, this means that users need to take care in deciding what tables get marked as |
318 | // implicit. |
319 | let is_visible_std_table = !(table.implicit && children.is_empty()); |
320 | |
321 | if path.is_empty() { |
322 | // don't print header for the root node |
323 | if !children.is_empty() { |
324 | *first_table = false; |
325 | } |
326 | } else if is_array_of_tables { |
327 | let default_decor = if *first_table { |
328 | *first_table = false; |
329 | ("" , DEFAULT_TABLE_DECOR.1) |
330 | } else { |
331 | DEFAULT_TABLE_DECOR |
332 | }; |
333 | table.decor.prefix_encode(buf, input, default_decor.0)?; |
334 | write!(buf, "[[" )?; |
335 | path.encode(buf, input, DEFAULT_KEY_PATH_DECOR)?; |
336 | write!(buf, "]]" )?; |
337 | table.decor.suffix_encode(buf, input, default_decor.1)?; |
338 | writeln!(buf)?; |
339 | } else if is_visible_std_table { |
340 | let default_decor = if *first_table { |
341 | *first_table = false; |
342 | ("" , DEFAULT_TABLE_DECOR.1) |
343 | } else { |
344 | DEFAULT_TABLE_DECOR |
345 | }; |
346 | table.decor.prefix_encode(buf, input, default_decor.0)?; |
347 | write!(buf, "[" )?; |
348 | path.encode(buf, input, DEFAULT_KEY_PATH_DECOR)?; |
349 | write!(buf, "]" )?; |
350 | table.decor.suffix_encode(buf, input, default_decor.1)?; |
351 | writeln!(buf)?; |
352 | } |
353 | // print table body |
354 | for (key_path, value) in children { |
355 | key_path.as_slice().encode(buf, input, DEFAULT_KEY_DECOR)?; |
356 | write!(buf, "=" )?; |
357 | value.encode(buf, input, DEFAULT_VALUE_DECOR)?; |
358 | writeln!(buf)?; |
359 | } |
360 | Ok(()) |
361 | } |
362 | |
363 | impl ValueRepr for String { |
364 | fn to_repr(&self) -> Repr { |
365 | to_string_repr(self, style:None, literal:None) |
366 | } |
367 | } |
368 | |
369 | pub(crate) fn to_string_repr( |
370 | value: &str, |
371 | style: Option<StringStyle>, |
372 | literal: Option<bool>, |
373 | ) -> Repr { |
374 | let (style, literal) = match (style, literal) { |
375 | (Some(style), Some(literal)) => (style, literal), |
376 | (_, Some(literal)) => (infer_style(value).0, literal), |
377 | (Some(style), _) => (style, infer_style(value).1), |
378 | (_, _) => infer_style(value), |
379 | }; |
380 | |
381 | let mut output = String::with_capacity(value.len() * 2); |
382 | if literal { |
383 | output.push_str(style.literal_start()); |
384 | output.push_str(value); |
385 | output.push_str(style.literal_end()); |
386 | } else { |
387 | output.push_str(style.standard_start()); |
388 | for ch in value.chars() { |
389 | match ch { |
390 | ' \u{8}' => output.push_str(" \\b" ), |
391 | ' \u{9}' => output.push_str(" \\t" ), |
392 | ' \u{a}' => match style { |
393 | StringStyle::NewlineTriple => output.push(' \n' ), |
394 | StringStyle::OnelineSingle => output.push_str(" \\n" ), |
395 | _ => unreachable!(), |
396 | }, |
397 | ' \u{c}' => output.push_str(" \\f" ), |
398 | ' \u{d}' => output.push_str(" \\r" ), |
399 | ' \u{22}' => output.push_str(" \\\"" ), |
400 | ' \u{5c}' => output.push_str(" \\\\" ), |
401 | c if c <= ' \u{1f}' || c == ' \u{7f}' => { |
402 | write!(output, " \\u {:04X}" , ch as u32).unwrap(); |
403 | } |
404 | ch => output.push(ch), |
405 | } |
406 | } |
407 | output.push_str(style.standard_end()); |
408 | } |
409 | |
410 | Repr::new_unchecked(output) |
411 | } |
412 | |
413 | #[derive (Copy, Clone, Debug, PartialEq, Eq)] |
414 | pub(crate) enum StringStyle { |
415 | NewlineTriple, |
416 | OnelineTriple, |
417 | OnelineSingle, |
418 | } |
419 | |
420 | impl StringStyle { |
421 | fn literal_start(self) -> &'static str { |
422 | match self { |
423 | Self::NewlineTriple => "''' \n" , |
424 | Self::OnelineTriple => "'''" , |
425 | Self::OnelineSingle => "'" , |
426 | } |
427 | } |
428 | fn literal_end(self) -> &'static str { |
429 | match self { |
430 | Self::NewlineTriple => "'''" , |
431 | Self::OnelineTriple => "'''" , |
432 | Self::OnelineSingle => "'" , |
433 | } |
434 | } |
435 | |
436 | fn standard_start(self) -> &'static str { |
437 | match self { |
438 | Self::NewlineTriple => " \"\"\"\n" , |
439 | // note: OnelineTriple can happen if do_pretty wants to do |
440 | // '''it's one line''' |
441 | // but literal == false |
442 | Self::OnelineTriple | Self::OnelineSingle => " \"" , |
443 | } |
444 | } |
445 | |
446 | fn standard_end(self) -> &'static str { |
447 | match self { |
448 | Self::NewlineTriple => " \"\"\"" , |
449 | // note: OnelineTriple can happen if do_pretty wants to do |
450 | // '''it's one line''' |
451 | // but literal == false |
452 | Self::OnelineTriple | Self::OnelineSingle => " \"" , |
453 | } |
454 | } |
455 | } |
456 | |
457 | fn infer_style(value: &str) -> (StringStyle, bool) { |
458 | // We need to determine: |
459 | // - if we are a "multi-line" pretty (if there are \n) |
460 | // - if ['''] appears if multi or ['] if single |
461 | // - if there are any invalid control characters |
462 | // |
463 | // Doing it any other way would require multiple passes |
464 | // to determine if a pretty string works or not. |
465 | let mut ty = StringStyle::OnelineSingle; |
466 | // found consecutive single quotes |
467 | let mut max_found_singles = 0; |
468 | let mut found_singles = 0; |
469 | let mut prefer_literal = false; |
470 | let mut can_be_pretty = true; |
471 | |
472 | for ch in value.chars() { |
473 | if can_be_pretty { |
474 | if ch == ' \'' { |
475 | found_singles += 1; |
476 | if found_singles >= 3 { |
477 | can_be_pretty = false; |
478 | } |
479 | } else { |
480 | if found_singles > max_found_singles { |
481 | max_found_singles = found_singles; |
482 | } |
483 | found_singles = 0 |
484 | } |
485 | match ch { |
486 | ' \t' => {} |
487 | ' \\' => { |
488 | prefer_literal = true; |
489 | } |
490 | ' \n' => ty = StringStyle::NewlineTriple, |
491 | // Escape codes are needed if any ascii control |
492 | // characters are present, including \b \f \r. |
493 | c if c <= ' \u{1f}' || c == ' \u{7f}' => can_be_pretty = false, |
494 | _ => {} |
495 | } |
496 | } else { |
497 | // the string cannot be represented as pretty, |
498 | // still check if it should be multiline |
499 | if ch == ' \n' { |
500 | ty = StringStyle::NewlineTriple; |
501 | } |
502 | } |
503 | } |
504 | if found_singles > 0 && value.ends_with(' \'' ) { |
505 | // We cannot escape the ending quote so we must use """ |
506 | can_be_pretty = false; |
507 | } |
508 | if !prefer_literal { |
509 | can_be_pretty = false; |
510 | } |
511 | if !can_be_pretty { |
512 | debug_assert!(ty != StringStyle::OnelineTriple); |
513 | return (ty, false); |
514 | } |
515 | if found_singles > max_found_singles { |
516 | max_found_singles = found_singles; |
517 | } |
518 | debug_assert!(max_found_singles < 3); |
519 | if ty == StringStyle::OnelineSingle && max_found_singles >= 1 { |
520 | // no newlines, but must use ''' because it has ' in it |
521 | ty = StringStyle::OnelineTriple; |
522 | } |
523 | (ty, true) |
524 | } |
525 | |
526 | impl ValueRepr for i64 { |
527 | fn to_repr(&self) -> Repr { |
528 | Repr::new_unchecked(self.to_string()) |
529 | } |
530 | } |
531 | |
532 | impl ValueRepr for f64 { |
533 | fn to_repr(&self) -> Repr { |
534 | to_f64_repr(*self) |
535 | } |
536 | } |
537 | |
538 | fn to_f64_repr(f: f64) -> Repr { |
539 | let repr: String = match (f.is_sign_negative(), f.is_nan(), f == 0.0) { |
540 | (true, true, _) => "-nan" .to_owned(), |
541 | (false, true, _) => "nan" .to_owned(), |
542 | (true, false, true) => "-0.0" .to_owned(), |
543 | (false, false, true) => "0.0" .to_owned(), |
544 | (_, false, false) => { |
545 | if f % 1.0 == 0.0 { |
546 | format!(" {}.0" , f) |
547 | } else { |
548 | format!(" {}" , f) |
549 | } |
550 | } |
551 | }; |
552 | Repr::new_unchecked(raw:repr) |
553 | } |
554 | |
555 | impl ValueRepr for bool { |
556 | fn to_repr(&self) -> Repr { |
557 | Repr::new_unchecked(self.to_string()) |
558 | } |
559 | } |
560 | |
561 | impl ValueRepr for Datetime { |
562 | fn to_repr(&self) -> Repr { |
563 | Repr::new_unchecked(self.to_string()) |
564 | } |
565 | } |
566 | |