* Implement pretty printing Signed-off-by: Victor Porof <victor.porof@gmail.com> * Rewrite pretty printing. This does a few things. First, it use pretty.rs directly, without the layer of macro obfuscation. The code is significantly simpler as a result. Second, it tightens the layout, using pretty.rs to group nested layouts that fit on a single line. This is Clojure's EDN style, more or less. Third, it drops "special format" support for queries. This wasn't completely implemented; if we want it, we can newtype Query(edn::Value) and figure out how to really implement this idea. * Rename to reflect functionality. * Make write interface more Rust-like. There isn't a clear standard in the stdlib, but a function that takes ownership of a writer and then returns it back is definitely not Rust-like. That's what a (mutable) reference is for. * Review comment: Use as_ref to avoid cloning strings. * Post: Fix tests to use `without_spans()`.
This commit is contained in:
parent
7476d0c0c8
commit
1b26e23d02
4 changed files with 200 additions and 3 deletions
|
@ -11,8 +11,10 @@ build = "build.rs"
|
||||||
readme = "./README.md"
|
readme = "./README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
itertools = "0.5.9"
|
||||||
num = "0.1.35"
|
num = "0.1.35"
|
||||||
ordered-float = "0.4.0"
|
ordered-float = "0.4.0"
|
||||||
|
pretty = "0.2.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
peg = "0.5.1"
|
peg = "0.5.1"
|
||||||
|
|
|
@ -8,13 +8,14 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
#![allow(dead_code)]
|
extern crate itertools;
|
||||||
|
|
||||||
extern crate ordered_float;
|
|
||||||
extern crate num;
|
extern crate num;
|
||||||
|
extern crate ordered_float;
|
||||||
|
extern crate pretty;
|
||||||
|
|
||||||
pub mod symbols;
|
pub mod symbols;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
pub mod pretty_print;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
pub mod parse {
|
pub mod parse {
|
||||||
|
|
192
edn/src/pretty_print.rs
Normal file
192
edn/src/pretty_print.rs
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
// Copyright 2016 Mozilla
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
// this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
use pretty;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use types::Value;
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
/// Return a pretty string representation of this `Value`.
|
||||||
|
pub fn to_pretty(&self, width: usize) -> Result<String, io::Error> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
self.write_pretty(width, &mut out)?;
|
||||||
|
Ok(String::from_utf8_lossy(&out).into_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a pretty representation of this `Value` to the given writer.
|
||||||
|
pub fn write_pretty<W>(&self, width: usize, out: &mut W) -> Result<(), io::Error> where W: io::Write {
|
||||||
|
self.as_doc(&pretty::BoxAllocator).1.render(width, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bracket a collection of values.
|
||||||
|
///
|
||||||
|
/// We aim for
|
||||||
|
/// [1 2 3]
|
||||||
|
/// and fall back if necessary to
|
||||||
|
/// [1,
|
||||||
|
/// 2,
|
||||||
|
/// 3].
|
||||||
|
fn bracket<'a, A, T, I>(&'a self, allocator: &'a A, open: T, vs: I, close: T) -> pretty::DocBuilder<'a, A>
|
||||||
|
where A: pretty::DocAllocator<'a>, T: Into<Cow<'a, str>>,
|
||||||
|
I: IntoIterator<Item=&'a Value>,
|
||||||
|
{
|
||||||
|
let open = open.into();
|
||||||
|
let n = open.len();
|
||||||
|
let i = vs.into_iter().map(|ref v| v.as_doc(allocator)).intersperse(allocator.space());
|
||||||
|
allocator.text(open)
|
||||||
|
.append(allocator.concat(i).nest(n))
|
||||||
|
.append(allocator.text(close))
|
||||||
|
.group()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively traverses this value and creates a pretty.rs document.
|
||||||
|
/// This pretty printing implementation is optimized for edn queries
|
||||||
|
/// readability and limited whitespace expansion.
|
||||||
|
pub fn as_doc<'a, A>(&'a self, pp: &'a A) -> pretty::DocBuilder<'a, A>
|
||||||
|
where A: pretty::DocAllocator<'a> {
|
||||||
|
match self {
|
||||||
|
&Value::Vector(ref vs) => self.bracket(pp, "[", vs, "]"),
|
||||||
|
&Value::List(ref vs) => self.bracket(pp, "(", vs, ")"),
|
||||||
|
&Value::Set(ref vs) => self.bracket(pp, "#{", vs, "}"),
|
||||||
|
&Value::Map(ref vs) => {
|
||||||
|
let xs = vs.iter().rev().map(|(ref k, ref v)| k.as_doc(pp).append(pp.space()).append(v.as_doc(pp)).group()).intersperse(pp.space());
|
||||||
|
pp.text("{")
|
||||||
|
.append(pp.concat(xs).nest(1))
|
||||||
|
.append(pp.text("}"))
|
||||||
|
.group()
|
||||||
|
}
|
||||||
|
&Value::NamespacedSymbol(ref v) => pp.text(v.namespace.as_ref()).append("/").append(v.name.as_ref()),
|
||||||
|
&Value::PlainSymbol(ref v) => pp.text(v.0.as_ref()),
|
||||||
|
&Value::NamespacedKeyword(ref v) => pp.text(":").append(v.namespace.as_ref()).append("/").append(v.name.as_ref()),
|
||||||
|
&Value::Keyword(ref v) => pp.text(":").append(v.0.as_ref()),
|
||||||
|
&Value::Text(ref v) => pp.text("\"").append(v.as_ref()).append("\""),
|
||||||
|
_ => pp.text(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use parse;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pp_io() {
|
||||||
|
let string = "$";
|
||||||
|
let data = parse::value(string).unwrap().without_spans();
|
||||||
|
|
||||||
|
assert_eq!(data.write_pretty(40, &mut Vec::new()).is_ok(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pp_types_empty() {
|
||||||
|
let string = "[ [ ] ( ) #{ } { }, \"\" ]";
|
||||||
|
let data = parse::value(string).unwrap().without_spans();
|
||||||
|
|
||||||
|
assert_eq!(data.to_pretty(40).unwrap(), "[[] () #{} {} \"\"]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_vector() {
|
||||||
|
let string = "[1 2 3 4 5 6]";
|
||||||
|
let data = parse::value(string).unwrap().without_spans();
|
||||||
|
|
||||||
|
assert_eq!(data.to_pretty(20).unwrap(), "[1 2 3 4 5 6]");
|
||||||
|
assert_eq!(data.to_pretty(10).unwrap(), "\
|
||||||
|
[1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map() {
|
||||||
|
let string = "{:a 1 :b 2 :c 3}";
|
||||||
|
let data = parse::value(string).unwrap().without_spans();
|
||||||
|
|
||||||
|
assert_eq!(data.to_pretty(20).unwrap(), "{:a 1 :b 2 :c 3}");
|
||||||
|
assert_eq!(data.to_pretty(10).unwrap(), "\
|
||||||
|
{:a 1
|
||||||
|
:b 2
|
||||||
|
:c 3}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pp_types() {
|
||||||
|
let string = "[ 1 2 ( 3.14 ) #{ 4N } { foo/bar 42 :baz/boz 43 } [ ] :five :six/seven eight nine/ten true false nil #f NaN #f -Infinity #f +Infinity ]";
|
||||||
|
let data = parse::value(string).unwrap().without_spans();
|
||||||
|
|
||||||
|
assert_eq!(data.to_pretty(40).unwrap(), "\
|
||||||
|
[1
|
||||||
|
2
|
||||||
|
(3.14)
|
||||||
|
#{4N}
|
||||||
|
{:baz/boz 43 foo/bar 42}
|
||||||
|
[]
|
||||||
|
:five
|
||||||
|
:six/seven
|
||||||
|
eight
|
||||||
|
nine/ten
|
||||||
|
true
|
||||||
|
false
|
||||||
|
nil
|
||||||
|
#f NaN
|
||||||
|
#f -Infinity
|
||||||
|
#f +Infinity]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pp_query1() {
|
||||||
|
let string = "[:find ?id ?bar ?baz :in $ :where [?id :session/keyword-foo ?symbol1 ?symbol2 \"some string\"] [?tx :db/tx ?ts]]";
|
||||||
|
let data = parse::value(string).unwrap().without_spans();
|
||||||
|
|
||||||
|
assert_eq!(data.to_pretty(40).unwrap(), "\
|
||||||
|
[:find
|
||||||
|
?id
|
||||||
|
?bar
|
||||||
|
?baz
|
||||||
|
:in
|
||||||
|
$
|
||||||
|
:where
|
||||||
|
[?id
|
||||||
|
:session/keyword-foo
|
||||||
|
?symbol1
|
||||||
|
?symbol2
|
||||||
|
\"some string\"]
|
||||||
|
[?tx :db/tx ?ts]]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_pp_query2() {
|
||||||
|
let string = "[:find [?id ?bar ?baz] :in [$] :where [?id :session/keyword-foo ?symbol1 ?symbol2 \"some string\"] [?tx :db/tx ?ts] (not-join [?id] [?id :session/keyword-bar _])]";
|
||||||
|
let data = parse::value(string).unwrap().without_spans();
|
||||||
|
|
||||||
|
assert_eq!(data.to_pretty(40).unwrap(), "\
|
||||||
|
[:find
|
||||||
|
[?id ?bar ?baz]
|
||||||
|
:in
|
||||||
|
[$]
|
||||||
|
:where
|
||||||
|
[?id
|
||||||
|
:session/keyword-foo
|
||||||
|
?symbol1
|
||||||
|
?symbol2
|
||||||
|
\"some string\"]
|
||||||
|
[?tx :db/tx ?ts]
|
||||||
|
(not-join
|
||||||
|
[?id]
|
||||||
|
[?id :session/keyword-bar _])]");
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,8 @@
|
||||||
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
// specific language governing permissions and limitations under the License.
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
#![cfg_attr(feature = "cargo-clippy", allow(linkedlist))]
|
||||||
|
|
||||||
use std::collections::{BTreeSet, BTreeMap, LinkedList};
|
use std::collections::{BTreeSet, BTreeMap, LinkedList};
|
||||||
use std::cmp::{Ordering, Ord, PartialOrd};
|
use std::cmp::{Ordering, Ord, PartialOrd};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
Loading…
Reference in a new issue