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>
* lib/prettyprint.rb: re-implemented for incremental output to handle

166
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
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
@ -102,6 +114,13 @@ PP#pp to print the object.
--- pretty_print_cycled(pp)
is a default pretty printing method for general objects that are
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
require 'prettyprint'
@ -172,60 +191,45 @@ class PP < PrettyPrint
end
end
def object_group(obj, &block)
group(1, '#<' + obj.class.name, '>', &block)
end
def comma_breakable
text ','
breakable
end
def pp_object(obj)
group {
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
breakable
group {
text v
text '='
nest(1) {
breakable ''
pp(obj.instance_eval v)
}
}
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
text v
text '='
group(1) {
breakable ''
pp(obj.instance_eval v)
}
}
text '>'
}
end
def pp_hash(obj)
group {
text '{'
nest(1) {
first = true
obj.each {|k, v|
if first
first = false
else
text ","
breakable
end
group {
pp k
text '=>'
nest(1) {
group {
breakable ''
pp v
}
}
group(1, '{', '}') {
obj.each {|k, v|
comma_breakable unless first?
group {
pp k
text '=>'
group(1) {
breakable ''
pp v
}
}
}
text '}'
}
end
@ -271,6 +275,10 @@ class PP < PrettyPrint
pp.breakable
pp.text sprintf("...>")
end
def pretty_print_instance_variables
instance_variables.sort
end
end
end
@ -284,20 +292,12 @@ end
class Array
def pretty_print(pp)
pp.text "["
pp.nest(1) {
first = true
pp.group(1, '[', ']') {
self.each {|v|
if first
first = false
else
pp.text ","
pp.breakable
end
pp.comma_breakable unless pp.first?
pp.pp v
}
}
pp.text "]"
end
def pretty_print_cycled(pp)
@ -323,27 +323,18 @@ end
class Struct
def pretty_print(pp)
pp.text sprintf("#<%s", self.class.name)
pp.nest(1) {
first = true
pp.object_group(self) {
self.members.each {|member|
if first
first = false
else
pp.text ","
end
pp.text "," unless pp.first?
pp.breakable
pp.group {
pp.text member.to_s
pp.text '='
pp.nest(1) {
pp.breakable ''
pp.pp self[member]
}
pp.text member.to_s
pp.text '='
pp.group(1) {
pp.breakable ''
pp.pp self[member]
}
}
}
pp.text ">"
end
def pretty_print_cycled(pp)
@ -365,12 +356,10 @@ class File
class Stat
def pretty_print(pp)
require 'etc.so'
pp.nest(1) {
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.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)
@ -389,10 +378,9 @@ class File
(m & 0002 == 0 ? ?- : ?w),
(m & 0001 == 0 ? (m & 01000 == 0 ? ?- : ?T) :
(m & 01000 == 0 ? ?x : ?t)))
pp.text ','
}
pp.breakable; pp.text "nlink="; pp.pp self.nlink; pp.text ','
pp.breakable
pp.comma_breakable
pp.text "nlink="; pp.pp self.nlink; pp.comma_breakable
pp.group {
pp.text "uid="; pp.pp self.uid
begin
@ -400,9 +388,8 @@ class File
pp.breakable; pp.text "(#{name})"
rescue ArgumentError
end
pp.text ','
}
pp.breakable
pp.comma_breakable
pp.group {
pp.text "gid="; pp.pp self.gid
begin
@ -410,39 +397,34 @@ class File
pp.breakable; pp.text "(#{name})"
rescue ArgumentError
end
pp.text ','
}
pp.breakable
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.text ','
}
pp.breakable; pp.text "size="; pp.pp self.size; pp.text ','
pp.breakable; pp.text "blksize="; pp.pp self.blksize; pp.text ','
pp.breakable; pp.text "blocks="; pp.pp self.blocks; pp.text ','
pp.breakable
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.text ','
}
pp.breakable
pp.comma_breakable
pp.group {
t = self.mtime
pp.text "mtime="; pp.pp t
pp.breakable; pp.text "(#{t.tv_sec})"
pp.text ','
}
pp.breakable
pp.comma_breakable
pp.group {
t = self.ctime
pp.text "ctime="; pp.pp t
pp.breakable; pp.text "(#{t.tv_sec})"
}
pp.text ">"
}
end
end

View file

@ -18,16 +18,17 @@ multibyte characters which has columns diffrent to number of bytes,
non-string formatting, etc.
== class methods
--- PrettyPrint.new(output[, maxwidth[, newline]]) [{|width| ...}]
--- PrettyPrint.new([output[, maxwidth[, newline]]]) [{|width| ...}]
creates a buffer for pretty printing.
((|output|)) is a output target. It should have a (({<<})) method
which accepts
((|output|)) is a output target.
If it is not specified, (({''})) is assumed.
It should have a (({<<})) method which accepts
the first argument ((|obj|)) of (({PrettyPrint#text})),
the first argument ((|sep|)) of (({PrettyPrint#breakable})),
the first argument ((|newline|)) of (({PrettyPrint.new})),
and
the result of a given block for (({PrettyPrint.new})).
the result of a given block for (({PrettyPrint.new})).
((|maxwidth|)) specifies maximum line length.
If it is not specified, 79 is assumed.
@ -40,6 +41,16 @@ non-string formatting, etc.
The block is used to generate spaces.
(({{|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
--- text(obj[, 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
in the block.
--- group {...}
--- group([indent[, open_obj[, close_obj[, open_width[, close_width]]]]]) {...}
groups line break hints added in the block.
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
outputs buffered data.
== Bugs
* Current API is for minimalists. More useful methods are required.
--- first?
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?
== References
@ -83,7 +115,14 @@ Philip Wadler, A prettier printer, March 1998,
=end
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
@maxwidth = maxwidth
@newline = newline
@ -105,18 +144,22 @@ class PrettyPrint
@group_stack.last
end
def first?
current_group.first?
end
def break_outmost_groups
while @maxwidth < @output_width + @buffer_width
return unless group = @group_queue.deq
until group.breakables.empty?
data = @buffer.shift
@output_width = data.output(@output, @output_width)
@buffer_width -= data.width
data = @buffer.shift
@output_width = data.output(@output, @output_width)
@buffer_width -= data.width
end
while !@buffer.empty? && Text === @buffer.first
text = @buffer.shift
@output_width = text.output(@output, @output_width)
@buffer_width -= text.width
text = @buffer.shift
@output_width = text.output(@output, @output_width)
@buffer_width -= text.width
end
end
end
@ -129,7 +172,7 @@ class PrettyPrint
text = @buffer.last
unless Text === text
text = Text.new
@buffer << text
@buffer << text
end
text.add(obj, width)
@buffer_width += width
@ -137,6 +180,10 @@ class PrettyPrint
end
end
def fill_breakable(sep=' ', width=sep.length)
group { breakable sep, width }
end
def breakable(sep=' ', width=sep.length)
group = @group_stack.last
if group.break?
@ -152,7 +199,17 @@ class PrettyPrint
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_stack.push group
@group_queue.enq group
@ -215,13 +272,13 @@ class PrettyPrint
def output(out, output_width)
@group.breakables.shift
if @group.break?
out << @pp.newline
out << @pp.genspace.call(@indent)
@indent
out << @pp.newline
out << @pp.genspace.call(@indent)
@indent
else
@pp.group_queue.delete @group if @group.breakables.empty?
out << @obj
output_width + @width
@pp.group_queue.delete @group if @group.breakables.empty?
out << @obj
output_width + @width
end
end
end
@ -241,6 +298,15 @@ class PrettyPrint
def break?
@break
end
def first?
if defined? @first
false
else
@first = false
true
end
end
end
class GroupQueue
@ -257,15 +323,15 @@ class PrettyPrint
def deq
@queue.each {|gs|
(gs.length-1).downto(0) {|i|
unless gs[i].breakables.empty?
group = gs.slice!(i, 1).first
group.break
return group
end
}
gs.each {|group| group.break}
gs.clear
(gs.length-1).downto(0) {|i|
unless gs[i].breakables.empty?
group = gs.slice!(i, 1).first
group.break
return group
end
}
gs.each {|group| group.break}
gs.clear
}
return nil
end
@ -283,27 +349,29 @@ if __FILE__ == $0
class WadlerExample < RUNIT::TestCase
def setup
@tree = Tree.new("aaaa", Tree.new("bbbbb", Tree.new("ccc"),
Tree.new("dd")),
Tree.new("eee"),
Tree.new("ffff", Tree.new("gg"),
Tree.new("hhh"),
Tree.new("ii")))
Tree.new("dd")),
Tree.new("eee"),
Tree.new("ffff", Tree.new("gg"),
Tree.new("hhh"),
Tree.new("ii")))
end
def hello(width)
out = ''
hello = PrettyPrint.new(out, width)
hello.group {
hello.group {
hello.group {
hello.group {
hello.text 'hello'
hello.breakable; hello.text 'a'}
hello.breakable; hello.text 'b'}
hello.breakable; hello.text 'c'}
hello.breakable; hello.text 'd'}
hello.flush
out
PrettyPrint.format('', width) {|hello|
hello.group {
hello.group {
hello.group {
hello.group {
hello.text 'hello'
hello.breakable; hello.text 'a'
}
hello.breakable; hello.text 'b'
}
hello.breakable; hello.text 'c'
}
hello.breakable; hello.text 'd'
}
}
end
def test_hello_00_06
@ -356,11 +424,7 @@ End
end
def tree(width)
out = ''
pp = PrettyPrint.new(out, width)
@tree.show(pp)
pp.flush
out
PrettyPrint.format('', width) {|pp| @tree.show(pp)}
end
def test_tree_00_19
@ -405,11 +469,7 @@ End
end
def tree_alt(width)
out = ''
pp = PrettyPrint.new(out, width)
@tree.altshow(pp)
pp.flush
out
PrettyPrint.format('', width) {|pp| @tree.altshow(pp)}
end
def test_tree_alt_00_18
@ -469,55 +529,55 @@ End
class Tree
def initialize(string, *children)
@string = string
@children = children
@children = children
end
def show(pp)
pp.group {
pp.text @string
pp.nest(@string.length) {
unless @children.empty?
pp.text '['
pp.nest(1) {
first = true
@children.each {|t|
if first
first = false
else
pp.text ','
pp.breakable
end
t.show(pp)
}
}
pp.text ']'
end
}
}
pp.group {
pp.text @string
pp.nest(@string.length) {
unless @children.empty?
pp.text '['
pp.nest(1) {
first = true
@children.each {|t|
if first
first = false
else
pp.text ','
pp.breakable
end
t.show(pp)
}
}
pp.text ']'
end
}
}
end
def altshow(pp)
pp.group {
pp.text @string
unless @children.empty?
pp.text '['
pp.nest(2) {
pp.breakable
first = true
@children.each {|t|
if first
first = false
else
pp.text ','
pp.breakable
end
t.altshow(pp)
}
}
pp.breakable
pp.text ']'
end
}
pp.group {
pp.text @string
unless @children.empty?
pp.text '['
pp.nest(2) {
pp.breakable
first = true
@children.each {|t|
if first
first = false
else
pp.text ','
pp.breakable
end
t.altshow(pp)
}
}
pp.breakable
pp.text ']'
end
}
end
end
@ -525,31 +585,29 @@ End
class StrictPrettyExample < RUNIT::TestCase
def prog(width)
out = ''
pp = PrettyPrint.new(out, width)
pp.group {
pp.group {pp.nest(2) {
pp.text "if"; pp.breakable;
pp.group {
pp.nest(2) {
pp.group {pp.text "a"; pp.breakable; pp.text "=="}
pp.breakable; pp.text "b"}}}}
pp.breakable
pp.group {pp.nest(2) {
pp.text "then"; pp.breakable;
pp.group {
pp.nest(2) {
pp.group {pp.text "a"; pp.breakable; pp.text "<<"}
pp.breakable; pp.text "2"}}}}
pp.breakable
pp.group {pp.nest(2) {
pp.text "else"; pp.breakable;
pp.group {
pp.nest(2) {
pp.group {pp.text "a"; pp.breakable; pp.text "+"}
pp.breakable; pp.text "b"}}}}}
pp.flush
out
PrettyPrint.format('', width) {|pp|
pp.group {
pp.group {pp.nest(2) {
pp.text "if"; pp.breakable;
pp.group {
pp.nest(2) {
pp.group {pp.text "a"; pp.breakable; pp.text "=="}
pp.breakable; pp.text "b"}}}}
pp.breakable
pp.group {pp.nest(2) {
pp.text "then"; pp.breakable;
pp.group {
pp.nest(2) {
pp.group {pp.text "a"; pp.breakable; pp.text "<<"}
pp.breakable; pp.text "2"}}}}
pp.breakable
pp.group {pp.nest(2) {
pp.text "else"; pp.breakable;
pp.group {
pp.nest(2) {
pp.group {pp.text "a"; pp.breakable; pp.text "+"}
pp.breakable; pp.text "b"}}}}}
}
end
def test_00_04
@ -672,34 +730,31 @@ End
class TailGroup < RUNIT::TestCase
def test_1
out = ''
pp = PrettyPrint.new(out, 10)
pp.group {
pp.group {
pp.text "abc"
pp.breakable
pp.text "def"
}
pp.group {
pp.text "ghi"
pp.breakable
pp.text "jkl"
}
out = PrettyPrint.format('', 10) {|pp|
pp.group {
pp.group {
pp.text "abc"
pp.breakable
pp.text "def"
}
pp.group {
pp.text "ghi"
pp.breakable
pp.text "jkl"
}
}
}
pp.flush
assert_equal("abc defghi\njkl", out)
end
end
class NonString < RUNIT::TestCase
def format(width)
out = []
pp = PrettyPrint.new(out, width, 'newline') {|n| "#{n} spaces"}
pp.text(3, 3)
pp.breakable(1, 1)
pp.text(3, 3)
pp.flush
out
PrettyPrint.format([], width, 'newline', lambda {|n| "#{n} spaces"}) {|pp|
pp.text(3, 3)
pp.breakable(1, 1)
pp.text(3, 3)
}
end
def test_6
@ -714,25 +769,23 @@ End
class Fill < RUNIT::TestCase
def format(width)
out = ''
pp = PrettyPrint.new(out, width)
pp.group {
pp.text 'abc'
pp.group { pp.breakable }
pp.text 'def'
pp.group { pp.breakable }
pp.text 'ghi'
pp.group { pp.breakable }
pp.text 'jkl'
pp.group { pp.breakable }
pp.text 'mno'
pp.group { pp.breakable }
pp.text 'pqr'
pp.group { pp.breakable }
pp.text 'stu'
PrettyPrint.format('', width) {|pp|
pp.group {
pp.text 'abc'
pp.fill_breakable
pp.text 'def'
pp.fill_breakable
pp.text 'ghi'
pp.fill_breakable
pp.text 'jkl'
pp.fill_breakable
pp.text 'mno'
pp.fill_breakable
pp.text 'pqr'
pp.fill_breakable
pp.text 'stu'
}
}
pp.flush
out
end
def test_00_06