класс PrettyPrint
Этот класс реализует алгоритм красивого форматирования. Он подбирает переносы строк и отступы для группированных структур.
По умолчанию класс предполагает, что примитивные элементы являются строками и каждый байт в строках занимает одну колонку. Однако его можно использовать и в других случаях, передав подходящие аргументы некоторым методам:
-
объект переноса строки и блок генерации пробелов для
PrettyPrint.new -
необязательный аргумент ширины для
PrettyPrint#text
Есть несколько вариантов применения:
-
форматирование текста с использованием пропорциональных шрифтов
-
многобайтовые символы, ширина которых в колонках отличается от количества байтов
-
форматирование нетекстовых данных
Ошибки
-
Форматирование на основе блоков?
-
Другая (лучшая) модель/алгоритм?
Сообщайте об ошибках на сайте bugs.ruby-lang.org
Ссылки
Christian Lindig, Strictly Pretty, март 2000 г., lindig.github.io/papers/strictly-pretty-2000.pdf
Philip Wadler, A prettier printer, март 1998 г., homepages.inf.ed.ac.uk/wadler/topics/language-design.html#prettier
Автор
Tanaka Akira <akr@fsij.org>
Константы
- VERSION
Атрибуты
Очередь PrettyPrint::GroupQueue с группами в стеке, подлежащими красивому форматированию
Количество пробелов в отступе
Максимальная ширина строки, после достижения которой она переносится на новую строку
Значение по умолчанию — 79; тип значения — Integer
Значение, добавляемое к output для перехода на новую строку.
По умолчанию используется «n»; тип значения — String
Объект вывода.
По умолчанию используется ”; объект должен поддерживать метод <<
Общедоступные методы класса
# 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 Это вспомогательный метод, эквивалентный следующему:
begin q = PrettyPrint.new(output, maxwidth, newline, &genspace) ... q.flush output end
# 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 Создаёт буфер для красивого форматирования.
output — целевой объект вывода. Если он не указан, предполагается ”. У объекта должен быть метод <<, принимающий первый аргумент obj метода PrettyPrint#text, первый аргумент sep метода PrettyPrint#breakable, первый аргумент newline метода PrettyPrint.new и результат заданного блока для PrettyPrint.new.
maxwidth задаёт максимальную длину строки. Если значение не указано, предполагается 79. Однако фактический вывод может превысить maxwidth, если передан длинный текст без возможности переноса.
newline используется для переноса строк. Если значение не указано, используется «n».
Блок используется для генерации пробелов. Если он не задан, используется {|width| ‘ ’ * width}.
# 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
Этот метод похож на PrettyPrint::format, но результат не содержит переносов.
maxwidth, newline и genspace игнорируются.
Вызов breakable в блоке не переносит строку и обрабатывается как обычный вызов text.
Общедоступные методы экземпляра
# 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 Разбивает буфер на строки, длина которых не превышает maxwidth
# 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 Этот метод указывает, что «при необходимости здесь можно перенести строку»; если в этом месте строка не переносится, вставляется текст sep шириной в width колонок.
Если sep не указан, используется « ».
Если width не указан, используется sep.length. Например, его нужно задать, если sep — многобайтовый символ.
# File lib/prettyprint.rb, line 157 def current_group @group_stack.last end
Возвращает группу, добавленную в стек последней.
Пример для иллюстрации:
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 214
def fill_breakable(sep=' ', width=sep.length)
group { breakable sep, width }
end Этот метод похож на breakable, но решение о переносе принимается отдельно для каждого случая.
Два вызова fill_breakable внутри группы могут дать 4 результата: (перенос, перенос), (перенос, без переноса), (без переноса, перенос), (без переноса, без переноса). Это отличается от breakable, поскольку два вызова breakable внутри группы могут дать 2 результата: (перенос, перенос), (без переноса, без переноса).
Если в этом месте строка не переносится, вставляется текст sep.
Если sep не указан, используется « ».
Если width не указан, используется sep.length. Например, его нужно задать, если sep — многобайтовый символ.
# File lib/prettyprint.rb, line 290
def flush
@buffer.each {|data|
@output_width = data.output(@output, @output_width)
}
@buffer.clear
@buffer_width = 0
end Выводит буферизованные данные.
# 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 Группирует подсказки о переносах строк, добавленные в блоке. Все подсказки о переносе либо используются, либо нет.
Если указан indent, вызов метода рассматривается как вложенный вызов nest(indent) { … }.
Если указан open_obj, перед группировкой вызывается text open_obj, open_width. Если указан close_obj, после группировки вызывается text close_obj, close_width.
# 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 Принимает блок и ставит в очередь новую группу с отступом на один уровень больше.
# File lib/prettyprint.rb, line 279
def nest(indent)
@indent += indent
begin
yield
ensure
@indent -= indent
end
end Увеличивает левое поле после переноса строки на indent для переносов, добавленных в блоке.
# 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 Добавляет obj как текст шириной width колонок.
Если width не указан, используется obj.length.
Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.