class PrettyPrint

这个类实现了一种美观打印算法。它可以为分组结构查找换行符和合适的缩进。

默认情况下,此类假定基本元素是字符串,并且字符串中的每个字节都具有单列宽度。但是,通过为某些方法提供合适的参数,它可以用于其他情况

有几个可能的用途

  • 使用比例字体进行文本格式化

  • 具有与字节数不同的列数的多字节字符

  • 非字符串格式化

错误

  • 基于盒子的格式化?

  • 其他(更好)的模型/算法?

请在 bugs.ruby-lang.org 上报告任何错误

参考

Christian Lindig, Strictly Pretty, 2000 年 3 月, lindig.github.io/papers/strictly-pretty-2000.pdf

Philip Wadler, A prettier printer, 1998 年 3 月, homepages.inf.ed.ac.uk/wadler/topics/language-design.html#prettier

作者

Tanaka Akira <[email protected]>

常量

VERSION

属性

genspace[R]

一个 lambda 或 Proc,它接受一个 Integer 类型的参数,并返回相应数量的空格。

默认情况下,它是

lambda {|n| ' ' * n}
group_queue[R]

PrettyPrint::GroupQueue 中待美观打印的组的堆栈

indent[R]

要缩进的空格数

maxwidth[R]

一行在被分割成换行符之前的最大宽度

此默认值为 79,应为整数

newline[R]

追加到 output 以添加新行的值。

此默认值为 “\n”,应为字符串

output[R]

输出对象。

此默认值为 '',应接受 << 方法

公共类方法

format(output=''.dup, maxwidth=79, newline="\n", genspace=lambda {|n| ' ' * n}) { |q| ... } 点击以切换源代码

这是一个便捷方法,与以下方法相同

begin
  q = PrettyPrint.new(output, maxwidth, newline, &genspace)
  ...
  q.flush
  output
end
# File prettyprint.rb, line 47
def PrettyPrint.format(output=''.dup, maxwidth=79, newline="\n", genspace=lambda {|n| ' ' * n})
  q = PrettyPrint.new(output, maxwidth, newline, &genspace)
  yield q
  q.flush
  output
end
new(output=''.dup, maxwidth=79, newline="\n", &genspace) 点击以切换源代码

创建一个用于美观打印的缓冲区。

output 是一个输出目标。如果未指定,则假定为 ''。它应该具有一个 << 方法,该方法接受 PrettyPrint#text 的第一个参数 objPrettyPrint#breakable 的第一个参数 sepPrettyPrint.new 的第一个参数 newline,以及为 PrettyPrint.new 给定块的结果。

maxwidth 指定最大行长。如果未指定,则假定为 79。但是,如果提供了长的不间断文本,则实际输出可能会超出 maxwidth

newline 用于换行。“\n” 如果未指定,则使用 “\n”。

该块用于生成空格。如果没有给出,则使用 {|width| ‘ ’ * width}。

# File prettyprint.rb, line 84
def initialize(output=''.dup, maxwidth=79, newline="\n", &genspace)
  @output = 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
end
singleline_format(output=''.dup, maxwidth=nil, newline=nil, genspace=nil) { |q| ... } 点击以切换源代码

这与 PrettyPrint::format 类似,但结果没有中断。

maxwidthnewlinegenspace 被忽略。

块中 breakable 的调用不会换行,并且被视为仅调用 text

# File prettyprint.rb, line 61
def PrettyPrint.singleline_format(output=''.dup, maxwidth=nil, newline=nil, genspace=nil)
  q = SingleLine.new(output)
  yield q
  output
end

公共实例方法

break_outmost_groups() 点击以切换源代码

将缓冲区分解为短于 maxwidth 的行

# File prettyprint.rb, line 162
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
    end
    while !@buffer.empty? && Text === @buffer.first
      text = @buffer.shift
      @output_width = text.output(@output, @output_width)
      @buffer_width -= text.width
    end
  end
end
breakable(sep=' ', width=sep.length) 点击以切换源代码

这表示“如果需要,您可以在此处换行”,如果该点未换行,则会插入一个 width 列的文本 sep

如果未指定 sep,则使用 “ ”。

如果未指定 width,则使用 sep.length。例如,当 sep 是一个多字节字符时,您将必须指定此项。

# File prettyprint.rb, line 226
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
end
current_group() 点击以切换源代码

返回最近添加到堆栈的组。

人为示例

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>
q.group {
  q.text q.current_group.inspect
  q.text q.newline
  q.group(q.current_group.depth + 1) {
    q.text q.current_group.inspect
    q.text q.newline
    q.group(q.current_group.depth + 1) {
      q.text q.current_group.inspect
      q.text q.newline
      q.group(q.current_group.depth + 1) {
        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>
# File prettyprint.rb, line 157
def current_group
  @group_stack.last
end
fill_breakable(sep=' ', width=sep.length) 点击以切换源代码

这与 breakable 类似,不同之处在于是否中断的决定是单独确定的。

一个组下的两个 fill_breakable 可能会导致 4 个结果:(break,break)、(break,non-break)、(non-break,break)、(non-break,non-break)。这与 breakable 不同,因为一个组下的两个 breakable 可能会导致 2 个结果:(break,break)、(non-break,non-break)。

如果此点未换行,则会插入文本 sep

如果未指定 sep,则使用 “ ”。

如果未指定 width,则使用 sep.length。例如,当 sep 是一个多字节字符时,您将必须指定此项。

# File prettyprint.rb, line 214
def fill_breakable(sep=' ', width=sep.length)
  group { breakable sep, width }
end
flush() 点击以切换源代码

输出缓冲数据。

# File prettyprint.rb, line 290
def flush
  @buffer.each {|data|
    @output_width = data.output(@output, @output_width)
  }
  @buffer.clear
  @buffer_width = 0
end
group(indent=0, open_obj='', close_obj='', open_width=open_obj.length, close_width=close_obj.length) { || ... } 点击以切换源代码

对块中添加的换行提示进行分组。所有换行提示都将被使用或不使用。

如果指定了 indent,则该方法调用被视为由 nest(indent) { ... } 嵌套。

如果指定了 open_obj,则在分组之前调用 text open_obj, open_width。如果指定了 close_obj,则在分组后调用 text close_obj, close_width

# File prettyprint.rb, line 251
def group(indent=0, open_obj='', close_obj='', open_width=open_obj.length, close_width=close_obj.length)
  text open_obj, open_width
  group_sub {
    nest(indent) {
      yield
    }
  }
  text close_obj, close_width
end
group_sub() { || ... } 点击以切换源代码

接受一个块,并排队一个新的组,该组的缩进级别进一步增加 1。

# File prettyprint.rb, line 262
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
end
nest(indent) { || ... } 点击以切换源代码

在块中添加的换行符的换行符后,左边距增加 indent

# File prettyprint.rb, line 279
def nest(indent)
  @indent += indent
  begin
    yield
  ensure
    @indent -= indent
  end
end
text(obj, width=obj.length) 点击以切换源代码

这会添加 obj 作为 width 列宽度的文本。

如果未指定 width,则使用 obj.length。

# File prettyprint.rb, line 182
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
end