summaryrefslogtreecommitdiff
path: root/lib/pp.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pp.rb')
-rw-r--r--lib/pp.rb458
1 files changed, 340 insertions, 118 deletions
diff --git a/lib/pp.rb b/lib/pp.rb
index 6e0c797d2e..5fd29a373a 100644
--- a/lib/pp.rb
+++ b/lib/pp.rb
@@ -1,32 +1,17 @@
-require 'prettyprint'
-
-module Kernel
- # returns a pretty printed object as a string.
- def pretty_inspect
- PP.pp(self, '')
- end
+# frozen_string_literal: true
- private
- # prints arguments in pretty form.
- #
- # pp returns argument(s).
- def pp(*objs) # :doc:
- objs.each {|obj|
- PP.pp(obj)
- }
- objs.size <= 1 ? objs.first : objs
- end
- module_function :pp
-end
+require 'prettyprint'
-# == Pretty-printer for Ruby objects.
+##
+# A pretty-printer for Ruby objects.
#
-# = Which seems better?
+##
+# == What PP Does
#
-# non-pretty-printed output by #p is:
+# Standard output by #p returns this:
# #<PP:0x81fedf0 @genspace=#<Proc:0x81feda0>, @group_queue=#<PrettyPrint::GroupQueue:0x81fed3c @queue=[[#<PrettyPrint::Group:0x81fed78 @breakables=[], @depth=0, @break=false>], []]>, @buffer=[], @newline="\n", @group_stack=[#<PrettyPrint::Group:0x81fed78 @breakables=[], @depth=0, @break=false>], @buffer_width=0, @indent=0, @maxwidth=79, @output_width=2, @output=#<IO:0x8114ee4>>
#
-# pretty-printed output by #pp is:
+# Pretty-printed output returns this:
# #<PP:0x81fedf0
# @buffer=[],
# @buffer_width=0,
@@ -44,37 +29,72 @@ end
# @output=#<IO:0x8114ee4>,
# @output_width=2>
#
-# I like the latter. If you do too, this library is for you.
-#
-# = Usage
+##
+# == Usage
#
# pp(obj) #=> obj
+# pp obj #=> obj
# pp(obj1, obj2, ...) #=> [obj1, obj2, ...]
# pp() #=> nil
#
-# output +obj(s)+ to +$>+ in pretty printed format.
+# Output <tt>obj(s)</tt> to <tt>$></tt> in pretty printed format.
+#
+# It returns <tt>obj(s)</tt>.
#
-# It returns +obj(s)+.
+##
+# == Output Customization
#
-# = 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 PP.
-# The method should use PP#text, PP#breakable, PP#nest, PP#group and
-# PP#pp to print the object.
+# To define a customized pretty printing function for your classes,
+# redefine method <code>#pretty_print(pp)</code> in the class.
+# Note that <code>require 'pp'</code> is needed before redefining <code>#pretty_print(pp)</code>.
#
-# = Author
-# Tanaka Akira <akr@m17n.org>
+# <code>#pretty_print</code> takes the +pp+ argument, which is an instance of the PP class.
+# The method uses #text, #breakable, #nest, #group and #pp to print the
+# object.
+#
+##
+# == Pretty-Print JSON
+#
+# To pretty-print JSON refer to JSON#pretty_generate.
+#
+##
+# == Author
+# Tanaka Akira <akr@fsij.org>
+
class PP < PrettyPrint
+
+ # The version string
+ VERSION = "0.6.3"
+
+ # Returns the usable width for +out+.
+ # As the width of +out+:
+ # 1. If +out+ is assigned to a tty device, its width is used.
+ # 2. Otherwise, or it could not get the value, the +COLUMN+
+ # environment variable is assumed to be set to the width.
+ # 3. If +COLUMN+ is not set to a non-zero number, 80 is assumed.
+ #
+ # And finally, returns the above width value - 1.
+ # * This -1 is for Windows command prompt, which moves the cursor to
+ # the next line if it reaches the last column.
+ def PP.width_for(out)
+ begin
+ require 'io/console'
+ _, width = out.winsize
+ rescue LoadError, NoMethodError, SystemCallError
+ end
+ (width || ENV['COLUMNS']&.to_i&.nonzero? || 80) - 1
+ end
+
# 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.
+ # If +out+ is omitted, <code>$></code> is assumed.
+ # If +width+ is omitted, the width of +out+ is assumed (see
+ # width_for).
#
# PP.pp returns +out+.
- def PP.pp(obj, out=$>, width=79)
- q = PP.new(out, width)
+ def PP.pp(obj, out=$>, width=width_for(out))
+ q = new(out, width)
q.guard_inspect_key {q.pp obj}
q.flush
#$pp = q
@@ -94,67 +114,105 @@ class PP < PrettyPrint
# :stopdoc:
def PP.mcall(obj, mod, meth, *args, &block)
- mod.instance_method(meth).bind(obj).call(*args, &block)
+ mod.instance_method(meth).bind_call(obj, *args, &block)
end
# :startdoc:
- @sharing_detection = false
- class << self
- # Returns the sharing detection flag as a boolean value.
- # It is false by default.
- attr_accessor :sharing_detection
- end
-
- module PPMethods
- def guard_inspect_key
- if Thread.current[:__recursive_key__] == nil
- Thread.current[:__recursive_key__] = {}.untrust
+ if defined? ::Ractor
+ class << self
+ # Returns the sharing detection flag as a boolean value.
+ # It is false (nil) by default.
+ def sharing_detection
+ Ractor.current[:pp_sharing_detection]
end
-
- if Thread.current[:__recursive_key__][:inspect] == nil
- Thread.current[:__recursive_key__][:inspect] = {}.untrust
+ # Sets the sharing detection flag to b.
+ def sharing_detection=(b)
+ Ractor.current[:pp_sharing_detection] = b
end
+ end
+ else
+ @sharing_detection = false
+ class << self
+ # Returns the sharing detection flag as a boolean value.
+ # It is false by default.
+ attr_accessor :sharing_detection
+ end
+ end
- save = Thread.current[:__recursive_key__][:inspect]
+ # Module that defines helper methods for pretty_print.
+ module PPMethods
+ # Yields to a block
+ # and preserves the previous set of objects being printed.
+ def guard_inspect_key
+ recursive_state = Thread.current[:__recursive_key__] ||= {}.compare_by_identity
+ save = recursive_state[:inspect] ||= {}.compare_by_identity
begin
- Thread.current[:__recursive_key__][:inspect] = {}.untrust
+ recursive_state[:inspect] = {}.compare_by_identity
yield
ensure
- Thread.current[:__recursive_key__][:inspect] = save
+ recursive_state[:inspect] = save
end
end
+ # Check whether the object_id +id+ is in the current buffer of objects
+ # to be pretty printed. Used to break cycles in chains of objects to be
+ # pretty printed.
def check_inspect_key(id)
- Thread.current[:__recursive_key__] &&
- Thread.current[:__recursive_key__][:inspect] &&
- Thread.current[:__recursive_key__][:inspect].include?(id)
+ recursive_state = Thread.current[:__recursive_key__] or return false
+ recursive_state[:inspect]&.include?(id)
end
+
+ # Adds the object_id +id+ to the set of objects being pretty printed, so
+ # as to not repeat objects.
def push_inspect_key(id)
Thread.current[:__recursive_key__][:inspect][id] = true
end
+
+ # Removes an object from the set of objects being pretty printed.
def pop_inspect_key(id)
Thread.current[:__recursive_key__][:inspect].delete id
end
+ private def guard_inspect(object) # :nodoc:
+ recursive_state = Thread.current[:__recursive_key__]
+
+ if recursive_state&.key?(:inspect)
+ begin
+ push_inspect_key(object)
+ yield
+ ensure
+ pop_inspect_key(object) unless PP.sharing_detection
+ end
+ else
+ guard_inspect_key do
+ push_inspect_key(object)
+ yield
+ end
+ end
+ end
+
# 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.
def pp(obj)
- id = obj.object_id
+ # If obj is a Delegator then use the object being delegated to for cycle
+ # detection
+ obj = obj.__getobj__ if defined?(::Delegator) and ::Delegator === obj
- if check_inspect_key(id)
+ if check_inspect_key(obj)
group {obj.pretty_print_cycle self}
return
end
- begin
- push_inspect_key(id)
- group {obj.pretty_print self}
- ensure
- pop_inspect_key(id) unless PP.sharing_detection
+ guard_inspect(obj) do
+ group do
+ obj.pretty_print self
+ rescue NoMethodError
+ text Kernel.instance_method(:inspect).bind_call(obj)
+ end
end
end
@@ -165,18 +223,12 @@ class PP < PrettyPrint
group(1, '#<' + obj.class.name, '>', &block)
end
- PointerMask = (1 << ([""].pack("p").size * 8)) - 1
-
- case Object.new.inspect
- when /\A\#<Object:0x([0-9a-f]+)>\z/
- PointerFormat = "%0#{$1.length}x"
- else
- PointerFormat = "%x"
- end
-
+ # A convenience method, like object_group, but also reformats the Object's
+ # object_id.
def object_address_group(obj, &block)
- id = PointerFormat % (obj.object_id * 2 & PointerMask)
- group(1, "\#<#{obj.class}:0x#{id}", '>', &block)
+ str = Kernel.instance_method(:to_s).bind_call(obj)
+ str.chomp!('>')
+ group(1, str, '>', &block)
end
# A convenience method which is same as follows:
@@ -215,16 +267,22 @@ class PP < PrettyPrint
def seplist(list, sep=nil, iter_method=:each) # :yield: element
sep ||= lambda { comma_breakable }
first = true
+ kwsplat = EMPTY_KWHASH
list.__send__(iter_method) {|*v|
if first
first = false
else
sep.call
end
- yield(*v)
+ kwsplat ? yield(*v, **kwsplat) : yield(*v)
}
end
+ EMPTY_KWHASH = if RUBY_VERSION >= "3.0" # :nodoc:
+ {}.freeze
+ end
+ private_constant :EMPTY_KWHASH
+ # A present standard failsafe for pretty printing any given Object
def pp_object(obj)
object_address_group(obj) {
seplist(obj.pretty_print_instance_variables, lambda { text ',' }) {|v|
@@ -240,29 +298,54 @@ class PP < PrettyPrint
}
end
+ # A pretty print for a Hash
def pp_hash(obj)
group(1, '{', '}') {
seplist(obj, nil, :each_pair) {|k, v|
group {
- pp k
- text '=>'
- group(1) {
- breakable ''
- pp v
- }
+ pp_hash_pair k, v
}
}
}
end
+
+ if RUBY_VERSION >= '3.4.'
+ # A pretty print for a pair of Hash
+ def pp_hash_pair(k, v)
+ if Symbol === k
+ if k.inspect.match?(%r[\A:["$@!]|[%&*+\-\/<=>@\]^`|~]\z])
+ k = k.to_s.inspect
+ end
+ text "#{k}:"
+ else
+ pp k
+ text ' '
+ text '=>'
+ end
+ group(1) {
+ breakable
+ pp v
+ }
+ end
+ else
+ def pp_hash_pair(k, v)
+ pp k
+ text '=>'
+ group(1) {
+ breakable ''
+ pp v
+ }
+ end
+ end
end
include PPMethods
- class SingleLine < PrettyPrint::SingleLine
+ class SingleLine < PrettyPrint::SingleLine # :nodoc:
include PPMethods
end
- module ObjectMixin
+ module ObjectMixin # :nodoc:
# 1. specific pretty_print
# 2. specific inspect
# 3. generic pretty_print
@@ -277,12 +360,12 @@ class PP < PrettyPrint
# This module provides predefined #pretty_print methods for some of
# the most commonly used built-in classes for convenience.
def pretty_print(q)
- method_method = Object.instance_method(:method).bind(self)
+ umethod_method = Object.instance_method(:method)
begin
- inspect_method = method_method.call(:inspect)
+ inspect_method = umethod_method.bind_call(self, :inspect)
rescue NameError
end
- if inspect_method && /\(Kernel\)#/ !~ inspect_method.inspect
+ if inspect_method && inspect_method.owner != Kernel
q.text self.inspect
elsif !inspect_method && self.respond_to?(:inspect)
q.text self.inspect
@@ -305,7 +388,8 @@ class PP < PrettyPrint
# This method should return an array of names of instance variables as symbols or strings as:
# +[:@a, :@b]+.
def pretty_print_instance_variables
- instance_variables.sort
+ ivars = respond_to?(:instance_variables_to_inspect, true) ? instance_variables_to_inspect || instance_variables : instance_variables
+ ivars.sort
end
# Is #inspect implementation using #pretty_print.
@@ -316,16 +400,16 @@ class PP < PrettyPrint
# However, doing this requires that every class that #inspect is called on
# implement #pretty_print, or a RuntimeError will be raised.
def pretty_print_inspect
- if /\(PP::ObjectMixin\)#/ =~ Object.instance_method(:method).bind(self).call(:pretty_print).inspect
+ if Object.instance_method(:method).bind_call(self, :pretty_print).owner == PP::ObjectMixin
raise "pretty_print is not overridden for #{self.class}"
end
- PP.singleline_pp(self, '')
+ PP.singleline_pp(self, ''.dup)
end
end
end
-class Array
- def pretty_print(q)
+class Array # :nodoc:
+ def pretty_print(q) # :nodoc:
q.group(1, '[', ']') {
q.seplist(self) {|v|
q.pp v
@@ -333,23 +417,45 @@ class Array
}
end
- def pretty_print_cycle(q)
+ def pretty_print_cycle(q) # :nodoc:
q.text(empty? ? '[]' : '[...]')
end
end
-class Hash
- def pretty_print(q)
+class Hash # :nodoc:
+ def pretty_print(q) # :nodoc:
q.pp_hash self
end
- def pretty_print_cycle(q)
+ def pretty_print_cycle(q) # :nodoc:
q.text(empty? ? '{}' : '{...}')
end
end
-class << ENV
- def pretty_print(q)
+if defined?(Set)
+ if set_pp = Set.instance_method(:initialize).source_location
+ set_pp = !set_pp.first.end_with?("/set.rb") # not defined in set.rb
+ else
+ set_pp = true # defined in C
+ end
+end
+class Set # :nodoc:
+ def pretty_print(pp) # :nodoc:
+ pp.group(1, "#{self.class.name}[", ']') {
+ pp.seplist(self) { |o|
+ pp.pp o
+ }
+ }
+ end
+
+ def pretty_print_cycle(pp) # :nodoc:
+ name = self.class.name
+ pp.text(empty? ? "#{name}[]" : "#{name}[...]")
+ end
+end if set_pp
+
+class << ENV # :nodoc:
+ def pretty_print(q) # :nodoc:
h = {}
ENV.keys.sort.each {|k|
h[k] = ENV[k]
@@ -358,8 +464,8 @@ class << ENV
end
end
-class Struct
- def pretty_print(q)
+class Struct # :nodoc:
+ def pretty_print(q) # :nodoc:
q.group(1, sprintf("#<struct %s", PP.mcall(self, Kernel, :class).name), '>') {
q.seplist(PP.mcall(self, Struct, :members), lambda { q.text "," }) {|member|
q.breakable
@@ -373,25 +479,83 @@ class Struct
}
end
- def pretty_print_cycle(q)
+ def pretty_print_cycle(q) # :nodoc:
q.text sprintf("#<struct %s:...>", PP.mcall(self, Kernel, :class).name)
end
end
-class Range
- def pretty_print(q)
- q.pp self.begin
+verbose, $VERBOSE = $VERBOSE, nil
+begin
+ has_data_define = defined?(Data.define)
+ensure
+ $VERBOSE = verbose
+end
+
+class Data # :nodoc:
+ def pretty_print(q) # :nodoc:
+ class_name = PP.mcall(self, Kernel, :class).name
+ class_name = " #{class_name}" if class_name
+ q.group(1, "#<data#{class_name}", '>') {
+
+ members = PP.mcall(self, Kernel, :class).members
+ values = []
+ members.select! do |member|
+ begin
+ values << __send__(member)
+ true
+ rescue NoMethodError
+ false
+ end
+ end
+
+ q.seplist(members.zip(values), lambda { q.text "," }) {|(member, value)|
+ q.breakable
+ q.text member.to_s
+ q.text '='
+ q.group(1) {
+ q.breakable ''
+ q.pp value
+ }
+ }
+ }
+ end
+
+ def pretty_print_cycle(q) # :nodoc:
+ q.text sprintf("#<data %s:...>", PP.mcall(self, Kernel, :class).name)
+ end
+end if has_data_define
+
+class Range # :nodoc:
+ def pretty_print(q) # :nodoc:
+ begin_nil = self.begin == nil
+ end_nil = self.end == nil
+ q.pp self.begin if !begin_nil || end_nil
q.breakable ''
q.text(self.exclude_end? ? '...' : '..')
q.breakable ''
- q.pp self.end
+ q.pp self.end if !end_nil || begin_nil
end
end
-class File < IO
- class Stat
- def pretty_print(q)
- require 'etc.so'
+class String # :nodoc:
+ def pretty_print(q) # :nodoc:
+ lines = self.lines
+ if lines.size > 1
+ q.group(0, '', '') do
+ q.seplist(lines, lambda { q.text ' +'; q.breakable }) do |v|
+ q.pp v
+ end
+ end
+ else
+ q.text inspect
+ end
+ end
+end
+
+class File < IO # :nodoc:
+ class Stat # :nodoc:
+ def pretty_print(q) # :nodoc:
+ require 'etc'
q.object_group(self) {
q.breakable
q.text sprintf("dev=0x%x", self.dev); q.comma_breakable
@@ -441,8 +605,10 @@ class File < IO
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)
+ if self.rdev_major && self.rdev_minor
+ q.breakable
+ q.text sprintf('(%d, %d)', self.rdev_major, self.rdev_minor)
+ end
}
q.comma_breakable
q.text "size="; q.pp self.size; q.comma_breakable
@@ -470,8 +636,8 @@ class File < IO
end
end
-class MatchData
- def pretty_print(q)
+class MatchData # :nodoc:
+ def pretty_print(q) # :nodoc:
nc = []
self.regexp.named_captures.each {|name, indexes|
indexes.each {|i| nc[i] = name }
@@ -495,7 +661,43 @@ class MatchData
end
end
-class Object < BasicObject
+if defined?(RubyVM::AbstractSyntaxTree)
+ class RubyVM::AbstractSyntaxTree::Node # :nodoc:
+ def pretty_print_children(q, names = [])
+ children.zip(names) do |c, n|
+ if n
+ q.breakable
+ q.text "#{n}:"
+ end
+ q.group(2) do
+ q.breakable
+ q.pp c
+ end
+ end
+ end
+
+ def pretty_print(q)
+ q.group(1, "(#{type}@#{first_lineno}:#{first_column}-#{last_lineno}:#{last_column}", ")") {
+ case type
+ when :SCOPE
+ pretty_print_children(q, %w"tbl args body")
+ when :ARGS
+ pretty_print_children(q, %w[pre_num pre_init opt first_post post_num post_init rest kw kwrest block])
+ when :DEFN
+ pretty_print_children(q, %w[mid body])
+ when :ARYPTN
+ pretty_print_children(q, %w[const pre rest post])
+ when :HSHPTN
+ pretty_print_children(q, %w[const kw kwrest])
+ else
+ pretty_print_children(q)
+ end
+ }
+ end
+ end
+end
+
+class Object < BasicObject # :nodoc:
include PP::ObjectMixin
end
@@ -514,3 +716,23 @@ end
end
}
}
+
+module Kernel
+ # Returns a pretty printed object as a string.
+ #
+ # See the PP module for more information.
+ def pretty_inspect
+ PP.pp(self, ''.dup)
+ end
+
+ # prints arguments in pretty form.
+ #
+ # +#pp+ returns argument(s).
+ def pp(*objs)
+ objs.each {|obj|
+ PP.pp(obj)
+ }
+ objs.size <= 1 ? objs.first : objs
+ end
+ module_function :pp
+end