lib/pp.rb


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