use crate::console::{Console, ConsoleOptions, Renderable};
use crate::highlighter::{Highlighter, ReprHighlighter};
use crate::panel::Panel;
use crate::segment::Segment;
use crate::style::Style;
use crate::text::Text;
use std::fmt;
pub struct Inspect<'a> {
value: &'a dyn fmt::Debug,
type_name: &'static str,
label: Option<String>,
doc: Option<String>,
pretty: bool,
title: Option<String>,
}
impl<'a> Inspect<'a> {
pub fn new<T: fmt::Debug + 'static>(value: &'a T) -> Self {
Self {
value,
type_name: std::any::type_name::<T>(),
label: None,
doc: None,
pretty: true,
title: None,
}
}
#[must_use]
pub fn with_label(mut self, label: &str) -> Self {
self.label = Some(label.to_string());
self
}
#[must_use]
pub fn with_doc(mut self, doc: &str) -> Self {
self.doc = Some(doc.to_string());
self
}
#[must_use]
pub fn with_pretty(mut self, pretty: bool) -> Self {
self.pretty = pretty;
self
}
#[must_use]
pub fn with_title(mut self, title: &str) -> Self {
self.title = Some(title.to_string());
self
}
fn short_type_name(&self) -> &str {
let full = self.type_name;
let prefix = full.split('<').next().unwrap_or(full);
prefix.rsplit("::").next().unwrap_or(full)
}
fn build_content(&self) -> Text {
let mut parts = Vec::new();
parts.push(format!(
"[bold cyan]Type:[/bold cyan] [italic]{}[/italic]",
self.type_name
));
if let Some(label) = &self.label {
parts.push(format!("[bold cyan]Name:[/bold cyan] {}", label));
}
if let Some(doc) = &self.doc {
parts.push(format!(
"[bold cyan]Doc:[/bold cyan] [dim italic]{}[/dim italic]",
doc
));
}
parts.push(String::new());
parts.push("[bold cyan]Value:[/bold cyan]".to_string());
let markup_part = parts.join("\n");
let mut text = Text::from_markup(&markup_part)
.unwrap_or_else(|_| Text::new(&markup_part, Style::null()));
let debug_str = if self.pretty {
format!("{:#?}", self.value)
} else {
format!("{:?}", self.value)
};
let mut debug_text = Text::new(&format!("\n{}", debug_str), Style::null());
let highlighter = ReprHighlighter::new();
highlighter.highlight(&mut debug_text);
text.append_text(&debug_text);
text
}
}
impl Renderable for Inspect<'_> {
fn gilt_console(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
let content = self.build_content();
let mut panel = Panel::new(content);
let title_str = self
.title
.clone()
.unwrap_or_else(|| format!("Inspect: {}", self.short_type_name()));
panel.title = Some(Text::new(&title_str, Style::null()));
panel.gilt_console(console, options)
}
}
impl fmt::Display for Inspect<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let w = f.width().unwrap_or(80);
let mut console = Console::builder()
.width(w)
.force_terminal(true)
.no_color(true)
.build();
console.begin_capture();
console.print(self);
let output = console.end_capture();
write!(f, "{}", output.trim_end_matches('\n'))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn capture_inspect(inspect: &Inspect) -> String {
let mut console = Console::builder()
.width(80)
.force_terminal(true)
.no_color(true)
.build();
console.begin_capture();
console.print(inspect);
console.end_capture()
}
#[test]
fn test_inspect_vec_i32() {
let data = vec![1, 2, 3];
let inspect = Inspect::new(&data);
let output = capture_inspect(&inspect);
assert!(
output.contains("Vec"),
"output should contain 'Vec': {}",
output
);
assert!(
output.contains("1"),
"output should contain '1': {}",
output
);
assert!(
output.contains("2"),
"output should contain '2': {}",
output
);
assert!(
output.contains("3"),
"output should contain '3': {}",
output
);
}
#[test]
fn test_inspect_string() {
let data = String::from("hello world");
let inspect = Inspect::new(&data);
let output = capture_inspect(&inspect);
assert!(
output.contains("String"),
"output should contain 'String': {}",
output
);
assert!(
output.contains("hello world"),
"output should contain the value: {}",
output
);
}
#[derive(Debug)]
struct TestPoint {
x: f64,
y: f64,
}
#[test]
fn test_inspect_struct() {
let point = TestPoint { x: 1.5, y: 2.5 };
let inspect = Inspect::new(&point);
let output = capture_inspect(&inspect);
assert!(
output.contains("TestPoint"),
"output should contain type name: {}",
output
);
assert!(
output.contains("1.5"),
"output should contain x value: {}",
output
);
assert!(
output.contains("2.5"),
"output should contain y value: {}",
output
);
}
#[test]
fn test_inspect_with_label() {
let data = 42u32;
let inspect = Inspect::new(&data).with_label("answer");
let output = capture_inspect(&inspect);
assert!(
output.contains("Name:"),
"output should contain 'Name:': {}",
output
);
assert!(
output.contains("answer"),
"output should contain label: {}",
output
);
}
#[test]
fn test_inspect_with_doc() {
let data = 42u32;
let inspect = Inspect::new(&data).with_doc("The meaning of life");
let output = capture_inspect(&inspect);
assert!(
output.contains("Doc:"),
"output should contain 'Doc:': {}",
output
);
assert!(
output.contains("The meaning of life"),
"output should contain doc text: {}",
output
);
}
#[test]
fn test_inspect_with_custom_title() {
let data = vec![1, 2, 3];
let inspect = Inspect::new(&data).with_title("My Custom Title");
let output = capture_inspect(&inspect);
assert!(
output.contains("My Custom Title"),
"output should contain custom title: {}",
output
);
}
#[test]
fn test_inspect_pretty_false() {
let data = vec![1, 2, 3];
let compact = Inspect::new(&data).with_pretty(false);
let pretty = Inspect::new(&data).with_pretty(true);
let compact_output = capture_inspect(&compact);
let pretty_output = capture_inspect(&pretty);
assert!(
compact_output.len() <= pretty_output.len(),
"compact output ({}) should be no longer than pretty output ({})",
compact_output.len(),
pretty_output.len()
);
assert!(
compact_output.contains("[1, 2, 3]"),
"compact should contain [1, 2, 3]: {}",
compact_output
);
}
#[test]
fn test_display_trait() {
let data = vec![1, 2, 3];
let inspect = Inspect::new(&data);
let output = format!("{}", inspect);
assert!(!output.is_empty(), "Display output should not be empty");
assert!(
output.contains("Vec"),
"Display output should contain 'Vec': {}",
output
);
}
#[test]
fn test_renderable_produces_segments() {
let data = 42u32;
let inspect = Inspect::new(&data);
let console = Console::builder().width(80).force_terminal(true).build();
let options = console.options();
let segments = inspect.gilt_console(&console, &options);
assert!(!segments.is_empty(), "Renderable should produce segments");
}
#[test]
fn test_type_name_extracted() {
let data = vec![1, 2, 3];
let inspect = Inspect::new(&data);
assert!(
inspect.type_name.contains("Vec"),
"type_name should contain 'Vec': {}",
inspect.type_name
);
}
#[test]
fn test_short_type_name_in_default_title() {
let data = vec![1, 2, 3];
let inspect = Inspect::new(&data);
let short = inspect.short_type_name();
assert_eq!(
short, "Vec",
"short_type_name should be 'Vec', got: {}",
short
);
let output = capture_inspect(&inspect);
assert!(
output.contains("Inspect: Vec"),
"default title should contain 'Inspect: Vec': {}",
output
);
}
#[derive(Debug)]
struct EmptyStruct;
#[test]
fn test_inspect_empty_struct() {
let data = EmptyStruct;
let inspect = Inspect::new(&data);
let output = capture_inspect(&inspect);
assert!(
output.contains("EmptyStruct"),
"output should contain 'EmptyStruct': {}",
output
);
}
#[test]
fn test_inspect_option_some() {
let data: Option<i32> = Some(42);
let inspect = Inspect::new(&data);
let output = capture_inspect(&inspect);
assert!(
output.contains("Option"),
"output should contain 'Option': {}",
output
);
assert!(
output.contains("42"),
"output should contain '42': {}",
output
);
}
#[test]
fn test_inspect_option_none() {
let data: Option<i32> = None;
let inspect = Inspect::new(&data);
let output = capture_inspect(&inspect);
assert!(
output.contains("None"),
"output should contain 'None': {}",
output
);
}
#[test]
fn test_inspect_hashmap() {
use std::collections::HashMap;
let mut data = HashMap::new();
data.insert("key", "value");
let inspect = Inspect::new(&data);
let output = capture_inspect(&inspect);
assert!(
output.contains("HashMap"),
"output should contain 'HashMap': {}",
output
);
assert!(
output.contains("key"),
"output should contain 'key': {}",
output
);
}
#[test]
fn test_inspect_builder_chaining() {
let data = 42u32;
let inspect = Inspect::new(&data)
.with_label("answer")
.with_doc("The answer to everything")
.with_title("Custom")
.with_pretty(false);
let output = capture_inspect(&inspect);
assert!(output.contains("answer"), "should have label: {}", output);
assert!(
output.contains("The answer to everything"),
"should have doc: {}",
output
);
assert!(
output.contains("Custom"),
"should have custom title: {}",
output
);
}
#[test]
fn test_inspect_display_with_width() {
let data = vec![1, 2, 3];
let inspect = Inspect::new(&data);
let output = format!("{:40}", inspect);
assert!(!output.is_empty(), "Display with width should not be empty");
}
#[test]
fn test_short_type_name_simple() {
let data = 42u32;
let inspect = Inspect::new(&data);
assert_eq!(inspect.short_type_name(), "u32");
}
#[test]
fn test_short_type_name_nested_generic() {
let data: Option<Vec<i32>> = Some(vec![1]);
let inspect = Inspect::new(&data);
assert_eq!(inspect.short_type_name(), "Option");
}
#[test]
fn test_type_name_field() {
let data = String::from("test");
let inspect = Inspect::new(&data);
assert!(inspect.type_name.contains("String"));
}
}