Skip to main content

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}