# $Id$ =begin = Pretty-printer for Ruby objects. == Which seems better? non-pretty-printed output by (({p})) is: #, @group_queue=#], []]>, @buffer=[], @newline="\n", @group_stack=[#], @buffer_width=0, @indent=0, @maxwidth=79, @output_width=2, @output=#> pretty-printed output by (({pp})) is: #, @group_queue= #], []]>, @group_stack= [#], @indent=0, @maxwidth=79, @newline="\n", @output=#, @output_width=2> I like the latter. If you do too, this library is for you. == Usage : pp(obj) output ((|obj|)) to (({$>})) in pretty printed format. It returns (({nil})). == Output Customization To define your customized pretty printing function for your classes, redefine a method (({pretty_print(((|pp|)))})) in the class. It takes an argument ((|pp|)) which is an instance of the class (()). The method should use PP#text, PP#breakable, PP#nest, PP#group and PP#pp to print the object. = PP == super class (()) == class methods --- PP.pp(obj[, out[, width]]) outputs ((|obj|)) to ((|out|)) in pretty printed format of ((|width|)) columns in width. If ((|out|)) is ommitted, (({$>})) is assumed. If ((|width|)) is ommitted, 79 is assumed. PP.pp returns ((|out|)). --- PP.singleline_pp(obj[, out]) outputs ((|obj|)) to ((|out|)) like (({PP.pp})) but with no indent and newline. PP.singleline_pp returns ((|out|)). --- PP.sharing_detection returns the sharing detection flag as a boolean value. It is false by default. --- PP.sharing_detection = boolean_value sets the sharing detection flag. == methods --- pp(obj) adds ((|obj|)) to the pretty printing buffer using Object#pretty_print or Object#pretty_print_cycle. Object#pretty_print_cycle is used when ((|obj|)) is already printed, a.k.a the object reference chain has a cycle. --- object_group(obj) { ... } is a convenience method which is same as follows: group(1, '#<' + obj.class.name, '>') { ... } --- comma_breakable is a convenience method which is same as follows: text ',' breakable = Object --- pretty_print(pp) is a default pretty printing method for general objects. It calls (({pretty_print_instance_variables})) to list instance variables. If (({self})) has a customized (redefined) (({inspect})) method, the result of (({self.inspect})) is used but it obviously has no line break hints. This module provides predefined pretty_print() methods for some of the most commonly used built-in classes for convenience. --- pretty_print_cycle(pp) is a default pretty printing method for general objects that are detected as part of a cycle. --- pretty_print_instance_variables returns a sorted array of instance variable names. This method should return an array of names of instance variables as symbols or strings as: (({[:@a, :@b]})). --- pretty_print_inspect is (({inspect})) implementation using (({pretty_print})). If you implement (({pretty_print})), it can be used as follows. alias inspect pretty_print_inspect =end require 'prettyprint' module Kernel private def pp(*objs) objs.each {|obj| PP.pp(obj) } nil end end class PP < PrettyPrint def PP.pp(obj, out=$>, width=79) pp = PP.new(out, width) pp.guard_inspect_key {pp.pp obj} pp.flush #$pp = pp out << "\n" end def PP.singleline_pp(obj, out=$>) pp = SingleLine.new(out) pp.guard_inspect_key {pp.pp obj} pp.flush out end @sharing_detection = false class << self attr_accessor :sharing_detection end module PPMethods InspectKey = :__inspect_key__ def guard_inspect_key if Thread.current[InspectKey] == nil Thread.current[InspectKey] = [] end save = Thread.current[InspectKey] begin Thread.current[InspectKey] = [] yield ensure Thread.current[InspectKey] = save end end def pp(obj) id = obj.__id__ if Thread.current[InspectKey].include? id group {obj.pretty_print_cycle self} return end begin Thread.current[InspectKey] << id group {obj.pretty_print self} ensure Thread.current[InspectKey].pop unless PP.sharing_detection end end def object_group(obj, &block) group(1, '#<' + obj.class.name, '>', &block) end def object_address_group(obj, &block) group(1, sprintf('#<%s:0x%x', obj.class.name, obj.__id__ * 2), '>', &block) end def comma_breakable text ',' breakable end def pp_object(obj) object_address_group(obj) { obj.pretty_print_instance_variables.each {|v| v = v.to_s if Symbol === v text ',' unless first? breakable text v text '=' group(1) { breakable '' pp(obj.instance_eval(v)) } } } end def pp_hash(obj) group(1, '{', '}') { obj.each {|k, v| comma_breakable unless first? group { pp k text '=>' group(1) { breakable '' pp v } } } } end end include PPMethods class SingleLine < PrettyPrint::SingleLine include PPMethods end module ObjectMixin # 1. specific pretty_print # 2. specific inspect or to_s # 3. generic pretty_print def pretty_print(pp) if /\(Kernel\)#/ !~ method(:inspect).inspect || /\(Kernel\)#/ !~ method(:to_s).inspect pp.text inspect else pp.pp_object(self) end end def pretty_print_cycle(pp) pp.object_address_group(self) { pp.breakable pp.text '...' } end def pretty_print_instance_variables instance_variables.sort end def pretty_print_inspect if /\(PP::ObjectMixin\)#/ =~ method(:pretty_print).inspect raise "pretty_print is not overriden." end PP.singleline_pp(self, '') end end end class Array def pretty_print(pp) pp.group(1, '[', ']') { self.each {|v| pp.comma_breakable unless pp.first? pp.pp v } } end def pretty_print_cycle(pp) pp.text(empty? ? '[]' : '[...]') end end class Hash def pretty_print(pp) pp.pp_hash self end def pretty_print_cycle(pp) pp.text(empty? ? '{}' : '{...}') end end class << ENV def pretty_print(pp) pp.pp_hash self end end class Struct def pretty_print(pp) pp.object_group(self) { self.members.each {|member| pp.text "," unless pp.first? pp.breakable pp.text member.to_s pp.text '=' pp.group(1) { pp.breakable '' pp.pp self[member] } } } end def pretty_print_cycle(pp) pp.text sprintf("#<%s:...>", self.class.name) end end class Range def pretty_print(pp) pp.pp self.begin pp.breakable '' pp.text(self.exclude_end? ? '...' : '..') pp.breakable '' pp.pp self.end end end class File class Stat def pretty_print(pp) require 'etc.so' pp.object_group(self) { pp.breakable pp.text sprintf("dev=0x%x", self.dev); pp.comma_breakable pp.text "ino="; pp.pp self.ino; pp.comma_breakable pp.group { m = self.mode pp.text sprintf("mode=0%o", m) pp.breakable pp.text sprintf("(%s %c%c%c%c%c%c%c%c%c)", self.ftype, (m & 0400 == 0 ? ?- : ?r), (m & 0200 == 0 ? ?- : ?w), (m & 0100 == 0 ? (m & 04000 == 0 ? ?- : ?S) : (m & 04000 == 0 ? ?x : ?s)), (m & 0040 == 0 ? ?- : ?r), (m & 0020 == 0 ? ?- : ?w), (m & 0010 == 0 ? (m & 02000 == 0 ? ?- : ?S) : (m & 02000 == 0 ? ?x : ?s)), (m & 0004 == 0 ? ?- : ?r), (m & 0002 == 0 ? ?- : ?w), (m & 0001 == 0 ? (m & 01000 == 0 ? ?- : ?T) : (m & 01000 == 0 ? ?x : ?t))) } pp.comma_breakable pp.text "nlink="; pp.pp self.nlink; pp.comma_breakable pp.group { pp.text "uid="; pp.pp self.uid begin name = Etc.getpwuid(self.uid).name pp.breakable; pp.text "(#{name})" rescue ArgumentError end } pp.comma_breakable pp.group { pp.text "gid="; pp.pp self.gid begin name = Etc.getgrgid(self.gid).name pp.breakable; pp.text "(#{name})" rescue ArgumentError end } pp.comma_breakable pp.group { pp.text sprintf("rdev=0x%x", self.rdev) pp.breakable pp.text sprintf('(%d, %d)', self.rdev_major, self.rdev_minor) } pp.comma_breakable pp.text "size="; pp.pp self.size; pp.comma_breakable pp.text "blksize="; pp.pp self.blksize; pp.comma_breakable pp.text "blocks="; pp.pp self.blocks; pp.comma_breakable pp.group { t = self.atime pp.text "atime="; pp.pp t pp.breakable; pp.text "(#{t.tv_sec})" } pp.comma_breakable pp.group { t = self.mtime pp.text "mtime="; pp.pp t pp.breakable; pp.text "(#{t.tv_sec})" } pp.comma_breakable pp.group { t = self.ctime pp.text "ctime="; pp.pp t pp.breakable; pp.text "(#{t.tv_sec})" } } end end end class Object include PP::ObjectMixin end if __FILE__ == $0 require 'runit/testcase' require 'runit/cui/testrunner' class PPTest < RUNIT::TestCase def test_list0123_12 assert_equal("[0, 1, 2, 3]\n", PP.pp([0,1,2,3], '', 12)) end def test_list0123_11 assert_equal("[0,\n 1,\n 2,\n 3]\n", PP.pp([0,1,2,3], '', 11)) end end class HasInspect def initialize(a) @a = a end def inspect return "" end end class HasPrettyPrint def initialize(a) @a = a end def pretty_print(pp) pp.text "" end end class HasBoth def initialize(a) @a = a end def inspect return "" end def pretty_print(pp) pp.text "" end end class PPInspectTest < RUNIT::TestCase def test_hasinspect a = HasInspect.new(1) assert_equal("\n", PP.pp(a, '')) end def test_hasprettyprint a = HasPrettyPrint.new(1) assert_equal("\n", PP.pp(a, '')) end def test_hasboth a = HasBoth.new(1) assert_equal("\n", PP.pp(a, '')) end end class PPCycleTest < RUNIT::TestCase def test_array a = [] a << a assert_equal("[[...]]\n", PP.pp(a, '')) end def test_hash a = {} a[0] = a assert_equal("{0=>{...}}\n", PP.pp(a, '')) end S = Struct.new("S", :a, :b) def test_struct a = S.new(1,2) a.b = a assert_equal("#>\n", PP.pp(a, '')) end def test_object a = Object.new a.instance_eval {@a = a} assert_equal(a.inspect + "\n", PP.pp(a, '')) end def test_withinspect a = [] a << HasInspect.new(a) assert_equal("[]\n", PP.pp(a, '')) end end RUNIT::CUI::TestRunner.run(PPTest.suite) RUNIT::CUI::TestRunner.run(PPInspectTest.suite) RUNIT::CUI::TestRunner.run(PPCycleTest.suite) end