oppen 0.9.7 → 1.0.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.
data/lib/wadler/print.rb CHANGED
@@ -4,117 +4,297 @@
4
4
  module Oppen
5
5
  # Wadler.
6
6
  class Wadler
7
+ # @return [Config]
8
+ # The printer's configuration, altering its behavior.
7
9
  attr_reader :config
10
+ # @return [Integer]
11
+ # the current indentation amount.
8
12
  attr_reader :current_indent
9
- attr_reader :space
13
+ # @return [String]
14
+ # the new line string, e.g. `\n`.
10
15
  attr_reader :new_line
16
+ # @return [Object]
17
+ # the output string buffer. It should have a `write` and `string` methods.
11
18
  attr_reader :out
19
+ # @return [Proc]
20
+ # space generator, a callable.
21
+ attr_reader :space_gen
22
+ # @return [Array<Token>]
23
+ # the tokens list that is being built.
12
24
  attr_reader :tokens
25
+ # @return [String]
26
+ # the whitespace character. Used to trim trailing whitespaces.
13
27
  attr_reader :whitespace
28
+ # @return [Integer]
29
+ # maximum line width.
14
30
  attr_reader :width
15
31
 
16
- # @param config [Oppen::Config]
17
- # @param space [String, Proc] could be a String or a callable.
18
- # If it's a string, spaces will be generated with the the
19
- # lambda `->(n){ n * space }`, where `n` is the number of columns
20
- # to indent.
21
- # If it's a callable, it will receive `n` and it needs to return
22
- # a string.
23
- # @param new_line [String]
24
- # @param out [Object] should have a write and string method
25
- # @param width [Integer]
26
- # @param whitespace [String] the whitespace character. Used to trim trailing whitespaces.
32
+ # @param base_indent [Integer]
33
+ # the starting indentation level for the whole printer.
34
+ # @param config [Config]
35
+ # to customize the printer's behavior.
36
+ # @param indent [Integer]
37
+ # the default indentation amount for {group} and {nest}.
38
+ # @param new_line [String]
39
+ # the new line String.
40
+ # @param out [Object]
41
+ # the output string buffer. It should have a `write` and `string` methods.
42
+ # @param space_gen [String, Proc]
43
+ # indentation string or a string generator.
44
+ # - If a `String`, spaces will be generated with the the lambda
45
+ # `->(n){ space * n }`, where `n` is the number of columns to indent.
46
+ # - If a `Proc`, it will receive `n` and it needs to return a `String`.
47
+ # @param whitespace [String] the whitespace character. Used to trim trailing whitespaces.
48
+ # @param width [Integer] maximum line width desired.
49
+ #
27
50
  # @see Token::Whitespace
28
- def initialize(config: Config.wadler, space: ' ',
29
- new_line: "\n", out: StringIO.new,
30
- width: 80, whitespace: ' ')
51
+ def initialize(base_indent: 0, config: Config.wadler, indent: 0, new_line: "\n",
52
+ out: StringIO.new, space_gen: ' ',
53
+ whitespace: ' ', width: 80)
31
54
  @config = config
32
- @current_indent = 0
33
- @space = space
34
- @width = width
55
+ @current_indent = base_indent
56
+ @indent = indent
35
57
  @new_line = new_line
36
58
  @out = out
59
+ @space_gen = space_gen
37
60
  @tokens = []
38
61
  @whitespace = whitespace
62
+ @width = width
39
63
  end
40
64
 
41
- # Add missing Begin, End or EOF tokens.
65
+ # Add missing {Token::Begin}, {Token::End} or {Token::EOF}.
66
+ #
42
67
  # @return [Nil]
43
68
  def add_missing_begin_and_end
44
- if !tokens.first.is_a? Token::Begin
45
- tokens.unshift Oppen.begin_consistent(offset: 0)
46
- tokens << Oppen.end
47
- end
69
+ tokens.unshift Oppen.begin_consistent(offset: 0)
70
+ tokens << Oppen.end
48
71
  tokens << Oppen.eof if !tokens.last.is_a?(Oppen::Token::EOF)
49
72
  end
50
73
 
51
- # Generate the output string of the built list of tokens
52
- # using Oppen's pretty printing algorithm.
74
+ # Call this to extract the final pretty-printed output.
53
75
  #
54
76
  # @return [String]
55
77
  def output
56
78
  add_missing_begin_and_end
57
- Oppen.print(tokens:, new_line:, config:, space:, out:, width:)
79
+ Oppen.print(
80
+ tokens:,
81
+ new_line:,
82
+ config:,
83
+ space: space_gen,
84
+ out:,
85
+ width:,
86
+ )
58
87
  end
59
88
 
60
- # Generate the the list of Wadler commands needed to build the built
61
- # list of tokens.
89
+ # Convert a list of tokens to its wadler representation.
90
+ #
91
+ # This method reverse engineers a tokens list to transform it into Wadler
92
+ # printing commands. It can be particularly useful when debugging a black
93
+ # box program.
94
+ #
95
+ # @option kwargs [Integer] :base_indent
96
+ # the base indentation amount of the output.
97
+ # @option kwargs [String] :printer_name
98
+ # the name of the Wadler instance in the output.
99
+ #
100
+ # @example
101
+ # out = Oppen::Wadler.new
102
+ # out.text('Hello World!')
103
+ # out.show_print_commands(out_name: 'out')
104
+ #
105
+ # # =>
106
+ # # out.group(:consistent, indent: 0) {
107
+ # # out.text("Hello World!", width: 12)
108
+ # # }
62
109
  #
63
110
  # @return [String]
64
- def show_print_commands(**)
111
+ def show_print_commands(**kwargs)
65
112
  add_missing_begin_and_end
66
- Oppen.tokens_to_wadler(tokens, **)
113
+ Oppen.tokens_to_wadler(tokens, **kwargs)
67
114
  end
68
115
 
69
- # @param indent [Integer] group indentation
70
- # @param open_obj [String] group opening delimiter
71
- # @param close_obj [String] group closing delimiter
72
- # @param break_type [Oppen::Token::BreakType] group breaking type
116
+ # Create a new group.
73
117
  #
74
- # @yield the block of text in a group
118
+ # @param indent [Integer]
119
+ # indentation.
120
+ # @param delim [Nil|String|Symbol|Array<Nil, String, Symbol>]
121
+ # delimiters, to be printed at the start and the end of the group:
122
+ # - If it's nil, nothing will be printed
123
+ # - If it's a Strings or a Symbol, it will be printed at both positions.
124
+ # - If it's an Array of many items, the first two elements will be used
125
+ # for the start and end of the group.
126
+ # @param break_type [Token::BreakType]
127
+ # break type.
75
128
  #
76
- # @return [Nil]
77
- def group(indent = 0, open_obj = '', close_obj = '',
78
- break_type = Oppen::Token::BreakType::CONSISTENT)
79
- raise ArgumentError, "#{open_obj.nil? ? 'open_obj' : 'close_obj'} cannot be nil" \
80
- if open_obj.nil? || close_obj.nil?
129
+ # @yield
130
+ # the block of text in a group.
131
+ #
132
+ # @example 1 String Delimiter
133
+ # out = Oppen::Wadler.new
134
+ # out
135
+ # .text('a')
136
+ # .group(indent: 2, delim: '|') {
137
+ # out.break.text 'b'
138
+ # }
139
+ # puts out.output
140
+ #
141
+ # # =>
142
+ # # a
143
+ # # |
144
+ # # b
145
+ # # |
146
+ #
147
+ # @example 1 Delimiter in Array
148
+ # out = Oppen::Wadler.new
149
+ # out
150
+ # .text('a')
151
+ # .group(indent: 2, delim: ['|']) {
152
+ # out.break.text 'b'
153
+ # }
154
+ # puts out.output
155
+ #
156
+ # # =>
157
+ # # a
158
+ # # |
159
+ # # b
160
+ #
161
+ # @example 2 Delimiters
162
+ # out = Oppen::Wadler.new
163
+ # out
164
+ # .text('a')
165
+ # .group(indent: 2, delim: %i[{ }]) {
166
+ # out.break.text 'b'
167
+ # }
168
+ # puts out.output
169
+ #
170
+ # # =>
171
+ # # a
172
+ # # {
173
+ # # b
174
+ # # }
175
+ #
176
+ # @example Consistent Breaking
177
+ # out = Oppen::Wadler.new
178
+ # out.group(:consistent) {
179
+ # out.text('a').break.text('b').breakable.text('c')
180
+ # }
181
+ # puts out.output
182
+ #
183
+ # # =>
184
+ # # a
185
+ # # b
186
+ # # c
187
+ #
188
+ # @example Inconsistent Breaking
189
+ # out = Oppen::Wadler.new
190
+ # out.group(:inconsistent) {
191
+ # out.text('a').break.text('b').breakable.text('c')
192
+ # }
193
+ # puts out.output
194
+ #
195
+ # # =>
196
+ # # a
197
+ # # b c
198
+ #
199
+ # @return [self]
200
+ #
201
+ # @see Oppen.begin_consistent
202
+ # @see Oppen.begin_inconsistent
203
+ def group(break_type = :consistent, delim: nil, indent: @indent)
204
+ lft, rgt =
205
+ case delim
206
+ in nil then ['', '']
207
+ in String | Symbol then [delim, delim]
208
+ in Array then delim.values_at(0, 1).map(&:to_s)
209
+ end
81
210
 
82
211
  tokens <<
83
212
  case break_type
84
- in Oppen::Token::BreakType::CONSISTENT
213
+ in :consistent
85
214
  Oppen.begin_consistent(offset: indent)
86
- in Oppen::Token::BreakType::INCONSISTENT
215
+ in :inconsistent
87
216
  Oppen.begin_inconsistent(offset: indent)
88
217
  end
89
218
 
90
- if !open_obj.empty?
219
+ if !lft.empty?
91
220
  self.break
92
- text(open_obj)
221
+ text lft
93
222
  end
94
223
 
95
224
  yield
96
225
 
97
- if !close_obj.empty?
226
+ if !rgt.empty?
98
227
  self.break
99
- text(close_obj)
228
+ text rgt
100
229
  end
101
230
 
102
231
  tokens << Oppen.end
232
+
233
+ self
234
+ end
235
+
236
+ # An alias for `group(:consistent, ...)`
237
+ def consistent(...)
238
+ group(:consistent, ...)
103
239
  end
104
240
 
105
- # @param indent [Integer] nest indentation
106
- # @param open_obj [String] nest opening delimiter
107
- # @param close_obj [String] nest closing delimiter
241
+ # An alias for `group(:inconsistent, ...)`
242
+ def inconsistent(...)
243
+ group(:inconsistent, ...)
244
+ end
245
+
246
+ # Create a new non-strict {group}.
108
247
  #
109
- # @return [Nil]
110
- def nest(indent, open_obj = '', close_obj = '')
111
- raise ArgumentError, "#{open_obj.nil? ? 'open_obj' : 'close_obj'} cannot be nil" \
112
- if open_obj.nil? || close_obj.nil?
248
+ # {group}s isolate breaking decisions, and in that sense they're considered
249
+ # strict; e.g. when a breakable is transformed into an actual break, its
250
+ # parent {group} might not get broken if the result could fit on the line.
251
+ #
252
+ # This is not the case with {nest}: if the same breakable was in a {nest}, the
253
+ # {group} containing the {nest} will also be broken.
254
+ #
255
+ # @note indentation cannot happen if there are no breaks in the {nest}.
256
+ #
257
+ # @note a {nest} will not forcibly indent its content if the break type of
258
+ # the enclosing {group} is `:inconsistent`.
259
+ #
260
+ # @param delim [Nil|String|Symbol|Array<Nil, String, Symbol>]
261
+ # delimiters, to be printed at the start and the end of the group:
262
+ # - `nil` is always the empty string.
263
+ # - If it's a Strings or a Symbol, it will be printed at both positions.
264
+ # - If it's an Array of many items, the first two elements will be used
265
+ # for the start and end of the group.
266
+ # @param indent [Integer]
267
+ # indentation.
268
+ #
269
+ # @yield
270
+ # the block of text in a nest.
271
+ #
272
+ # @example
273
+ # out = Oppen::Wadler.new
274
+ # out.nest(delim: %i[{ }], indent: 2) {
275
+ # out.text('a').break.text('b')
276
+ # }
277
+ # puts out.output
278
+ #
279
+ # # =>
280
+ # # {
281
+ # # a
282
+ # # b
283
+ # # }
284
+ #
285
+ # @return [self]
286
+ def nest(delim: nil, indent: @indent)
287
+ lft, rgt =
288
+ case delim
289
+ in nil then ['', '']
290
+ in String | Symbol then [delim, delim]
291
+ in Array then delim.values_at(0, 1).map(&:to_s)
292
+ end
113
293
 
114
294
  @current_indent += indent
115
295
 
116
- if !open_obj.empty?
117
- text(open_obj)
296
+ if !lft.empty?
297
+ text lft
118
298
  self.break
119
299
  end
120
300
 
@@ -124,15 +304,20 @@ module Oppen
124
304
  @current_indent -= indent
125
305
  end
126
306
 
127
- return if close_obj.empty?
307
+ if !rgt.empty?
308
+ self.break
309
+ text rgt
310
+ end
128
311
 
129
- self.break
130
- text(close_obj)
312
+ self
131
313
  end
132
314
 
315
+ # Create a new text element.
316
+ #
133
317
  # @param value [String]
318
+ # the value of the token.
134
319
  #
135
- # @return [Nil]
320
+ # @return [self]
136
321
  def text(value, width: value.length)
137
322
  if config.trim_trailing_whitespaces? && value.match(/((?:#{Regexp.escape(whitespace)})+)\z/)
138
323
  match = Regexp.last_match(1)
@@ -144,94 +329,533 @@ module Oppen
144
329
  else
145
330
  tokens << Oppen.string(value, width:)
146
331
  end
332
+ self
147
333
  end
148
334
 
149
- # @param str [String]
150
- # @param line_continuation [String] If a new line is needed display this string before the new line
335
+ # Create a new breakable element.
151
336
  #
152
- # @return [Nil]
153
- def breakable(str = ' ', width: str.length, line_continuation: '')
337
+ # @param str [String]
338
+ # the value of the token that will be displayed if no new line is needed.
339
+ # @param line_continuation [String]
340
+ # printed before the line break.
341
+ # @param width [Integer]
342
+ # the width of the token.
343
+ #
344
+ # @return [self]
345
+ #
346
+ # @see Wadler#break example on `line_continuation`.
347
+ def breakable(str = ' ', line_continuation: '', width: str.length)
154
348
  tokens << Oppen.break(str, width:, line_continuation:, offset: current_indent)
349
+ self
155
350
  end
156
351
 
157
- # @param line_continuation [String] If a new line is needed display this string before the new line
352
+ # Create a new break element.
158
353
  #
159
- # @return [Nil]
354
+ # @param line_continuation [String]
355
+ # printed before the line break.
356
+ #
357
+ # @example
358
+ # out = Oppen::Wadler.new
359
+ # out.text 'a'
360
+ # out.break
361
+ # out.text 'b'
362
+ # out.break line_continuation: '#'
363
+ # out.text 'c'
364
+ # puts out.output
365
+ #
366
+ # # =>
367
+ # # a
368
+ # # b#
369
+ # # c
370
+ #
371
+ # @return [self]
160
372
  def break(line_continuation: '')
161
373
  tokens << Oppen.line_break(line_continuation:, offset: current_indent)
374
+ self
162
375
  end
163
376
 
164
- # @!group Helpers
377
+ # A convenient way to avoid breaking chains of calls.
378
+ #
379
+ # @example
380
+ # out
381
+ # .do { fn_call(fn_arg) }
382
+ # .breakable
383
+ # .text('=')
384
+ # .breakable
385
+ # .do { fn_call(fn_arg) }
386
+ #
387
+ # @yield to execute the passed block
388
+ #
389
+ # @return [self]
390
+ def do
391
+ yield
392
+ self
393
+ end
165
394
 
166
- # Set a base indenetaion level to the printer.
395
+ # A means to wrap a piece of code in several ways.
167
396
  #
168
- # @param indent [Integer]
397
+ # @example
398
+ # out
399
+ # .wrap {
400
+ # # all printing instructions here will be deferred.
401
+ # # they will be executed in `when` blocks by calling the `wrapped`.
402
+ # out.text(...)
403
+ # # ...
404
+ # } # This is "wrapped".
405
+ # .when(cond1){ |wrapped|
406
+ # # when cond1 is true you execute this block.
407
+ # out.text("before wrapped")
408
+ # # call the wrapped
409
+ # wrapped.call
410
+ # # and continue printing
411
+ # out.text("after wrapped)
412
+ # }
413
+ # .when(cond2){ |wrapped|
414
+ # # and you cand define many conditions.
415
+ # }
416
+ # .end
169
417
  #
170
- # @return [Nil]
171
- def base_indent(indent = 0)
172
- @current_indent = indent if !indent.nil?
418
+ # @example Calling `end` is not needed if there's another call after the last `when`:
419
+ # out
420
+ # .wrap{...} # This is "wrapped".
421
+ # .when(cond1){ |wrapped| ... }
422
+ # .when(cond2){ |wrapped| ... }
423
+ # .text('foo')
424
+ #
425
+ # @return [Wrap]
426
+ def wrap(&blk)
427
+ Wrap.new(blk)
173
428
  end
174
429
 
175
- # Open a consistent group.
430
+ # Produce a separated list.
176
431
  #
177
- # @param inconsistent [Boolean]
178
- # @param indent [Integer]
432
+ # @example Consistent Breaking
433
+ # puts out.separate((1..3).map(&:to_s), ',') { |i| out.text i}
179
434
  #
180
- # @return [Nil]
181
- def group_open(inconsistent: false, indent: 0)
182
- tokens <<
183
- if inconsistent
184
- Oppen.begin_inconsistent(offset: indent)
435
+ # # =>
436
+ # # 1,
437
+ # # 2,
438
+ # # 3
439
+ #
440
+ # @example Inconsistent Breaking
441
+ # puts out.separate((1..3).map(&:to_s), ',', break_type: :inconsistent) { |i| out.text i}
442
+ #
443
+ # # =>
444
+ # # 1, 2,
445
+ # # 3
446
+ #
447
+ # @param args [String]
448
+ # a list of values.
449
+ # @param sep [String]
450
+ # a separator.
451
+ # @param breakable [String|Nil]
452
+ # adds a `breakable` after the separator.
453
+ # @param break_pos [Symbol]
454
+ # whether to break :before or :after the seraparator.
455
+ # @param break_type [Symbol|Nil]
456
+ # whether the break is :consistent or :inconsistent.
457
+ # If nil is given, the tokens will not be surrounded by a group.
458
+ # @param indent [Boolean|Integer]
459
+ # - If `true`, indent by @indent.
460
+ # - If an 'Integer', indent by its value.
461
+ # @param force_break [Boolean]
462
+ # adds a `break` after the separator.
463
+ # @param line_continuation [String]
464
+ # string to display before new line.
465
+ #
466
+ # @yield to execute the passed block.
467
+ #
468
+ # @return [self]
469
+ def separate(args, sep, breakable: ' ', break_pos: :after,
470
+ break_type: nil, indent: false,
471
+ force_break: false, line_continuation: '')
472
+ if args.is_a?(Enumerator) ? args.count == 1 : args.length == 1
473
+ yield(*args[0])
474
+ return self
475
+ end
476
+
477
+ first = true
478
+ wrap {
479
+ wrap {
480
+ args&.each do |*as|
481
+ if first
482
+ breakable '' if !line_continuation.empty? && break_pos == :after
483
+ first = false
484
+ elsif break_pos == :after
485
+ text sep
486
+ breakable(breakable, line_continuation:) if breakable && !force_break
487
+ self.break(line_continuation:) if force_break
488
+ else
489
+ breakable(breakable, line_continuation:) if breakable && !force_break
490
+ self.break(line_continuation:) if force_break
491
+ text sep
492
+ end
493
+ yield(*as)
494
+ end
495
+ }
496
+ .when(break_type) { |body|
497
+ group(break_type, indent: 0) {
498
+ body.()
499
+ }
500
+ }
501
+ .end
502
+ }
503
+ .when(indent) { |body|
504
+ nest(indent: indent.is_a?(Integer) ? indent : @indent) {
505
+ body.()
506
+ }
507
+ }.end
508
+ breakable('', line_continuation:) if !line_continuation.empty? && !break_type
509
+
510
+ self
511
+ end
512
+
513
+ # A shorhand for `text ' '`.
514
+ #
515
+ # @return [self]
516
+ def space
517
+ text ' '
518
+ end
519
+
520
+ # Surround a block with +lft+ and +rgt+
521
+ #
522
+ # @param lft [String] lft
523
+ # left surrounding string.
524
+ # @param rgt [String] rgt
525
+ # right surrounding string.
526
+ #
527
+ # @yield the passed block to be surrounded with `lft` and `rgt`.
528
+ #
529
+ # @option opts [Boolean] :group (true)
530
+ # whether to create a group enclosing `lft`, `rgt`, and the passed block.
531
+ # @option opts [Boolean] :indent (@indent)
532
+ # whether to indent the passed block.
533
+ # @option opts [String] :lft_breakable ('')
534
+ # left breakable string.
535
+ # @option opts [Boolean] :lft_can_break (true)
536
+ # injects `break` or `breakable` only if true;
537
+ # i.e. `lft_breakable` will be ignored if false.
538
+ # @option opts [Boolean] :lft_force_break (false)
539
+ # force break instead of using `lft_breakable`.
540
+ # @option opts [String] :rgt_breakable ('')
541
+ # right breakable string.
542
+ # @option opts [Boolean] :rgt_can_break (true)
543
+ # injects `break` or `breakable` only if true.
544
+ # i.e. `rgt_breakable` will be ignored if false.
545
+ # @option opts [Boolean] :rgt_force_break (false)
546
+ # force break instead of using `rgt_breakable`.
547
+ #
548
+ # @return [self]
549
+ def surround(lft, rgt, **opts)
550
+ group = opts.fetch(:group, true)
551
+ group_open(break_type: :inconsistent) if group
552
+
553
+ text lft if lft
554
+
555
+ indent = opts.fetch(:indent, @indent)
556
+ nest_open(indent:)
557
+
558
+ lft_breakable = opts.fetch(:lft_breakable, '')
559
+ lft_can_break = opts.fetch(:lft_can_break, true)
560
+ lft_force_break = opts.fetch(:lft_force_break, false)
561
+ if lft && lft_can_break
562
+ if lft_force_break
563
+ self.break
185
564
  else
186
- Oppen.begin_consistent(offset: indent)
565
+ breakable lft_breakable
566
+ end
567
+ end
568
+
569
+ if block_given?
570
+ yield
571
+ end
572
+
573
+ nest_close
574
+
575
+ rgt_breakable = opts.fetch(:rgt_breakable, '')
576
+ rgt_can_break = opts.fetch(:rgt_can_break, true)
577
+ rgt_force_break = opts.fetch(:rgt_force_break, false)
578
+ if rgt
579
+ if rgt_can_break
580
+ if rgt_force_break
581
+ self.break
582
+ else
583
+ breakable rgt_breakable
584
+ end
187
585
  end
586
+ text rgt
587
+ end
588
+
589
+ group_close if group
590
+
591
+ self
592
+ end
593
+
594
+ # @!group Convenience Methods Built On {separate}
595
+
596
+ # Separate args into lines.
597
+ #
598
+ # This is a wrapper around {separate} where `breakable: true`.
599
+ #
600
+ # @see [separate]
601
+ def lines(*args, **kwargs, &)
602
+ separate(*args, **kwargs.merge(force_break: true), &)
603
+ end
604
+
605
+ # Concatenates args.
606
+ #
607
+ # This is a wrapper around {separate} where `breakable: false`.
608
+ #
609
+ # @see [separate]
610
+ def concat(*args, **kwargs, &)
611
+ separate(*args, **kwargs.merge(breakable: false), &)
612
+ end
613
+
614
+ # @!endgroup
615
+ # @!group Convenience Methods Built On {surround}
616
+
617
+ # YARD doesn't drop into blocks, so we can't use metaprogramming
618
+ # to generate all these functions, so we're copy-pastring.
619
+
620
+ # {surround} with `< >`. New lines can appear after and before the delimiters.
621
+ #
622
+ # @param padding [String] ('')
623
+ # Passed to `lft_breakable` and `rgt_breakable`.
624
+ #
625
+ # @return [self]
626
+ def angles(padding: '', **kwargs, &block)
627
+ surround(
628
+ '<', '>',
629
+ **kwargs.merge(lft_breakable: padding, rgt_breakable: padding),
630
+ &block
631
+ )
632
+ end
633
+
634
+ # {surround} with `< >`. New lines cannot appear after and before the delimiters.
635
+ #
636
+ # @return [self]
637
+ def angles_break_both(**kwargs, &)
638
+ angles(**kwargs.merge(lft_force_break: true, rgt_force_break: true), &)
639
+ end
640
+
641
+ # {surround} with `< >`. New lines will appear after and before the delimiters.
642
+ #
643
+ # @return [self]
644
+ def angles_break_none(**kwargs, &)
645
+ angles(**kwargs.merge(lft_can_break: false, rgt_can_break: false), &)
646
+ end
647
+
648
+ # {surround} with `{ }`. New lines can appear after and before the delimiters.
649
+ #
650
+ # @param padding [String] ('')
651
+ # Passed to `lft_breakable` and `rgt_breakable`.
652
+ #
653
+ # @return [self]
654
+ def braces(padding: '', **kwargs, &block)
655
+ surround(
656
+ '{', '}',
657
+ **kwargs.merge(lft_breakable: padding, rgt_breakable: padding),
658
+ &block
659
+ )
660
+ end
661
+
662
+ # {surround} with `{ }`. New lines cannot appear after and before the delimiters.
663
+ #
664
+ # @return [self]
665
+ def braces_break_both(**kwargs, &)
666
+ braces(**kwargs.merge(lft_force_break: true, rgt_force_break: true), &)
667
+ end
668
+
669
+ # {surround} with `{ }`. New lines will appear after and before the delimiters.
670
+ #
671
+ # @return [self]
672
+ def braces_break_none(**kwargs, &)
673
+ braces(**kwargs.merge(lft_can_break: false, rgt_can_break: false), &)
674
+ end
675
+
676
+ # {surround} with `[ ]`. New lines can appear after and before the delimiters.
677
+ #
678
+ # @param padding [String] ('')
679
+ # Passed to `lft_breakable` and `rgt_breakable`.
680
+ #
681
+ # @return [self]
682
+ def brackets(padding: '', **kwargs, &block)
683
+ surround(
684
+ '[', ']',
685
+ **kwargs.merge(lft_breakable: padding, rgt_breakable: padding),
686
+ &block
687
+ )
688
+ end
689
+
690
+ # {surround} with `[ ]`. New lines cannot appear after and before the delimiters.
691
+ #
692
+ # @return [self]
693
+ def brackets_break_both(**kwargs, &)
694
+ brackets(**kwargs.merge(lft_force_break: true, rgt_force_break: true), &)
695
+ end
696
+
697
+ # {surround} with `[ ]`. New lines will appear after and before the delimiters.
698
+ #
699
+ # @return [self]
700
+ def brackets_break_none(**kwargs, &)
701
+ brackets(**kwargs.merge(lft_can_break: false, rgt_can_break: false), &)
702
+ end
703
+
704
+ # {surround} with `( )`. New lines can appear after and before the delimiters.
705
+ #
706
+ # @param padding [String] ('')
707
+ # Passed to `lft_breakable` and `rgt_breakable`.
708
+ #
709
+ # @return [self]
710
+ def parens(padding: '', **kwargs, &block)
711
+ surround(
712
+ '(', ')',
713
+ **kwargs.merge(lft_breakable: padding, rgt_breakable: padding),
714
+ &block
715
+ )
716
+ end
717
+
718
+ # {surround} with `( )`. New lines cannot appear after and before the delimiters.
719
+ #
720
+ # @return [self]
721
+ def parens_break_both(**kwargs, &)
722
+ parens(**kwargs.merge(lft_force_break: true, rgt_force_break: true), &)
723
+ end
724
+
725
+ # {surround} with `( )`. New lines will appear after and before the delimiters.
726
+ #
727
+ # @return [self]
728
+ def parens_break_none(**kwargs, &)
729
+ parens(**kwargs.merge(lft_can_break: false, rgt_can_break: false), &)
730
+ end
731
+
732
+ # {surround} with `` ` ` ``. New lines cannot appear after and before the delimiters
733
+ # unless you specify it with `rgt_can_break` and `lft_can_break`.
734
+ #
735
+ # @return [self]
736
+ def backticks(**kwargs, &)
737
+ surround('`', '`', lft_can_break: false, rgt_can_break: false, **kwargs, &)
738
+ end
739
+
740
+ # {surround} with `" "`. New lines cannot appear after and before the delimiters
741
+ # unless you specify it with `rgt_can_break` and `lft_can_break`.
742
+ #
743
+ # @return [self]
744
+ def quote_double(**kwargs, &)
745
+ surround('"', '"', lft_can_break: false, rgt_can_break: false, **kwargs, &)
746
+ end
747
+
748
+ # {surround} with `' '`. New lines cannot appear after and before the delimiters
749
+ # unless you specify it with `rgt_can_break` and `lft_can_break`.
750
+ #
751
+ # @return [self]
752
+ def quote_single(**kwargs, &)
753
+ surround("'", "'", lft_can_break: false, rgt_can_break: false, **kwargs, &)
754
+ end
755
+
756
+ # Open a consistent group.
757
+ #
758
+ # @param break_type [Symbol]
759
+ # `:consistent` or `:inconsistent`
760
+ # @param indent [Integer]
761
+ # the amount of indentation of the group.
762
+ #
763
+ # @return [self]
764
+ #
765
+ # @see Oppen.begin_consistent
766
+ # @see Oppen.begin_inconsistent
767
+ def group_open(break_type: :consistent, indent: 0)
768
+ if %i[consistent inconsistent].none?(break_type)
769
+ raise ArgumentError, '%s is not a valid type. Choose one: :consistent or :inconsistent'
770
+ end
771
+
772
+ tokens << Oppen.send(:"begin_#{break_type}", offset: indent)
773
+ self
188
774
  end
189
775
 
190
776
  # Close a group.
191
777
  #
192
- # @return [Nil]
193
- def group_close(_)
778
+ # @return [self]
779
+ def group_close
194
780
  tokens << Oppen.end
781
+ self
195
782
  end
196
783
 
197
- # Open a consistent group with indent.
784
+ # Open a consistent group and add indent amount.
198
785
  #
199
786
  # @param indent [Integer]
787
+ # the amount of indentation of the group.
200
788
  #
201
- # @return [Nil]
202
- def indent_open(indent)
789
+ # @return [self]
790
+ def indent_open(indent: @indent)
203
791
  @current_indent += indent
204
792
  group_open
205
793
  end
206
794
 
207
- # Close a group with indent.
795
+ # Close a group and subtract indent.
208
796
  #
209
797
  # @param indent [Integer]
798
+ # the amount of indentation of the group.
210
799
  #
211
- # @return [Nil]
212
- def indent_close(group, indent)
800
+ # @return [self]
801
+ def indent_close(indent: @indent)
213
802
  @current_indent -= indent
214
- group_close(group)
803
+ group_close
215
804
  end
216
805
 
217
- # Open a nest by indent.
806
+ # Open a nest by adding indent.
218
807
  #
219
808
  # @param indent [Integer]
809
+ # the amount of indentation of the nest.
220
810
  #
221
- # @return [Nil]
222
- def nest_open(indent)
811
+ # @return [self]
812
+ def nest_open(indent: @indent)
223
813
  @current_indent += indent
814
+ self
224
815
  end
225
816
 
226
- # Close a nest by indent.
817
+ # Close a nest by subtracting indent.
227
818
  #
228
819
  # @param indent [Integer]
820
+ # the amount of indentation of the nest.
229
821
  #
230
- # @return [Nil]
231
- def nest_close(indent)
822
+ # @return [self]
823
+ def nest_close(indent: @indent)
232
824
  @current_indent -= indent
825
+ self
233
826
  end
234
827
 
235
828
  # @!endgroup
829
+
830
+ # Helper class to allow conditional printing.
831
+ class Wrap
832
+ def initialize(blk)
833
+ @wrapped = blk
834
+ @wrapper = nil
835
+ end
836
+
837
+ # Conditional.
838
+ def when(cond, &blk)
839
+ if cond
840
+ @wrapper = blk
841
+ end
842
+ self
843
+ end
844
+
845
+ # Flush.
846
+ def end
847
+ @wrapper ? @wrapper.(@wrapped) : @wrapped.()
848
+ end
849
+
850
+ # To re-enable chaining.
851
+ def method_missing(meth, ...)
852
+ self.end.send(meth, ...)
853
+ end
854
+
855
+ # To re-enable chaining.
856
+ def respond_to_missing?(meth, include_private)
857
+ self.end.respond_to_missing?(meth, include_private)
858
+ end
859
+ end
236
860
  end
237
861
  end