use crate::PerlValue;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RenderedVariable {
pub name: String,
pub value: String,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_name: Option<String>,
pub variables_reference: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub named_variables: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub indexed_variables: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub presentation_hint: Option<VariablePresentationHint>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_reference: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VariablePresentationHint {
#[serde(skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attributes: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub visibility: Option<String>,
}
impl RenderedVariable {
#[must_use]
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
Self {
name: name.into(),
value: value.into(),
type_name: None,
variables_reference: 0,
named_variables: None,
indexed_variables: None,
presentation_hint: None,
memory_reference: None,
}
}
#[must_use]
pub fn with_type(mut self, type_name: impl Into<String>) -> Self {
self.type_name = Some(type_name.into());
self
}
#[must_use]
pub fn with_reference(mut self, reference: i64) -> Self {
self.variables_reference = reference;
self
}
#[must_use]
pub fn with_indexed_variables(mut self, count: i64) -> Self {
self.indexed_variables = Some(count);
self
}
#[must_use]
pub fn with_named_variables(mut self, count: i64) -> Self {
self.named_variables = Some(count);
self
}
#[must_use]
pub fn is_expandable(&self) -> bool {
self.variables_reference != 0
}
}
pub trait VariableRenderer {
fn render(&self, name: &str, value: &PerlValue) -> RenderedVariable;
fn render_with_reference(
&self,
name: &str,
value: &PerlValue,
reference_id: i64,
) -> RenderedVariable;
fn render_children(
&self,
value: &PerlValue,
start: usize,
count: usize,
) -> Vec<RenderedVariable>;
}
#[derive(Debug, Default)]
pub struct PerlVariableRenderer {
max_string_length: usize,
max_array_preview: usize,
max_hash_preview: usize,
}
impl PerlVariableRenderer {
#[must_use]
pub fn new() -> Self {
Self { max_string_length: 100, max_array_preview: 3, max_hash_preview: 3 }
}
#[must_use]
pub fn with_max_string_length(mut self, length: usize) -> Self {
self.max_string_length = length;
self
}
#[must_use]
pub fn with_max_array_preview(mut self, count: usize) -> Self {
self.max_array_preview = count;
self
}
#[must_use]
pub fn with_max_hash_preview(mut self, count: usize) -> Self {
self.max_hash_preview = count;
self
}
fn format_string(&self, s: &str) -> String {
let truncated = if s.len() > self.max_string_length {
let mut end = self.max_string_length;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
format!("{}...", &s[..end])
} else {
s.to_string()
};
let escaped = truncated
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t");
format!("\"{}\"", escaped)
}
fn format_array_preview(&self, elements: &[PerlValue]) -> String {
if elements.is_empty() {
return "[]".to_string();
}
let preview: Vec<String> = elements
.iter()
.take(self.max_array_preview)
.map(|v| self.format_value_brief(v))
.collect();
let suffix = if elements.len() > self.max_array_preview {
format!(", ... ({} total)", elements.len())
} else {
String::new()
};
format!("[{}{}]", preview.join(", "), suffix)
}
fn format_hash_preview(&self, pairs: &[(String, PerlValue)]) -> String {
if pairs.is_empty() {
return "{}".to_string();
}
let preview: Vec<String> = pairs
.iter()
.take(self.max_hash_preview)
.map(|(k, v)| format!("{} => {}", k, self.format_value_brief(v)))
.collect();
let suffix = if pairs.len() > self.max_hash_preview {
format!(", ... ({} keys)", pairs.len())
} else {
String::new()
};
format!("{{{}{}}}", preview.join(", "), suffix)
}
fn ref_prefix_chain(&self, value: &PerlValue) -> String {
let mut depth = 0u32;
let mut current = value;
while let PerlValue::Reference(inner) = current {
depth += 1;
current = inner;
if depth >= 10 {
break;
}
}
"\\".repeat(depth as usize)
}
fn format_deref_target_brief(&self, value: &PerlValue) -> String {
let mut current = value;
let mut safety = 0u32;
while let PerlValue::Reference(inner) = current {
current = inner;
safety += 1;
if safety >= 10 {
break;
}
}
match current {
PerlValue::Reference(_) => "REF(...)".to_string(),
other => self.format_non_ref_brief(other),
}
}
fn format_deref_target(&self, value: &PerlValue) -> String {
let mut current = value;
let mut safety = 0u32;
while let PerlValue::Reference(inner) = current {
current = inner;
safety += 1;
if safety >= 10 {
break;
}
}
match current {
PerlValue::Reference(_) => "REF(...)".to_string(),
other => self.format_value(other),
}
}
fn format_non_ref_brief(&self, value: &PerlValue) -> String {
match value {
PerlValue::Reference(_) => "REF".to_string(),
other => self.format_value_brief(other),
}
}
fn format_value_brief(&self, value: &PerlValue) -> String {
match value {
PerlValue::Undef => "undef".to_string(),
PerlValue::Scalar(s) => self.format_string(s),
PerlValue::Number(n) => n.to_string(),
PerlValue::Integer(i) => i.to_string(),
PerlValue::Array(elements) => format!("ARRAY({})", elements.len()),
PerlValue::Hash(pairs) => format!("HASH({})", pairs.len()),
PerlValue::Reference(inner) => {
let prefix = self.ref_prefix_chain(value);
format!("{}{}", prefix, self.format_deref_target_brief(inner))
}
PerlValue::Object { class, .. } => format!("{}=...", class),
PerlValue::Code { name } => {
name.as_ref().map_or_else(|| "CODE(...)".to_string(), |n| format!("\\&{}", n))
}
PerlValue::Glob(name) => format!("*{}", name),
PerlValue::Regex(pattern) => format!("qr/{}/", pattern),
PerlValue::Tied { class, .. } => format!("TIED({})", class),
PerlValue::Truncated { summary, .. } => summary.clone(),
PerlValue::Error(msg) => format!("<error: {}>", msg),
}
}
fn format_value(&self, value: &PerlValue) -> String {
match value {
PerlValue::Undef => "undef".to_string(),
PerlValue::Scalar(s) => self.format_string(s),
PerlValue::Number(n) => n.to_string(),
PerlValue::Integer(i) => i.to_string(),
PerlValue::Array(elements) => self.format_array_preview(elements),
PerlValue::Hash(pairs) => self.format_hash_preview(pairs),
PerlValue::Reference(inner) => {
let prefix = self.ref_prefix_chain(value);
format!("{}{}", prefix, self.format_deref_target(inner))
}
PerlValue::Object { class, value } => {
format!("{}={}", class, self.format_value_brief(value))
}
PerlValue::Code { name } => {
name.as_ref().map_or_else(|| "sub { ... }".to_string(), |n| format!("\\&{}", n))
}
PerlValue::Glob(name) => format!("*{}", name),
PerlValue::Regex(pattern) => format!("qr/{}/", pattern),
PerlValue::Tied { class, value } => {
if let Some(v) = value {
format!("TIED({}) = {}", class, self.format_value_brief(v))
} else {
format!("TIED({})", class)
}
}
PerlValue::Truncated { summary, total_count } => {
if let Some(count) = total_count {
format!("{} ({} total)", summary, count)
} else {
summary.clone()
}
}
PerlValue::Error(msg) => format!("<error: {}>", msg),
}
}
}
impl VariableRenderer for PerlVariableRenderer {
fn render(&self, name: &str, value: &PerlValue) -> RenderedVariable {
let formatted_value = self.format_value(value);
let type_name = value.type_name().to_string();
let mut rendered = RenderedVariable::new(name, formatted_value).with_type(type_name);
match value {
PerlValue::Array(elements) => {
rendered.indexed_variables = Some(elements.len() as i64);
}
PerlValue::Hash(pairs) => {
rendered.named_variables = Some(pairs.len() as i64);
}
PerlValue::Object { class, value: inner } => {
rendered.type_name = Some(class.clone());
match inner.as_ref() {
PerlValue::Hash(pairs) => {
rendered.named_variables = Some(pairs.len() as i64);
}
PerlValue::Array(elements) => {
rendered.indexed_variables = Some(elements.len() as i64);
}
_ => {}
}
}
_ => {}
}
rendered
}
fn render_with_reference(
&self,
name: &str,
value: &PerlValue,
reference_id: i64,
) -> RenderedVariable {
let mut rendered = self.render(name, value);
if value.is_expandable() {
rendered.variables_reference = reference_id;
}
rendered
}
fn render_children(
&self,
value: &PerlValue,
start: usize,
count: usize,
) -> Vec<RenderedVariable> {
match value {
PerlValue::Array(elements) => elements
.iter()
.enumerate()
.skip(start)
.take(count)
.map(|(i, v)| self.render(&format!("[{}]", i), v))
.collect(),
PerlValue::Hash(pairs) => {
pairs.iter().skip(start).take(count).map(|(k, v)| self.render(k, v)).collect()
}
PerlValue::Reference(inner) => {
vec![self.render("$_", inner)]
}
PerlValue::Object { value: inner, .. } => self.render_children(inner, start, count),
PerlValue::Tied { value: Some(inner), .. } => self.render_children(inner, start, count),
_ => vec![],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_render_scalar() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Scalar("hello".to_string());
let rendered = renderer.render("$x", &value);
assert_eq!(rendered.name, "$x");
assert_eq!(rendered.value, "\"hello\"");
assert_eq!(rendered.type_name, Some("SCALAR".to_string()));
assert_eq!(rendered.variables_reference, 0);
}
#[test]
fn test_render_integer() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Integer(42);
let rendered = renderer.render("$n", &value);
assert_eq!(rendered.name, "$n");
assert_eq!(rendered.value, "42");
assert_eq!(rendered.type_name, Some("SCALAR".to_string()));
}
#[test]
fn test_render_array() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Array(vec![
PerlValue::Integer(1),
PerlValue::Integer(2),
PerlValue::Integer(3),
]);
let rendered = renderer.render("@arr", &value);
assert_eq!(rendered.name, "@arr");
assert!(rendered.value.starts_with('['));
assert_eq!(rendered.type_name, Some("ARRAY".to_string()));
assert_eq!(rendered.indexed_variables, Some(3));
}
#[test]
fn test_render_hash() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Hash(vec![
("key1".to_string(), PerlValue::Scalar("value1".to_string())),
("key2".to_string(), PerlValue::Integer(42)),
]);
let rendered = renderer.render("%hash", &value);
assert_eq!(rendered.name, "%hash");
assert!(rendered.value.starts_with('{'));
assert_eq!(rendered.type_name, Some("HASH".to_string()));
assert_eq!(rendered.named_variables, Some(2));
}
#[test]
fn test_render_with_reference() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Array(vec![PerlValue::Integer(1)]);
let rendered = renderer.render_with_reference("@arr", &value, 42);
assert_eq!(rendered.variables_reference, 42);
assert!(rendered.is_expandable());
}
#[test]
fn test_render_children_array() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Array(vec![
PerlValue::Integer(10),
PerlValue::Integer(20),
PerlValue::Integer(30),
]);
let children = renderer.render_children(&value, 0, 10);
assert_eq!(children.len(), 3);
assert_eq!(children[0].name, "[0]");
assert_eq!(children[0].value, "10");
assert_eq!(children[1].name, "[1]");
assert_eq!(children[2].name, "[2]");
}
#[test]
fn test_render_children_hash() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Hash(vec![
("foo".to_string(), PerlValue::Integer(1)),
("bar".to_string(), PerlValue::Integer(2)),
]);
let children = renderer.render_children(&value, 0, 10);
assert_eq!(children.len(), 2);
assert_eq!(children[0].name, "foo");
assert_eq!(children[1].name, "bar");
}
#[test]
fn test_render_object() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Object {
class: "My::Class".to_string(),
value: Box::new(PerlValue::Hash(vec![(
"attr".to_string(),
PerlValue::Scalar("value".to_string()),
)])),
};
let rendered = renderer.render("$obj", &value);
assert_eq!(rendered.name, "$obj");
assert!(rendered.value.contains("My::Class"));
assert_eq!(rendered.type_name, Some("My::Class".to_string()));
assert_eq!(rendered.named_variables, Some(1));
}
#[test]
fn test_string_truncation() {
let renderer = PerlVariableRenderer::new().with_max_string_length(10);
let value = PerlValue::Scalar("this is a very long string".to_string());
let rendered = renderer.render("$s", &value);
assert!(rendered.value.contains("..."));
assert!(rendered.value.len() < 30);
}
#[test]
fn test_string_escaping() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Scalar("line1\nline2\ttab".to_string());
let rendered = renderer.render("$s", &value);
assert!(rendered.value.contains("\\n"));
assert!(rendered.value.contains("\\t"));
}
#[test]
fn test_render_undef() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Undef;
let rendered = renderer.render("$x", &value);
assert_eq!(rendered.value, "undef");
assert_eq!(rendered.type_name, Some("undef".to_string()));
}
#[test]
fn test_render_reference() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Reference(Box::new(PerlValue::Integer(42)));
let rendered = renderer.render("$ref", &value);
assert_eq!(rendered.name, "$ref");
assert!(rendered.value.contains("42"));
assert_eq!(rendered.type_name, Some("REF".to_string()));
}
#[test]
fn test_render_code() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Code { name: Some("my_sub".to_string()) };
let rendered = renderer.render("$code", &value);
assert!(rendered.value.contains("my_sub"));
assert_eq!(rendered.type_name, Some("CODE".to_string()));
}
#[test]
fn test_render_self_referential_hash() {
let renderer = PerlVariableRenderer::new();
let circular_marker =
PerlValue::Truncated { summary: "HASH(0x...circular)".to_string(), total_count: None };
let value = PerlValue::Hash(vec![(
"a".to_string(),
PerlValue::Reference(Box::new(circular_marker)),
)]);
let rendered = renderer.render("$self", &value);
assert_eq!(rendered.name, "$self");
assert_eq!(rendered.type_name, Some("HASH".to_string()));
assert_eq!(rendered.named_variables, Some(1));
assert!(rendered.value.contains("circular"));
let children = renderer.render_children(&value, 0, 10);
assert_eq!(children.len(), 1);
assert_eq!(children[0].name, "a");
assert!(children[0].value.contains("circular"));
}
#[test]
fn test_render_deep_nesting_over_100_levels_bounded() {
let renderer = PerlVariableRenderer::new();
let mut value = PerlValue::Integer(42);
for _ in 0..150 {
value = PerlValue::Reference(Box::new(value));
}
let rendered = renderer.render("$deep", &value);
assert_eq!(rendered.name, "$deep");
assert_eq!(rendered.type_name, Some("REF".to_string()));
let backslash_prefix_count = rendered.value.chars().take_while(|&c| c == '\\').count();
assert!(
backslash_prefix_count <= 10,
"backslash prefix count {} should be <= 10",
backslash_prefix_count,
);
assert!(
rendered.value.contains("REF(...)"),
"deeply nested ref should contain REF(...), got: {}",
rendered.value,
);
assert!(
rendered.value.len() < 200,
"rendered value length {} should be < 200",
rendered.value.len(),
);
}
#[test]
fn test_render_reference_chain_at_depth_limit() {
let renderer = PerlVariableRenderer::new();
let mut value = PerlValue::Scalar("leaf".to_string());
for _ in 0..10 {
value = PerlValue::Reference(Box::new(value));
}
let rendered = renderer.render("$ref10", &value);
assert!(rendered.value.contains("leaf") || rendered.value.contains("REF(...)"));
assert!(rendered.value.len() < 200);
}
#[test]
fn test_render_large_array_over_10k_elements_truncates() {
let renderer = PerlVariableRenderer::new();
let elements: Vec<PerlValue> = (0..10_001).map(PerlValue::Integer).collect();
let value = PerlValue::Array(elements);
let rendered = renderer.render("@big", &value);
assert_eq!(rendered.name, "@big");
assert_eq!(rendered.type_name, Some("ARRAY".to_string()));
assert_eq!(rendered.indexed_variables, Some(10_001));
assert!(
rendered.value.contains("10001 total"),
"should show total count, got: {}",
rendered.value,
);
assert!(rendered.value.starts_with('['));
assert!(rendered.value.ends_with(']'));
assert!(
rendered.value.len() < 500,
"preview length {} should be < 500",
rendered.value.len(),
);
}
#[test]
fn test_render_children_large_array_pagination() {
let renderer = PerlVariableRenderer::new();
let elements: Vec<PerlValue> = (0..10_001).map(PerlValue::Integer).collect();
let value = PerlValue::Array(elements);
let children = renderer.render_children(&value, 5000, 100);
assert_eq!(children.len(), 100);
assert_eq!(children[0].name, "[5000]");
assert_eq!(children[0].value, "5000");
assert_eq!(children[99].name, "[5099]");
assert_eq!(children[99].value, "5099");
let tail = renderer.render_children(&value, 10_000, 100);
assert_eq!(tail.len(), 1);
assert_eq!(tail[0].name, "[10000]");
}
#[test]
fn test_render_large_hash_over_5k_pairs_truncates() {
let renderer = PerlVariableRenderer::new();
let pairs: Vec<(String, PerlValue)> =
(0..5_001).map(|i| (format!("key_{}", i), PerlValue::Integer(i))).collect();
let value = PerlValue::Hash(pairs);
let rendered = renderer.render("%big", &value);
assert_eq!(rendered.name, "%big");
assert_eq!(rendered.type_name, Some("HASH".to_string()));
assert_eq!(rendered.named_variables, Some(5_001));
assert!(
rendered.value.contains("5001 keys"),
"should show key count, got: {}",
rendered.value,
);
assert!(rendered.value.starts_with('{'));
assert!(rendered.value.ends_with('}'));
assert!(
rendered.value.len() < 500,
"preview length {} should be < 500",
rendered.value.len(),
);
}
#[test]
fn test_render_children_large_hash_pagination() {
let renderer = PerlVariableRenderer::new();
let pairs: Vec<(String, PerlValue)> =
(0..5_001).map(|i| (format!("key_{}", i), PerlValue::Integer(i))).collect();
let value = PerlValue::Hash(pairs);
let children = renderer.render_children(&value, 2500, 50);
assert_eq!(children.len(), 50);
assert_eq!(children[0].name, "key_2500");
assert_eq!(children[0].value, "2500");
let tail = renderer.render_children(&value, 5000, 100);
assert_eq!(tail.len(), 1);
assert_eq!(tail[0].name, "key_5000");
}
#[test]
fn test_render_blessed_object_hash_based() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Object {
class: "HTTP::Response".to_string(),
value: Box::new(PerlValue::Hash(vec![
("_rc".to_string(), PerlValue::Integer(200)),
("_content".to_string(), PerlValue::Scalar("OK".to_string())),
(
"_headers".to_string(),
PerlValue::Hash(vec![(
"Content-Type".to_string(),
PerlValue::Scalar("text/html".to_string()),
)]),
),
])),
};
let rendered = renderer.render("$resp", &value);
assert_eq!(rendered.name, "$resp");
assert_eq!(rendered.type_name, Some("HTTP::Response".to_string()));
assert_eq!(rendered.named_variables, Some(3));
assert!(rendered.value.contains("HTTP::Response"));
let children = renderer.render_children(&value, 0, 10);
assert_eq!(children.len(), 3);
assert_eq!(children[0].name, "_rc");
assert_eq!(children[0].value, "200");
assert_eq!(children[1].name, "_content");
assert!(children[1].value.contains("OK"));
}
#[test]
fn test_render_blessed_object_array_based() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Object {
class: "My::InsideOut".to_string(),
value: Box::new(PerlValue::Array(vec![
PerlValue::Scalar("field_a".to_string()),
PerlValue::Integer(99),
])),
};
let rendered = renderer.render("$io", &value);
assert_eq!(rendered.type_name, Some("My::InsideOut".to_string()));
assert_eq!(rendered.indexed_variables, Some(2));
assert!(rendered.value.contains("My::InsideOut"));
let children = renderer.render_children(&value, 0, 10);
assert_eq!(children.len(), 2);
assert_eq!(children[0].name, "[0]");
assert_eq!(children[1].name, "[1]");
}
#[test]
fn test_render_blessed_object_scalar_based() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Object {
class: "URI".to_string(),
value: Box::new(PerlValue::Scalar("https://example.com".to_string())),
};
let rendered = renderer.render("$uri", &value);
assert_eq!(rendered.type_name, Some("URI".to_string()));
assert_eq!(rendered.named_variables, None);
assert_eq!(rendered.indexed_variables, None);
assert!(rendered.value.contains("URI"));
assert!(rendered.value.contains("https://example.com"));
}
#[test]
fn test_render_blessed_object_deep_namespace() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Object {
class: "Very::Deep::Nested::Package::Name".to_string(),
value: Box::new(PerlValue::Hash(vec![])),
};
let rendered = renderer.render("$obj", &value);
assert_eq!(rendered.type_name, Some("Very::Deep::Nested::Package::Name".to_string()),);
assert!(rendered.value.contains("Very::Deep::Nested::Package::Name"));
assert_eq!(rendered.named_variables, Some(0));
}
#[test]
fn test_render_blessed_object_with_reference() {
let renderer = PerlVariableRenderer::new();
let value = PerlValue::Object {
class: "DBI::db".to_string(),
value: Box::new(PerlValue::Hash(vec![(
"Driver".to_string(),
PerlValue::Scalar("SQLite".to_string()),
)])),
};
let rendered = renderer.render_with_reference("$dbh", &value, 99);
assert_eq!(rendered.variables_reference, 99);
assert!(rendered.is_expandable());
assert_eq!(rendered.type_name, Some("DBI::db".to_string()));
}
#[test]
fn test_render_hash_with_multiple_back_references() {
let renderer = PerlVariableRenderer::new();
let grandparent_marker = PerlValue::Truncated {
summary: "HASH(0xaaa...circular)".to_string(),
total_count: Some(5),
};
let self_marker = PerlValue::Truncated {
summary: "HASH(0xbbb...circular)".to_string(),
total_count: Some(3),
};
let value = PerlValue::Hash(vec![
("parent".to_string(), PerlValue::Reference(Box::new(grandparent_marker))),
("child".to_string(), PerlValue::Reference(Box::new(self_marker))),
("name".to_string(), PerlValue::Scalar("node".to_string())),
]);
let rendered = renderer.render("$node", &value);
assert_eq!(rendered.named_variables, Some(3));
assert!(rendered.value.len() < 500);
let children = renderer.render_children(&value, 0, 10);
assert_eq!(children.len(), 3);
assert!(children[0].value.contains("circular"));
assert!(children[1].value.contains("circular"));
assert!(children[2].value.contains("node"));
}
#[test]
fn test_render_empty_collections() {
let renderer = PerlVariableRenderer::new();
let empty_arr = PerlValue::Array(vec![]);
let rendered = renderer.render("@empty", &empty_arr);
assert_eq!(rendered.value, "[]");
assert_eq!(rendered.indexed_variables, Some(0));
let empty_hash = PerlValue::Hash(vec![]);
let rendered = renderer.render("%empty", &empty_hash);
assert_eq!(rendered.value, "{}");
assert_eq!(rendered.named_variables, Some(0));
}
#[test]
fn test_render_large_array_with_custom_preview_limit() {
let renderer =
PerlVariableRenderer::new().with_max_array_preview(1).with_max_hash_preview(1);
let elements: Vec<PerlValue> = (0..100).map(PerlValue::Integer).collect();
let value = PerlValue::Array(elements);
let rendered = renderer.render("@arr", &value);
assert!(rendered.value.contains("100 total"));
assert!(rendered.value.starts_with("[0"));
}
#[test]
fn test_render_large_hash_with_custom_preview_limit() {
let renderer = PerlVariableRenderer::new().with_max_hash_preview(1);
let pairs: Vec<(String, PerlValue)> =
(0..100).map(|i| (format!("k{}", i), PerlValue::Integer(i))).collect();
let value = PerlValue::Hash(pairs);
let rendered = renderer.render("%h", &value);
assert!(rendered.value.contains("100 keys"));
assert!(rendered.value.starts_with('{'));
}
}