syntax_tree 2.4.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -1
- data/Gemfile +0 -2
- data/Gemfile.lock +5 -3
- data/lib/syntax_tree/formatter.rb +1 -1
- data/lib/syntax_tree/node.rb +12 -92
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree/visitor/match_visitor.rb +2 -2
- data/lib/syntax_tree.rb +3 -27
- data/syntax_tree.gemspec +3 -0
- metadata +31 -4
- data/lib/syntax_tree/prettyprint.rb +0 -1159
@@ -1,1159 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
#
|
3
|
-
# This class implements a pretty printing algorithm. It finds line breaks and
|
4
|
-
# nice indentations for grouped structure.
|
5
|
-
#
|
6
|
-
# By default, the class assumes that primitive elements are strings and each
|
7
|
-
# byte in the strings is a single column in width. But it can be used for other
|
8
|
-
# situations by giving suitable arguments for some methods:
|
9
|
-
#
|
10
|
-
# * newline object and space generation block for PrettyPrint.new
|
11
|
-
# * optional width argument for PrettyPrint#text
|
12
|
-
# * PrettyPrint#breakable
|
13
|
-
#
|
14
|
-
# There are several candidate uses:
|
15
|
-
# * text formatting using proportional fonts
|
16
|
-
# * multibyte characters which has columns different to number of bytes
|
17
|
-
# * non-string formatting
|
18
|
-
#
|
19
|
-
# == Usage
|
20
|
-
#
|
21
|
-
# To use this module, you will need to generate a tree of print nodes that
|
22
|
-
# represent indentation and newline behavior before it gets sent to the printer.
|
23
|
-
# Each node has different semantics, depending on the desired output.
|
24
|
-
#
|
25
|
-
# The most basic node is a Text node. This represents plain text content that
|
26
|
-
# cannot be broken up even if it doesn't fit on one line. You would create one
|
27
|
-
# of those with the text method, as in:
|
28
|
-
#
|
29
|
-
# PrettyPrint.format { |q| q.text('my content') }
|
30
|
-
#
|
31
|
-
# No matter what the desired output width is, the output for the snippet above
|
32
|
-
# will always be the same.
|
33
|
-
#
|
34
|
-
# If you want to allow the printer to break up the content on the space
|
35
|
-
# character when there isn't enough width for the full string on the same line,
|
36
|
-
# you can use the Breakable and Group nodes. For example:
|
37
|
-
#
|
38
|
-
# PrettyPrint.format do |q|
|
39
|
-
# q.group do
|
40
|
-
# q.text('my')
|
41
|
-
# q.breakable
|
42
|
-
# q.text('content')
|
43
|
-
# end
|
44
|
-
# end
|
45
|
-
#
|
46
|
-
# Now, if everything fits on one line (depending on the maximum width specified)
|
47
|
-
# then it will be the same output as the first example. If, however, there is
|
48
|
-
# not enough room on the line, then you will get two lines of output, one for
|
49
|
-
# the first string and one for the second.
|
50
|
-
#
|
51
|
-
# There are other nodes for the print tree as well, described in the
|
52
|
-
# documentation below. They control alignment, indentation, conditional
|
53
|
-
# formatting, and more.
|
54
|
-
#
|
55
|
-
# == Bugs
|
56
|
-
# * Box based formatting?
|
57
|
-
#
|
58
|
-
# Report any bugs at http://bugs.ruby-lang.org
|
59
|
-
#
|
60
|
-
# == References
|
61
|
-
# Christian Lindig, Strictly Pretty, March 2000,
|
62
|
-
# https://lindig.github.io/papers/strictly-pretty-2000.pdf
|
63
|
-
#
|
64
|
-
# Philip Wadler, A prettier printer, March 1998,
|
65
|
-
# https://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
|
66
|
-
#
|
67
|
-
# == Author
|
68
|
-
# Tanaka Akira <akr@fsij.org>
|
69
|
-
#
|
70
|
-
class PrettyPrint
|
71
|
-
# A node in the print tree that represents aligning nested nodes to a certain
|
72
|
-
# prefix width or string.
|
73
|
-
class Align
|
74
|
-
attr_reader :indent, :contents
|
75
|
-
|
76
|
-
def initialize(indent:, contents: [])
|
77
|
-
@indent = indent
|
78
|
-
@contents = contents
|
79
|
-
end
|
80
|
-
|
81
|
-
def pretty_print(q)
|
82
|
-
q.group(2, "align#{indent}([", "])") do
|
83
|
-
q.seplist(contents) { |content| q.pp(content) }
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
# A node in the print tree that represents a place in the buffer that the
|
89
|
-
# content can be broken onto multiple lines.
|
90
|
-
class Breakable
|
91
|
-
attr_reader :separator, :width
|
92
|
-
|
93
|
-
def initialize(
|
94
|
-
separator = " ",
|
95
|
-
width = separator.length,
|
96
|
-
force: false,
|
97
|
-
indent: true
|
98
|
-
)
|
99
|
-
@separator = separator
|
100
|
-
@width = width
|
101
|
-
@force = force
|
102
|
-
@indent = indent
|
103
|
-
end
|
104
|
-
|
105
|
-
def force?
|
106
|
-
@force
|
107
|
-
end
|
108
|
-
|
109
|
-
def indent?
|
110
|
-
@indent
|
111
|
-
end
|
112
|
-
|
113
|
-
def pretty_print(q)
|
114
|
-
q.text("breakable")
|
115
|
-
|
116
|
-
attributes = [
|
117
|
-
("force=true" if force?),
|
118
|
-
("indent=false" unless indent?)
|
119
|
-
].compact
|
120
|
-
|
121
|
-
if attributes.any?
|
122
|
-
q.text("(")
|
123
|
-
q.seplist(attributes, -> { q.text(", ") }) do |attribute|
|
124
|
-
q.text(attribute)
|
125
|
-
end
|
126
|
-
q.text(")")
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
# A node in the print tree that forces the surrounding group to print out in
|
132
|
-
# the "break" mode as opposed to the "flat" mode. Useful for when you need to
|
133
|
-
# force a newline into a group.
|
134
|
-
class BreakParent
|
135
|
-
def pretty_print(q)
|
136
|
-
q.text("break-parent")
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# A node in the print tree that represents a group of items which the printer
|
141
|
-
# should try to fit onto one line. This is the basic command to tell the
|
142
|
-
# printer when to break. Groups are usually nested, and the printer will try
|
143
|
-
# to fit everything on one line, but if it doesn't fit it will break the
|
144
|
-
# outermost group first and try again. It will continue breaking groups until
|
145
|
-
# everything fits (or there are no more groups to break).
|
146
|
-
class Group
|
147
|
-
attr_reader :depth, :contents
|
148
|
-
|
149
|
-
def initialize(depth, contents: [])
|
150
|
-
@depth = depth
|
151
|
-
@contents = contents
|
152
|
-
@break = false
|
153
|
-
end
|
154
|
-
|
155
|
-
def break
|
156
|
-
@break = true
|
157
|
-
end
|
158
|
-
|
159
|
-
def break?
|
160
|
-
@break
|
161
|
-
end
|
162
|
-
|
163
|
-
def pretty_print(q)
|
164
|
-
q.group(2, break? ? "breakGroup([" : "group([", "])") do
|
165
|
-
q.seplist(contents) { |content| q.pp(content) }
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# A node in the print tree that represents printing one thing if the
|
171
|
-
# surrounding group node is broken and another thing if the surrounding group
|
172
|
-
# node is flat.
|
173
|
-
class IfBreak
|
174
|
-
attr_reader :break_contents, :flat_contents
|
175
|
-
|
176
|
-
def initialize(break_contents: [], flat_contents: [])
|
177
|
-
@break_contents = break_contents
|
178
|
-
@flat_contents = flat_contents
|
179
|
-
end
|
180
|
-
|
181
|
-
def pretty_print(q)
|
182
|
-
q.group(2, "if-break(", ")") do
|
183
|
-
q.breakable("")
|
184
|
-
q.group(2, "[", "],") do
|
185
|
-
q.seplist(break_contents) { |content| q.pp(content) }
|
186
|
-
end
|
187
|
-
q.breakable
|
188
|
-
q.group(2, "[", "]") do
|
189
|
-
q.seplist(flat_contents) { |content| q.pp(content) }
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
# A node in the print tree that is a variant of the Align node that indents
|
196
|
-
# its contents by one level.
|
197
|
-
class Indent
|
198
|
-
attr_reader :contents
|
199
|
-
|
200
|
-
def initialize(contents: [])
|
201
|
-
@contents = contents
|
202
|
-
end
|
203
|
-
|
204
|
-
def pretty_print(q)
|
205
|
-
q.group(2, "indent([", "])") do
|
206
|
-
q.seplist(contents) { |content| q.pp(content) }
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
# A node in the print tree that has its own special buffer for implementing
|
212
|
-
# content that should flush before any newline.
|
213
|
-
#
|
214
|
-
# Useful for implementating trailing content, as it's not always practical to
|
215
|
-
# constantly check where the line ends to avoid accidentally printing some
|
216
|
-
# content after a line suffix node.
|
217
|
-
class LineSuffix
|
218
|
-
DEFAULT_PRIORITY = 1
|
219
|
-
|
220
|
-
attr_reader :priority, :contents
|
221
|
-
|
222
|
-
def initialize(priority: DEFAULT_PRIORITY, contents: [])
|
223
|
-
@priority = priority
|
224
|
-
@contents = contents
|
225
|
-
end
|
226
|
-
|
227
|
-
def pretty_print(q)
|
228
|
-
q.group(2, "line-suffix([", "])") do
|
229
|
-
q.seplist(contents) { |content| q.pp(content) }
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
# A node in the print tree that represents plain content that cannot be broken
|
235
|
-
# up (by default this assumes strings, but it can really be anything).
|
236
|
-
class Text
|
237
|
-
attr_reader :objects, :width
|
238
|
-
|
239
|
-
def initialize
|
240
|
-
@objects = []
|
241
|
-
@width = 0
|
242
|
-
end
|
243
|
-
|
244
|
-
def add(object: "", width: object.length)
|
245
|
-
@objects << object
|
246
|
-
@width += width
|
247
|
-
end
|
248
|
-
|
249
|
-
def pretty_print(q)
|
250
|
-
q.group(2, "text([", "])") do
|
251
|
-
q.seplist(objects) { |object| q.pp(object) }
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
# A node in the print tree that represents trimming all of the indentation of
|
257
|
-
# the current line, in the rare case that you need to ignore the indentation
|
258
|
-
# that you've already created. This node should be placed after a Breakable.
|
259
|
-
class Trim
|
260
|
-
def pretty_print(q)
|
261
|
-
q.text("trim")
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
# When building up the contents in the output buffer, it's convenient to be
|
266
|
-
# able to trim trailing whitespace before newlines. If the output object is a
|
267
|
-
# string or array or strings, then we can do this with some gsub calls. If
|
268
|
-
# not, then this effectively just wraps the output object and forwards on
|
269
|
-
# calls to <<.
|
270
|
-
module Buffer
|
271
|
-
# This is the default output buffer that provides a base implementation of
|
272
|
-
# trim! that does nothing. It's effectively a wrapper around whatever output
|
273
|
-
# object was given to the format command.
|
274
|
-
class DefaultBuffer
|
275
|
-
attr_reader :output
|
276
|
-
|
277
|
-
def initialize(output = [])
|
278
|
-
@output = output
|
279
|
-
end
|
280
|
-
|
281
|
-
def <<(object)
|
282
|
-
@output << object
|
283
|
-
end
|
284
|
-
|
285
|
-
def trim!
|
286
|
-
0
|
287
|
-
end
|
288
|
-
end
|
289
|
-
|
290
|
-
# This is an output buffer that wraps a string output object. It provides a
|
291
|
-
# trim! method that trims off trailing whitespace from the string using
|
292
|
-
# gsub!.
|
293
|
-
class StringBuffer < DefaultBuffer
|
294
|
-
def initialize(output = "".dup)
|
295
|
-
super(output)
|
296
|
-
end
|
297
|
-
|
298
|
-
def trim!
|
299
|
-
length = output.length
|
300
|
-
output.gsub!(/[\t ]*\z/, "")
|
301
|
-
length - output.length
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
# This is an output buffer that wraps an array output object. It provides a
|
306
|
-
# trim! method that trims off trailing whitespace from the last element in
|
307
|
-
# the array if it's an unfrozen string using the same method as the
|
308
|
-
# StringBuffer.
|
309
|
-
class ArrayBuffer < DefaultBuffer
|
310
|
-
def initialize(output = [])
|
311
|
-
super(output)
|
312
|
-
end
|
313
|
-
|
314
|
-
def trim!
|
315
|
-
return 0 if output.empty?
|
316
|
-
|
317
|
-
trimmed = 0
|
318
|
-
|
319
|
-
while output.any? && output.last.is_a?(String) &&
|
320
|
-
output.last.match?(/\A[\t ]*\z/)
|
321
|
-
trimmed += output.pop.length
|
322
|
-
end
|
323
|
-
|
324
|
-
if output.any? && output.last.is_a?(String) && !output.last.frozen?
|
325
|
-
length = output.last.length
|
326
|
-
output.last.gsub!(/[\t ]*\z/, "")
|
327
|
-
trimmed += length - output.last.length
|
328
|
-
end
|
329
|
-
|
330
|
-
trimmed
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
# This is a switch for building the correct output buffer wrapper class for
|
335
|
-
# the given output object.
|
336
|
-
def self.for(output)
|
337
|
-
case output
|
338
|
-
when String
|
339
|
-
StringBuffer.new(output)
|
340
|
-
when Array
|
341
|
-
ArrayBuffer.new(output)
|
342
|
-
else
|
343
|
-
DefaultBuffer.new(output)
|
344
|
-
end
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
# PrettyPrint::SingleLine is used by PrettyPrint.singleline_format
|
349
|
-
#
|
350
|
-
# It is passed to be similar to a PrettyPrint object itself, by responding to
|
351
|
-
# all of the same print tree node builder methods, as well as the #flush
|
352
|
-
# method.
|
353
|
-
#
|
354
|
-
# The significant difference here is that there are no line breaks in the
|
355
|
-
# output. If an IfBreak node is used, only the flat contents are printed.
|
356
|
-
# LineSuffix nodes are printed at the end of the buffer when #flush is called.
|
357
|
-
class SingleLine
|
358
|
-
# The output object. It stores rendered text and should respond to <<.
|
359
|
-
attr_reader :output
|
360
|
-
|
361
|
-
# The current array of contents that the print tree builder methods should
|
362
|
-
# append to.
|
363
|
-
attr_reader :target
|
364
|
-
|
365
|
-
# A buffer output that wraps any calls to line_suffix that will be flushed
|
366
|
-
# at the end of printing.
|
367
|
-
attr_reader :line_suffixes
|
368
|
-
|
369
|
-
# Create a PrettyPrint::SingleLine object
|
370
|
-
#
|
371
|
-
# Arguments:
|
372
|
-
# * +output+ - String (or similar) to store rendered text. Needs to respond
|
373
|
-
# to '<<'.
|
374
|
-
# * +maxwidth+ - Argument position expected to be here for compatibility.
|
375
|
-
# This argument is a noop.
|
376
|
-
# * +newline+ - Argument position expected to be here for compatibility.
|
377
|
-
# This argument is a noop.
|
378
|
-
def initialize(output, _maxwidth = nil, _newline = nil)
|
379
|
-
@output = Buffer.for(output)
|
380
|
-
@target = @output
|
381
|
-
@line_suffixes = Buffer::ArrayBuffer.new
|
382
|
-
end
|
383
|
-
|
384
|
-
# Flushes the line suffixes onto the output buffer.
|
385
|
-
def flush
|
386
|
-
line_suffixes.output.each { |doc| output << doc }
|
387
|
-
end
|
388
|
-
|
389
|
-
# --------------------------------------------------------------------------
|
390
|
-
# Markers node builders
|
391
|
-
# --------------------------------------------------------------------------
|
392
|
-
|
393
|
-
# Appends +separator+ to the text to be output. By default +separator+ is
|
394
|
-
# ' '
|
395
|
-
#
|
396
|
-
# The +width+, +indent+, and +force+ arguments are here for compatibility.
|
397
|
-
# They are all noop arguments.
|
398
|
-
def breakable(
|
399
|
-
separator = " ",
|
400
|
-
_width = separator.length,
|
401
|
-
indent: nil,
|
402
|
-
force: nil
|
403
|
-
)
|
404
|
-
target << separator
|
405
|
-
end
|
406
|
-
|
407
|
-
# Here for compatibility, does nothing.
|
408
|
-
def break_parent
|
409
|
-
end
|
410
|
-
|
411
|
-
# Appends +separator+ to the output buffer. +width+ is a noop here for
|
412
|
-
# compatibility.
|
413
|
-
def fill_breakable(separator = " ", _width = separator.length)
|
414
|
-
target << separator
|
415
|
-
end
|
416
|
-
|
417
|
-
# Immediately trims the output buffer.
|
418
|
-
def trim
|
419
|
-
target.trim!
|
420
|
-
end
|
421
|
-
|
422
|
-
# --------------------------------------------------------------------------
|
423
|
-
# Container node builders
|
424
|
-
# --------------------------------------------------------------------------
|
425
|
-
|
426
|
-
# Opens a block for grouping objects to be pretty printed.
|
427
|
-
#
|
428
|
-
# Arguments:
|
429
|
-
# * +indent+ - noop argument. Present for compatibility.
|
430
|
-
# * +open_obj+ - text appended before the &block. Default is ''
|
431
|
-
# * +close_obj+ - text appended after the &block. Default is ''
|
432
|
-
# * +open_width+ - noop argument. Present for compatibility.
|
433
|
-
# * +close_width+ - noop argument. Present for compatibility.
|
434
|
-
def group(
|
435
|
-
_indent = nil,
|
436
|
-
open_object = "",
|
437
|
-
close_object = "",
|
438
|
-
_open_width = nil,
|
439
|
-
_close_width = nil
|
440
|
-
)
|
441
|
-
target << open_object
|
442
|
-
yield
|
443
|
-
target << close_object
|
444
|
-
end
|
445
|
-
|
446
|
-
# A class that wraps the ability to call #if_flat. The contents of the
|
447
|
-
# #if_flat block are executed immediately, so effectively this class and the
|
448
|
-
# #if_break method that triggers it are unnecessary, but they're here to
|
449
|
-
# maintain compatibility.
|
450
|
-
class IfBreakBuilder
|
451
|
-
def if_flat
|
452
|
-
yield
|
453
|
-
end
|
454
|
-
end
|
455
|
-
|
456
|
-
# Effectively unnecessary, but here for compatibility.
|
457
|
-
def if_break
|
458
|
-
IfBreakBuilder.new
|
459
|
-
end
|
460
|
-
|
461
|
-
# Also effectively unnecessary, but here for compatibility.
|
462
|
-
def if_flat
|
463
|
-
end
|
464
|
-
|
465
|
-
# A noop that immediately yields.
|
466
|
-
def indent
|
467
|
-
yield
|
468
|
-
end
|
469
|
-
|
470
|
-
# Changes the target output buffer to the line suffix output buffer which
|
471
|
-
# will get flushed at the end of printing.
|
472
|
-
def line_suffix
|
473
|
-
previous_target, @target = @target, line_suffixes
|
474
|
-
yield
|
475
|
-
@target = previous_target
|
476
|
-
end
|
477
|
-
|
478
|
-
# Takes +indent+ arg, but does nothing with it.
|
479
|
-
#
|
480
|
-
# Yields to a block.
|
481
|
-
def nest(_indent)
|
482
|
-
yield
|
483
|
-
end
|
484
|
-
|
485
|
-
# Add +object+ to the text to be output.
|
486
|
-
#
|
487
|
-
# +width+ argument is here for compatibility. It is a noop argument.
|
488
|
-
def text(object = "", _width = nil)
|
489
|
-
target << object
|
490
|
-
end
|
491
|
-
end
|
492
|
-
|
493
|
-
# This object represents the current level of indentation within the printer.
|
494
|
-
# It has the ability to generate new levels of indentation through the #align
|
495
|
-
# and #indent methods.
|
496
|
-
class IndentLevel
|
497
|
-
IndentPart = Object.new
|
498
|
-
DedentPart = Object.new
|
499
|
-
|
500
|
-
StringAlignPart = Struct.new(:n)
|
501
|
-
NumberAlignPart = Struct.new(:n)
|
502
|
-
|
503
|
-
attr_reader :genspace, :value, :length, :queue, :root
|
504
|
-
|
505
|
-
def initialize(
|
506
|
-
genspace:,
|
507
|
-
value: genspace.call(0),
|
508
|
-
length: 0,
|
509
|
-
queue: [],
|
510
|
-
root: nil
|
511
|
-
)
|
512
|
-
@genspace = genspace
|
513
|
-
@value = value
|
514
|
-
@length = length
|
515
|
-
@queue = queue
|
516
|
-
@root = root
|
517
|
-
end
|
518
|
-
|
519
|
-
# This can accept a whole lot of different kinds of objects, due to the
|
520
|
-
# nature of the flexibility of the Align node.
|
521
|
-
def align(n)
|
522
|
-
case n
|
523
|
-
when NilClass
|
524
|
-
self
|
525
|
-
when String
|
526
|
-
indent(StringAlignPart.new(n))
|
527
|
-
else
|
528
|
-
indent(n < 0 ? DedentPart : NumberAlignPart.new(n))
|
529
|
-
end
|
530
|
-
end
|
531
|
-
|
532
|
-
def indent(part = IndentPart)
|
533
|
-
next_value = genspace.call(0)
|
534
|
-
next_length = 0
|
535
|
-
next_queue = (part == DedentPart ? queue[0...-1] : [*queue, part])
|
536
|
-
|
537
|
-
last_spaces = 0
|
538
|
-
|
539
|
-
add_spaces = ->(count) do
|
540
|
-
next_value << genspace.call(count)
|
541
|
-
next_length += count
|
542
|
-
end
|
543
|
-
|
544
|
-
flush_spaces = -> do
|
545
|
-
add_spaces[last_spaces] if last_spaces > 0
|
546
|
-
last_spaces = 0
|
547
|
-
end
|
548
|
-
|
549
|
-
next_queue.each do |next_part|
|
550
|
-
case next_part
|
551
|
-
when IndentPart
|
552
|
-
flush_spaces.call
|
553
|
-
add_spaces.call(2)
|
554
|
-
when StringAlignPart
|
555
|
-
flush_spaces.call
|
556
|
-
next_value += next_part.n
|
557
|
-
next_length += next_part.n.length
|
558
|
-
when NumberAlignPart
|
559
|
-
last_spaces += next_part.n
|
560
|
-
end
|
561
|
-
end
|
562
|
-
|
563
|
-
flush_spaces.call
|
564
|
-
|
565
|
-
IndentLevel.new(
|
566
|
-
genspace: genspace,
|
567
|
-
value: next_value,
|
568
|
-
length: next_length,
|
569
|
-
queue: next_queue,
|
570
|
-
root: root
|
571
|
-
)
|
572
|
-
end
|
573
|
-
end
|
574
|
-
|
575
|
-
# When printing, you can optionally specify the value that should be used
|
576
|
-
# whenever a group needs to be broken onto multiple lines. In this case the
|
577
|
-
# default is \n.
|
578
|
-
DEFAULT_NEWLINE = "\n"
|
579
|
-
|
580
|
-
# When generating spaces after a newline for indentation, by default we
|
581
|
-
# generate one space per character needed for indentation. You can change this
|
582
|
-
# behavior (for instance to use tabs) by passing a different genspace
|
583
|
-
# procedure.
|
584
|
-
DEFAULT_GENSPACE = ->(n) { " " * n }
|
585
|
-
|
586
|
-
# There are two modes in printing, break and flat. When we're in break mode,
|
587
|
-
# any lines will use their newline, any if-breaks will use their break
|
588
|
-
# contents, etc.
|
589
|
-
MODE_BREAK = 1
|
590
|
-
|
591
|
-
# This is another print mode much like MODE_BREAK. When we're in flat mode, we
|
592
|
-
# attempt to print everything on one line until we either hit a broken group,
|
593
|
-
# a forced line, or the maximum width.
|
594
|
-
MODE_FLAT = 2
|
595
|
-
|
596
|
-
# This is a convenience method which is same as follows:
|
597
|
-
#
|
598
|
-
# begin
|
599
|
-
# q = PrettyPrint.new(output, maxwidth, newline, &genspace)
|
600
|
-
# ...
|
601
|
-
# q.flush
|
602
|
-
# output
|
603
|
-
# end
|
604
|
-
#
|
605
|
-
def self.format(
|
606
|
-
output = "".dup,
|
607
|
-
maxwidth = 80,
|
608
|
-
newline = DEFAULT_NEWLINE,
|
609
|
-
genspace = DEFAULT_GENSPACE
|
610
|
-
)
|
611
|
-
q = new(output, maxwidth, newline, &genspace)
|
612
|
-
yield q
|
613
|
-
q.flush
|
614
|
-
output
|
615
|
-
end
|
616
|
-
|
617
|
-
# This is similar to PrettyPrint::format but the result has no breaks.
|
618
|
-
#
|
619
|
-
# +maxwidth+, +newline+ and +genspace+ are ignored.
|
620
|
-
#
|
621
|
-
# The invocation of +breakable+ in the block doesn't break a line and is
|
622
|
-
# treated as just an invocation of +text+.
|
623
|
-
#
|
624
|
-
def self.singleline_format(
|
625
|
-
output = "".dup,
|
626
|
-
_maxwidth = nil,
|
627
|
-
_newline = nil,
|
628
|
-
_genspace = nil
|
629
|
-
)
|
630
|
-
q = SingleLine.new(output)
|
631
|
-
yield q
|
632
|
-
output
|
633
|
-
end
|
634
|
-
|
635
|
-
# The output object. It represents the final destination of the contents of
|
636
|
-
# the print tree. It should respond to <<.
|
637
|
-
#
|
638
|
-
# This defaults to "".dup
|
639
|
-
attr_reader :output
|
640
|
-
|
641
|
-
# This is an output buffer that wraps the output object and provides
|
642
|
-
# additional functionality depending on its type.
|
643
|
-
#
|
644
|
-
# This defaults to Buffer::StringBuffer.new("".dup)
|
645
|
-
attr_reader :buffer
|
646
|
-
|
647
|
-
# The maximum width of a line, before it is separated in to a newline
|
648
|
-
#
|
649
|
-
# This defaults to 80, and should be an Integer
|
650
|
-
attr_reader :maxwidth
|
651
|
-
|
652
|
-
# The value that is appended to +output+ to add a new line.
|
653
|
-
#
|
654
|
-
# This defaults to "\n", and should be String
|
655
|
-
attr_reader :newline
|
656
|
-
|
657
|
-
# An object that responds to call that takes one argument, of an Integer, and
|
658
|
-
# returns the corresponding number of spaces.
|
659
|
-
#
|
660
|
-
# By default this is: ->(n) { ' ' * n }
|
661
|
-
attr_reader :genspace
|
662
|
-
|
663
|
-
# The stack of groups that are being printed.
|
664
|
-
attr_reader :groups
|
665
|
-
|
666
|
-
# The current array of contents that calls to methods that generate print tree
|
667
|
-
# nodes will append to.
|
668
|
-
attr_reader :target
|
669
|
-
|
670
|
-
# Creates a buffer for pretty printing.
|
671
|
-
#
|
672
|
-
# +output+ is an output target. If it is not specified, '' is assumed. It
|
673
|
-
# should have a << method which accepts the first argument +obj+ of
|
674
|
-
# PrettyPrint#text, the first argument +separator+ of PrettyPrint#breakable,
|
675
|
-
# the first argument +newline+ of PrettyPrint.new, and the result of a given
|
676
|
-
# block for PrettyPrint.new.
|
677
|
-
#
|
678
|
-
# +maxwidth+ specifies maximum line length. If it is not specified, 80 is
|
679
|
-
# assumed. However actual outputs may overflow +maxwidth+ if long
|
680
|
-
# non-breakable texts are provided.
|
681
|
-
#
|
682
|
-
# +newline+ is used for line breaks. "\n" is used if it is not specified.
|
683
|
-
#
|
684
|
-
# The block is used to generate spaces. ->(n) { ' ' * n } is used if it is not
|
685
|
-
# given.
|
686
|
-
def initialize(
|
687
|
-
output = "".dup,
|
688
|
-
maxwidth = 80,
|
689
|
-
newline = DEFAULT_NEWLINE,
|
690
|
-
&genspace
|
691
|
-
)
|
692
|
-
@output = output
|
693
|
-
@buffer = Buffer.for(output)
|
694
|
-
@maxwidth = maxwidth
|
695
|
-
@newline = newline
|
696
|
-
@genspace = genspace || DEFAULT_GENSPACE
|
697
|
-
reset
|
698
|
-
end
|
699
|
-
|
700
|
-
# Returns the group most recently added to the stack.
|
701
|
-
#
|
702
|
-
# Contrived example:
|
703
|
-
# out = ""
|
704
|
-
# => ""
|
705
|
-
# q = PrettyPrint.new(out)
|
706
|
-
# => #<PrettyPrint:0x0>
|
707
|
-
# q.group {
|
708
|
-
# q.text q.current_group.inspect
|
709
|
-
# q.text q.newline
|
710
|
-
# q.group(q.current_group.depth + 1) {
|
711
|
-
# q.text q.current_group.inspect
|
712
|
-
# q.text q.newline
|
713
|
-
# q.group(q.current_group.depth + 1) {
|
714
|
-
# q.text q.current_group.inspect
|
715
|
-
# q.text q.newline
|
716
|
-
# q.group(q.current_group.depth + 1) {
|
717
|
-
# q.text q.current_group.inspect
|
718
|
-
# q.text q.newline
|
719
|
-
# }
|
720
|
-
# }
|
721
|
-
# }
|
722
|
-
# }
|
723
|
-
# => 284
|
724
|
-
# puts out
|
725
|
-
# #<PrettyPrint::Group:0x0 @depth=1>
|
726
|
-
# #<PrettyPrint::Group:0x0 @depth=2>
|
727
|
-
# #<PrettyPrint::Group:0x0 @depth=3>
|
728
|
-
# #<PrettyPrint::Group:0x0 @depth=4>
|
729
|
-
def current_group
|
730
|
-
groups.last
|
731
|
-
end
|
732
|
-
|
733
|
-
# Flushes all of the generated print tree onto the output buffer, then clears
|
734
|
-
# the generated tree from memory.
|
735
|
-
def flush
|
736
|
-
# First, get the root group, since we placed one at the top to begin with.
|
737
|
-
doc = groups.first
|
738
|
-
|
739
|
-
# This represents how far along the current line we are. It gets reset
|
740
|
-
# back to 0 when we encounter a newline.
|
741
|
-
position = 0
|
742
|
-
|
743
|
-
# This is our command stack. A command consists of a triplet of an
|
744
|
-
# indentation level, the mode (break or flat), and a doc node.
|
745
|
-
commands = [[IndentLevel.new(genspace: genspace), MODE_BREAK, doc]]
|
746
|
-
|
747
|
-
# This is a small optimization boolean. It keeps track of whether or not
|
748
|
-
# when we hit a group node we should check if it fits on the same line.
|
749
|
-
should_remeasure = false
|
750
|
-
|
751
|
-
# This is a separate command stack that includes the same kind of triplets
|
752
|
-
# as the commands variable. It is used to keep track of things that should
|
753
|
-
# go at the end of printed lines once the other doc nodes are accounted for.
|
754
|
-
# Typically this is used to implement comments.
|
755
|
-
line_suffixes = []
|
756
|
-
|
757
|
-
# This is a special sort used to order the line suffixes by both the
|
758
|
-
# priority set on the line suffix and the index it was in the original
|
759
|
-
# array.
|
760
|
-
line_suffix_sort = ->(line_suffix) do
|
761
|
-
[-line_suffix.last, -line_suffixes.index(line_suffix)]
|
762
|
-
end
|
763
|
-
|
764
|
-
# This is a linear stack instead of a mutually recursive call defined on
|
765
|
-
# the individual doc nodes for efficiency.
|
766
|
-
while (indent, mode, doc = commands.pop)
|
767
|
-
case doc
|
768
|
-
when Text
|
769
|
-
doc.objects.each { |object| buffer << object }
|
770
|
-
position += doc.width
|
771
|
-
when Array
|
772
|
-
doc.reverse_each { |part| commands << [indent, mode, part] }
|
773
|
-
when Indent
|
774
|
-
commands << [indent.indent, mode, doc.contents]
|
775
|
-
when Align
|
776
|
-
commands << [indent.align(doc.indent), mode, doc.contents]
|
777
|
-
when Trim
|
778
|
-
position -= buffer.trim!
|
779
|
-
when Group
|
780
|
-
if mode == MODE_FLAT && !should_remeasure
|
781
|
-
commands << [
|
782
|
-
indent,
|
783
|
-
doc.break? ? MODE_BREAK : MODE_FLAT,
|
784
|
-
doc.contents
|
785
|
-
]
|
786
|
-
else
|
787
|
-
should_remeasure = false
|
788
|
-
next_cmd = [indent, MODE_FLAT, doc.contents]
|
789
|
-
commands << if !doc.break? &&
|
790
|
-
fits?(next_cmd, commands, maxwidth - position)
|
791
|
-
next_cmd
|
792
|
-
else
|
793
|
-
[indent, MODE_BREAK, doc.contents]
|
794
|
-
end
|
795
|
-
end
|
796
|
-
when IfBreak
|
797
|
-
if mode == MODE_BREAK && doc.break_contents.any?
|
798
|
-
commands << [indent, mode, doc.break_contents]
|
799
|
-
elsif mode == MODE_FLAT && doc.flat_contents.any?
|
800
|
-
commands << [indent, mode, doc.flat_contents]
|
801
|
-
end
|
802
|
-
when LineSuffix
|
803
|
-
line_suffixes << [indent, mode, doc.contents, doc.priority]
|
804
|
-
when Breakable
|
805
|
-
if mode == MODE_FLAT
|
806
|
-
if doc.force?
|
807
|
-
# This line was forced into the output even if we were in flat mode,
|
808
|
-
# so we need to tell the next group that no matter what, it needs to
|
809
|
-
# remeasure because the previous measurement didn't accurately
|
810
|
-
# capture the entire expression (this is necessary for nested
|
811
|
-
# groups).
|
812
|
-
should_remeasure = true
|
813
|
-
else
|
814
|
-
buffer << doc.separator
|
815
|
-
position += doc.width
|
816
|
-
next
|
817
|
-
end
|
818
|
-
end
|
819
|
-
|
820
|
-
# If there are any commands in the line suffix buffer, then we're going
|
821
|
-
# to flush them now, as we are about to add a newline.
|
822
|
-
if line_suffixes.any?
|
823
|
-
commands << [indent, mode, doc]
|
824
|
-
commands += line_suffixes.sort_by(&line_suffix_sort)
|
825
|
-
line_suffixes = []
|
826
|
-
next
|
827
|
-
end
|
828
|
-
|
829
|
-
if !doc.indent?
|
830
|
-
buffer << newline
|
831
|
-
|
832
|
-
if indent.root
|
833
|
-
buffer << indent.root.value
|
834
|
-
position = indent.root.length
|
835
|
-
else
|
836
|
-
position = 0
|
837
|
-
end
|
838
|
-
else
|
839
|
-
position -= buffer.trim!
|
840
|
-
buffer << newline
|
841
|
-
buffer << indent.value
|
842
|
-
position = indent.length
|
843
|
-
end
|
844
|
-
when BreakParent
|
845
|
-
# do nothing
|
846
|
-
else
|
847
|
-
# Special case where the user has defined some way to get an extra doc
|
848
|
-
# node that we don't explicitly support into the list. In this case
|
849
|
-
# we're going to assume it's 0-width and just append it to the output
|
850
|
-
# buffer.
|
851
|
-
#
|
852
|
-
# This is useful behavior for putting marker nodes into the list so that
|
853
|
-
# you can know how things are getting mapped before they get printed.
|
854
|
-
buffer << doc
|
855
|
-
end
|
856
|
-
|
857
|
-
if commands.empty? && line_suffixes.any?
|
858
|
-
commands += line_suffixes.sort_by(&line_suffix_sort)
|
859
|
-
line_suffixes = []
|
860
|
-
end
|
861
|
-
end
|
862
|
-
|
863
|
-
# Reset the group stack and target array so that this pretty printer object
|
864
|
-
# can continue to be used before calling flush again if desired.
|
865
|
-
reset
|
866
|
-
end
|
867
|
-
|
868
|
-
# ----------------------------------------------------------------------------
|
869
|
-
# Markers node builders
|
870
|
-
# ----------------------------------------------------------------------------
|
871
|
-
|
872
|
-
# This says "you can break a line here if necessary", and a +width+\-column
|
873
|
-
# text +separator+ is inserted if a line is not broken at the point.
|
874
|
-
#
|
875
|
-
# If +separator+ is not specified, ' ' is used.
|
876
|
-
#
|
877
|
-
# If +width+ is not specified, +separator.length+ is used. You will have to
|
878
|
-
# specify this when +separator+ is a multibyte character, for example.
|
879
|
-
#
|
880
|
-
# By default, if the surrounding group is broken and a newline is inserted,
|
881
|
-
# the printer will indent the subsequent line up to the current level of
|
882
|
-
# indentation. You can disable this behavior with the +indent+ argument if
|
883
|
-
# that's not desired (rare).
|
884
|
-
#
|
885
|
-
# By default, when you insert a Breakable into the print tree, it only breaks
|
886
|
-
# the surrounding group when the group's contents cannot fit onto the
|
887
|
-
# remaining space of the current line. You can force it to break the
|
888
|
-
# surrounding group instead if you always want the newline with the +force+
|
889
|
-
# argument.
|
890
|
-
def breakable(
|
891
|
-
separator = " ",
|
892
|
-
width = separator.length,
|
893
|
-
indent: true,
|
894
|
-
force: false
|
895
|
-
)
|
896
|
-
doc = Breakable.new(separator, width, indent: indent, force: force)
|
897
|
-
|
898
|
-
target << doc
|
899
|
-
break_parent if force
|
900
|
-
|
901
|
-
doc
|
902
|
-
end
|
903
|
-
|
904
|
-
# This inserts a BreakParent node into the print tree which forces the
|
905
|
-
# surrounding and all parent group nodes to break.
|
906
|
-
def break_parent
|
907
|
-
doc = BreakParent.new
|
908
|
-
target << doc
|
909
|
-
|
910
|
-
groups.reverse_each do |group|
|
911
|
-
break if group.break?
|
912
|
-
group.break
|
913
|
-
end
|
914
|
-
|
915
|
-
doc
|
916
|
-
end
|
917
|
-
|
918
|
-
# This is similar to #breakable except the decision to break or not is
|
919
|
-
# determined individually.
|
920
|
-
#
|
921
|
-
# Two #fill_breakable under a group may cause 4 results:
|
922
|
-
# (break,break), (break,non-break), (non-break,break), (non-break,non-break).
|
923
|
-
# This is different to #breakable because two #breakable under a group
|
924
|
-
# may cause 2 results: (break,break), (non-break,non-break).
|
925
|
-
#
|
926
|
-
# The text +separator+ is inserted if a line is not broken at this point.
|
927
|
-
#
|
928
|
-
# If +separator+ is not specified, ' ' is used.
|
929
|
-
#
|
930
|
-
# If +width+ is not specified, +separator.length+ is used. You will have to
|
931
|
-
# specify this when +separator+ is a multibyte character, for example.
|
932
|
-
def fill_breakable(separator = " ", width = separator.length)
|
933
|
-
group { breakable(separator, width) }
|
934
|
-
end
|
935
|
-
|
936
|
-
# This inserts a Trim node into the print tree which, when printed, will clear
|
937
|
-
# all whitespace at the end of the output buffer. This is useful for the rare
|
938
|
-
# case where you need to delete printed indentation and force the next node
|
939
|
-
# to start at the beginning of the line.
|
940
|
-
def trim
|
941
|
-
doc = Trim.new
|
942
|
-
target << doc
|
943
|
-
|
944
|
-
doc
|
945
|
-
end
|
946
|
-
|
947
|
-
# ----------------------------------------------------------------------------
|
948
|
-
# Container node builders
|
949
|
-
# ----------------------------------------------------------------------------
|
950
|
-
|
951
|
-
# Groups line break hints added in the block. The line break hints are all to
|
952
|
-
# be used or not.
|
953
|
-
#
|
954
|
-
# If +indent+ is specified, the method call is regarded as nested by
|
955
|
-
# nest(indent) { ... }.
|
956
|
-
#
|
957
|
-
# If +open_object+ is specified, <tt>text(open_object, open_width)</tt> is
|
958
|
-
# called before grouping. If +close_object+ is specified,
|
959
|
-
# <tt>text(close_object, close_width)</tt> is called after grouping.
|
960
|
-
def group(
|
961
|
-
indent = 0,
|
962
|
-
open_object = "",
|
963
|
-
close_object = "",
|
964
|
-
open_width = open_object.length,
|
965
|
-
close_width = close_object.length
|
966
|
-
)
|
967
|
-
text(open_object, open_width) if open_object != ""
|
968
|
-
|
969
|
-
doc = Group.new(groups.last.depth + 1)
|
970
|
-
groups << doc
|
971
|
-
target << doc
|
972
|
-
|
973
|
-
with_target(doc.contents) do
|
974
|
-
if indent != 0
|
975
|
-
nest(indent) { yield }
|
976
|
-
else
|
977
|
-
yield
|
978
|
-
end
|
979
|
-
end
|
980
|
-
|
981
|
-
groups.pop
|
982
|
-
text(close_object, close_width) if close_object != ""
|
983
|
-
|
984
|
-
doc
|
985
|
-
end
|
986
|
-
|
987
|
-
# A small DSL-like object used for specifying the alternative contents to be
|
988
|
-
# printed if the surrounding group doesn't break for an IfBreak node.
|
989
|
-
class IfBreakBuilder
|
990
|
-
attr_reader :builder, :if_break
|
991
|
-
|
992
|
-
def initialize(builder, if_break)
|
993
|
-
@builder = builder
|
994
|
-
@if_break = if_break
|
995
|
-
end
|
996
|
-
|
997
|
-
def if_flat(&block)
|
998
|
-
builder.with_target(if_break.flat_contents, &block)
|
999
|
-
end
|
1000
|
-
end
|
1001
|
-
|
1002
|
-
# Inserts an IfBreak node with the contents of the block being added to its
|
1003
|
-
# list of nodes that should be printed if the surrounding node breaks. If it
|
1004
|
-
# doesn't, then you can specify the contents to be printed with the #if_flat
|
1005
|
-
# method used on the return object from this method. For example,
|
1006
|
-
#
|
1007
|
-
# q.if_break { q.text('do') }.if_flat { q.text('{') }
|
1008
|
-
#
|
1009
|
-
# In the example above, if the surrounding group is broken it will print 'do'
|
1010
|
-
# and if it is not it will print '{'.
|
1011
|
-
def if_break
|
1012
|
-
doc = IfBreak.new
|
1013
|
-
target << doc
|
1014
|
-
|
1015
|
-
with_target(doc.break_contents) { yield }
|
1016
|
-
IfBreakBuilder.new(self, doc)
|
1017
|
-
end
|
1018
|
-
|
1019
|
-
# This is similar to if_break in that it also inserts an IfBreak node into the
|
1020
|
-
# print tree, however it's starting from the flat contents, and cannot be used
|
1021
|
-
# to build the break contents.
|
1022
|
-
def if_flat
|
1023
|
-
doc = IfBreak.new
|
1024
|
-
target << doc
|
1025
|
-
|
1026
|
-
with_target(doc.flat_contents) { yield }
|
1027
|
-
end
|
1028
|
-
|
1029
|
-
# Very similar to the #nest method, this indents the nested content by one
|
1030
|
-
# level by inserting an Indent node into the print tree. The contents of the
|
1031
|
-
# node are determined by the block.
|
1032
|
-
def indent
|
1033
|
-
doc = Indent.new
|
1034
|
-
target << doc
|
1035
|
-
|
1036
|
-
with_target(doc.contents) { yield }
|
1037
|
-
doc
|
1038
|
-
end
|
1039
|
-
|
1040
|
-
# Inserts a LineSuffix node into the print tree. The contents of the node are
|
1041
|
-
# determined by the block.
|
1042
|
-
def line_suffix(priority: LineSuffix::DEFAULT_PRIORITY)
|
1043
|
-
doc = LineSuffix.new(priority: priority)
|
1044
|
-
target << doc
|
1045
|
-
|
1046
|
-
with_target(doc.contents) { yield }
|
1047
|
-
doc
|
1048
|
-
end
|
1049
|
-
|
1050
|
-
# Increases left margin after newline with +indent+ for line breaks added in
|
1051
|
-
# the block.
|
1052
|
-
def nest(indent)
|
1053
|
-
doc = Align.new(indent: indent)
|
1054
|
-
target << doc
|
1055
|
-
|
1056
|
-
with_target(doc.contents) { yield }
|
1057
|
-
doc
|
1058
|
-
end
|
1059
|
-
|
1060
|
-
# This adds +object+ as a text of +width+ columns in width.
|
1061
|
-
#
|
1062
|
-
# If +width+ is not specified, object.length is used.
|
1063
|
-
def text(object = "", width = object.length)
|
1064
|
-
doc = target.last
|
1065
|
-
|
1066
|
-
unless doc.is_a?(Text)
|
1067
|
-
doc = Text.new
|
1068
|
-
target << doc
|
1069
|
-
end
|
1070
|
-
|
1071
|
-
doc.add(object: object, width: width)
|
1072
|
-
doc
|
1073
|
-
end
|
1074
|
-
|
1075
|
-
# ----------------------------------------------------------------------------
|
1076
|
-
# Internal APIs
|
1077
|
-
# ----------------------------------------------------------------------------
|
1078
|
-
|
1079
|
-
# A convenience method used by a lot of the print tree node builders that
|
1080
|
-
# temporarily changes the target that the builders will append to.
|
1081
|
-
def with_target(target)
|
1082
|
-
previous_target, @target = @target, target
|
1083
|
-
yield
|
1084
|
-
@target = previous_target
|
1085
|
-
end
|
1086
|
-
|
1087
|
-
private
|
1088
|
-
|
1089
|
-
# This method returns a boolean as to whether or not the remaining commands
|
1090
|
-
# fit onto the remaining space on the current line. If we finish printing
|
1091
|
-
# all of the commands or if we hit a newline, then we return true. Otherwise
|
1092
|
-
# if we continue printing past the remaining space, we return false.
|
1093
|
-
def fits?(next_command, rest_commands, remaining)
|
1094
|
-
# This is the index in the remaining commands that we've handled so far.
|
1095
|
-
# We reverse through the commands and add them to the stack if we've run
|
1096
|
-
# out of nodes to handle.
|
1097
|
-
rest_index = rest_commands.length
|
1098
|
-
|
1099
|
-
# This is our stack of commands, very similar to the commands list in the
|
1100
|
-
# print method.
|
1101
|
-
commands = [next_command]
|
1102
|
-
|
1103
|
-
# This is our output buffer, really only necessary to keep track of
|
1104
|
-
# because we could encounter a Trim doc node that would actually add
|
1105
|
-
# remaining space.
|
1106
|
-
fit_buffer = buffer.class.new
|
1107
|
-
|
1108
|
-
while remaining >= 0
|
1109
|
-
if commands.empty?
|
1110
|
-
return true if rest_index == 0
|
1111
|
-
|
1112
|
-
rest_index -= 1
|
1113
|
-
commands << rest_commands[rest_index]
|
1114
|
-
next
|
1115
|
-
end
|
1116
|
-
|
1117
|
-
indent, mode, doc = commands.pop
|
1118
|
-
|
1119
|
-
case doc
|
1120
|
-
when Text
|
1121
|
-
doc.objects.each { |object| fit_buffer << object }
|
1122
|
-
remaining -= doc.width
|
1123
|
-
when Array
|
1124
|
-
doc.reverse_each { |part| commands << [indent, mode, part] }
|
1125
|
-
when Indent
|
1126
|
-
commands << [indent.indent, mode, doc.contents]
|
1127
|
-
when Align
|
1128
|
-
commands << [indent.align(doc.indent), mode, doc.contents]
|
1129
|
-
when Trim
|
1130
|
-
remaining += fit_buffer.trim!
|
1131
|
-
when Group
|
1132
|
-
commands << [indent, doc.break? ? MODE_BREAK : mode, doc.contents]
|
1133
|
-
when IfBreak
|
1134
|
-
if mode == MODE_BREAK && doc.break_contents.any?
|
1135
|
-
commands << [indent, mode, doc.break_contents]
|
1136
|
-
elsif mode == MODE_FLAT && doc.flat_contents.any?
|
1137
|
-
commands << [indent, mode, doc.flat_contents]
|
1138
|
-
end
|
1139
|
-
when Breakable
|
1140
|
-
if mode == MODE_FLAT && !doc.force?
|
1141
|
-
fit_buffer << doc.separator
|
1142
|
-
remaining -= doc.width
|
1143
|
-
next
|
1144
|
-
end
|
1145
|
-
|
1146
|
-
return true
|
1147
|
-
end
|
1148
|
-
end
|
1149
|
-
|
1150
|
-
false
|
1151
|
-
end
|
1152
|
-
|
1153
|
-
# Resets the group stack and target array so that this pretty printer object
|
1154
|
-
# can continue to be used before calling flush again if desired.
|
1155
|
-
def reset
|
1156
|
-
@groups = [Group.new(0)]
|
1157
|
-
@target = @groups.last.contents
|
1158
|
-
end
|
1159
|
-
end
|