formally_io/print.rs
1//
2// ::formally - the open-source formal methods toolchain
3//
4// Copyright (c) 2025 Nicola Gigante
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22// SOFTWARE.
23//
24
25//! Facilities to pretty-print output text.
26//!
27//! The [print] module is for now just a set of small amenities on top of the [pretty] crate, which
28//! provides facilities to pretty-print text accounting for the number of columns available (e.g.
29//! the size of the terminal).
30//!
31//! The [Print] trait is used for this purpose, rather than [std::fmt::Display], indeed because of
32//! the possibility of specifying the number of columns to print in.
33
34pub use ::pretty::RcDoc;
35use std::{fmt, io};
36
37/// A trait for write streams that know if they are terminals.
38///
39/// This trait is automatically implemented for any type implementing both [io::Write] and
40/// [io::IsTerminal].
41pub trait RenderTarget: io::Write + io::IsTerminal {}
42impl<T: io::Write + io::IsTerminal> RenderTarget for T {}
43
44/// Types that can print themselves accounting for the number of available columns.
45pub trait Print {
46 /// Render an object to the given `target` write stream in `width` number of columns.
47 fn render(&self, target: &mut dyn io::Write, width: u16) -> io::Result<()>;
48
49 /// Render an object to the given `fmt::Write` formatter in `width` number of columns.
50 fn render_fmt(&self, target: &mut dyn fmt::Write, width: u16) -> fmt::Result;
51
52 /// Prints an object to the given [RenderTarget] by calling [render()](Print::render) with an
53 /// automatically detected `width` parameter if a terminal is attached.
54 fn print(&self, target: &mut dyn RenderTarget) -> io::Result<()> {
55 if target.is_terminal()
56 && let Some(size) = termsize::get()
57 {
58 self.render(target, size.cols)
59 } else {
60 self.render(target, u16::MAX)
61 }
62 }
63
64 /// As [print()](Print::print) but appends a new line at the end.
65 fn println(&self, target: &mut dyn RenderTarget) -> io::Result<()> {
66 self.print(target)?;
67 writeln!(target)
68 }
69}
70
71/// Types that can print themselves to an [RcDoc] object.
72pub trait Pretty {
73 fn pretty(&self) -> RcDoc<'static>;
74}
75
76impl<T: Pretty> Print for T {
77 fn render(&self, target: &mut dyn io::Write, width: u16) -> io::Result<()> {
78 self.pretty().render(width as usize, target)
79 }
80
81 fn render_fmt(&self, target: &mut dyn fmt::Write, width: u16) -> fmt::Result {
82 self.pretty().render_fmt(width as usize, target)
83 }
84}
85
86/// Little helper to put quotes around an [RcDoc].
87pub fn quotes(doc: RcDoc) -> RcDoc {
88 let quote = RcDoc::as_string('"');
89 quote.clone().append(doc).append(quote)
90}
91
92/// Little helper to put delimiters around an [RcDoc] with softlines telling the renderer to break
93/// lines there but only if needed.
94pub fn enclose<'a>(doc: RcDoc<'a>, start: &str, end: &str) -> RcDoc<'a> {
95 RcDoc::as_string(start)
96 .append(
97 RcDoc::softline_()
98 .append(doc)
99 .append(RcDoc::softline_())
100 .nest(1)
101 .group(),
102 )
103 .append(RcDoc::as_string(end))
104}
105
106/// Little helper to put parens around an [RcDoc] with softelines.
107pub fn parens(doc: RcDoc) -> RcDoc {
108 enclose(doc, "(", ")")
109}
110
111/// Little helper to put brackets around an [RcDoc] with softelines.
112pub fn brackets(doc: RcDoc) -> RcDoc {
113 enclose(doc, "[", "]")
114}
115
116/// Little helper to put braces around an [RcDoc] with softelines.
117pub fn braces(doc: RcDoc) -> RcDoc {
118 enclose(doc, "{", "}")
119}