[rubygems/rubygems] Use a constant empty tar header to avoid extra allocations
[ruby.git] / lib / prettyprint.rb
blob6f50192f5daf29f853ff9b0460104973615ee463
1 # frozen_string_literal: true
3 # This class implements a pretty printing algorithm. It finds line breaks and
4 # nice indentations for grouped structure.
6 # By default, the class assumes that primitive elements are strings and each
7 # byte in the strings have single column in width. But it can be used for
8 # other situations by giving suitable arguments for some methods:
9 # * newline object and space generation block for PrettyPrint.new
10 # * optional width argument for PrettyPrint#text
11 # * PrettyPrint#breakable
13 # There are several candidate uses:
14 # * text formatting using proportional fonts
15 # * multibyte characters which has columns different to number of bytes
16 # * non-string formatting
18 # == Bugs
19 # * Box based formatting?
20 # * Other (better) model/algorithm?
22 # Report any bugs at http://bugs.ruby-lang.org
24 # == References
25 # Christian Lindig, Strictly Pretty, March 2000,
26 # https://lindig.github.io/papers/strictly-pretty-2000.pdf
28 # Philip Wadler, A prettier printer, March 1998,
29 # https://homepages.inf.ed.ac.uk/wadler/topics/language-design.html#prettier
31 # == Author
32 # Tanaka Akira <akr@fsij.org>
34 class PrettyPrint
36   VERSION = "0.2.0"
38   # This is a convenience method which is same as follows:
39   #
40   #   begin
41   #     q = PrettyPrint.new(output, maxwidth, newline, &genspace)
42   #     ...
43   #     q.flush
44   #     output
45   #   end
46   #
47   def PrettyPrint.format(output=''.dup, maxwidth=79, newline="\n", genspace=lambda {|n| ' ' * n})
48     q = PrettyPrint.new(output, maxwidth, newline, &genspace)
49     yield q
50     q.flush
51     output
52   end
54   # This is similar to PrettyPrint::format but the result has no breaks.
55   #
56   # +maxwidth+, +newline+ and +genspace+ are ignored.
57   #
58   # The invocation of +breakable+ in the block doesn't break a line and is
59   # treated as just an invocation of +text+.
60   #
61   def PrettyPrint.singleline_format(output=''.dup, maxwidth=nil, newline=nil, genspace=nil)
62     q = SingleLine.new(output)
63     yield q
64     output
65   end
67   # Creates a buffer for pretty printing.
68   #
69   # +output+ is an output target. If it is not specified, '' is assumed. It
70   # should have a << method which accepts the first argument +obj+ of
71   # PrettyPrint#text, the first argument +sep+ of PrettyPrint#breakable, the
72   # first argument +newline+ of PrettyPrint.new, and the result of a given
73   # block for PrettyPrint.new.
74   #
75   # +maxwidth+ specifies maximum line length. If it is not specified, 79 is
76   # assumed. However actual outputs may overflow +maxwidth+ if long
77   # non-breakable texts are provided.
78   #
79   # +newline+ is used for line breaks. "\n" is used if it is not specified.
80   #
81   # The block is used to generate spaces. {|width| ' ' * width} is used if it
82   # is not given.
83   #
84   def initialize(output=''.dup, maxwidth=79, newline="\n", &genspace)
85     @output = output
86     @maxwidth = maxwidth
87     @newline = newline
88     @genspace = genspace || lambda {|n| ' ' * n}
90     @output_width = 0
91     @buffer_width = 0
92     @buffer = []
94     root_group = Group.new(0)
95     @group_stack = [root_group]
96     @group_queue = GroupQueue.new(root_group)
97     @indent = 0
98   end
100   # The output object.
101   #
102   # This defaults to '', and should accept the << method
103   attr_reader :output
105   # The maximum width of a line, before it is separated in to a newline
106   #
107   # This defaults to 79, and should be an Integer
108   attr_reader :maxwidth
110   # The value that is appended to +output+ to add a new line.
111   #
112   # This defaults to "\n", and should be String
113   attr_reader :newline
115   # A lambda or Proc, that takes one argument, of an Integer, and returns
116   # the corresponding number of spaces.
117   #
118   # By default this is:
119   #   lambda {|n| ' ' * n}
120   attr_reader :genspace
122   # The number of spaces to be indented
123   attr_reader :indent
125   # The PrettyPrint::GroupQueue of groups in stack to be pretty printed
126   attr_reader :group_queue
128   # Returns the group most recently added to the stack.
129   #
130   # Contrived example:
131   #   out = ""
132   #   => ""
133   #   q = PrettyPrint.new(out)
134   #   => #<PrettyPrint:0x82f85c0 @output="", @maxwidth=79, @newline="\n", @genspace=#<Proc:0x82f8368@/home/vbatts/.rvm/rubies/ruby-head/lib/ruby/2.0.0/prettyprint.rb:82 (lambda)>, @output_width=0, @buffer_width=0, @buffer=[], @group_stack=[#<PrettyPrint::Group:0x82f8138 @depth=0, @breakables=[], @break=false>], @group_queue=#<PrettyPrint::GroupQueue:0x82fb7c0 @queue=[[#<PrettyPrint::Group:0x82f8138 @depth=0, @breakables=[], @break=false>]]>, @indent=0>
135   #   q.group {
136   #     q.text q.current_group.inspect
137   #     q.text q.newline
138   #     q.group(q.current_group.depth + 1) {
139   #       q.text q.current_group.inspect
140   #       q.text q.newline
141   #       q.group(q.current_group.depth + 1) {
142   #         q.text q.current_group.inspect
143   #         q.text q.newline
144   #         q.group(q.current_group.depth + 1) {
145   #           q.text q.current_group.inspect
146   #           q.text q.newline
147   #         }
148   #       }
149   #     }
150   #   }
151   #   => 284
152   #    puts out
153   #   #<PrettyPrint::Group:0x8354758 @depth=1, @breakables=[], @break=false>
154   #   #<PrettyPrint::Group:0x8354550 @depth=2, @breakables=[], @break=false>
155   #   #<PrettyPrint::Group:0x83541cc @depth=3, @breakables=[], @break=false>
156   #   #<PrettyPrint::Group:0x8347e54 @depth=4, @breakables=[], @break=false>
157   def current_group
158     @group_stack.last
159   end
161   # Breaks the buffer into lines that are shorter than #maxwidth
162   def break_outmost_groups
163     while @maxwidth < @output_width + @buffer_width
164       return unless group = @group_queue.deq
165       until group.breakables.empty?
166         data = @buffer.shift
167         @output_width = data.output(@output, @output_width)
168         @buffer_width -= data.width
169       end
170       while !@buffer.empty? && Text === @buffer.first
171         text = @buffer.shift
172         @output_width = text.output(@output, @output_width)
173         @buffer_width -= text.width
174       end
175     end
176   end
178   # This adds +obj+ as a text of +width+ columns in width.
179   #
180   # If +width+ is not specified, obj.length is used.
181   #
182   def text(obj, width=obj.length)
183     if @buffer.empty?
184       @output << obj
185       @output_width += width
186     else
187       text = @buffer.last
188       unless Text === text
189         text = Text.new
190         @buffer << text
191       end
192       text.add(obj, width)
193       @buffer_width += width
194       break_outmost_groups
195     end
196   end
198   # This is similar to #breakable except
199   # the decision to break or not is determined individually.
200   #
201   # Two #fill_breakable under a group may cause 4 results:
202   # (break,break), (break,non-break), (non-break,break), (non-break,non-break).
203   # This is different to #breakable because two #breakable under a group
204   # may cause 2 results:
205   # (break,break), (non-break,non-break).
206   #
207   # The text +sep+ is inserted if a line is not broken at this point.
208   #
209   # If +sep+ is not specified, " " is used.
210   #
211   # If +width+ is not specified, +sep.length+ is used. You will have to
212   # specify this when +sep+ is a multibyte character, for example.
213   #
214   def fill_breakable(sep=' ', width=sep.length)
215     group { breakable sep, width }
216   end
218   # This says "you can break a line here if necessary", and a +width+\-column
219   # text +sep+ is inserted if a line is not broken at the point.
220   #
221   # If +sep+ is not specified, " " is used.
222   #
223   # If +width+ is not specified, +sep.length+ is used. You will have to
224   # specify this when +sep+ is a multibyte character, for example.
225   #
226   def breakable(sep=' ', width=sep.length)
227     group = @group_stack.last
228     if group.break?
229       flush
230       @output << @newline
231       @output << @genspace.call(@indent)
232       @output_width = @indent
233       @buffer_width = 0
234     else
235       @buffer << Breakable.new(sep, width, self)
236       @buffer_width += width
237       break_outmost_groups
238     end
239   end
241   # Groups line break hints added in the block. The line break hints are all
242   # to be used or not.
243   #
244   # If +indent+ is specified, the method call is regarded as nested by
245   # nest(indent) { ... }.
246   #
247   # If +open_obj+ is specified, <tt>text open_obj, open_width</tt> is called
248   # before grouping. If +close_obj+ is specified, <tt>text close_obj,
249   # close_width</tt> is called after grouping.
250   #
251   def group(indent=0, open_obj='', close_obj='', open_width=open_obj.length, close_width=close_obj.length)
252     text open_obj, open_width
253     group_sub {
254       nest(indent) {
255         yield
256       }
257     }
258     text close_obj, close_width
259   end
261   # Takes a block and queues a new group that is indented 1 level further.
262   def group_sub
263     group = Group.new(@group_stack.last.depth + 1)
264     @group_stack.push group
265     @group_queue.enq group
266     begin
267       yield
268     ensure
269       @group_stack.pop
270       if group.breakables.empty?
271         @group_queue.delete group
272       end
273     end
274   end
276   # Increases left margin after newline with +indent+ for line breaks added in
277   # the block.
278   #
279   def nest(indent)
280     @indent += indent
281     begin
282       yield
283     ensure
284       @indent -= indent
285     end
286   end
288   # outputs buffered data.
289   #
290   def flush
291     @buffer.each {|data|
292       @output_width = data.output(@output, @output_width)
293     }
294     @buffer.clear
295     @buffer_width = 0
296   end
298   # The Text class is the means by which to collect strings from objects.
299   #
300   # This class is intended for internal use of the PrettyPrint buffers.
301   class Text # :nodoc:
303     # Creates a new text object.
304     #
305     # This constructor takes no arguments.
306     #
307     # The workflow is to append a PrettyPrint::Text object to the buffer, and
308     # being able to call the buffer.last() to reference it.
309     #
310     # As there are objects, use PrettyPrint::Text#add to include the objects
311     # and the width to utilized by the String version of this object.
312     def initialize
313       @objs = []
314       @width = 0
315     end
317     # The total width of the objects included in this Text object.
318     attr_reader :width
320     # Render the String text of the objects that have been added to this Text object.
321     #
322     # Output the text to +out+, and increment the width to +output_width+
323     def output(out, output_width)
324       @objs.each {|obj| out << obj}
325       output_width + @width
326     end
328     # Include +obj+ in the objects to be pretty printed, and increment
329     # this Text object's total width by +width+
330     def add(obj, width)
331       @objs << obj
332       @width += width
333     end
334   end
336   # The Breakable class is used for breaking up object information
337   #
338   # This class is intended for internal use of the PrettyPrint buffers.
339   class Breakable # :nodoc:
341     # Create a new Breakable object.
342     #
343     # Arguments:
344     # * +sep+ String of the separator
345     # * +width+ Integer width of the +sep+
346     # * +q+ parent PrettyPrint object, to base from
347     def initialize(sep, width, q)
348       @obj = sep
349       @width = width
350       @pp = q
351       @indent = q.indent
352       @group = q.current_group
353       @group.breakables.push self
354     end
356     # Holds the separator String
357     #
358     # The +sep+ argument from ::new
359     attr_reader :obj
361     # The width of +obj+ / +sep+
362     attr_reader :width
364     # The number of spaces to indent.
365     #
366     # This is inferred from +q+ within PrettyPrint, passed in ::new
367     attr_reader :indent
369     # Render the String text of the objects that have been added to this
370     # Breakable object.
371     #
372     # Output the text to +out+, and increment the width to +output_width+
373     def output(out, output_width)
374       @group.breakables.shift
375       if @group.break?
376         out << @pp.newline
377         out << @pp.genspace.call(@indent)
378         @indent
379       else
380         @pp.group_queue.delete @group if @group.breakables.empty?
381         out << @obj
382         output_width + @width
383       end
384     end
385   end
387   # The Group class is used for making indentation easier.
388   #
389   # While this class does neither the breaking into newlines nor indentation,
390   # it is used in a stack (as well as a queue) within PrettyPrint, to group
391   # objects.
392   #
393   # For information on using groups, see PrettyPrint#group
394   #
395   # This class is intended for internal use of the PrettyPrint buffers.
396   class Group # :nodoc:
397     # Create a Group object
398     #
399     # Arguments:
400     # * +depth+ - this group's relation to previous groups
401     def initialize(depth)
402       @depth = depth
403       @breakables = []
404       @break = false
405     end
407     # This group's relation to previous groups
408     attr_reader :depth
410     # Array to hold the Breakable objects for this Group
411     attr_reader :breakables
413     # Makes a break for this Group, and returns true
414     def break
415       @break = true
416     end
418     # Boolean of whether this Group has made a break
419     def break?
420       @break
421     end
423     # Boolean of whether this Group has been queried for being first
424     #
425     # This is used as a predicate, and ought to be called first.
426     def first?
427       if defined? @first
428         false
429       else
430         @first = false
431         true
432       end
433     end
434   end
436   # The GroupQueue class is used for managing the queue of Group to be pretty
437   # printed.
438   #
439   # This queue groups the Group objects, based on their depth.
440   #
441   # This class is intended for internal use of the PrettyPrint buffers.
442   class GroupQueue # :nodoc:
443     # Create a GroupQueue object
444     #
445     # Arguments:
446     # * +groups+ - one or more PrettyPrint::Group objects
447     def initialize(*groups)
448       @queue = []
449       groups.each {|g| enq g}
450     end
452     # Enqueue +group+
453     #
454     # This does not strictly append the group to the end of the queue,
455     # but instead adds it in line, base on the +group.depth+
456     def enq(group)
457       depth = group.depth
458       @queue << [] until depth < @queue.length
459       @queue[depth] << group
460     end
462     # Returns the outer group of the queue
463     def deq
464       @queue.each {|gs|
465         (gs.length-1).downto(0) {|i|
466           unless gs[i].breakables.empty?
467             group = gs.slice!(i, 1).first
468             group.break
469             return group
470           end
471         }
472         gs.each {|group| group.break}
473         gs.clear
474       }
475       return nil
476     end
478     # Remote +group+ from this queue
479     def delete(group)
480       @queue[group.depth].delete(group)
481     end
482   end
484   # PrettyPrint::SingleLine is used by PrettyPrint.singleline_format
485   #
486   # It is passed to be similar to a PrettyPrint object itself, by responding to:
487   # * #text
488   # * #breakable
489   # * #nest
490   # * #group
491   # * #flush
492   # * #first?
493   #
494   # but instead, the output has no line breaks
495   #
496   class SingleLine
497     # Create a PrettyPrint::SingleLine object
498     #
499     # Arguments:
500     # * +output+ - String (or similar) to store rendered text. Needs to respond to '<<'
501     # * +maxwidth+ - Argument position expected to be here for compatibility.
502     #                This argument is a noop.
503     # * +newline+ - Argument position expected to be here for compatibility.
504     #               This argument is a noop.
505     def initialize(output, maxwidth=nil, newline=nil)
506       @output = output
507       @first = [true]
508     end
510     # Add +obj+ to the text to be output.
511     #
512     # +width+ argument is here for compatibility. It is a noop argument.
513     def text(obj, width=nil)
514       @output << obj
515     end
517     # Appends +sep+ to the text to be output. By default +sep+ is ' '
518     #
519     # +width+ argument is here for compatibility. It is a noop argument.
520     def breakable(sep=' ', width=nil)
521       @output << sep
522     end
524     # Takes +indent+ arg, but does nothing with it.
525     #
526     # Yields to a block.
527     def nest(indent) # :nodoc:
528       yield
529     end
531     # Opens a block for grouping objects to be pretty printed.
532     #
533     # Arguments:
534     # * +indent+ - noop argument. Present for compatibility.
535     # * +open_obj+ - text appended before the &blok. Default is ''
536     # * +close_obj+ - text appended after the &blok. Default is ''
537     # * +open_width+ - noop argument. Present for compatibility.
538     # * +close_width+ - noop argument. Present for compatibility.
539     def group(indent=nil, open_obj='', close_obj='', open_width=nil, close_width=nil)
540       @first.push true
541       @output << open_obj
542       yield
543       @output << close_obj
544       @first.pop
545     end
547     # Method present for compatibility, but is a noop
548     def flush # :nodoc:
549     end
551     # This is used as a predicate, and ought to be called first.
552     def first?
553       result = @first[-1]
554       @first[-1] = false
555       result
556     end
557   end