Skip to main content

rich/
pretty.rs

1//! Pretty-printing of values.
2//!
3//! Rust-native reimagining of `rich/pretty.py`. Upstream pretty-prints and
4//! colorizes a *Python* object's `repr`; Rust has no runtime reflection, so
5//! [`Pretty`] instead formats a value with its [`Debug`] implementation
6//! (pretty by default, `{:#?}`) and colorizes the result with the built-in
7//! [`ReprHighlighter`](crate::highlighter::ReprHighlighter) — numbers, strings,
8//! `None`/`Some`, paths, URLs, and so on.
9//!
10//! **Divergence:** the coloring targets repr-style output; a few Rust spellings
11//! differ from Python's (`true`/`false` vs `True`/`False`), so those tokens are
12//! left unstyled. See docs/DIVERGENCES.md.
13
14use std::fmt::Debug;
15
16use crate::console::{Console, ConsoleOptions};
17use crate::highlighter::ReprHighlighter;
18use crate::measure::Measurement;
19use crate::protocol::{Highlighter, Renderable};
20use crate::segment::Segment;
21use crate::text::Text;
22
23/// A syntax-highlighted view of a value's [`Debug`] output. Mirrors
24/// `rich.pretty.Pretty`.
25pub struct Pretty {
26    text: Text,
27}
28
29impl Pretty {
30    /// Pretty-print `value` (`{:#?}`, multi-line and indented) and highlight it.
31    pub fn new(value: &impl Debug) -> Self {
32        Pretty::from_string(format!("{value:#?}"))
33    }
34
35    /// Format `value` compactly on one line (`{:?}`) and highlight it.
36    pub fn compact(value: &impl Debug) -> Self {
37        Pretty::from_string(format!("{value:?}"))
38    }
39
40    fn from_string(rendered: String) -> Self {
41        let mut text = Text::new(rendered);
42        ReprHighlighter::new().highlight(&mut text);
43        Pretty { text }
44    }
45}
46
47impl Renderable for Pretty {
48    fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
49        self.text.rich_render(console, options)
50    }
51
52    fn measure(&self, console: &Console, options: &ConsoleOptions) -> Measurement {
53        self.text.measure(console, options)
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use crate::color::ColorSystem;
61
62    fn render(pretty: &Pretty) -> String {
63        Console::builder()
64            .force_terminal(true)
65            .color_system(Some(ColorSystem::Truecolor))
66            .width(80)
67            .no_color(false)
68            .build()
69            .render_to_string(pretty)
70    }
71
72    #[test]
73    fn highlights_numbers_in_debug_output() {
74        // Numbers get the repr.number style (bold cyan → 1;36).
75        let out = render(&Pretty::compact(&vec![1, 2, 3]));
76        assert!(out.contains("\x1b[1;36m1\x1b[0m"), "got {out:?}");
77        assert!(out.contains('3'));
78    }
79
80    #[test]
81    fn highlights_string_in_debug_output() {
82        // A quoted string gets the repr.str style (green → 32).
83        let out = render(&Pretty::compact(&"hello"));
84        assert!(out.contains("\x1b[32m\"hello\"\x1b[0m"), "got {out:?}");
85    }
86
87    #[test]
88    fn pretty_multiline_preserves_structure() {
89        // `{:#?}` on a nested collection spans multiple indented lines.
90        let out = render(&Pretty::new(&vec![vec![1, 2], vec![3, 4]]));
91        assert!(out.contains('\n'), "expected pretty multi-line layout");
92        assert!(out.contains("\x1b[1;36m4\x1b[0m"), "numbers highlighted");
93    }
94}