DEFINITIONS
This source file includes following functions.
1 # $Id$
2
3 =begin
4 = Pretty-printer for Ruby objects.
5
6 == Which seems better?
7
8 non-pretty-printed output by (({p})) is:
9 #<PP:0x81a0d10 @stack=[], @genspace=#<Proc:0x81a0cc0>, @nest=[0], @newline="\n", @buf=#<PrettyPrint::Group:0x81a0c98 @group=0, @tail=0, @buf=[#<PrettyPrint::Group:0x81a0ba8 @group=1, @tail=0, @buf=[#<PrettyPrint::Text:0x81a0b30 @tail=2, @width=1, @text="[">, #<PrettyPrint::Group:0x81a0a68 @group=2, @tail=1, @buf=[#<PrettyPrint::Text:0x81a09f0 @tail=1, @width=1, @text="1">], @singleline_width=1>, #<PrettyPrint::Text:0x81a0a7c @tail=0, @width=1, @text=",">, #<PrettyPrint::Breakable:0x81a0a2c @group=2, @genspace=#<Proc:0x81a0cc0>, @newline="\n", @indent=1, @tail=2, @sep=" ", @width=1>, #<PrettyPrint::Group:0x81a09c8 @group=2, @tail=1, @buf=[#<PrettyPrint::Text:0x81a0950 @tail=1, @width=1, @text="2">], @singleline_width=1>, #<PrettyPrint::Text:0x81a0af4 @tail=0, @width=1, @text="]">], @singleline_width=6>], @singleline_width=6>, @sharing_detection=false>
10
11 pretty-printed output by (({pp})) is:
12 #<PP:0x40d0688
13 @buf=
14 #<PrettyPrint::Group:0x40d064c
15 @buf=
16 [#<PrettyPrint::Group:0x40d05d4
17 @buf=
18 [#<PrettyPrint::Text:0x40d0598 @tail=2, @text="[", @width=1>,
19 #<PrettyPrint::Group:0x40d0534
20 @buf=[#<PrettyPrint::Text:0x40d04f8 @tail=1, @text="1", @width=1>],
21 @group=2,
22 @singleline_width=1,
23 @tail=1>,
24 #<PrettyPrint::Text:0x40d053e @tail=0, @text=",", @width=1>,
25 #<PrettyPrint::Breakable:0x40d0516
26 @genspace=#<Proc:0x40d0656>,
27 @group=2,
28 @indent=1,
29 @newline="\n",
30 @sep=" ",
31 @tail=2,
32 @width=1>,
33 #<PrettyPrint::Group:0x40d04e4
34 @buf=[#<PrettyPrint::Text:0x40d04a8 @tail=1, @text="2", @width=1>],
35 @group=2,
36 @singleline_width=1,
37 @tail=1>,
38 #<PrettyPrint::Text:0x40d057a @tail=0, @text="]", @width=1>],
39 @group=1,
40 @singleline_width=6,
41 @tail=0>],
42 @group=0,
43 @singleline_width=6,
44 @tail=0>,
45 @genspace=#<Proc:0x40d0656>,
46 @nest=[0],
47 @newline="\n",
48 @sharing_detection=false,
49 @stack=[]>
50
51 I like the latter. If you do too, this library is for you.
52
53 == Usage
54
55 : pp(obj)
56 output ((|obj|)) to (({$>})) in pretty printed format.
57
58 It returns (({nil})).
59
60 == Output Customization
61 To define your customized pretty printing function for your classes,
62 redefine a method (({pretty_print(((|pp|)))})) in the class.
63 It takes an argument ((|pp|)) which is an instance of the class ((<PP>)).
64 The method should use PP#text, PP#breakable, PP#nest, PP#group and
65 PP#pp to print the object.
66
67 = PP
68 == super class
69 ((<PrettyPrint>))
70
71 == class methods
72 --- PP.pp(obj[, out[, width]])
73 outputs ((|obj|)) to ((|out|)) in pretty printed format of
74 ((|width|)) columns in width.
75
76 If ((|out|)) is ommitted, (({$>})) is assumed.
77 If ((|width|)) is ommitted, 79 is assumed.
78
79 PP.pp returns ((|out|)).
80
81 --- PP.sharing_detection
82 returns the sharing detection flag as boolean value.
83 It is false by default.
84
85 --- PP.sharing_detection = boolean_value
86 sets the sharing detection flag.
87
88 == methods
89 --- pp(obj)
90 adds ((|obj|)) to the pretty printing buffer
91 using Object#pretty_print or Object#pretty_print_cycle.
92
93 Object#pretty_print_cycle is used when ((|obj|)) is already
94 printed, a.k.a the object reference chain has a cycle.
95
96 --- object_group(obj) { ... }
97 is a convenience method which is same as follows:
98
99 group(1, '#<' + obj.class.name, '>') { ... }
100
101 --- comma_breakable
102 is a convenience method which is same as follows:
103
104 text ','
105 breakable
106
107 = Object
108 --- pretty_print(pp)
109 is a default pretty printing method for general objects.
110 It calls (({pretty_print_instance_variables})) to list instance variables.
111
112 If (({self})) has a customized (redefined) (({inspect})) method,
113 the result of (({self.inspect})) is used but it obviously has no
114 line break hints.
115
116 This module provides predefined pretty_print() methods for some of
117 the most commonly used built-in classes for convenience.
118
119 --- pretty_print_cycle(pp)
120 is a default pretty printing method for general objects that are
121 detected as part of a cycle.
122
123 --- pretty_print_instance_variables
124 returns a sorted array of instance variable names.
125
126 This method should return an array of names of instance variables as symbols or strings as:
127 (({[:@a, :@b]})).
128 =end
129
130 require 'prettyprint'
131
132 module Kernel
133 private
134 def pp(*objs)
135 objs.each {|obj|
136 PP.pp(obj)
137 }
138 nil
139 end
140 end
141
142 class PP < PrettyPrint
143 def PP.pp(obj, out=$>, width=79)
144 pp = PP.new(out, width)
145 pp.guard_inspect_key {pp.pp obj}
146 pp.flush
147 #$pp = pp
148 out << "\n"
149 end
150
151 @@sharing_detection = false
152 def PP.sharing_detection
153 return @@sharing_detection
154 end
155
156 def PP.sharing_detection=(val)
157 @@sharing_detection = val
158 end
159
160 def initialize(out, width=79)
161 super
162 @sharing_detection = @@sharing_detection
163 end
164
165 InspectKey = :__inspect_key__
166
167 def guard_inspect_key
168 if Thread.current[InspectKey] == nil
169 Thread.current[InspectKey] = []
170 end
171
172 save = Thread.current[InspectKey]
173
174 begin
175 Thread.current[InspectKey] = []
176 yield
177 ensure
178 Thread.current[InspectKey] = save
179 end
180 end
181
182 def pp(obj)
183 id = obj.__id__
184
185 if Thread.current[InspectKey].include? id
186 group {obj.pretty_print_cycle self}
187 return
188 end
189
190 begin
191 Thread.current[InspectKey] << id
192 group {obj.pretty_print self}
193 ensure
194 Thread.current[InspectKey].pop unless @sharing_detection
195 end
196 end
197
198 def object_group(obj, &block)
199 group(1, '#<' + obj.class.name, '>', &block)
200 end
201
202 def object_address_group(obj, &block)
203 group(1, sprintf('#<%s:0x%x', obj.class.name, obj.__id__ * 2), '>', &block)
204 end
205
206 def comma_breakable
207 text ','
208 breakable
209 end
210
211 def pp_object(obj)
212 object_address_group(obj) {
213 obj.pretty_print_instance_variables.each {|v|
214 v = v.to_s if Symbol === v
215 text ',' unless first?
216 breakable
217 text v
218 text '='
219 group(1) {
220 breakable ''
221 pp(obj.instance_eval(v))
222 }
223 }
224 }
225 end
226
227 def pp_hash(obj)
228 group(1, '{', '}') {
229 obj.each {|k, v|
230 comma_breakable unless first?
231 group {
232 pp k
233 text '=>'
234 group(1) {
235 breakable ''
236 pp v
237 }
238 }
239 }
240 }
241 end
242
243 module ObjectMixin
244 # 1. specific pretty_print
245 # 2. specific inspect
246 # 3. generic pretty_print
247
248 Key = :__pp_instead_of_inspect__
249
250 def pretty_print(pp)
251 # specific pretty_print is not defined, try specific inspect.
252 begin
253 old = Thread.current[Key]
254 result1 = sprintf('#<%s:0x%x pretty_printed>', self.class.name, self.__id__ * 2)
255 Thread.current[Key] = [pp, result1]
256 result2 = ObjectMixin.pp_call_inspect(self)
257 ensure
258 Thread.current[Key] = old
259 end
260
261 unless result1.equal? result2
262 pp.text result2
263 end
264 end
265
266 def ObjectMixin.pp_call_inspect(obj); obj.inspect; end
267 CallInspectFrame = "#{__FILE__}:#{__LINE__ - 1}:in `pp_call_inspect'"
268
269 def inspect
270 if CallInspectFrame == caller[0]
271 # specific inspect is also not defined, use generic pretty_print.
272 pp, result = Thread.current[Key]
273 pp.pp_object(self)
274 result
275 else
276 super
277 end
278 end
279
280 def pretty_print_cycle(pp)
281 pp.object_address_group(self) {
282 pp.breakable
283 pp.text '...'
284 }
285 end
286
287 def pretty_print_instance_variables
288 instance_variables.sort
289 end
290 end
291 end
292
293 [Numeric, FalseClass, TrueClass, Module].each {|c|
294 c.class_eval {
295 def pretty_print(pp)
296 pp.text self.to_s
297 end
298 }
299 }
300
301 class Array
302 def pretty_print(pp)
303 pp.group(1, '[', ']') {
304 self.each {|v|
305 pp.comma_breakable unless pp.first?
306 pp.pp v
307 }
308 }
309 end
310
311 def pretty_print_cycle(pp)
312 pp.text(empty? ? '[]' : '[...]')
313 end
314 end
315
316 class Hash
317 def pretty_print(pp)
318 pp.pp_hash self
319 end
320
321 def pretty_print_cycle(pp)
322 pp.text(empty? ? '{}' : '{...}')
323 end
324 end
325
326 class << ENV
327 def pretty_print(pp)
328 pp.pp_hash self
329 end
330 end
331
332 class Struct
333 def pretty_print(pp)
334 pp.object_group(self) {
335 self.members.each {|member|
336 pp.text "," unless pp.first?
337 pp.breakable
338 pp.text member.to_s
339 pp.text '='
340 pp.group(1) {
341 pp.breakable ''
342 pp.pp self[member]
343 }
344 }
345 }
346 end
347
348 def pretty_print_cycle(pp)
349 pp.text sprintf("#<%s:...>", self.class.name)
350 end
351 end
352
353 class Range
354 def pretty_print(pp)
355 pp.pp self.begin
356 pp.breakable ''
357 pp.text(self.exclude_end? ? '...' : '..')
358 pp.breakable ''
359 pp.pp self.end
360 end
361 end
362
363 class File
364 class Stat
365 def pretty_print(pp)
366 require 'etc.so'
367 pp.object_group(self) {
368 pp.breakable
369 pp.text sprintf("dev=0x%x", self.dev); pp.comma_breakable
370 pp.text "ino="; pp.pp self.ino; pp.comma_breakable
371 pp.group {
372 m = self.mode
373 pp.text sprintf("mode=0%o", m)
374 pp.breakable
375 pp.text sprintf("(%s %c%c%c%c%c%c%c%c%c)",
376 self.ftype,
377 (m & 0400 == 0 ? ?- : ?r),
378 (m & 0200 == 0 ? ?- : ?w),
379 (m & 0100 == 0 ? (m & 04000 == 0 ? ?- : ?S) :
380 (m & 04000 == 0 ? ?x : ?s)),
381 (m & 0040 == 0 ? ?- : ?r),
382 (m & 0020 == 0 ? ?- : ?w),
383 (m & 0010 == 0 ? (m & 02000 == 0 ? ?- : ?S) :
384 (m & 02000 == 0 ? ?x : ?s)),
385 (m & 0004 == 0 ? ?- : ?r),
386 (m & 0002 == 0 ? ?- : ?w),
387 (m & 0001 == 0 ? (m & 01000 == 0 ? ?- : ?T) :
388 (m & 01000 == 0 ? ?x : ?t)))
389 }
390 pp.comma_breakable
391 pp.text "nlink="; pp.pp self.nlink; pp.comma_breakable
392 pp.group {
393 pp.text "uid="; pp.pp self.uid
394 begin
395 name = Etc.getpwuid(self.uid).name
396 pp.breakable; pp.text "(#{name})"
397 rescue ArgumentError
398 end
399 }
400 pp.comma_breakable
401 pp.group {
402 pp.text "gid="; pp.pp self.gid
403 begin
404 name = Etc.getgrgid(self.gid).name
405 pp.breakable; pp.text "(#{name})"
406 rescue ArgumentError
407 end
408 }
409 pp.comma_breakable
410 pp.group {
411 pp.text sprintf("rdev=0x%x", self.rdev)
412 pp.breakable
413 pp.text sprintf('(%d, %d)', self.rdev_major, self.rdev_minor)
414 }
415 pp.comma_breakable
416 pp.text "size="; pp.pp self.size; pp.comma_breakable
417 pp.text "blksize="; pp.pp self.blksize; pp.comma_breakable
418 pp.text "blocks="; pp.pp self.blocks; pp.comma_breakable
419 pp.group {
420 t = self.atime
421 pp.text "atime="; pp.pp t
422 pp.breakable; pp.text "(#{t.tv_sec})"
423 }
424 pp.comma_breakable
425 pp.group {
426 t = self.mtime
427 pp.text "mtime="; pp.pp t
428 pp.breakable; pp.text "(#{t.tv_sec})"
429 }
430 pp.comma_breakable
431 pp.group {
432 t = self.ctime
433 pp.text "ctime="; pp.pp t
434 pp.breakable; pp.text "(#{t.tv_sec})"
435 }
436 }
437 end
438 end
439 end
440
441 class << ARGF
442 def pretty_print(pp)
443 pp.text self.to_s
444 end
445 end
446
447 class Object
448 include PP::ObjectMixin
449 end
450
451 [Numeric, Symbol, FalseClass, TrueClass, NilClass, Module].each {|c|
452 c.class_eval {
453 alias :pretty_print_cycle :pretty_print
454 }
455 }
456
457 if __FILE__ == $0
458 require 'runit/testcase'
459 require 'runit/cui/testrunner'
460
461 class PPTest < RUNIT::TestCase
462 def test_list0123_12
463 assert_equal("[0, 1, 2, 3]\n", PP.pp([0,1,2,3], '', 12))
464 end
465
466 def test_list0123_11
467 assert_equal("[0,\n 1,\n 2,\n 3]\n", PP.pp([0,1,2,3], '', 11))
468 end
469 end
470
471 class HasInspect
472 def initialize(a)
473 @a = a
474 end
475
476 def inspect
477 return "<inspect:#{@a.inspect}>"
478 end
479 end
480
481 class HasPrettyPrint
482 def initialize(a)
483 @a = a
484 end
485
486 def pretty_print(pp)
487 pp.text "<pretty_print:"
488 pp.pp @a
489 pp.text ">"
490 end
491 end
492
493 class HasBoth
494 def initialize(a)
495 @a = a
496 end
497
498 def inspect
499 return "<inspect:#{@a.inspect}>"
500 end
501
502 def pretty_print(pp)
503 pp.text "<pretty_print:"
504 pp.pp @a
505 pp.text ">"
506 end
507 end
508
509 class PPInspectTest < RUNIT::TestCase
510 def test_hasinspect
511 a = HasInspect.new(1)
512 assert_equal("<inspect:1>\n", PP.pp(a, ''))
513 end
514
515 def test_hasprettyprint
516 a = HasPrettyPrint.new(1)
517 assert_equal("<pretty_print:1>\n", PP.pp(a, ''))
518 end
519
520 def test_hasboth
521 a = HasBoth.new(1)
522 assert_equal("<pretty_print:1>\n", PP.pp(a, ''))
523 end
524 end
525
526 class PPCycleTest < RUNIT::TestCase
527 def test_array
528 a = []
529 a << a
530 assert_equal("[[...]]\n", PP.pp(a, ''))
531 end
532
533 def test_hash
534 a = {}
535 a[0] = a
536 assert_equal("{0=>{...}}\n", PP.pp(a, ''))
537 end
538
539 S = Struct.new("S", :a, :b)
540 def test_struct
541 a = S.new(1,2)
542 a.b = a
543 assert_equal("#<Struct::S a=1, b=#<Struct::S:...>>\n", PP.pp(a, ''))
544 end
545
546 def test_object
547 a = Object.new
548 a.instance_eval {@a = a}
549 assert_equal(a.inspect + "\n", PP.pp(a, ''))
550 end
551
552 def test_withinspect
553 a = []
554 a << HasInspect.new(a)
555 assert_equal("[<inspect:[...]>]\n", PP.pp(a, ''))
556 end
557 end
558
559 RUNIT::CUI::TestRunner.run(PPTest.suite)
560 RUNIT::CUI::TestRunner.run(PPInspectTest.suite)
561 RUNIT::CUI::TestRunner.run(PPCycleTest.suite)
562 end