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.
- checksums.yaml +4 -4
- data/README.md +99 -29
- data/lib/oppen/mixins.rb +39 -41
- data/lib/oppen/print_stack.rb +61 -68
- data/lib/oppen/printer.rb +90 -77
- data/lib/oppen/scan_stack.rb +36 -16
- data/lib/oppen/token.rb +50 -35
- data/lib/oppen/version.rb +3 -2
- data/lib/oppen.rb +174 -74
- data/lib/wadler/print.rb +719 -95
- metadata +8 -8
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
|
-
|
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
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
# @param out
|
25
|
-
#
|
26
|
-
# @param
|
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,
|
29
|
-
|
30
|
-
|
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 =
|
33
|
-
@
|
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
|
65
|
+
# Add missing {Token::Begin}, {Token::End} or {Token::EOF}.
|
66
|
+
#
|
42
67
|
# @return [Nil]
|
43
68
|
def add_missing_begin_and_end
|
44
|
-
|
45
|
-
|
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
|
-
#
|
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(
|
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
|
-
#
|
61
|
-
#
|
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
|
-
#
|
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
|
-
# @
|
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
|
-
# @
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
213
|
+
in :consistent
|
85
214
|
Oppen.begin_consistent(offset: indent)
|
86
|
-
in
|
215
|
+
in :inconsistent
|
87
216
|
Oppen.begin_inconsistent(offset: indent)
|
88
217
|
end
|
89
218
|
|
90
|
-
if !
|
219
|
+
if !lft.empty?
|
91
220
|
self.break
|
92
|
-
text
|
221
|
+
text lft
|
93
222
|
end
|
94
223
|
|
95
224
|
yield
|
96
225
|
|
97
|
-
if !
|
226
|
+
if !rgt.empty?
|
98
227
|
self.break
|
99
|
-
text
|
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
|
-
#
|
106
|
-
|
107
|
-
|
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
|
-
#
|
110
|
-
|
111
|
-
|
112
|
-
|
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 !
|
117
|
-
text
|
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
|
-
|
307
|
+
if !rgt.empty?
|
308
|
+
self.break
|
309
|
+
text rgt
|
310
|
+
end
|
128
311
|
|
129
|
-
self
|
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 [
|
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
|
-
#
|
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
|
-
# @
|
153
|
-
|
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
|
-
#
|
352
|
+
# Create a new break element.
|
158
353
|
#
|
159
|
-
# @
|
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
|
-
#
|
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
|
-
#
|
395
|
+
# A means to wrap a piece of code in several ways.
|
167
396
|
#
|
168
|
-
# @
|
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
|
-
# @
|
171
|
-
|
172
|
-
|
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
|
-
#
|
430
|
+
# Produce a separated list.
|
176
431
|
#
|
177
|
-
# @
|
178
|
-
#
|
432
|
+
# @example Consistent Breaking
|
433
|
+
# puts out.separate((1..3).map(&:to_s), ',') { |i| out.text i}
|
179
434
|
#
|
180
|
-
#
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
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 [
|
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
|
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 [
|
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
|
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 [
|
212
|
-
def indent_close(
|
800
|
+
# @return [self]
|
801
|
+
def indent_close(indent: @indent)
|
213
802
|
@current_indent -= indent
|
214
|
-
group_close
|
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 [
|
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 [
|
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
|