| 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 | // For doing pretty prints we store in a new String |
| 459 | // because there are too many cases where pretty cannot |
| 460 | // work. We need to determine: |
| 461 | // - if we are a "multi-line" pretty (if there are \n) |
| 462 | // - if ['''] appears if multi or ['] if single |
| 463 | // - if there are any invalid control characters |
| 464 | // |
| 465 | // Doing it any other way would require multiple passes |
| 466 | // to determine if a pretty string works or not. |
| 467 | let mut out = String::with_capacity(value.len() * 2); |
| 468 | let mut ty = StringStyle::OnelineSingle; |
| 469 | // found consecutive single quotes |
| 470 | let mut max_found_singles = 0; |
| 471 | let mut found_singles = 0; |
| 472 | let mut prefer_literal = false; |
| 473 | let mut can_be_pretty = true; |
| 474 | |
| 475 | for ch in value.chars() { |
| 476 | if can_be_pretty { |
| 477 | if ch == ' \'' { |
| 478 | found_singles += 1; |
| 479 | if found_singles >= 3 { |
| 480 | can_be_pretty = false; |
| 481 | } |
| 482 | } else { |
| 483 | if found_singles > max_found_singles { |
| 484 | max_found_singles = found_singles; |
| 485 | } |
| 486 | found_singles = 0 |
| 487 | } |
| 488 | match ch { |
| 489 | ' \t' => {} |
| 490 | ' \\' => { |
| 491 | prefer_literal = true; |
| 492 | } |
| 493 | ' \n' => ty = StringStyle::NewlineTriple, |
| 494 | // Escape codes are needed if any ascii control |
| 495 | // characters are present, including \b \f \r. |
| 496 | c if c <= ' \u{1f}' || c == ' \u{7f}' => can_be_pretty = false, |
| 497 | _ => {} |
| 498 | } |
| 499 | out.push(ch); |
| 500 | } else { |
| 501 | // the string cannot be represented as pretty, |
| 502 | // still check if it should be multiline |
| 503 | if ch == ' \n' { |
| 504 | ty = StringStyle::NewlineTriple; |
| 505 | } |
| 506 | } |
| 507 | } |
| 508 | if found_singles > 0 && value.ends_with(' \'' ) { |
| 509 | // We cannot escape the ending quote so we must use """ |
| 510 | can_be_pretty = false; |
| 511 | } |
| 512 | if !prefer_literal { |
| 513 | can_be_pretty = false; |
| 514 | } |
| 515 | if !can_be_pretty { |
| 516 | debug_assert!(ty != StringStyle::OnelineTriple); |
| 517 | return (ty, false); |
| 518 | } |
| 519 | if found_singles > max_found_singles { |
| 520 | max_found_singles = found_singles; |
| 521 | } |
| 522 | debug_assert!(max_found_singles < 3); |
| 523 | if ty == StringStyle::OnelineSingle && max_found_singles >= 1 { |
| 524 | // no newlines, but must use ''' because it has ' in it |
| 525 | ty = StringStyle::OnelineTriple; |
| 526 | } |
| 527 | (ty, true) |
| 528 | } |
| 529 | |
| 530 | impl ValueRepr for i64 { |
| 531 | fn to_repr(&self) -> Repr { |
| 532 | Repr::new_unchecked(self.to_string()) |
| 533 | } |
| 534 | } |
| 535 | |
| 536 | impl ValueRepr for f64 { |
| 537 | fn to_repr(&self) -> Repr { |
| 538 | to_f64_repr(*self) |
| 539 | } |
| 540 | } |
| 541 | |
| 542 | fn to_f64_repr(f: f64) -> Repr { |
| 543 | let repr: String = match (f.is_sign_negative(), f.is_nan(), f == 0.0) { |
| 544 | (true, true, _) => "-nan" .to_owned(), |
| 545 | (false, true, _) => "nan" .to_owned(), |
| 546 | (true, false, true) => "-0.0" .to_owned(), |
| 547 | (false, false, true) => "0.0" .to_owned(), |
| 548 | (_, false, false) => { |
| 549 | if f % 1.0 == 0.0 { |
| 550 | format!(" {}.0" , f) |
| 551 | } else { |
| 552 | format!(" {}" , f) |
| 553 | } |
| 554 | } |
| 555 | }; |
| 556 | Repr::new_unchecked(raw:repr) |
| 557 | } |
| 558 | |
| 559 | impl ValueRepr for bool { |
| 560 | fn to_repr(&self) -> Repr { |
| 561 | Repr::new_unchecked(self.to_string()) |
| 562 | } |
| 563 | } |
| 564 | |
| 565 | impl ValueRepr for Datetime { |
| 566 | fn to_repr(&self) -> Repr { |
| 567 | Repr::new_unchecked(self.to_string()) |
| 568 | } |
| 569 | } |
| 570 | |