1 | use std::borrow::Cow; |
2 | use std::fmt::{Display, Formatter, Result, Write}; |
3 | |
4 | use toml_datetime::Datetime; |
5 | |
6 | use crate::inline_table::DEFAULT_INLINE_KEY_DECOR; |
7 | use crate::key::Key; |
8 | use crate::repr::{Formatted, Repr, ValueRepr}; |
9 | use crate::table::{ |
10 | DEFAULT_KEY_DECOR, DEFAULT_KEY_PATH_DECOR, DEFAULT_ROOT_DECOR, DEFAULT_TABLE_DECOR, |
11 | }; |
12 | use crate::value::{ |
13 | DEFAULT_LEADING_VALUE_DECOR, DEFAULT_TRAILING_VALUE_DECOR, DEFAULT_VALUE_DECOR, |
14 | }; |
15 | use crate::DocumentMut; |
16 | use crate::{Array, InlineTable, Item, Table, Value}; |
17 | |
18 | pub(crate) fn encode_key(this: &Key, buf: &mut dyn Write, input: Option<&str>) -> Result { |
19 | if let Some(input: &str) = input { |
20 | let repr: Cow<'_, Repr> = thisOption> |
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: Cow<'_, str> = this.display_repr(); |
27 | write!(buf, " {repr}" )?; |
28 | }; |
29 | |
30 | Ok(()) |
31 | } |
32 | |
33 | fn 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 | |
64 | pub(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 | |
95 | pub(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: &Decor = this.decor(); |
102 | decor.prefix_encode(buf, input, default_decor.0)?; |
103 | |
104 | if let Some(input: &str) = input { |
105 | let repr: Cow<'_, Repr> = thisOption> |
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: Cow<'_, str> = this.display_repr(); |
112 | write!(buf, " {repr}" )?; |
113 | }; |
114 | |
115 | decor.suffix_encode(buf, input, default_decor.1)?; |
116 | Ok(()) |
117 | } |
118 | |
119 | pub(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 | |
150 | pub(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 | |
183 | pub(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: &Formatted) => encode_formatted(this:repr, buf, input, default_decor), |
191 | Value::Integer(repr: &Formatted) => encode_formatted(this:repr, buf, input, default_decor), |
192 | Value::Float(repr: &Formatted) => encode_formatted(this:repr, buf, input, default_decor), |
193 | Value::Boolean(repr: &Formatted) => encode_formatted(this:repr, buf, input, default_decor), |
194 | Value::Datetime(repr: &Formatted) => encode_formatted(this:repr, buf, input, default_decor), |
195 | Value::Array(array: &Array) => encode_array(this:array, buf, input, default_decor), |
196 | Value::InlineTable(table: &InlineTable) => encode_table(this:table, buf, input, default_decor), |
197 | } |
198 | } |
199 | |
200 | impl 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 | |
227 | fn 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 |
233 | where |
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 | |
262 | fn 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 | // We are intentionally hiding implicit tables without any tables nested under them (ie |
272 | // `table.is_empty()` which is in contrast to `table.get_values().is_empty()`). We are |
273 | // trusting the user that an empty implicit table is not semantically meaningful |
274 | // |
275 | // This allows a user to delete all tables under this implicit table and the implicit table |
276 | // will disappear. |
277 | // |
278 | // However, this means that users need to take care in deciding what tables get marked as |
279 | // implicit. |
280 | let is_visible_std_table = !(table.implicit && children.is_empty()); |
281 | |
282 | if path.is_empty() { |
283 | // don't print header for the root node |
284 | 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 | // print table body |
315 | 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 | |
324 | impl ValueRepr for String { |
325 | fn to_repr(&self) -> Repr { |
326 | to_string_repr(self, style:None, literal:None) |
327 | } |
328 | } |
329 | |
330 | pub(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)] |
370 | pub(crate) enum StringStyle { |
371 | NewlineTriple, |
372 | OnelineTriple, |
373 | OnelineSingle, |
374 | } |
375 | |
376 | impl 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 | // note: OnelineTriple can happen if do_pretty wants to do |
396 | // '''it's one line''' |
397 | // but literal == false |
398 | Self::OnelineTriple | Self::OnelineSingle => " \"" , |
399 | } |
400 | } |
401 | |
402 | fn standard_end(self) -> &'static str { |
403 | match self { |
404 | Self::NewlineTriple => " \"\"\"" , |
405 | // note: OnelineTriple can happen if do_pretty wants to do |
406 | // '''it's one line''' |
407 | // but literal == false |
408 | Self::OnelineTriple | Self::OnelineSingle => " \"" , |
409 | } |
410 | } |
411 | } |
412 | |
413 | fn infer_style( |
414 | value: &str, |
415 | style: Option<StringStyle>, |
416 | literal: Option<bool>, |
417 | ) -> (StringStyle, bool) { |
418 | match (style, literal) { |
419 | (Some(style: StringStyle), Some(literal: bool)) => (style, literal), |
420 | (None, Some(literal: bool)) => (infer_all_style(value).0, literal), |
421 | (Some(style: StringStyle), None) => { |
422 | let literal: bool = infer_literal(value); |
423 | (style, literal) |
424 | } |
425 | (None, None) => infer_all_style(value), |
426 | } |
427 | } |
428 | |
429 | fn infer_literal(value: &str) -> bool { |
430 | #[cfg (feature = "parse" )] |
431 | { |
432 | use winnow::stream::ContainsToken as _; |
433 | (value.contains('"' ) | value.contains(' \\' )) |
434 | && valueChars<'_> |
435 | .chars() |
436 | .all(|c: char| crate::parser::strings::LITERAL_CHAR.contains_token(c)) |
437 | } |
438 | #[cfg (not(feature = "parse" ))] |
439 | { |
440 | false |
441 | } |
442 | } |
443 | |
444 | fn infer_all_style(value: &str) -> (StringStyle, bool) { |
445 | // We need to determine: |
446 | // - if we are a "multi-line" pretty (if there are \n) |
447 | // - if ['''] appears if multi or ['] if single |
448 | // - if there are any invalid control characters |
449 | // |
450 | // Doing it any other way would require multiple passes |
451 | // to determine if a pretty string works or not. |
452 | let mut ty = StringStyle::OnelineSingle; |
453 | // found consecutive single quotes |
454 | 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 | // Escape codes are needed if any ascii control |
482 | // characters are present, including \b \f \r. |
483 | c if c <= ' \u{1f}' || c == ' \u{7f}' => can_be_pretty = false, |
484 | _ => {} |
485 | } |
486 | } else { |
487 | // the string cannot be represented as pretty, |
488 | // still check if it should be multiline |
489 | if ch == ' \n' { |
490 | ty = StringStyle::NewlineTriple; |
491 | } |
492 | } |
493 | } |
494 | if found_singles > 0 && value.ends_with(' \'' ) { |
495 | // We cannot escape the ending quote so we must use """ |
496 | 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 | // no newlines, but must use ''' because it has ' in it |
511 | ty = StringStyle::OnelineTriple; |
512 | } |
513 | (ty, true) |
514 | } |
515 | |
516 | impl ValueRepr for i64 { |
517 | fn to_repr(&self) -> Repr { |
518 | Repr::new_unchecked(self.to_string()) |
519 | } |
520 | } |
521 | |
522 | impl ValueRepr for f64 { |
523 | fn to_repr(&self) -> Repr { |
524 | to_f64_repr(*self) |
525 | } |
526 | } |
527 | |
528 | fn to_f64_repr(f: f64) -> Repr { |
529 | let repr: String = 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(raw:repr) |
543 | } |
544 | |
545 | impl ValueRepr for bool { |
546 | fn to_repr(&self) -> Repr { |
547 | Repr::new_unchecked(self.to_string()) |
548 | } |
549 | } |
550 | |
551 | impl ValueRepr for Datetime { |
552 | fn to_repr(&self) -> Repr { |
553 | Repr::new_unchecked(self.to_string()) |
554 | } |
555 | } |
556 | |
557 | #[cfg (test)] |
558 | mod 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 | |
571 | string: |
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 | |
589 | string: |
590 | ``` |
591 | {string} |
592 | ``` |
593 | " ) |
594 | }); |
595 | } |
596 | } |
597 | } |
598 | |