oppen 0.9.6 → 0.9.8

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,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
  - - ">="