# $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 omitted, (({$>})) is assumed. If ((|width|)) is omitted, 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 --- seplist(list[, separator_proc[, iter_method]]) {|elt| ... } adds a separated list. The list is separated by comma with breakable space, by default. seplist iterates the ((|list|)) using ((|iter_method|)). It yields each object to the block given for seplist. The procedure ((|separator_proc|)) is called between each yields. If the iteration is zero times, ((|separator_proc|)) is not called at all. If ((|separator_proc|)) is nil or not given, (({lambda { comma_breakable }})) is used. If ((|iter_method|)) is not given, (({:each})) is used. For example, following 3 code fragments has similar effect. q.seplist([1,2,3]) {|v| xxx v } q.seplist([1,2,3], lambda { comma_breakable }, :each) {|v| xxx v } xxx 1 q.comma_breakable xxx 2 q.comma_breakable xxx 3 = 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 == AUTHOR Tanaka Akira =end require 'prettyprint' module Kernel private def pp(*objs) objs.each {|obj| PP.pp(obj) } nil end module_function :pp end class PP < PrettyPrint def PP.pp(obj, out=$>, width=79) q = PP.new(out, width) q.guard_inspect_key {q.pp obj} q.flush #$pp = q out << "\n" end def PP.singleline_pp(obj, out=$>) q = SingleLine.new(out) q.guard_inspect_key {q.pp obj} q.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, "\#<#{obj.class}:#{("0x%x" % (obj.__id__ * 2)).sub(/\.\.f/, '')}", '>', &block) end def comma_breakable text ',' breakable end def seplist(list, sep=nil, iter_method=:each) sep ||= lambda { comma_breakable } first = true list.__send__(iter_method) {|*v| if first first = false else sep.call end yield(*v) } end def pp_object(obj) object_address_group(obj) { seplist(obj.pretty_print_instance_variables, lambda { text ',' }) {|v| breakable v = v.to_s if Symbol === v text v text '=' group(1) { breakable '' pp(obj.instance_eval(v)) } } } end def pp_hash(obj) group(1, '{', '}') { seplist(obj, nil, :each_pair) {|k, v| 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 # 3. specific to_s if instance variable is empty # 4. generic pretty_print def pretty_print(q) if /\(Kernel\)#/ !~ method(:inspect).inspect q.text self.inspect elsif /\(Kernel\)#/ !~ method(:to_s).inspect && instance_variables.empty? q.text self.to_s else q.pp_object(self) end end def pretty_print_cycle(q) q.object_address_group(self) { q.breakable q.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 overridden." end PP.singleline_pp(self, '') end end end class Array def pretty_print(q) q.group(1, '[', ']') { q.seplist(self) {|v| q.pp v } } end def pretty_print_cycle(q) q.text(empty? ? '[]' : '[...]') end end class Hash def pretty_print(q) q.pp_hash self end def pretty_print_cycle(q) q.text(empty? ? '{}' : '{...}') end end class << ENV def pretty_print(q) q.pp_hash self end end class Struct def pretty_print(q) q.group(1, '#') { q.seplist(self.members, lambda { q.text "," }) {|member| q.breakable q.text member.to_s q.text '=' q.group(1) { q.breakable '' q.pp self[member] } } } end def pretty_print_cycle(q) q.text sprintf("#", self.class.name) end end class Range def pretty_print(q) q.pp self.begin q.breakable '' q.text(self.exclude_end? ? '...' : '..') q.breakable '' q.pp self.end end end class File class Stat def pretty_print(q) require 'etc.so' q.object_group(self) { q.breakable q.text sprintf("dev=0x%x", self.dev); q.comma_breakable q.text "ino="; q.pp self.ino; q.comma_breakable q.group { m = self.mode q.text sprintf("mode=0%o", m) q.breakable q.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))) } q.comma_breakable q.text "nlink="; q.pp self.nlink; q.comma_breakable q.group { q.text "uid="; q.pp self.uid begin name = Etc.getpwuid(self.uid).name q.breakable; q.text "(#{name})" rescue ArgumentError end } q.comma_breakable q.group { q.text "gid="; q.pp self.gid begin name = Etc.getgrgid(self.gid).name q.breakable; q.text "(#{name})" rescue ArgumentError end } q.comma_breakable q.group { q.text sprintf("rdev=0x%x", self.rdev) q.breakable q.text sprintf('(%d, %d)', self.rdev_major, self.rdev_minor) } q.comma_breakable q.text "size="; q.pp self.size; q.comma_breakable q.text "blksize="; q.pp self.blksize; q.comma_breakable q.text "blocks="; q.pp self.blocks; q.comma_breakable q.group { t = self.atime q.text "atime="; q.pp t q.breakable; q.text "(#{t.tv_sec})" } q.comma_breakable q.group { t = self.mtime q.text "mtime="; q.pp t q.breakable; q.text "(#{t.tv_sec})" } q.comma_breakable q.group { t = self.ctime q.text "ctime="; q.pp t q.breakable; q.text "(#{t.tv_sec})" } } end end end class MatchData def pretty_print(q) q.object_group(self) { q.breakable q.seplist(1..self.size, lambda { q.breakable }) {|i| q.pp self[i-1] } } end end class Object include PP::ObjectMixin end [Numeric, Symbol, FalseClass, TrueClass, NilClass, Module].each {|c| c.class_eval { def pretty_print_cycle(q) q.text inspect end } } [Numeric, FalseClass, TrueClass, Module].each {|c| c.class_eval { def pretty_print(q) q.text inspect end } } if __FILE__ == $0 require 'test/unit' class PPTest < Test::Unit::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(q) q.text "" end end class HasBoth def initialize(a) @a = a end def inspect return "" end def pretty_print(q) q.text "" end end class PrettyPrintInspect < HasPrettyPrint alias inspect pretty_print_inspect end class PrettyPrintInspectWithoutPrettyPrint alias inspect pretty_print_inspect end class PPInspectTest < Test::Unit::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 def test_pretty_print_inspect a = PrettyPrintInspect.new(1) assert_equal("", a.inspect) a = PrettyPrintInspectWithoutPrettyPrint.new assert_raise(RuntimeError) { a.inspect } end def test_proc a = proc {1} assert_equal("#{a.inspect}\n", PP.pp(a, '')) end def test_to_s_with_iv a = Object.new def a.to_s() "aaa" end a.instance_eval { @a = nil } result = PP.pp(a, '') assert_equal("#{a.inspect}\n", result) assert_match(/\A#\n\z/m, result) a = 1.0 a.instance_eval { @a = nil } result = PP.pp(a, '') assert_equal("#{a.inspect}\n", result) end def test_to_s_without_iv a = Object.new def a.to_s() "aaa" end result = PP.pp(a, '') assert_equal("#{a.inspect}\n", result) assert_equal("aaa\n", result) end end class PPCycleTest < Test::Unit::TestCase def test_array a = [] a << a assert_equal("[[...]]\n", PP.pp(a, '')) assert_equal("#{a.inspect}\n", PP.pp(a, '')) end def test_hash a = {} a[0] = a assert_equal("{0=>{...}}\n", PP.pp(a, '')) assert_equal("#{a.inspect}\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, '')) assert_equal("#{a.inspect}\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_anonymous a = Class.new.new assert_equal(a.inspect + "\n", PP.pp(a, '')) end def test_withinspect a = [] a << HasInspect.new(a) assert_equal("[]\n", PP.pp(a, '')) assert_equal("#{a.inspect}\n", PP.pp(a, '')) end def test_share_nil begin PP.sharing_detection = true a = [nil, nil] assert_equal("[nil, nil]\n", PP.pp(a, '')) ensure PP.sharing_detection = false end end end class PPSingleLineTest < Test::Unit::TestCase def test_hash assert_equal("{1=>1}", PP.singleline_pp({ 1 => 1}, '')) # [ruby-core:02699] assert_equal("[1#{', 1'*99}]", PP.singleline_pp([1]*100, '')) end end end