use {
facet::Shape,
siphasher::sip::SipHasher,
};
use crate::format::Indent;
use {
alloc::collections::VecDeque,
core::{
fmt::{
self,
Write,
},
hash::{
Hash,
Hasher,
},
str,
},
};
use {
facet_core::{
Def,
Facet,
FieldFlags,
PointerType,
PrimitiveType,
SequenceType,
StructKind,
TextualType,
Type,
TypeNameOpts,
UserType,
},
facet_reflect::Peek,
};
#[cfg(feature = "std")]
type Map<K, V> = std::collections::HashMap<K, V>;
#[cfg(not(feature = "std"))]
type Map<K, V> = alloc::collections::BTreeMap<K, V>;
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct ValueIdOrd {
pub(crate) shape: *const Shape,
pub(crate) ptr: *const u8,
}
fn id(peek: Peek) -> ValueIdOrd {
ValueIdOrd {
shape: peek.shape(),
ptr: peek.data().as_byte_ptr(),
}
}
use super::color::ColorGenerator;
pub struct PrettyPrinter {
indent_level: usize,
indent: Indent,
max_depth: Option<usize>,
color_generator: ColorGenerator,
use_colors: bool,
list_u8_as_bytes: bool,
}
impl Default for PrettyPrinter {
fn default() -> Self {
Self {
indent_level: 0,
indent: Indent::default(),
max_depth: None,
color_generator: ColorGenerator::default(),
use_colors: false,
list_u8_as_bytes: true,
}
}
}
enum StackState {
Start,
ProcessStructField { field_index: usize },
ProcessSeqItem { item_index: usize, kind: SeqKind },
ProcessBytesItem { item_index: usize },
ProcessMapEntry,
Finish { newline: bool },
OptionFinish,
}
enum SeqKind {
List,
Tuple,
}
struct StackItem<'mem, 'facet> {
value: Peek<'mem, 'facet>,
format_depth: usize,
type_depth: usize,
state: StackState,
}
impl PrettyPrinter {
pub fn new() -> Self {
Self::default()
}
pub fn with_indent_size(
mut self,
size: Indent,
) -> Self {
self.indent = size;
self
}
pub fn with_indent_level(
mut self,
level: usize,
) -> Self {
self.indent_level = level;
self
}
pub fn with_max_depth(
mut self,
depth: usize,
) -> Self {
self.max_depth = Some(depth);
self
}
pub fn with_color_generator(
mut self,
generator: ColorGenerator,
) -> Self {
self.color_generator = generator;
self
}
pub fn with_colors(
mut self,
use_colors: bool,
) -> Self {
self.use_colors = use_colors;
self
}
pub fn format<'a, T: Facet<'a>>(
&self,
value: &T,
) -> String {
let value = Peek::new(value);
let mut output = String::new();
self.format_peek_internal(value, &mut output, &mut Map::new())
.expect("Formatting failed");
output
}
pub fn format_to<'a, T: Facet<'a>>(
&self,
value: &T,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let value = Peek::new(value);
self.format_peek_internal(value, f, &mut Map::new())
}
pub fn format_peek(
&self,
value: Peek<'_, '_>,
) -> String {
let mut output = String::new();
self.format_peek_internal(value, &mut output, &mut Map::new())
.expect("Formatting failed");
output
}
pub fn format_peek_to(
&self,
value: Peek<'_, '_>,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
self.format_peek_internal(value, f, &mut Map::new())
}
pub(crate) fn format_peek_internal(
&self,
initial_value: Peek<'_, '_>,
f: &mut impl Write,
visited: &mut Map<ValueIdOrd, usize>,
) -> fmt::Result {
let mut stack = VecDeque::new();
stack.push_back(StackItem {
value: initial_value,
format_depth: self.indent_level,
type_depth: 0,
state: StackState::Start,
});
while let Some(mut item) = stack.pop_back() {
loop {
if let Ok(ptr) = item.value.into_pointer() {
if let Some(ptr) = ptr.borrow_inner() {
item.value = ptr;
continue;
}
}
break;
}
match item.state {
StackState::Start => {
if let Some(max_depth) = self.max_depth {
if item.format_depth > max_depth {
self.write_punctuation(f, "[")?;
write!(f, "...")?;
continue;
}
}
if let Some(&ptr_type_depth) = visited.get(&id(item.value)) {
if item.type_depth > ptr_type_depth + 1 {
self.write_type_name(f, item.value)?;
self.write_punctuation(f, " { ")?;
self.write_comment(
f,
&format!(
"/* cycle detected at {} (first seen at type_depth {}) */",
item.value.id(),
ptr_type_depth
),
)?;
self.write_punctuation(f, " }")?;
continue;
}
} else {
visited.insert(id(item.value), item.type_depth);
}
match (item.value.shape().def, item.value.shape().ty) {
(Def::Scalar, _) => {
self.format_scalar(item.value, f)?;
},
(Def::Option(_def), _) => {
let option = item.value.into_option().unwrap();
self.write_type_name(f, item.value)?;
if option.is_some() {
self.write_punctuation(f, "::Some(")?;
if let Some(inner_value) = option.value() {
let start_item = StackItem {
value: inner_value,
format_depth: item.format_depth,
type_depth: item.type_depth + 1,
state: StackState::Start,
};
let close_paren_item = StackItem {
value: item.value,
format_depth: item.format_depth,
type_depth: item.type_depth,
state: StackState::OptionFinish,
};
stack.push_back(close_paren_item);
stack.push_back(start_item);
}
continue;
} else {
self.write_punctuation(f, "::None")?;
}
},
(_, Type::User(UserType::Struct(struct_def)))
if struct_def.kind == StructKind::Tuple =>
{
self.write_type_name(f, item.value)?;
item.state = StackState::ProcessSeqItem {
item_index: 0,
kind: SeqKind::Tuple,
};
self.write_punctuation(f, " (")?;
writeln!(f)?;
item.format_depth += 1;
item.type_depth += 1;
stack.push_back(item);
},
(_, Type::User(UserType::Struct(_))) => {
let struct_ = item.value.into_struct().unwrap();
let newline = !(matches!(
struct_.ty().kind,
StructKind::Tuple | StructKind::TupleStruct
) && struct_.ty().fields.len() == 1
&& struct_.ty().fields[0].doc.is_empty());
let doc_comments = item.value.shape().doc;
if !doc_comments.is_empty() {
for line in doc_comments {
self.write_comment(f, &format!("///{line}"))?;
writeln!(f)?;
}
}
match struct_.ty().kind {
StructKind::Tuple => {},
_ => self.write_type_name(f, item.value)?,
}
match struct_.ty().kind {
StructKind::Unit => continue,
StructKind::Struct => {
self.write_punctuation(f, " {")?;
if struct_.field_count() == 0 {
self.write_punctuation(f, "}")?;
continue;
}
},
StructKind::Tuple | StructKind::TupleStruct => {
self.write_punctuation(f, "(")?;
if struct_.field_count() == 0 {
self.write_punctuation(f, ")")?;
continue;
}
},
}
if newline {
writeln!(f)?;
}
item.state = StackState::ProcessStructField { field_index: 0 };
item.format_depth += newline as usize;
stack.push_back(item);
},
(Def::List(_), _) => {
self.handle_list(&mut stack, item, f)?;
continue;
},
(_, Type::Sequence(SequenceType::Slice(_) | SequenceType::Array(_))) => {
self.handle_list(&mut stack, item, f)?;
},
(_, Type::Pointer(PointerType::Reference(r))) => {
let target = (r.target)();
match target.ty {
Type::Primitive(PrimitiveType::Textual(TextualType::Str)) => {
if self.use_colors {
write!(f, "\x1b[33m{}\x1b[0m", item.value)?;
} else {
write!(f, "{}", item.value)?;
}
},
_ => {
write!(f, "unsupported reference type: {:?}", item.value)?;
},
}
},
(Def::Map(_), _) => {
let _map = item.value.into_map().unwrap();
self.write_type_name(f, item.value)?;
self.write_punctuation(f, " {")?;
writeln!(f)?;
item.state = StackState::ProcessMapEntry;
item.format_depth += 1;
item.type_depth += 1; stack.push_back(item);
},
(_, Type::User(UserType::Enum(_))) => {
let enum_peek = item.value.into_enum().unwrap();
let variant = match enum_peek.active_variant() {
Ok(v) => v,
Err(_) => {
self.write_type_name(f, item.value)?;
write!(f, " /* cannot determine variant */")?;
continue;
},
};
let doc_comments = item.value.shape().doc;
for line in doc_comments {
self.write_comment(f, &format!("///{line}"))?;
writeln!(f)?;
}
for line in variant.doc {
self.write_comment(f, &format!("///{line}"))?;
writeln!(f)?;
}
self.write_type_name(f, item.value)?;
self.write_punctuation(f, "::")?;
if self.use_colors {
if self.use_colors {
write!(f, "\x1b[1m{}\x1b[0m", variant.name)?;
} else {
write!(f, "{}", variant.name)?;
}
} else {
write!(f, "{}", variant.name)?;
}
match variant.data.kind {
StructKind::Unit => {
},
StructKind::Tuple => {
self.write_punctuation(f, "(")?;
if variant.data.fields.is_empty() {
self.write_punctuation(f, ")")?;
continue;
}
let newline = !(matches!(
variant.data.kind,
StructKind::Tuple | StructKind::TupleStruct
) && variant.data.fields.len() == 1
&& variant.data.fields[0].doc.is_empty());
if newline {
writeln!(f)?;
}
item.state = StackState::ProcessStructField { field_index: 0 };
item.format_depth += newline as usize;
stack.push_back(item);
},
StructKind::Struct => {
self.write_punctuation(f, " {")?;
let has_fields = !variant.data.fields.is_empty();
if !has_fields {
self.write_punctuation(f, " }")?;
continue;
}
writeln!(f)?;
item.state = StackState::ProcessStructField { field_index: 0 };
item.format_depth += 1;
stack.push_back(item);
},
_ => {
write!(f, " /* unsupported variant kind */")?;
},
}
},
(_, Type::Pointer(PointerType::Function(_))) => {
self.write_type_name(f, item.value)?;
write!(f, " /* function pointer (not yet supported) */")?;
},
_ => {
write!(f, "unsupported peek variant: {:?}", item.value)?;
},
}
},
StackState::ProcessStructField { field_index } => {
if let Type::User(UserType::Struct(struct_)) = item.value.shape().ty {
let newline =
!(matches!(struct_.kind, StructKind::Tuple | StructKind::TupleStruct)
&& struct_.fields.len() == 1
&& struct_.fields[0].doc.is_empty());
let peek_struct = item.value.into_struct().unwrap();
if field_index >= struct_.fields.len() {
let delim = match struct_.kind {
StructKind::Unit => unreachable!(),
StructKind::Struct => "}",
StructKind::TupleStruct | StructKind::Tuple => ")",
};
if newline {
self.indent.write(f, item.format_depth - 1)?;
}
write!(f, "{}", self.style_punctuation(delim))?;
continue;
}
let field = struct_.fields[field_index];
let field_value = peek_struct.field(field_index).unwrap();
if !field.doc.is_empty() {
if field_index > 0 {
writeln!(f)?;
}
for line in field.doc {
self.indent.write(f, item.format_depth)?;
self.write_comment(f, &format!("///{line}"))?;
writeln!(f)?;
}
}
if newline {
self.indent.write(f, item.format_depth)?;
if let StructKind::Struct = struct_.kind {
self.write_field_name(f, field.name)?;
self.write_punctuation(f, ": ")?;
}
}
if field.flags.contains(FieldFlags::SENSITIVE) {
self.write_redacted(f, "[REDACTED]")?;
if newline {
self.write_punctuation(f, ",")?;
writeln!(f)?;
}
item.state = StackState::ProcessStructField {
field_index: field_index + 1,
};
stack.push_back(item);
} else {
item.state = StackState::ProcessStructField {
field_index: field_index + 1,
};
let finish_item = StackItem {
value: field_value,
format_depth: item.format_depth,
type_depth: item.type_depth + 1,
state: StackState::Finish { newline },
};
let start_item = StackItem {
value: field_value,
format_depth: item.format_depth,
type_depth: item.type_depth + 1,
state: StackState::Start,
};
stack.push_back(item);
stack.push_back(finish_item);
stack.push_back(start_item);
}
} else if let Type::User(UserType::Enum(_def)) = item.value.shape().ty {
let enum_val = item.value.into_enum().unwrap();
let variant = match enum_val.active_variant() {
Ok(v) => v,
Err(_) => {
continue;
},
};
let newline = !(matches!(
variant.data.kind,
StructKind::Tuple | StructKind::TupleStruct
) && variant.data.fields.len() == 1
&& variant.data.fields[0].doc.is_empty());
if field_index >= variant.data.fields.len() {
match variant.data.kind {
StructKind::Tuple => {
if newline {
self.indent.write(f, item.format_depth - 1)?;
}
write!(f, "{}", self.style_punctuation(")"))?;
},
StructKind::Struct => {
if newline {
self.indent.write(f, item.format_depth - 1)?;
}
write!(f, "{}", self.style_punctuation("}"))?;
},
_ => {},
}
continue;
}
let field = variant.data.fields[field_index];
let field_value = match enum_val.field(field_index) {
Ok(Some(v)) => v,
_ => {
item.state = StackState::ProcessStructField {
field_index: field_index + 1,
};
stack.push_back(item);
continue;
},
};
if newline {
self.indent.write(f, item.format_depth)?;
}
if !field.doc.is_empty() {
for line in field.doc {
self.write_comment(f, &format!("///{line}"))?;
writeln!(f)?;
self.indent.write(f, item.format_depth)?;
}
}
if let StructKind::Struct = variant.data.kind {
self.write_field_name(f, field.name)?;
self.write_punctuation(f, ": ")?;
}
item.state = StackState::ProcessStructField {
field_index: field_index + 1,
};
let finish_item = StackItem {
value: field_value,
format_depth: item.format_depth,
type_depth: item.type_depth + 1,
state: StackState::Finish { newline },
};
let start_item = StackItem {
value: field_value,
format_depth: item.format_depth,
type_depth: item.type_depth + 1,
state: StackState::Start,
};
stack.push_back(item);
stack.push_back(finish_item);
stack.push_back(start_item);
}
},
StackState::ProcessSeqItem { item_index, kind } => {
let (len, elem) = match kind {
SeqKind::List => {
let list = item.value.into_list_like().unwrap();
(list.len(), list.get(item_index))
},
SeqKind::Tuple => {
let tuple = item.value.into_tuple().unwrap();
(tuple.len(), tuple.field(item_index))
},
};
if item_index >= len {
self.indent.write(f, item.format_depth - 1)?;
self.write_punctuation(
f,
match kind {
SeqKind::List => "]",
SeqKind::Tuple => ")",
},
)?;
continue;
}
self.indent.write(f, item.format_depth)?;
item.state = StackState::ProcessSeqItem {
item_index: item_index + 1,
kind,
};
let next_format_depth = item.format_depth;
let next_type_depth = item.type_depth + 1;
stack.push_back(item);
let elem = elem.unwrap();
stack.push_back(StackItem {
value: elem,
format_depth: next_format_depth,
type_depth: next_type_depth,
state: StackState::Finish { newline: true },
});
stack.push_back(StackItem {
value: elem,
format_depth: next_format_depth,
type_depth: next_type_depth,
state: StackState::Start, });
},
StackState::ProcessBytesItem { item_index } => {
let list = item.value.into_list().unwrap();
if item_index >= list.len() {
self.indent.write(f, item.format_depth - 1)?;
continue;
}
if item_index == 0 {
write!(f, " ")?;
}
if item_index > 0 && item_index % 16 == 0 {
writeln!(f)?;
self.indent.write(f, item.format_depth)?;
} else if item_index > 0 {
write!(f, " ")?;
}
let byte_value = list.get(item_index).unwrap();
let byte = byte_value.get::<u8>().unwrap_or(&0);
let mut hasher = SipHasher::new();
byte.hash(&mut hasher);
let hash = hasher.finish();
let color = self.color_generator.generate_color(hash);
if self.use_colors {
write!(f, "\x1b[38;2;{};{};{}m", color.r, color.g, color.b)?;
}
write!(f, "{:02x}", *byte)?;
item.state = StackState::ProcessBytesItem {
item_index: item_index + 1,
};
stack.push_back(item);
},
StackState::ProcessMapEntry => {
self.indent.write(f, item.format_depth)?;
write!(f, "{}", self.style_comment("/* Map contents */"))?;
writeln!(f)?;
self.indent.write(f, item.format_depth - 1)?;
write!(f, "{}", self.style_punctuation("}"))?;
},
StackState::Finish { newline } => {
if newline {
self.write_punctuation(f, ",")?;
writeln!(f)?;
}
},
StackState::OptionFinish => {
self.write_punctuation(f, ")")?;
},
}
}
Ok(())
}
fn handle_list<'mem, 'facet>(
&self,
stack: &mut VecDeque<StackItem<'mem, 'facet>>,
mut item: StackItem<'mem, 'facet>,
f: &mut impl Write,
) -> fmt::Result {
let list = item.value.into_list_like().unwrap();
let new_type_depth =
item.type_depth + 1;
self.write_punctuation(f, "<")?;
self.write_type_name(f, item.value)?;
self.write_punctuation(f, ">")?;
if list.def().t().is_type::<u8>() && self.list_u8_as_bytes {
item.state = StackState::ProcessBytesItem { item_index: 0 };
writeln!(f)?;
write!(f, " ")?;
} else {
item.state = StackState::ProcessSeqItem {
item_index: 0,
kind: SeqKind::List,
};
self.write_punctuation(f, " [")?;
if list.is_empty() {
self.write_punctuation(f, "]")?;
return Ok(());
}
writeln!(f)?;
}
item.format_depth += 1;
item.type_depth = new_type_depth;
stack.push_back(item);
Ok(())
}
fn format_scalar(
&self,
value: Peek,
f: &mut impl Write,
) -> fmt::Result {
let mut hasher = SipHasher::new();
value.shape().id.hash(&mut hasher);
let hash = hasher.finish();
let color = self.color_generator.generate_color(hash);
struct DisplayWrapper<'mem, 'facet>(&'mem Peek<'mem, 'facet>);
impl fmt::Display for DisplayWrapper<'_, '_> {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
if self.0.shape().is_display() {
write!(f, "{}", self.0)?;
} else if self.0.shape().is_debug() {
write!(f, "{:?}", self.0)?;
} else {
write!(f, "{}", self.0.shape())?;
write!(f, "(⋯)")?;
}
Ok(())
}
}
if self.use_colors {
write!(
f,
"\x1b[38;2;{};{};{}m{}",
color.r,
color.g,
color.b,
DisplayWrapper(&value)
)?;
write!(f, "\x1b[0m")?;
} else {
write!(f, "{}", DisplayWrapper(&value))?;
}
Ok(())
}
pub(crate) fn write_type_name<W: fmt::Write>(
&self,
f: &mut W,
peek: Peek,
) -> fmt::Result {
struct TypeNameWriter<'mem, 'facet>(Peek<'mem, 'facet>);
impl core::fmt::Display for TypeNameWriter<'_, '_> {
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
self.0.type_name(f, TypeNameOpts::infinite())
}
}
let type_name = TypeNameWriter(peek);
if self.use_colors {
write!(f, "\x1b[1m{type_name}\x1b[0m") } else {
write!(f, "{type_name}")
}
}
#[allow(dead_code)]
fn style_type_name(
&self,
peek: Peek,
) -> String {
let mut result = String::new();
self.write_type_name(&mut result, peek).unwrap();
result
}
fn write_field_name<W: fmt::Write>(
&self,
f: &mut W,
name: &str,
) -> fmt::Result {
if self.use_colors {
write!(f, "\x1b[36m{name}\x1b[0m") } else {
write!(f, "{name}")
}
}
fn write_punctuation<W: fmt::Write>(
&self,
f: &mut W,
text: &str,
) -> fmt::Result {
if self.use_colors {
write!(f, "\x1b[2m{text}\x1b[0m") } else {
write!(f, "{text}")
}
}
fn style_punctuation(
&self,
text: &str,
) -> String {
let mut result = String::new();
self.write_punctuation(&mut result, text).unwrap();
result
}
fn write_comment<W: fmt::Write>(
&self,
f: &mut W,
text: &str,
) -> fmt::Result {
if self.use_colors {
write!(f, "\x1b[2m{text}\x1b[0m") } else {
write!(f, "{text}")
}
}
fn style_comment(
&self,
text: &str,
) -> String {
let mut result = String::new();
self.write_comment(&mut result, text).unwrap();
result
}
fn write_redacted<W: fmt::Write>(
&self,
f: &mut W,
text: &str,
) -> fmt::Result {
if self.use_colors {
if self.use_colors {
write!(f, "\x1b[91;1m{text}\x1b[0m") } else {
write!(f, "{text}")
}
} else {
write!(f, "{text}")
}
}
#[allow(dead_code)]
fn style_redacted(
&self,
text: &str,
) -> String {
let mut result = String::new();
self.write_redacted(&mut result, text).unwrap();
result
}
}