PrettyPrint

PrettyPrint

PrettyPrint는 예쁜 프린트 알고리즘을 구현한 클래스예요. 그룹화된 구조에 줄바꿈 지점과 좋은 들여쓰기를 찾아내서, 복잡한 데이터를 읽기 좋게 펼쳐 줘요.

출처: Ruby 4.0 API

본문

기본적으로 이 클래스는 원시 요소가 문자열이고, 문자열의 각 바이트가 너비에서 한 칼럼을 차지한다고 가정해요. 하지만 몇몇 메서드에 적절한 인자를 주면 다른 상황에도 쓸 수 있어요.

  • PrettyPrint.new에 대한 newline 객체와 공백 생성 블록
  • PrettyPrint#text의 선택적 width 인자
  • PrettyPrint#breakable

그래서 이런 용도로도 활용할 수 있어요.

  • 비례 폰트(proportional font)를 이용한 텍스트 포맷팅
  • 바이트 수와 칼럼 수가 다른 멀티바이트 문자
  • 문자열이 아닌 대상의 포맷팅

Bugs

  • Box 기반 포매팅?
  • 다른(더 나은) 모델/알고리즘?

버그는 bugs.ruby-lang.org에 보고하세요.

References

  • Christian Lindig, Strictly Pretty, March 2000, lindig.github.io/papers/strictly-pretty-2000.pdf
  • Philip Wadler, A prettier printer, March 1998, homepages.inf.ed.ac.uk/wadler/topics/language-design.html#prettier

Author

Tanaka Akira [email protected]

Constants

VERSION

버전 문자열

Attributes

정수 하나를 인자로 받아서 그에 해당하는 공백 수를 돌려주는 lambdaProc. 기본값은 이렇게 생겼어요.

lambda {|n| ' ' * n}

예쁘게 출력할 스택 위의 그룹들의 PrettyPrint::GroupQueue

들여쓸 공백의 수

한 줄이 줄바꿈으로 나뉘기 전까지의 최대 너비. 기본값은 79이고 Integer여야 해요.

출력에 줄바꿈을 추가할 때 덧붙이는 값. 기본값은 "\n"이고 String이어야 해요.

출력 객체. 기본값은 ""이고 << 메서드를 받을 수 있어야 해요.

Public Class Methods

format(output=''.dup, maxwidth=79, newline="\n", genspace=lambda {|n| ' ' * n}) { |q| ... }

다음과 같은 코드와 동일한 편의 메서드예요.

begin
  q = PrettyPrint.new(output, maxwidth, newline, &genspace)
  ...
  q.flush
  output
end

new(output=''.dup, maxwidth=79, newline="\n", &genspace)

예쁜 프린트용 버퍼를 만들어요. output은 출력 대상이에요. 지정하지 않으면 ""을 가정해요. outputPrettyPrint#text의 첫 인자 obj, PrettyPrint#breakable의 첫 인자 sep, PrettyPrint.new의 첫 인자 newline, 그리고 PrettyPrint.new에 주어진 블록의 결과를 받아들이는 << 메서드를 가져야 해요.

maxwidth는 최대 줄 길이를 지정해요. 지정하지 않으면 79를 가정해요. 다만 긴 non-breakable 텍스트가 주어지면 실제 출력이 maxwidth를 넘을 수 있어요.

newline은 줄바꿈에 사용돼요. 지정하지 않으면 "\n"을 사용해요.

블록은 공백을 생성하는 데 사용돼요. 주어지지 않으면 {|width| ' ' * width}를 사용해요.

singleline_format(output=''.dup, maxwidth=nil, newline=nil, genspace=nil) { |q| ... }

PrettyPrint::format과 비슷하지만 결과에 줄바꿈(break)이 없어요. maxwidth, newline, genspace는 무시돼요. 블록 안에서 breakable을 호출해도 줄이 끊기지 않고, 그냥 text를 호출한 것처럼 취급돼요.

Public Instance Methods

break_outmost_groups()

maxwidth보다 짧은 줄들로 버퍼를 나눠요.

breakable(sep=' ', width=sep.length)

"필요하면 여기서 줄을 끊어도 된다"는 뜻이에요. 그 지점에서 줄이 끊기지 않으면 width 칼럼 너비의 텍스트 sep이 삽입돼요. sep을 지정하지 않으면 " "를 사용해요. width를 지정하지 않으면 sep.length를 사용해요. sep이 멀티바이트 문자라면 width를 꼭 지정해야 해요.

current_group()

스택에 가장 최근에 추가된 그룹을 돌려줘요. 억지로 지어낸(shoehorned) 예시를 보면 동작을 더 쉽게 알 수 있어요.

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>

fill_breakable(sep=' ', width=sep.length)

breakable과 비슷하지만 끊을지 말지를 각각 따로(individually) 결정해요. 한 그룹 안의 fill_breakable 두 개는 (break,break), (break,non-break), (non-break,break), (non-break,non-break)의 4가지 결과를 만들 수 있어요. 이는 breakable과 다르죠. breakable 두 개는 (break,break)와 (non-break,non-break)의 2가지 결과만 만드니까요.

이 지점에서 줄이 끊기지 않으면 텍스트 sep이 삽입돼요. sep을 지정하지 않으면 " "를 사용해요. width를 지정하지 않으면 sep.length를 사용해요. sep이 멀티바이트 문자라면 width를 지정해야 해요.

flush()

버퍼링된 데이터를 출력해요.

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)가 호출돼요.

group_sub() { || ... }

블록을 받아 1단계 더 들여쓴 새 그룹을 큐에 넣어요.

nest(indent) { || ... }

블록 안에서 추가된 줄바꿈에 대해 줄바꿈 이후의 왼쪽 여백을 indent만큼 늘려요.

text(obj, width=obj.length)

obj를 너비 width 칼럼의 텍스트로 추가해요. width를 지정하지 않으면 obj.length를 사용해요.