oppen 0.9.6 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
data/lib/wadler/print.rb CHANGED
@@ -4,26 +4,49 @@
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
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.
27
+ attr_reader :whitespace
28
+ # @return [Integer]
29
+ # maximum line width.
13
30
  attr_reader :width
14
31
 
15
- # @param config [Oppen::Config]
16
- # @param space [String, Proc] could be a String or a callable.
17
- # If it's a string, spaces will be generated with the the
18
- # lambda `->(n){ n * space }`, where `n` is the number of columns
19
- # to indent.
20
- # If it's a callable, it will receive `n` and it needs to return
21
- # a string.
22
- # @param new_line [String]
23
- # @param out [Object] should have a write and string method
24
- # @param width [Integer]
25
- def initialize(config: Config.wadler, space: ' ',
26
- new_line: "\n", out: StringIO.new, width: 80)
32
+ # @param config [Config]
33
+ # to customize the printer's behavior.
34
+ # @param new_line [String]
35
+ # the new line String.
36
+ # @param out [Object]
37
+ # the output string buffer. It should have a `write` and `string` methods.
38
+ # @param space [String, Proc]
39
+ # indentation string or a string generator.
40
+ # - If a `String`, spaces will be generated with the the lambda
41
+ # `->(n){ space * n }`, where `n` is the number of columns to indent.
42
+ # - If a `Proc`, it will receive `n` and it needs to return a `String`.
43
+ # @param whitespace [String] the whitespace character. Used to trim trailing whitespaces.
44
+ # @param width [Integer] maximum line width desired.
45
+ #
46
+ # @see Token::Whitespace
47
+ def initialize(config: Config.wadler, new_line: "\n",
48
+ out: StringIO.new, space: ' ',
49
+ whitespace: ' ', width: 80)
27
50
  @config = config
28
51
  @current_indent = 0
29
52
  @space = space
@@ -31,38 +54,138 @@ module Oppen
31
54
  @new_line = new_line
32
55
  @out = out
33
56
  @tokens = []
57
+ @whitespace = whitespace
34
58
  end
35
59
 
36
- # @return [String]
37
- def output
60
+ # Add missing {Token::Begin}, {Token::End} or {Token::EOF}.
61
+ #
62
+ # @return [Nil]
63
+ def add_missing_begin_and_end
38
64
  if !tokens.first.is_a? Token::Begin
39
65
  tokens.unshift Oppen.begin_consistent(offset: 0)
40
66
  tokens << Oppen.end
41
67
  end
42
- if !tokens.last.is_a? Oppen::Token::EOF
43
- tokens << Oppen.eof
44
- end
45
- Oppen.print(tokens:, new_line:, config:, space:, out:, width:)
68
+ tokens << Oppen.eof if !tokens.last.is_a?(Oppen::Token::EOF)
69
+ end
70
+
71
+ # Call this to extract the final pretty-printed output.
72
+ #
73
+ # @return [String]
74
+ def output
75
+ add_missing_begin_and_end
76
+ Oppen.print(
77
+ tokens: tokens,
78
+ new_line: new_line,
79
+ config: config,
80
+ space: space,
81
+ out: out,
82
+ width: width,
83
+ )
84
+ end
85
+
86
+ # Convert a list of tokens to its wadler representation.
87
+ #
88
+ # This method reverse engineers a tokens list to transform it into Wadler
89
+ # printing commands. It can be particularly useful when debugging a black
90
+ # box program.
91
+ #
92
+ # @option kwargs [Integer] :base_indent
93
+ # the base indentation amount of the output.
94
+ # @option kwargs [String] :printer_name
95
+ # the name of the Wadler instance in the output.
96
+ #
97
+ # @example
98
+ # out = Oppen::Wadler.new
99
+ # out.group {
100
+ # out.text('Hello World!')
101
+ # }
102
+ # out.show_print_commands(out_name: 'out')
103
+ #
104
+ # # =>
105
+ # # out.group(0, "", "", :consistent) {
106
+ # # out.text("Hello World!", width: 12)
107
+ # # }
108
+ #
109
+ # @return [String]
110
+ def show_print_commands(**kwargs)
111
+ add_missing_begin_and_end
112
+ Oppen.tokens_to_wadler(tokens, **kwargs)
46
113
  end
47
114
 
48
- # @param indent [Integer] group indentation
49
- # @param open_obj [String] group opening delimiter
50
- # @param close_obj [String] group closing delimiter
51
- # @param break_type [Oppen::Token::BreakType] group breaking type
115
+ # Create a new group.
116
+ #
117
+ # @param indent [Integer]
118
+ # indentation.
119
+ # @param open_obj [String]
120
+ # opening delimiter.
121
+ # @param close_obj [String]
122
+ # closing delimiter.
123
+ # @param break_type [Token::BreakType]
124
+ # break type.
52
125
  #
53
- # @yield the block of text in a group
126
+ # @yield
127
+ # the block of text in a group.
128
+ #
129
+ # @example
130
+ # out = Oppen::Wadler.new
131
+ # out.text 'a'
132
+ # out.group(2, '{', '}') {
133
+ # out.break
134
+ # out.text 'b'
135
+ # }
136
+ # out.output
137
+ #
138
+ # # =>
139
+ # # a
140
+ # # {
141
+ # # b
142
+ # # }
143
+ #
144
+ # @example Consistent Breaking
145
+ # out = Oppen::Wadler.new
146
+ # out.group(0, '', '', :consistent) {
147
+ # out.text 'a'
148
+ # out.break
149
+ # out.text 'b'
150
+ # out.breakable
151
+ # out.text 'c'
152
+ # }
153
+ # out.output
154
+ #
155
+ # # =>
156
+ # # a
157
+ # # b
158
+ # # c
159
+ #
160
+ # @example Inconsistent Breaking
161
+ # out = Oppen::Wadler.new
162
+ # out.group(0, '', '', :inconsistent) {
163
+ # out.text 'a'
164
+ # out.break
165
+ # out.text 'b'
166
+ # out.breakable
167
+ # out.text 'c'
168
+ # }
169
+ # out.output
170
+ #
171
+ # # =>
172
+ # # a
173
+ # # b c
54
174
  #
55
175
  # @return [Nil]
176
+ #
177
+ # @see Oppen.begin_consistent
178
+ # @see Oppen.begin_inconsistent
56
179
  def group(indent = 0, open_obj = '', close_obj = '',
57
- break_type = Oppen::Token::BreakType::CONSISTENT)
180
+ break_type = :consistent)
58
181
  raise ArgumentError, "#{open_obj.nil? ? 'open_obj' : 'close_obj'} cannot be nil" \
59
182
  if open_obj.nil? || close_obj.nil?
60
183
 
61
184
  tokens <<
62
185
  case break_type
63
- in Oppen::Token::BreakType::CONSISTENT
186
+ in :consistent
64
187
  Oppen.begin_consistent(offset: indent)
65
- in Oppen::Token::BreakType::INCONSISTENT
188
+ in :inconsistent
66
189
  Oppen.begin_inconsistent(offset: indent)
67
190
  end
68
191
 
@@ -81,14 +204,47 @@ module Oppen
81
204
  tokens << Oppen.end
82
205
  end
83
206
 
84
- # @param indent [Integer] nest indentation
85
- # @param open_obj [String] nest opening delimiter
86
- # @param close_obj [String] nest closing delimiter
87
- # @param break_type [Oppen::Token::BreakType] nest breaking type
207
+ # Create a new non-strict {group}.
208
+ #
209
+ # {group}s isolate breaking decisions, and in that sense they're considered
210
+ # strict; e.g. when a breakable is transformed into an actual break, its
211
+ # parent {group} might not get broken if the result could fit on the line.
212
+ #
213
+ # This is not the case with {nest}: if the same breakable was in a {nest}, the
214
+ # {group} containing the {nest} will also be broken.
215
+ #
216
+ # @note indentation cannot happen if there are no breaks in the {nest}.
217
+ #
218
+ # @note a {nest} will not forcibly indent its content if the break type of
219
+ # the enclosing {group} is `:inconsistent`.
220
+ #
221
+ # @param indent [Integer]
222
+ # indentation.
223
+ # @param open_obj [String]
224
+ # opening delimiter. A {break} is implicitly slipped after it if it's not empty.
225
+ # @param close_obj [String]
226
+ # closing delimiter. A {break} is implicitly slipped before it if it's not empty.
227
+ #
228
+ # @yield
229
+ # the block of text in a nest.
230
+ #
231
+ # @example
232
+ # out = Oppen::Wadler.new
233
+ # out.nest(2, '{', '}') {
234
+ # out.text 'a'
235
+ # out.break
236
+ # out.text 'b'
237
+ # }
238
+ # out.output
239
+ #
240
+ # # =>
241
+ # # {
242
+ # # a
243
+ # # b
244
+ # # }
88
245
  #
89
246
  # @return [Nil]
90
- def nest(indent, open_obj = '', close_obj = '',
91
- break_type = Oppen::Token::BreakType::CONSISTENT)
247
+ def nest(indent, open_obj = '', close_obj = '')
92
248
  raise ArgumentError, "#{open_obj.nil? ? 'open_obj' : 'close_obj'} cannot be nil" \
93
249
  if open_obj.nil? || close_obj.nil?
94
250
 
@@ -111,33 +267,71 @@ module Oppen
111
267
  text(close_obj)
112
268
  end
113
269
 
270
+ # Create a new text element.
271
+ #
114
272
  # @param value [String]
273
+ # the value of the token.
115
274
  #
116
275
  # @return [Nil]
117
276
  def text(value, width: value.length)
118
- tokens << Oppen.string(value, width:)
277
+ if config.trim_trailing_whitespaces? && value.match(/((?:#{Regexp.escape(whitespace)})+)\z/)
278
+ match = Regexp.last_match(1)
279
+ matched_length = match.length
280
+ if value.length != matched_length
281
+ tokens << Oppen.string(value[0...-matched_length], width: width - matched_length)
282
+ end
283
+ tokens << Oppen.whitespace(match)
284
+ else
285
+ tokens << Oppen.string(value, width: width)
286
+ end
119
287
  end
120
288
 
121
- # @param str [String]
122
- # @param line_continuation [String] If a new line is needed display this string before the new line
289
+ # Create a new breakable element.
290
+ #
291
+ # @param str [String]
292
+ # the value of the token that will be displayed if no new line is needed.
293
+ # @param line_continuation [String]
294
+ # printed before the line break.
295
+ # @param width [Integer]
296
+ # the width of the token.
123
297
  #
124
298
  # @return [Nil]
125
- def breakable(str = ' ', width: str.length, line_continuation: '')
126
- tokens << Oppen.break(str, width:, line_continuation:, offset: current_indent)
299
+ #
300
+ # @see Wadler#break example on `line_continuation`.
301
+ def breakable(str = ' ', line_continuation: '', width: str.length)
302
+ tokens << Oppen.break(str, width: width, line_continuation: line_continuation, offset: current_indent)
127
303
  end
128
304
 
129
- # @param line_continuation [String] If a new line is needed display this string before the new line
305
+ # Create a new break element.
306
+ #
307
+ # @param line_continuation [String]
308
+ # printed before the line break.
309
+ #
310
+ # @example
311
+ # out = Oppen::Wadler.new
312
+ # out.text 'a'
313
+ # out.break
314
+ # out.text 'b'
315
+ # out.break line_continuation: '#'
316
+ # out.text 'c'
317
+ # out.output
318
+ #
319
+ # # =>
320
+ # # a
321
+ # # b#
322
+ # # c
130
323
  #
131
324
  # @return [Nil]
132
325
  def break(line_continuation: '')
133
- tokens << Oppen.line_break(line_continuation:, offset: current_indent)
326
+ tokens << Oppen.line_break(line_continuation: line_continuation, offset: current_indent)
134
327
  end
135
328
 
136
329
  # @!group Helpers
137
330
 
138
- # Set a base indenetaion level to the printer.
331
+ # Set a base indenetaion level for the printer.
139
332
  #
140
333
  # @param indent [Integer]
334
+ # the amount of indentation.
141
335
  #
142
336
  # @return [Nil]
143
337
  def base_indent(indent = 0)
@@ -147,9 +341,14 @@ module Oppen
147
341
  # Open a consistent group.
148
342
  #
149
343
  # @param inconsistent [Boolean]
344
+ # whether the break type of the group should be inconsistent.
150
345
  # @param indent [Integer]
346
+ # the amount of indentation of the group.
151
347
  #
152
348
  # @return [Nil]
349
+ #
350
+ # @see Oppen.begin_consistent
351
+ # @see Oppen.begin_inconsistent
153
352
  def group_open(inconsistent: false, indent: 0)
154
353
  tokens <<
155
354
  if inconsistent
@@ -166,9 +365,10 @@ module Oppen
166
365
  tokens << Oppen.end
167
366
  end
168
367
 
169
- # Open a consistent group with indent.
368
+ # Open a consistent group and add indent amount.
170
369
  #
171
370
  # @param indent [Integer]
371
+ # the amount of indentation of the group.
172
372
  #
173
373
  # @return [Nil]
174
374
  def indent_open(indent)
@@ -176,9 +376,10 @@ module Oppen
176
376
  group_open
177
377
  end
178
378
 
179
- # Close a group with indent.
379
+ # Close a group and subtract indent.
180
380
  #
181
381
  # @param indent [Integer]
382
+ # the amount of indentation of the group.
182
383
  #
183
384
  # @return [Nil]
184
385
  def indent_close(group, indent)
@@ -186,18 +387,20 @@ module Oppen
186
387
  group_close(group)
187
388
  end
188
389
 
189
- # Open a nest by indent.
390
+ # Open a nest by adding indent.
190
391
  #
191
392
  # @param indent [Integer]
393
+ # the amount of indentation of the nest.
192
394
  #
193
395
  # @return [Nil]
194
396
  def nest_open(indent)
195
397
  @current_indent += indent
196
398
  end
197
399
 
198
- # Close a nest by indent.
400
+ # Close a nest by subtracting indent.
199
401
  #
200
402
  # @param indent [Integer]
403
+ # the amount of indentation of the nest.
201
404
  #
202
405
  # @return [Nil]
203
406
  def nest_close(indent)
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oppen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.6
4
+ version: 0.9.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amine Mike El Maalouf <amine.el-maalouf@epita.fr>
8
- - Firas al-Khalil <firasalkhalil@gmail.com>
8
+ - Firas al-Khalil <firas.alkhalil@faveod.com>
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-11-25 00:00:00.000000000 Z
12
+ date: 2024-12-30 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Implementation of the Oppen's pretty printing algorithm
15
15
  email:
@@ -37,9 +37,9 @@ require_paths:
37
37
  - lib
38
38
  required_ruby_version: !ruby/object:Gem::Requirement
39
39
  requirements:
40
- - - "~>"
40
+ - - ">="
41
41
  - !ruby/object:Gem::Version
42
- version: '3.2'
42
+ version: '3.0'
43
43
  required_rubygems_version: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="