From a2ba32fab9b2bc0d968cfd77f62385beff67042d Mon Sep 17 00:00:00 2001 From: akr Date: Mon, 24 Dec 2001 17:38:33 +0000 Subject: lib/pp.rb lib/prettyprint.rb: new file. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@1936 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/pp.rb | 549 ++++++++++++++++++++++++++++++++++++++++++ lib/prettyprint.rb | 681 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1230 insertions(+) create mode 100644 lib/pp.rb create mode 100644 lib/prettyprint.rb (limited to 'lib') diff --git a/lib/pp.rb b/lib/pp.rb new file mode 100644 index 0000000000..7178c5c78a --- /dev/null +++ b/lib/pp.rb @@ -0,0 +1,549 @@ +# $Id$ + +=begin += Pretty-printer for Ruby objects. + +== Which seems better? + +non-pretty-printed output by (({p})) is: + #, #]>, #, #, #]>, #, #, #]>, #]>]>> + +pretty-printed output by (({pp})) is: + #, + #], + @singleline_length=1, + @tail=1>, + #, + #, + #], + @singleline_length=1, + @tail=1>, + #, + #, + #], + @singleline_length=1, + @tail=1>, + #], + @singleline_length=9, + @tail=0>], + @singleline_length=9, + @tail=0>, + @nest=[0], + @stack=[]> + +I like the latter. If you do too, this library is for you. + +== Usage + +: pp(obj) + output ((|obj|)) to (({$>})) in pretty printed format. + +== Customized output +To define your customized pretty printing function for your class, +redefine a method (({pretty_print(((|pp|)))})) in the class. +It takes an argument ((|pp|)) which is an instance of the class (()). +The method should use PP#text, PP#breakable, PP#nest, PP#group and +PP#pp to print the object. + += PP +== super class +(()) + +== class methods +--- PP.pp(obj[, width[, out]]) + outputs ((|obj|)) to ((|out|)) in pretty printed format of + ((|width|)) columns in width. + +--- PP.sharing_detection + returns the sharing detection flag as boolean value. + It is false by default. + +--- PP.sharing_detection = value + sets the sharing detection flag. + +== methods +--- pp(obj) + adds ((|obj|)) to the pretty printing buffer + using Object#pretty_print or Object#pretty_print_cycled. + + Object#pretty_print_cycled is used when ((|obj|)) is already + printed, a.k.a the object reference chain has a cycle. + += Object +--- pretty_print(pp) + is a default pretty printing method for general objects. + + If (({self})) has a customized (redefined) (({inspect})) method, + the result of (({self.inspect})) is used but it obviously has no + line break hints. + + This module provides predefined pretty_print() methods for some of + the most commonly used built-in classes for convenience. + +--- pretty_print_cycled(pp) + is a default pretty printing method for general objects that are + detected as part of a cycle. +=end + +require 'prettyprint' + +module Kernel + private + def pp(*objs) + objs.each {|obj| + PP.pp(obj) + } + end +end + +class PP < PrettyPrint + def PP.pp(obj, width=79, out=$>) + pp = PP.new + pp.guard_inspect_key {pp.pp obj} + pp.format(out, width) + out << "\n" + end + + @@sharing_detection = false + def PP.sharing_detection + return @@sharing_detection + end + + def PP.sharing_detection=(val) + @@sharing_detection = val + end + + def initialize + super + @sharing_detection = @@sharing_detection + end + + InspectKey = :__inspect_key__ + + def guard_inspect_key + if Thread.current[InspectKey] == nil + Thread.current[InspectKey] = [] + end + + save = Thread.current[InspectKey] + + begin + Thread.current[InspectKey] = [] + yield + ensure + Thread.current[InspectKey] = save + end + end + + def pp(obj) + id = obj.__id__ + + if Thread.current[InspectKey].include? id + group {obj.pretty_print_cycled self} + return + end + + begin + Thread.current[InspectKey] << id + group {obj.pretty_print self} + ensure + Thread.current[InspectKey].pop unless @sharing_detection + end + 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) + } + } + } + } + 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 + } + } + } + } + } + text '}' + } + end + + module ObjectMixin + # 1. specific pretty_print + # 2. specific inspect + # 3. generic pretty_print + + Key = :__pp_instead_of_inspect__ + + def pretty_print(pp) + # specific pretty_print is not defined, try specific inspect. + begin + old = Thread.current[Key] + result1 = sprintf('#<%s:0x%x pretty_printed>', self.class.name, self.__id__) + Thread.current[Key] = [pp, result1] + result2 = ObjectMixin.pp_call_inspect(self) + ensure + Thread.current[Key] = old + end + + unless result1.equal? result2 + pp.text result2 + end + end + + def ObjectMixin.pp_call_inspect(obj); obj.inspect; end + CallInspectFrame = "#{__FILE__}:#{__LINE__ - 1}:in `pp_call_inspect'" + + def inspect + if CallInspectFrame == caller[0] + # specific inspect is also not defined, use generic pretty_print. + pp, result = Thread.current[Key] + pp.pp_object(self) + result + else + super + end + end + + def pretty_print_cycled(pp) + pp.text sprintf("#<%s:0x%x", self.class.name, self.__id__) + pp.breakable + pp.text sprintf("...>") + end + end +end + +[Numeric, FalseClass, TrueClass, Module].each {|c| + c.class_eval { + def pretty_print(pp) + pp.text self.to_s + end + } +} + +class Array + def pretty_print(pp) + pp.text "[" + pp.nest(1) { + first = true + self.each {|v| + if first + first = false + else + pp.text "," + pp.breakable + end + pp.pp v + } + } + pp.text "]" + end + + def pretty_print_cycled(pp) + pp.text(empty? ? '[]' : '[...]') + end +end + +class Hash + def pretty_print(pp) + pp.pp_hash self + end + + def pretty_print_cycled(pp) + pp.text(empty? ? '{}' : '{...}') + end +end + +class << ENV + def pretty_print(pp) + pp.pp_hash self + end +end + +class Struct + def pretty_print(pp) + pp.text sprintf("#<%s", self.class.name) + pp.nest(1) { + first = true + self.members.each {|member| + if first + first = false + else + pp.text "," + end + pp.breakable + pp.group { + pp.text member.to_s + pp.text '=' + pp.nest(1) { + pp.breakable '' + pp.pp self[member] + } + } + } + } + pp.text ">" + end + + def pretty_print_cycled(pp) + pp.text sprintf("#<%s:...>", self.class.name) + end +end + +class Range + def pretty_print(pp) + pp.pp self.begin + pp.breakable '' + pp.text(self.exclude_end? ? '...' : '..') + pp.breakable '' + pp.pp self.end + end +end + +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 "dev="; pp.pp self.dev; pp.text ',' + pp.breakable; pp.text "ino="; pp.pp self.ino; pp.text ',' + pp.breakable + pp.group { + m = self.mode + pp.text "mode="; pp.pp m + pp.breakable + pp.text sprintf("(0%o %s %c%c%c%c%c%c%c%c%c)", + m, self.ftype, + (m & 0400 == 0 ? ?- : ?r), + (m & 0200 == 0 ? ?- : ?w), + (m & 0100 == 0 ? (m & 04000 == 0 ? ?- : ?S) : + (m & 04000 == 0 ? ?x : ?s)), + (m & 0040 == 0 ? ?- : ?r), + (m & 0020 == 0 ? ?- : ?w), + (m & 0010 == 0 ? (m & 02000 == 0 ? ?- : ?S) : + (m & 02000 == 0 ? ?x : ?s)), + (m & 0004 == 0 ? ?- : ?r), + (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.group { + pp.text "uid="; pp.pp self.uid + begin + name = Etc.getpwuid(self.uid).name + pp.breakable; pp.text "(#{name})" + rescue ArgumentError + end + pp.text ',' + } + pp.breakable + pp.group { + pp.text "gid="; pp.pp self.gid + begin + name = Etc.getgrgid(self.gid).name + pp.breakable; pp.text "(#{name})" + rescue ArgumentError + end + pp.text ',' + } + pp.breakable; pp.text "rdev="; pp.pp self.rdev; 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.group { + t = self.atime + pp.text "atime="; pp.pp t + pp.breakable; pp.text "(#{t.tv_sec})" + pp.text ',' + } + pp.breakable + pp.group { + t = self.mtime + pp.text "mtime="; pp.pp t + pp.breakable; pp.text "(#{t.tv_sec})" + pp.text ',' + } + pp.breakable + pp.group { + t = self.ctime + pp.text "ctime="; pp.pp t + pp.breakable; pp.text "(#{t.tv_sec})" + } + pp.text ">" + } + end + end +end + +class Object + include PP::ObjectMixin +end + +[Numeric, Symbol, FalseClass, TrueClass, NilClass, Module].each {|c| + c.class_eval { + alias :pretty_print_cycled :pretty_print + } +} + +if __FILE__ == $0 + require 'runit/testcase' + require 'runit/cui/testrunner' + + class PPTest < RUNIT::TestCase + def test_list0123_12 + assert_equal("[0, 1, 2, 3]\n", PP.pp([0,1,2,3], 12, '')) + end + + def test_list0123_11 + assert_equal("[0,\n 1,\n 2,\n 3]\n", PP.pp([0,1,2,3], 11, '')) + end + end + + class HasInspect + def initialize(a) + @a = a + end + + def inspect + return "" + end + end + + class HasPrettyPrint + def initialize(a) + @a = a + end + + def pretty_print(pp) + pp.text "" + end + end + + class HasBoth + def initialize(a) + @a = a + end + + def inspect + return "" + end + + def pretty_print(pp) + pp.text "" + end + end + + class PPInspectTest < RUNIT::TestCase + def test_hasinspect + a = HasInspect.new(1) + assert_equal("\n", PP.pp(a, 79, '')) + end + + def test_hasprettyprint + a = HasPrettyPrint.new(1) + assert_equal("\n", PP.pp(a, 79, '')) + end + + def test_hasboth + a = HasBoth.new(1) + assert_equal("\n", PP.pp(a, 79, '')) + end + end + + class PPCycleTest < RUNIT::TestCase + def test_array + a = [] + a << a + assert_equal("[[...]]\n", PP.pp(a, 79, '')) + end + + def test_hash + a = {} + a[0] = a + assert_equal("{0=>{...}}\n", PP.pp(a, 79, '')) + end + + S = Struct.new("S", :a, :b) + def test_struct + a = S.new(1,2) + a.b = a + assert_equal("#>\n", PP.pp(a, 79, '')) + end + + def test_object + a = Object.new + a.instance_eval {@a = a} + hex = '0x' + ('%x'%a.id) + assert_equal("#>\n", PP.pp(a, 79, '')) + end + + def test_withinspect + a = [] + a << HasInspect.new(a) + assert_equal("[]\n", PP.pp(a, 79, '')) + end + end + + RUNIT::CUI::TestRunner.run(PPTest.suite) + RUNIT::CUI::TestRunner.run(PPInspectTest.suite) + RUNIT::CUI::TestRunner.run(PPCycleTest.suite) +end diff --git a/lib/prettyprint.rb b/lib/prettyprint.rb new file mode 100644 index 0000000000..6d4a6f97b5 --- /dev/null +++ b/lib/prettyprint.rb @@ -0,0 +1,681 @@ +# $Id$ + +=begin += PrettyPrint +The class implements pretty printing algorithm. +It finds line breaks and nice indentations for grouped structure. + +By default, the class assumes that primitive elements are strings and +each byte in the strings have single column in width. +But it can be used for other situasions +by giving suitable arguments for some methods: +newline object and space generation block for (({PrettyPrint.new})), +optional width argument for (({PrettyPrint#text})), +(({PrettyPrint#breakable})), etc. +There are several candidates to use them: +text formatting using proportional fonts, +multibyte characters which has columns diffrent to number of bytes, +non-string formatting, etc. + +== class methods +--- PrettyPrint.new([newline]) [{|width| ...}] + creates a buffer for pretty printing. + + ((|newline|)) is used for line breaks. + (({"\n"})) is used if it is not specified. + + The block is used to generate spaces. + (({{|width| ' ' * width}})) is used if it is not given. + +== methods +--- text(obj[, width]) + adds ((|obj|)) as a text of ((|width|)) columns in width. + + If ((|width|)) is not specified, (({((|obj|)).length})) is used. + +--- breakable([sep[, width]]) + tells "you can break a line here if necessary", and a + ((|width|))-column text ((|sep|)) is inserted if a line is not + broken at the point. + + If ((|sep|)) is not specified, (({" "})) is used. + + If ((|width|)) is not specified, (({((|sep|)).length})) is used. + You will have to specify this when ((|sep|)) is a multibyte + character, for example. + +--- nest(indent) {...} + increases left margin after newline with ((|indent|)) for line breaks added in the block. + +--- group {...} + groups line break hints added in the block. + +--- format(out[, width]) + outputs buffered data to ((|out|)). + It tries to restrict the line length to ((|width|)) but it may + overflow. + + If ((|width|)) is not specified, 79 is assumed. + + ((|out|)) must have a method named (({<<})) which accepts + a first argument ((|obj|)) of (({PrettyPrint#text})), + a first argument ((|sep|)) of (({PrettyPrint#breakable})), + a first argument ((|newline|)) of (({PrettyPrint.new})), + and + a result of a given block for (({PrettyPrint.new})). + +== Bugs +* Line breaks in a group is constrained to whether all line break hints are + to be breaked or not. Maybe, non-constrained version of + PrettyPrint#group should be provided to filling multi lines. + +* Box based formatting? + +== References +Strictly Pretty, Christian Lindig, March 2000, +(()) + +A prettier printer, Philip Wadler, March 1998, +(()) + +=end + +class PrettyPrint + def initialize(newline="\n", &genspace) + @newline = newline + @genspace = genspace || lambda {|n| ' ' * n} + @buf = Group.new + @nest = [0] + @stack = [] + end + + def text(obj, width=obj.length) + @buf << Text.new(obj, width) + end + + def breakable(sep=' ', width=sep.length) + @buf << Breakable.new(sep, width, @nest.last, @newline, @genspace) + end + + def nest(indent) + nest_enter(indent) + begin + yield + ensure + nest_leave + end + end + + def nest_enter(indent) + @nest << @nest.last + indent + end + + def nest_leave + @nest.pop + end + + def group + group_enter + begin + yield + ensure + group_leave + end + end + + def group_enter + g = Group.new + @buf << g + @stack << @buf + @buf = g + end + + def group_leave + @buf = @stack.pop + end + + def format(out, width=79) + tails = [[-1, 0]] + @buf.update_tails(tails, 0) + @buf.multiline_output(out, 0, 0, width) + end + + class Text + def initialize(text, width) + @text = text + @width = width + end + + def update_tails(tails, group) + tails[-1][1] += @width + end + + def singleline_width + return @width + end + + def singleline_output(out) + out << @text + end + + def multiline_output(out, group, margin, width) + singleline_output(out) + return margin + singleline_width + end + end + + class Breakable + def initialize(sep, width, indent, newline, genspace) + @sep = sep + @width = width + @indent = indent + @newline = newline + @genspace = genspace + end + + def update_tails(tails, group) + if group == tails[-1][0] + tails[-2][1] += @width + tails[-1][1] + tails[-1][1] = 0 + else + tails[-1][1] += @width + tails << [group, 0] + end + end + + def singleline_width + return @width + end + + def singleline_output(out) + out << @sep + end + + def multiline_output(out, group, margin, width) + out << @newline + out << @genspace.call(@indent) + return @indent + end + end + + class Group + def initialize + @buf = [] + @singleline_width = nil + end + + def <<(obj) + @buf << obj + end + + def update_tails(tails, group) + @tail = tails.empty? ? 0 : tails[-1][1] + len = 0 + @buf.reverse_each {|obj| + obj.update_tails(tails, group + 1) + len += obj.singleline_width + } + @singleline_width = len + while !tails.empty? && group <= tails[-1][0] + tails[-2][1] += tails[-1][1] + tails.pop + end + end + + def singleline_width + return @singleline_width + end + + def singleline_output(out) + @buf.each {|obj| obj.singleline_output(out)} + end + + def multiline_output(out, group, margin, width) + if margin + singleline_width + @tail <= width + singleline_output(out) + margin += singleline_width + else + @buf.each {|obj| + margin = obj.multiline_output(out, group + 1, margin, width) + } + end + return margin + end + end +end + +if __FILE__ == $0 + require 'runit/testcase' + require 'runit/cui/testrunner' + + class WadlerExample < RUNIT::TestCase + def setup + @hello = PrettyPrint.new + @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' + } + + @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"))) + end + + def test_hello_00_06 + expected = <<'End'.chomp +hello +a +b +c +d +End + @hello.format(out='', 0); assert_equal(expected, out) + @hello.format(out='', 6); assert_equal(expected, out) + end + + def test_hello_07_08 + expected = <<'End'.chomp +hello a +b +c +d +End + @hello.format(out='', 7); assert_equal(expected, out) + @hello.format(out='', 8); assert_equal(expected, out) + end + + def test_hello_09_10 + expected = <<'End'.chomp +hello a b +c +d +End + @hello.format(out='', 9); assert_equal(expected, out) + @hello.format(out='', 10); assert_equal(expected, out) + end + + def test_hello_11_12 + expected = <<'End'.chomp +hello a b c +d +End + @hello.format(out='', 11); assert_equal(expected, out) + @hello.format(out='', 12); assert_equal(expected, out) + end + + def test_hello_13 + expected = <<'End'.chomp +hello a b c d +End + @hello.format(out='', 13); assert_equal(expected, out) + end + + def test_tree_00_19 + pp = PrettyPrint.new + @tree.show(pp) + expected = <<'End'.chomp +aaaa[bbbbb[ccc, + dd], + eee, + ffff[gg, + hhh, + ii]] +End + pp.format(out='', 0); assert_equal(expected, out) + pp.format(out='', 19); assert_equal(expected, out) + end + + def test_tree_20_22 + pp = PrettyPrint.new + @tree.show(pp) + expected = <<'End'.chomp +aaaa[bbbbb[ccc, dd], + eee, + ffff[gg, + hhh, + ii]] +End + pp.format(out='', 20); assert_equal(expected, out) + pp.format(out='', 22); assert_equal(expected, out) + end + + def test_tree_23_43 + pp = PrettyPrint.new + @tree.show(pp) + expected = <<'End'.chomp +aaaa[bbbbb[ccc, dd], + eee, + ffff[gg, hhh, ii]] +End + pp.format(out='', 23); assert_equal(expected, out) + pp.format(out='', 43); assert_equal(expected, out) + end + + def test_tree_44 + pp = PrettyPrint.new + @tree.show(pp) + pp.format(out='', 44) + assert_equal(<<'End'.chomp, out) +aaaa[bbbbb[ccc, dd], eee, ffff[gg, hhh, ii]] +End + end + + def test_tree_alt_00_18 + pp = PrettyPrint.new + @tree.altshow(pp) + expected = <<'End'.chomp +aaaa[ + bbbbb[ + ccc, + dd + ], + eee, + ffff[ + gg, + hhh, + ii + ] +] +End + pp.format(out='', 0); assert_equal(expected, out) + pp.format(out='', 18); assert_equal(expected, out) + end + + def test_tree_alt_19_20 + pp = PrettyPrint.new + @tree.altshow(pp) + expected = <<'End'.chomp +aaaa[ + bbbbb[ ccc, dd ], + eee, + ffff[ + gg, + hhh, + ii + ] +] +End + pp.format(out='', 19); assert_equal(expected, out) + pp.format(out='', 20); assert_equal(expected, out) + end + + def test_tree_alt_20_49 + pp = PrettyPrint.new + @tree.altshow(pp) + expected = <<'End'.chomp +aaaa[ + bbbbb[ ccc, dd ], + eee, + ffff[ gg, hhh, ii ] +] +End + pp.format(out='', 21); assert_equal(expected, out) + pp.format(out='', 49); assert_equal(expected, out) + end + + def test_tree_alt_50 + pp = PrettyPrint.new + @tree.altshow(pp) + expected = <<'End'.chomp +aaaa[ bbbbb[ ccc, dd ], eee, ffff[ gg, hhh, ii ] ] +End + pp.format(out='', 50); assert_equal(expected, out) + end + + class Tree + def initialize(string, *children) + @string = string + @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 + } + } + 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 + } + end + + end + end + + class StrictPrettyExample < RUNIT::TestCase + def setup + @pp = PrettyPrint.new + @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 + expected = <<'End'.chomp +if + a + == + b +then + a + << + 2 +else + a + + + b +End + @pp.format(out='', 0); assert_equal(expected, out) + @pp.format(out='', 4); assert_equal(expected, out) + end + + def test_05 + expected = <<'End'.chomp +if + a + == + b +then + a + << + 2 +else + a + + b +End + @pp.format(out='', 5); assert_equal(expected, out) + end + + def test_06 + expected = <<'End'.chomp +if + a == + b +then + a << + 2 +else + a + + b +End + @pp.format(out='', 6); assert_equal(expected, out) + end + + def test_07 + expected = <<'End'.chomp +if + a == + b +then + a << + 2 +else + a + b +End + @pp.format(out='', 7); assert_equal(expected, out) + end + + def test_08 + expected = <<'End'.chomp +if + a == b +then + a << 2 +else + a + b +End + @pp.format(out='', 8); assert_equal(expected, out) + end + + def test_09 + expected = <<'End'.chomp +if a == b +then + a << 2 +else + a + b +End + @pp.format(out='', 9); assert_equal(expected, out) + end + + def test_10 + expected = <<'End'.chomp +if a == b +then + a << 2 +else a + b +End + @pp.format(out='', 10); assert_equal(expected, out) + end + + def test_11_31 + expected = <<'End'.chomp +if a == b +then a << 2 +else a + b +End + @pp.format(out='', 11); assert_equal(expected, out) + @pp.format(out='', 15); assert_equal(expected, out) + @pp.format(out='', 31); assert_equal(expected, out) + end + + def test_32 + expected = <<'End'.chomp +if a == b then a << 2 else a + b +End + @pp.format(out='', 32); assert_equal(expected, out) + end + + end + + class TailGroup < RUNIT::TestCase + def test_1 + pp = PrettyPrint.new + pp.group { + pp.group { + pp.text "abc" + pp.breakable + pp.text "def" + } + pp.group { + pp.text "ghi" + pp.breakable + pp.text "jkl" + } + } + pp.format(out='', 10) + assert_equal("abc\ndefghi jkl", out) + end + end + + class NonString < RUNIT::TestCase + def setup + @pp = PrettyPrint.new('newline') {|n| "#{n} spaces"} + @pp.text(3, 3) + @pp.breakable(1, 1) + @pp.text(3, 3) + end + + def test_6 + @pp.format(out=[], 6) + assert_equal([3, "newline", "0 spaces", 3], out) + end + + def test_7 + @pp.format(out=[], 7) + assert_equal([3, 1, 3], out) + end + + end + + RUNIT::CUI::TestRunner.run(WadlerExample.suite) + RUNIT::CUI::TestRunner.run(StrictPrettyExample.suite) + RUNIT::CUI::TestRunner.run(TailGroup.suite) + RUNIT::CUI::TestRunner.run(NonString.suite) +end -- cgit v1.2.3