syntax_tree 2.3.1 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +20 -1
- data/.rubocop.yml +80 -0
- data/CHANGELOG.md +36 -1
- data/Gemfile.lock +24 -1
- data/README.md +126 -5
- data/Rakefile +27 -5
- data/config/rubocop.yml +64 -0
- data/lib/syntax_tree/cli.rb +63 -27
- data/lib/syntax_tree/formatter/single_quotes.rb +13 -0
- data/lib/syntax_tree/formatter.rb +7 -6
- data/lib/syntax_tree/language_server/inlay_hints.rb +87 -38
- data/lib/syntax_tree/language_server.rb +50 -14
- data/lib/syntax_tree/node.rb +499 -387
- data/lib/syntax_tree/parser.rb +447 -112
- data/lib/syntax_tree/plugin/single_quotes.rb +4 -0
- data/lib/syntax_tree/version.rb +1 -1
- data/lib/syntax_tree/visitor/field_visitor.rb +1115 -0
- data/lib/syntax_tree/visitor/json_visitor.rb +25 -1305
- data/lib/syntax_tree/visitor/match_visitor.rb +122 -0
- data/lib/syntax_tree/visitor/pretty_print_visitor.rb +35 -1163
- data/lib/syntax_tree/visitor.rb +6 -1
- data/lib/syntax_tree.rb +12 -18
- data/syntax_tree.gemspec +26 -21
- metadata +38 -5
- data/lib/syntax_tree/prettyprint.rb +0 -1156
@@ -1,1156 +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 |part|
|
550
|
-
case part
|
551
|
-
when IndentPart
|
552
|
-
flush_spaces.call
|
553
|
-
add_spaces.call(2)
|
554
|
-
when StringAlignPart
|
555
|
-
flush_spaces.call
|
556
|
-
next_value += part.n
|
557
|
-
next_length += part.n.length
|
558
|
-
when NumberAlignPart
|
559
|
-
last_spaces += 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, doc.break? ? MODE_BREAK : MODE_FLAT, doc.contents]
|
783
|
-
else
|
784
|
-
should_remeasure = false
|
785
|
-
next_cmd = [indent, MODE_FLAT, doc.contents]
|
786
|
-
|
787
|
-
if !doc.break? && fits?(next_cmd, commands, maxwidth - position)
|
788
|
-
commands << next_cmd
|
789
|
-
else
|
790
|
-
commands << [indent, MODE_BREAK, doc.contents]
|
791
|
-
end
|
792
|
-
end
|
793
|
-
when IfBreak
|
794
|
-
if mode == MODE_BREAK && doc.break_contents.any?
|
795
|
-
commands << [indent, mode, doc.break_contents]
|
796
|
-
elsif mode == MODE_FLAT && doc.flat_contents.any?
|
797
|
-
commands << [indent, mode, doc.flat_contents]
|
798
|
-
end
|
799
|
-
when LineSuffix
|
800
|
-
line_suffixes << [indent, mode, doc.contents, doc.priority]
|
801
|
-
when Breakable
|
802
|
-
if mode == MODE_FLAT
|
803
|
-
if doc.force?
|
804
|
-
# This line was forced into the output even if we were in flat mode,
|
805
|
-
# so we need to tell the next group that no matter what, it needs to
|
806
|
-
# remeasure because the previous measurement didn't accurately
|
807
|
-
# capture the entire expression (this is necessary for nested
|
808
|
-
# groups).
|
809
|
-
should_remeasure = true
|
810
|
-
else
|
811
|
-
buffer << doc.separator
|
812
|
-
position += doc.width
|
813
|
-
next
|
814
|
-
end
|
815
|
-
end
|
816
|
-
|
817
|
-
# If there are any commands in the line suffix buffer, then we're going
|
818
|
-
# to flush them now, as we are about to add a newline.
|
819
|
-
if line_suffixes.any?
|
820
|
-
commands << [indent, mode, doc]
|
821
|
-
commands += line_suffixes.sort_by(&line_suffix_sort)
|
822
|
-
line_suffixes = []
|
823
|
-
next
|
824
|
-
end
|
825
|
-
|
826
|
-
if !doc.indent?
|
827
|
-
buffer << newline
|
828
|
-
|
829
|
-
if indent.root
|
830
|
-
buffer << indent.root.value
|
831
|
-
position = indent.root.length
|
832
|
-
else
|
833
|
-
position = 0
|
834
|
-
end
|
835
|
-
else
|
836
|
-
position -= buffer.trim!
|
837
|
-
buffer << newline
|
838
|
-
buffer << indent.value
|
839
|
-
position = indent.length
|
840
|
-
end
|
841
|
-
when BreakParent
|
842
|
-
# do nothing
|
843
|
-
else
|
844
|
-
# Special case where the user has defined some way to get an extra doc
|
845
|
-
# node that we don't explicitly support into the list. In this case
|
846
|
-
# we're going to assume it's 0-width and just append it to the output
|
847
|
-
# buffer.
|
848
|
-
#
|
849
|
-
# This is useful behavior for putting marker nodes into the list so that
|
850
|
-
# you can know how things are getting mapped before they get printed.
|
851
|
-
buffer << doc
|
852
|
-
end
|
853
|
-
|
854
|
-
if commands.empty? && line_suffixes.any?
|
855
|
-
commands += line_suffixes.sort_by(&line_suffix_sort)
|
856
|
-
line_suffixes = []
|
857
|
-
end
|
858
|
-
end
|
859
|
-
|
860
|
-
# Reset the group stack and target array so that this pretty printer object
|
861
|
-
# can continue to be used before calling flush again if desired.
|
862
|
-
reset
|
863
|
-
end
|
864
|
-
|
865
|
-
# ----------------------------------------------------------------------------
|
866
|
-
# Markers node builders
|
867
|
-
# ----------------------------------------------------------------------------
|
868
|
-
|
869
|
-
# This says "you can break a line here if necessary", and a +width+\-column
|
870
|
-
# text +separator+ is inserted if a line is not broken at the point.
|
871
|
-
#
|
872
|
-
# If +separator+ is not specified, ' ' is used.
|
873
|
-
#
|
874
|
-
# If +width+ is not specified, +separator.length+ is used. You will have to
|
875
|
-
# specify this when +separator+ is a multibyte character, for example.
|
876
|
-
#
|
877
|
-
# By default, if the surrounding group is broken and a newline is inserted,
|
878
|
-
# the printer will indent the subsequent line up to the current level of
|
879
|
-
# indentation. You can disable this behavior with the +indent+ argument if
|
880
|
-
# that's not desired (rare).
|
881
|
-
#
|
882
|
-
# By default, when you insert a Breakable into the print tree, it only breaks
|
883
|
-
# the surrounding group when the group's contents cannot fit onto the
|
884
|
-
# remaining space of the current line. You can force it to break the
|
885
|
-
# surrounding group instead if you always want the newline with the +force+
|
886
|
-
# argument.
|
887
|
-
def breakable(
|
888
|
-
separator = " ",
|
889
|
-
width = separator.length,
|
890
|
-
indent: true,
|
891
|
-
force: false
|
892
|
-
)
|
893
|
-
doc = Breakable.new(separator, width, indent: indent, force: force)
|
894
|
-
|
895
|
-
target << doc
|
896
|
-
break_parent if force
|
897
|
-
|
898
|
-
doc
|
899
|
-
end
|
900
|
-
|
901
|
-
# This inserts a BreakParent node into the print tree which forces the
|
902
|
-
# surrounding and all parent group nodes to break.
|
903
|
-
def break_parent
|
904
|
-
doc = BreakParent.new
|
905
|
-
target << doc
|
906
|
-
|
907
|
-
groups.reverse_each do |group|
|
908
|
-
break if group.break?
|
909
|
-
group.break
|
910
|
-
end
|
911
|
-
|
912
|
-
doc
|
913
|
-
end
|
914
|
-
|
915
|
-
# This is similar to #breakable except the decision to break or not is
|
916
|
-
# determined individually.
|
917
|
-
#
|
918
|
-
# Two #fill_breakable under a group may cause 4 results:
|
919
|
-
# (break,break), (break,non-break), (non-break,break), (non-break,non-break).
|
920
|
-
# This is different to #breakable because two #breakable under a group
|
921
|
-
# may cause 2 results: (break,break), (non-break,non-break).
|
922
|
-
#
|
923
|
-
# The text +separator+ is inserted if a line is not broken at this point.
|
924
|
-
#
|
925
|
-
# If +separator+ is not specified, ' ' is used.
|
926
|
-
#
|
927
|
-
# If +width+ is not specified, +separator.length+ is used. You will have to
|
928
|
-
# specify this when +separator+ is a multibyte character, for example.
|
929
|
-
def fill_breakable(separator = " ", width = separator.length)
|
930
|
-
group { breakable(separator, width) }
|
931
|
-
end
|
932
|
-
|
933
|
-
# This inserts a Trim node into the print tree which, when printed, will clear
|
934
|
-
# all whitespace at the end of the output buffer. This is useful for the rare
|
935
|
-
# case where you need to delete printed indentation and force the next node
|
936
|
-
# to start at the beginning of the line.
|
937
|
-
def trim
|
938
|
-
doc = Trim.new
|
939
|
-
target << doc
|
940
|
-
|
941
|
-
doc
|
942
|
-
end
|
943
|
-
|
944
|
-
# ----------------------------------------------------------------------------
|
945
|
-
# Container node builders
|
946
|
-
# ----------------------------------------------------------------------------
|
947
|
-
|
948
|
-
# Groups line break hints added in the block. The line break hints are all to
|
949
|
-
# be used or not.
|
950
|
-
#
|
951
|
-
# If +indent+ is specified, the method call is regarded as nested by
|
952
|
-
# nest(indent) { ... }.
|
953
|
-
#
|
954
|
-
# If +open_object+ is specified, <tt>text(open_object, open_width)</tt> is
|
955
|
-
# called before grouping. If +close_object+ is specified,
|
956
|
-
# <tt>text(close_object, close_width)</tt> is called after grouping.
|
957
|
-
def group(
|
958
|
-
indent = 0,
|
959
|
-
open_object = "",
|
960
|
-
close_object = "",
|
961
|
-
open_width = open_object.length,
|
962
|
-
close_width = close_object.length
|
963
|
-
)
|
964
|
-
text(open_object, open_width) if open_object != ""
|
965
|
-
|
966
|
-
doc = Group.new(groups.last.depth + 1)
|
967
|
-
groups << doc
|
968
|
-
target << doc
|
969
|
-
|
970
|
-
with_target(doc.contents) do
|
971
|
-
if indent != 0
|
972
|
-
nest(indent) { yield }
|
973
|
-
else
|
974
|
-
yield
|
975
|
-
end
|
976
|
-
end
|
977
|
-
|
978
|
-
groups.pop
|
979
|
-
text(close_object, close_width) if close_object != ""
|
980
|
-
|
981
|
-
doc
|
982
|
-
end
|
983
|
-
|
984
|
-
# A small DSL-like object used for specifying the alternative contents to be
|
985
|
-
# printed if the surrounding group doesn't break for an IfBreak node.
|
986
|
-
class IfBreakBuilder
|
987
|
-
attr_reader :builder, :if_break
|
988
|
-
|
989
|
-
def initialize(builder, if_break)
|
990
|
-
@builder = builder
|
991
|
-
@if_break = if_break
|
992
|
-
end
|
993
|
-
|
994
|
-
def if_flat(&block)
|
995
|
-
builder.with_target(if_break.flat_contents, &block)
|
996
|
-
end
|
997
|
-
end
|
998
|
-
|
999
|
-
# Inserts an IfBreak node with the contents of the block being added to its
|
1000
|
-
# list of nodes that should be printed if the surrounding node breaks. If it
|
1001
|
-
# doesn't, then you can specify the contents to be printed with the #if_flat
|
1002
|
-
# method used on the return object from this method. For example,
|
1003
|
-
#
|
1004
|
-
# q.if_break { q.text('do') }.if_flat { q.text('{') }
|
1005
|
-
#
|
1006
|
-
# In the example above, if the surrounding group is broken it will print 'do'
|
1007
|
-
# and if it is not it will print '{'.
|
1008
|
-
def if_break
|
1009
|
-
doc = IfBreak.new
|
1010
|
-
target << doc
|
1011
|
-
|
1012
|
-
with_target(doc.break_contents) { yield }
|
1013
|
-
IfBreakBuilder.new(self, doc)
|
1014
|
-
end
|
1015
|
-
|
1016
|
-
# This is similar to if_break in that it also inserts an IfBreak node into the
|
1017
|
-
# print tree, however it's starting from the flat contents, and cannot be used
|
1018
|
-
# to build the break contents.
|
1019
|
-
def if_flat
|
1020
|
-
doc = IfBreak.new
|
1021
|
-
target << doc
|
1022
|
-
|
1023
|
-
with_target(doc.flat_contents) { yield }
|
1024
|
-
end
|
1025
|
-
|
1026
|
-
# Very similar to the #nest method, this indents the nested content by one
|
1027
|
-
# level by inserting an Indent node into the print tree. The contents of the
|
1028
|
-
# node are determined by the block.
|
1029
|
-
def indent
|
1030
|
-
doc = Indent.new
|
1031
|
-
target << doc
|
1032
|
-
|
1033
|
-
with_target(doc.contents) { yield }
|
1034
|
-
doc
|
1035
|
-
end
|
1036
|
-
|
1037
|
-
# Inserts a LineSuffix node into the print tree. The contents of the node are
|
1038
|
-
# determined by the block.
|
1039
|
-
def line_suffix(priority: LineSuffix::DEFAULT_PRIORITY)
|
1040
|
-
doc = LineSuffix.new(priority: priority)
|
1041
|
-
target << doc
|
1042
|
-
|
1043
|
-
with_target(doc.contents) { yield }
|
1044
|
-
doc
|
1045
|
-
end
|
1046
|
-
|
1047
|
-
# Increases left margin after newline with +indent+ for line breaks added in
|
1048
|
-
# the block.
|
1049
|
-
def nest(indent)
|
1050
|
-
doc = Align.new(indent: indent)
|
1051
|
-
target << doc
|
1052
|
-
|
1053
|
-
with_target(doc.contents) { yield }
|
1054
|
-
doc
|
1055
|
-
end
|
1056
|
-
|
1057
|
-
# This adds +object+ as a text of +width+ columns in width.
|
1058
|
-
#
|
1059
|
-
# If +width+ is not specified, object.length is used.
|
1060
|
-
def text(object = "", width = object.length)
|
1061
|
-
doc = target.last
|
1062
|
-
|
1063
|
-
unless Text === doc
|
1064
|
-
doc = Text.new
|
1065
|
-
target << doc
|
1066
|
-
end
|
1067
|
-
|
1068
|
-
doc.add(object: object, width: width)
|
1069
|
-
doc
|
1070
|
-
end
|
1071
|
-
|
1072
|
-
# ----------------------------------------------------------------------------
|
1073
|
-
# Internal APIs
|
1074
|
-
# ----------------------------------------------------------------------------
|
1075
|
-
|
1076
|
-
# A convenience method used by a lot of the print tree node builders that
|
1077
|
-
# temporarily changes the target that the builders will append to.
|
1078
|
-
def with_target(target)
|
1079
|
-
previous_target, @target = @target, target
|
1080
|
-
yield
|
1081
|
-
@target = previous_target
|
1082
|
-
end
|
1083
|
-
|
1084
|
-
private
|
1085
|
-
|
1086
|
-
# This method returns a boolean as to whether or not the remaining commands
|
1087
|
-
# fit onto the remaining space on the current line. If we finish printing
|
1088
|
-
# all of the commands or if we hit a newline, then we return true. Otherwise
|
1089
|
-
# if we continue printing past the remaining space, we return false.
|
1090
|
-
def fits?(next_command, rest_commands, remaining)
|
1091
|
-
# This is the index in the remaining commands that we've handled so far.
|
1092
|
-
# We reverse through the commands and add them to the stack if we've run
|
1093
|
-
# out of nodes to handle.
|
1094
|
-
rest_index = rest_commands.length
|
1095
|
-
|
1096
|
-
# This is our stack of commands, very similar to the commands list in the
|
1097
|
-
# print method.
|
1098
|
-
commands = [next_command]
|
1099
|
-
|
1100
|
-
# This is our output buffer, really only necessary to keep track of
|
1101
|
-
# because we could encounter a Trim doc node that would actually add
|
1102
|
-
# remaining space.
|
1103
|
-
fit_buffer = buffer.class.new
|
1104
|
-
|
1105
|
-
while remaining >= 0
|
1106
|
-
if commands.empty?
|
1107
|
-
return true if rest_index == 0
|
1108
|
-
|
1109
|
-
rest_index -= 1
|
1110
|
-
commands << rest_commands[rest_index]
|
1111
|
-
next
|
1112
|
-
end
|
1113
|
-
|
1114
|
-
indent, mode, doc = commands.pop
|
1115
|
-
|
1116
|
-
case doc
|
1117
|
-
when Text
|
1118
|
-
doc.objects.each { |object| fit_buffer << object }
|
1119
|
-
remaining -= doc.width
|
1120
|
-
when Array
|
1121
|
-
doc.reverse_each { |part| commands << [indent, mode, part] }
|
1122
|
-
when Indent
|
1123
|
-
commands << [indent.indent, mode, doc.contents]
|
1124
|
-
when Align
|
1125
|
-
commands << [indent.align(doc.indent), mode, doc.contents]
|
1126
|
-
when Trim
|
1127
|
-
remaining += fit_buffer.trim!
|
1128
|
-
when Group
|
1129
|
-
commands << [indent, doc.break? ? MODE_BREAK : mode, doc.contents]
|
1130
|
-
when IfBreak
|
1131
|
-
if mode == MODE_BREAK && doc.break_contents.any?
|
1132
|
-
commands << [indent, mode, doc.break_contents]
|
1133
|
-
elsif mode == MODE_FLAT && doc.flat_contents.any?
|
1134
|
-
commands << [indent, mode, doc.flat_contents]
|
1135
|
-
end
|
1136
|
-
when Breakable
|
1137
|
-
if mode == MODE_FLAT && !doc.force?
|
1138
|
-
fit_buffer << doc.separator
|
1139
|
-
remaining -= doc.width
|
1140
|
-
next
|
1141
|
-
end
|
1142
|
-
|
1143
|
-
return true
|
1144
|
-
end
|
1145
|
-
end
|
1146
|
-
|
1147
|
-
false
|
1148
|
-
end
|
1149
|
-
|
1150
|
-
# Resets the group stack and target array so that this pretty printer object
|
1151
|
-
# can continue to be used before calling flush again if desired.
|
1152
|
-
def reset
|
1153
|
-
@groups = [Group.new(0)]
|
1154
|
-
@target = @groups.last.contents
|
1155
|
-
end
|
1156
|
-
end
|