blob: 0021a289a438c1e4c80ac6fa536115e7dc1701d0 [file] [log] [blame]
Rich Lanea06d0c32013-03-25 08:52:03 -07001:: # Copyright 2013, Big Switch Networks, Inc.
2:: #
3:: # LoxiGen is licensed under the Eclipse Public License, version 1.0 (EPL), with
4:: # the following special exception:
5:: #
6:: # LOXI Exception
7:: #
8:: # As a special exception to the terms of the EPL, you may distribute libraries
9:: # generated by LoxiGen (LoxiGen Libraries) under the terms of your choice, provided
10:: # that copyright and licensing notices generated by LoxiGen are not altered or removed
11:: # from the LoxiGen Libraries and the notice provided below is (i) included in
12:: # the LoxiGen Libraries, if distributed in source code form and (ii) included in any
13:: # documentation for the LoxiGen Libraries, if distributed in binary form.
14:: #
15:: # Notice: "Copyright 2013, Big Switch Networks, Inc. This library was generated by the LoxiGen Compiler."
16:: #
17:: # You may not use this file except in compliance with the EPL or LOXI Exception. You may obtain
18:: # a copy of the EPL at:
19:: #
20:: # http://www.eclipse.org/legal/epl-v10.html
21:: #
22:: # Unless required by applicable law or agreed to in writing, software
23:: # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
24:: # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
25:: # EPL for the specific language governing permissions and limitations
26:: # under the EPL.
27::
28# Copyright 2013, Big Switch Networks, Inc.
29
30"""
31pp - port of Ruby's PP library
32Also based on Lindig, C., & GbR, G. D. (2000). Strictly Pretty.
33
34Example usage:
35>>> import pp.pp as pp
36>>> print pp([[1, 2], [3, 4]], maxwidth=15)
37[
38 [ 1, 2 ],
39 [ 3, 4 ]
40]
41"""
42import unittest
43from contextlib import contextmanager
44
45def pp(obj, maxwidth=79):
46 """
47 Pretty-print the given object.
48 """
49 ctx = PrettyPrinter(maxwidth=maxwidth)
50 ctx.pp(obj)
51 return str(ctx)
52
53
54## Pretty-printers for builtin classes
55
56def pretty_print_list(pp, obj):
57 with pp.group():
58 pp.text('[')
59 with pp.indent(2):
60 for v in obj:
61 if not pp.first(): pp.text(',')
62 pp.breakable()
63 pp.pp(v)
64 pp.breakable()
65 pp.text(']')
66
67def pretty_print_dict(pp, obj):
68 with pp.group():
69 pp.text('{')
70 with pp.indent(2):
71 for (k, v) in sorted(obj.items()):
72 if not pp.first(): pp.text(',')
73 pp.breakable()
74 pp.pp(k)
75 pp.text(': ')
76 pp.pp(v)
77 pp.breakable()
78 pp.text('}')
79
80pretty_printers = {
81 list: pretty_print_list,
82 dict: pretty_print_dict,
83}
84
85
86## Implementation
87
88class PrettyPrinter(object):
89 def __init__(self, maxwidth):
90 self.maxwidth = maxwidth
91 self.cur_indent = 0
92 self.root_group = Group()
93 self.group_stack = [self.root_group]
94
95 def current_group(self):
96 return self.group_stack[-1]
97
98 def text(self, s):
99 self.current_group().append(str(s))
100
101 def breakable(self, sep=' '):
102 self.current_group().append(Breakable(sep, self.cur_indent))
103
104 def first(self):
105 return self.current_group().first()
106
107 @contextmanager
108 def indent(self, n):
109 self.cur_indent += n
110 yield
111 self.cur_indent -= n
112
113 @contextmanager
114 def group(self):
115 self.group_stack.append(Group())
116 yield
117 new_group = self.group_stack.pop()
118 self.current_group().append(new_group)
119
120 def pp(self, obj):
121 if hasattr(obj, "pretty_print"):
122 obj.pretty_print(self)
123 elif type(obj) in pretty_printers:
124 pretty_printers[type(obj)](self, obj)
125 else:
126 self.text(repr(obj))
127
128 def __str__(self):
129 return self.root_group.render(0, self.maxwidth)
130
131class Group(object):
132 __slots__ = ["fragments", "length", "_first"]
133
134 def __init__(self):
135 self.fragments = []
136 self.length = 0
137 self._first = True
138
139 def append(self, x):
140 self.fragments.append(x)
141 self.length += len(x)
142
143 def first(self):
144 if self._first:
145 self._first = False
146 return True
147 return False
148
149 def __len__(self):
150 return self.length
151
152 def render(self, curwidth, maxwidth):
153 dobreak = len(self) > (maxwidth - curwidth)
154
155 a = []
156 for x in self.fragments:
157 if isinstance(x, Breakable):
158 if dobreak:
159 a.append('\n')
160 a.append(' ' * x.indent)
161 curwidth = 0
162 else:
163 a.append(x.sep)
164 elif isinstance(x, Group):
165 a.append(x.render(curwidth, maxwidth))
166 else:
167 a.append(x)
168 curwidth += len(a[-1])
169 return ''.join(a)
170
171class Breakable(object):
172 __slots__ = ["sep", "indent"]
173
174 def __init__(self, sep, indent):
175 self.sep = sep
176 self.indent = indent
177
178 def __len__(self):
179 return len(self.sep)
180
181
182## Tests
183
184class TestPP(unittest.TestCase):
185 def test_scalars(self):
186 self.assertEquals(pp(1), "1")
187 self.assertEquals(pp("foo"), "'foo'")
188
189 def test_hash(self):
190 expected = """{ 1: 'a', 'b': 2 }"""
191 self.assertEquals(pp(eval(expected)), expected)
192 expected = """\
193{
194 1: 'a',
195 'b': 2
196}"""
197 self.assertEquals(pp(eval(expected), maxwidth=0), expected)
198
199 def test_array(self):
200 expected = """[ 1, 'a', 2 ]"""
201 self.assertEquals(pp(eval(expected)), expected)
202 expected = """\
203[
204 1,
205 'a',
206 2
207]"""
208 self.assertEquals(pp(eval(expected), maxwidth=0), expected)
209
210 def test_nested(self):
211 expected = """[ [ 1, 2 ], [ 3, 4 ] ]"""
212 self.assertEquals(pp(eval(expected)), expected)
213 expected = """\
214[
215 [
216 1,
217 2
218 ],
219 [
220 3,
221 4
222 ]
223]"""
224 self.assertEquals(pp(eval(expected), maxwidth=0), expected)
225
226 def test_breaking(self):
227 expected = """\
228[
229 [ 1, 2 ],
230 'abcdefghijklmnopqrstuvwxyz'
231]"""
232 self.assertEquals(pp(eval(expected), maxwidth=24), expected)
233 expected = """\
234[
235 [ 'abcd', 2 ],
236 [ '0123456789' ],
237 [
238 '0123456789',
239 'abcdefghij'
240 ],
241 [ 'abcdefghijklmnop' ],
242 [
243 'abcdefghijklmnopq'
244 ],
245 { 'k': 'v' },
246 {
247 1: [ 2, [ 3, 4 ] ],
248 'foo': 'abcdefghijklmnop'
249 }
250]"""
251 self.assertEquals(pp(eval(expected), maxwidth=24), expected)
252 expected = """\
253[
254 [ 1, 2 ],
255 [ 3, 4 ]
256]"""
257 self.assertEquals(pp(eval(expected), maxwidth=15), expected)
258
259 # This is an edge case where our simpler algorithm breaks down.
260 @unittest.expectedFailure
261 def test_greedy_breaking(self):
262 expected = """\
263abc def
264ghijklmnopqrstuvwxyz\
265"""
266 pp = PrettyPrinter(maxwidth=8)
267 pp.text("abc")
268 with pp.group():
269 pp.breakable()
270 pp.text("def")
271 with pp.group():
272 pp.breakable()
273 pp.text("ghijklmnopqrstuvwxyz")
274 self.assertEquals(str(pp), expected)
275
276if __name__ == '__main__':
277 unittest.main()