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.
@@ -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