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