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
정수 하나를 인자로 받아서 그에 해당하는 공백 수를 돌려주는 lambda나 Proc. 기본값은 이렇게 생겼어요.
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은 출력 대상이에요. 지정하지 않으면 ""을 가정해요. output은 PrettyPrint#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를 사용해요.