hegel/pretty.rs
1//! Pretty-printing of generated values.
2//!
3//! [`Document`] owns one pretty-printed document: its builder methods
4//! choose the layout options, [`Document::printer`] exposes the surface to
5//! write through, and [`Document::finish`] consumes it to render exactly
6//! once at the end.
7//!
8//! [`PrettyPrinter`] is that write surface, wrapping libhegel's layout
9//! engine (an Oppen-style pretty-printer ported from Hypothesis's
10//! `hypothesis.vendor.pretty`). Output is built from three primitives:
11//! [`PrettyPrinter::text`] emits literal text, [`PrettyPrinter::breakable`]
12//! marks a point that renders as a separator when the enclosing group fits
13//! on one line and as a newline plus indentation when it does not, and
14//! [`PrettyPrinter::begin_group`] / [`PrettyPrinter::end_group`] delimit
15//! the groups those decisions are made over. A group either fits — every
16//! breakable renders as its separator — or breaks as a whole, outermost
17//! groups first.
18//!
19//! [`PrettyPrintable`] is the protocol a value uses to describe its own
20//! representation, in Rust-expression syntax wherever possible. It is
21//! implemented for the standard types the generator library produces,
22//! derivable for user types with `#[derive(PrettyPrintable)]`, and
23//! available for any `Debug` type — without writing an implementation —
24//! through [`pretty_print_as_debug!`](crate::pretty_print_as_debug).
25
26use crate::ffi::{PrinterCallError, PrinterHandle};
27use std::cell::Cell;
28use std::marker::PhantomData;
29
30/// Accept a printer operation's outcome: misuse panics with libhegel's
31/// diagnostic, while writing to a dead region — a straggling thread printing
32/// after the document was read, or into a region whose anchor was retracted
33/// — is a silent no-op, so a writer that outlives its document never brings
34/// the process down.
35fn tolerate(result: Result<(), PrinterCallError>) {
36 match result {
37 Ok(()) | Err(PrinterCallError::DeadRegion) => {}
38 Err(PrinterCallError::Other(message)) => panic!("{message}"),
39 }
40}
41use crate::test_case::invalid_argument;
42
43/// The line width documents are laid out to when none is configured.
44pub(crate) const DEFAULT_MAX_WIDTH: u64 = 79;
45
46/// One pretty-printed document: the owner of its layout options, its
47/// content, and its rendering.
48///
49/// Configure the layout with the builder methods (before anything is
50/// printed), write content through [`printer`](Document::printer), and
51/// render by consuming the document with [`finish`](Document::finish) —
52/// rendering happens exactly once, at the end. The [`PrettyPrinter`] this
53/// hands out is write-only, so code that is *given* a printer (a
54/// [`PrettyPrintable`] implementation, a
55/// [`PrintableGenerator`](crate::PrintableGenerator)) can never render or
56/// otherwise observe the document it is contributing to.
57///
58/// # Example
59///
60/// ```
61/// use hegel::Document;
62///
63/// let mut doc = Document::new().max_width(10);
64/// let p = doc.printer();
65/// p.begin_group(1, "[");
66/// p.text("first");
67/// p.text(",");
68/// p.breakable(" ");
69/// p.text("second");
70/// p.end_group("]");
71/// assert_eq!(doc.finish(), "[first,\n second]");
72/// ```
73#[derive(Debug)]
74pub struct Document {
75 max_width: u64,
76 printer: Option<PrettyPrinter>,
77}
78
79impl Document {
80 /// Create an empty document with the default layout options (a maximum
81 /// line width of 79 characters).
82 pub fn new() -> Self {
83 Document {
84 max_width: DEFAULT_MAX_WIDTH,
85 printer: None,
86 }
87 }
88
89 /// Keep lines within `max_width` characters where the group structure
90 /// allows it. Defaults to 79.
91 ///
92 /// Layout options describe the whole document, so they must be chosen
93 /// up front: calling this after [`printer`](Document::printer) has been
94 /// used is an error, as is a `max_width` of 0.
95 pub fn max_width(mut self, max_width: usize) -> Self {
96 if self.printer.is_some() {
97 invalid_argument!("max_width must be set before the document is printed to");
98 }
99 if max_width == 0 {
100 invalid_argument!("max_width must be positive");
101 }
102 self.max_width = max_width as u64;
103 self
104 }
105
106 /// The printer to write this document's content through.
107 pub fn printer(&mut self) -> &mut PrettyPrinter {
108 self.printer
109 .get_or_insert_with(|| PrettyPrinter::from_handle(PrinterHandle::new(self.max_width)))
110 }
111
112 /// Splice any outstanding deferred content into place, lay the document
113 /// out, and return it.
114 ///
115 /// Consuming the document is what makes rendering a once-at-the-end
116 /// operation; there is no way to observe a partially built document.
117 pub fn finish(mut self) -> String {
118 match &mut self.printer {
119 Some(printer) => printer.value(),
120 None => String::new(),
121 }
122 }
123}
124
125impl Default for Document {
126 fn default() -> Self {
127 Document::new()
128 }
129}
130
131/// The write surface of a pretty-printed document.
132///
133/// See the [module docs](self) for the printing model. Obtained from
134/// [`Document::printer`] — or received, already positioned, by printing
135/// code such as a [`PrettyPrintable`] implementation. Rejections of the
136/// layout protocol (an [`end_group`](PrettyPrinter::end_group) with no open
137/// group) panic, since they indicate a bug in the calling printing code.
138pub struct PrettyPrinter {
139 /// `None` is the no-op printer: every emitting method returns without
140 /// doing anything, so one drawing body can serve both the silent and the
141 /// printing draw paths.
142 handle: Option<PrinterHandle>,
143 /// A printer belongs to one thread at a time (it may move — the type is
144 /// `Send` — but never be shared), exactly like [`TestCase`]: the region
145 /// model makes cross-thread output deterministic only because each
146 /// region has a single writer.
147 ///
148 /// [`TestCase`]: crate::TestCase
149 _single_owner: PhantomData<Cell<()>>,
150}
151
152impl std::fmt::Debug for PrettyPrinter {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 f.debug_struct("PrettyPrinter")
155 .field("handle", &self.handle)
156 .finish()
157 }
158}
159
160impl PrettyPrinter {
161 /// Create a printer that discards everything printed to it.
162 ///
163 /// This is how a [`PrintableGenerator`](crate::PrintableGenerator) with
164 /// one shared drawing body implements its silent path:
165 /// [`Generator::do_draw`](crate::Generator::do_draw) simply calls
166 /// `self.do_draw_and_print(tc, &mut PrettyPrinter::noop())`. The
167 /// contract that both paths consume identical choices then holds by
168 /// construction. Guard any expensive formatting with
169 /// [`should_print`](PrettyPrinter::should_print) so the silent path
170 /// stays cheap.
171 pub fn noop() -> Self {
172 PrettyPrinter {
173 handle: None,
174 _single_owner: PhantomData,
175 }
176 }
177
178 /// Whether printing to this printer produces output: `false` for the
179 /// discarding printer returned by [`noop`](PrettyPrinter::noop). Use it
180 /// to skip work — formatting a value, say — whose only purpose is to be
181 /// printed.
182 pub fn should_print(&self) -> bool {
183 self.handle.is_some()
184 }
185
186 /// Wrap an existing engine printer handle (e.g. a test case's shared
187 /// document).
188 pub(crate) fn from_handle(handle: PrinterHandle) -> Self {
189 PrettyPrinter {
190 handle: Some(handle),
191 _single_owner: PhantomData,
192 }
193 }
194
195 /// Emit literal, unbreakable text.
196 ///
197 /// Newlines in `s` are honored as unconditional line breaks (equivalent
198 /// to [`hard_break`](PrettyPrinter::hard_break), so the new line starts
199 /// at the current indentation).
200 pub fn text(&mut self, s: &str) {
201 let Some(handle) = &self.handle else { return };
202 let mut first = true;
203 for segment in s.split('\n') {
204 if !first {
205 tolerate(handle.hard_break());
206 }
207 first = false;
208 if !segment.is_empty() {
209 tolerate(handle.text(segment));
210 }
211 }
212 }
213
214 /// Emit a potential break point: renders as `sep` if the enclosing group
215 /// fits on the current line, and as a newline plus the current
216 /// indentation if the group breaks.
217 pub fn breakable(&mut self, sep: &str) {
218 let Some(handle) = &self.handle else { return };
219 tolerate(handle.breakable(sep));
220 }
221
222 /// Emit an unconditional newline followed by the current indentation.
223 pub fn hard_break(&mut self) {
224 let Some(handle) = &self.handle else { return };
225 tolerate(handle.hard_break());
226 }
227
228 /// Open a group: emit `open`, then increase the indentation applied by
229 /// subsequent break points by `indent` (conventionally the width of
230 /// `open`, so continuation lines align just inside the delimiter).
231 pub fn begin_group(&mut self, indent: usize, open: &str) {
232 let Some(handle) = &self.handle else { return };
233 tolerate(handle.begin_group(indent as u64, open));
234 }
235
236 /// Close the innermost group: undo the indentation its
237 /// [`begin_group`](PrettyPrinter::begin_group) added, then emit `close`.
238 /// Panics if no group is open.
239 pub fn end_group(&mut self, close: &str) {
240 let Some(handle) = &self.handle else { return };
241 tolerate(handle.end_group(close));
242 }
243
244 /// Adjust the indentation applied by subsequent break points by `delta`.
245 pub fn shift_indent(&mut self, delta: isize) {
246 let Some(handle) = &self.handle else { return };
247 tolerate(handle.shift_indent(delta as i64));
248 }
249
250 /// Attach a comment to the line currently being written: `text` is
251 /// rendered as ` // text` at the end of that line, every group open at
252 /// this position is forced to break — nothing else may share a line with
253 /// a comment — and the comment is excluded from line-width accounting. A
254 /// group forced to break by a comment also breaks before its closing
255 /// delimiter, so the delimiter is not caught up in a comment on the
256 /// group's last element.
257 ///
258 /// `text` must not contain newlines; a comment is a single-line
259 /// construct.
260 pub fn comment(&mut self, text: &str) {
261 let Some(handle) = &self.handle else { return };
262 tolerate(handle.comment(&format!(" // {text}")));
263 }
264
265 /// Splice in any outstanding deferred content, flush pending break
266 /// points, and return everything printed so far. Only ever called by an
267 /// owner of the document — [`Document::finish`], or the run lifecycle
268 /// reading a test case's document — never by printing code, which only
269 /// sees the write surface. Panics on a layout error in the printed
270 /// content (an unbalanced `end_group` that could only be detected once
271 /// the whole document was assembled).
272 pub(crate) fn value(&mut self) -> String {
273 self.try_value()
274 .unwrap_or_else(|message| panic!("{message}"))
275 }
276
277 /// [`value`](PrettyPrinter::value), reporting a layout error in the
278 /// printed content as an `Err` instead of panicking — for the run
279 /// lifecycle, which renders the output of user printing code after the
280 /// test body's panic handling has finished and must not let a printing
281 /// bug take down the whole run.
282 pub(crate) fn try_value(&mut self) -> Result<String, String> {
283 let Some(handle) = &self.handle else {
284 unreachable!("only rendering printers have their value read");
285 };
286 let _ = handle.resolve();
287 match handle.value() {
288 Ok(rendered) => Ok(rendered),
289 Err(PrinterCallError::Other(message)) => Err(message),
290 Err(PrinterCallError::DeadRegion) => {
291 unreachable!("a document's own region never dies before it renders")
292 }
293 }
294 }
295
296 /// Open a speculative region: output printed through the returned
297 /// [`Speculation`] is held back until [`Speculation::commit`] emits it or
298 /// [`Speculation::abort`] discards it. Dropping the `Speculation` without
299 /// committing (e.g. on unwind) aborts it.
300 ///
301 /// This is how draw-time printing survives rejection: a combinator that
302 /// may retract a draw — a filter retry, a rejected collection element —
303 /// prints each attempt inside a speculative region and only commits the
304 /// accepted one.
305 pub fn speculate(&mut self) -> Speculation<'_> {
306 if let Some(handle) = &self.handle {
307 tolerate(handle.begin_speculative());
308 }
309 Speculation {
310 printer: self,
311 resolved: false,
312 }
313 }
314}
315
316/// Cloning a printer opens a *child region*: a hole in the document,
317/// anchored at the printer's current position, that the clone writes into.
318///
319/// Whatever the clone prints — at any later point, from any thread that owns
320/// it — appears at the anchor when the document renders, with line-breaking
321/// behaving as if it had been printed inline. This is how output crosses
322/// threads deterministically (each clone's output lands where the clone was
323/// made, however the threads were scheduled), and how a generator whose
324/// value's representation is only known during test execution (a
325/// Hegel-controlled random number generator, say) prints: it clones the
326/// printer at draw time and records into the clone as the value is used.
327///
328/// A child region dies when the document renders, or when a speculative
329/// region its anchor sat inside is aborted; a dead region's writes are
330/// silent no-ops, so a clone that outlives its document can keep trying to
331/// record without consequence. Cloning a no-op printer yields a no-op
332/// printer, and cloning into a dead region yields a printer whose writes
333/// discard.
334impl Clone for PrettyPrinter {
335 fn clone(&self) -> Self {
336 let handle = match &self.handle {
337 None => None,
338 Some(handle) => match handle.deferred() {
339 Ok(child) => Some(child),
340 Err(PrinterCallError::DeadRegion) => None,
341 Err(PrinterCallError::Other(message)) => unreachable!("{message}"),
342 },
343 };
344 PrettyPrinter {
345 handle,
346 _single_owner: PhantomData,
347 }
348 }
349}
350
351/// An open speculative region on a [`PrettyPrinter`]; see
352/// [`PrettyPrinter::speculate`].
353#[derive(Debug)]
354pub struct Speculation<'a> {
355 printer: &'a mut PrettyPrinter,
356 resolved: bool,
357}
358
359impl Speculation<'_> {
360 /// The printer to print the speculative output through.
361 pub fn printer(&mut self) -> &mut PrettyPrinter {
362 self.printer
363 }
364
365 /// Close the region, keeping its output.
366 pub fn commit(mut self) {
367 self.resolved = true;
368 if let Some(handle) = &self.printer.handle {
369 tolerate(handle.commit_speculative());
370 }
371 }
372
373 /// Close the region, discarding its output.
374 pub fn abort(mut self) {
375 self.resolved = true;
376 if let Some(handle) = &self.printer.handle {
377 tolerate(handle.abort_speculative());
378 }
379 }
380}
381
382/// Dropping an uncommitted speculation — most importantly during an unwind
383/// out of a speculative draw, such as a budget-exhausted `StopTest` or a
384/// failed assumption mid-attempt — discards its output, so a partial attempt
385/// never corrupts the document. The result is deliberately ignored: this can
386/// run during a panic, where a second panic would abort the process.
387impl Drop for Speculation<'_> {
388 fn drop(&mut self) {
389 if !self.resolved {
390 if let Some(handle) = &self.printer.handle {
391 let _ = handle.abort_speculative();
392 }
393 }
394 }
395}
396
397/// Print a `{:?}` representation through the layout machinery.
398///
399/// The output of a derived `Debug` implementation follows a small grammar —
400/// `Name { field: value, … }`, `Name(…)`, `(…)`, `[…]`, `{key: value, …}`,
401/// string and character literals, atoms — and this function re-emits it
402/// through the printer's group and breakable primitives, so a large value
403/// wraps exactly like one printed by `#[derive(PrettyPrintable)]`. Anything
404/// that doesn't parse as that grammar (a hand-written `Debug` can produce
405/// arbitrary text) is emitted verbatim, with embedded newlines honored as
406/// hard breaks.
407///
408/// This is the engine behind [`pretty_print_as_debug!`](crate::pretty_print_as_debug)
409/// and [`print_as_debug`](crate::Generator::print_as_debug); it is exposed
410/// for hand-written [`PrettyPrintable`] implementations that want to embed a
411/// `Debug` representation in a larger layout.
412pub fn print_debug_repr(repr: &str, printer: &mut PrettyPrinter) {
413 match DebugRepr::parse(repr) {
414 Some(nodes) => emit_debug_nodes(&nodes, printer),
415 None => printer.text(repr),
416 }
417}
418
419/// One parsed piece of a `Debug` representation: literal text, or a
420/// delimited group laid out with a breakable point after each comma.
421enum DebugNode {
422 Leaf(String),
423 Group {
424 /// The atom glued to the open delimiter (`Some` in `Some(5)`, `Name`
425 /// in `Name { … }`); empty for bare tuples, lists, and map braces.
426 prefix: String,
427 delimiter: char,
428 /// Brace group in derived struct style (`Name { … }`, spaces inside
429 /// the braces) as opposed to map style (`{… }`).
430 named: bool,
431 items: Vec<Vec<DebugNode>>,
432 },
433}
434
435/// Recursive-descent parser over the derived-`Debug` grammar. Any input
436/// outside the grammar makes a parsing method return `None`, and the whole
437/// representation falls back to verbatim text.
438struct DebugRepr {
439 chars: Vec<char>,
440 pos: usize,
441 depth: usize,
442}
443
444/// How deeply groups may nest before [`DebugRepr::parse`] gives up. The
445/// parser, the emitter, and the parsed tree's destructor all recurse
446/// per nesting level, so an unbounded representation would overflow the
447/// stack during failure reporting; past this depth the representation is
448/// emitted verbatim instead.
449const MAX_DEBUG_DEPTH: usize = 64;
450
451impl DebugRepr {
452 fn parse(repr: &str) -> Option<Vec<DebugNode>> {
453 if repr.contains('\n') {
454 return None;
455 }
456 let mut parser = DebugRepr {
457 chars: repr.chars().collect(),
458 pos: 0,
459 depth: 0,
460 };
461 let nodes = parser.parse_item()?;
462 if parser.pos != parser.chars.len() {
463 return None;
464 }
465 Some(nodes)
466 }
467
468 fn peek(&self) -> Option<char> {
469 self.chars.get(self.pos).copied()
470 }
471
472 fn peek_next(&self) -> Option<char> {
473 self.chars.get(self.pos + 1).copied()
474 }
475
476 fn bump(&mut self) -> Option<char> {
477 let c = self.peek()?;
478 self.pos += 1;
479 Some(c)
480 }
481
482 /// Parse one comma-separated item — literal runs and nested groups —
483 /// stopping (without consuming) at a `", "`, a close delimiter, or the
484 /// end of the input.
485 fn parse_item(&mut self) -> Option<Vec<DebugNode>> {
486 let mut nodes = Vec::new();
487 let mut text = String::new();
488 loop {
489 match self.peek() {
490 None | Some(']' | ')' | '}') => break,
491 Some(',') if self.peek_next() == Some(' ') => break,
492 Some(' ') if self.peek_next() == Some('}') => break,
493 Some('"' | '\'') => {
494 flush_text(&mut text, &mut nodes);
495 nodes.push(DebugNode::Leaf(self.lex_quoted()?));
496 }
497 Some(delimiter @ ('[' | '(' | '{')) => {
498 let prefix = take_group_prefix(&mut text, delimiter);
499 flush_text(&mut text, &mut nodes);
500 nodes.push(self.parse_group(prefix)?);
501 }
502 Some(c) => {
503 text.push(c);
504 self.bump();
505 }
506 }
507 }
508 flush_text(&mut text, &mut nodes);
509 Some(nodes)
510 }
511
512 /// Parse a delimited group whose open delimiter is the current char.
513 fn parse_group(&mut self, prefix: String) -> Option<DebugNode> {
514 if self.depth == MAX_DEBUG_DEPTH {
515 return None;
516 }
517 self.depth += 1;
518 let delimiter = self.bump()?;
519 let close = match delimiter {
520 '[' => ']',
521 '(' => ')',
522 _ => '}',
523 };
524 let named = delimiter == '{' && !prefix.is_empty() && self.peek() == Some(' ');
525 if named {
526 self.bump();
527 }
528 let mut items = Vec::new();
529 if !named && self.peek() == Some(close) {
530 self.bump();
531 } else {
532 loop {
533 items.push(self.parse_item()?);
534 match self.peek() {
535 Some(',') if self.peek_next() == Some(' ') => {
536 self.bump();
537 self.bump();
538 }
539 Some(' ') if named && self.peek_next() == Some(close) => {
540 self.bump();
541 self.bump();
542 break;
543 }
544 Some(c) if !named && c == close => {
545 self.bump();
546 break;
547 }
548 _ => return None,
549 }
550 }
551 }
552 self.depth -= 1;
553 Some(DebugNode::Group {
554 prefix,
555 delimiter,
556 named,
557 items,
558 })
559 }
560
561 /// Lex a string or character literal, including its quotes. A backslash
562 /// escapes the following character, which is all the lexer needs: no
563 /// escape sequence contains an unescaped closing quote.
564 fn lex_quoted(&mut self) -> Option<String> {
565 let quote = self.bump()?;
566 let mut lit = String::new();
567 lit.push(quote);
568 loop {
569 let c = self.bump()?;
570 lit.push(c);
571 if c == '\\' {
572 lit.push(self.bump()?);
573 } else if c == quote {
574 return Some(lit);
575 }
576 }
577 }
578}
579
580/// Move accumulated literal text into a leaf node.
581fn flush_text(text: &mut String, nodes: &mut Vec<DebugNode>) {
582 if !text.is_empty() {
583 nodes.push(DebugNode::Leaf(std::mem::take(text)));
584 }
585}
586
587/// Split the atom glued to an open delimiter off the accumulated text:
588/// `Some` from `Some(`, and `Name` (dropping the joining space) from
589/// `Name {`. Brace groups only take a prefix across that space — a brace
590/// directly following text is not the derived-struct shape.
591fn take_group_prefix(text: &mut String, delimiter: char) -> String {
592 if delimiter == '{' {
593 let Some(without_space) = text.strip_suffix(' ') else {
594 return String::new();
595 };
596 let start = without_space.rfind(' ').map(|index| index + 1).unwrap_or(0);
597 let prefix = without_space[start..].to_string();
598 if prefix.is_empty() {
599 return String::new();
600 }
601 text.truncate(text.len() - prefix.len() - 1);
602 prefix
603 } else {
604 let start = text.rfind(' ').map(|index| index + 1).unwrap_or(0);
605 let prefix = text[start..].to_string();
606 text.truncate(start);
607 prefix
608 }
609}
610
611/// Emit parsed nodes, matching the layout `#[derive(PrettyPrintable)]`
612/// produces for the same shapes.
613fn emit_debug_nodes(nodes: &[DebugNode], printer: &mut PrettyPrinter) {
614 for node in nodes {
615 match node {
616 DebugNode::Leaf(text) => printer.text(text),
617 DebugNode::Group {
618 prefix,
619 delimiter,
620 named,
621 items,
622 } => {
623 let (open, close, indent) = match (delimiter, named) {
624 ('{', true) => (format!("{prefix} {{"), " }", 4),
625 ('{', false) if prefix.is_empty() => ("{".to_string(), "}", 1),
626 ('{', false) => (format!("{prefix} {{"), "}", 1),
627 ('[', _) => (format!("{prefix}["), "]", 1),
628 _ => (format!("{prefix}("), ")", 1),
629 };
630 printer.begin_group(indent, &open);
631 if *named {
632 printer.breakable(" ");
633 }
634 for (index, item) in items.iter().enumerate() {
635 if index > 0 {
636 printer.text(",");
637 printer.breakable(" ");
638 }
639 emit_debug_nodes(item, printer);
640 }
641 printer.end_group(close);
642 }
643 }
644 }
645}
646
647/// A value that can describe its own printed representation.
648///
649/// Implementations should print the value in Rust-expression syntax wherever
650/// possible, so a reported failing example can be pasted back into code, and
651/// should express any internal structure through the printer's group and
652/// breakable primitives so large values wrap readably.
653///
654/// Provided for the standard types the generator library produces. For user
655/// types, either `#[derive(PrettyPrintable)]` or — to reuse an existing
656/// `Debug` representation without writing anything —
657/// [`pretty_print_as_debug!`](crate::pretty_print_as_debug).
658///
659/// `HashMap` and `HashSet` print as `HashMap::from([…])` /
660/// `HashSet::from([…])`, expressions that only construct the default-hasher
661/// types, so maps and sets with a custom hasher are deliberately not
662/// printable — print those through
663/// [`print_as_debug`](crate::generators::Generator::print_as_debug) or
664/// [`print_with`](crate::generators::Generator::print_with) instead:
665///
666/// ```compile_fail,E0277
667/// use std::collections::{HashMap, HashSet};
668/// use std::hash::{BuildHasherDefault, DefaultHasher};
669///
670/// fn assert_printable<T: hegel::PrettyPrintable>() {}
671/// assert_printable::<HashSet<i32, BuildHasherDefault<DefaultHasher>>>();
672/// assert_printable::<HashMap<i32, bool, BuildHasherDefault<DefaultHasher>>>();
673/// ```
674#[diagnostic::on_unimplemented(
675 message = "`{Self}` has no printed representation",
676 label = "`{Self}` does not implement `PrettyPrintable`",
677 note = "for your own type, add `#[derive(PrettyPrintable)]` (or `hegel::pretty_print_as_debug!` for a `Debug` type)",
678 note = "for a foreign type, make the generator printable instead: `.print_as_debug()` prints any `Debug` value, `.print_with(..)` prints a custom representation",
679 note = "or draw without reporting the value via `tc.draw_silent(..)`"
680)]
681pub trait PrettyPrintable {
682 /// Print this value's representation to `printer`.
683 fn pretty_print(&self, printer: &mut PrettyPrinter);
684}
685
686/// Implement [`PrettyPrintable`] for one or more local `Debug` types by
687/// printing their `{:?}` representation through
688/// [`print_debug_repr`](crate::pretty::print_debug_repr), so derived-`Debug`
689/// output wraps like a native implementation.
690///
691/// This is for **your own types** whose `Debug` output is already the
692/// representation you want: the orphan rule means it cannot implement a
693/// hegel trait for a type from another crate (including the standard
694/// library). To print a foreign type by its `Debug` representation, make
695/// the *generator* printable instead with
696/// [`print_as_debug`](crate::Generator::print_as_debug).
697///
698/// ```
699/// use hegel::{Document, PrettyPrintable};
700///
701/// #[derive(Debug)]
702/// struct Point {
703/// x: i32,
704/// y: i32,
705/// }
706/// hegel::pretty_print_as_debug!(Point);
707///
708/// let mut doc = Document::new();
709/// Point { x: 1, y: 2 }.pretty_print(doc.printer());
710/// assert_eq!(doc.finish(), "Point { x: 1, y: 2 }");
711/// ```
712#[macro_export]
713macro_rules! pretty_print_as_debug {
714 ($($t:ty),+ $(,)?) => {$(
715 impl $crate::PrettyPrintable for $t {
716 fn pretty_print(&self, printer: &mut $crate::PrettyPrinter) {
717 $crate::pretty::print_debug_repr(&::std::format!("{:?}", self), printer);
718 }
719 }
720 )+};
721}
722
723macro_rules! pretty_via_display {
724 ($($t:ty),+) => {$(
725 impl PrettyPrintable for $t {
726 fn pretty_print(&self, printer: &mut PrettyPrinter) {
727 printer.text(&format!("{}", self));
728 }
729 }
730 )+};
731}
732
733pretty_via_display!(
734 i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, bool
735);
736
737macro_rules! pretty_via_debug {
738 ($($t:ty),+) => {$(
739 impl PrettyPrintable for $t {
740 fn pretty_print(&self, printer: &mut PrettyPrinter) {
741 printer.text(&format!("{:?}", self));
742 }
743 }
744 )+};
745}
746
747pretty_via_debug!(char, str);
748
749impl PrettyPrintable for String {
750 fn pretty_print(&self, printer: &mut PrettyPrinter) {
751 printer.text(&format!("{self:?}.to_string()"));
752 }
753}
754
755impl PrettyPrintable for std::time::Duration {
756 fn pretty_print(&self, printer: &mut PrettyPrinter) {
757 printer.text(&format!(
758 "Duration::new({}, {})",
759 self.as_secs(),
760 self.subsec_nanos()
761 ));
762 }
763}
764
765impl PrettyPrintable for std::net::Ipv4Addr {
766 fn pretty_print(&self, printer: &mut PrettyPrinter) {
767 let [a, b, c, d] = self.octets();
768 printer.text(&format!("Ipv4Addr::new({a}, {b}, {c}, {d})"));
769 }
770}
771
772impl PrettyPrintable for std::net::Ipv6Addr {
773 fn pretty_print(&self, printer: &mut PrettyPrinter) {
774 let segments = self
775 .segments()
776 .map(|segment| format!("{segment:#x}"))
777 .join(", ");
778 printer.text(&format!("Ipv6Addr::new({segments})"));
779 }
780}
781
782impl PrettyPrintable for std::net::IpAddr {
783 fn pretty_print(&self, printer: &mut PrettyPrinter) {
784 match self {
785 std::net::IpAddr::V4(addr) => {
786 printer.text("IpAddr::V4(");
787 addr.pretty_print(printer);
788 printer.text(")");
789 }
790 std::net::IpAddr::V6(addr) => {
791 printer.text("IpAddr::V6(");
792 addr.pretty_print(printer);
793 printer.text(")");
794 }
795 }
796 }
797}
798
799macro_rules! pretty_float {
800 ($t:ty, $name:literal) => {
801 impl PrettyPrintable for $t {
802 fn pretty_print(&self, printer: &mut PrettyPrinter) {
803 if self.is_nan() {
804 if self.to_bits() == <$t>::NAN.to_bits() {
805 printer.text(concat!($name, "::NAN"));
806 } else {
807 printer.text(&format!(
808 concat!($name, "::from_bits(0x{:x})"),
809 self.to_bits()
810 ));
811 }
812 } else if *self == <$t>::INFINITY {
813 printer.text(concat!($name, "::INFINITY"));
814 } else if *self == <$t>::NEG_INFINITY {
815 printer.text(concat!($name, "::NEG_INFINITY"));
816 } else {
817 printer.text(&format!("{:?}", self));
818 }
819 }
820 }
821 };
822}
823
824pretty_float!(f32, "f32");
825pretty_float!(f64, "f64");
826
827macro_rules! pretty_delegating {
828 ($($t:ty),+) => {$(
829 impl<T: PrettyPrintable + ?Sized> PrettyPrintable for $t {
830 fn pretty_print(&self, printer: &mut PrettyPrinter) {
831 (**self).pretty_print(printer);
832 }
833 }
834 )+};
835}
836
837pretty_delegating!(&T, &mut T);
838
839macro_rules! pretty_smart_pointer {
840 ($($t:ty, $open:literal);+) => {$(
841 impl<T: PrettyPrintable> PrettyPrintable for $t {
842 fn pretty_print(&self, printer: &mut PrettyPrinter) {
843 printer.begin_group($open.len(), $open);
844 (**self).pretty_print(printer);
845 printer.end_group(")");
846 }
847 }
848 )+};
849}
850
851pretty_smart_pointer!(
852 Box<T>, "Box::new(";
853 std::rc::Rc<T>, "Rc::new(";
854 std::sync::Arc<T>, "Arc::new("
855);
856
857/// `Box::new` cannot build a boxed unsized value, so `Box<str>` prints its
858/// target instead of a constructor.
859impl PrettyPrintable for Box<str> {
860 fn pretty_print(&self, printer: &mut PrettyPrinter) {
861 (**self).pretty_print(printer);
862 }
863}
864
865/// Print `items` as a delimited, comma-separated sequence: inline when it
866/// fits, one element per line (aligned just inside `open`) when it does not.
867fn pretty_seq<'a, T: PrettyPrintable + ?Sized + 'a>(
868 printer: &mut PrettyPrinter,
869 open: &str,
870 close: &str,
871 items: impl Iterator<Item = &'a T>,
872) {
873 printer.begin_group(open.chars().count(), open);
874 for (index, item) in items.enumerate() {
875 if index > 0 {
876 printer.text(",");
877 printer.breakable(" ");
878 }
879 item.pretty_print(printer);
880 }
881 printer.end_group(close);
882}
883
884impl<T: PrettyPrintable> PrettyPrintable for [T] {
885 fn pretty_print(&self, printer: &mut PrettyPrinter) {
886 pretty_seq(printer, "[", "]", self.iter());
887 }
888}
889
890impl<T: PrettyPrintable> PrettyPrintable for Vec<T> {
891 fn pretty_print(&self, printer: &mut PrettyPrinter) {
892 pretty_seq(printer, "vec![", "]", self.iter());
893 }
894}
895
896impl<T: PrettyPrintable, const N: usize> PrettyPrintable for [T; N] {
897 fn pretty_print(&self, printer: &mut PrettyPrinter) {
898 self.as_slice().pretty_print(printer);
899 }
900}
901
902impl<T: PrettyPrintable> PrettyPrintable for std::collections::HashSet<T> {
903 fn pretty_print(&self, printer: &mut PrettyPrinter) {
904 pretty_seq(printer, "HashSet::from([", "])", self.iter());
905 }
906}
907
908impl<T: PrettyPrintable> PrettyPrintable for std::collections::BTreeSet<T> {
909 fn pretty_print(&self, printer: &mut PrettyPrinter) {
910 pretty_seq(printer, "BTreeSet::from([", "])", self.iter());
911 }
912}
913
914/// Print `entries` as a `Name::from([(key, value), …])` map: inline when it
915/// fits, one entry per line when it does not.
916fn pretty_map<'a, K: PrettyPrintable + 'a, V: PrettyPrintable + 'a>(
917 printer: &mut PrettyPrinter,
918 open: &str,
919 entries: impl Iterator<Item = (&'a K, &'a V)>,
920) {
921 printer.begin_group(open.chars().count(), open);
922 for (index, (key, value)) in entries.enumerate() {
923 if index > 0 {
924 printer.text(",");
925 printer.breakable(" ");
926 }
927 printer.text("(");
928 key.pretty_print(printer);
929 printer.text(", ");
930 value.pretty_print(printer);
931 printer.text(")");
932 }
933 printer.end_group("])");
934}
935
936impl<K: PrettyPrintable, V: PrettyPrintable> PrettyPrintable for std::collections::HashMap<K, V> {
937 fn pretty_print(&self, printer: &mut PrettyPrinter) {
938 pretty_map(printer, "HashMap::from([", self.iter());
939 }
940}
941
942impl<K: PrettyPrintable, V: PrettyPrintable> PrettyPrintable for std::collections::BTreeMap<K, V> {
943 fn pretty_print(&self, printer: &mut PrettyPrinter) {
944 pretty_map(printer, "BTreeMap::from([", self.iter());
945 }
946}
947
948impl<T: PrettyPrintable> PrettyPrintable for Option<T> {
949 fn pretty_print(&self, printer: &mut PrettyPrinter) {
950 match self {
951 None => printer.text("None"),
952 Some(value) => {
953 printer.begin_group(5, "Some(");
954 value.pretty_print(printer);
955 printer.end_group(")");
956 }
957 }
958 }
959}
960
961impl<T: PrettyPrintable, E: PrettyPrintable> PrettyPrintable for Result<T, E> {
962 fn pretty_print(&self, printer: &mut PrettyPrinter) {
963 match self {
964 Ok(value) => {
965 printer.begin_group(3, "Ok(");
966 value.pretty_print(printer);
967 printer.end_group(")");
968 }
969 Err(error) => {
970 printer.begin_group(4, "Err(");
971 error.pretty_print(printer);
972 printer.end_group(")");
973 }
974 }
975 }
976}
977
978impl PrettyPrintable for () {
979 fn pretty_print(&self, printer: &mut PrettyPrinter) {
980 printer.text("()");
981 }
982}
983
984impl<A: PrettyPrintable> PrettyPrintable for (A,) {
985 fn pretty_print(&self, printer: &mut PrettyPrinter) {
986 printer.begin_group(1, "(");
987 self.0.pretty_print(printer);
988 printer.end_group(",)");
989 }
990}
991
992macro_rules! pretty_tuple {
993 ($(($($name:ident),+)),+ $(,)?) => {$(
994 #[allow(non_snake_case)]
995 impl<$($name: PrettyPrintable),+> PrettyPrintable for ($($name,)+) {
996 fn pretty_print(&self, printer: &mut PrettyPrinter) {
997 let ($($name,)+) = self;
998 printer.begin_group(1, "(");
999 let mut index = 0usize;
1000 $(
1001 if index > 0 {
1002 printer.text(",");
1003 printer.breakable(" ");
1004 }
1005 index += 1;
1006 $name.pretty_print(printer);
1007 )+
1008 let _ = index;
1009 printer.end_group(")");
1010 }
1011 }
1012 )+};
1013}
1014
1015pretty_tuple!(
1016 (A, B),
1017 (A, B, C),
1018 (A, B, C, D),
1019 (A, B, C, D, E),
1020 (A, B, C, D, E, F),
1021 (A, B, C, D, E, F, G),
1022 (A, B, C, D, E, F, G, H),
1023 (A, B, C, D, E, F, G, H, I),
1024 (A, B, C, D, E, F, G, H, I, J),
1025 (A, B, C, D, E, F, G, H, I, J, K),
1026 (A, B, C, D, E, F, G, H, I, J, K, L),
1027);