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