1use crate::console::{ConsoleOptions, RenderResult, Renderable};
8#[cfg(feature = "syntax-highlighting")]
9use crate::highlighter::ReprHighlighter;
10use crate::segment::Segment;
11use crate::text::Text;
12
13#[derive(Debug, Clone)]
19pub struct Node {
20 pub key: Option<String>,
22 pub value: Option<String>,
24 pub children: Vec<Node>,
26 pub is_container: bool,
28 pub is_iter: bool,
30 pub is_mapping: bool,
32 pub is_attrs: bool,
34 pub open_brace: String,
36 pub close_brace: String,
38 pub empty: String,
40 pub last: bool,
42}
43
44impl Node {
45 pub fn new(key: Option<String>, value: Option<String>) -> Self {
47 Self {
48 key,
49 value,
50 children: Vec::new(),
51 is_container: false,
52 is_iter: false,
53 is_mapping: false,
54 is_attrs: false,
55 open_brace: "(".to_string(),
56 close_brace: ")".to_string(),
57 empty: String::new(),
58 last: true,
59 }
60 }
61
62 pub fn add_child(&mut self, child: Node) {
64 self.children.push(child);
65 }
66}
67
68#[derive(Debug, Clone)]
77pub struct Pretty {
78 node: Node,
80 indent_guides: bool,
82 max_depth: Option<usize>,
84 max_string: Option<usize>,
86 max_length: Option<usize>,
88 expand_all: bool,
90 #[cfg(feature = "syntax-highlighting")]
92 highlighter: ReprHighlighter,
93}
94
95impl Pretty {
96 pub fn new(node: Node) -> Self {
98 Self {
99 node,
100 indent_guides: true,
101 max_depth: None,
102 max_string: None,
103 max_length: None,
104 expand_all: false,
105 #[cfg(feature = "syntax-highlighting")]
106 highlighter: ReprHighlighter::new(),
107 }
108 }
109
110 pub fn indent_guides(mut self, value: bool) -> Self {
112 self.indent_guides = value;
113 self
114 }
115
116 pub fn max_depth(mut self, depth: usize) -> Self {
118 self.max_depth = Some(depth);
119 self
120 }
121
122 pub fn max_string(mut self, max: usize) -> Self {
124 self.max_string = Some(max);
125 self
126 }
127
128 pub fn max_length(mut self, max: usize) -> Self {
130 self.max_length = Some(max);
131 self
132 }
133
134 pub fn expand_all(mut self) -> Self {
136 self.expand_all = true;
137 self
138 }
139
140 pub fn from_debug<T: std::fmt::Debug>(value: &T) -> Self {
144 let debug_str = format!("{:#?}", value);
145 let node = parse_debug_to_node(&debug_str);
146 Self::new(node)
147 }
148
149 pub fn from_json(value: &serde_json::Value) -> Self {
151 let node = json_to_node(value, None);
152 Self::new(node)
153 }
154}
155
156impl Renderable for Pretty {
157 fn render(&self, options: &ConsoleOptions) -> RenderResult {
158 let mut lines: Vec<Vec<Segment>> = Vec::new();
159 let prefix = String::new();
160 let depth = 0;
161 self.render_node(&self.node, &mut lines, &prefix, depth, options);
162 RenderResult {
163 lines,
164 items: Vec::new(),
165 }
166 }
167}
168
169impl Pretty {
170 fn render_node(
171 &self,
172 node: &Node,
173 lines: &mut Vec<Vec<Segment>>,
174 prefix: &str,
175 depth: usize,
176 options: &ConsoleOptions,
177 ) {
178 if let Some(max) = self.max_depth {
180 if depth > max && !self.expand_all {
181 lines.push(vec![
182 Segment::new(prefix),
183 Segment::new("..."),
184 Segment::line(),
185 ]);
186 return;
187 }
188 }
189
190 let indent = " ";
191 let guide = if self.indent_guides && !options.ascii_only {
192 "│ "
193 } else {
194 " "
195 };
196
197 let mut line_text = String::from(prefix);
198
199 if let Some(ref key) = node.key {
201 #[cfg(feature = "syntax-highlighting")]
202 {
203 let highlighted = self.highlighter.highlight_str(key);
204 line_text.push_str(&highlighted.plain);
205 }
206 #[cfg(not(feature = "syntax-highlighting"))]
207 {
208 line_text.push_str(key);
209 }
210 line_text.push_str(": ");
211 }
212
213 if node.children.is_empty() {
215 if let Some(ref value) = node.value {
216 let truncated = if let Some(max) = self.max_string {
217 if value.len() > max {
218 format!("{}...", &value[..max])
219 } else {
220 value.clone()
221 }
222 } else {
223 value.clone()
224 };
225 #[cfg(feature = "syntax-highlighting")]
226 let highlighted = self.highlighter.highlight_str(&truncated);
227 #[cfg(feature = "syntax-highlighting")]
228 {
229 line_text.push_str(&highlighted.plain);
230 }
231 #[cfg(not(feature = "syntax-highlighting"))]
232 {
233 line_text.push_str(&truncated);
234 }
235 }
236 lines.push(vec![Segment::new(&line_text), Segment::line()]);
237 } else {
238 line_text.push_str(&node.open_brace);
240 lines.push(vec![Segment::new(&line_text), Segment::line()]);
241
242 let max_len = self.max_length.unwrap_or(usize::MAX);
243 let count = node.children.len();
244 let show_ellipsis = count > max_len;
245
246 for (i, child) in node.children.iter().enumerate() {
247 if i >= max_len {
248 if show_ellipsis {
249 let child_prefix = format!("{prefix}{indent}");
250 lines.push(vec![
251 Segment::new(format!("{child_prefix}... ({} more)", count - max_len)),
252 Segment::line(),
253 ]);
254 }
255 break;
256 }
257 let child_prefix = if self.indent_guides && i < count - 1 {
258 format!("{prefix}{guide}")
259 } else {
260 format!("{prefix}{indent}")
261 };
262 self.render_node(child, lines, &child_prefix, depth + 1, options);
263 }
264
265 lines.push(vec![
267 Segment::new(format!("{prefix}{}", node.close_brace)),
268 Segment::line(),
269 ]);
270 }
271 }
272}
273
274pub fn install() {}
280
281pub fn pprint<T: std::fmt::Debug>(value: &T, console: &mut crate::console::Console) {
283 let pretty = Pretty::from_debug(value);
284 console.println(&pretty);
285}
286
287pub fn pretty_repr<T: std::fmt::Debug>(value: &T) -> Text {
289 let debug_str = format!("{:#?}", value);
290 #[cfg(feature = "syntax-highlighting")]
291 {
292 let highlighter = ReprHighlighter::new();
293 highlighter.highlight_str(&debug_str)
294 }
295 #[cfg(not(feature = "syntax-highlighting"))]
296 {
297 let mut t = Text::new("");
298 t.plain = debug_str;
299 t
300 }
301}
302
303pub fn traverse(value: &dyn std::fmt::Debug) -> Node {
307 let debug_str = format!("{:#?}", value);
308 parse_debug_to_node(&debug_str)
309}
310
311fn parse_debug_to_node(debug: &str) -> Node {
317 let lines: Vec<&str> = debug.lines().collect();
318 if lines.is_empty() {
319 return Node::new(None, Some(String::new()));
320 }
321
322 if lines.len() == 1 {
324 return Node::new(None, Some(lines[0].trim().to_string()));
325 }
326
327 let trimmed = debug.trim();
329 let (open_brace, close_brace) = if trimmed.starts_with('{') {
330 ("{", "}")
331 } else if trimmed.starts_with('[') {
332 ("[", "]")
333 } else {
334 ("(", ")")
335 };
336
337 let mut node = Node::new(None, None);
338 node.open_brace = open_brace.to_string();
339 node.close_brace = close_brace.to_string();
340 node.is_container = true;
341
342 for line in lines.iter().skip(1) {
344 let trimmed_line = line.trim();
345 if trimmed_line.is_empty() || trimmed_line == close_brace {
346 continue;
347 }
348 if let Some(idx) = trimmed_line.find(": ") {
350 let key = trimmed_line[..idx].trim().to_string();
351 let value = trimmed_line[idx + 2..].trim().to_string();
352 let child = Node::new(Some(key), Some(value));
353 node.children.push(child);
354 } else {
355 let child = Node::new(None, Some(trimmed_line.to_string()));
356 node.children.push(child);
357 }
358 }
359
360 node
361}
362
363fn json_to_node(value: &serde_json::Value, key: Option<String>) -> Node {
365 match value {
366 serde_json::Value::Null => {
367 let mut node = Node::new(key, Some("null".to_string()));
368 node.is_container = false;
369 node
370 }
371 serde_json::Value::Bool(b) => {
372 let mut node = Node::new(key, Some(b.to_string()));
373 node.is_container = false;
374 node
375 }
376 serde_json::Value::Number(n) => {
377 let mut node = Node::new(key, Some(n.to_string()));
378 node.is_container = false;
379 node
380 }
381 serde_json::Value::String(s) => {
382 let display = format!("\"{}\"", s);
383 let mut node = Node::new(key, Some(display));
384 node.is_container = false;
385 node
386 }
387 serde_json::Value::Array(arr) => {
388 let mut node = Node::new(key, None);
389 node.is_container = true;
390 node.is_iter = true;
391 node.open_brace = "[".to_string();
392 node.close_brace = "]".to_string();
393 node.empty = "[]".to_string();
394 for item in arr {
395 node.children.push(json_to_node(item, None));
396 }
397 node
398 }
399 serde_json::Value::Object(obj) => {
400 let mut node = Node::new(key, None);
401 node.is_container = true;
402 node.is_mapping = true;
403 node.open_brace = "{".to_string();
404 node.close_brace = "}".to_string();
405 node.empty = "{}".to_string();
406 for (k, v) in obj {
407 node.children.push(json_to_node(v, Some(k.clone())));
408 }
409 node
410 }
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 use crate::console::ConsoleOptions;
418
419 #[test]
420 fn test_pretty_from_debug() {
421 let value = vec!["hello", "world"];
422 let pretty = Pretty::from_debug(&value);
423 let opts = ConsoleOptions::default();
424 let result = pretty.render(&opts);
425 let ansi = result.to_ansi();
426 assert!(ansi.contains("hello"));
427 assert!(ansi.contains("world"));
428 }
429
430 #[test]
431 fn test_pretty_from_json() {
432 let value = serde_json::json!({"name": "Alice", "age": 30});
433 let pretty = Pretty::from_json(&value);
434 let opts = ConsoleOptions::default();
435 let result = pretty.render(&opts);
436 let ansi = result.to_ansi();
437 assert!(ansi.contains("Alice"));
438 assert!(ansi.contains("30"));
439 }
440
441 #[test]
442 fn test_pretty_repr() {
443 let text = pretty_repr(&42);
444 assert!(!text.plain.is_empty());
445 }
446
447 #[test]
448 fn test_max_depth() {
449 let inner = serde_json::json!({"a": {"b": {"c": 1}}});
450 let pretty = Pretty::from_json(&inner).max_depth(1);
451 let opts = ConsoleOptions::default();
452 let result = pretty.render(&opts);
453 let ansi = result.to_ansi();
454 assert!(ansi.contains("...") || ansi.contains("1"));
456 }
457
458 #[test]
459 fn test_json_to_node_empty_object() {
460 let value = serde_json::Value::Object(serde_json::Map::new());
461 let node = json_to_node(&value, None);
462 assert!(node.is_container);
463 assert!(node.children.is_empty());
464 }
465
466 #[test]
467 fn test_json_to_node_empty_array() {
468 let value = serde_json::Value::Array(Vec::new());
469 let node = json_to_node(&value, None);
470 assert!(node.is_container);
471 assert!(node.children.is_empty());
472 }
473
474 #[test]
475 fn test_json_to_node_scalars() {
476 let null_node = json_to_node(&serde_json::Value::Null, None);
477 assert_eq!(null_node.value.as_deref(), Some("null"));
478
479 let bool_node = json_to_node(&serde_json::Value::Bool(true), Some("flag".into()));
480 assert_eq!(bool_node.key.as_deref(), Some("flag"));
481 assert_eq!(bool_node.value.as_deref(), Some("true"));
482
483 let num_node = json_to_node(&serde_json::json!(42), None);
484 assert_eq!(num_node.value.as_deref(), Some("42"));
485
486 let str_node = json_to_node(&serde_json::json!("hello"), None);
487 assert!(str_node.value.as_deref().unwrap_or("").contains("hello"));
488 }
489
490 #[test]
491 fn test_install_is_noop() {
492 install();
494 }
495
496 #[test]
497 fn test_traverse() {
498 let node = traverse(&"test");
499 assert!(node.value.is_some());
500 }
501
502 #[test]
503 fn test_builder_methods() {
504 let node = Node::new(None, Some("value".to_string()));
505 let pretty = Pretty::new(node)
506 .indent_guides(false)
507 .max_depth(5)
508 .max_string(100)
509 .max_length(10)
510 .expand_all();
511 assert!(!pretty.indent_guides);
512 assert_eq!(pretty.max_depth, Some(5));
513 assert_eq!(pretty.max_string, Some(100));
514 assert_eq!(pretty.max_length, Some(10));
515 assert!(pretty.expand_all);
516 }
517}