类 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,应该是一个 Integer

newline[R]

添加到 output 中用于添加新行的值。

默认值为 “\n”,应该是一个 String

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 lib/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”。

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

# File lib/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 lib/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 lib/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 lib/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 lib/prettyprint.rb, line 157
def current_group
  @group_stack.last
end
fill_breakable(sep=' ', width=sep.length) 点击切换源代码

这类似于breakable,只是决定是否换行是单独决定的。

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

如果在此处未换行,则插入文本sep

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

如果未指定width,则使用sep.length。例如,当sep是多字节字符时,您将需要指定它。

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

输出缓冲数据。

# File lib/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 lib/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() { || ... } 点击切换源代码

接受一个块,并排队一个新的组,该组的缩进级别比当前级别高一级。

# File lib/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 lib/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 lib/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