use crate::segment::{Segment, Segments};
use crate::style::Style;
#[derive(Debug, Clone)]
pub struct Pretty {
content: String,
indent_size: usize,
indent_guides: bool,
max_depth: Option<usize>,
expand_all: bool,
max_length: Option<usize>,
max_string: Option<usize>,
}
impl Pretty {
#[must_use]
pub fn new<T: std::fmt::Debug>(value: &T) -> Self {
Self {
content: format!("{:#?}", value),
indent_size: 4,
indent_guides: false,
max_depth: None,
expand_all: false,
max_length: None,
max_string: None,
}
}
#[must_use]
pub fn from_string(s: impl Into<String>) -> Self {
Self {
content: s.into(),
indent_size: 4,
indent_guides: false,
max_depth: None,
expand_all: false,
max_length: None,
max_string: None,
}
}
#[must_use]
#[inline]
pub const fn indent_size(mut self, size: usize) -> Self {
self.indent_size = size;
self
}
#[must_use]
#[inline]
pub const fn indent_guides(mut self, show: bool) -> Self {
self.indent_guides = show;
self
}
#[must_use]
#[inline]
pub const fn max_depth(mut self, depth: usize) -> Self {
self.max_depth = Some(depth);
self
}
#[must_use]
#[inline]
pub const fn expand_all(mut self, expand: bool) -> Self {
self.expand_all = expand;
self
}
#[must_use]
#[inline]
pub const fn max_length(mut self, length: usize) -> Self {
self.max_length = Some(length);
self
}
#[must_use]
#[inline]
pub const fn max_string(mut self, length: usize) -> Self {
self.max_string = Some(length);
self
}
#[must_use]
pub fn render(&self, _max_width: usize) -> Segments {
let mut segments = Segments::new();
let mut current_depth = 0;
for line in self.content.lines() {
let trimmed = line.trim_start();
let indent_chars = line.len().saturating_sub(trimmed.len());
let indent_level = indent_chars.checked_div(self.indent_size).unwrap_or(0);
if let Some(max) = self.max_depth {
if indent_level > max {
if current_depth <= max {
segments.push(Segment::styled("...".to_string(), Style::default().dim()));
segments.push(Segment::newline());
}
current_depth = indent_level;
continue;
}
}
current_depth = indent_level;
if self.indent_guides && indent_level > 0 {
let guide_style = Style::default().dim();
for _ in 0..indent_level {
let spaces = " ".repeat(self.indent_size.saturating_sub(1));
segments.push(Segment::styled(format!("{}│", spaces), guide_style.clone()));
}
} else {
segments.push(Segment::new(" ".repeat(indent_chars)));
}
let styled_content = self.highlight_line(trimmed);
segments.extend(styled_content.into_iter());
segments.push(Segment::newline());
}
segments
}
fn highlight_line(&self, line: &str) -> Segments {
let mut segments = Segments::new();
let mut chars = line.chars().peekable();
let mut current = String::new();
while let Some(ch) = chars.next() {
match ch {
'"' => {
if !current.is_empty() {
segments.push(Segment::new(std::mem::take(&mut current)));
}
let mut string_content = String::from('"');
while let Some(c) = chars.next() {
string_content.push(c);
if c == '"' {
break;
}
if c == '\\' {
if let Some(escaped) = chars.next() {
string_content.push(escaped);
}
}
}
let display = if let Some(max) = self.max_string {
if string_content.len() > max.saturating_add(2) {
format!("{}...\"", &string_content[..max.saturating_add(1)])
} else {
string_content
}
} else {
string_content
};
segments.push(Segment::new(display));
}
':' => {
if !current.is_empty() {
segments.push(Segment::new(std::mem::take(&mut current)));
}
segments.push(Segment::new(":"));
}
'{' | '}' | '[' | ']' | '(' | ')' => {
if !current.is_empty() {
segments.push(Segment::new(std::mem::take(&mut current)));
}
segments.push(Segment::styled(ch.to_string(), Style::default().bold()));
}
',' => {
if !current.is_empty() {
segments.push(Segment::new(std::mem::take(&mut current)));
}
segments.push(Segment::new(","));
}
_ => {
current.push(ch);
}
}
}
if !current.is_empty() {
segments.push(Segment::new(current));
}
segments
}
}
#[must_use]
pub fn inspect<T: std::fmt::Debug>(value: &T) -> String {
let pretty = Pretty::new(value);
pretty.render(80).to_ansi()
}
#[must_use]
pub fn inspect_with_options<T: std::fmt::Debug>(
value: &T,
indent_size: usize,
max_depth: Option<usize>,
) -> String {
let mut pretty = Pretty::new(value).indent_size(indent_size);
if let Some(depth) = max_depth {
pretty = pretty.max_depth(depth);
}
pretty.render(80).to_ansi()
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn test_pretty_new() {
let value = vec![1, 2, 3];
let pretty = Pretty::new(&value);
assert!(pretty.content.contains("["));
}
#[test]
fn test_pretty_from_string() {
let pretty = Pretty::from_string("test string");
assert_eq!(pretty.content, "test string");
}
#[test]
fn test_pretty_indent_size() {
let pretty = Pretty::from_string("test").indent_size(2);
assert_eq!(pretty.indent_size, 2);
}
#[test]
fn test_pretty_indent_guides() {
let pretty = Pretty::from_string("test").indent_guides(true);
assert!(pretty.indent_guides);
}
#[test]
fn test_pretty_max_depth() {
let pretty = Pretty::from_string("test").max_depth(3);
assert_eq!(pretty.max_depth, Some(3));
}
#[test]
fn test_pretty_expand_all() {
let pretty = Pretty::from_string("test").expand_all(true);
assert!(pretty.expand_all);
}
#[test]
fn test_pretty_max_length() {
let pretty = Pretty::from_string("test").max_length(100);
assert_eq!(pretty.max_length, Some(100));
}
#[test]
fn test_pretty_max_string() {
let pretty = Pretty::from_string("test").max_string(50);
assert_eq!(pretty.max_string, Some(50));
}
#[test]
fn test_pretty_render_vec() {
let value = vec![1, 2, 3];
let pretty = Pretty::new(&value);
let segments = pretty.render(80);
let output = segments.to_ansi();
assert!(output.contains("1"));
assert!(output.contains("2"));
assert!(output.contains("3"));
}
#[test]
fn test_pretty_render_hashmap() {
let mut map = HashMap::new();
map.insert("key", "value");
let pretty = Pretty::new(&map);
let segments = pretty.render(80);
let output = segments.to_ansi();
assert!(output.contains("key"));
assert!(output.contains("value"));
}
#[test]
fn test_inspect_function() {
let value = (1, "hello", true);
let output = inspect(&value);
assert!(output.contains("hello"));
}
#[test]
fn test_inspect_with_options() {
let value = vec![vec![1, 2], vec![3, 4]];
let output = inspect_with_options(&value, 2, Some(1));
assert!(!output.is_empty());
}
}