wrapture 0.3.0 → 0.4.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/bin/wrapture +26 -2
- data/lib/wrapture.rb +6 -1
- data/lib/wrapture/action_spec.rb +2 -3
- data/lib/wrapture/class_spec.rb +101 -53
- data/lib/wrapture/comment.rb +106 -0
- data/lib/wrapture/constant_spec.rb +29 -4
- data/lib/wrapture/constants.rb +9 -1
- data/lib/wrapture/enum_spec.rb +154 -0
- data/lib/wrapture/errors.rb +27 -1
- data/lib/wrapture/function_spec.rb +198 -94
- data/lib/wrapture/normalize.rb +2 -2
- data/lib/wrapture/param_spec.rb +132 -0
- data/lib/wrapture/rule_spec.rb +14 -9
- data/lib/wrapture/scope.rb +46 -14
- data/lib/wrapture/struct_spec.rb +5 -3
- data/lib/wrapture/template_spec.rb +432 -0
- data/lib/wrapture/type_spec.rb +156 -0
- data/lib/wrapture/version.rb +19 -1
- data/lib/wrapture/wrapped_function_spec.rb +2 -0
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a808f785d4a2a78afea58ed889d1b177f73dbd820206bd1d0ff55f0a9de1d16
|
4
|
+
data.tar.gz: 91197fff6cf5343c365b8f4ad3a2efdc913576355bc90a82c5040fa28442058b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 561bdb4547a1e4617bba11c99921b2b475e4e6178e58202b066f51e2e6fee89ce11b64408d33e73952869dcef1ac1e2c793295f1bb79ac9bdcbee0e6b9e4eb49
|
7
|
+
data.tar.gz: ed3a3e6b8a2062bc0938cd18253f9f90f64893a2cfc26b9b4fe0f043ca66e2806f75cc9acfc0054cf18e2a99f93cd1b6fd089561a91363f76bd4accc7cae22b5
|
data/bin/wrapture
CHANGED
@@ -1,7 +1,23 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
4
|
+
|
3
5
|
# frozen_string_literal: true
|
4
6
|
|
7
|
+
# Copyright 2019-2020 Joel E. Anderson
|
8
|
+
#
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
10
|
+
# you may not use this file except in compliance with the License.
|
11
|
+
# You may obtain a copy of the License at
|
12
|
+
#
|
13
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
14
|
+
#
|
15
|
+
# Unless required by applicable law or agreed to in writing, software
|
16
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
17
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
18
|
+
# See the License for the specific language governing permissions and
|
19
|
+
# limitations under the License.
|
20
|
+
|
5
21
|
require 'yaml'
|
6
22
|
require 'wrapture'
|
7
23
|
|
@@ -10,8 +26,16 @@ scope = Wrapture::Scope.new
|
|
10
26
|
ARGV.each do |spec_file|
|
11
27
|
spec = YAML.load_file(spec_file)
|
12
28
|
|
13
|
-
spec
|
14
|
-
Wrapture::
|
29
|
+
spec.fetch('templates', []).each do |temp_spec|
|
30
|
+
scope << Wrapture::TemplateSpec.new(temp_spec)
|
31
|
+
end
|
32
|
+
|
33
|
+
spec.fetch('classes', []).each do |class_spec|
|
34
|
+
scope.add_class_spec_hash(class_spec)
|
35
|
+
end
|
36
|
+
|
37
|
+
spec.fetch('enums', []).each do |enum_spec|
|
38
|
+
scope.add_enum_spec_hash(enum_spec)
|
15
39
|
end
|
16
40
|
end
|
17
41
|
|
data/lib/wrapture.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# frozen_string_literal: true
|
4
4
|
|
5
|
-
# Copyright 2019 Joel E. Anderson
|
5
|
+
# Copyright 2019-2020 Joel E. Anderson
|
6
6
|
#
|
7
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
8
|
# you may not use this file except in compliance with the License.
|
@@ -19,15 +19,20 @@
|
|
19
19
|
# Classes and functions for generating language wrappers
|
20
20
|
module Wrapture
|
21
21
|
require 'wrapture/action_spec'
|
22
|
+
require 'wrapture/comment'
|
22
23
|
require 'wrapture/constant_spec'
|
23
24
|
require 'wrapture/constants'
|
24
25
|
require 'wrapture/class_spec'
|
26
|
+
require 'wrapture/enum_spec'
|
25
27
|
require 'wrapture/errors'
|
26
28
|
require 'wrapture/function_spec'
|
27
29
|
require 'wrapture/normalize'
|
28
30
|
require 'wrapture/rule_spec'
|
31
|
+
require 'wrapture/param_spec'
|
29
32
|
require 'wrapture/scope'
|
30
33
|
require 'wrapture/struct_spec'
|
34
|
+
require 'wrapture/template_spec'
|
35
|
+
require 'wrapture/type_spec'
|
31
36
|
require 'wrapture/version'
|
32
37
|
require 'wrapture/wrapped_function_spec'
|
33
38
|
end
|
data/lib/wrapture/action_spec.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
# frozen_string_literal: true
|
4
4
|
|
5
|
+
#--
|
5
6
|
# Copyright 2020 Joel E. Anderson
|
6
7
|
#
|
7
8
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -15,9 +16,7 @@
|
|
15
16
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
17
|
# See the License for the specific language governing permissions and
|
17
18
|
# limitations under the License.
|
18
|
-
|
19
|
-
require 'wrapture/constants'
|
20
|
-
require 'wrapture/errors'
|
19
|
+
#++
|
21
20
|
|
22
21
|
module Wrapture
|
23
22
|
# An action to take within a generated program.
|
data/lib/wrapture/class_spec.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
# frozen_string_literal: true
|
4
4
|
|
5
|
+
#--
|
5
6
|
# Copyright 2019-2020 Joel E. Anderson
|
6
7
|
#
|
7
8
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -15,11 +16,7 @@
|
|
15
16
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
17
|
# See the License for the specific language governing permissions and
|
17
18
|
# limitations under the License.
|
18
|
-
|
19
|
-
require 'wrapture/constant_spec'
|
20
|
-
require 'wrapture/constants'
|
21
|
-
require 'wrapture/function_spec'
|
22
|
-
require 'wrapture/normalize'
|
19
|
+
#++
|
23
20
|
|
24
21
|
module Wrapture
|
25
22
|
# A description of a class, including its constants, functions, and other
|
@@ -34,6 +31,9 @@ module Wrapture
|
|
34
31
|
# it uses an unsupported version type, then an exception is raised.
|
35
32
|
def self.normalize_spec_hash(spec)
|
36
33
|
raise NoNamespace unless spec.key?('namespace')
|
34
|
+
raise MissingSpecKey, 'name key is required' unless spec.key?('name')
|
35
|
+
|
36
|
+
Comment.validate_doc(spec['doc']) if spec.key?('doc')
|
37
37
|
|
38
38
|
normalized = spec.dup
|
39
39
|
normalized.default = []
|
@@ -71,11 +71,6 @@ module Wrapture
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
-
# Returns a string of the variable with it's type, properly formatted.
|
75
|
-
def self.typed_variable(type, name)
|
76
|
-
"#{type}#{' ' unless type.end_with?('*')}#{name}"
|
77
|
-
end
|
78
|
-
|
79
74
|
# The underlying struct of this class.
|
80
75
|
attr_reader :struct
|
81
76
|
|
@@ -89,14 +84,20 @@ module Wrapture
|
|
89
84
|
# equivalent-struct:: a hash describing the struct this class wraps
|
90
85
|
#
|
91
86
|
# The following keys are optional:
|
87
|
+
# doc:: a string containing the documentation for this class
|
92
88
|
# constructors:: a list of function specs that can create this class
|
93
89
|
# destructor:: a function spec for the destructor of the class
|
94
90
|
# functions:: a list of function specs
|
95
91
|
# constants:: a list of constant specs
|
96
92
|
def initialize(spec, scope: Scope.new)
|
97
|
-
@spec =
|
93
|
+
@spec = Marshal.load(Marshal.dump(spec))
|
94
|
+
TemplateSpec.replace_all_uses(@spec, *scope.templates)
|
95
|
+
|
96
|
+
@spec = ClassSpec.normalize_spec_hash(@spec)
|
98
97
|
|
99
|
-
@struct =
|
98
|
+
@struct = if @spec.key?(EQUIVALENT_STRUCT_KEYWORD)
|
99
|
+
StructSpec.new(@spec[EQUIVALENT_STRUCT_KEYWORD])
|
100
|
+
end
|
100
101
|
|
101
102
|
@functions = @spec['constructors'].map do |constructor_spec|
|
102
103
|
full_spec = constructor_spec.dup
|
@@ -121,32 +122,27 @@ module Wrapture
|
|
121
122
|
ConstantSpec.new(constant_spec)
|
122
123
|
end
|
123
124
|
|
125
|
+
@doc = @spec.key?('doc') ? Comment.new(@spec['doc']) : nil
|
126
|
+
|
124
127
|
scope << self
|
125
128
|
@scope = scope
|
126
129
|
end
|
127
130
|
|
128
|
-
# Returns a cast of an instance of this class
|
129
|
-
#
|
130
|
-
|
131
|
+
# Returns a cast of an instance of this class with the provided name to the
|
132
|
+
# specified type. Optionally the from parameter may hold the type of the
|
133
|
+
# instance, either a reference or a pointer.
|
134
|
+
def cast(instance, to, from = name)
|
135
|
+
member_access = from.pointer? ? '->' : '.'
|
136
|
+
|
131
137
|
struct = "struct #{@struct.name}"
|
132
138
|
|
133
|
-
if [EQUIVALENT_STRUCT_KEYWORD, struct].include?(
|
134
|
-
|
135
|
-
elsif [EQUIVALENT_POINTER_KEYWORD, "#{struct} *"].include?(
|
136
|
-
|
139
|
+
if [EQUIVALENT_STRUCT_KEYWORD, struct].include?(to)
|
140
|
+
"#{'*' if pointer_wrapper?}#{instance}#{member_access}equivalent"
|
141
|
+
elsif [EQUIVALENT_POINTER_KEYWORD, "#{struct} *"].include?(to)
|
142
|
+
"#{'&' unless pointer_wrapper?}#{instance}#{member_access}equivalent"
|
137
143
|
end
|
138
144
|
end
|
139
145
|
|
140
|
-
# The equivalent struct of this class from an instance of it.
|
141
|
-
def equivalent_struct(instance_name)
|
142
|
-
"#{'*' if pointer_wrapper?}#{instance_name}.equivalent"
|
143
|
-
end
|
144
|
-
|
145
|
-
# A pointer to the equivalent struct of this class from an instance of it.
|
146
|
-
def equivalent_struct_pointer(instance_name)
|
147
|
-
"#{'&' unless pointer_wrapper?}#{instance_name}.equivalent"
|
148
|
-
end
|
149
|
-
|
150
146
|
# Generates the wrapper class declaration and definition files.
|
151
147
|
def generate_wrappers
|
152
148
|
[generate_declaration_file, generate_definition_file]
|
@@ -163,7 +159,7 @@ module Wrapture
|
|
163
159
|
# class cannot have any rules in its equivalent struct, or it will not be
|
164
160
|
# overloaded.
|
165
161
|
def overloads?(parent_spec)
|
166
|
-
return false unless parent_spec.struct
|
162
|
+
return false unless parent_spec.struct&.rules&.empty?
|
167
163
|
|
168
164
|
parent_spec.struct.name == struct_name &&
|
169
165
|
parent_spec.name == parent_name &&
|
@@ -172,7 +168,12 @@ module Wrapture
|
|
172
168
|
|
173
169
|
# The name of the parent of this class, or nil if there is no parent.
|
174
170
|
def parent_name
|
175
|
-
@spec['parent']['name'] if
|
171
|
+
@spec['parent']['name'] if child?
|
172
|
+
end
|
173
|
+
|
174
|
+
# Determines if this class is a wrapper for a struct pointer or not.
|
175
|
+
def pointer_wrapper?
|
176
|
+
@spec['type'] == 'pointer'
|
176
177
|
end
|
177
178
|
|
178
179
|
# The name of the equivalent struct of this class.
|
@@ -208,6 +209,11 @@ module Wrapture
|
|
208
209
|
|
209
210
|
private
|
210
211
|
|
212
|
+
# True if the class has a parent.
|
213
|
+
def child?
|
214
|
+
@spec.key?('parent')
|
215
|
+
end
|
216
|
+
|
211
217
|
# Gives the content of the class declaration to a block, line by line.
|
212
218
|
def declaration_contents
|
213
219
|
yield "#ifndef #{header_guard}"
|
@@ -222,7 +228,8 @@ module Wrapture
|
|
222
228
|
yield "namespace #{@spec['namespace']} {"
|
223
229
|
yield
|
224
230
|
|
225
|
-
|
231
|
+
documentation { |line| yield " #{line}" }
|
232
|
+
parent = if child?
|
226
233
|
": public #{parent_name} "
|
227
234
|
else
|
228
235
|
''
|
@@ -233,23 +240,25 @@ module Wrapture
|
|
233
240
|
|
234
241
|
yield unless @constants.empty?
|
235
242
|
@constants.each do |const|
|
236
|
-
yield " #{
|
243
|
+
const.declaration { |line| yield " #{line}" }
|
237
244
|
end
|
238
245
|
|
239
246
|
yield
|
240
|
-
yield " #{
|
247
|
+
equivalent_member_declaration { |line| yield " #{line}" }
|
241
248
|
yield
|
242
249
|
|
243
250
|
member_constructor_declaration { |line| yield " #{line}" }
|
244
251
|
|
245
252
|
pointer_constructor_declaration { |line| yield " #{line}" }
|
246
253
|
|
247
|
-
|
254
|
+
unless !@struct || pointer_wrapper?
|
255
|
+
yield " #{struct_constructor_signature};"
|
256
|
+
end
|
248
257
|
|
249
258
|
overload_declaration { |line| yield " #{line}" }
|
250
259
|
|
251
260
|
@functions.each do |func|
|
252
|
-
yield " #{
|
261
|
+
func.declaration { |line| yield " #{line}" }
|
253
262
|
end
|
254
263
|
|
255
264
|
yield ' };' # end of class
|
@@ -263,7 +272,7 @@ module Wrapture
|
|
263
272
|
def declaration_includes
|
264
273
|
includes = @spec['includes'].dup
|
265
274
|
|
266
|
-
includes.concat(@struct.includes)
|
275
|
+
includes.concat(@struct.includes) if @struct
|
267
276
|
|
268
277
|
@functions.each do |func|
|
269
278
|
includes.concat(func.declaration_includes)
|
@@ -273,7 +282,7 @@ module Wrapture
|
|
273
282
|
includes.concat(const.declaration_includes)
|
274
283
|
end
|
275
284
|
|
276
|
-
includes.concat(@spec['parent']['includes']) if
|
285
|
+
includes.concat(@spec['parent']['includes']) if child?
|
277
286
|
|
278
287
|
includes.uniq
|
279
288
|
end
|
@@ -296,7 +305,7 @@ module Wrapture
|
|
296
305
|
|
297
306
|
pointer_constructor_definition { |line| yield " #{line}" }
|
298
307
|
|
299
|
-
unless pointer_wrapper?
|
308
|
+
unless pointer_wrapper? || !@struct
|
300
309
|
yield
|
301
310
|
yield " #{@spec['name']}::#{struct_constructor_signature} {"
|
302
311
|
|
@@ -313,7 +322,7 @@ module Wrapture
|
|
313
322
|
@functions.each do |func|
|
314
323
|
yield
|
315
324
|
|
316
|
-
func.definition
|
325
|
+
func.definition do |def_line|
|
317
326
|
yield " #{def_line}"
|
318
327
|
end
|
319
328
|
end
|
@@ -341,6 +350,29 @@ module Wrapture
|
|
341
350
|
includes.uniq
|
342
351
|
end
|
343
352
|
|
353
|
+
# Yields the class documentation one line at a time.
|
354
|
+
def documentation
|
355
|
+
@doc&.format_as_doxygen(max_line_length: 78) { |line| yield line }
|
356
|
+
end
|
357
|
+
|
358
|
+
# Yields the declaration of the equivalent member if this class has one.
|
359
|
+
#
|
360
|
+
# A class might not have an equivalent member if it is able to use the
|
361
|
+
# parent class's, for example if the child class wraps the same struct.
|
362
|
+
def equivalent_member_declaration
|
363
|
+
return unless @struct
|
364
|
+
|
365
|
+
if child?
|
366
|
+
parent_spec = @scope.type(TypeSpec.new(parent_name))
|
367
|
+
member_reusable = !parent_spec.nil? &&
|
368
|
+
parent_spec.struct_name == @struct.name &&
|
369
|
+
parent_spec.pointer_wrapper? == pointer_wrapper?
|
370
|
+
return if member_reusable
|
371
|
+
end
|
372
|
+
|
373
|
+
yield "#{@struct.declaration(equivalent_name)};"
|
374
|
+
end
|
375
|
+
|
344
376
|
# Gives the name of the equivalent struct.
|
345
377
|
def equivalent_name
|
346
378
|
"#{'*' if pointer_wrapper?}equivalent"
|
@@ -380,7 +412,7 @@ module Wrapture
|
|
380
412
|
# Yields the declaration of the member constructor for a class. This will be
|
381
413
|
# empty if the wrapped struct is a pointer wrapper.
|
382
414
|
def member_constructor_declaration
|
383
|
-
return unless @struct
|
415
|
+
return unless @struct&.members?
|
384
416
|
|
385
417
|
yield "#{@spec['name']}( #{@struct.member_list_with_defaults} );"
|
386
418
|
end
|
@@ -388,7 +420,7 @@ module Wrapture
|
|
388
420
|
# Yields the definition of the member constructor for a class. This will be
|
389
421
|
# empty if the wrapped struct is a pointer wrapper.
|
390
422
|
def member_constructor_definition
|
391
|
-
return unless @struct
|
423
|
+
return unless @struct&.members?
|
392
424
|
|
393
425
|
yield "#{@spec['name']}::#{@spec['name']}( #{@struct.member_list} ) {"
|
394
426
|
|
@@ -440,9 +472,12 @@ module Wrapture
|
|
440
472
|
|
441
473
|
# Yields the declaration of the pointer constructor for a class.
|
442
474
|
#
|
443
|
-
# If
|
444
|
-
#
|
475
|
+
# If this class does not have an equivalent struct, or if there is already
|
476
|
+
# a constructor defined with this signature, then this function will return
|
477
|
+
# with no output.
|
445
478
|
def pointer_constructor_declaration
|
479
|
+
return unless @struct
|
480
|
+
|
446
481
|
signature_prefix = "#{@spec['name']}( #{@struct.pointer_declaration('')}"
|
447
482
|
return if @functions.any? do |func|
|
448
483
|
func.constructor? && func.signature.start_with?(signature_prefix)
|
@@ -453,21 +488,25 @@ module Wrapture
|
|
453
488
|
|
454
489
|
# Yields the definition of the pointer constructor for a class.
|
455
490
|
#
|
456
|
-
# If
|
457
|
-
# function will return
|
491
|
+
# If this class has no equivalent struct, or if there is already a
|
492
|
+
# constructor provided with this signature, then this function will return
|
493
|
+
# with no output.
|
458
494
|
#
|
459
495
|
# If this is a pointer wrapper class, then the constructor will simply set
|
460
|
-
# the underlying pointer to the
|
496
|
+
# the underlying pointer to the provided one, and return the new object.
|
461
497
|
#
|
462
498
|
# If this is a struct wrapper class, then a constructor will be created that
|
463
499
|
# sets each member of the wrapped struct to the provided value.
|
464
500
|
def pointer_constructor_definition
|
501
|
+
return unless @struct
|
502
|
+
|
465
503
|
signature_prefix = "#{@spec['name']}( #{@struct.pointer_declaration('')}"
|
466
504
|
return if @functions.any? do |func|
|
467
505
|
func.constructor? && func.signature.start_with?(signature_prefix)
|
468
506
|
end
|
469
507
|
|
470
|
-
|
508
|
+
initializer = pointer_constructor_initializer
|
509
|
+
yield "#{@spec['name']}::#{pointer_constructor_signature} #{initializer}{"
|
471
510
|
|
472
511
|
if pointer_wrapper?
|
473
512
|
yield ' this->equivalent = equivalent;'
|
@@ -481,16 +520,25 @@ module Wrapture
|
|
481
520
|
yield '}'
|
482
521
|
end
|
483
522
|
|
523
|
+
# The initializer for the pointer constructor, if one is available, or an
|
524
|
+
# empty string if not.
|
525
|
+
def pointer_constructor_initializer
|
526
|
+
if pointer_wrapper? && child?
|
527
|
+
parent_spec = @scope.type(TypeSpec.new(parent_name))
|
528
|
+
parent_usable = !parent_spec.nil? &&
|
529
|
+
parent_spec.pointer_wrapper? &&
|
530
|
+
parent_spec.struct_name == @struct.name
|
531
|
+
return ": #{parent_name}( equivalent ) " if parent_usable
|
532
|
+
end
|
533
|
+
|
534
|
+
''
|
535
|
+
end
|
536
|
+
|
484
537
|
# The signature of the constructor given an equivalent strucct pointer.
|
485
538
|
def pointer_constructor_signature
|
486
539
|
"#{@spec['name']}( #{@struct.pointer_declaration 'equivalent'} )"
|
487
540
|
end
|
488
541
|
|
489
|
-
# Determines if this class is a wrapper for a struct pointer or not.
|
490
|
-
def pointer_wrapper?
|
491
|
-
@spec['type'] == 'pointer'
|
492
|
-
end
|
493
|
-
|
494
542
|
# The signature of the constructor given an equivalent struct type.
|
495
543
|
def struct_constructor_signature
|
496
544
|
"#{@spec['name']}( #{@struct.declaration 'equivalent'} )"
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
#--
|
6
|
+
# Copyright 2020 Joel E. Anderson
|
7
|
+
#
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
# you may not use this file except in compliance with the License.
|
10
|
+
# You may obtain a copy of the License at
|
11
|
+
#
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
#
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
#++
|
20
|
+
|
21
|
+
module Wrapture
|
22
|
+
# A comment that can be inserted in generated source code.
|
23
|
+
#
|
24
|
+
# Comments are primarily used to insert documentation about generated code for
|
25
|
+
# documentation generation tools such as Doxygen.
|
26
|
+
class Comment
|
27
|
+
# Validates a doc string.
|
28
|
+
def self.validate_doc(doc)
|
29
|
+
raise InvalidDoc, 'a doc must be a string' unless doc.is_a?(String)
|
30
|
+
end
|
31
|
+
|
32
|
+
# The raw text of the comment.
|
33
|
+
attr_reader :text
|
34
|
+
|
35
|
+
# Creates a comment from a string. If the provided string is nil, then an
|
36
|
+
# empty string is used.
|
37
|
+
def initialize(comment = '')
|
38
|
+
@text = comment.nil? ? '' : comment
|
39
|
+
end
|
40
|
+
|
41
|
+
# True if this comment is empty, false otherwise.
|
42
|
+
def empty?
|
43
|
+
@text.empty?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Yields each line of the comment formatted as specified.
|
47
|
+
def format(line_prefix: '// ', first_line: nil, last_line: nil,
|
48
|
+
max_line_length: 80)
|
49
|
+
return if @text.empty?
|
50
|
+
|
51
|
+
yield first_line if first_line
|
52
|
+
|
53
|
+
paragraphs(max_line_length - line_prefix.length) do |line|
|
54
|
+
yield "#{line_prefix}#{line}".rstrip
|
55
|
+
end
|
56
|
+
|
57
|
+
yield last_line if last_line
|
58
|
+
end
|
59
|
+
|
60
|
+
# Yields each line of the comment formatted using Doxygen style.
|
61
|
+
def format_as_doxygen(max_line_length: 80)
|
62
|
+
format(line_prefix: ' * ', first_line: '/**',
|
63
|
+
last_line: ' */', max_line_length: max_line_length) do |line|
|
64
|
+
yield line
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Yields the comment converted into paragraph-style blocks.
|
71
|
+
#
|
72
|
+
# Consecutive lines with text are concatenated together to the maximum line
|
73
|
+
# length, regardless of the original line length in the comment. One or more
|
74
|
+
# empty lines are written as a single empty line, separating paragraphs.
|
75
|
+
#
|
76
|
+
# Yielded lines may have trailing spaces, which are not considered part of
|
77
|
+
# the maximum length. The caller must strip these off.
|
78
|
+
def paragraphs(line_length)
|
79
|
+
running_line = String.new
|
80
|
+
newline_mode = true
|
81
|
+
@text.each_line do |line|
|
82
|
+
if line.strip.empty?
|
83
|
+
unless newline_mode
|
84
|
+
yield running_line
|
85
|
+
yield ''
|
86
|
+
running_line.clear
|
87
|
+
newline_mode = true
|
88
|
+
end
|
89
|
+
else
|
90
|
+
newline_mode = false
|
91
|
+
end
|
92
|
+
|
93
|
+
line.scan(/\S+/) do |word|
|
94
|
+
if running_line.length + word.length > line_length
|
95
|
+
yield running_line
|
96
|
+
running_line = word + ' '
|
97
|
+
else
|
98
|
+
running_line << word << ' '
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
yield running_line
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|