vendor pretty-print dep

This commit is contained in:
Jana Dönszelmann 2026-04-03 18:10:49 +02:00
parent 4a6c1020f4
commit 52a80cfb0e
No known key found for this signature in database
40 changed files with 2766 additions and 6 deletions

View file

@ -0,0 +1,25 @@
[package]
name = "pretty-print"
version = "0.1.8"
authors = ["Aster <192607617@qq.com>"]
description = "pretty print tree"
repository = "https://github.com/oovm/pretty-print"
documentation = "https://docs.rs/pretty-print"
readme = "Readme.md"
license = "MPL-2.0"
edition = "2021"
[dependencies]
unicode-segmentation = "1.10.1"
[dependencies.color-ansi]
version = "0.1.0"
#default-features = false
#path = 'C:\Users\Dell\CLionProjects\color-rs\projects\color-ansi'
[dev-dependencies]
[features]
default = ["std"]
std = []

View file

@ -0,0 +1,6 @@
{
"private": true,
"scripts": {
"p": "cargo publish --allow-dirty"
}
}

View file

@ -0,0 +1,24 @@
This crate defines a
[Wadler-style](http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf)
pretty-printing API.
Start with the static functions of [Doc](enum.Doc.html).
## Quick start
Let's pretty-print simple sexps! We want to pretty print sexps like
```lisp
(1 2 3)
```
or, if the line would be too long, like
```lisp
((1)
(2 3)
(4 5 6))
```
A _simple symbolic expression_ consists of a numeric _atom_ or a nested ordered _list_ of
symbolic expression children.

View file

@ -0,0 +1,193 @@
//! Document formatting of "helpers" such as where some number of prefixes and suffixes would
//! ideally be layed out onto a single line instead of breaking them up into multiple lines. See
//! `BlockDoc` for an example
use crate::{docs, DocumentTree, DocumentTree};
pub struct Affixes<'doc, D, A>
{
prefix: DocumentTree<'doc, D, A>,
suffix: DocumentTree<'doc, D, A>,
nest: bool,
}
impl<'a, D> Clone for Affixes<'a, D>
{
fn clone(&self) -> Self {
Affixes {
prefix: self.prefix.clone(),
suffix: self.suffix.clone(),
nest: self.nest,
}
}
}
impl<'doc, D, A> Affixes<'doc, D, A>
{
pub fn new(prefix: DocumentTree<'doc, D, A>, suffix: DocumentTree<'doc, D, A>) -> Self {
Affixes {
prefix,
suffix,
nest: false,
}
}
pub fn nest(mut self) -> Self {
self.nest = true;
self
}
}
/// Formats a set of `prefix` and `suffix` documents around a `body`
///
/// The following document split into the prefixes [\x y ->, \z ->, {], suffixes [nil, nil, }] and
/// body [result: x + y - z] will try to be formatted
///
/// ```gluon
/// \x y -> \z -> { result: x + y - z }
/// ```
///
/// ```gluon
/// \x y -> \z -> {
/// result: x + y - z
/// }
/// ```
///
/// ```gluon
/// \x y -> \z ->
/// {
/// result: x + y - z
/// }
/// ```
///
/// ```gluon
/// \x y ->
/// \z ->
/// {
/// result: x + y - z
/// }
/// ```
pub struct BlockDoc<'doc, D, A>
where
D: DocAllocator<'doc, A>,
{
pub affixes: Vec<Affixes<'doc, D, A>>,
pub body: DocumentTree<'doc, D, A>,
}
impl<'doc, D, A> BlockDoc<'doc, D, A>
where
D: DocAllocator<'doc, A>,
D::Doc: Clone,
A: Clone,
{
pub fn format(self, nest: isize) -> DocumentTree<'doc, D, A> {
let arena = self.body.0;
let fail_on_multi_line = arena.fail().flat_alt(arena.nil());
(1..self.affixes.len() + 1)
.rev()
.map(|split| {
let (before, after) = self.affixes.split_at(split);
let last = before.len() == 1;
docs![
arena,
docs![
arena,
arena.concat(before.iter().map(|affixes| affixes.prefix.clone())),
if last {
arena.nil()
} else {
fail_on_multi_line.clone()
}
]
.group(),
docs![
arena,
after.iter().rev().cloned().fold(
docs![
arena,
self.body.clone(),
// If there is no prefix then we must not allow the body to laid out on multiple
// lines without nesting
if !last
&& before
.iter()
.all(|affixes| matches!(&*affixes.prefix.1, DocumentTree::Nil))
{
fail_on_multi_line.clone()
} else {
arena.nil()
},
]
.nest(nest)
.append(
arena.concat(after.iter().map(|affixes| affixes.suffix.clone()))
),
|acc, affixes| {
let mut doc = affixes.prefix.append(acc);
if affixes.nest {
doc = doc.nest(nest);
}
doc.group()
},
),
arena.concat(before.iter().map(|affixes| affixes.suffix.clone())),
]
.group(),
]
})
.fold(None::<DocumentTree<_, _>>, |acc, doc| {
Some(match acc {
None => doc,
Some(acc) => acc.union(doc),
})
})
.unwrap_or(self.body)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Arena;
#[test]
fn format_block() {
let arena = &Arena::<()>::new();
let mk_doc = || BlockDoc {
affixes: vec![
Affixes::new(docs![arena, "\\x y ->"], arena.nil()).nest(),
Affixes::new(docs![arena, arena.line(), "\\z ->"], arena.nil()).nest(),
Affixes::new(
docs![arena, arena.line(), "{"],
docs![arena, arena.line(), "}"],
)
.nest(),
],
body: docs![arena, arena.line(), "result"],
};
expect_test::expect![[r#"\x y -> \z -> { result }"#]]
.assert_eq(&mk_doc().format(4).1.pretty(40).to_string());
expect_test::expect![[r#"
\x y -> \z -> {
result
}"#]]
.assert_eq(&mk_doc().format(4).1.pretty(15).to_string());
expect_test::expect![[r#"
\x y -> \z ->
{
result
}"#]]
.assert_eq(&mk_doc().format(4).1.pretty(14).to_string());
expect_test::expect![[r#"
\x y ->
\z ->
{
result
}"#]]
.assert_eq(&mk_doc().format(4).1.pretty(12).to_string());
}
}

View file

@ -0,0 +1,66 @@
use super::*;
/// A soft block is a block that is not required to be on a new line.
///
/// ```vk
/// {a, b, c}
///
/// {
/// a,
/// b,
/// }
/// ```
#[derive(Clone, Debug)]
pub struct HardBlock {
/// The indentation of the soft block
pub indent: usize,
/// The left hand side of the soft block
pub lhs: &'static str,
/// The right hand side of the soft block
pub rhs: &'static str,
/// The joint node of the soft block
pub joint: PrettyTree,
}
impl HardBlock {
/// Build a new soft block
pub fn new(lhs: &'static str, rhs: &'static str) -> Self {
Self { lhs, rhs, indent: 4, joint: PrettyTree::line_or_space() }
}
/// Build a new soft block with the parentheses syntax
pub fn parentheses() -> Self {
Self::new("(", ")")
}
/// Build a new soft block with the brackets syntax
pub fn brackets() -> Self {
Self::new("[", "]")
}
/// Build a new soft block with the curly braces syntax
pub fn curly_braces() -> Self {
Self::new("{", "}")
}
/// Set the joint node of the soft block
pub fn with_joint(self, joint: PrettyTree) -> Self {
Self { joint, ..self }
}
}
impl HardBlock {
/// Join a slice of pretty printables with the soft block
pub fn join_slice<T: PrettyPrint>(&self, slice: &[T], theme: &PrettyProvider) -> PrettyTree {
let mut outer = PrettySequence::new(5);
outer += self.lhs;
outer += PrettyTree::Hardline;
let mut inner = PrettySequence::new(slice.len() * 2);
for (idx, term) in slice.iter().enumerate() {
if idx != 0 {
inner += self.joint.clone();
}
inner += term.pretty(theme);
}
outer += inner.indent(self.indent);
outer += PrettyTree::Hardline;
outer += self.rhs;
outer.into()
}
}

View file

@ -0,0 +1,61 @@
use super::*;
use crate::PrettyBuilder;
/// `K & R` style brackets
///
/// ```vk
/// a {}
/// ```
///
/// ```vk
/// a {
///
/// }
/// ```
#[derive(Copy, Clone, Debug)]
pub struct KAndRBracket {
/// Whether to add a space after the keyword
pub head_space: bool,
/// The left bracket
pub bracket_l: &'static str,
/// The right bracket
pub bracket_r: &'static str,
}
impl KAndRBracket {
/// Build a bracketed block
pub fn curly_braces() -> Self {
Self { head_space: true, bracket_l: "{", bracket_r: "}" }
}
/// Build a bracketed block
pub fn build<'a, I>(
&self,
items: &[I],
allocator: &'a PrettyProvider,
inline_join: PrettyTree,
block_join: PrettyTree,
) -> PrettyTree
where
I: PrettyPrint,
{
let mut output = PrettySequence::new(5);
if self.head_space {
output.push(" ");
}
output.push(self.bracket_l);
// inline
let mut inline = PrettySequence::new(3);
inline.push(" ");
inline.push(allocator.join_slice(items, inline_join));
inline.push(" ");
// block
let mut block = PrettySequence::new(3);
block.push(PrettyTree::Hardline);
block.push(allocator.join_slice(items, block_join).indent(4));
block.push(PrettyTree::Hardline);
//
output.push(block.flat_alt(inline));
output.push(self.bracket_r);
output.into()
}
}

View file

@ -0,0 +1,13 @@
#![doc = include_str!("readme.md")]
use crate::{PrettyBuilder, PrettyPrint, PrettyProvider, PrettyTree};
use alloc::vec::Vec;
use core::ops::AddAssign;
mod hard_block;
mod k_and_r_bracket;
mod sequence;
mod soft_block;
// mod affixes;
pub use self::{hard_block::HardBlock, k_and_r_bracket::KAndRBracket, sequence::PrettySequence, soft_block::SoftBlock};

View file

@ -0,0 +1,60 @@
use super::*;
/// The document sequence type.
#[derive(Clone, Debug, Default)]
pub struct PrettySequence {
items: Vec<PrettyTree>,
}
impl PrettySequence {
/// Create a new sequence with the given capacity.
pub fn new(capacity: usize) -> Self {
Self { items: Vec::with_capacity(capacity) }
}
/// Create a new sequence with the given capacity.
pub fn push<T>(&mut self, item: T)
where
T: Into<PrettyTree>,
{
self.items.push(item.into());
}
/// Create a new sequence with the given capacity.
pub fn extend<I, T>(&mut self, items: I)
where
I: IntoIterator<Item = T>,
T: Into<PrettyTree>,
{
self.items.extend(items.into_iter().map(|x| x.into()));
}
}
impl PrettyBuilder for PrettySequence {
fn flat_alt<E>(self, flat: E) -> PrettyTree
where
E: Into<PrettyTree>,
{
PrettyTree::from(self).flat_alt(flat)
}
fn indent(self, indent: usize) -> PrettyTree {
PrettyTree::from(self).indent(indent)
}
fn nest(self, offset: isize) -> PrettyTree {
PrettyTree::from(self).nest(offset)
}
}
impl<T> AddAssign<T> for PrettySequence
where
T: Into<PrettyTree>,
{
fn add_assign(&mut self, rhs: T) {
self.push(rhs);
}
}
impl From<PrettySequence> for PrettyTree {
fn from(value: PrettySequence) -> Self {
Self::concat(value.items)
}
}

View file

@ -0,0 +1,72 @@
use super::*;
/// A soft block is a block that is not required to be on a new line.
///
/// ```vk
/// {a, b, c}
///
/// {
/// a,
/// b,
/// }
/// ```
#[derive(Clone, Debug)]
pub struct SoftBlock {
/// The indentation of the soft block
pub indent: usize,
/// The left hand side of the soft block
pub lhs: &'static str,
/// The right hand side of the soft block
pub rhs: &'static str,
/// The joint node of the soft block
pub joint: PrettyTree,
/// The tail node of the soft block
pub tail: PrettyTree,
}
impl SoftBlock {
/// Build a new soft block
pub fn new(lhs: &'static str, rhs: &'static str) -> Self {
Self { lhs, rhs, indent: 4, joint: PrettyTree::line_or_space(), tail: PrettyTree::Nil }
}
/// Build a new soft block with the tuple syntax
pub fn tuple() -> Self {
Self::new("(", ")")
}
/// Build a new soft block with the parentheses syntax
pub fn parentheses() -> Self {
Self::new("(", ")")
}
/// Build a new soft block with the brackets syntax
pub fn brackets() -> Self {
Self::new("[", "]")
}
/// Build a new soft block with the curly braces syntax
pub fn curly_braces() -> Self {
Self::new("{", "}")
}
/// Set the joint node of the soft block
pub fn with_joint(self, joint: PrettyTree) -> Self {
Self { joint, ..self }
}
}
impl SoftBlock {
/// Join a slice of pretty printables with the soft block
pub fn join_slice<T: PrettyPrint>(&self, slice: &[T], theme: &PrettyProvider) -> PrettyTree {
let mut outer = PrettySequence::new(5);
outer += self.lhs;
outer += PrettyTree::line_or_space();
let mut inner = PrettySequence::new(slice.len() * 2);
for (idx, term) in slice.iter().enumerate() {
if idx != 0 {
inner += self.joint.clone();
}
inner += term.pretty(theme);
}
outer += inner.indent(self.indent);
outer += PrettyTree::line_or_space();
outer += self.rhs;
outer.into()
}
}

View file

@ -0,0 +1,51 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_debug_implementations, missing_copy_implementations)]
#![warn(missing_docs, rustdoc::missing_crate_level_docs)]
#![doc = include_str!("../readme.md")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/oovm/shape-rs/dev/projects/images/Trapezohedron.svg")]
#![doc(html_favicon_url = "https://raw.githubusercontent.com/oovm/shape-rs/dev/projects/images/Trapezohedron.svg")]
extern crate alloc;
extern crate core;
pub mod helpers;
mod providers;
mod render;
mod traits;
mod tree;
pub use self::render::{
write_fmt::{BufferWrite, FmtWrite},
PrettyFormatter, Render, RenderAnnotated,
};
#[cfg(feature = "std")]
pub use crate::render::write_io::{IoWrite, TerminalWriter};
pub use crate::{
providers::PrettyProvider,
traits::{printer::PrettyPrint, PrettyBuilder},
tree::PrettyTree,
};
pub use color_ansi::*;
/// Concatenates a number of documents (or values that can be converted into a document via the
/// `Pretty` trait, like `&str`)
///
/// ```
/// use pretty_print::docs;
/// let doc =
/// docs!["let", arena.softline(), "x", arena.softline(), "=", arena.softline(), Some("123"),];
/// assert_eq!(doc.1.pretty(80).to_string(), "let x = 123");
/// ```
#[macro_export]
macro_rules! docs {
($alloc: expr, $first: expr $(,)?) => {
$crate::Pretty::pretty($first, $alloc)
};
($alloc: expr, $first: expr $(, $rest: expr)+ $(,)?) => {{
let mut doc = $crate::Pretty::pretty($first, $alloc);
$(
doc = doc.append($rest);
)*
doc
}}
}

View file

@ -0,0 +1,236 @@
use crate::{PrettyPrint, PrettyTree};
use alloc::{borrow::Cow, rc::Rc};
use color_ansi::AnsiStyle;
use core::fmt::{Debug, Formatter};
/// Represents a pretty-printable tree provider.
pub struct PrettyProvider {
width: usize,
keyword: Rc<AnsiStyle>,
string: Rc<AnsiStyle>,
number: Rc<AnsiStyle>,
macros: Rc<AnsiStyle>,
argument: Rc<AnsiStyle>,
argument_mut: Rc<AnsiStyle>,
local: Rc<AnsiStyle>,
local_mut: Rc<AnsiStyle>,
operator: Rc<AnsiStyle>,
structure: Rc<AnsiStyle>,
variant: Rc<AnsiStyle>,
interface: Rc<AnsiStyle>,
}
impl Debug for PrettyProvider {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PrettyProvider").finish()
}
}
impl PrettyProvider {
/// Creates a new pretty-printable tree provider.
pub fn new(width: usize) -> Self {
Self {
width,
keyword: AnsiStyle::rgb(197, 119, 207).into(),
string: AnsiStyle::rgb(152, 195, 121).into(),
number: AnsiStyle::rgb(206, 153, 100).into(),
macros: AnsiStyle::rgb(87, 182, 194).into(),
argument: AnsiStyle::rgb(239, 112, 117).into(),
argument_mut: AnsiStyle::rgb(239, 112, 117).with_underline().into(),
local: AnsiStyle::rgb(152, 195, 121).into(),
local_mut: AnsiStyle::rgb(152, 195, 121).with_underline().into(),
operator: AnsiStyle::rgb(90, 173, 238).into(),
structure: AnsiStyle::rgb(197, 119, 207).into(),
variant: AnsiStyle::rgb(239, 112, 117).into(),
interface: AnsiStyle::rgb(197, 119, 207).into(),
}
}
}
impl PrettyProvider {
/// Gets the width of the document.
pub fn get_width(&self) -> usize {
self.width
}
/// Sets the width of the document.
pub fn set_width(&mut self, width: usize) {
self.width = width;
}
/// Gets the width of the document.
pub fn text<S>(&self, text: S) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
PrettyTree::text(text)
}
/// Gets the width of the document.
pub fn custom<S>(&self, text: S, style: Rc<AnsiStyle>) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
PrettyTree::text(text).annotate(style)
}
/// Allocate a document containing the given text.
pub fn keyword<S>(&self, text: S) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
PrettyTree::text(text).annotate(self.keyword.clone())
}
/// Allocate a document containing the given text.
pub fn identifier<S>(&self, text: S) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
PrettyTree::text(text).annotate(self.operator.clone())
}
/// Allocate a document containing the given text.
pub fn generic<S>(&self, text: S) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
PrettyTree::text(text).annotate(self.macros.clone())
}
/// Allocate a document containing the given text.
pub fn variable<S>(&self, text: S, mutable: bool) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
if mutable {
PrettyTree::text(text).annotate(self.local_mut.clone())
}
else {
PrettyTree::text(text).annotate(self.local.clone())
}
}
/// Allocate a document containing the given text.
pub fn argument<S>(&self, text: S, mutable: bool) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
if mutable {
PrettyTree::text(text).annotate(self.argument_mut.clone())
}
else {
PrettyTree::text(text).annotate(self.argument.clone())
}
}
/// Allocate a document containing the given text.
pub fn operator<S>(&self, text: S) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
PrettyTree::text(text).annotate(self.operator.clone())
}
/// Allocate a document containing the given text.
pub fn string<S>(&self, text: S) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
PrettyTree::text(text).annotate(self.string.clone())
}
/// Allocate a document containing the given text.
pub fn annotation<S>(&self, text: S) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
PrettyTree::text(text).annotate(self.macros.clone())
}
/// Allocate a document containing the given text.
pub fn number<S>(&self, text: S) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
PrettyTree::text(text).annotate(self.number.clone())
}
/// Allocate a document containing the given text.
pub fn structure<S>(&self, text: S) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
PrettyTree::text(text).annotate(self.structure.clone())
}
/// Allocate a document containing the given text.
pub fn variant<S>(&self, text: S) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
PrettyTree::text(text).annotate(self.variant.clone())
}
/// Allocate a document containing the given text.
pub fn interface<S>(&self, text: S) -> PrettyTree
where
S: Into<Cow<'static, str>>,
{
PrettyTree::text(text).annotate(self.interface.clone())
}
}
impl PrettyProvider {
/// Allocate a document containing the given text.
///
/// # Examples
///
/// ```
/// # use pretty_print::PrettyProvider;
/// let theme = PrettyProvider::new(80);
/// theme.join(vec!["a", "b", "c"], ", ");
/// ```
pub fn join<I, T1, T2>(&self, iter: I, joint: T2) -> PrettyTree
where
I: IntoIterator<Item = T1>,
T1: PrettyPrint,
T2: PrettyPrint,
{
PrettyTree::join(iter.into_iter().map(|x| x.pretty(self)), joint.pretty(self))
}
/// Allocate a document containing the given text.
///
/// # Examples
///
/// ```
/// # use pretty_print::PrettyProvider;
/// let theme = PrettyProvider::new(80);
/// theme.join(&["a", "b", "c"], ", ");
/// ```
pub fn join_slice<I, T>(&self, iter: &[I], joint: T) -> PrettyTree
where
I: PrettyPrint,
T: PrettyPrint,
{
PrettyTree::join(iter.iter().map(|s| s.pretty(self)), joint.pretty(self))
}
/// Allocate a document containing the given text.
///
/// # Examples
///
/// ```
/// # use pretty_print::PrettyProvider;
/// let theme = PrettyProvider::new(80);
/// theme.concat(vec!["1", "2", "3"]);
/// ```
pub fn concat<I, T>(&self, iter: I) -> PrettyTree
where
I: IntoIterator<Item = T>,
T: PrettyPrint,
{
PrettyTree::concat(iter.into_iter().map(|x| x.pretty(self)))
}
/// Allocate a document containing the given text.
///
/// # Examples
///
/// ```
/// # use pretty_print::PrettyProvider;
/// let theme = PrettyProvider::new(80);
/// theme.concat_slice(&["1", "2", "3"]);
/// ```
pub fn concat_slice<T>(&self, iter: &[T]) -> PrettyTree
where
T: PrettyPrint,
{
PrettyTree::concat(iter.iter().map(|s| s.pretty(self)))
}
}

View file

@ -0,0 +1,372 @@
use crate::{BufferWrite, PrettyTree};
use alloc::{rc::Rc, vec, vec::Vec};
use color_ansi::AnsiStyle;
use core::fmt::{Debug, Display, Formatter};
#[cfg(feature = "std")]
pub mod write_io;
pub mod write_fmt;
/// Trait representing the operations necessary to render a document
pub trait Render {
/// The type of the output
type Error;
/// Write a string to the output
fn write_str(&mut self, s: &str) -> Result<usize, Self::Error>;
/// Write a character to the output
fn write_str_all(&mut self, mut s: &str) -> Result<(), Self::Error> {
while !s.is_empty() {
let count = self.write_str(s)?;
s = &s[count..];
}
Ok(())
}
/// Write a character to the output
fn fail_doc(&self) -> Self::Error;
}
/// The given text, which must not contain line breaks.
#[derive(Debug)]
pub struct PrettyFormatter<'a> {
tree: &'a PrettyTree,
width: usize,
}
impl<'a> Display for PrettyFormatter<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
self.tree.render_fmt(self.width, f)
}
}
impl PrettyTree {
/// Returns a value which implements `std::fmt::Display`
///
/// ```
/// use pretty::{BoxDoc, Doc};
/// let doc =
/// BoxDoc::<()>::group(BoxDoc::text("hello").append(Doc::line()).append(Doc::text("world")));
/// assert_eq!(format!("{}", doc.pretty(80)), "hello world");
/// ```
#[inline]
pub fn pretty(&self, width: usize) -> PrettyFormatter<'_> {
PrettyFormatter { tree: self, width }
}
}
/// Trait representing the operations necessary to write an annotated document.
pub trait RenderAnnotated: Render {
/// Push an annotation onto the stack
fn push_annotation(&mut self, annotation: Rc<AnsiStyle>) -> Result<(), Self::Error>;
/// Pop an annotation from the stack
fn pop_annotation(&mut self) -> Result<(), Self::Error>;
}
#[derive(Debug)]
enum Annotation<A> {
Push(Rc<A>),
Pop,
}
macro_rules! make_spaces {
() => { "" };
($s: tt $($t: tt)*) => { concat!(" ", make_spaces!($($t)*)) };
}
pub(crate) const SPACES: &str = make_spaces!(,,,,,,,,,,);
fn append_docs2(ldoc: Rc<PrettyTree>, rdoc: Rc<PrettyTree>, mut consumer: impl FnMut(Rc<PrettyTree>)) -> Rc<PrettyTree> {
let d = append_docs(rdoc, &mut consumer);
consumer(d);
append_docs(ldoc, &mut consumer)
}
fn append_docs(mut doc: Rc<PrettyTree>, consumer: &mut impl FnMut(Rc<PrettyTree>)) -> Rc<PrettyTree> {
loop {
// Since appended documents often appear in sequence on the left side we
// gain a slight performance increase by batching these pushes (avoiding
// to push and directly pop `Append` documents)
match doc.as_ref() {
PrettyTree::Append { lhs, rhs } => {
let d = append_docs(rhs.clone(), consumer);
consumer(d);
doc = lhs.clone();
}
_ => return doc,
}
}
}
pub fn best<W>(doc: Rc<PrettyTree>, width: usize, out: &mut W) -> Result<(), W::Error>
where
W: RenderAnnotated,
W: ?Sized,
{
Best {
pos: 0,
back_cmds: vec![RenderCommand { indent: 0, mode: Mode::Break, node: doc }],
front_cmds: vec![],
annotation_levels: vec![],
width,
}
.best(0, out)?;
Ok(())
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
enum Mode {
Break,
Flat,
}
struct RenderCommand {
indent: usize,
mode: Mode,
node: Rc<PrettyTree>,
}
fn write_newline<W>(ind: usize, out: &mut W) -> Result<(), W::Error>
where
W: ?Sized + Render,
{
out.write_str_all("\n")?;
write_spaces(ind, out)
}
fn write_spaces<W>(spaces: usize, out: &mut W) -> Result<(), W::Error>
where
W: ?Sized + Render,
{
let mut inserted = 0;
while inserted < spaces {
let insert = core::cmp::min(SPACES.len(), spaces - inserted);
inserted += out.write_str(&SPACES[..insert])?;
}
Ok(())
}
struct Best {
pos: usize,
back_cmds: Vec<RenderCommand>,
front_cmds: Vec<Rc<PrettyTree>>,
annotation_levels: Vec<usize>,
width: usize,
}
impl Best {
fn fitting(&mut self, next: Rc<PrettyTree>, mut pos: usize, ind: usize) -> bool {
let mut bidx = self.back_cmds.len();
self.front_cmds.clear(); // clear from previous calls from best
self.front_cmds.push(next);
let mut mode = Mode::Flat;
loop {
let mut doc = match self.front_cmds.pop() {
None => {
if bidx == 0 {
// All commands have been processed
return true;
} else {
bidx -= 1;
mode = Mode::Break;
self.back_cmds[bidx].node.clone()
}
}
Some(cmd) => cmd,
};
loop {
match doc.as_ref() {
PrettyTree::Nil => {}
PrettyTree::Append { lhs, rhs } => {
doc = append_docs2(lhs.clone(), rhs.clone(), |send| self.front_cmds.push(send));
continue;
}
// Newlines inside the group makes it not fit, but those outside lets it
// fit on the current line
PrettyTree::Hardline => return mode == Mode::Break,
PrettyTree::RenderLength { length: len, body: _ } => {
pos += len;
if pos > self.width {
return false;
}
}
PrettyTree::StaticText(str) => {
pos += str.len();
if pos > self.width {
return false;
}
}
PrettyTree::Text(ref str) => {
pos += str.len();
if pos > self.width {
return false;
}
}
PrettyTree::MaybeInline { block: flat, inline } => {
doc = match mode {
Mode::Break => flat.clone(),
Mode::Flat => inline.clone(),
};
continue;
}
PrettyTree::Column { invoke: function } => {
doc = Rc::new(function(pos));
continue;
}
PrettyTree::Nesting { invoke: function } => {
doc = Rc::new(function(ind));
continue;
}
PrettyTree::Nest { space: _, doc: next }
| PrettyTree::Group { items: next }
| PrettyTree::Annotated { style: _, body: next }
| PrettyTree::Union { lhs: _, rhs: next } => {
doc = next.clone();
continue;
}
PrettyTree::Fail => return false,
}
break;
}
}
}
fn best<W>(&mut self, top: usize, out: &mut W) -> Result<bool, W::Error>
where
W: RenderAnnotated,
W: ?Sized,
{
let mut fits = true;
while top < self.back_cmds.len() {
let mut cmd = self.back_cmds.pop().unwrap();
loop {
let RenderCommand { indent: ind, mode, node } = cmd;
match node.as_ref() {
PrettyTree::Nil => {}
PrettyTree::Append { lhs, rhs } => {
cmd.node = append_docs2(lhs.clone(), rhs.clone(), |send| {
self.back_cmds.push(RenderCommand { indent: ind, mode, node: send })
});
continue;
}
PrettyTree::MaybeInline { block, inline } => {
cmd.node = match mode {
Mode::Break => block.clone(),
Mode::Flat => inline.clone(),
};
continue;
}
PrettyTree::Group { items } => {
match mode {
Mode::Break if self.fitting(items.clone(), self.pos, ind) => {
cmd.mode = Mode::Flat;
}
_ => {}
}
cmd.node = items.clone();
continue;
}
PrettyTree::Nest { space, doc } => {
// Once https://doc.rust-lang.org/std/primitive.usize.html#method.saturating_add_signed is stable
// this can be replaced
let new_ind = if *space >= 0 {
ind.saturating_add(*space as usize)
} else {
ind.saturating_sub(space.unsigned_abs())
};
cmd = RenderCommand { indent: new_ind, mode, node: doc.clone() };
continue;
}
PrettyTree::Hardline => {
// The next document may have different indentation so we should use it if we can
match self.back_cmds.pop() {
Some(next) => {
write_newline(next.indent, out)?;
self.pos = next.indent;
cmd = next;
continue;
}
None => {
write_newline(ind, out)?;
self.pos = ind;
}
}
}
PrettyTree::RenderLength { length: len, body: doc } => match doc.as_ref() {
PrettyTree::Text(s) => {
out.write_str_all(s)?;
self.pos += len;
fits &= self.pos <= self.width;
}
PrettyTree::StaticText(s) => {
out.write_str_all(s)?;
self.pos += len;
fits &= self.pos <= self.width;
}
_ => unreachable!(),
},
PrettyTree::Text(ref s) => {
out.write_str_all(s)?;
self.pos += s.len();
fits &= self.pos <= self.width;
}
PrettyTree::StaticText(s) => {
out.write_str_all(s)?;
self.pos += s.len();
fits &= self.pos <= self.width;
}
PrettyTree::Annotated { style: color, body: doc } => {
out.push_annotation(color.clone())?;
self.annotation_levels.push(self.back_cmds.len());
cmd.node = doc.clone();
continue;
}
PrettyTree::Union { lhs: left, rhs: right } => {
let pos = self.pos;
let annotation_levels = self.annotation_levels.len();
let bcmds = self.back_cmds.len();
self.back_cmds.push(RenderCommand { indent: ind, mode, node: left.clone() });
let mut buffer = BufferWrite::new(0);
match self.best(bcmds, &mut buffer) {
Ok(true) => buffer.render(out)?,
Ok(false) | Err(_) => {
self.pos = pos;
self.back_cmds.truncate(bcmds);
self.annotation_levels.truncate(annotation_levels);
cmd.node = right.clone();
continue;
}
}
}
PrettyTree::Column { invoke: column } => {
cmd.node = Rc::new(column(self.pos));
continue;
}
PrettyTree::Nesting { invoke: nesting } => {
cmd.node = Rc::new(nesting(self.pos));
continue;
}
PrettyTree::Fail => return Err(out.fail_doc()),
}
break;
}
while self.annotation_levels.last() == Some(&self.back_cmds.len()) {
self.annotation_levels.pop();
out.pop_annotation()?;
}
}
Ok(fits)
}
}

View file

@ -0,0 +1,121 @@
use crate::{render::Annotation, Render, RenderAnnotated};
use alloc::rc::Rc;
use color_ansi::AnsiStyle;
use core::fmt::{Debug, Formatter};
/// Writes to something implementing `std::fmt::Write`
pub struct FmtWrite<W> {
upstream: W,
}
/// Represents a terminal writer.
#[derive(Debug)]
pub struct BufferWrite {
buffer: String,
annotations: Vec<(usize, Annotation<AnsiStyle>)>,
}
impl BufferWrite {
/// Creates a new terminal writer.
pub fn new(capacity: usize) -> Self {
BufferWrite { buffer: String::with_capacity(capacity), annotations: Vec::new() }
}
/// Creates a new terminal writer.
pub fn render<W>(&mut self, render: &mut W) -> Result<(), W::Error>
where
W: RenderAnnotated,
W: ?Sized,
{
let mut start = 0;
for (end, annotation) in &self.annotations {
let s = &self.buffer[start..*end];
if !s.is_empty() {
render.write_str_all(s)?;
}
start = *end;
match annotation {
Annotation::Push(a) => render.push_annotation(a.clone())?,
Annotation::Pop => render.pop_annotation()?,
}
}
let s = &self.buffer[start..];
if !s.is_empty() {
render.write_str_all(s)?;
}
Ok(())
}
}
impl Render for BufferWrite {
type Error = core::fmt::Error;
fn write_str(&mut self, s: &str) -> Result<usize, Self::Error> {
self.buffer.push_str(s);
Ok(s.len())
}
fn write_str_all(&mut self, s: &str) -> Result<(), Self::Error> {
self.buffer.push_str(s);
Ok(())
}
fn fail_doc(&self) -> Self::Error {
core::fmt::Error::default()
}
}
impl<W> RenderAnnotated for FmtWrite<W>
where
W: core::fmt::Write,
{
fn push_annotation(&mut self, _: Rc<AnsiStyle>) -> Result<(), Self::Error> {
Ok(())
}
fn pop_annotation(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
impl RenderAnnotated for BufferWrite {
fn push_annotation(&mut self, annotation: Rc<AnsiStyle>) -> Result<(), Self::Error> {
self.annotations.push((self.buffer.len(), Annotation::Push(annotation)));
Ok(())
}
fn pop_annotation(&mut self) -> Result<(), Self::Error> {
self.annotations.push((self.buffer.len(), Annotation::Pop));
Ok(())
}
}
impl<W> Debug for FmtWrite<W> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("IoWrite").finish()
}
}
impl<W> FmtWrite<W> {
/// Create a new `FmtWrite` from something implementing `std::fmt::Write`
pub fn new(upstream: W) -> FmtWrite<W> {
FmtWrite { upstream }
}
}
impl<W> Render for FmtWrite<W>
where
W: core::fmt::Write,
{
type Error = core::fmt::Error;
fn write_str(&mut self, s: &str) -> Result<usize, core::fmt::Error> {
self.write_str_all(s).map(|_| s.len())
}
fn write_str_all(&mut self, s: &str) -> core::fmt::Result {
self.upstream.write_str(s)
}
fn fail_doc(&self) -> Self::Error {
core::fmt::Error
}
}

View file

@ -0,0 +1,114 @@
use crate::{Render, RenderAnnotated};
use alloc::rc::Rc;
use color_ansi::{AnsiAbility, AnsiStyle, AnsiWriter};
use core::fmt::{Debug, Formatter};
use std::io::{Error, ErrorKind, Write};
/// Represents a terminal writer.
pub struct TerminalWriter<W> {
color_stack: Vec<Rc<AnsiStyle>>,
upstream: AnsiWriter<W>,
}
/// Writes to something implementing `std::io::Write`
pub struct IoWrite<W> {
upstream: W,
}
impl<W> Debug for IoWrite<W> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("IoWrite").finish()
}
}
impl<W> IoWrite<W> {
/// Creates a new terminal writer.
pub fn new(upstream: W) -> IoWrite<W> {
IoWrite { upstream }
}
}
#[cfg(feature = "std")]
impl<W> Render for IoWrite<W>
where
W: std::io::Write,
{
type Error = std::io::Error;
fn write_str(&mut self, s: &str) -> std::io::Result<usize> {
self.upstream.write(s.as_bytes())
}
fn write_str_all(&mut self, s: &str) -> std::io::Result<()> {
self.upstream.write_all(s.as_bytes())
}
fn fail_doc(&self) -> Self::Error {
std::io::Error::new(std::io::ErrorKind::Other, "Document failed to render")
}
}
#[cfg(feature = "std")]
impl<W> RenderAnnotated for IoWrite<W>
where
W: std::io::Write,
{
fn push_annotation(&mut self, _: Rc<AnsiStyle>) -> Result<(), Self::Error> {
Ok(())
}
fn pop_annotation(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}
impl<W> Debug for TerminalWriter<W> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TerminalWriter").finish()
}
}
impl<W: Write> TerminalWriter<W> {
/// Creates a new terminal writer.
pub fn new(upstream: W) -> Self {
TerminalWriter { color_stack: Vec::new(), upstream: AnsiWriter::new(upstream) }
}
/// Creates a new terminal writer with a specific color.
pub fn with_color(mut self, color: AnsiAbility) -> Self {
self.upstream.set_ability(color);
self
}
}
impl<W> Render for TerminalWriter<W>
where
W: Write,
{
type Error = Error;
fn write_str(&mut self, s: &str) -> std::io::Result<usize> {
self.upstream.write(s.as_bytes())
}
fn write_str_all(&mut self, s: &str) -> std::io::Result<()> {
self.upstream.write_all(s.as_bytes())
}
fn fail_doc(&self) -> Self::Error {
Error::new(ErrorKind::Other, "Document failed to render")
}
}
impl<W: Write> RenderAnnotated for TerminalWriter<W> {
fn push_annotation(&mut self, color: Rc<AnsiStyle>) -> Result<(), Self::Error> {
self.color_stack.push(color.clone());
self.upstream.set_style(&color)
}
fn pop_annotation(&mut self) -> Result<(), Self::Error> {
self.color_stack.pop();
match self.color_stack.last() {
Some(previous) => self.upstream.set_style(previous),
None => self.upstream.reset_style(),
}
}
}

View file

@ -0,0 +1,34 @@
use crate::{providers::PrettyProvider, PrettyTree};
use alloc::string::String;
pub mod printer;
/// The `PrettyPrint` trait is implemented by types that can be pretty-printed.
pub trait PrettyBuilder {
/// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line.
///
/// ```
/// use pretty::{Arena, DocAllocator};
///
/// let arena = Arena::<()>::new();
/// let body = arena.line().append("x");
/// let doc = arena
/// .text("let")
/// .append(arena.line())
/// .append("x")
/// .group()
/// .append(body.clone().flat_alt(arena.line().append("in").append(body)))
/// .group();
///
/// assert_eq!(doc.1.pretty(100).to_string(), "let x in x");
/// assert_eq!(doc.1.pretty(8).to_string(), "let x\nx");
/// ```
fn flat_alt<E>(self, inline: E) -> PrettyTree
where
E: Into<PrettyTree>;
/// Acts as `self` when laid out on a single line and acts as `that` when laid out on multiple lines.
fn indent(self, indent: usize) -> PrettyTree;
/// Increase the indentation level of this document.
fn nest(self, offset: isize) -> PrettyTree;
}

View file

@ -0,0 +1,37 @@
use super::*;
/// Marker trait for types that can be pretty printed.
pub trait PrettyPrint {
/// Build a pretty tree for this type.
fn pretty(&self, theme: &PrettyProvider) -> PrettyTree;
/// Get a pretty string for this type.
fn pretty_string(&self, theme: &PrettyProvider) -> String {
let mut buffer = String::new();
if let Err(e) = self.pretty(&theme).render_fmt(theme.get_width(), &mut buffer) {
panic!("Error: {}", e);
}
buffer
}
/// Print a pretty string for this type.
fn pretty_colorful(&self, theme: &PrettyProvider) -> String {
let mut buffer = vec![];
if let Err(e) = self.pretty(&theme).render_colored(theme.get_width(), &mut buffer) {
panic!("Error: {}", e);
}
match String::from_utf8(buffer) {
Ok(s) => s,
Err(e) => panic!("Error: {}", e),
}
}
}
impl PrettyPrint for PrettyTree {
fn pretty(&self, _: &PrettyProvider) -> PrettyTree {
self.clone()
}
}
impl PrettyPrint for &'static str {
fn pretty(&self, _: &PrettyProvider) -> PrettyTree {
PrettyTree::StaticText(*self)
}
}

View file

@ -0,0 +1,89 @@
use super::*;
impl Default for PrettyTree {
fn default() -> Self {
Self::Nil
}
}
impl Clone for PrettyTree {
fn clone(&self) -> Self {
match self {
Self::Nil => Self::Nil,
Self::Hardline => Self::Hardline,
Self::Text(s) => Self::Text(s.clone()),
Self::StaticText(s) => Self::StaticText(*s),
Self::Annotated { style: color, body: doc } => Self::Annotated { style: color.clone(), body: doc.clone() },
Self::Append { lhs, rhs } => Self::Append { lhs: lhs.clone(), rhs: rhs.clone() },
Self::Group { items } => Self::Group { items: items.clone() },
Self::MaybeInline { block, inline } => Self::MaybeInline { block: block.clone(), inline: inline.clone() },
Self::Nest { space, doc } => Self::Nest { space: *space, doc: doc.clone() },
Self::RenderLength { length: len, body: doc } => Self::RenderLength { length: *len, body: doc.clone() },
Self::Union { lhs: left, rhs: right } => Self::Union { lhs: left.clone(), rhs: right.clone() },
Self::Column { invoke: column } => Self::Column { invoke: column.clone() },
Self::Nesting { invoke: nesting } => Self::Nesting { invoke: nesting.clone() },
Self::Fail => Self::Fail,
}
}
}
impl Debug for PrettyTree {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let is_line = |doc: &PrettyTree| match doc {
PrettyTree::MaybeInline { block: flat, inline: alt } => {
matches!((&**flat, &**alt), (PrettyTree::Hardline, PrettyTree::StaticText(" ")))
}
_ => false,
};
let is_line_ = |doc: &PrettyTree| match doc {
PrettyTree::MaybeInline { block: flat, inline: alt } => {
matches!((&**flat, &**alt), (PrettyTree::Hardline, PrettyTree::Nil))
}
_ => false,
};
match self {
PrettyTree::Nil => f.debug_tuple("Nil").finish(),
PrettyTree::Append { lhs: _, rhs: _ } => {
let mut f = f.debug_list();
append_docs(self, &mut |doc| {
f.entry(doc);
});
f.finish()
}
_ if is_line(self) => f.debug_tuple("Line").finish(),
_ if is_line_(self) => f.debug_tuple("Line?").finish(),
PrettyTree::MaybeInline { block, inline } => f.debug_tuple("FlatAlt").field(block).field(inline).finish(),
PrettyTree::Group { items } => {
if is_line(self) {
return f.debug_tuple("SoftLine").finish();
}
if is_line_(self) {
return f.debug_tuple("SoftLine?").finish();
}
f.debug_tuple("Group").field(items).finish()
}
PrettyTree::Nest { space, doc } => f.debug_tuple("Nest").field(&space).field(doc).finish(),
PrettyTree::Hardline => f.debug_tuple("Hardline").finish(),
PrettyTree::RenderLength { body: doc, .. } => doc.fmt(f),
PrettyTree::Text(s) => Debug::fmt(s, f),
PrettyTree::StaticText(s) => Debug::fmt(s, f),
PrettyTree::Annotated { style: color, body: doc } => f.debug_tuple("Annotated").field(color).field(doc).finish(),
PrettyTree::Union { lhs: left, rhs: right } => f.debug_tuple("Union").field(left).field(right).finish(),
PrettyTree::Column { .. } => f.debug_tuple("Column(..)").finish(),
PrettyTree::Nesting { .. } => f.debug_tuple("Nesting(..)").finish(),
PrettyTree::Fail => f.debug_tuple("Fail").finish(),
}
}
}
fn append_docs(mut doc: &PrettyTree, consumer: &mut impl FnMut(&PrettyTree)) {
loop {
match doc {
PrettyTree::Append { lhs, rhs } => {
append_docs(lhs, consumer);
doc = rhs;
}
_ => break consumer(doc),
}
}
}

View file

@ -0,0 +1,50 @@
use super::*;
impl<T> Add<T> for PrettyTree
where
T: Into<Self>,
{
type Output = Self;
fn add(self, rhs: T) -> Self::Output {
self.append(rhs.into())
}
}
impl<T> AddAssign<T> for PrettyTree
where
T: Into<Self>,
{
fn add_assign(&mut self, rhs: T) {
*self = self.clone().append(rhs.into());
}
}
impl<T> From<Option<T>> for PrettyTree
where
Self: From<T>,
{
fn from(x: Option<T>) -> Self {
match x {
Some(x) => x.into(),
None => Self::Nil,
}
}
}
impl From<()> for PrettyTree {
fn from(_: ()) -> Self {
Self::Nil
}
}
impl From<&'static str> for PrettyTree {
fn from(s: &'static str) -> Self {
Self::StaticText(s)
}
}
impl From<String> for PrettyTree {
fn from(s: String) -> Self {
Self::Text(Rc::from(s))
}
}

View file

@ -0,0 +1,449 @@
use crate::{helpers::PrettySequence, render, FmtWrite, PrettyBuilder, RenderAnnotated};
use alloc::{borrow::Cow, rc::Rc, string::String};
use color_ansi::AnsiStyle;
use core::{
fmt::{Debug, Formatter},
ops::{Add, AddAssign},
};
use std::io::Write;
use unicode_segmentation::UnicodeSegmentation;
mod display;
mod into;
/// The concrete document type. This type is not meant to be used directly. Instead use the static
/// functions on `Doc` or the methods on an `DocAllocator`.
///
/// The `T` parameter is used to abstract over pointers to `Doc`. See `RefDoc` and `BoxDoc` for how
/// it is used
pub enum PrettyTree {
/// Nothing to show
Nil,
/// A hard line break
Hardline,
/// A dynamic text document, all newlines are hard line breaks
Text(Rc<str>),
/// A static text document, all newlines are hard line breaks
StaticText(&'static str),
/// A document with ansi styles
Annotated {
/// The style to use for the text
style: Rc<AnsiStyle>,
/// The text to display
body: Rc<Self>,
},
/// Concatenates two documents
Append {
/// The first document
lhs: Rc<Self>,
/// The second document
rhs: Rc<Self>,
},
/// Concatenates two documents with a space in between
Group {
/// The first document
items: Rc<Self>,
},
/// Concatenates two documents with a line in between
MaybeInline {
/// The first document
block: Rc<Self>,
/// The second document
inline: Rc<Self>,
},
/// Concatenates two documents with a line in between
Nest {
/// The first document
space: isize,
/// The second document
doc: Rc<Self>,
},
/// Stores the length of a string document that is not just ascii
RenderLength {
/// The length of the string
length: usize,
/// The document
body: Rc<Self>,
},
/// Concatenates two documents with a line in between
Union {
/// The first document
lhs: Rc<Self>,
/// The second document
rhs: Rc<Self>,
},
/// Concatenates two documents with a line in between
Column {
/// The first document
invoke: Rc<dyn Fn(usize) -> Self>,
},
/// Concatenates two documents with a line in between
Nesting {
/// The first document
invoke: Rc<dyn Fn(usize) -> Self>,
},
/// Concatenates two documents with a line in between
Fail,
}
#[allow(non_upper_case_globals)]
impl PrettyTree {
/// A hard line break
pub const Space: Self = PrettyTree::StaticText(" ");
/// A line acts like a `\n` but behaves like `space` if it is grouped on a single line.
#[inline]
pub fn line_or_space() -> Self {
Self::Hardline.flat_alt(Self::Space).into()
}
/// A line acts like `\n` but behaves like `nil` if it is grouped on a single line.
#[inline]
pub fn line_or_comma() -> Self {
Self::Hardline.flat_alt(PrettyTree::StaticText(", ")).into()
}
/// Acts like `line` but behaves like `nil` if grouped on a single line
#[inline]
pub fn line_or_nil() -> Self {
Self::Hardline.flat_alt(Self::Nil).into()
}
}
impl PrettyTree {
/// The given text, which must not contain line breaks.
#[inline]
pub fn text<U: Into<Cow<'static, str>>>(data: U) -> Self {
match data.into() {
Cow::Borrowed(s) => PrettyTree::StaticText(s),
Cow::Owned(s) => PrettyTree::Text(Rc::from(s)),
}
.with_utf8_len()
}
}
impl PrettyTree {
/// Writes a rendered document to a `std::io::Write` object.
#[inline]
#[cfg(feature = "std")]
pub fn render<W>(&self, width: usize, out: &mut W) -> std::io::Result<()>
where
W: ?Sized + std::io::Write,
{
self.render_raw(width, &mut crate::IoWrite::new(out))
}
/// Writes a rendered document to a `std::fmt::Write` object.
#[inline]
pub fn render_fmt<W>(&self, width: usize, out: &mut W) -> core::fmt::Result
where
W: ?Sized + core::fmt::Write,
{
self.render_raw(width, &mut FmtWrite::new(out))
}
/// Writes a rendered document to a `RenderAnnotated<A>` object.
#[inline]
pub fn render_raw<W>(&self, width: usize, out: &mut W) -> Result<(), W::Error>
where
W: RenderAnnotated,
W: ?Sized,
{
render::best(Rc::new(self.clone()), width, out)
}
}
impl PrettyTree {
/// The given text, which must not contain line breaks.
#[inline]
#[cfg(feature = "std")]
pub fn render_colored<W: Write>(&self, width: usize, out: W) -> std::io::Result<()> {
render::best(Rc::new(self.clone()), width, &mut crate::TerminalWriter::new(out))
}
}
impl PrettyBuilder for PrettyTree {
/// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line.
///
/// ```
/// use pretty::{Arena, DocAllocator};
///
/// let arena = Arena::<()>::new();
/// let body = arena.line().append("x");
/// let doc = arena
/// .text("let")
/// .append(arena.line())
/// .append("x")
/// .group()
/// .append(body.clone().flat_alt(arena.line().append("in").append(body)))
/// .group();
///
/// assert_eq!(doc.1.pretty(100).to_string(), "let x in x");
/// assert_eq!(doc.1.pretty(8).to_string(), "let x\nx");
/// ```
#[inline]
fn flat_alt<E>(self, flat: E) -> Self
where
E: Into<PrettyTree>,
{
Self::MaybeInline { block: Rc::new(self), inline: Rc::new(flat.into()) }
}
/// Indents `self` by `adjust` spaces from the current cursor position
///
/// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr
/// like `RefDoc` or `RcDoc`
///
/// ```rust
/// use pretty::DocAllocator;
///
/// let arena = pretty::Arena::<()>::new();
/// let doc = arena
/// .text("prefix")
/// .append(arena.text(" "))
/// .append(arena.reflow("The indent function indents these words!").indent(4));
/// assert_eq!(
/// doc.1.pretty(24).to_string(),
/// "
/// prefix The indent
/// function
/// indents these
/// words!"
/// .trim_start(),
/// );
/// ```
#[inline]
fn indent(self, adjust: usize) -> Self {
let spaces = {
use crate::render::SPACES;
let mut doc = PrettyTree::Nil;
let mut remaining = adjust;
while remaining != 0 {
let i = SPACES.len().min(remaining);
remaining -= i;
doc = doc.append(PrettyTree::text(&SPACES[..i]))
}
doc
};
spaces.append(self).hang(adjust.try_into().unwrap())
}
/// Increase the indentation level of this document.
#[inline]
fn nest(self, offset: isize) -> Self {
if let Self::Nil = self {
return self;
}
if offset == 0 {
return self;
}
Self::Nest { space: offset, doc: Rc::new(self) }
}
}
impl PrettyTree {
fn with_utf8_len(self) -> Self {
let s = match &self {
Self::Text(s) => s.as_ref(),
Self::StaticText(s) => s,
// Doc::SmallText(s) => s,
_ => return self,
};
if s.is_ascii() {
self
}
else {
let grapheme_len = s.graphemes(true).count();
Self::RenderLength { length: grapheme_len, body: Rc::new(self) }
}
}
/// Append the given document after this document.
#[inline]
pub fn append<E>(self, follow: E) -> Self
where
E: Into<PrettyTree>,
{
let rhs = follow.into();
match (&self, &rhs) {
(Self::Nil, _) => rhs,
(_, Self::Nil) => self,
_ => Self::Append { lhs: Rc::new(self), rhs: Rc::new(rhs) },
}
}
/// Allocate a document that intersperses the given separator `S` between the given documents
/// `[A, B, C, ..., Z]`, yielding `[A, S, B, S, C, S, ..., S, Z]`.
///
/// Compare [the `intersperse` method from the `itertools` crate](https://docs.rs/itertools/0.5.9/itertools/trait.Itertools.html#method.intersperse).
///
/// NOTE: The separator type, `S` may need to be cloned. Consider using cheaply cloneable ptr
/// like `RefDoc` or `RcDoc`
#[inline]
pub fn join<I, T1, T2>(terms: I, joint: T2) -> PrettyTree
where
I: IntoIterator<Item = T1>,
T1: Into<PrettyTree>,
T2: Into<PrettyTree>,
{
let joint = joint.into();
let mut iter = terms.into_iter().map(|s| s.into());
let mut terms = PrettySequence::new(0);
terms += iter.next().unwrap_or(PrettyTree::Nil);
for term in iter {
terms += joint.clone();
terms += term;
}
terms.into()
}
/// Allocate a document that intersperses the given separator `S` between the given documents
pub fn concat<I>(docs: I) -> Self
where
I: IntoIterator,
I::Item: Into<PrettyTree>,
{
let mut head = Self::Nil;
for item in docs.into_iter() {
head += item.into();
}
head
}
/// Mark this document as a group.
///
/// Groups are layed out on a single line if possible. Within a group, all basic documents with
/// several possible layouts are assigned the same layout, that is, they are all layed out
/// horizontally and combined into a one single line, or they are each layed out on their own
/// line.
#[inline]
pub fn group(self) -> Self {
match self {
Self::Group { .. } | Self::Text(_) | Self::StaticText(_) | Self::Nil => self,
_ => Self::Group { items: Rc::new(self) },
}
}
/// Mark this document as a comment.
#[inline]
pub fn annotate(self, style: Rc<AnsiStyle>) -> Self {
Self::Annotated { style: style, body: Rc::new(self) }
}
/// Mark this document as a hard line break.
#[inline]
pub fn union<E>(self, other: E) -> Self
where
E: Into<PrettyTree>,
{
Self::Union { lhs: Rc::new(self), rhs: Rc::new(other.into()) }
}
/// Lays out `self` so with the nesting level set to the current column
///
/// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr
/// like `RefDoc` or `RcDoc`
///
/// ```rust
/// use pretty::{docs, DocAllocator};
///
/// let arena = &pretty::Arena::<()>::new();
/// let doc = docs![
/// arena,
/// "lorem",
/// " ",
/// arena.intersperse(["ipsum", "dolor"].iter().cloned(), arena.line_()).align(),
/// arena.hardline(),
/// "next",
/// ];
/// assert_eq!(doc.1.pretty(80).to_string(), "lorem ipsum\n dolor\nnext");
/// ```
#[inline]
pub fn align(self) -> Self {
Self::Column {
invoke: Rc::new(move |col| {
let self_ = self.clone();
Self::Nesting { invoke: Rc::new(move |nest| self_.clone().nest(col as isize - nest as isize)) }
}),
}
}
/// Lays out `self` with a nesting level set to the current level plus `adjust`.
///
/// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr
/// like `RefDoc` or `RcDoc`
///
/// ```rust
/// use pretty::DocAllocator;
///
/// let arena = pretty::Arena::<()>::new();
/// let doc = arena
/// .text("prefix")
/// .append(arena.text(" "))
/// .append(arena.reflow("Indenting these words with nest").hang(4));
/// assert_eq!(
/// doc.1.pretty(24).to_string(),
/// "prefix Indenting these\n words with\n nest",
/// );
/// ```
#[inline]
pub fn hang(self, adjust: isize) -> Self {
self.nest(adjust).align()
}
/// Lays out `self` and provides the column width of it available to `f`
///
/// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr
/// like `RefDoc` or `RcDoc`
///
/// ```rust
/// use pretty::DocAllocator;
///
/// let arena = pretty::Arena::<()>::new();
/// let doc = arena
/// .text("prefix ")
/// .append(arena.column(|l| arena.text("| <- column ").append(arena.as_string(l)).into_doc()));
/// assert_eq!(doc.1.pretty(80).to_string(), "prefix | <- column 7");
/// ```
#[inline]
pub fn width<F>(self, f: F) -> Self
where
F: Fn(isize) -> Self + Clone + 'static,
{
Self::Column {
invoke: Rc::new(move |start| {
let f = f.clone();
self.clone() + Self::Column { invoke: Rc::new(move |end| f(end as isize - start as isize)) }
}),
}
}
/// Puts `self` between `before` and `after`
#[inline]
pub fn enclose<E, F>(self, before: E, after: F) -> Self
where
E: Into<Self>,
F: Into<Self>,
{
before.into().append(self).append(after.into())
}
/// Puts `self` between `before` and `after` if `cond` is true
pub fn single_quotes(self) -> Self {
self.enclose("'", "'")
}
/// Puts `self` between `before` and `after` if `cond` is true
pub fn double_quotes(self) -> Self {
self.enclose("\"", "\"")
}
/// Puts `self` between `before` and `after` if `cond` is true
pub fn parens(self) -> Self {
self.enclose("(", ")")
}
/// Puts `self` between `before` and `after` if `cond` is true
pub fn angles(self) -> Self {
self.enclose("<", ">")
}
/// Puts `self` between `before` and `after` if `cond` is true
pub fn braces(self) -> Self {
self.enclose("{", "}")
}
/// Puts `self` between `before` and `after` if `cond` is true
pub fn brackets(self) -> Self {
self.enclose("[", "]")
}
}

View file

@ -0,0 +1,6 @@
#[test]
fn ready() {
println!("it works!")
}
mod readme;

View file

@ -0,0 +1,5 @@
## Tests
```bash
wee test
```

View file

@ -0,0 +1,38 @@
use pretty_print::*;
use SExp::*;
enum SExp {
Atom(u32),
List(Vec<SExp>),
}
impl SExp {
/// Return a pretty printed format of self.
pub fn to_doc(&self) -> PrettyTree {
match self {
Atom(x) => PrettyTree::text(x.to_string()),
List(xs) => PrettyTree::text("(")
.append(PrettyTree::join(xs.into_iter().map(|x| x.to_doc()), PrettyTree::line_or_space()).nest(1).group())
.append(PrettyTree::text(")")),
}
}
/// Return a pretty printed format of self.
pub fn to_pretty(&self, width: usize) -> String {
let mut w = Vec::new();
self.to_doc().render(width, &mut w).unwrap();
String::from_utf8(w).unwrap()
}
}
fn main() {
let atom = Atom(5);
assert_eq!("5", atom.to_pretty(10));
let list = List(vec![Atom(1), Atom(2), Atom(3)]);
assert_eq!("(1 2 3)", list.to_pretty(10));
assert_eq!(
"\
(1
2
3)",
list.to_pretty(5)
);
}

View file

@ -0,0 +1,29 @@
[package]
name = "pretty-test"
publish = false
version = "0.0.0"
authors = ["Aster <192607617@qq.com>"]
description = "..."
repository = "https://github.com/oovm/sub_projects"
documentation = "https://docs.rs/sub_projects"
readme = "Readme.md"
license = "MPL-2.0"
edition = "2021"
exclude = ["package.json", "tests/**"]
[dependencies]
pretty = "0.12.1"
[dependencies.pretty-print]
version = "*"
default-features = false
features = ["std"]
path = "../pretty-print"
[dev-dependencies]
[features]
default = []
[package.metadata.docs.rs]
all-features = true

View file

@ -0,0 +1,6 @@
{
"private": true,
"scripts": {
"p": "cargo publish --allow-dirty"
}
}

View file

@ -0,0 +1,2 @@
Title
=====

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,5 @@
#![deny(missing_debug_implementations, missing_copy_implementations)]
#![warn(missing_docs, rustdoc::missing_crate_level_docs)]
#![doc = include_str!("../readme.md")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/oovm/shape-rs/dev/projects/images/Trapezohedron.svg")]
#![doc(html_favicon_url = "https://raw.githubusercontent.com/oovm/shape-rs/dev/projects/images/Trapezohedron.svg")]

View file

@ -0,0 +1,46 @@
#![allow(dead_code, unused)]
use pretty_print::{AnsiColor, AnsiStyle, PrettyBuilder, PrettyTree};
use std::{io::stdout, rc::Rc};
#[test]
fn ready() {
println!("it works!")
}
#[test]
fn box1() {
let doc = PrettyTree::text("hello")
.annotate(Rc::new(AnsiStyle::new(AnsiColor::Red)))
.append(PrettyTree::Hardline)
.append("the")
.append("")
.append("world");
let mut buffer = vec![];
doc.render_colored(10, &mut buffer).unwrap();
println!("{}", String::from_utf8_lossy(&buffer));
}
#[test]
fn box2() {
let doc = PrettyTree::join(vec!["hello", " ", " ", "world"], "").group();
let mut buffer = vec![];
doc.render_colored(10, &mut buffer).unwrap();
println!("{}", String::from_utf8_lossy(&buffer));
}
#[test]
fn box_doc_inference() {
let doc = PrettyTree::text("test").append(PrettyTree::line_or_space()).append(PrettyTree::text("test")).group();
println!("{}", doc.pretty(10));
}
#[test]
fn newline_in_text() {
let doc = PrettyTree::text("test")
.append(PrettyTree::line_or_space().append(PrettyTree::text("\"test\n test\"")).nest(4))
.group();
println!("{}", doc.pretty(10));
}

View file

@ -0,0 +1,5 @@
## Tests
```bash
wee test
```