Skip to main content

edn/
pretty_print.rs

1// Copyright 2016 Mozilla
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4// this file except in compliance with the License. You may obtain a copy of the
5// License at http://www.apache.org/licenses/LICENSE-2.0
6// Unless required by applicable law or agreed to in writing, software distributed
7// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8// CONDITIONS OF ANY KIND, either express or implied. See the License for the
9// specific language governing permissions and limitations under the License.
10
11use chrono::SecondsFormat;
12
13use itertools::Itertools;
14use pretty;
15
16use std::borrow::Cow;
17use std::io;
18
19use crate::types::Value;
20
21impl Value {
22    /// Return a pretty string representation of this `Value`.
23    pub fn to_pretty(&self, width: usize) -> Result<String, io::Error> {
24        let mut out = Vec::new();
25        self.write_pretty(width, &mut out)?;
26        Ok(String::from_utf8_lossy(&out).into_owned())
27    }
28
29    /// Write a pretty representation of this `Value` to the given writer.
30    fn write_pretty<W>(&self, width: usize, out: &mut W) -> Result<(), io::Error>
31    where
32        W: io::Write,
33    {
34        self.as_doc(&pretty::BoxAllocator).1.render(width, out)
35    }
36
37    /// Bracket a collection of values.
38    ///
39    /// We aim for
40    /// [1 2 3]
41    /// and fall back if necessary to
42    /// [1,
43    ///  2,
44    ///  3].
45    #[allow(unstable_name_collisions)]
46    fn bracket<'a, A, T, I>(
47        &'a self,
48        allocator: &'a A,
49        open: T,
50        vs: I,
51        close: T,
52    ) -> pretty::DocBuilder<'a, A>
53    where
54        A: pretty::DocAllocator<'a>,
55        T: Into<Cow<'a, str>>,
56        I: IntoIterator<Item = &'a Value>,
57    {
58        let open = open.into();
59        let n = open.len();
60        let i = vs
61            .into_iter()
62            .map(|v| v.as_doc(allocator))
63            .intersperse_with(|| allocator.line());
64        allocator
65            .text(open)
66            .append(allocator.concat(i).nest(n as isize))
67            .append(allocator.text(close))
68            .group()
69    }
70
71    /// Recursively traverses this value and creates a pretty.rs document.
72    /// This pretty printing implementation is optimized for edn queries
73    /// readability and limited whitespace expansion.
74    #[allow(unstable_name_collisions)]
75    fn as_doc<'a, A>(&'a self, pp: &'a A) -> pretty::DocBuilder<'a, A>
76    where
77        A: pretty::DocAllocator<'a>,
78    {
79        match *self {
80            Value::Vector(ref vs) => self.bracket(pp, "[", vs, "]"),
81            Value::List(ref vs) => self.bracket(pp, "(", vs, ")"),
82            Value::Set(ref vs) => self.bracket(pp, "#{", vs, "}"),
83            Value::Map(ref vs) => {
84                let xs = vs
85                    .iter()
86                    .rev()
87                    .map(|(k, v)| k.as_doc(pp).append(pp.space()).append(v.as_doc(pp)).group())
88                    .intersperse_with(|| pp.line());
89                pp.text("{")
90                    .append(pp.concat(xs).nest(1))
91                    .append(pp.text("}"))
92                    .group()
93            }
94            Value::NamespacedSymbol(ref v) => pp.text(v.namespace()).append("/").append(v.name()),
95            Value::PlainSymbol(ref v) => pp.text(v.to_string()),
96            Value::Keyword(ref v) => pp.text(v.to_string()),
97            Value::Text(ref v) => pp.text("\"").append(v.as_str()).append("\""),
98            Value::Uuid(ref u) => pp
99                .text("#uuid \"")
100                .append(u.hyphenated().to_string())
101                .append("\""),
102            Value::Instant(ref v) => pp
103                .text("#inst \"")
104                .append(v.to_rfc3339_opts(SecondsFormat::AutoSi, true))
105                .append("\""),
106            _ => pp.text(self.to_string()),
107        }
108    }
109}
110
111#[cfg(test)]
112mod test {
113    use crate::parse;
114
115    #[test]
116    fn test_pp_io() {
117        let string = "$";
118        let data = parse::value(string).unwrap().without_spans();
119
120        assert!(data.write_pretty(40, &mut Vec::new()).is_ok());
121    }
122
123    #[test]
124    fn test_pp_types_empty() {
125        let string = "[ [ ] ( ) #{ } { }, \"\" ]";
126        let data = parse::value(string).unwrap().without_spans();
127
128        assert_eq!(data.to_pretty(40).unwrap(), "[[] () #{} {} \"\"]");
129    }
130
131    #[test]
132    fn test_vector() {
133        let string = "[1 2 3 4 5 6]";
134        let data = parse::value(string).unwrap().without_spans();
135
136        assert_eq!(data.to_pretty(20).unwrap(), "[1 2 3 4 5 6]");
137        assert_eq!(
138            data.to_pretty(10).unwrap(),
139            "\
140[1
141 2
142 3
143 4
144 5
145 6]"
146        );
147    }
148
149    #[test]
150    fn test_map() {
151        let string = "{:a 1 :b 2 :c 3}";
152        let data = parse::value(string).unwrap().without_spans();
153
154        assert_eq!(data.to_pretty(20).unwrap(), "{:a 1 :b 2 :c 3}");
155        assert_eq!(
156            data.to_pretty(10).unwrap(),
157            "\
158{:a 1
159 :b 2
160 :c 3}"
161        );
162    }
163
164    #[test]
165    fn test_pp_types() {
166        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 ]";
167        let data = parse::value(string).unwrap().without_spans();
168
169        assert_eq!(
170            data.to_pretty(40).unwrap(),
171            "\
172[1
173 2
174 (3.14)
175 #{4N}
176 {:baz/boz 43 foo/bar 42}
177 []
178 :five
179 :six/seven
180 eight
181 nine/ten
182 true
183 false
184 nil
185 #f NaN
186 #f -Infinity
187 #f +Infinity]"
188        );
189    }
190
191    #[test]
192    fn test_pp_query1() {
193        let string = "[:find ?id ?bar ?baz :in $ :where [?id :session/keyword-foo ?symbol1 ?symbol2 \"some string\"] [?tx :db/tx ?ts]]";
194        let data = parse::value(string).unwrap().without_spans();
195
196        assert_eq!(
197            data.to_pretty(40).unwrap(),
198            "\
199[:find
200 ?id
201 ?bar
202 ?baz
203 :in
204 $
205 :where
206 [?id
207  :session/keyword-foo
208  ?symbol1
209  ?symbol2
210  \"some string\"]
211 [?tx :db/tx ?ts]]"
212        );
213    }
214
215    #[test]
216    fn test_pp_query2() {
217        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 _])]";
218        let data = parse::value(string).unwrap().without_spans();
219
220        assert_eq!(
221            data.to_pretty(40).unwrap(),
222            "\
223[:find
224 [?id ?bar ?baz]
225 :in
226 [$]
227 :where
228 [?id
229  :session/keyword-foo
230  ?symbol1
231  ?symbol2
232  \"some string\"]
233 [?tx :db/tx ?ts]
234 (not-join
235  [?id]
236  [?id :session/keyword-bar _])]"
237        );
238    }
239}