docrb 0.2.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 +7 -0
- data/.editorconfig +21 -0
- data/.rspec +3 -0
- data/.rubocop.yml +70 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +79 -0
- data/Rakefile +12 -0
- data/bin/console +16 -0
- data/bin/json +16 -0
- data/bin/md +8 -0
- data/bin/setup +8 -0
- data/docrb.gemspec +33 -0
- data/exe/docrb +205 -0
- data/lib/docrb/comment_parser/code_example_block.rb +36 -0
- data/lib/docrb/comment_parser/code_example_parser.rb +29 -0
- data/lib/docrb/comment_parser/field_block.rb +18 -0
- data/lib/docrb/comment_parser/field_list_parser.rb +90 -0
- data/lib/docrb/comment_parser/text_block.rb +43 -0
- data/lib/docrb/comment_parser.rb +272 -0
- data/lib/docrb/doc_compiler/base_container/computations.rb +178 -0
- data/lib/docrb/doc_compiler/base_container.rb +123 -0
- data/lib/docrb/doc_compiler/doc_attribute.rb +58 -0
- data/lib/docrb/doc_compiler/doc_blocks.rb +111 -0
- data/lib/docrb/doc_compiler/doc_class.rb +43 -0
- data/lib/docrb/doc_compiler/doc_method.rb +66 -0
- data/lib/docrb/doc_compiler/doc_module.rb +9 -0
- data/lib/docrb/doc_compiler/file_ref.rb +41 -0
- data/lib/docrb/doc_compiler/object_container.rb +68 -0
- data/lib/docrb/doc_compiler.rb +55 -0
- data/lib/docrb/markdown.rb +62 -0
- data/lib/docrb/module_extensions.rb +13 -0
- data/lib/docrb/resolvable.rb +178 -0
- data/lib/docrb/ruby_parser.rb +630 -0
- data/lib/docrb/spec.rb +31 -0
- data/lib/docrb/version.rb +5 -0
- data/lib/docrb.rb +71 -0
- metadata +139 -0
@@ -0,0 +1,630 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docrb
|
4
|
+
# RubyParser parses comments and source from a given file path and provides
|
5
|
+
# its digested contents in standardised structures.
|
6
|
+
class RubyParser
|
7
|
+
COMMENT_PREFIX = /^\s*#\s?/
|
8
|
+
|
9
|
+
attr_reader :modules, :classes, :methods, :ast
|
10
|
+
|
11
|
+
# Initializes a new parser for a given path
|
12
|
+
#
|
13
|
+
# path - Path of the file to be parsed
|
14
|
+
def initialize(path)
|
15
|
+
@ast, @comments = ::Parser::CurrentRuby.parse_file_with_comments(path)
|
16
|
+
@modules = []
|
17
|
+
@classes = []
|
18
|
+
@methods = []
|
19
|
+
end
|
20
|
+
|
21
|
+
# Kicks off the parsing process
|
22
|
+
#
|
23
|
+
# After this method is called, #modules, #classes, and #methods can be
|
24
|
+
# used to enumerate items contained within the parsed file.
|
25
|
+
def parse
|
26
|
+
parse!(@ast)
|
27
|
+
cleanup!
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# Recursivelly cleans modules and classes found in the parsed file by
|
33
|
+
# removing uneeded internal fields used by the parser.
|
34
|
+
def cleanup!
|
35
|
+
clean_modules!
|
36
|
+
clean_classes!
|
37
|
+
end
|
38
|
+
|
39
|
+
# Parses a given node and parent. This method is a dispatch source for
|
40
|
+
# other parsing methods.
|
41
|
+
#
|
42
|
+
# node - Node to be parsed
|
43
|
+
# parent - Parent node of the node being parsed
|
44
|
+
def parse!(node = nil, parent = nil)
|
45
|
+
return if node.nil?
|
46
|
+
|
47
|
+
case node.type
|
48
|
+
when :def
|
49
|
+
parse_def(node, parent)
|
50
|
+
|
51
|
+
when :defs
|
52
|
+
parse_singleton_method(node, parent)
|
53
|
+
|
54
|
+
when :class
|
55
|
+
parse_class(node, parent)
|
56
|
+
|
57
|
+
when :sclass
|
58
|
+
parse_singleton_class(node, parent)
|
59
|
+
|
60
|
+
when :module
|
61
|
+
parse_module(node, parent)
|
62
|
+
|
63
|
+
when :begin
|
64
|
+
parse_begin(node, parent)
|
65
|
+
|
66
|
+
when :send
|
67
|
+
parse_send(node, parent)
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Attempts to find a comment associated with a given line.
|
73
|
+
#
|
74
|
+
# line - Integer representing the line number to be checked for comments
|
75
|
+
#
|
76
|
+
# Returns a comment preceding the provided line, or nil, in case there's
|
77
|
+
# none.
|
78
|
+
def comment_for_line(line)
|
79
|
+
return nil if line.zero? || line.negative?
|
80
|
+
|
81
|
+
@comments.find { |c| c.loc.line == line }
|
82
|
+
end
|
83
|
+
|
84
|
+
# Finds and parses a comment block preceding a given line number.
|
85
|
+
#
|
86
|
+
# line - Integer representing the line being processed.
|
87
|
+
# type: - Type of node depicted in the provided line number. This is used
|
88
|
+
# to provide better utilities to handle the node being commented.
|
89
|
+
#
|
90
|
+
# Returns a hash containing the parsed comment.
|
91
|
+
def comments_from(line, type:)
|
92
|
+
# First, find a comment for the line before this
|
93
|
+
comments = []
|
94
|
+
comment = comment_for_line(line)
|
95
|
+
|
96
|
+
while comment
|
97
|
+
comments << comment
|
98
|
+
comment = comment_for_line(line - 1)
|
99
|
+
line -= 1
|
100
|
+
end
|
101
|
+
|
102
|
+
return nil if comments.empty?
|
103
|
+
|
104
|
+
comments = comments
|
105
|
+
.collect(&:text)
|
106
|
+
.collect { |l| l.gsub(COMMENT_PREFIX, "") }
|
107
|
+
.reverse
|
108
|
+
.join("\n")
|
109
|
+
CommentParser.parse(type:, comment: comments)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Parses a `begin` keyword
|
113
|
+
#
|
114
|
+
# node - The node being parsed
|
115
|
+
# parent - The node's parent object
|
116
|
+
def parse_begin(node, parent)
|
117
|
+
node.children.each { |n| parse!(n, parent) }
|
118
|
+
end
|
119
|
+
|
120
|
+
ATTRIBUTES = %i[attr_reader attr_writer attr_accessor].freeze
|
121
|
+
CLASS_MODIFIERS = %i[extend include].freeze
|
122
|
+
VISIBILITY_MODIFIERS = %i[private protected public].freeze
|
123
|
+
MODULE_MODIFIERS = [:module_function].freeze
|
124
|
+
SEND_DISPATCH = {
|
125
|
+
ATTRIBUTES => :parse_attribute,
|
126
|
+
CLASS_MODIFIERS => :parse_class_modifier,
|
127
|
+
VISIBILITY_MODIFIERS => :parse_visibility_modifier,
|
128
|
+
MODULE_MODIFIERS => :parse_module_modifier
|
129
|
+
}.freeze
|
130
|
+
|
131
|
+
PARSEABLE_SENDS = [*ATTRIBUTES, *CLASS_MODIFIERS, *VISIBILITY_MODIFIERS, *MODULE_MODIFIERS].freeze
|
132
|
+
|
133
|
+
# Parses a `send` instruction (a method call)
|
134
|
+
#
|
135
|
+
# node - The node being parsed
|
136
|
+
# parent - The node's parent object
|
137
|
+
def parse_send(node, parent)
|
138
|
+
arr = node.to_a
|
139
|
+
target, name = arr.slice(0..1)
|
140
|
+
args = arr.slice(2..)
|
141
|
+
|
142
|
+
# TODO: Parse when target is not nil?
|
143
|
+
return if target || !PARSEABLE_SENDS.include?(name)
|
144
|
+
|
145
|
+
delegate = SEND_DISPATCH.find { |inner, _del| inner.include? name }&.last
|
146
|
+
send(delegate, node, parent, target, name, args) if delegate
|
147
|
+
end
|
148
|
+
|
149
|
+
# Parses a module modifier. Currently this only handles a `module_function`
|
150
|
+
# keyword.
|
151
|
+
#
|
152
|
+
# _node - Unused.
|
153
|
+
# parent - The parent object of this modifier
|
154
|
+
# _target - Unused.
|
155
|
+
# name - Name of the modifier being invoked
|
156
|
+
# args - Arguments passed to the modifier method
|
157
|
+
def parse_module_modifier(_node, parent, _target, name, args)
|
158
|
+
# FIXME: `module_function` does a lot:
|
159
|
+
# Creates module functions for the named methods. These functions may
|
160
|
+
# be called with the module as a receiver, and also become available as
|
161
|
+
# instance methods to classes that mix in the module. Module functions
|
162
|
+
# are copies of the original, and so may be changed independently. The
|
163
|
+
# instance-method versions are made private. If used with no arguments,
|
164
|
+
# subsequently defined methods become module functions. String arguments
|
165
|
+
# are converted to symbols.
|
166
|
+
# ...but we will only treat it as defs, for now.
|
167
|
+
return if args.empty? # So when nothing is provided, nothing is done.
|
168
|
+
|
169
|
+
# That's a poor decision by itself, but we may
|
170
|
+
# address this in the future.
|
171
|
+
|
172
|
+
args.each do |a|
|
173
|
+
handle_module_function(parent, name, a)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Handles a `module_function` call and either registers and modifies a
|
178
|
+
# function definition following it, or attempts to change previously-defined
|
179
|
+
# functions visibility based on the call arguments.
|
180
|
+
#
|
181
|
+
# parent - The object on which the function has as its receiver
|
182
|
+
# _name - Unused.
|
183
|
+
# target - Target object being passed to the function
|
184
|
+
def handle_module_function(parent, _name, target)
|
185
|
+
case target.try?(:type)
|
186
|
+
when :def
|
187
|
+
# This will not be ideal, but let's parse the def as a def (duh), then
|
188
|
+
# apply the same logic of :sym here. This is as not ideal as the impl.
|
189
|
+
# of parse_module_modifier itself.
|
190
|
+
parse_def(target, parent)
|
191
|
+
change_method_kind(of: target.children.first, to: :sdef, on: parent)
|
192
|
+
when :sym
|
193
|
+
# This moves the target from `def` to `sdef`...
|
194
|
+
change_method_kind(of: target.children.first, to: :sdef, on: parent)
|
195
|
+
when :string
|
196
|
+
change_method_kind(of: target.children.first.to_sym, to: :sdef, on: parent)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Parses an attribute definition
|
201
|
+
#
|
202
|
+
# node - The node being parsed
|
203
|
+
# parent - The parent containing the node being parsed
|
204
|
+
# _target - Unused.
|
205
|
+
# name - Name of the attribute definer being called
|
206
|
+
# args - List of attribute names being defined
|
207
|
+
def parse_attribute(node, parent, _target, name, args)
|
208
|
+
parent[name] ||= []
|
209
|
+
docs = nil
|
210
|
+
if args.length == 1
|
211
|
+
# When we have a single accessor, we may check for docs before it.
|
212
|
+
docs = comments_from(node.loc.expression.first_line - 1, type: :attribute)
|
213
|
+
end
|
214
|
+
|
215
|
+
args.map(&:to_a).flatten.each do |n|
|
216
|
+
parent[name].append({
|
217
|
+
docs:,
|
218
|
+
name: n,
|
219
|
+
writer_visibility: parent[:_visibility],
|
220
|
+
reader_visibility: parent[:_visibility]
|
221
|
+
})
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Parses a given class modifier (`include`, `extend`).
|
226
|
+
#
|
227
|
+
# _node - Unused.
|
228
|
+
# parent - Parent object on which the modified is being called against.
|
229
|
+
# _target - Unused.
|
230
|
+
# name - Name of the modifier being called.
|
231
|
+
# args - List of objects passed to the modifier.
|
232
|
+
def parse_class_modifier(_node, parent, _target, name, args)
|
233
|
+
parent[name] ||= []
|
234
|
+
args.each do |n|
|
235
|
+
path, base_name = parse_class_path(n)
|
236
|
+
parent[name].append({
|
237
|
+
name: base_name,
|
238
|
+
class_path: path
|
239
|
+
})
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Parses a given visibility modifier (`private`, `public`, `protected`) and
|
244
|
+
# sets the current class visibility option based on whether the call
|
245
|
+
# contains extra parameters or not. When passing names or a method
|
246
|
+
# definition after the keyword, only listed objects are changed. Otherwise,
|
247
|
+
# marks all subsequent objects following the keyword with its access level
|
248
|
+
# until another modifier is found.
|
249
|
+
#
|
250
|
+
# _node - Unused.
|
251
|
+
# parent - Parent on which the keyword is being invoked against.
|
252
|
+
# _target - Unused.
|
253
|
+
# name - Name of the modifier being invoked.
|
254
|
+
# args - List of arguments passed to the modifier.
|
255
|
+
def parse_visibility_modifier(_node, parent, _target, name, args)
|
256
|
+
# Empty args changes all items defined after that point to the
|
257
|
+
# visibility level `name`
|
258
|
+
return parent[:_visibility] = name if args.empty?
|
259
|
+
|
260
|
+
args.each do |a|
|
261
|
+
handle_visibility_keyword(parent, name, a)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Handles a visibility keyword for a specific target.
|
266
|
+
#
|
267
|
+
# parent - Parent containing the method call.
|
268
|
+
# name - Name of the visibility keyword being applied
|
269
|
+
# target - Target object on which the visibility keyword is being invoked
|
270
|
+
# against.
|
271
|
+
def handle_visibility_keyword(parent, name, target)
|
272
|
+
case target.try?(:type)
|
273
|
+
when :def
|
274
|
+
old_visibility = parent[:_visibility]
|
275
|
+
parent[:_visibility] = name
|
276
|
+
parse_def(target, parent)
|
277
|
+
parent[:_visibility] = old_visibility
|
278
|
+
|
279
|
+
when :defs
|
280
|
+
old_visibility = parent[:_visibility]
|
281
|
+
parent[:_visibility] = name
|
282
|
+
parse_singleton_method(target, parent)
|
283
|
+
parent[:_visibility] = old_visibility
|
284
|
+
|
285
|
+
when :send
|
286
|
+
# This one will be tricky... The called method can return anything,
|
287
|
+
# so we will support at least the attr_* family.
|
288
|
+
old_visibility = parent[:_visibility]
|
289
|
+
parent[:_visibility] = name
|
290
|
+
parse_send(target, parent)
|
291
|
+
parent[:_visibility] = old_visibility
|
292
|
+
|
293
|
+
when :sym
|
294
|
+
# Oh no.
|
295
|
+
change_visibility(of: target.children.first, to: name, on: parent)
|
296
|
+
|
297
|
+
else
|
298
|
+
puts "BUG: Unexpected element on handle_visibility_keyword. Please file an issue."
|
299
|
+
exit(1)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Changes the visibility of a single object on a given parent to a provided
|
304
|
+
# value.
|
305
|
+
#
|
306
|
+
# of: - Name of the object having its visibility changed.
|
307
|
+
# to: - New visibility value for the object.
|
308
|
+
# on: - The object's parent container.
|
309
|
+
def change_visibility(of:, to:, on:)
|
310
|
+
# Method?
|
311
|
+
if on.key?(:methods) && (method = on[:methods].find { |m| m[:name] == of })
|
312
|
+
return method[:visibility] = to
|
313
|
+
end
|
314
|
+
|
315
|
+
# Accessor?
|
316
|
+
writer = of.end_with? "="
|
317
|
+
normalized = of.to_s.gsub(/=$/, "").to_sym
|
318
|
+
if on.key?(:attr_accessor) && (acc = on[:attr_accessor].find { |a| a[:name] == normalized })
|
319
|
+
return acc[writer ? :writer_visibility : :reader_visibility] = to
|
320
|
+
end
|
321
|
+
|
322
|
+
# Reader or writer?
|
323
|
+
type = writer ? :attr_writer : :attr_reader
|
324
|
+
return unless on.key?(type) && (acc = on[type].find { |a| a[:name] == normalized })
|
325
|
+
|
326
|
+
acc[writer ? :writer_visibility : :reader_visibility] = to
|
327
|
+
end
|
328
|
+
|
329
|
+
# Changes the kind of a method object (E.g.: Between sdef and def)
|
330
|
+
#
|
331
|
+
# of: - Name of the method having its kind changed.
|
332
|
+
# to: - New kind value for the method.
|
333
|
+
# on: - The methods's parent container.
|
334
|
+
def change_method_kind(of:, to:, on:)
|
335
|
+
return unless on.key?(:methods) && (method = on[:methods].find { |m| m[:name] == of })
|
336
|
+
|
337
|
+
method[:type] = to
|
338
|
+
end
|
339
|
+
|
340
|
+
# Parses an instance method definition
|
341
|
+
#
|
342
|
+
# node - Node representing the method being defined
|
343
|
+
# parent - The node's parent container (e.g. class on which it is being
|
344
|
+
# defined on)
|
345
|
+
def parse_def(node, parent)
|
346
|
+
loc = node.loc.keyword
|
347
|
+
comments = comments_from(loc.line - 1, type: :def)
|
348
|
+
def_meta = method_to_meta(node)
|
349
|
+
def_meta[:doc] = comments
|
350
|
+
def_meta[:visibility] = parent ? parent[:_visibility] : :public
|
351
|
+
if parent.nil?
|
352
|
+
@methods << def_meta
|
353
|
+
else
|
354
|
+
parent[:methods] ||= []
|
355
|
+
parent[:methods] << def_meta
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
# Parses a class method definition
|
360
|
+
#
|
361
|
+
# node - Node representing the method being defined
|
362
|
+
# parent - The node's parent container (e.g. class on which it is being
|
363
|
+
# defined on)
|
364
|
+
def parse_singleton_method(node, parent)
|
365
|
+
loc = node.loc.keyword
|
366
|
+
comments = comments_from(loc.line - 1, type: :defs)
|
367
|
+
def_meta = singleton_method_to_meta(node)
|
368
|
+
def_meta[:doc] = comments
|
369
|
+
def_meta[:visibility] = parent ? parent[:_visibility] : :public
|
370
|
+
if parent.nil?
|
371
|
+
@methods << def_meta
|
372
|
+
else
|
373
|
+
parent[:methods] ||= []
|
374
|
+
parent[:methods] << def_meta
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# Parses a class definition
|
379
|
+
#
|
380
|
+
# node - Node representing the class being defined
|
381
|
+
# parent - The node's parent container (e.g. class/module on which it is
|
382
|
+
# being defined on)
|
383
|
+
def parse_class(node, parent)
|
384
|
+
loc = node.loc.keyword
|
385
|
+
comments = comments_from(loc.line - 1, type: :class)
|
386
|
+
class_meta = class_to_meta(node)
|
387
|
+
class_meta[:doc] = comments
|
388
|
+
if parent.nil?
|
389
|
+
@classes << class_meta
|
390
|
+
else
|
391
|
+
parent[:classes] ||= []
|
392
|
+
parent[:classes] << class_meta
|
393
|
+
end
|
394
|
+
node.to_a.slice(2...).each do |n|
|
395
|
+
parse!(n, class_meta)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
# Parses a singleton class definition
|
400
|
+
#
|
401
|
+
# node - Node representing the singleton class being defined
|
402
|
+
# parent - The node's parent container (e.g. class/module on which it is
|
403
|
+
# being defined on)
|
404
|
+
def parse_singleton_class(node, parent)
|
405
|
+
loc = node.loc.keyword
|
406
|
+
comments = comments_from(loc.line - 1, type: :sclass)
|
407
|
+
class_meta = singleton_class_to_meta(node)
|
408
|
+
class_meta[:doc] = comments
|
409
|
+
if parent.nil?
|
410
|
+
@classes << class_meta
|
411
|
+
else
|
412
|
+
parent[:classes] ||= []
|
413
|
+
parent[:classes] << class_meta
|
414
|
+
end
|
415
|
+
|
416
|
+
node.to_a.slice(1...).each do |n|
|
417
|
+
parse!(n, class_meta)
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
# Parses a module definition
|
422
|
+
#
|
423
|
+
# node - Node representing the module being defined
|
424
|
+
# parent - The node's parent container (e.g. class/module on which it is
|
425
|
+
# being defined on)
|
426
|
+
def parse_module(node, parent)
|
427
|
+
loc = node.loc.keyword
|
428
|
+
comments = comments_from(loc.line - 1, type: :module)
|
429
|
+
module_meta = module_to_meta(node)
|
430
|
+
module_meta[:doc] = comments
|
431
|
+
if parent.nil?
|
432
|
+
@modules << module_meta
|
433
|
+
else
|
434
|
+
parent[:modules] ||= []
|
435
|
+
parent[:modules] << module_meta
|
436
|
+
end
|
437
|
+
|
438
|
+
node.to_a.slice(1...).each do |n|
|
439
|
+
parse!(n, module_meta)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
# Generates metadata for a given method, such as its name, arguments,
|
444
|
+
# and boundaries.
|
445
|
+
#
|
446
|
+
# node - Method node being processed
|
447
|
+
#
|
448
|
+
# Returns a Hash containing metadata about the provided method.
|
449
|
+
def method_to_meta(node)
|
450
|
+
{
|
451
|
+
type: :def,
|
452
|
+
name: node.children.first,
|
453
|
+
args: parse_method_args(node),
|
454
|
+
start_at: node.loc.keyword.line,
|
455
|
+
end_at: node.loc&.end&.line || node.loc.keyword.line
|
456
|
+
}
|
457
|
+
end
|
458
|
+
|
459
|
+
# Generates metadata for a given singleton method, such as its name,
|
460
|
+
# arguments, and boundaries.
|
461
|
+
#
|
462
|
+
# node - Method node being processed
|
463
|
+
#
|
464
|
+
# Returns a Hash containing metadata about the provided singleton method.
|
465
|
+
def singleton_method_to_meta(node)
|
466
|
+
class_path, name = parse_class_path(node.children.first)
|
467
|
+
{
|
468
|
+
type: :defs,
|
469
|
+
target: name,
|
470
|
+
class_path:,
|
471
|
+
name: node.children[1],
|
472
|
+
args: parse_method_args(node),
|
473
|
+
start_at: node.loc.keyword.line,
|
474
|
+
end_at: node.loc&.end&.line || node.loc.keyword.line
|
475
|
+
}
|
476
|
+
end
|
477
|
+
|
478
|
+
# Generates metadata for a given class, such as its name, arguments,
|
479
|
+
# and boundaries.
|
480
|
+
#
|
481
|
+
# node - Class node being processed
|
482
|
+
#
|
483
|
+
# Returns a Hash containing metadata about the provided class.
|
484
|
+
def class_to_meta(node)
|
485
|
+
class_path, name = parse_class_path(node.children.first)
|
486
|
+
{
|
487
|
+
type: :class,
|
488
|
+
name:,
|
489
|
+
class_path:,
|
490
|
+
start_at: node.loc.keyword.line,
|
491
|
+
end_at: node.loc.end.line,
|
492
|
+
_visibility: :public
|
493
|
+
}.tap do |h|
|
494
|
+
if (inherits = node.children[1])
|
495
|
+
h[:inherits] = inherits.children.last
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
# Generates metadata for a given singleton class, such as its name,
|
501
|
+
# arguments, and boundaries.
|
502
|
+
#
|
503
|
+
# node - Singleton class node being processed
|
504
|
+
#
|
505
|
+
# Returns a Hash containing metadata about the provided singleton class.
|
506
|
+
def singleton_class_to_meta(node)
|
507
|
+
target = node.children.first.type
|
508
|
+
target = node.children[0].children.last if target == :const
|
509
|
+
{
|
510
|
+
type: :sclass,
|
511
|
+
target:,
|
512
|
+
_visibility: :public,
|
513
|
+
start_at: node.loc.keyword.line,
|
514
|
+
end_at: node.loc.end.line
|
515
|
+
}
|
516
|
+
end
|
517
|
+
|
518
|
+
# Generates metadata for a given module, such as its name, arguments,
|
519
|
+
# and boundaries.
|
520
|
+
#
|
521
|
+
# node - Class node being processed
|
522
|
+
#
|
523
|
+
# Returns a Hash containing metadata about the provided module.
|
524
|
+
def module_to_meta(node)
|
525
|
+
class_path, name = parse_class_path(node.children.first)
|
526
|
+
{
|
527
|
+
type: :module,
|
528
|
+
name:,
|
529
|
+
_visibility: :public,
|
530
|
+
class_path:,
|
531
|
+
start_at: node.loc.keyword.line,
|
532
|
+
end_at: node.loc.end.line
|
533
|
+
}
|
534
|
+
end
|
535
|
+
|
536
|
+
# Parses an argument list of a method defintion
|
537
|
+
#
|
538
|
+
# node - The method's node definition
|
539
|
+
#
|
540
|
+
# Returns a list of standardised hashes representing all method's arguments
|
541
|
+
def parse_method_args(node)
|
542
|
+
node.children
|
543
|
+
.find { |n| n.try?(:type) == :args }
|
544
|
+
&.to_a
|
545
|
+
&.map do |n|
|
546
|
+
{
|
547
|
+
type: n.type,
|
548
|
+
name: n.children.first
|
549
|
+
}.tap do |m|
|
550
|
+
if n.children.length > 1
|
551
|
+
val = n.children[1]
|
552
|
+
represented_type = nil
|
553
|
+
represented_value = nil
|
554
|
+
case val.type
|
555
|
+
when true, :true
|
556
|
+
represented_type = :bool
|
557
|
+
represented_value = true
|
558
|
+
when false, :false
|
559
|
+
represented_type = :bool
|
560
|
+
represented_value = false
|
561
|
+
when :nil
|
562
|
+
represented_type = :nil
|
563
|
+
represented_value = :nil
|
564
|
+
when :send
|
565
|
+
represented_type = :send
|
566
|
+
represented_value = {
|
567
|
+
target: parse_class_path(val.children.first).flatten.compact,
|
568
|
+
name: val.children.last
|
569
|
+
}
|
570
|
+
when :const
|
571
|
+
represented_type = :const
|
572
|
+
represented_value = {
|
573
|
+
target: parse_class_path(val.children.first).flatten.compact,
|
574
|
+
name: val.children.last
|
575
|
+
}
|
576
|
+
else
|
577
|
+
represented_type = val.type
|
578
|
+
represented_value = val.children.first
|
579
|
+
end
|
580
|
+
m[:value_type] = represented_type
|
581
|
+
m[:value] = represented_value
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
# Parses a given class path into an interable list of objects representing
|
588
|
+
# its full path
|
589
|
+
#
|
590
|
+
# path - Path to be processed
|
591
|
+
#
|
592
|
+
# Returns a multidimensional array structure representing the path to the
|
593
|
+
# provided value.
|
594
|
+
def parse_class_path(path)
|
595
|
+
return [[], :self] if path.try?(:type) == :self
|
596
|
+
|
597
|
+
path = path.to_a
|
598
|
+
name = path[1]
|
599
|
+
parents = []
|
600
|
+
parent = path.first.try?(:to_a) || []
|
601
|
+
until parent&.empty?
|
602
|
+
parents << parent.last
|
603
|
+
parent = parent.first.to_a
|
604
|
+
end
|
605
|
+
[parents.reverse, name]
|
606
|
+
end
|
607
|
+
|
608
|
+
# Strips all contained module's structures of its internal keys used by
|
609
|
+
# the parser.
|
610
|
+
def clean_modules!
|
611
|
+
@modules.each { |m| m.delete(:_visibility) }
|
612
|
+
end
|
613
|
+
|
614
|
+
# Recursivelly calls #clean_class for all classes contained within the
|
615
|
+
# parser.
|
616
|
+
def clean_classes!
|
617
|
+
@classes.map! { |c| clean_class(c) }
|
618
|
+
end
|
619
|
+
|
620
|
+
# Strps the provided class structure of its internal keys used by the
|
621
|
+
# parser.
|
622
|
+
#
|
623
|
+
# cls - Class structure to be cleaned.
|
624
|
+
def clean_class(cls)
|
625
|
+
cls.delete(:_visibility)
|
626
|
+
cls[:classes].map! { |c| clean_class(c) } if cls.key? :classes
|
627
|
+
cls
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|
data/lib/docrb/spec.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Docrb
|
4
|
+
# Spec provides a small wrapper around Gem::Specification to provide metadata
|
5
|
+
# about a given gem being documented.
|
6
|
+
class Spec
|
7
|
+
# Public: Finds a .gemspec file within the provided input folder and
|
8
|
+
# processes it.
|
9
|
+
#
|
10
|
+
# input - Folder to search for a .gemspec file
|
11
|
+
#
|
12
|
+
# Returns a hash containing extracted information from the found gemspec
|
13
|
+
# file. The following keys are returned: :summary, :name, :license,
|
14
|
+
# :git_url, :authors, :host_url
|
15
|
+
def self.parse_folder(input)
|
16
|
+
spec = Dir["#{input}/*"].find { |f| f.end_with? ".gemspec" }
|
17
|
+
return if spec.nil?
|
18
|
+
|
19
|
+
data = Gem::Specification.load(spec)
|
20
|
+
is_private = data.metadata.key? "allowed_push_host"
|
21
|
+
{
|
22
|
+
summary: data.summary,
|
23
|
+
name: data.name,
|
24
|
+
license: data.license,
|
25
|
+
git_url: data.metadata["source_code_uri"],
|
26
|
+
authors: data.authors,
|
27
|
+
host_url: is_private ? nil : "https://rubygems.org/gems/#{data.name}"
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|