Feature #18360 ยป prettyprint.patch
| lib/prettyprint.rb | ||
|---|---|---|
| # 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 situations by giving suitable arguments for some methods: | ||
| # byte in the strings is a single column in width. But it can be used for other | ||
| # situations by giving suitable arguments for some methods: | ||
| # | ||
| # * newline object and space generation block for PrettyPrint.new | ||
| # * optional width argument for PrettyPrint#text | ||
| # * PrettyPrint#breakable | ||
| ... | ... | |
| # * multibyte characters which has columns different to number of bytes | ||
| # * non-string formatting | ||
| # | ||
| # == Usage | ||
| # | ||
| # To use this module, you will need to generate a tree of print nodes that | ||
| # represent indentation and newline behavior before it gets sent to the printer. | ||
| # Each node has different semantics, depending on the desired output. | ||
| # | ||
| # The most basic node is a Text node. This represents plain text content that | ||
| # cannot be broken up even if it doesn't fit on one line. You would create one | ||
| # of those with the text method, as in: | ||
| # | ||
| #     PrettyPrint.format { |q| q.text('my content') } | ||
| # | ||
| # No matter what the desired output width is, the output for the snippet above | ||
| # will always be the same. | ||
| # | ||
| # If you want to allow the printer to break up the content on the space | ||
| # character when there isn't enough width for the full string on the same line, | ||
| # you can use the Breakable and Group nodes. For example: | ||
| # | ||
| #     PrettyPrint.format do |q| | ||
| #       q.group do | ||
| #         q.text('my') | ||
| #         q.breakable | ||
| #         q.text('content') | ||
| #       end | ||
| #     end | ||
| # | ||
| # Now, if everything fits on one line (depending on the maximum width specified) | ||
| # then it will be the same output as the first example. If, however, there is | ||
| # not enough room on the line, then you will get two lines of output, one for | ||
| # the first string and one for the second. | ||
| # | ||
| # There are other nodes for the print tree as well, described in the | ||
| # documentation below. They control alignment, indentation, conditional | ||
| # formatting, and more. | ||
| # | ||
| # == Bugs | ||
| # * Box based formatting? | ||
| # * Other (better) model/algorithm? | ||
| # | ||
| # Report any bugs at http://bugs.ruby-lang.org | ||
| # | ||
| # == References | ||
| # Christian Lindig, Strictly Pretty, March 2000, | ||
| # http://www.st.cs.uni-sb.de/~lindig/papers/#pretty | ||
| # https://lindig.github.io/papers/strictly-pretty-2000.pdf | ||
| # | ||
| # Philip Wadler, A prettier printer, March 1998, | ||
| # http://homepages.inf.ed.ac.uk/wadler/topics/language-design.html#prettier | ||
| # https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf | ||
| # | ||
| # == Author | ||
| # Tanaka Akira <akr@fsij.org> | ||
| # | ||
| class PrettyPrint | ||
|   # A node in the print tree that represents aligning nested nodes to a certain | ||
|   # prefix width or string. | ||
|   class Align | ||
|     attr_reader :indent, :contents | ||
|     def initialize(indent:, contents: []) | ||
|       @indent = indent | ||
|       @contents = contents | ||
|     end | ||
|     def pretty_print(q) | ||
|       q.group(2, 'align([', '])') do | ||
|         q.seplist(contents) { |content| q.pp(content) } | ||
|       end | ||
|     end | ||
|   end | ||
|   # A node in the print tree that represents a place in the buffer that the | ||
|   # content can be broken onto multiple lines. | ||
|   class Breakable | ||
|     attr_reader :separator, :width | ||
|     def initialize(separator = ' ', width = separator.length, force: false, indent: true) | ||
|       @separator = separator | ||
|       @width = width | ||
|       @force = force | ||
|       @indent = indent | ||
|     end | ||
|     def force? | ||
|       @force | ||
|     end | ||
|     def indent? | ||
|       @indent | ||
|     end | ||
|     def pretty_print(q) | ||
|       q.text('breakable') | ||
|       attributes = | ||
|         [('force=true' if force?), ('indent=false' unless indent?)].compact | ||
|       if attributes.any? | ||
|         q.text('(') | ||
|         q.seplist(attributes, -> { q.text(', ') }) do |attribute| | ||
|           q.text(attribute) | ||
|         end | ||
|         q.text(')') | ||
|       end | ||
|     end | ||
|   end | ||
|   # A node in the print tree that forces the surrounding group to print out in | ||
|   # the "break" mode as opposed to the "flat" mode. Useful for when you need to | ||
|   # force a newline into a group. | ||
|   class BreakParent | ||
|     def pretty_print(q) | ||
|       q.text('break-parent') | ||
|     end | ||
|   end | ||
|   # A node in the print tree that represents a group of items which the printer | ||
|   # should try to fit onto one line. This is the basic command to tell the | ||
|   # printer when to break. Groups are usually nested, and the printer will try | ||
|   # to fit everything on one line, but if it doesn't fit it will break the | ||
|   # outermost group first and try again. It will continue breaking groups until | ||
|   # everything fits (or there are no more groups to break). | ||
|   class Group | ||
|     attr_reader :depth, :contents | ||
|     def initialize(depth, contents: []) | ||
|       @depth = depth | ||
|       @contents = contents | ||
|       @break = false | ||
|     end | ||
|     def break | ||
|       @break = true | ||
|     end | ||
|     def break? | ||
|       @break | ||
|     end | ||
|     def pretty_print(q) | ||
|       q.group(2, 'group([', '])') do | ||
|         q.seplist(contents) { |content| q.pp(content) } | ||
|       end | ||
|     end | ||
|   end | ||
|   # A node in the print tree that represents printing one thing if the | ||
|   # surrounding group node is broken and another thing if the surrounding group | ||
|   # node is flat. | ||
|   class IfBreak | ||
|     attr_reader :break_contents, :flat_contents | ||
|     def initialize(break_contents: [], flat_contents: []) | ||
|       @break_contents = break_contents | ||
|       @flat_contents = flat_contents | ||
|     end | ||
|     def pretty_print(q) | ||
|       q.group(2, 'if-break(', ')') do | ||
|         q.breakable('') | ||
|         q.group(2, '[', '],') do | ||
|           q.seplist(break_contents) { |content| q.pp(content) } | ||
|         end | ||
|         q.breakable | ||
|         q.group(2, '[', ']') do | ||
|           q.seplist(flat_contents) { |content| q.pp(content) } | ||
|         end | ||
|       end | ||
|     end | ||
|   end | ||
|   # A node in the print tree that is a variant of the Align node that indents | ||
|   # its contents by one level. | ||
|   class Indent | ||
|     attr_reader :contents | ||
|     def initialize(contents: []) | ||
|       @contents = contents | ||
|     end | ||
|     def pretty_print(q) | ||
|       q.group(2, 'indent([', '])') do | ||
|         q.seplist(contents) { |content| q.pp(content) } | ||
|       end | ||
|     end | ||
|   end | ||
|   # A node in the print tree that has its own special buffer for implementing | ||
|   # content that should flush before any newline. | ||
|   # | ||
|   # Useful for implementating trailing content, as it's not always practical to | ||
|   # constantly check where the line ends to avoid accidentally printing some | ||
|   # content after a line suffix node. | ||
|   class LineSuffix | ||
|     attr_reader :contents | ||
|     def initialize(contents: []) | ||
|       @contents = contents | ||
|     end | ||
|     def pretty_print(q) | ||
|       q.group(2, 'line-suffix([', '])') do | ||
|         q.seplist(contents) { |content| q.pp(content) } | ||
|       end | ||
|     end | ||
|   end | ||
|   # A node in the print tree that represents plain content that cannot be broken | ||
|   # up (by default this assumes strings, but it can really be anything). | ||
|   class Text | ||
|     attr_reader :objects, :width | ||
|     def initialize | ||
|       @objects = [] | ||
|       @width = 0 | ||
|     end | ||
|     def add(object: '', width: object.length) | ||
|       @objects << object | ||
|       @width += width | ||
|     end | ||
|     def pretty_print(q) | ||
|       q.group(2, 'text([', '])') do | ||
|         q.seplist(objects) { |object| q.pp(object) } | ||
|       end | ||
|     end | ||
|   end | ||
|   # A node in the print tree that represents trimming all of the indentation of | ||
|   # the current line, in the rare case that you need to ignore the indentation | ||
|   # that you've already created. This node should be placed after a Breakable. | ||
|   class Trim | ||
|     def pretty_print(q) | ||
|       q.text('trim') | ||
|     end | ||
|   end | ||
|   # When building up the contents in the output buffer, it's convenient to be | ||
|   # able to trim trailing whitespace before newlines. If the output object is a | ||
|   # string or array or strings, then we can do this with some gsub calls. If | ||
|   # not, then this effectively just wraps the output object and forwards on | ||
|   # calls to <<. | ||
|   module Buffer | ||
|     # This is the default output buffer that provides a base implementation of | ||
|     # trim! that does nothing. It's effectively a wrapper around whatever output | ||
|     # object was given to the format command. | ||
|     class DefaultBuffer | ||
|       attr_reader :output | ||
|       def initialize(output = []) | ||
|         @output = output | ||
|       end | ||
|       def <<(object) | ||
|         @output << object | ||
|       end | ||
|       def trim! | ||
|         0 | ||
|       end | ||
|     end | ||
|     # This is an output buffer that wraps a string output object. It provides a | ||
|     # trim! method that trims off trailing whitespace from the string using | ||
|     # gsub!. | ||
|     class StringBuffer < DefaultBuffer | ||
|       def initialize(output = ''.dup) | ||
|         super(output) | ||
|       end | ||
|       def trim! | ||
|         length = output.length | ||
|         output.gsub!(/[\t ]*\z/, '') | ||
|         length - output.length | ||
|       end | ||
|     end | ||
|     # This is an output buffer that wraps an array output object. It provides a | ||
|     # trim! method that trims off trailing whitespace from the last element in | ||
|     # the array if it's an unfrozen string using the same method as the | ||
|     # StringBuffer. | ||
|     class ArrayBuffer < DefaultBuffer | ||
|       def initialize(output = []) | ||
|         super(output) | ||
|       end | ||
|       def trim! | ||
|         return 0 if output.empty? | ||
|         trimmed = 0 | ||
|         while output.any? && output.last.is_a?(String) && output.last.match?(/\A[\t ]*\z/) | ||
|           trimmed += parts.pop.length | ||
|         end | ||
|         if output.any? && output.last.is_a?(String) && !output.last.frozen? | ||
|           length = output.last.length | ||
|           output.last.gsub!(/[\t ]*\z/, '') | ||
|           trimmed += length - output.last.length | ||
|         end | ||
|         trimmed | ||
|       end | ||
|     end | ||
|     # This is a switch for building the correct output buffer wrapper class for | ||
|     # the given output object. | ||
|     def self.for(output) | ||
|       case output | ||
|       when String | ||
|         StringBuffer.new(output) | ||
|       when Array | ||
|         ArrayBuffer.new(output) | ||
|       else | ||
|         DefaultBuffer.new(output) | ||
|       end | ||
|     end | ||
|   end | ||
|   # PrettyPrint::SingleLine is used by PrettyPrint.singleline_format | ||
|   # | ||
|   # It is passed to be similar to a PrettyPrint object itself, by responding to | ||
|   # all of the same print tree node builder methods, as well as the #flush | ||
|   # method. | ||
|   # | ||
|   # The significant difference here is that there are no line breaks in the | ||
|   # output. If an IfBreak node is used, only the flat contents are printed. | ||
|   # LineSuffix nodes are printed at the end of the buffer when #flush is called. | ||
|   class SingleLine | ||
|     # The output object. It stores rendered text and shoudl respond to <<. | ||
|     attr_reader :output | ||
|     # The current array of contents that the print tree builder methods should | ||
|     # append to. | ||
|     attr_reader :target | ||
|     # A buffer output that wraps any calls to line_suffix that will be flushed | ||
|     # at the end of printing. | ||
|     attr_reader :line_suffixes | ||
|     # Create a PrettyPrint::SingleLine object | ||
|     # | ||
|     # Arguments: | ||
|     # * +output+ - String (or similar) to store rendered text. Needs to respond | ||
|     #              to '<<'. | ||
|     # * +maxwidth+ - Argument position expected to be here for compatibility. | ||
|     #                This argument is a noop. | ||
|     # * +newline+ - Argument position expected to be here for compatibility. | ||
|     #               This argument is a noop. | ||
|     def initialize(output, maxwidth = nil, newline = nil) | ||
|       @output = Buffer.for(output) | ||
|       @target = @output | ||
|       @line_suffixes = Buffer::ArrayBuffer.new | ||
|     end | ||
|     # Flushes the line suffixes onto the output buffer. | ||
|     def flush | ||
|       line_suffixes.output.each { |doc| output << doc } | ||
|     end | ||
|     # -------------------------------------------------------------------------- | ||
|     # Markers node builders | ||
|     # -------------------------------------------------------------------------- | ||
|     # Appends +separator+ to the text to be output. By default +separator+ is | ||
|     # ' ' | ||
|     # | ||
|     # The +width+, +indent+, and +force+ arguments are here for compatibility. | ||
|     # They are all noop arguments. | ||
|     def breakable(separator = ' ', width = separator.length, indent: nil, force: nil) | ||
|       target << separator | ||
|     end | ||
|     # Here for compatibility, does nothing. | ||
|     def break_parent | ||
|     end | ||
|     # Appends +separator+ to the output buffer. +width+ is a noop here for | ||
|     # compatibility. | ||
|     def fill_breakable(separator = ' ', width = separator.length) | ||
|       target << separator | ||
|     end | ||
|     # Immediately trims the output buffer. | ||
|     def trim | ||
|       target.trim! | ||
|     end | ||
|     # ---------------------------------------------------------------------------- | ||
|     # Container node builders | ||
|     # ---------------------------------------------------------------------------- | ||
|     # Opens a block for grouping objects to be pretty printed. | ||
|     # | ||
|     # Arguments: | ||
|     # * +indent+ - noop argument. Present for compatibility. | ||
|     # * +open_obj+ - text appended before the &block. Default is '' | ||
|     # * +close_obj+ - text appended after the &block. Default is '' | ||
|     # * +open_width+ - noop argument. Present for compatibility. | ||
|     # * +close_width+ - noop argument. Present for compatibility. | ||
|     def group(indent = nil, open_object = '', close_object = '', open_width = nil, close_width = nil) | ||
|       target << open_object | ||
|       yield | ||
|       target << close_object | ||
|     end | ||
|     # A class that wraps the ability to call #if_flat. The contents of the | ||
|     # #if_flat block are executed immediately, so effectively this class and the | ||
|     # #if_break method that triggers it are unnecessary, but they're here to | ||
|     # maintain compatibility. | ||
|     class IfBreakBuilder | ||
|       def if_flat | ||
|         yield | ||
|       end | ||
|     end | ||
|     # Effectively unnecessary, but here for compatibility. | ||
|     def if_break | ||
|       IfBreakBuilder.new | ||
|     end | ||
|     # A noop that immediately yields. | ||
|     def indent | ||
|       yield | ||
|     end | ||
|     # Changes the target output buffer to the line suffix output buffer which | ||
|     # will get flushed at the end of printing. | ||
|     def line_suffix | ||
|       previous_target, @target = @target, line_suffixes | ||
|       yield | ||
|       @target = previous_target | ||
|     end | ||
|     # Takes +indent+ arg, but does nothing with it. | ||
|     # | ||
|     # Yields to a block. | ||
|     def nest(indent) | ||
|       yield | ||
|     end | ||
|     # Add +object+ to the text to be output. | ||
|     # | ||
|     # +width+ argument is here for compatibility. It is a noop argument. | ||
|     def text(object = '', width = nil) | ||
|       target << object | ||
|     end | ||
|   end | ||
|   # This object represents the current level of indentation within the printer. | ||
|   # It has the ability to generate new levels of indentation through the #align | ||
|   # and #indent methods. | ||
|   class IndentLevel | ||
|     IndentPart = Object.new | ||
|     DedentPart = Object.new | ||
|     StringAlignPart = Struct.new(:n) | ||
|     NumberAlignPart = Struct.new(:n) | ||
|     attr_reader :genspace, :value, :length, :queue, :root | ||
|     def initialize(genspace:, value: genspace.call(0), length: 0, queue: [], root: nil) | ||
|       @genspace = genspace | ||
|       @value = value | ||
|       @length = length | ||
|       @queue = queue | ||
|       @root = root | ||
|     end | ||
|     # This can accept a whole lot of different kinds of objects, due to the | ||
|     # nature of the flexibility of the Align node. | ||
|     def align(n) | ||
|       case n | ||
|       when NilClass | ||
|         self | ||
|       when String | ||
|         indent(StringAlignPart.new(n)) | ||
|       else | ||
|         indent(n < 0 ? DedentPart : NumberAlignPart.new(n)) | ||
|       end | ||
|     end | ||
|     def indent(part = IndentPart) | ||
|       next_value = genspace.call(0) | ||
|       next_length = 0 | ||
|       next_queue = (part == DedentPart ? queue[0...-1] : [*queue, part]) | ||
|       last_spaces = 0 | ||
|       add_spaces = ->(count) { | ||
|         next_value << genspace.call(count) | ||
|         next_length += count | ||
|       } | ||
|       flush_spaces = -> { | ||
|         add_spaces[last_spaces] if last_spaces > 0 | ||
|         last_spaces = 0 | ||
|       } | ||
|       next_queue.each do |part| | ||
|         case part | ||
|         when IndentPart | ||
|           flush_spaces.call | ||
|           add_spaces.call(2) | ||
|         when StringAlignPart | ||
|           flush_spaces.call | ||
|           next_value += part.n | ||
|           next_length += part.n.length | ||
|         when NumberAlignPart | ||
|           last_spaces += part.n | ||
|         end | ||
|       end | ||
|       flush_spaces.call | ||
|       IndentLevel.new( | ||
|         genspace: genspace, | ||
|         value: next_value, | ||
|         length: next_length, | ||
|         queue: next_queue, | ||
|         root: root | ||
|       ) | ||
|     end | ||
|   end | ||
|   # This is a visitor that can be passed to PrettyPrint.visit that will | ||
|   # propagate BreakParent nodes all of the way up the tree. When a BreakParent | ||
|   # is encountered, it will break the surrounding group, and then that group | ||
|   # will break its parent, and so on. | ||
|   class PropagateBreaksVisitor | ||
|     attr_reader :groups, :visited | ||
|     def initialize | ||
|       @groups = [] | ||
|       @visited = [] | ||
|     end | ||
|     def on_enter(doc) | ||
|       case doc | ||
|       when BreakParent | ||
|         groups.last&.break | ||
|       when Group | ||
|         groups << doc | ||
|         return false if visited.include?(doc) | ||
|         visited << doc | ||
|       end | ||
|       true | ||
|     end | ||
|     def on_exit(doc) | ||
|       groups.last&.break if doc.is_a?(Group) && groups.pop.break? | ||
|     end | ||
|   end | ||
|   # When printing, you can optionally specify the value that should be used | ||
|   # whenever a group needs to be broken onto multiple lines. In this case the | ||
|   # default is \n. | ||
|   DEFAULT_NEWLINE = "\n" | ||
|   # When generating spaces after a newline for indentation, by default we | ||
|   # generate one space per character needed for indentation. You can change this | ||
|   # behavior (for instance to use tabs) by passing a different genspace | ||
|   # procedure. | ||
|   DEFAULT_GENSPACE = ->(n) { ' ' * n } | ||
|   # There are two modes in printing, break and flat. When we're in break mode, | ||
|   # any lines will use their newline, any if-breaks will use their break | ||
|   # contents, etc. | ||
|   MODE_BREAK = 1 | ||
|   # This is another print mode much like MODE_BREAK. When we're in flat mode, we | ||
|   # attempt to print everything on one line until we either hit a broken group, | ||
|   # a forced line, or the maximum width. | ||
|   MODE_FLAT = 2 | ||
|   # This is a convenience method which is same as follows: | ||
|   # | ||
| ... | ... | |
|   #     output | ||
|   #   end | ||
|   # | ||
|   def PrettyPrint.format(output=''.dup, maxwidth=79, newline="\n", genspace=lambda {|n| ' ' * n}) | ||
|     q = PrettyPrint.new(output, maxwidth, newline, &genspace) | ||
|   def self.format(output = ''.dup, maxwidth = 80, newline = DEFAULT_NEWLINE, genspace = DEFAULT_GENSPACE) | ||
|     q = new(output, maxwidth, newline, &genspace) | ||
|     yield q | ||
|     q.flush | ||
|     output | ||
| ... | ... | |
|   # The invocation of +breakable+ in the block doesn't break a line and is | ||
|   # treated as just an invocation of +text+. | ||
|   # | ||
|   def PrettyPrint.singleline_format(output=''.dup, maxwidth=nil, newline=nil, genspace=nil) | ||
|   def self.singleline_format(output = ''.dup, maxwidth = nil, newline = nil, genspace = nil) | ||
|     q = SingleLine.new(output) | ||
|     yield q | ||
|     output | ||
|   end | ||
|   # This method provides a way to walk through the print tree with a specified | ||
|   # +visitor+ object. +visitor+ should respond to both #on_enter(doc) and | ||
|   # #on_exit(doc). | ||
|   def self.visit(doc, visitor) | ||
|     marker = Object.new | ||
|     stack = [doc] | ||
|     while stack.any? | ||
|       doc = stack.pop | ||
|       if doc == marker | ||
|         visitor.on_exit(stack.pop) | ||
|         next | ||
|       end | ||
|       stack += [doc, marker] | ||
|       if visitor.on_enter(doc) | ||
|         case doc | ||
|         when Array | ||
|           doc.reverse_each { |part| stack << part } | ||
|         when IfBreak | ||
|           stack << doc.break_contents if doc.break_contents | ||
|           stack << doc.flat_contents if doc.flat_contents | ||
|         when Align, Indent, Group, LineSuffix | ||
|           stack << doc.contents | ||
|         end | ||
|       end | ||
|     end | ||
|   end | ||
|   # The output object. It represents the final destination of the contents of | ||
|   # the print tree. Its type is one of the classes in the Buffer module. Those | ||
|   # classes all wrap an object that should respond to <<. | ||
|   # | ||
|   # This defaults to Buffer::StringBuffer.new('') | ||
|   attr_reader :output | ||
|   # The maximum width of a line, before it is separated in to a newline | ||
|   # | ||
|   # This defaults to 80, and should be an Integer | ||
|   attr_reader :maxwidth | ||
|   # The value that is appended to +output+ to add a new line. | ||
|   # | ||
|   # This defaults to "\n", and should be String | ||
|   attr_reader :newline | ||
|   # An object that responds to call that takes one argument, of an Integer, and | ||
|   # returns the corresponding number of spaces. | ||
|   # | ||
|   # By default this is: ->(n) { ' ' * n } | ||
|   attr_reader :genspace | ||
|   # The stack of groups that are being printed. | ||
|   attr_reader :groups | ||
|   # The current array of contents that calls to methods that generate print tree | ||
|   # nodes will append to. | ||
|   attr_reader :target | ||
|   # Creates a buffer for pretty printing. | ||
|   # | ||
|   # +output+ is an 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 | ||
|   # PrettyPrint#text, the first argument +separator+ of PrettyPrint#breakable, | ||
|   # the first argument +newline+ of PrettyPrint.new, and the result of a given | ||
|   # block for PrettyPrint.new. | ||
|   # | ||
|   # +maxwidth+ specifies maximum line length. If it is not specified, 79 is | ||
|   # +maxwidth+ specifies maximum line length. If it is not specified, 80 is | ||
|   # assumed. However actual outputs may overflow +maxwidth+ if long | ||
|   # non-breakable texts are provided. | ||
|   # | ||
|   # +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. | ||
|   # | ||
|   def initialize(output=''.dup, maxwidth=79, newline="\n", &genspace) | ||
|     @output = output | ||
|   # The block is used to generate spaces. ->(n) { ' ' * n } is used if it is not | ||
|   # given. | ||
|   def initialize(output = ''.dup, maxwidth = 80, newline = DEFAULT_NEWLINE, &genspace) | ||
|     @output = Buffer.for(output) | ||
|     @maxwidth = maxwidth | ||
|     @newline = newline | ||
|     @genspace = genspace || lambda {|n| ' ' * n} | ||
|     @output_width = 0 | ||
|     @buffer_width = 0 | ||
|     @buffer = [] | ||
|     root_group = Group.new(0) | ||
|     @group_stack = [root_group] | ||
|     @group_queue = GroupQueue.new(root_group) | ||
|     @indent = 0 | ||
|     @genspace = genspace || DEFAULT_GENSPACE | ||
|     reset | ||
|   end | ||
|   # The output object. | ||
|   # | ||
|   # This defaults to '', and should accept the << method | ||
|   attr_reader :output | ||
|   # The maximum width of a line, before it is separated in to a newline | ||
|   # | ||
|   # This defaults to 79, and should be an Integer | ||
|   attr_reader :maxwidth | ||
|   # The value that is appended to +output+ to add a new line. | ||
|   # | ||
|   # This defaults to "\n", and should be String | ||
|   attr_reader :newline | ||
|   # A lambda or Proc, that takes one argument, of an Integer, and returns | ||
|   # the corresponding number of spaces. | ||
|   # | ||
|   # By default this is: | ||
|   #   lambda {|n| ' ' * n} | ||
|   attr_reader :genspace | ||
|   # The number of spaces to be indented | ||
|   attr_reader :indent | ||
|   # The PrettyPrint::GroupQueue of groups in stack to be pretty printed | ||
|   attr_reader :group_queue | ||
|   # Returns the group most recently added to the stack. | ||
|   # | ||
|   # Contrived example: | ||
|   #   out = "" | ||
|   #   => "" | ||
|   #   q = PrettyPrint.new(out) | ||
|   #   => #<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> | ||
|   #   => #<PrettyPrint:0x0> | ||
|   #   q.group { | ||
|   #     q.text q.current_group.inspect | ||
|   #     q.text q.newline | ||
| ... | ... | |
|   #   } | ||
|   #   => 284 | ||
|   #    puts out | ||
|   #   #<PrettyPrint::Group:0x8354758 @depth=1, @breakables=[], @break=false> | ||
|   #   #<PrettyPrint::Group:0x8354550 @depth=2, @breakables=[], @break=false> | ||
|   #   #<PrettyPrint::Group:0x83541cc @depth=3, @breakables=[], @break=false> | ||
|   #   #<PrettyPrint::Group:0x8347e54 @depth=4, @breakables=[], @break=false> | ||
|   #   #<PrettyPrint::Group:0x0 @depth=1> | ||
|   #   #<PrettyPrint::Group:0x0 @depth=2> | ||
|   #   #<PrettyPrint::Group:0x0 @depth=3> | ||
|   #   #<PrettyPrint::Group:0x0 @depth=4> | ||
|   def current_group | ||
|     @group_stack.last | ||
|     groups.last | ||
|   end | ||
|   # Breaks the buffer into lines that are shorter than #maxwidth | ||
|   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 | ||
|   # Flushes all of the generated print tree onto the output buffer, then clears | ||
|   # the generated tree from memory. | ||
|   def flush | ||
|     # First, ensure that we've propagated all of the necessary break-parent | ||
|     # nodes throughout the tree. | ||
|     doc = groups.first | ||
|     PrettyPrint.visit(doc, PropagateBreaksVisitor.new) | ||
|     # This represents how far along the current line we are. It gets reset | ||
|     # back to 0 when we encounter a newline. | ||
|     position = 0 | ||
|     # This is our command stack. A command consists of a triplet of an | ||
|     # indentation level, the mode (break or flat), and a doc node. | ||
|     commands = [[IndentLevel.new(genspace: genspace), MODE_BREAK, doc]] | ||
|     # This is a small optimization boolean. It keeps track of whether or not | ||
|     # when we hit a group node we should check if it fits on the same line. | ||
|     should_remeasure = false | ||
|     # This is a separate command stack that includes the same kind of triplets | ||
|     # as the commands variable. It is used to keep track of things that should | ||
|     # go at the end of printed lines once the other doc nodes are | ||
|     # accounted for. Typically this is used to implement comments. | ||
|     line_suffixes = [] | ||
|     # This is a linear stack instead of a mutually recursive call defined on | ||
|     # the individual doc nodes for efficiency. | ||
|     while commands.any? | ||
|       indent, mode, doc = commands.pop | ||
|       case doc | ||
|       when Text | ||
|         doc.objects.each { |object| output << object } | ||
|         position += doc.width | ||
|       when Array | ||
|         doc.reverse_each { |part| commands << [indent, mode, part] } | ||
|       when Indent | ||
|         commands << [indent.indent, mode, doc.contents] | ||
|       when Align | ||
|         commands << [indent.align(doc.indent), mode, doc.contents] | ||
|       when Trim | ||
|         position -= output.trim! | ||
|       when Group | ||
|         if mode == MODE_FLAT && !should_remeasure | ||
|           commands << [indent, doc.break? ? MODE_BREAK : MODE_FLAT, doc.contents] | ||
|         else | ||
|           should_remeasure = false | ||
|           next_cmd = [indent, MODE_FLAT, doc.contents] | ||
|           if !doc.break? && fits?(next_cmd, commands, maxwidth - position) | ||
|             commands << next_cmd | ||
|           else | ||
|             commands << [indent, MODE_BREAK, doc.contents] | ||
|           end | ||
|         end | ||
|       when IfBreak | ||
|         if mode == MODE_BREAK | ||
|           commands << [indent, mode, doc.break_contents] if doc.break_contents | ||
|         elsif mode == MODE_FLAT | ||
|           commands << [indent, mode, doc.flat_contents] if doc.flat_contents | ||
|         end | ||
|       when LineSuffix | ||
|         line_suffixes << [indent, mode, doc.contents] | ||
|       when Breakable | ||
|         if mode == MODE_FLAT | ||
|           if doc.force? | ||
|             should_remeasure = true | ||
|           else | ||
|             output << doc.separator | ||
|             position += doc.width | ||
|             next | ||
|           end | ||
|         end | ||
|         if line_suffixes.any? | ||
|           commands << [indent, mode, doc] | ||
|           commands += line_suffixes.reverse | ||
|           line_suffixes = [] | ||
|         elsif !doc.indent? | ||
|           output << newline | ||
|           if indent.root | ||
|             output << indent.root.value | ||
|             position = indent.root.length | ||
|           else | ||
|             position = 0 | ||
|           end | ||
|         else | ||
|           position -= output.trim! | ||
|           output << newline | ||
|           output << indent.value | ||
|           position = indent.length | ||
|         end | ||
|       when BreakParent | ||
|         # do nothing | ||
|       else | ||
|         # Special case where the user has defined some way to get an extra doc | ||
|         # node that we don't explicitly support into the list. In this case | ||
|         # we're going to assume it's 0-width and just append it to the output | ||
|         # buffer. | ||
|         # | ||
|         # This is useful behavior for putting marker nodes into the list so that | ||
|         # you can know how things are getting mapped before they get printed. | ||
|         output << doc | ||
|       end | ||
|       while !@buffer.empty? && Text === @buffer.first | ||
|         text = @buffer.shift | ||
|         @output_width = text.output(@output, @output_width) | ||
|         @buffer_width -= text.width | ||
|       if commands.empty? && line_suffixes.any? | ||
|         commands += line_suffixes.reverse | ||
|         line_suffixes = [] | ||
|       end | ||
|     end | ||
|     # Reset the group stack and target array so that this pretty printer object | ||
|     # can continue to be used before calling flush again if desired. | ||
|     reset | ||
|   end | ||
|   # This adds +obj+ as a text of +width+ columns in width. | ||
|   # ---------------------------------------------------------------------------- | ||
|   # Markers node builders | ||
|   # ---------------------------------------------------------------------------- | ||
|   # This says "you can break a line here if necessary", and a +width+\-column | ||
|   # text +separator+ is inserted if a line is not broken at the point. | ||
|   # | ||
|   # If +width+ is not specified, obj.length is used. | ||
|   # If +separator+ is not specified, ' ' is used. | ||
|   # | ||
|   def text(obj, width=obj.length) | ||
|     if @buffer.empty? | ||
|       @output << obj | ||
|       @output_width += width | ||
|     else | ||
|       text = @buffer.last | ||
|       unless Text === text | ||
|         text = Text.new | ||
|         @buffer << text | ||
|       end | ||
|       text.add(obj, width) | ||
|       @buffer_width += width | ||
|       break_outmost_groups | ||
|     end | ||
|   # If +width+ is not specified, +separator.length+ is used. You will have to | ||
|   # specify this when +separator+ is a multibyte character, for example. | ||
|   # | ||
|   # By default, if the surrounding group is broken and a newline is inserted, | ||
|   # the printer will indent the subsequent line up to the current level of | ||
|   # indentation. You can disable this behavior with the +indent+ argument if | ||
|   # that's not desired (rare). | ||
|   # | ||
|   # By default, when you insert a Breakable into the print tree, it only breaks | ||
|   # the surrounding group when the group's contents cannot fit onto the | ||
|   # remaining space of the current line. You can force it to break the | ||
|   # surrounding group instead if you always want the newline with the +force+ | ||
|   # argument. | ||
|   def breakable(separator = ' ', width = separator.length, indent: true, force: false) | ||
|     doc = Breakable.new(separator, width, indent: indent, force: force) | ||
|     target << doc | ||
|     break_parent if force | ||
|     doc | ||
|   end | ||
|   # This inserts a BreakParent node into the print tree which forces the | ||
|   # surrounding and all parent group nodes to break. | ||
|   def break_parent | ||
|     doc = BreakParent.new | ||
|     target << doc | ||
|     doc | ||
|   end | ||
|   # This is similar to #breakable except | ||
|   # the decision to break or not is determined individually. | ||
|   # This is similar to #breakable except the decision to break or not is | ||
|   # determined individually. | ||
|   # | ||
|   # Two #fill_breakable under a group may cause 4 results: | ||
|   # (break,break), (break,non-break), (non-break,break), (non-break,non-break). | ||
|   # This is different to #breakable because two #breakable under a group | ||
|   # may cause 2 results: | ||
|   # (break,break), (non-break,non-break). | ||
|   # may cause 2 results: (break,break), (non-break,non-break). | ||
|   # | ||
|   # The text +sep+ is inserted if a line is not broken at this point. | ||
|   # The text +separator+ is inserted if a line is not broken at this point. | ||
|   # | ||
|   # If +sep+ is not specified, " " is used. | ||
|   # If +separator+ 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. | ||
|   # | ||
|   def fill_breakable(sep=' ', width=sep.length) | ||
|     group { breakable sep, width } | ||
|   # If +width+ is not specified, +separator.length+ is used. You will have to | ||
|   # specify this when +separator+ is a multibyte character, for example. | ||
|   def fill_breakable(separator = ' ', width = separator.length) | ||
|     group { breakable(separator, width) } | ||
|   end | ||
|   # This says "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. | ||
|   # | ||
|   def breakable(sep=' ', width=sep.length) | ||
|     group = @group_stack.last | ||
|     if group.break? | ||
|       flush | ||
|       @output << @newline | ||
|       @output << @genspace.call(@indent) | ||
|       @output_width = @indent | ||
|       @buffer_width = 0 | ||
|     else | ||
|       @buffer << Breakable.new(sep, width, self) | ||
|       @buffer_width += width | ||
|       break_outmost_groups | ||
|     end | ||
|   # This inserts a Trim node into the print tree which, when printed, will clear | ||
|   # all whitespace at the end of the output buffer. This is useful for the rare | ||
|   # case where you need to delete printed indentation and force the next node | ||
|   # to start at the beginning of the line. | ||
|   def trim | ||
|     doc = Trim.new | ||
|     target << doc | ||
|     doc | ||
|   end | ||
|   # Groups line break hints added in the block. The line break hints are all | ||
|   # to be used or not. | ||
|   # ---------------------------------------------------------------------------- | ||
|   # Container node builders | ||
|   # ---------------------------------------------------------------------------- | ||
|   # Groups line break hints added in the block. The line break hints are all to | ||
|   # be used or not. | ||
|   # | ||
|   # If +indent+ is specified, the method call is regarded as nested by | ||
|   # nest(indent) { ... }. | ||
|   # | ||
|   # If +open_obj+ is specified, <tt>text open_obj, open_width</tt> is called | ||
|   # before grouping. If +close_obj+ is specified, <tt>text close_obj, | ||
|   # close_width</tt> is called after grouping. | ||
|   # | ||
|   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) { | ||
|   # If +open_object+ is specified, <tt>text(open_object, open_width)</tt> is | ||
|   # called before grouping. If +close_object+ is specified, | ||
|   # <tt>text(close_object, close_width)</tt> is called after grouping. | ||
|   def group(indent = 0, open_object = '', close_object = '', open_width = open_object.length, close_width = close_object.length) | ||
|     text(open_object, open_width) if open_object != '' | ||
|     doc = Group.new(groups.last.depth + 1) | ||
|     groups << doc | ||
|     target << doc | ||
|     with_target(doc.contents) do | ||
|       if indent != 0 | ||
|         nest(indent) { yield } | ||
|       else | ||
|         yield | ||
|       } | ||
|     } | ||
|     text close_obj, close_width | ||
|   end | ||
|   # Takes a block and queues a new group that is indented 1 level further. | ||
|   def group_sub | ||
|     group = Group.new(@group_stack.last.depth + 1) | ||
|     @group_stack.push group | ||
|     @group_queue.enq group | ||
|     begin | ||
|       yield | ||
|     ensure | ||
|       @group_stack.pop | ||
|       if group.breakables.empty? | ||
|         @group_queue.delete group | ||
|       end | ||
|     end | ||
|     groups.pop | ||
|     text(close_object, close_width) if close_object != '' | ||
|   end | ||
|   # A small DSL-like object used for specifying the alternative contents to be | ||
|   # printed if the surrounding group doesn't break for an IfBreak node. | ||
|   class IfBreakBuilder | ||
|     attr_reader :builder, :if_break | ||
|     def initialize(builder, if_break) | ||
|       @builder = builder | ||
|       @if_break = if_break | ||
|     end | ||
|     def if_flat(&block) | ||
|       builder.with_target(if_break.flat_contents, &block) | ||
|     end | ||
|   end | ||
|   # Inserts an IfBreak node with the contents of the block being added to its | ||
|   # list of nodes that should be printed if the surrounding node breaks. If it | ||
|   # doesn't, then you can specify the contents to be printed with the #if_flat | ||
|   # method used on the return object from this method. For example, | ||
|   # | ||
|   #     q.if_break { q.text('do') }.if_flat { q.text('{') } | ||
|   # | ||
|   # In the example above, if the surrounding group is broken it will print 'do' | ||
|   # and if it is not it will print '{'. | ||
|   def if_break | ||
|     doc = IfBreak.new | ||
|     target << doc | ||
|     with_target(doc.break_contents) { yield } | ||
|     IfBreakBuilder.new(self, doc) | ||
|   end | ||
|   # Very similar to the #nest method, this indents the nested content by one | ||
|   # level by inserting an Indent node into the print tree. The contents of the | ||
|   # node are determined by the block. | ||
|   def indent | ||
|     doc = Indent.new | ||
|     target << doc | ||
|     with_target(doc.contents) { yield } | ||
|     doc | ||
|   end | ||
|   # Inserts a LineSuffix node into the print tree. The contents of the node are | ||
|   # determined by the block. | ||
|   def line_suffix | ||
|     doc = LineSuffix.new | ||
|     target << doc | ||
|     with_target(doc.contents) { yield } | ||
|     doc | ||
|   end | ||
|   # Increases left margin after newline with +indent+ for line breaks added in | ||
|   # the block. | ||
|   # | ||
|   def nest(indent) | ||
|     @indent += indent | ||
|     begin | ||
|       yield | ||
|     ensure | ||
|       @indent -= indent | ||
|     end | ||
|   end | ||
|     doc = Align.new(indent: indent) | ||
|     target << doc | ||
|   # outputs buffered data. | ||
|   # | ||
|   def flush | ||
|     @buffer.each {|data| | ||
|       @output_width = data.output(@output, @output_width) | ||
|     } | ||
|     @buffer.clear | ||
|     @buffer_width = 0 | ||
|     with_target(doc.contents) { yield } | ||
|     doc | ||
|   end | ||
|   # The Text class is the means by which to collect strings from objects. | ||
|   # This adds +object+ as a text of +width+ columns in width. | ||
|   # | ||
|   # This class is intended for internal use of the PrettyPrint buffers. | ||
|   class Text # :nodoc: | ||
|   # If +width+ is not specified, object.length is used. | ||
|   def text(object = '', width = object.length) | ||
|     doc = target.last | ||
|     # Creates a new text object. | ||
|     # | ||
|     # This constructor takes no arguments. | ||
|     # | ||
|     # The workflow is to append a PrettyPrint::Text object to the buffer, and | ||
|     # being able to call the buffer.last() to reference it. | ||
|     # | ||
|     # As there are objects, use PrettyPrint::Text#add to include the objects | ||
|     # and the width to utilized by the String version of this object. | ||
|     def initialize | ||
|       @objs = [] | ||
|       @width = 0 | ||
|     unless Text === doc | ||
|       doc = Text.new | ||
|       target << doc | ||
|     end | ||
|     # The total width of the objects included in this Text object. | ||
|     attr_reader :width | ||
|     # Render the String text of the objects that have been added to this Text object. | ||
|     # | ||
|     # Output the text to +out+, and increment the width to +output_width+ | ||
|     def output(out, output_width) | ||
|       @objs.each {|obj| out << obj} | ||
|       output_width + @width | ||
|     end | ||
|     # Include +obj+ in the objects to be pretty printed, and increment | ||
|     # this Text object's total width by +width+ | ||
|     def add(obj, width) | ||
|       @objs << obj | ||
|       @width += width | ||
|     end | ||
|     doc.add(object: object, width: width) | ||
|     doc | ||
|   end | ||
|   # The Breakable class is used for breaking up object information | ||
|   # | ||
|   # This class is intended for internal use of the PrettyPrint buffers. | ||
|   class Breakable # :nodoc: | ||
|   # ---------------------------------------------------------------------------- | ||
|   # Internal APIs | ||
|   # ---------------------------------------------------------------------------- | ||
|     # Create a new Breakable object. | ||
|     # | ||
|     # Arguments: | ||
|     # * +sep+ String of the separator | ||
|     # * +width+ Integer width of the +sep+ | ||
|     # * +q+ parent PrettyPrint object, to base from | ||
|     def initialize(sep, width, q) | ||
|       @obj = sep | ||
|       @width = width | ||
|       @pp = q | ||
|       @indent = q.indent | ||
|       @group = q.current_group | ||
|       @group.breakables.push self | ||
|     end | ||
|     # Holds the separator String | ||
|     # | ||
|     # The +sep+ argument from ::new | ||
|     attr_reader :obj | ||
|     # The width of +obj+ / +sep+ | ||
|     attr_reader :width | ||
|     # The number of spaces to indent. | ||
|     # | ||
|     # This is inferred from +q+ within PrettyPrint, passed in ::new | ||
|     attr_reader :indent | ||
|     # Render the String text of the objects that have been added to this | ||
|     # Breakable object. | ||
|     # | ||
|     # Output the text to +out+, and increment the width to +output_width+ | ||
|     def output(out, output_width) | ||
|       @group.breakables.shift | ||
|       if @group.break? | ||
|         out << @pp.newline | ||
|         out << @pp.genspace.call(@indent) | ||
|         @indent | ||
|       else | ||
|         @pp.group_queue.delete @group if @group.breakables.empty? | ||
|         out << @obj | ||
|         output_width + @width | ||
|       end | ||
|     end | ||
|   # A convenience method used by a lot of the print tree node builders that | ||
|   # temporarily changes the target that the builders will append to. | ||
|   def with_target(target) | ||
|     previous_target, @target = @target, target | ||
|     yield | ||
|     @target = previous_target | ||
|   end | ||
|   # The Group class is used for making indentation easier. | ||
|   # | ||
|   # While this class does neither the breaking into newlines nor indentation, | ||
|   # it is used in a stack (as well as a queue) within PrettyPrint, to group | ||
|   # objects. | ||
|   # | ||
|   # For information on using groups, see PrettyPrint#group | ||
|   # | ||
|   # This class is intended for internal use of the PrettyPrint buffers. | ||
|   class Group # :nodoc: | ||
|     # Create a Group object | ||
|     # | ||
|     # Arguments: | ||
|     # * +depth+ - this group's relation to previous groups | ||
|     def initialize(depth) | ||
|       @depth = depth | ||
|       @breakables = [] | ||
|       @break = false | ||
|     end | ||
|     # This group's relation to previous groups | ||
|     attr_reader :depth | ||
|     # Array to hold the Breakable objects for this Group | ||
|     attr_reader :breakables | ||
|     # Makes a break for this Group, and returns true | ||
|     def break | ||
|       @break = true | ||
|     end | ||
|     # Boolean of whether this Group has made a break | ||
|     def break? | ||
|       @break | ||
|     end | ||
|     # Boolean of whether this Group has been queried for being first | ||
|     # | ||
|     # This is used as a predicate, and ought to be called first. | ||
|     def first? | ||
|       if defined? @first | ||
|         false | ||
|       else | ||
|         @first = false | ||
|         true | ||
|   private | ||
|   # This method returns a boolean as to whether or not the remaining commands | ||
|   # fit onto the remaining space on the current line. If we finish printing | ||
|   # all of the commands or if we hit a newline, then we return true. Otherwise | ||
|   # if we continue printing past the remaining space, we return false. | ||
|   def fits?(next_command, rest_commands, remaining) | ||
|     # This is the index in the remaining commands that we've handled so far. | ||
|     # We reverse through the commands and add them to the stack if we've run | ||
|     # out of nodes to handle. | ||
|     rest_index = rest_commands.length | ||
|     # This is our stack of commands, very similar to the commands list in the | ||
|     # print method. | ||
|     commands = [next_command] | ||
|     # This is our output buffer, really only necessary to keep track of | ||
|     # because we could encounter a Trim doc node that would actually add | ||
|     # remaining space. | ||
|     buffer = output.class.new | ||
|     while remaining >= 0 | ||
|       if commands.empty? | ||
|         return true if rest_index == 0 | ||
|         rest_index -= 1 | ||
|         commands << rest_commands[rest_index] | ||
|         next | ||
|       end | ||
|     end | ||
|   end | ||
|   # The GroupQueue class is used for managing the queue of Group to be pretty | ||
|   # printed. | ||
|   # | ||
|   # This queue groups the Group objects, based on their depth. | ||
|   # | ||
|   # This class is intended for internal use of the PrettyPrint buffers. | ||
|   class GroupQueue # :nodoc: | ||
|     # Create a GroupQueue object | ||
|     # | ||
|     # Arguments: | ||
|     # * +groups+ - one or more PrettyPrint::Group objects | ||
|     def initialize(*groups) | ||
|       @queue = [] | ||
|       groups.each {|g| enq g} | ||
|     end | ||
|     # Enqueue +group+ | ||
|     # | ||
|     # This does not strictly append the group to the end of the queue, | ||
|     # but instead adds it in line, base on the +group.depth+ | ||
|     def enq(group) | ||
|       depth = group.depth | ||
|       @queue << [] until depth < @queue.length | ||
|       @queue[depth] << group | ||
|     end | ||
|     # Returns the outer group of the queue | ||
|     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 | ||
|       indent, mode, doc = commands.pop | ||
|       case doc | ||
|       when Text | ||
|         doc.objects.each { |object| buffer << object } | ||
|         remaining -= doc.width | ||
|       when Array | ||
|         doc.reverse_each { |part| commands << [indent, mode, part] } | ||
|       when Indent | ||
|         commands << [indent.indent, mode, doc.contents] | ||
|       when Align | ||
|         commands << [indent.align(doc.indent), mode, doc.contents] | ||
|       when Trim | ||
|         remaining += buffer.trim! | ||
|       when Group | ||
|         commands << [indent, doc.break? ? MODE_BREAK : mode, doc.contents] | ||
|       when IfBreak | ||
|         if mode == MODE_BREAK | ||
|           commands << [indent, mode, doc.break_contents] if doc.break_contents | ||
|         else | ||
|           commands << [indent, mode, doc.flat_contents] if doc.flat_contents | ||
|         end | ||
|       when Breakable | ||
|         if mode == MODE_FLAT | ||
|           if !doc.force? | ||
|             buffer << doc.separator | ||
|             remaining -= doc.width | ||
|             next | ||
|           end | ||
|         } | ||
|         gs.each {|group| group.break} | ||
|         gs.clear | ||
|       } | ||
|       return nil | ||
|     end | ||
|         end | ||
|     # Remote +group+ from this queue | ||
|     def delete(group) | ||
|       @queue[group.depth].delete(group) | ||
|         return true | ||
|       end | ||
|     end | ||
|     false | ||
|   end | ||
|   # PrettyPrint::SingleLine is used by PrettyPrint.singleline_format | ||
|   # | ||
|   # It is passed to be similar to a PrettyPrint object itself, by responding to: | ||
|   # * #text | ||
|   # * #breakable | ||
|   # * #nest | ||
|   # * #group | ||
|   # * #flush | ||
|   # * #first? | ||
|   # | ||
|   # but instead, the output has no line breaks | ||
|   # | ||
|   class SingleLine | ||
|     # Create a PrettyPrint::SingleLine object | ||
|     # | ||
|     # Arguments: | ||
|     # * +output+ - String (or similar) to store rendered text. Needs to respond to '<<' | ||
|     # * +maxwidth+ - Argument position expected to be here for compatibility. | ||
|     #                This argument is a noop. | ||
|     # * +newline+ - Argument position expected to be here for compatibility. | ||
|     #               This argument is a noop. | ||
|     def initialize(output, maxwidth=nil, newline=nil) | ||
|       @output = output | ||
|       @first = [true] | ||
|     end | ||
|     # Add +obj+ to the text to be output. | ||
|     # | ||
|     # +width+ argument is here for compatibility. It is a noop argument. | ||
|     def text(obj, width=nil) | ||
|       @output << obj | ||
|     end | ||
|     # Appends +sep+ to the text to be output. By default +sep+ is ' ' | ||
|     # | ||
|     # +width+ argument is here for compatibility. It is a noop argument. | ||
|     def breakable(sep=' ', width=nil) | ||
|       @output << sep | ||
|     end | ||
|     # Takes +indent+ arg, but does nothing with it. | ||
|     # | ||
|     # Yields to a block. | ||
|     def nest(indent) # :nodoc: | ||
|       yield | ||
|     end | ||
|     # Opens a block for grouping objects to be pretty printed. | ||
|     # | ||
|     # Arguments: | ||
|     # * +indent+ - noop argument. Present for compatibility. | ||
|     # * +open_obj+ - text appended before the &blok. Default is '' | ||
|     # * +close_obj+ - text appended after the &blok. Default is '' | ||
|     # * +open_width+ - noop argument. Present for compatibility. | ||
|     # * +close_width+ - noop argument. Present for compatibility. | ||
|     def group(indent=nil, open_obj='', close_obj='', open_width=nil, close_width=nil) | ||
|       @first.push true | ||
|       @output << open_obj | ||
|       yield | ||
|       @output << close_obj | ||
|       @first.pop | ||
|     end | ||
|     # Method present for compatibility, but is a noop | ||
|     def flush # :nodoc: | ||
|     end | ||
|     # This is used as a predicate, and ought to be called first. | ||
|     def first? | ||
|       result = @first[-1] | ||