1use std::fmt::Debug;
21
22use crate::cells::cell_len;
23use crate::console::{Console, ConsoleOptions, Justify, Overflow};
24use crate::highlighter::ReprHighlighter;
25use crate::measure::Measurement;
26use crate::protocol::{Highlighter, Renderable};
27use crate::pyformat::float_repr;
28use crate::segment::Segment;
29use crate::text::Text;
30
31#[derive(Clone, Debug, PartialEq)]
36pub enum PrettyValue {
37 None,
39 Bool(bool),
41 Int(i128),
43 Float(f64),
45 Str(String),
47 Bytes(Vec<u8>),
49 Repr(String),
51 List(Vec<PrettyValue>),
53 Tuple(Vec<PrettyValue>),
55 Set(Vec<PrettyValue>),
57 FrozenSet(Vec<PrettyValue>),
59 Dict(Vec<(PrettyValue, PrettyValue)>),
61 Object(PrettyObject),
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum ObjectRepr {
69 RichRepr { angular: bool },
71 Dataclass,
73 NamedTuple,
75}
76
77#[derive(Clone, Debug, PartialEq)]
80pub struct PrettyObject {
81 pub name: String,
82 pub fields: Vec<(Option<String>, PrettyValue)>,
83 pub kind: ObjectRepr,
84}
85
86impl PrettyObject {
87 pub fn new(
89 name: impl Into<String>,
90 kind: ObjectRepr,
91 fields: Vec<(Option<String>, PrettyValue)>,
92 ) -> Self {
93 PrettyObject {
94 name: name.into(),
95 fields,
96 kind,
97 }
98 }
99}
100
101impl From<&serde_json::Value> for PrettyValue {
102 fn from(value: &serde_json::Value) -> Self {
106 use serde_json::Value;
107 match value {
108 Value::Null => PrettyValue::None,
109 Value::Bool(b) => PrettyValue::Bool(*b),
110 Value::Number(n) => {
111 if let Some(i) = n.as_i64() {
112 PrettyValue::Int(i128::from(i))
113 } else if let Some(u) = n.as_u64() {
114 PrettyValue::Int(i128::from(u))
115 } else {
116 PrettyValue::Float(n.as_f64().unwrap_or(f64::NAN))
117 }
118 }
119 Value::String(s) => PrettyValue::Str(s.clone()),
120 Value::Array(items) => PrettyValue::List(items.iter().map(Into::into).collect()),
121 Value::Object(map) => PrettyValue::Dict(
122 map.iter()
123 .map(|(k, v)| (PrettyValue::Str(k.clone()), v.into()))
124 .collect(),
125 ),
126 }
127 }
128}
129
130fn is_printable(ch: char) -> bool {
136 let c = ch as u32;
137 !matches!(c,
138 0x00..=0x1F | 0x7F..=0xA0 | 0xAD
139 | 0x600..=0x605 | 0x61C | 0x6DD | 0x70F | 0x890..=0x891 | 0x8E2
140 | 0x1680 | 0x180E
141 | 0x2000..=0x200F | 0x2028..=0x202F | 0x205F..=0x2064 | 0x2066..=0x206F
142 | 0x3000 | 0xE000..=0xF8FF | 0xFEFF | 0xFFF9..=0xFFFB
143 | 0x110BD | 0x110CD | 0x13430..=0x1343F | 0x1BCA0..=0x1BCA3
144 | 0x1D173..=0x1D17A | 0xE0001 | 0xE0020..=0xE007F
145 | 0xF0000..=0xFFFFD | 0x100000..=0x10FFFD)
146}
147
148pub fn str_repr(value: &str) -> String {
150 let quote = if value.contains('\'') && !value.contains('"') {
151 '"'
152 } else {
153 '\''
154 };
155 let mut out = String::with_capacity(value.len() + 2);
156 out.push(quote);
157 for ch in value.chars() {
158 match ch {
159 '\\' => out.push_str("\\\\"),
160 '\n' => out.push_str("\\n"),
161 '\r' => out.push_str("\\r"),
162 '\t' => out.push_str("\\t"),
163 ch if ch == quote => {
164 out.push('\\');
165 out.push(ch);
166 }
167 ch if !is_printable(ch) => {
168 let c = ch as u32;
169 if c < 0x100 {
170 out.push_str(&format!("\\x{c:02x}"));
171 } else if c < 0x10000 {
172 out.push_str(&format!("\\u{c:04x}"));
173 } else {
174 out.push_str(&format!("\\U{c:08x}"));
175 }
176 }
177 ch => out.push(ch),
178 }
179 }
180 out.push(quote);
181 out
182}
183
184pub fn bytes_repr(value: &[u8]) -> String {
186 let quote = if value.contains(&b'\'') && !value.contains(&b'"') {
187 b'"'
188 } else {
189 b'\''
190 };
191 let mut out = String::with_capacity(value.len() + 3);
192 out.push('b');
193 out.push(quote as char);
194 for &byte in value {
195 match byte {
196 b'\\' => out.push_str("\\\\"),
197 b'\n' => out.push_str("\\n"),
198 b'\r' => out.push_str("\\r"),
199 b'\t' => out.push_str("\\t"),
200 b if b == quote => {
201 out.push('\\');
202 out.push(b as char);
203 }
204 0x20..=0x7E => out.push(byte as char),
205 _ => out.push_str(&format!("\\x{byte:02x}")),
206 }
207 }
208 out.push(quote as char);
209 out
210}
211
212#[derive(Clone, Debug, PartialEq)]
215pub struct Node {
216 pub key_repr: String,
217 pub value_repr: String,
218 pub open_brace: String,
219 pub close_brace: String,
220 pub empty: String,
221 pub last: bool,
222 pub is_tuple: bool,
223 pub is_namedtuple: bool,
224 pub children: Option<Vec<Node>>,
225 pub key_separator: String,
226 pub separator: String,
227}
228
229impl Default for Node {
230 fn default() -> Self {
231 Node {
232 key_repr: String::new(),
233 value_repr: String::new(),
234 open_brace: String::new(),
235 close_brace: String::new(),
236 empty: String::new(),
237 last: false,
238 is_tuple: false,
239 is_namedtuple: false,
240 children: None,
241 key_separator: ": ".to_string(),
242 separator: ", ".to_string(),
243 }
244 }
245}
246
247impl Node {
248 fn atom(value_repr: impl Into<String>) -> Node {
249 Node {
250 value_repr: value_repr.into(),
251 ..Node::default()
252 }
253 }
254
255 fn visit_tokens(&self, visit: &mut dyn FnMut(&str) -> bool) -> bool {
258 if !self.key_repr.is_empty() && !(visit(&self.key_repr) && visit(&self.key_separator)) {
259 return false;
260 }
261 if !self.value_repr.is_empty() {
262 return visit(&self.value_repr);
263 }
264 if let Some(children) = &self.children {
265 if children.is_empty() {
266 return visit(&self.empty);
267 }
268 if !visit(&self.open_brace) {
269 return false;
270 }
271 if self.is_tuple && !self.is_namedtuple && children.len() == 1 {
272 if !(children[0].visit_tokens(visit) && visit(",")) {
273 return false;
274 }
275 } else {
276 for child in children {
277 if !child.visit_tokens(visit) {
278 return false;
279 }
280 if !child.last && !visit(&self.separator) {
281 return false;
282 }
283 }
284 }
285 return visit(&self.close_brace);
286 }
287 true
288 }
289
290 pub fn check_length(&self, start_length: usize, max_length: usize) -> bool {
293 let mut total_length = start_length;
294 self.visit_tokens(&mut |token| {
295 total_length += cell_len(token);
296 total_length <= max_length
297 })
298 }
299
300 pub fn render(&self, max_width: usize, indent_size: usize, expand_all: bool) -> String {
304 let mut lines = vec![Line {
305 node: Some(self),
306 ..Line::default()
307 }];
308 let mut line_no = 0;
309 while line_no < lines.len() {
310 let line = &lines[line_no];
311 if line.expandable() && (expand_all || !line.check_length(max_width)) {
312 let expanded = line.expand(indent_size);
313 lines.splice(line_no..=line_no, expanded);
314 }
315 line_no += 1;
316 }
317 lines
318 .iter()
319 .map(Line::to_string)
320 .collect::<Vec<_>>()
321 .join("\n")
322 }
323}
324
325impl std::fmt::Display for Node {
326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 let mut result = Ok(());
328 self.visit_tokens(&mut |token| {
329 result = f.write_str(token);
330 result.is_ok()
331 });
332 result
333 }
334}
335
336#[derive(Default)]
338struct Line<'a> {
339 node: Option<&'a Node>,
340 text: String,
341 suffix: String,
342 whitespace: String,
343 last: bool,
344}
345
346impl<'a> Line<'a> {
347 fn expandable(&self) -> bool {
348 self.node
349 .and_then(|node| node.children.as_ref())
350 .is_some_and(|children| !children.is_empty())
351 }
352
353 fn check_length(&self, max_length: usize) -> bool {
354 let start_length = self.whitespace.len() + cell_len(&self.text) + cell_len(&self.suffix);
355 self.node
356 .expect("an expandable line has a node")
357 .check_length(start_length, max_length)
358 }
359
360 fn expand(&self, indent_size: usize) -> Vec<Line<'a>> {
361 let node = self.node.expect("an expandable line has a node");
362 let children = node.children.as_ref().expect("expandable");
363 let whitespace = self.whitespace.clone();
364 let text = if node.key_repr.is_empty() {
365 node.open_brace.clone()
366 } else {
367 format!("{}{}{}", node.key_repr, node.key_separator, node.open_brace)
368 };
369 let mut lines = vec![Line {
370 text,
371 whitespace: whitespace.clone(),
372 ..Line::default()
373 }];
374 let child_whitespace = format!("{whitespace}{}", " ".repeat(indent_size));
375 let tuple_of_one = node.is_tuple && children.len() == 1;
376 let count = children.len();
377 for (index, child) in children.iter().enumerate() {
378 let last = index + 1 == count;
379 lines.push(Line {
380 node: Some(child),
381 whitespace: child_whitespace.clone(),
382 suffix: if tuple_of_one {
383 ",".to_string()
384 } else {
385 node.separator.clone()
386 },
387 last: last && !tuple_of_one,
388 ..Line::default()
389 });
390 }
391 lines.push(Line {
392 text: node.close_brace.clone(),
393 whitespace,
394 suffix: self.suffix.clone(),
395 last: self.last,
396 ..Line::default()
397 });
398 lines
399 }
400}
401
402impl std::fmt::Display for Line<'_> {
403 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
404 write!(f, "{}{}", self.whitespace, self.text)?;
405 if let Some(node) = self.node {
406 write!(f, "{node}")?;
407 }
408 if !self.last {
409 f.write_str(self.suffix.trim_end())?;
410 }
411 Ok(())
412 }
413}
414
415#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
417pub struct ReprLimits {
418 pub max_length: Option<usize>,
420 pub max_string: Option<usize>,
422 pub max_depth: Option<usize>,
424}
425
426pub fn py_repr(value: &PrettyValue) -> String {
428 traverse(value, ReprLimits::default()).to_string()
429}
430
431fn to_repr(value: &PrettyValue, max_string: Option<usize>) -> String {
433 match value {
434 PrettyValue::Str(s) => match max_string {
435 Some(max) if s.chars().count() > max => {
436 let truncated = s.chars().count() - max;
437 let head: String = s.chars().take(max).collect();
438 format!("{}+{truncated}", str_repr(&head))
439 }
440 _ => str_repr(s),
441 },
442 PrettyValue::Bytes(b) => match max_string {
443 Some(max) if b.len() > max => {
444 format!("{}+{}", bytes_repr(&b[..max]), b.len() - max)
445 }
446 _ => bytes_repr(b),
447 },
448 PrettyValue::None => "None".to_string(),
449 PrettyValue::Bool(true) => "True".to_string(),
450 PrettyValue::Bool(false) => "False".to_string(),
451 PrettyValue::Int(i) => i.to_string(),
452 PrettyValue::Float(f) => float_repr(*f),
453 PrettyValue::Repr(r) => r.clone(),
454 other => py_repr(other),
455 }
456}
457
458pub fn traverse(value: &PrettyValue, limits: ReprLimits) -> Node {
460 traverse_node(value, true, 0, limits)
461}
462
463fn traverse_node(value: &PrettyValue, root: bool, depth: usize, limits: ReprLimits) -> Node {
464 let reached_max_depth = limits.max_depth.is_some_and(|max| depth >= max);
465 let mut node = match value {
466 PrettyValue::Object(object) => traverse_object(object, root, depth, limits),
467 PrettyValue::List(items)
468 | PrettyValue::Tuple(items)
469 | PrettyValue::Set(items)
470 | PrettyValue::FrozenSet(items) => {
471 let (open_brace, close_brace, empty) = match value {
472 PrettyValue::List(_) => ("[", "]", "[]"),
473 PrettyValue::Tuple(_) => ("(", ")", "()"),
474 PrettyValue::Set(_) => ("{", "}", "set()"),
475 _ => ("frozenset({", "})", "frozenset()"),
476 };
477 if reached_max_depth {
478 Node::atom(format!("{open_brace}...{close_brace}"))
479 } else if items.is_empty() {
480 Node {
481 empty: empty.to_string(),
482 children: Some(Vec::new()),
483 last: root,
484 ..Node::default()
485 }
486 } else {
487 let last_item_index = items.len() - 1;
488 let shown = limits.max_length.unwrap_or(usize::MAX);
489 let mut children: Vec<Node> = items
490 .iter()
491 .take(shown)
492 .enumerate()
493 .map(|(index, child)| {
494 let mut child_node = traverse_node(child, false, depth + 1, limits);
495 child_node.last = index == last_item_index;
496 child_node
497 })
498 .collect();
499 push_more(&mut children, items.len(), limits.max_length);
500 Node {
501 open_brace: open_brace.to_string(),
502 close_brace: close_brace.to_string(),
503 children: Some(children),
504 last: root,
505 ..Node::default()
506 }
507 }
508 }
509 PrettyValue::Dict(items) => {
510 if reached_max_depth {
511 Node::atom("{...}")
512 } else if items.is_empty() {
513 Node {
514 empty: "{}".to_string(),
515 children: Some(Vec::new()),
516 last: root,
517 ..Node::default()
518 }
519 } else {
520 let last_item_index = items.len() - 1;
521 let shown = limits.max_length.unwrap_or(usize::MAX);
522 let mut children: Vec<Node> = items
523 .iter()
524 .take(shown)
525 .enumerate()
526 .map(|(index, (key, child))| {
527 let mut child_node = traverse_node(child, false, depth + 1, limits);
528 child_node.key_repr = to_repr(key, limits.max_string);
529 child_node.last = index == last_item_index;
530 child_node
531 })
532 .collect();
533 push_more(&mut children, items.len(), limits.max_length);
534 Node {
535 open_brace: "{".to_string(),
536 close_brace: "}".to_string(),
537 children: Some(children),
538 last: root,
539 ..Node::default()
540 }
541 }
542 }
543 atom => Node {
544 value_repr: to_repr(atom, limits.max_string),
545 last: root,
546 ..Node::default()
547 },
548 };
549 node.is_tuple = matches!(value, PrettyValue::Tuple(_));
550 node.is_namedtuple = matches!(
551 value,
552 PrettyValue::Object(PrettyObject {
553 kind: ObjectRepr::NamedTuple,
554 ..
555 })
556 );
557 node
558}
559
560fn push_more(children: &mut Vec<Node>, num_items: usize, max_length: Option<usize>) {
562 if let Some(max_length) = max_length {
563 if num_items > max_length {
564 children.push(Node {
565 value_repr: format!("... +{}", num_items - max_length),
566 last: true,
567 ..Node::default()
568 });
569 }
570 }
571}
572
573fn traverse_object(object: &PrettyObject, root: bool, depth: usize, limits: ReprLimits) -> Node {
574 let reached_max_depth = limits.max_depth.is_some_and(|max| depth >= max);
575 let name = &object.name;
576 let children = || -> Vec<Node> {
577 let count = object.fields.len();
578 object
579 .fields
580 .iter()
581 .enumerate()
582 .map(|(index, (key, child))| {
583 let mut child_node = traverse_node(child, false, depth + 1, limits);
584 child_node.last = index + 1 == count;
585 if let Some(key) = key {
586 child_node.key_repr = key.clone();
587 child_node.key_separator = "=".to_string();
588 }
589 child_node
590 })
591 .collect()
592 };
593 match object.kind {
594 ObjectRepr::RichRepr { angular } => {
595 if object.fields.is_empty() {
596 Node {
597 value_repr: if angular {
598 format!("<{name}>")
599 } else {
600 format!("{name}()")
601 },
602 children: Some(Vec::new()),
603 last: root,
604 ..Node::default()
605 }
606 } else if reached_max_depth {
607 Node::atom(if angular {
608 format!("<{name}...>")
609 } else {
610 format!("{name}(...)")
611 })
612 } else if angular {
613 Node {
614 open_brace: format!("<{name} "),
615 close_brace: ">".to_string(),
616 children: Some(children()),
617 last: root,
618 separator: " ".to_string(),
619 ..Node::default()
620 }
621 } else {
622 Node {
623 open_brace: format!("{name}("),
624 close_brace: ")".to_string(),
625 children: Some(children()),
626 last: root,
627 ..Node::default()
628 }
629 }
630 }
631 ObjectRepr::Dataclass | ObjectRepr::NamedTuple => {
632 if reached_max_depth {
633 Node::atom(format!("{name}(...)"))
634 } else {
635 Node {
636 open_brace: format!("{name}("),
637 close_brace: ")".to_string(),
638 children: Some(children()),
639 last: root && object.kind == ObjectRepr::Dataclass,
641 empty: format!("{name}()"),
642 ..Node::default()
643 }
644 }
645 }
646 }
647}
648
649pub fn pretty_repr(
652 value: &PrettyValue,
653 max_width: usize,
654 indent_size: usize,
655 limits: ReprLimits,
656 expand_all: bool,
657) -> String {
658 traverse(value, limits).render(max_width, indent_size, expand_all)
659}
660
661enum Source {
662 Debug(Text),
664 Value(PrettyValue),
666}
667
668pub struct Pretty {
670 source: Source,
671 highlighter: Box<dyn Highlighter + Send + Sync>,
672 indent_size: usize,
673 justify: Option<Justify>,
674 overflow: Option<Overflow>,
675 no_wrap: Option<bool>,
676 indent_guides: bool,
677 limits: ReprLimits,
678 expand_all: bool,
679 margin: usize,
680 insert_line: bool,
681}
682
683impl Pretty {
684 pub fn new(value: &impl Debug) -> Self {
687 Pretty::from_debug_string(format!("{value:#?}"))
688 }
689
690 pub fn compact(value: &impl Debug) -> Self {
692 Pretty::from_debug_string(format!("{value:?}"))
693 }
694
695 fn from_debug_string(rendered: String) -> Self {
696 let mut text = Text::new(rendered);
697 ReprHighlighter::new().highlight(&mut text);
698 Pretty::with_source(Source::Debug(text))
699 }
700
701 pub fn from_value(value: PrettyValue) -> Self {
703 Pretty::with_source(Source::Value(value))
704 }
705
706 fn with_source(source: Source) -> Self {
707 Pretty {
708 source,
709 highlighter: Box::new(ReprHighlighter::new()),
710 indent_size: 4,
711 justify: None,
712 overflow: None,
713 no_wrap: Some(false),
714 indent_guides: false,
715 limits: ReprLimits::default(),
716 expand_all: false,
717 margin: 0,
718 insert_line: false,
719 }
720 }
721
722 pub fn highlighter(mut self, highlighter: impl Highlighter + Send + Sync + 'static) -> Self {
725 self.highlighter = Box::new(highlighter);
726 self
727 }
728
729 pub fn indent_size(mut self, indent_size: usize) -> Self {
731 self.indent_size = indent_size;
732 self
733 }
734
735 pub fn justify(mut self, justify: Option<Justify>) -> Self {
737 self.justify = justify;
738 self
739 }
740
741 pub fn overflow(mut self, overflow: Option<Overflow>) -> Self {
743 self.overflow = overflow;
744 self
745 }
746
747 pub fn no_wrap(mut self, no_wrap: Option<bool>) -> Self {
750 self.no_wrap = no_wrap;
751 self
752 }
753
754 pub fn indent_guides(mut self, indent_guides: bool) -> Self {
756 self.indent_guides = indent_guides;
757 self
758 }
759
760 pub fn max_length(mut self, max_length: Option<usize>) -> Self {
762 self.limits.max_length = max_length;
763 self
764 }
765
766 pub fn max_string(mut self, max_string: Option<usize>) -> Self {
768 self.limits.max_string = max_string;
769 self
770 }
771
772 pub fn max_depth(mut self, max_depth: Option<usize>) -> Self {
774 self.limits.max_depth = max_depth;
775 self
776 }
777
778 pub fn expand_all(mut self, expand_all: bool) -> Self {
780 self.expand_all = expand_all;
781 self
782 }
783
784 pub fn margin(mut self, margin: usize) -> Self {
787 self.margin = margin;
788 self
789 }
790
791 pub fn insert_line(mut self, insert_line: bool) -> Self {
794 self.insert_line = insert_line;
795 self
796 }
797
798 fn repr_at(&self, value: &PrettyValue, max_width: usize) -> String {
799 pretty_repr(
800 value,
801 max_width,
802 self.indent_size,
803 self.limits,
804 self.expand_all,
805 )
806 }
807}
808
809impl Renderable for Pretty {
810 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
811 let value = match &self.source {
812 Source::Debug(text) => return text.rich_render(console, options),
813 Source::Value(value) => value,
814 };
815 let pretty_str = self.repr_at(value, options.max_width.saturating_sub(self.margin));
816 let mut pretty_text = Text::from_ansi(&pretty_str, "pretty");
817 pretty_text.set_justify(self.justify.unwrap_or(options.justify));
818 pretty_text.set_overflow(self.overflow.or(options.overflow));
819 pretty_text.set_no_wrap(Some(self.no_wrap.or(options.no_wrap).unwrap_or(false)));
820 if pretty_text.is_empty() {
821 pretty_text = Text::styled("__repr__ returned empty string", "dim italic");
824 } else {
825 self.highlighter.highlight(&mut pretty_text);
826 }
827 if self.indent_guides && !options.ascii_only() {
828 pretty_text =
829 pretty_text.with_indent_guides(Some(self.indent_size), "│", "repr.indent");
830 }
831 let mut segments = Vec::new();
832 if self.insert_line && pretty_text.plain().contains('\n') {
833 segments.push(Segment::line());
834 }
835 segments.extend(pretty_text.rich_render(console, options));
836 segments
837 }
838
839 fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
840 let value = match &self.source {
841 Source::Debug(text) => return text.measure(console, options),
842 Source::Value(value) => value,
843 };
844 let pretty_str = self.repr_at(value, options.max_width);
845 let text_width = pretty_str.lines().map(cell_len).max().unwrap_or(0);
846 Measurement {
847 minimum: text_width,
848 maximum: text_width,
849 }
850 }
851}
852
853#[cfg(test)]
854mod tests {
855 use super::*;
856 use crate::color::ColorSystem;
857
858 fn render(pretty: &Pretty) -> String {
859 Console::builder()
860 .force_terminal(true)
861 .color_system(Some(ColorSystem::Truecolor))
862 .width(80)
863 .no_color(false)
864 .build()
865 .render_to_string(pretty)
866 }
867
868 #[test]
869 fn highlights_numbers_in_debug_output() {
870 let out = render(&Pretty::compact(&vec![1, 2, 3]));
872 assert!(out.contains("\x1b[1;36m1\x1b[0m"), "got {out:?}");
873 assert!(out.contains('3'));
874 }
875
876 #[test]
877 fn highlights_string_in_debug_output() {
878 let out = render(&Pretty::compact(&"hello"));
880 assert!(out.contains("\x1b[32m\"hello\"\x1b[0m"), "got {out:?}");
881 }
882
883 #[test]
884 fn pretty_multiline_preserves_structure() {
885 let out = render(&Pretty::new(&vec![vec![1, 2], vec![3, 4]]));
887 assert!(out.contains('\n'), "expected pretty multi-line layout");
888 assert!(out.contains("\x1b[1;36m4\x1b[0m"), "numbers highlighted");
889 }
890
891 #[test]
892 fn python_reprs() {
893 use PrettyValue as V;
894 assert_eq!(str_repr("it's"), "\"it's\"");
895 assert_eq!(
896 str_repr("a'\"b\n\x01é\u{2028}"),
897 "'a\\'\"b\\n\\x01é\\u2028'"
898 );
899 assert_eq!(bytes_repr(b"a\x00'"), "b\"a\\x00'\"");
900 let value = V::List(vec![
901 V::Tuple(vec![V::Int(1)]),
902 V::Set(vec![]),
903 V::Dict(vec![(V::Str("k".into()), V::Float(1e16))]),
904 V::None,
905 V::Bool(true),
906 ]);
907 assert_eq!(py_repr(&value), "[(1,), set(), {'k': 1e+16}, None, True]");
908 }
909
910 #[test]
911 fn expands_only_what_does_not_fit() {
912 use PrettyValue as V;
913 let value = V::Dict(vec![
914 (V::Str("a".into()), V::List((0..3).map(V::Int).collect())),
915 (V::Str("b".into()), V::Tuple(vec![V::Str("x".repeat(30))])),
916 ]);
917 let out = pretty_repr(&value, 20, 4, ReprLimits::default(), false);
918 assert_eq!(
919 out,
920 "{\n 'a': [0, 1, 2],\n 'b': (\n 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',\n )\n}"
921 );
922 }
923}