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]
|