mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
770 lines
19 KiB
Rust
770 lines
19 KiB
Rust
//! Pretty printing.
|
|
|
|
use std::fmt::{self, Arguments, Write};
|
|
|
|
use crate::color::{Color, RgbaColor};
|
|
use crate::eval::*;
|
|
use crate::geom::{Angle, Fractional, Length, Linear, Relative};
|
|
use crate::syntax::*;
|
|
|
|
/// Pretty print an item and return the resulting string.
|
|
pub fn pretty<T>(item: &T) -> String
|
|
where
|
|
T: Pretty + ?Sized,
|
|
{
|
|
let mut p = Printer::new();
|
|
item.pretty(&mut p);
|
|
p.finish()
|
|
}
|
|
|
|
/// Pretty print an item.
|
|
pub trait Pretty {
|
|
/// Pretty print this item into the given printer.
|
|
fn pretty(&self, p: &mut Printer);
|
|
}
|
|
|
|
/// A buffer into which items are printed.
|
|
pub struct Printer {
|
|
buf: String,
|
|
}
|
|
|
|
impl Printer {
|
|
/// Create a new pretty printer.
|
|
pub fn new() -> Self {
|
|
Self { buf: String::new() }
|
|
}
|
|
|
|
/// Push a character into the buffer.
|
|
pub fn push(&mut self, c: char) {
|
|
self.buf.push(c);
|
|
}
|
|
|
|
/// Push a string into the buffer.
|
|
pub fn push_str(&mut self, string: &str) {
|
|
self.buf.push_str(string);
|
|
}
|
|
|
|
/// Write formatted items into the buffer.
|
|
pub fn write_fmt(&mut self, fmt: Arguments<'_>) -> fmt::Result {
|
|
Write::write_fmt(self, fmt)
|
|
}
|
|
|
|
/// Write a list of items joined by a joiner.
|
|
pub fn join<T, I, F>(&mut self, items: I, joiner: &str, mut write_item: F)
|
|
where
|
|
I: IntoIterator<Item = T>,
|
|
F: FnMut(T, &mut Self),
|
|
{
|
|
let mut iter = items.into_iter();
|
|
if let Some(first) = iter.next() {
|
|
write_item(first, self);
|
|
}
|
|
for item in iter {
|
|
self.push_str(joiner);
|
|
write_item(item, self);
|
|
}
|
|
}
|
|
|
|
/// Finish pretty printing and return the underlying buffer.
|
|
pub fn finish(self) -> String {
|
|
self.buf
|
|
}
|
|
}
|
|
|
|
impl Write for Printer {
|
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
|
self.push_str(s);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Pretty for SyntaxTree {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
for node in self {
|
|
node.pretty(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for SyntaxNode {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
match self {
|
|
// TODO: Handle escaping.
|
|
Self::Text(text) => p.push_str(text),
|
|
Self::Space => p.push(' '),
|
|
Self::Linebreak(_) => p.push_str(r"\"),
|
|
Self::Parbreak(_) => p.push_str("\n\n"),
|
|
Self::Strong(_) => p.push('*'),
|
|
Self::Emph(_) => p.push('_'),
|
|
Self::Raw(raw) => raw.pretty(p),
|
|
Self::Heading(n) => n.pretty(p),
|
|
Self::List(n) => n.pretty(p),
|
|
Self::Enum(n) => n.pretty(p),
|
|
Self::Expr(n) => {
|
|
if n.has_short_form() {
|
|
p.push('#');
|
|
}
|
|
n.pretty(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for RawNode {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
// Find out how many backticks we need.
|
|
let mut backticks = 1;
|
|
|
|
// Language tag and block-level are only possible with 3+ backticks.
|
|
if self.lang.is_some() || self.block {
|
|
backticks = 3;
|
|
}
|
|
|
|
// More backticks may be required if there are lots of consecutive
|
|
// backticks.
|
|
let mut count = 0;
|
|
for c in self.text.chars() {
|
|
if c == '`' {
|
|
count += 1;
|
|
backticks = backticks.max(3).max(count + 1);
|
|
} else {
|
|
count = 0;
|
|
}
|
|
}
|
|
|
|
// Starting backticks.
|
|
for _ in 0 .. backticks {
|
|
p.push('`');
|
|
}
|
|
|
|
// Language tag.
|
|
if let Some(lang) = &self.lang {
|
|
lang.pretty(p);
|
|
}
|
|
|
|
// Start untrimming.
|
|
if self.block {
|
|
p.push('\n');
|
|
} else if backticks >= 3 {
|
|
p.push(' ');
|
|
}
|
|
|
|
// The lines.
|
|
p.push_str(&self.text);
|
|
|
|
// End untrimming.
|
|
if self.block {
|
|
p.push('\n');
|
|
} else if self.text.trim_end().ends_with('`') {
|
|
p.push(' ');
|
|
}
|
|
|
|
// Ending backticks.
|
|
for _ in 0 .. backticks {
|
|
p.push('`');
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for HeadingNode {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
for _ in 0 .. self.level {
|
|
p.push('=');
|
|
}
|
|
p.push(' ');
|
|
self.body.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for ListItem {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("- ");
|
|
self.body.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for EnumItem {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
if let Some(number) = self.number {
|
|
write!(p, "{}", number).unwrap();
|
|
}
|
|
p.push_str(". ");
|
|
self.body.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for Expr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
match self {
|
|
Self::None(_) => p.push_str("none"),
|
|
Self::Auto(_) => p.push_str("auto"),
|
|
Self::Bool(_, v) => v.pretty(p),
|
|
Self::Int(_, v) => v.pretty(p),
|
|
Self::Float(_, v) => v.pretty(p),
|
|
Self::Length(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
|
|
Self::Angle(_, v, u) => write!(p, "{}{}", v, u).unwrap(),
|
|
Self::Percent(_, v) => write!(p, "{}%", v).unwrap(),
|
|
Self::Fractional(_, v) => write!(p, "{}fr", v).unwrap(),
|
|
Self::Str(_, v) => v.pretty(p),
|
|
Self::Ident(v) => v.pretty(p),
|
|
Self::Array(v) => v.pretty(p),
|
|
Self::Dict(v) => v.pretty(p),
|
|
Self::Template(v) => v.pretty(p),
|
|
Self::Group(v) => v.pretty(p),
|
|
Self::Block(v) => v.pretty(p),
|
|
Self::Unary(v) => v.pretty(p),
|
|
Self::Binary(v) => v.pretty(p),
|
|
Self::Call(v) => v.pretty(p),
|
|
Self::Closure(v) => v.pretty(p),
|
|
Self::With(v) => v.pretty(p),
|
|
Self::Let(v) => v.pretty(p),
|
|
Self::If(v) => v.pretty(p),
|
|
Self::While(v) => v.pretty(p),
|
|
Self::For(v) => v.pretty(p),
|
|
Self::Import(v) => v.pretty(p),
|
|
Self::Include(v) => v.pretty(p),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for ArrayExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
p.join(&self.items, ", ", |item, p| item.pretty(p));
|
|
if self.items.len() == 1 {
|
|
p.push(',');
|
|
}
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for DictExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
if self.items.is_empty() {
|
|
p.push(':');
|
|
} else {
|
|
p.join(&self.items, ", ", |named, p| named.pretty(p));
|
|
}
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for Named {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
self.name.pretty(p);
|
|
p.push_str(": ");
|
|
self.expr.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for TemplateExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('[');
|
|
self.tree.pretty(p);
|
|
p.push(']');
|
|
}
|
|
}
|
|
|
|
impl Pretty for GroupExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
self.expr.pretty(p);
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for BlockExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('{');
|
|
if self.exprs.len() > 1 {
|
|
p.push(' ');
|
|
}
|
|
p.join(&self.exprs, "; ", |expr, p| expr.pretty(p));
|
|
if self.exprs.len() > 1 {
|
|
p.push(' ');
|
|
}
|
|
p.push('}');
|
|
}
|
|
}
|
|
|
|
impl Pretty for UnaryExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
self.op.pretty(p);
|
|
if self.op == UnOp::Not {
|
|
p.push(' ');
|
|
}
|
|
self.expr.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for UnOp {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str(self.as_str());
|
|
}
|
|
}
|
|
|
|
impl Pretty for BinaryExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
self.lhs.pretty(p);
|
|
p.push(' ');
|
|
self.op.pretty(p);
|
|
p.push(' ');
|
|
self.rhs.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for BinOp {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str(self.as_str());
|
|
}
|
|
}
|
|
|
|
impl Pretty for CallExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
self.callee.pretty(p);
|
|
|
|
let mut write_args = |items: &[CallArg]| {
|
|
p.push('(');
|
|
p.join(items, ", ", |item, p| item.pretty(p));
|
|
p.push(')');
|
|
};
|
|
|
|
match self.args.items.as_slice() {
|
|
// This can be moved behind the arguments.
|
|
//
|
|
// Example: Transforms "#v(a, [b])" => "#v(a)[b]".
|
|
[head @ .., CallArg::Pos(Expr::Template(template))] => {
|
|
if !head.is_empty() {
|
|
write_args(head);
|
|
}
|
|
template.pretty(p);
|
|
}
|
|
|
|
items => write_args(items),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for CallArgs {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.join(&self.items, ", ", |item, p| item.pretty(p));
|
|
}
|
|
}
|
|
|
|
impl Pretty for CallArg {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
match self {
|
|
Self::Pos(expr) => expr.pretty(p),
|
|
Self::Named(named) => named.pretty(p),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for ClosureExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
p.join(self.params.iter(), ", ", |item, p| item.pretty(p));
|
|
p.push_str(") => ");
|
|
self.body.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for ClosureParam {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
match self {
|
|
Self::Pos(ident) => ident.pretty(p),
|
|
Self::Named(named) => named.pretty(p),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for WithExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
self.callee.pretty(p);
|
|
p.push_str(" with (");
|
|
self.args.pretty(p);
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for LetExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("let ");
|
|
self.binding.pretty(p);
|
|
if let Some(init) = &self.init {
|
|
p.push_str(" = ");
|
|
init.pretty(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for IfExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("if ");
|
|
self.condition.pretty(p);
|
|
p.push(' ');
|
|
self.if_body.pretty(p);
|
|
if let Some(expr) = &self.else_body {
|
|
// FIXME: Hashtag in markup.
|
|
p.push_str(" else ");
|
|
expr.pretty(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for WhileExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("while ");
|
|
self.condition.pretty(p);
|
|
p.push(' ');
|
|
self.body.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for ForExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("for ");
|
|
self.pattern.pretty(p);
|
|
p.push_str(" in ");
|
|
self.iter.pretty(p);
|
|
p.push(' ');
|
|
self.body.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for ForPattern {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
match self {
|
|
Self::Value(v) => v.pretty(p),
|
|
Self::KeyValue(k, v) => {
|
|
k.pretty(p);
|
|
p.push_str(", ");
|
|
v.pretty(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for ImportExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("import ");
|
|
self.imports.pretty(p);
|
|
p.push_str(" from ");
|
|
self.path.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for Imports {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
match self {
|
|
Self::Wildcard => p.push('*'),
|
|
Self::Idents(idents) => p.join(idents, ", ", |item, p| item.pretty(p)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for IncludeExpr {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("include ");
|
|
self.path.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for Ident {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str(self.as_str());
|
|
}
|
|
}
|
|
|
|
impl Pretty for Value {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
match self {
|
|
Self::None => p.push_str("none"),
|
|
Self::Auto => p.push_str("auto"),
|
|
Self::Bool(v) => v.pretty(p),
|
|
Self::Int(v) => v.pretty(p),
|
|
Self::Float(v) => v.pretty(p),
|
|
Self::Length(v) => v.pretty(p),
|
|
Self::Angle(v) => v.pretty(p),
|
|
Self::Relative(v) => v.pretty(p),
|
|
Self::Linear(v) => v.pretty(p),
|
|
Self::Fractional(v) => v.pretty(p),
|
|
Self::Color(v) => v.pretty(p),
|
|
Self::Str(v) => v.pretty(p),
|
|
Self::Array(v) => v.pretty(p),
|
|
Self::Dict(v) => v.pretty(p),
|
|
Self::Template(v) => v.pretty(p),
|
|
Self::Func(v) => v.pretty(p),
|
|
Self::Dyn(v) => v.pretty(p),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Pretty for Array {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
p.join(self, ", ", |item, p| item.pretty(p));
|
|
if self.len() == 1 {
|
|
p.push(',');
|
|
}
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for Dict {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
if self.is_empty() {
|
|
p.push(':');
|
|
} else {
|
|
p.join(self, ", ", |(key, value), p| {
|
|
p.push_str(key);
|
|
p.push_str(": ");
|
|
value.pretty(p);
|
|
});
|
|
}
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for Template {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("<template>");
|
|
}
|
|
}
|
|
|
|
impl Pretty for Function {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push_str("<function");
|
|
if let Some(name) = self.name() {
|
|
p.push(' ');
|
|
p.push_str(name);
|
|
}
|
|
p.push('>');
|
|
}
|
|
}
|
|
|
|
impl Pretty for FuncArgs {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('(');
|
|
p.join(&self.items, ", ", |item, p| item.pretty(p));
|
|
p.push(')');
|
|
}
|
|
}
|
|
|
|
impl Pretty for FuncArg {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
if let Some(name) = &self.name {
|
|
p.push_str(&name);
|
|
p.push_str(": ");
|
|
}
|
|
self.value.v.pretty(p);
|
|
}
|
|
}
|
|
|
|
impl Pretty for str {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
p.push('"');
|
|
for c in self.chars() {
|
|
match c {
|
|
'\\' => p.push_str(r"\\"),
|
|
'"' => p.push_str(r#"\""#),
|
|
'\n' => p.push_str(r"\n"),
|
|
'\r' => p.push_str(r"\r"),
|
|
'\t' => p.push_str(r"\t"),
|
|
_ => p.push(c),
|
|
}
|
|
}
|
|
p.push('"');
|
|
}
|
|
}
|
|
|
|
macro_rules! pretty_display {
|
|
($($type:ty),* $(,)?) => {
|
|
$(impl Pretty for $type {
|
|
fn pretty(&self, p: &mut Printer) {
|
|
write!(p, "{}", self).unwrap();
|
|
}
|
|
})*
|
|
};
|
|
}
|
|
|
|
pretty_display! {
|
|
i64,
|
|
f64,
|
|
bool,
|
|
Length,
|
|
Angle,
|
|
Relative,
|
|
Linear,
|
|
Fractional,
|
|
RgbaColor,
|
|
Color,
|
|
Dynamic,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::parse::parse;
|
|
use crate::source::SourceFile;
|
|
|
|
#[track_caller]
|
|
fn roundtrip(src: &str) {
|
|
test_parse(src, src);
|
|
}
|
|
|
|
#[track_caller]
|
|
fn test_parse(src: &str, exp: &str) {
|
|
let source = SourceFile::detached(src);
|
|
let ast = parse(&source).unwrap();
|
|
let found = pretty(&ast);
|
|
if exp != found {
|
|
println!("tree: {:#?}", ast);
|
|
println!("expected: {}", exp);
|
|
println!("found: {}", found);
|
|
panic!("test failed");
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
fn test_value(value: impl Into<Value>, exp: &str) {
|
|
assert_eq!(pretty(&value.into()), exp);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pretty_print_node() {
|
|
// Basic text and markup.
|
|
roundtrip("*");
|
|
roundtrip("_");
|
|
roundtrip(" ");
|
|
roundtrip("\\ ");
|
|
roundtrip("\n\n");
|
|
roundtrip("hi");
|
|
roundtrip("= *Ok*");
|
|
roundtrip("- Ok");
|
|
|
|
// Raw.
|
|
roundtrip("``");
|
|
roundtrip("`nolang 1`");
|
|
roundtrip("```lang 1```");
|
|
roundtrip("```lang 1 ```");
|
|
roundtrip("```hi line ```");
|
|
roundtrip("```py\ndef\n```");
|
|
roundtrip("```\n line \n```");
|
|
roundtrip("```\n`\n```");
|
|
roundtrip("``` ` ```");
|
|
roundtrip("````\n```\n```\n````");
|
|
test_parse("```lang```", "```lang ```");
|
|
test_parse("```1 ```", "``");
|
|
test_parse("``` 1```", "`1`");
|
|
test_parse("``` 1 ```", "`1 `");
|
|
test_parse("```` ` ````", "``` ` ```");
|
|
}
|
|
|
|
#[test]
|
|
fn test_pretty_print_expr() {
|
|
// Basic expressions.
|
|
roundtrip("{none}");
|
|
roundtrip("{auto}");
|
|
roundtrip("{true}");
|
|
roundtrip("{10}");
|
|
roundtrip("{3.14}");
|
|
roundtrip("{10pt}");
|
|
roundtrip("{14.1deg}");
|
|
roundtrip("{20%}");
|
|
roundtrip("{0.5fr}");
|
|
roundtrip(r#"{"hi"}"#);
|
|
test_parse(r#"{"let's \" go"}"#, r#"{"let's \" go"}"#);
|
|
roundtrip("{hi}");
|
|
|
|
// Arrays.
|
|
roundtrip("{()}");
|
|
roundtrip("{(1)}");
|
|
roundtrip("{(1, 2, 3)}");
|
|
|
|
// Dictionaries.
|
|
roundtrip("{(:)}");
|
|
roundtrip("{(key: value)}");
|
|
roundtrip("{(a: 1, b: 2)}");
|
|
|
|
// Templates.
|
|
roundtrip("[]");
|
|
roundtrip("[*Ok*]");
|
|
roundtrip("{[f]}");
|
|
|
|
// Groups.
|
|
roundtrip("{(1)}");
|
|
|
|
// Blocks.
|
|
roundtrip("{}");
|
|
roundtrip("{1}");
|
|
roundtrip("{ let x = 1; x += 2; x + 1 }");
|
|
roundtrip("[{}]");
|
|
|
|
// Operators.
|
|
roundtrip("{-x}");
|
|
roundtrip("{not true}");
|
|
roundtrip("{1 + 3}");
|
|
|
|
// Functions.
|
|
roundtrip("{v()}");
|
|
roundtrip("{v()()}");
|
|
roundtrip("{v(1)}");
|
|
roundtrip("{v(a: 1, b)}");
|
|
roundtrip("#v()");
|
|
roundtrip("#v(1)");
|
|
roundtrip("#v(1, 2)[*Ok*]");
|
|
roundtrip("#v(1, f[2])");
|
|
roundtrip("{(a, b) => a + b}");
|
|
|
|
// Control flow.
|
|
roundtrip("#let x = 1 + 2");
|
|
test_parse("#let f(x) = y", "#let f = (x) => y");
|
|
test_parse("#if x [y] #else [z]", "#if x [y] else [z]");
|
|
roundtrip("#while x {y}");
|
|
roundtrip("#for x in y {z}");
|
|
roundtrip("#for k, x in y {z}");
|
|
roundtrip("#import * from \"file.typ\"");
|
|
roundtrip("#include \"chapter1.typ\"");
|
|
}
|
|
|
|
#[test]
|
|
fn test_pretty_print_value() {
|
|
// Primitives.
|
|
test_value(Value::None, "none");
|
|
test_value(false, "false");
|
|
test_value(12i64, "12");
|
|
test_value(3.14, "3.14");
|
|
test_value(Length::pt(5.5), "5.5pt");
|
|
test_value(Angle::deg(90.0), "90deg");
|
|
test_value(Relative::one() / 2.0, "50%");
|
|
test_value(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm");
|
|
test_value(Fractional::one() * 7.55, "7.55fr");
|
|
test_value(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
|
|
|
|
// Collections.
|
|
test_value("hello", r#""hello""#);
|
|
test_value("\n", r#""\n""#);
|
|
test_value("\\", r#""\\""#);
|
|
test_value("\"", r#""\"""#);
|
|
test_value(array![], "()");
|
|
test_value(array![Value::None], "(none,)");
|
|
test_value(array![1, 2], "(1, 2)");
|
|
test_value(dict![], "(:)");
|
|
test_value(dict!["one" => 1], "(one: 1)");
|
|
test_value(dict!["two" => false, "one" => 1], "(one: 1, two: false)");
|
|
|
|
// Functions.
|
|
test_value(Function::new(None, |_, _| Ok(Value::None)), "<function>");
|
|
test_value(
|
|
Function::new(Some("nil".into()), |_, _| Ok(Value::None)),
|
|
"<function nil>",
|
|
);
|
|
|
|
// Dynamics.
|
|
test_value(Dynamic::new(1), "1");
|
|
}
|
|
}
|