docscribe 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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +11 -0
- data/.rubocop_todo.yml +73 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +73 -0
- data/LICENSE.txt +21 -0
- data/README.md +451 -0
- data/Rakefile +12 -0
- data/exe/docscribe +77 -0
- data/lib/docscribe/config.rb +245 -0
- data/lib/docscribe/infer.rb +302 -0
- data/lib/docscribe/inline_rewriter.rb +534 -0
- data/lib/docscribe/version.rb +5 -0
- data/lib/docscribe.rb +10 -0
- data/rakelib/docs.rake +73 -0
- data/stingray_docs_internal.gemspec +41 -0
- metadata +144 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'parser/current'
|
|
4
|
+
require 'docscribe/infer'
|
|
5
|
+
|
|
6
|
+
module Docscribe
|
|
7
|
+
module InlineRewriter
|
|
8
|
+
# +Docscribe::InlineRewriter.insert_comments+ -> Object
|
|
9
|
+
#
|
|
10
|
+
# Method documentation.
|
|
11
|
+
#
|
|
12
|
+
# @param [Object] code Param documentation.
|
|
13
|
+
# @param [Boolean] rewrite Param documentation.
|
|
14
|
+
# @param [nil] config Param documentation.
|
|
15
|
+
# @return [Object]
|
|
16
|
+
def self.insert_comments(code, rewrite: false, config: nil)
|
|
17
|
+
buffer = Parser::Source::Buffer.new('(inline)')
|
|
18
|
+
buffer.source = code
|
|
19
|
+
parser = Parser::CurrentRuby.new
|
|
20
|
+
ast = parser.parse(buffer)
|
|
21
|
+
return code unless ast
|
|
22
|
+
|
|
23
|
+
config ||= Docscribe::Config.load
|
|
24
|
+
|
|
25
|
+
collector = Collector.new(buffer)
|
|
26
|
+
collector.process(ast)
|
|
27
|
+
|
|
28
|
+
rewriter = Parser::Source::TreeRewriter.new(buffer)
|
|
29
|
+
|
|
30
|
+
collector.insertions
|
|
31
|
+
.sort_by { |ins| ins.node.loc.expression.begin_pos }
|
|
32
|
+
.reverse_each do |ins|
|
|
33
|
+
bol_range = line_start_range(buffer, ins.node)
|
|
34
|
+
|
|
35
|
+
if rewrite
|
|
36
|
+
# If there is a comment block immediately above, remove it (and its trailing blank lines)
|
|
37
|
+
if (range = comment_block_removal_range(buffer, bol_range.begin_pos))
|
|
38
|
+
rewriter.remove(range)
|
|
39
|
+
end
|
|
40
|
+
elsif already_has_doc_immediately_above?(buffer, bol_range.begin_pos)
|
|
41
|
+
# Skip if a doc already exists immediately above
|
|
42
|
+
next
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
doc = build_doc_for_node(buffer, ins, config)
|
|
46
|
+
next unless doc && !doc.empty?
|
|
47
|
+
|
|
48
|
+
rewriter.insert_before(bol_range, doc)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
rewriter.process
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# +Docscribe::InlineRewriter.line_start_range+ -> Range
|
|
55
|
+
#
|
|
56
|
+
# Method documentation.
|
|
57
|
+
#
|
|
58
|
+
# @param [Object] buffer Param documentation.
|
|
59
|
+
# @param [Object] node Param documentation.
|
|
60
|
+
# @return [Range]
|
|
61
|
+
def self.line_start_range(buffer, node)
|
|
62
|
+
start_pos = node.loc.expression.begin_pos
|
|
63
|
+
src = buffer.source
|
|
64
|
+
bol = src.rindex("\n", start_pos - 1) || -1
|
|
65
|
+
Parser::Source::Range.new(buffer, bol + 1, bol + 1)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# +Docscribe::InlineRewriter.node_name+ -> Object
|
|
69
|
+
#
|
|
70
|
+
# Method documentation.
|
|
71
|
+
#
|
|
72
|
+
# @param [Object] node Param documentation.
|
|
73
|
+
# @return [Object]
|
|
74
|
+
def self.node_name(node)
|
|
75
|
+
case node.type
|
|
76
|
+
when :def
|
|
77
|
+
node.children[0]
|
|
78
|
+
when :defs
|
|
79
|
+
node.children[1] # method name symbol
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# +Docscribe::InlineRewriter.comment_block_removal_range+ -> Range
|
|
84
|
+
#
|
|
85
|
+
# Method documentation.
|
|
86
|
+
#
|
|
87
|
+
# @param [Object] buffer Param documentation.
|
|
88
|
+
# @param [Object] def_bol_pos Param documentation.
|
|
89
|
+
# @return [Range]
|
|
90
|
+
def self.comment_block_removal_range(buffer, def_bol_pos)
|
|
91
|
+
src = buffer.source
|
|
92
|
+
lines = src.lines
|
|
93
|
+
# Find def line index
|
|
94
|
+
def_line_idx = src[0...def_bol_pos].count("\n")
|
|
95
|
+
i = def_line_idx - 1
|
|
96
|
+
|
|
97
|
+
# Walk up and skip blank lines directly above def
|
|
98
|
+
i -= 1 while i >= 0 && lines[i].strip.empty?
|
|
99
|
+
# Now if the nearest non-blank line isn't a comment, nothing to remove
|
|
100
|
+
return nil unless i >= 0 && lines[i] =~ /^\s*#/
|
|
101
|
+
|
|
102
|
+
# Find the start of the contiguous comment block
|
|
103
|
+
start_idx = i
|
|
104
|
+
start_idx -= 1 while start_idx >= 0 && lines[start_idx] =~ /^\s*#/
|
|
105
|
+
start_idx += 1
|
|
106
|
+
|
|
107
|
+
# End position is at def_bol_pos; start position is BOL of start_idx
|
|
108
|
+
# Compute absolute buffer positions
|
|
109
|
+
# Position of BOL for start_idx:
|
|
110
|
+
start_pos = 0
|
|
111
|
+
if start_idx.positive?
|
|
112
|
+
# Sum lengths of all preceding lines
|
|
113
|
+
start_pos = lines[0...start_idx].join.length
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
Parser::Source::Range.new(buffer, start_pos, def_bol_pos)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# +Docscribe::InlineRewriter.already_has_doc_immediately_above?+ -> Object
|
|
120
|
+
#
|
|
121
|
+
# Method documentation.
|
|
122
|
+
#
|
|
123
|
+
# @param [Object] buffer Param documentation.
|
|
124
|
+
# @param [Object] insert_pos Param documentation.
|
|
125
|
+
# @return [Object]
|
|
126
|
+
def self.already_has_doc_immediately_above?(buffer, insert_pos)
|
|
127
|
+
src = buffer.source
|
|
128
|
+
lines = src.lines
|
|
129
|
+
current_line_index = src[0...insert_pos].count("\n")
|
|
130
|
+
i = current_line_index - 1
|
|
131
|
+
i -= 1 while i >= 0 && lines[i].strip.empty?
|
|
132
|
+
return false if i.negative?
|
|
133
|
+
|
|
134
|
+
!!(lines[i] =~ /^\s*#/)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# +Docscribe::InlineRewriter.build_doc_for_node+ -> Object
|
|
138
|
+
#
|
|
139
|
+
# Method documentation.
|
|
140
|
+
#
|
|
141
|
+
# @param [Object] _buffer Param documentation.
|
|
142
|
+
# @param [Object] insertion Param documentation.
|
|
143
|
+
# @param [Object] config Param documentation.
|
|
144
|
+
# @raise [StandardError]
|
|
145
|
+
# @return [Object]
|
|
146
|
+
# @return [nil] if StandardError
|
|
147
|
+
def self.build_doc_for_node(_buffer, insertion, config)
|
|
148
|
+
node = insertion.node
|
|
149
|
+
indent = ' ' * node.loc.expression.column
|
|
150
|
+
|
|
151
|
+
name =
|
|
152
|
+
case node.type
|
|
153
|
+
when :def then node.children[0]
|
|
154
|
+
when :defs then node.children[1]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
scope = insertion.scope
|
|
158
|
+
visibility = insertion.visibility
|
|
159
|
+
|
|
160
|
+
method_symbol = scope == :instance ? '#' : '.'
|
|
161
|
+
container = insertion.container
|
|
162
|
+
|
|
163
|
+
# Params
|
|
164
|
+
params_block = config.emit_param_tags? ? build_params_block(node, indent) : nil
|
|
165
|
+
|
|
166
|
+
# Raises (rescue and/or raise calls)
|
|
167
|
+
raise_types = config.emit_raise_tags? ? Docscribe::Infer.infer_raises_from_node(node) : []
|
|
168
|
+
|
|
169
|
+
# Returns: normal + conditional rescue returns
|
|
170
|
+
spec = Docscribe::Infer.returns_spec_from_node(node)
|
|
171
|
+
normal_type = spec[:normal]
|
|
172
|
+
rescue_specs = spec[:rescues]
|
|
173
|
+
|
|
174
|
+
lines = []
|
|
175
|
+
if config.emit_header?
|
|
176
|
+
lines << "#{indent}# +#{container}#{method_symbol}#{name}+ -> #{normal_type}"
|
|
177
|
+
lines << "#{indent}#"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Default doc text (configurable per scope/vis)
|
|
181
|
+
lines << "#{indent}# #{config.default_message(scope, visibility)}"
|
|
182
|
+
lines << "#{indent}#"
|
|
183
|
+
|
|
184
|
+
if config.emit_visibility_tags?
|
|
185
|
+
case visibility
|
|
186
|
+
when :private then lines << "#{indent}# @private"
|
|
187
|
+
when :protected then lines << "#{indent}# @protected"
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
lines.concat(params_block) if params_block
|
|
192
|
+
|
|
193
|
+
raise_types.each { |rt| lines << "#{indent}# @raise [#{rt}]" } if config.emit_raise_tags?
|
|
194
|
+
|
|
195
|
+
lines << "#{indent}# @return [#{normal_type}]" if config.emit_return_tag?(scope, visibility)
|
|
196
|
+
|
|
197
|
+
if config.emit_rescue_conditional_returns?
|
|
198
|
+
rescue_specs.each do |(exceptions, rtype)|
|
|
199
|
+
ex_display = exceptions.join(', ')
|
|
200
|
+
lines << "#{indent}# @return [#{rtype}] if #{ex_display}"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
lines.map { |l| "#{l}\n" }.join
|
|
205
|
+
rescue StandardError
|
|
206
|
+
nil
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# +Docscribe::InlineRewriter.build_params_block+ -> Object?
|
|
210
|
+
#
|
|
211
|
+
# Method documentation.
|
|
212
|
+
#
|
|
213
|
+
# @param [Object] node Param documentation.
|
|
214
|
+
# @param [Object] indent Param documentation.
|
|
215
|
+
# @return [Object?]
|
|
216
|
+
def self.build_params_block(node, indent)
|
|
217
|
+
args =
|
|
218
|
+
case node.type
|
|
219
|
+
when :def then node.children[1]
|
|
220
|
+
when :defs then node.children[2] # FIX: args is children[2], not [3]
|
|
221
|
+
end
|
|
222
|
+
return nil unless args
|
|
223
|
+
|
|
224
|
+
params = []
|
|
225
|
+
(args.children || []).each do |a|
|
|
226
|
+
case a.type
|
|
227
|
+
when :arg
|
|
228
|
+
name = a.children.first.to_s
|
|
229
|
+
ty = Infer.infer_param_type(name, nil)
|
|
230
|
+
params << "#{indent}# @param [#{ty}] #{name} Param documentation."
|
|
231
|
+
when :optarg
|
|
232
|
+
name, default = *a
|
|
233
|
+
ty = Infer.infer_param_type(name.to_s, default&.loc&.expression&.source)
|
|
234
|
+
params << "#{indent}# @param [#{ty}] #{name} Param documentation."
|
|
235
|
+
when :kwarg
|
|
236
|
+
name = "#{a.children.first}:"
|
|
237
|
+
ty = Infer.infer_param_type(name, nil)
|
|
238
|
+
pname = name.sub(/:$/, '')
|
|
239
|
+
params << "#{indent}# @param [#{ty}] #{pname} Param documentation."
|
|
240
|
+
when :kwoptarg
|
|
241
|
+
name, default = *a
|
|
242
|
+
name = "#{name}:"
|
|
243
|
+
ty = Infer.infer_param_type(name, default&.loc&.expression&.source)
|
|
244
|
+
pname = name.sub(/:$/, '')
|
|
245
|
+
params << "#{indent}# @param [#{ty}] #{pname} Param documentation."
|
|
246
|
+
when :restarg
|
|
247
|
+
name = "*#{a.children.first}"
|
|
248
|
+
ty = Infer.infer_param_type(name, nil)
|
|
249
|
+
pname = a.children.first.to_s
|
|
250
|
+
params << "#{indent}# @param [#{ty}] #{pname} Param documentation."
|
|
251
|
+
when :kwrestarg
|
|
252
|
+
name = "**#{a.children.first || 'kwargs'}"
|
|
253
|
+
ty = Infer.infer_param_type(name, nil)
|
|
254
|
+
pname = (a.children.first || 'kwargs').to_s
|
|
255
|
+
params << "#{indent}# @param [#{ty}] #{pname} Param documentation."
|
|
256
|
+
when :blockarg
|
|
257
|
+
name = "&#{a.children.first}"
|
|
258
|
+
ty = Infer.infer_param_type(name, nil)
|
|
259
|
+
pname = a.children.first.to_s
|
|
260
|
+
params << "#{indent}# @param [#{ty}] #{pname} Param documentation."
|
|
261
|
+
when :forward_arg
|
|
262
|
+
# Ruby 3 '...' forwarding; skip
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
params.empty? ? nil : params
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
class VisibilityCtx
|
|
269
|
+
attr_accessor :default_instance_vis, :default_class_vis, :inside_sclass
|
|
270
|
+
attr_reader :explicit_instance, :explicit_class
|
|
271
|
+
|
|
272
|
+
# +Docscribe::InlineRewriter::VisibilityCtx#initialize+ -> Object
|
|
273
|
+
#
|
|
274
|
+
# Method documentation.
|
|
275
|
+
#
|
|
276
|
+
# @return [Object]
|
|
277
|
+
def initialize
|
|
278
|
+
@default_instance_vis = :public
|
|
279
|
+
@default_class_vis = :public
|
|
280
|
+
@explicit_instance = {} # { name_sym => :private|:protected|:public }
|
|
281
|
+
@explicit_class = {} # { name_sym => :private|:protected|:public }
|
|
282
|
+
@inside_sclass = false
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# +Docscribe::InlineRewriter::VisibilityCtx#dup+ -> Object
|
|
286
|
+
#
|
|
287
|
+
# Method documentation.
|
|
288
|
+
#
|
|
289
|
+
# @return [Object]
|
|
290
|
+
def dup
|
|
291
|
+
c = VisibilityCtx.new
|
|
292
|
+
c.default_instance_vis = default_instance_vis
|
|
293
|
+
c.default_class_vis = default_class_vis
|
|
294
|
+
c.inside_sclass = inside_sclass
|
|
295
|
+
c.explicit_instance.merge!(explicit_instance)
|
|
296
|
+
c.explicit_class.merge!(explicit_class)
|
|
297
|
+
c
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Walks nodes and records where to insert docstrings
|
|
302
|
+
class Collector < Parser::AST::Processor
|
|
303
|
+
Insertion = Struct.new(:node, :scope, :visibility, :container)
|
|
304
|
+
|
|
305
|
+
attr_reader :insertions
|
|
306
|
+
|
|
307
|
+
# +Docscribe::InlineRewriter::Collector#initialize+ -> Object
|
|
308
|
+
#
|
|
309
|
+
# Method documentation.
|
|
310
|
+
#
|
|
311
|
+
# @param [Object] buffer Param documentation.
|
|
312
|
+
# @return [Object]
|
|
313
|
+
def initialize(buffer)
|
|
314
|
+
super()
|
|
315
|
+
@buffer = buffer
|
|
316
|
+
@insertions = []
|
|
317
|
+
@name_stack = [] # e.g., ['Demo']
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# +Docscribe::InlineRewriter::Collector#on_class+ -> Object
|
|
321
|
+
#
|
|
322
|
+
# Method documentation.
|
|
323
|
+
#
|
|
324
|
+
# @param [Object] node Param documentation.
|
|
325
|
+
# @return [Object]
|
|
326
|
+
def on_class(node)
|
|
327
|
+
cname_node, _super_node, body = *node
|
|
328
|
+
@name_stack.push(const_name(cname_node))
|
|
329
|
+
ctx = VisibilityCtx.new
|
|
330
|
+
process_body(body, ctx)
|
|
331
|
+
@name_stack.pop
|
|
332
|
+
node
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# +Docscribe::InlineRewriter::Collector#on_module+ -> Object
|
|
336
|
+
#
|
|
337
|
+
# Method documentation.
|
|
338
|
+
#
|
|
339
|
+
# @param [Object] node Param documentation.
|
|
340
|
+
# @return [Object]
|
|
341
|
+
def on_module(node)
|
|
342
|
+
cname_node, body = *node
|
|
343
|
+
@name_stack.push(const_name(cname_node))
|
|
344
|
+
ctx = VisibilityCtx.new
|
|
345
|
+
process_body(body, ctx)
|
|
346
|
+
@name_stack.pop
|
|
347
|
+
node
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# +Docscribe::InlineRewriter::Collector#on_def+ -> Object
|
|
351
|
+
#
|
|
352
|
+
# Method documentation.
|
|
353
|
+
#
|
|
354
|
+
# @param [Object] node Param documentation.
|
|
355
|
+
# @return [Object]
|
|
356
|
+
def on_def(node)
|
|
357
|
+
@insertions << Insertion.new(node, :instance, :public, current_container)
|
|
358
|
+
node
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# +Docscribe::InlineRewriter::Collector#on_defs+ -> Object
|
|
362
|
+
#
|
|
363
|
+
# Method documentation.
|
|
364
|
+
#
|
|
365
|
+
# @param [Object] node Param documentation.
|
|
366
|
+
# @return [Object]
|
|
367
|
+
def on_defs(node)
|
|
368
|
+
@insertions << Insertion.new(node, :class, :public, current_container)
|
|
369
|
+
node
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
private
|
|
373
|
+
|
|
374
|
+
# +Docscribe::InlineRewriter::Collector#process_stmt+ -> Object
|
|
375
|
+
#
|
|
376
|
+
# Method documentation.
|
|
377
|
+
#
|
|
378
|
+
# @private
|
|
379
|
+
# @param [Object] node Param documentation.
|
|
380
|
+
# @param [Object] ctx Param documentation.
|
|
381
|
+
# @return [Object]
|
|
382
|
+
def process_stmt(node, ctx)
|
|
383
|
+
return unless node
|
|
384
|
+
|
|
385
|
+
case node.type
|
|
386
|
+
when :def
|
|
387
|
+
name, _args, _body = *node
|
|
388
|
+
if ctx.inside_sclass
|
|
389
|
+
vis = ctx.explicit_class[name] || ctx.default_class_vis
|
|
390
|
+
scope = :class
|
|
391
|
+
else
|
|
392
|
+
vis = ctx.explicit_instance[name] || ctx.default_instance_vis
|
|
393
|
+
scope = :instance
|
|
394
|
+
end
|
|
395
|
+
@insertions << Insertion.new(node, scope, vis, current_container)
|
|
396
|
+
|
|
397
|
+
when :defs
|
|
398
|
+
_, name, _args, _body = *node
|
|
399
|
+
vis = ctx.explicit_class[name] || ctx.default_class_vis
|
|
400
|
+
@insertions << Insertion.new(node, :class, vis, current_container)
|
|
401
|
+
|
|
402
|
+
when :sclass
|
|
403
|
+
recv, body = *node
|
|
404
|
+
inner_ctx = ctx.dup
|
|
405
|
+
inner_ctx.inside_sclass = self_node?(recv)
|
|
406
|
+
inner_ctx.default_class_vis = :public
|
|
407
|
+
process_body(body, inner_ctx)
|
|
408
|
+
|
|
409
|
+
when :send
|
|
410
|
+
process_visibility_send(node, ctx)
|
|
411
|
+
|
|
412
|
+
else
|
|
413
|
+
process(node)
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# +Docscribe::InlineRewriter::Collector#process_visibility_send+ -> Object
|
|
418
|
+
#
|
|
419
|
+
# Method documentation.
|
|
420
|
+
#
|
|
421
|
+
# @private
|
|
422
|
+
# @param [Object] node Param documentation.
|
|
423
|
+
# @param [Object] ctx Param documentation.
|
|
424
|
+
# @return [Object]
|
|
425
|
+
def process_visibility_send(node, ctx)
|
|
426
|
+
recv, meth, *args = *node
|
|
427
|
+
return unless recv.nil? && %i[private protected public].include?(meth)
|
|
428
|
+
|
|
429
|
+
if args.empty?
|
|
430
|
+
# bare keyword: affects current def-target
|
|
431
|
+
if ctx.inside_sclass
|
|
432
|
+
ctx.default_class_vis = meth
|
|
433
|
+
else
|
|
434
|
+
ctx.default_instance_vis = meth
|
|
435
|
+
end
|
|
436
|
+
else
|
|
437
|
+
# explicit list: affects current def-target
|
|
438
|
+
args.each do |arg|
|
|
439
|
+
sym = extract_name_sym(arg)
|
|
440
|
+
next unless sym
|
|
441
|
+
|
|
442
|
+
if ctx.inside_sclass
|
|
443
|
+
ctx.explicit_class[sym] = meth
|
|
444
|
+
else
|
|
445
|
+
ctx.explicit_instance[sym] = meth
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
target = ctx.inside_sclass ? 'class' : 'instance'
|
|
449
|
+
if args.empty?
|
|
450
|
+
puts "[vis] bare #{meth} -> default_#{target}_vis=#{meth}"
|
|
451
|
+
else
|
|
452
|
+
puts "[vis] explicit #{meth} -> #{target} names=#{args.map { |a| extract_name_sym(a) }.inspect}"
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# +Docscribe::InlineRewriter::Collector#extract_name_sym+ -> Object
|
|
459
|
+
#
|
|
460
|
+
# Method documentation.
|
|
461
|
+
#
|
|
462
|
+
# @private
|
|
463
|
+
# @param [Object] arg Param documentation.
|
|
464
|
+
# @return [Object]
|
|
465
|
+
def extract_name_sym(arg)
|
|
466
|
+
case arg.type
|
|
467
|
+
when :sym then arg.children.first
|
|
468
|
+
when :str then arg.children.first.to_sym
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
# +Docscribe::InlineRewriter::Collector#self_node?+ -> Object
|
|
473
|
+
#
|
|
474
|
+
# Method documentation.
|
|
475
|
+
#
|
|
476
|
+
# @private
|
|
477
|
+
# @param [Object] node Param documentation.
|
|
478
|
+
# @return [Object]
|
|
479
|
+
def self_node?(node)
|
|
480
|
+
node && node.type == :self
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# +Docscribe::InlineRewriter::Collector#current_container+ -> Object
|
|
484
|
+
#
|
|
485
|
+
# Method documentation.
|
|
486
|
+
#
|
|
487
|
+
# @private
|
|
488
|
+
# @return [Object]
|
|
489
|
+
def current_container
|
|
490
|
+
@name_stack.empty? ? 'Object' : @name_stack.join('::')
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# +Docscribe::InlineRewriter::Collector#const_name+ -> Object
|
|
494
|
+
#
|
|
495
|
+
# Method documentation.
|
|
496
|
+
#
|
|
497
|
+
# @private
|
|
498
|
+
# @param [Object] node Param documentation.
|
|
499
|
+
# @return [Object]
|
|
500
|
+
def const_name(node)
|
|
501
|
+
return 'Object' unless node
|
|
502
|
+
|
|
503
|
+
case node.type
|
|
504
|
+
when :const
|
|
505
|
+
scope, name = *node
|
|
506
|
+
scope_name = scope ? const_name(scope) : nil
|
|
507
|
+
[scope_name, name].compact.join('::')
|
|
508
|
+
when :cbase
|
|
509
|
+
'' # leading ::
|
|
510
|
+
else
|
|
511
|
+
node.loc.expression.source # fallback
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# +Docscribe::InlineRewriter::Collector#process_body+ -> Object
|
|
516
|
+
#
|
|
517
|
+
# Method documentation.
|
|
518
|
+
#
|
|
519
|
+
# @private
|
|
520
|
+
# @param [Object] body Param documentation.
|
|
521
|
+
# @param [Object] ctx Param documentation.
|
|
522
|
+
# @return [Object]
|
|
523
|
+
def process_body(body, ctx)
|
|
524
|
+
return unless body
|
|
525
|
+
|
|
526
|
+
if body.type == :begin
|
|
527
|
+
body.children.each { |child| process_stmt(child, ctx) }
|
|
528
|
+
else
|
|
529
|
+
process_stmt(body, ctx)
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
end
|
data/lib/docscribe.rb
ADDED
data/rakelib/docs.rake
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
require 'yard'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
|
|
7
|
+
GEM_NAME = Bundler.load_gemspec(Dir.glob('*.gemspec').first).name
|
|
8
|
+
DOCS_REPO_NAME = "#{GEM_NAME}_docs".freeze
|
|
9
|
+
DOCS_REPO_PATH = "../#{DOCS_REPO_NAME}".freeze
|
|
10
|
+
|
|
11
|
+
namespace :docs do # rubocop:disable Metrics/BlockLength
|
|
12
|
+
desc 'Generate new docs and push them to repo'
|
|
13
|
+
task generate: :clean do
|
|
14
|
+
puts 'Generating docs...'
|
|
15
|
+
YARD::CLI::Yardoc.run
|
|
16
|
+
puts 'OK!'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc 'Clean existing docs'
|
|
20
|
+
task :clean do
|
|
21
|
+
if File.directory?('doc')
|
|
22
|
+
FileUtils.rm_rf('doc')
|
|
23
|
+
puts 'Cleaned existing docs directory'
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
desc 'Pushes docs to github'
|
|
28
|
+
task push: :generate do
|
|
29
|
+
unless File.directory?(DOCS_REPO_PATH)
|
|
30
|
+
puts "Error: Docs repo not found at #{DOCS_REPO_PATH}"
|
|
31
|
+
puts 'Please clone the docs repo first:'
|
|
32
|
+
puts " git clone git@github.com:unurgunite/#{DOCS_REPO_NAME}.git #{DOCS_REPO_PATH}"
|
|
33
|
+
exit 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
puts "Copying docs to #{DOCS_REPO_PATH}..."
|
|
37
|
+
FileUtils.mkdir_p('doc') unless File.directory?('doc')
|
|
38
|
+
FileUtils.cp_r('doc/.', DOCS_REPO_PATH)
|
|
39
|
+
|
|
40
|
+
puts 'Changing dir...'
|
|
41
|
+
Dir.chdir(DOCS_REPO_PATH) do
|
|
42
|
+
puts 'Checking git status...'
|
|
43
|
+
status_output = `git status --porcelain`
|
|
44
|
+
|
|
45
|
+
if status_output.strip.empty?
|
|
46
|
+
puts 'No changes to commit'
|
|
47
|
+
else
|
|
48
|
+
puts 'Committing git changes...'
|
|
49
|
+
puts `git add .`
|
|
50
|
+
commit_result = `git commit -m "Update docs for #{GEM_NAME} #{Time.now.utc.strftime('%Y-%m-%d %H:%M:%S UTC')}"`
|
|
51
|
+
puts commit_result
|
|
52
|
+
|
|
53
|
+
if $CHILD_STATUS.success?
|
|
54
|
+
puts 'Pushing to GitHub...'
|
|
55
|
+
push_result = `git push origin master 2>&1`
|
|
56
|
+
puts push_result
|
|
57
|
+
if $CHILD_STATUS.success?
|
|
58
|
+
puts 'Docs successfully pushed!'
|
|
59
|
+
else
|
|
60
|
+
puts 'Push failed!'
|
|
61
|
+
exit 1
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
puts 'Commit failed!'
|
|
65
|
+
exit 1
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
desc 'Generate and push docs in one command'
|
|
72
|
+
task deploy: :push
|
|
73
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/docscribe/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'docscribe'
|
|
7
|
+
spec.version = Docscribe::VERSION
|
|
8
|
+
spec.authors = ['unurgunite']
|
|
9
|
+
spec.email = ['senpaiguru1488@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Autogenerate documentation for Ruby code with YARD syntax.'
|
|
12
|
+
spec.homepage = 'https://github.com/unurgunite/docscribe'
|
|
13
|
+
spec.required_ruby_version = '>= 3.0'
|
|
14
|
+
|
|
15
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
16
|
+
spec.metadata['source_code_uri'] = 'https://github.com/unurgunite/docscribe'
|
|
17
|
+
spec.metadata['changelog_uri'] = 'https://github.com/unurgunite/docscribe/blob/master/CHANGELOG.md'
|
|
18
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
19
|
+
|
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
24
|
+
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
spec.bindir = 'exe'
|
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
29
|
+
spec.require_paths = ['lib']
|
|
30
|
+
|
|
31
|
+
# Uncomment to register a new dependency of your gem
|
|
32
|
+
spec.add_dependency 'parser', '>= 3.0'
|
|
33
|
+
spec.add_development_dependency 'rake'
|
|
34
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
35
|
+
spec.add_development_dependency 'rubocop'
|
|
36
|
+
spec.add_development_dependency 'rubocop-sorted_methods_by_call'
|
|
37
|
+
spec.add_development_dependency 'yard', '>= 0.9.34'
|
|
38
|
+
|
|
39
|
+
# For more information and examples about making a new gem, check out our
|
|
40
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
41
|
+
end
|