syntax_tree 2.3.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,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