1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

* lib/prettyprint.rb, lib/pp.rb: convenience methods added.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@2602 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
akr 2002-06-27 12:01:07 +00:00
parent 48fbacb11c
commit 571ff1d6b9
3 changed files with 297 additions and 258 deletions

View file

@ -1,3 +1,7 @@
Thu Jun 27 20:57:45 2002 Tanaka Akira <akr@m17n.org>
* lib/prettyprint.rb, lib/pp.rb: convenience methods added.
Thu Jun 27 15:22:18 2002 Tanaka Akira <akr@m17n.org> Thu Jun 27 15:22:18 2002 Tanaka Akira <akr@m17n.org>
* lib/prettyprint.rb: re-implemented for incremental output to handle * lib/prettyprint.rb: re-implemented for incremental output to handle

136
lib/pp.rb
View file

@ -88,9 +88,21 @@ PP#pp to print the object.
Object#pretty_print_cycled is used when ((|obj|)) is already Object#pretty_print_cycled is used when ((|obj|)) is already
printed, a.k.a the object reference chain has a cycle. 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 = Object
--- pretty_print(pp) --- pretty_print(pp)
is a default pretty printing method for general objects. 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, If (({self})) has a customized (redefined) (({inspect})) method,
the result of (({self.inspect})) is used but it obviously has no the result of (({self.inspect})) is used but it obviously has no
@ -102,6 +114,13 @@ PP#pp to print the object.
--- pretty_print_cycled(pp) --- pretty_print_cycled(pp)
is a default pretty printing method for general objects that are is a default pretty printing method for general objects that are
detected as part of a cycle. detected as part of a cycle.
--- pretty_print_instance_variables
is a method to list instance variables used by the default implementation
of (({pretty_print})).
This method should return an array of names of instance variables as symbols or strings as:
(({[:@a, :@b]})).
=end =end
require 'prettyprint' require 'prettyprint'
@ -172,61 +191,46 @@ class PP < PrettyPrint
end end
end end
def pp_object(obj) def object_group(obj, &block)
group { group(1, '#<' + obj.class.name, '>', &block)
text '#<'
nest(1) {
text obj.class.name
text ':'
text sprintf('0x%x', obj.__id__)
first = true
obj.instance_variables.sort.each {|v|
if first
first = false
else
text ','
end end
def comma_breakable
text ','
breakable
end
def pp_object(obj)
object_group(obj) {
text sprintf(':0x%x', obj.__id__)
obj.pretty_print_instance_variables.each {|v|
v = v.to_s if Symbol === v
text ',' unless first?
breakable breakable
group {
text v text v
text '=' text '='
nest(1) { group(1) {
breakable '' breakable ''
pp(obj.instance_eval v) pp(obj.instance_eval v)
} }
} }
} }
}
text '>'
}
end end
def pp_hash(obj) def pp_hash(obj)
group { group(1, '{', '}') {
text '{'
nest(1) {
first = true
obj.each {|k, v| obj.each {|k, v|
if first comma_breakable unless first?
first = false
else
text ","
breakable
end
group { group {
pp k pp k
text '=>' text '=>'
nest(1) { group(1) {
group {
breakable '' breakable ''
pp v pp v
} }
} }
} }
} }
}
text '}'
}
end end
module ObjectMixin module ObjectMixin
@ -271,6 +275,10 @@ class PP < PrettyPrint
pp.breakable pp.breakable
pp.text sprintf("...>") pp.text sprintf("...>")
end end
def pretty_print_instance_variables
instance_variables.sort
end
end end
end end
@ -284,20 +292,12 @@ end
class Array class Array
def pretty_print(pp) def pretty_print(pp)
pp.text "[" pp.group(1, '[', ']') {
pp.nest(1) {
first = true
self.each {|v| self.each {|v|
if first pp.comma_breakable unless pp.first?
first = false
else
pp.text ","
pp.breakable
end
pp.pp v pp.pp v
} }
} }
pp.text "]"
end end
def pretty_print_cycled(pp) def pretty_print_cycled(pp)
@ -323,27 +323,18 @@ end
class Struct class Struct
def pretty_print(pp) def pretty_print(pp)
pp.text sprintf("#<%s", self.class.name) pp.object_group(self) {
pp.nest(1) {
first = true
self.members.each {|member| self.members.each {|member|
if first pp.text "," unless pp.first?
first = false
else
pp.text ","
end
pp.breakable pp.breakable
pp.group {
pp.text member.to_s pp.text member.to_s
pp.text '=' pp.text '='
pp.nest(1) { pp.group(1) {
pp.breakable '' pp.breakable ''
pp.pp self[member] pp.pp self[member]
} }
} }
} }
}
pp.text ">"
end end
def pretty_print_cycled(pp) def pretty_print_cycled(pp)
@ -365,12 +356,10 @@ class File
class Stat class Stat
def pretty_print(pp) def pretty_print(pp)
require 'etc.so' require 'etc.so'
pp.nest(1) { pp.object_group(self) {
pp.text "#<"
pp.text self.class.name
pp.breakable; pp.text sprintf("dev=0x%x", self.dev); pp.text ','
pp.breakable; pp.text "ino="; pp.pp self.ino; pp.text ','
pp.breakable 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 { pp.group {
m = self.mode m = self.mode
pp.text sprintf("mode=0%o", m) pp.text sprintf("mode=0%o", m)
@ -389,10 +378,9 @@ class File
(m & 0002 == 0 ? ?- : ?w), (m & 0002 == 0 ? ?- : ?w),
(m & 0001 == 0 ? (m & 01000 == 0 ? ?- : ?T) : (m & 0001 == 0 ? (m & 01000 == 0 ? ?- : ?T) :
(m & 01000 == 0 ? ?x : ?t))) (m & 01000 == 0 ? ?x : ?t)))
pp.text ','
} }
pp.breakable; pp.text "nlink="; pp.pp self.nlink; pp.text ',' pp.comma_breakable
pp.breakable pp.text "nlink="; pp.pp self.nlink; pp.comma_breakable
pp.group { pp.group {
pp.text "uid="; pp.pp self.uid pp.text "uid="; pp.pp self.uid
begin begin
@ -400,9 +388,8 @@ class File
pp.breakable; pp.text "(#{name})" pp.breakable; pp.text "(#{name})"
rescue ArgumentError rescue ArgumentError
end end
pp.text ','
} }
pp.breakable pp.comma_breakable
pp.group { pp.group {
pp.text "gid="; pp.pp self.gid pp.text "gid="; pp.pp self.gid
begin begin
@ -410,39 +397,34 @@ class File
pp.breakable; pp.text "(#{name})" pp.breakable; pp.text "(#{name})"
rescue ArgumentError rescue ArgumentError
end end
pp.text ','
} }
pp.breakable pp.comma_breakable
pp.group { pp.group {
pp.text sprintf("rdev=0x%x", self.rdev) pp.text sprintf("rdev=0x%x", self.rdev)
pp.breakable pp.breakable
pp.text sprintf('(%d, %d)', self.rdev_major, self.rdev_minor) pp.text sprintf('(%d, %d)', self.rdev_major, self.rdev_minor)
pp.text ','
} }
pp.breakable; pp.text "size="; pp.pp self.size; pp.text ',' pp.comma_breakable
pp.breakable; pp.text "blksize="; pp.pp self.blksize; pp.text ',' pp.text "size="; pp.pp self.size; pp.comma_breakable
pp.breakable; pp.text "blocks="; pp.pp self.blocks; pp.text ',' pp.text "blksize="; pp.pp self.blksize; pp.comma_breakable
pp.breakable pp.text "blocks="; pp.pp self.blocks; pp.comma_breakable
pp.group { pp.group {
t = self.atime t = self.atime
pp.text "atime="; pp.pp t pp.text "atime="; pp.pp t
pp.breakable; pp.text "(#{t.tv_sec})" pp.breakable; pp.text "(#{t.tv_sec})"
pp.text ','
} }
pp.breakable pp.comma_breakable
pp.group { pp.group {
t = self.mtime t = self.mtime
pp.text "mtime="; pp.pp t pp.text "mtime="; pp.pp t
pp.breakable; pp.text "(#{t.tv_sec})" pp.breakable; pp.text "(#{t.tv_sec})"
pp.text ','
} }
pp.breakable pp.comma_breakable
pp.group { pp.group {
t = self.ctime t = self.ctime
pp.text "ctime="; pp.pp t pp.text "ctime="; pp.pp t
pp.breakable; pp.text "(#{t.tv_sec})" pp.breakable; pp.text "(#{t.tv_sec})"
} }
pp.text ">"
} }
end end
end end

View file

@ -18,11 +18,12 @@ multibyte characters which has columns diffrent to number of bytes,
non-string formatting, etc. non-string formatting, etc.
== class methods == class methods
--- PrettyPrint.new(output[, maxwidth[, newline]]) [{|width| ...}] --- PrettyPrint.new([output[, maxwidth[, newline]]]) [{|width| ...}]
creates a buffer for pretty printing. creates a buffer for pretty printing.
((|output|)) is a output target. It should have a (({<<})) method ((|output|)) is a output target.
which accepts If it is not specified, (({''})) is assumed.
It should have a (({<<})) method which accepts
the first argument ((|obj|)) of (({PrettyPrint#text})), the first argument ((|obj|)) of (({PrettyPrint#text})),
the first argument ((|sep|)) of (({PrettyPrint#breakable})), the first argument ((|sep|)) of (({PrettyPrint#breakable})),
the first argument ((|newline|)) of (({PrettyPrint.new})), the first argument ((|newline|)) of (({PrettyPrint.new})),
@ -40,6 +41,16 @@ non-string formatting, etc.
The block is used to generate spaces. The block is used to generate spaces.
(({{|width| ' ' * width}})) is used if it is not given. (({{|width| ' ' * width}})) is used if it is not given.
--- PrettyPrint.format([output[, maxwidth[, newline[, genspace]]]]) {|pp| ...}
is a convenience method which is same as follows:
begin
pp = PrettyPrint.format(output, maxwidth, newline, &genspace)
...
pp.flush
output
end
== methods == methods
--- text(obj[, width]) --- text(obj[, width])
adds ((|obj|)) as a text of ((|width|)) columns in width. adds ((|obj|)) as a text of ((|width|)) columns in width.
@ -61,16 +72,37 @@ non-string formatting, etc.
increases left margin after newline with ((|indent|)) for line breaks added increases left margin after newline with ((|indent|)) for line breaks added
in the block. in the block.
--- group {...} --- group([indent[, open_obj[, close_obj[, open_width[, close_width]]]]]) {...}
groups line break hints added in the block. groups line break hints added in the block.
The line break hints are all to be breaked or not. The line break hints are all to be breaked or not.
If ((|indent|)) is specified, the method call is regarded as nested by
(({nest(((|indent|))) { ... }})).
If ((|open_obj|)) is specified, (({text open_obj, open_width})) is called
at first.
If ((|close_obj|)) is specified, (({text close_obj, close_width})) is
called at last.
--- flush --- flush
outputs buffered data. outputs buffered data.
== Bugs --- first?
* Current API is for minimalists. More useful methods are required. is a predicate to test the call is a first call to (({first?})) with
current group.
It is useful to format comma separated values as:
pp.group(1, '[', ']') {
xxx.each {|yyy|
unless pp.first?
pp.text ','
pp.breakable
end
... pretty printing yyy ...
}
}
== Bugs
* Box based formatting? Other (better) model/algorithm? * Box based formatting? Other (better) model/algorithm?
== References == References
@ -83,7 +115,14 @@ Philip Wadler, A prettier printer, March 1998,
=end =end
class PrettyPrint class PrettyPrint
def initialize(output, maxwidth=79, newline="\n", &genspace) def PrettyPrint.format(output='', maxwidth=79, newline="\n", genspace=lambda {|n| ' ' * n})
pp = PrettyPrint.new(output, maxwidth, newline, &genspace)
yield pp
pp.flush
output
end
def initialize(output='', maxwidth=79, newline="\n", &genspace)
@output = output @output = output
@maxwidth = maxwidth @maxwidth = maxwidth
@newline = newline @newline = newline
@ -105,6 +144,10 @@ class PrettyPrint
@group_stack.last @group_stack.last
end end
def first?
current_group.first?
end
def break_outmost_groups def break_outmost_groups
while @maxwidth < @output_width + @buffer_width while @maxwidth < @output_width + @buffer_width
return unless group = @group_queue.deq return unless group = @group_queue.deq
@ -137,6 +180,10 @@ class PrettyPrint
end end
end end
def fill_breakable(sep=' ', width=sep.length)
group { breakable sep, width }
end
def breakable(sep=' ', width=sep.length) def breakable(sep=' ', width=sep.length)
group = @group_stack.last group = @group_stack.last
if group.break? if group.break?
@ -152,7 +199,17 @@ class PrettyPrint
end end
end end
def group def group(indent=0, open_obj='', close_obj='', open_width=open_obj.length, close_width=close_obj.length)
text open_obj, open_width
group_sub {
nest(indent) {
yield
}
}
text close_obj, close_width
end
def group_sub
group = Group.new(@group_stack.last.depth + 1) group = Group.new(@group_stack.last.depth + 1)
@group_stack.push group @group_stack.push group
@group_queue.enq group @group_queue.enq group
@ -241,6 +298,15 @@ class PrettyPrint
def break? def break?
@break @break
end end
def first?
if defined? @first
false
else
@first = false
true
end
end
end end
class GroupQueue class GroupQueue
@ -291,19 +357,21 @@ if __FILE__ == $0
end end
def hello(width) def hello(width)
out = '' PrettyPrint.format('', width) {|hello|
hello = PrettyPrint.new(out, width)
hello.group { hello.group {
hello.group { hello.group {
hello.group { hello.group {
hello.group { hello.group {
hello.text 'hello' hello.text 'hello'
hello.breakable; hello.text 'a'} hello.breakable; hello.text 'a'
hello.breakable; hello.text 'b'} }
hello.breakable; hello.text 'c'} hello.breakable; hello.text 'b'
hello.breakable; hello.text 'd'} }
hello.flush hello.breakable; hello.text 'c'
out }
hello.breakable; hello.text 'd'
}
}
end end
def test_hello_00_06 def test_hello_00_06
@ -356,11 +424,7 @@ End
end end
def tree(width) def tree(width)
out = '' PrettyPrint.format('', width) {|pp| @tree.show(pp)}
pp = PrettyPrint.new(out, width)
@tree.show(pp)
pp.flush
out
end end
def test_tree_00_19 def test_tree_00_19
@ -405,11 +469,7 @@ End
end end
def tree_alt(width) def tree_alt(width)
out = '' PrettyPrint.format('', width) {|pp| @tree.altshow(pp)}
pp = PrettyPrint.new(out, width)
@tree.altshow(pp)
pp.flush
out
end end
def test_tree_alt_00_18 def test_tree_alt_00_18
@ -525,8 +585,7 @@ End
class StrictPrettyExample < RUNIT::TestCase class StrictPrettyExample < RUNIT::TestCase
def prog(width) def prog(width)
out = '' PrettyPrint.format('', width) {|pp|
pp = PrettyPrint.new(out, width)
pp.group { pp.group {
pp.group {pp.nest(2) { pp.group {pp.nest(2) {
pp.text "if"; pp.breakable; pp.text "if"; pp.breakable;
@ -548,8 +607,7 @@ End
pp.nest(2) { pp.nest(2) {
pp.group {pp.text "a"; pp.breakable; pp.text "+"} pp.group {pp.text "a"; pp.breakable; pp.text "+"}
pp.breakable; pp.text "b"}}}}} pp.breakable; pp.text "b"}}}}}
pp.flush }
out
end end
def test_00_04 def test_00_04
@ -672,8 +730,7 @@ End
class TailGroup < RUNIT::TestCase class TailGroup < RUNIT::TestCase
def test_1 def test_1
out = '' out = PrettyPrint.format('', 10) {|pp|
pp = PrettyPrint.new(out, 10)
pp.group { pp.group {
pp.group { pp.group {
pp.text "abc" pp.text "abc"
@ -686,20 +743,18 @@ End
pp.text "jkl" pp.text "jkl"
} }
} }
pp.flush }
assert_equal("abc defghi\njkl", out) assert_equal("abc defghi\njkl", out)
end end
end end
class NonString < RUNIT::TestCase class NonString < RUNIT::TestCase
def format(width) def format(width)
out = [] PrettyPrint.format([], width, 'newline', lambda {|n| "#{n} spaces"}) {|pp|
pp = PrettyPrint.new(out, width, 'newline') {|n| "#{n} spaces"}
pp.text(3, 3) pp.text(3, 3)
pp.breakable(1, 1) pp.breakable(1, 1)
pp.text(3, 3) pp.text(3, 3)
pp.flush }
out
end end
def test_6 def test_6
@ -714,25 +769,23 @@ End
class Fill < RUNIT::TestCase class Fill < RUNIT::TestCase
def format(width) def format(width)
out = '' PrettyPrint.format('', width) {|pp|
pp = PrettyPrint.new(out, width)
pp.group { pp.group {
pp.text 'abc' pp.text 'abc'
pp.group { pp.breakable } pp.fill_breakable
pp.text 'def' pp.text 'def'
pp.group { pp.breakable } pp.fill_breakable
pp.text 'ghi' pp.text 'ghi'
pp.group { pp.breakable } pp.fill_breakable
pp.text 'jkl' pp.text 'jkl'
pp.group { pp.breakable } pp.fill_breakable
pp.text 'mno' pp.text 'mno'
pp.group { pp.breakable } pp.fill_breakable
pp.text 'pqr' pp.text 'pqr'
pp.group { pp.breakable } pp.fill_breakable
pp.text 'stu' pp.text 'stu'
} }
pp.flush }
out
end end
def test_00_06 def test_00_06