From 571ff1d6b974eab38d4707d41f071a176960704b Mon Sep 17 00:00:00 2001 From: akr Date: Thu, 27 Jun 2002 12:01:07 +0000 Subject: * 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 --- lib/pp.rb | 166 ++++++++++------------- lib/prettyprint.rb | 385 ++++++++++++++++++++++++++++++----------------------- 2 files changed, 293 insertions(+), 258 deletions(-) (limited to 'lib') diff --git a/lib/pp.rb b/lib/pp.rb index cf1f6788d2..0c7e6b3bcc 100644 --- a/lib/pp.rb +++ b/lib/pp.rb @@ -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 diff --git a/lib/prettyprint.rb b/lib/prettyprint.rb index 650ffb8482..e1b7e7c59a 100644 --- a/lib/prettyprint.rb +++ b/lib/prettyprint.rb @@ -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 -- cgit v1.2.3