docscribe 1.2.1 → 1.3.1
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/README.md +296 -2
- data/lib/docscribe/cli/config_builder.rb +17 -5
- data/lib/docscribe/cli/generate.rb +309 -0
- data/lib/docscribe/cli/options.rb +8 -1
- data/lib/docscribe/cli/run.rb +52 -51
- data/lib/docscribe/cli.rb +8 -2
- data/lib/docscribe/config/defaults.rb +8 -2
- data/lib/docscribe/config/filtering.rb +2 -2
- data/lib/docscribe/config/plugin.rb +29 -0
- data/lib/docscribe/config/rbs.rb +38 -1
- data/lib/docscribe/config/template.rb +54 -130
- data/lib/docscribe/config.rb +1 -0
- data/lib/docscribe/infer/returns.rb +151 -12
- data/lib/docscribe/infer.rb +7 -2
- data/lib/docscribe/inline_rewriter/collector.rb +144 -97
- data/lib/docscribe/inline_rewriter/doc_block.rb +10 -1
- data/lib/docscribe/inline_rewriter/doc_builder.rb +256 -54
- data/lib/docscribe/inline_rewriter.rb +216 -56
- data/lib/docscribe/plugin/base/collector_plugin.rb +53 -0
- data/lib/docscribe/plugin/base/tag_plugin.rb +38 -0
- data/lib/docscribe/plugin/context.rb +38 -0
- data/lib/docscribe/plugin/registry.rb +69 -0
- data/lib/docscribe/plugin/tag.rb +23 -0
- data/lib/docscribe/plugin.rb +58 -0
- data/lib/docscribe/types/rbs/collection_loader.rb +50 -0
- data/lib/docscribe/types/rbs/provider.rb +3 -0
- data/lib/docscribe/version.rb +1 -1
- metadata +13 -5
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
|
|
5
|
+
module Docscribe
|
|
6
|
+
module CLI
|
|
7
|
+
# Generator for TagPlugin and CollectorPlugin boilerplate.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# docscribe generate tag MyPlugin
|
|
11
|
+
# docscribe generate collector MyPlugin
|
|
12
|
+
# docscribe generate tag MyPlugin --output lib/docscribe_plugins
|
|
13
|
+
# docscribe generate tag MyPlugin --stdout
|
|
14
|
+
module Generate
|
|
15
|
+
PLUGIN_TYPES = %w[tag collector].freeze
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
# Run the `generate` subcommand.
|
|
19
|
+
#
|
|
20
|
+
# @param [Array<String>] argv
|
|
21
|
+
# @raise [OptionParser::InvalidOption]
|
|
22
|
+
# @return [Integer] exit code
|
|
23
|
+
def run(argv)
|
|
24
|
+
opts = {
|
|
25
|
+
output: nil,
|
|
26
|
+
stdout: false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
parser = OptionParser.new do |o|
|
|
30
|
+
o.banner = <<~TEXT
|
|
31
|
+
Usage: docscribe generate <type> <PluginName> [options]
|
|
32
|
+
|
|
33
|
+
Types:
|
|
34
|
+
tag Generate a TagPlugin skeleton
|
|
35
|
+
collector Generate a CollectorPlugin skeleton
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
TEXT
|
|
39
|
+
|
|
40
|
+
o.on('--output DIR', 'Directory to write the plugin file (default: .)') { |v| opts[:output] = v }
|
|
41
|
+
o.on('--stdout', 'Print the generated plugin to STDOUT instead of writing a file') { opts[:stdout] = true }
|
|
42
|
+
o.on('-h', '--help', 'Show this help') do
|
|
43
|
+
puts o
|
|
44
|
+
return 0
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
begin
|
|
49
|
+
parser.parse!(argv)
|
|
50
|
+
rescue OptionParser::InvalidOption => e
|
|
51
|
+
warn e.message
|
|
52
|
+
warn parser
|
|
53
|
+
return 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
plugin_type = argv.shift
|
|
57
|
+
class_name = argv.shift
|
|
58
|
+
|
|
59
|
+
unless plugin_type && class_name
|
|
60
|
+
warn 'Error: both <type> and <PluginName> are required.'
|
|
61
|
+
warn parser
|
|
62
|
+
return 1
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
unless PLUGIN_TYPES.include?(plugin_type)
|
|
66
|
+
warn "Error: unknown type #{plugin_type.inspect}. Must be one of: #{PLUGIN_TYPES.join(', ')}."
|
|
67
|
+
return 1
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
unless valid_constant?(class_name)
|
|
71
|
+
warn "Error: #{class_name.inspect} is not a valid Ruby constant name."
|
|
72
|
+
return 1
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
content = render(plugin_type, class_name)
|
|
76
|
+
|
|
77
|
+
if opts[:stdout]
|
|
78
|
+
puts content
|
|
79
|
+
return 0
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
write_plugin(content, plugin_type: plugin_type, class_name: class_name, output_dir: opts[:output] || '.')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# Check whether a string is a valid Ruby constant name.
|
|
88
|
+
#
|
|
89
|
+
# @private
|
|
90
|
+
# @param [String] str
|
|
91
|
+
# @return [Boolean]
|
|
92
|
+
def valid_constant?(str)
|
|
93
|
+
!!(str =~ /\A[A-Z][A-Za-z0-9]*(?:::[A-Z][A-Za-z0-9]*)*\z/)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Render plugin boilerplate for the given type and class name.
|
|
97
|
+
#
|
|
98
|
+
# @private
|
|
99
|
+
# @param [String] plugin_type 'tag' or 'collector'
|
|
100
|
+
# @param [String] class_name CamelCase plugin class name
|
|
101
|
+
# @return [String]
|
|
102
|
+
def render(plugin_type, class_name)
|
|
103
|
+
case plugin_type
|
|
104
|
+
when 'tag' then tag_template(class_name)
|
|
105
|
+
when 'collector' then collector_template(class_name)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Write the generated content to a file.
|
|
110
|
+
#
|
|
111
|
+
# @private
|
|
112
|
+
# @param [String] content
|
|
113
|
+
# @param [String] plugin_type
|
|
114
|
+
# @param [String] class_name
|
|
115
|
+
# @param [String] output_dir
|
|
116
|
+
# @return [Integer] exit code
|
|
117
|
+
def write_plugin(content, plugin_type:, class_name:, output_dir:)
|
|
118
|
+
filename = "#{underscore(class_name)}.rb"
|
|
119
|
+
path = File.join(output_dir, filename)
|
|
120
|
+
|
|
121
|
+
if File.exist?(path)
|
|
122
|
+
warn "Error: #{path} already exists. Remove it first or use --stdout."
|
|
123
|
+
return 1
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
require 'fileutils'
|
|
127
|
+
FileUtils.mkdir_p(output_dir)
|
|
128
|
+
File.write(path, content)
|
|
129
|
+
puts "Created: #{path}"
|
|
130
|
+
puts
|
|
131
|
+
puts next_steps(plugin_type, path)
|
|
132
|
+
0
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Print registration instructions after file creation.
|
|
136
|
+
#
|
|
137
|
+
# @private
|
|
138
|
+
# @param [String] plugin_type
|
|
139
|
+
# @param [String] path
|
|
140
|
+
# @return [String]
|
|
141
|
+
def next_steps(plugin_type, path)
|
|
142
|
+
base_name = File.basename(path, '.rb').split('_').map(&:capitalize).join
|
|
143
|
+
|
|
144
|
+
implement_hint = case plugin_type
|
|
145
|
+
when 'tag'
|
|
146
|
+
'Implement the #call method to return Array<Docscribe::Plugin::Tag>.'
|
|
147
|
+
when 'collector'
|
|
148
|
+
'Implement the #collect method to return Array<{anchor_node:, doc:}>.'
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
<<~TEXT
|
|
152
|
+
Next steps:
|
|
153
|
+
1. Open #{path} and implement the plugin logic.
|
|
154
|
+
#{implement_hint}
|
|
155
|
+
|
|
156
|
+
2. Register the plugin in your docscribe_plugins.rb:
|
|
157
|
+
|
|
158
|
+
require_relative '#{path.delete_suffix('.rb')}'
|
|
159
|
+
Docscribe::Plugin::Registry.register(#{base_name}.new)
|
|
160
|
+
|
|
161
|
+
3. Add the file to docscribe.yml:
|
|
162
|
+
|
|
163
|
+
plugins:
|
|
164
|
+
require:
|
|
165
|
+
- ./docscribe_plugins
|
|
166
|
+
TEXT
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Template for a TagPlugin.
|
|
170
|
+
#
|
|
171
|
+
# @private
|
|
172
|
+
# @param [String] class_name
|
|
173
|
+
# @return [String]
|
|
174
|
+
def tag_template(class_name)
|
|
175
|
+
<<~RUBY
|
|
176
|
+
# frozen_string_literal: true
|
|
177
|
+
|
|
178
|
+
require 'docscribe/plugin'
|
|
179
|
+
|
|
180
|
+
# #{class_name} — a Docscribe TagPlugin.
|
|
181
|
+
#
|
|
182
|
+
# TagPlugins hook into already-collected method insertions and append
|
|
183
|
+
# additional YARD tags to the generated doc block.
|
|
184
|
+
#
|
|
185
|
+
# The +#call+ method is invoked once per documented method. Return an
|
|
186
|
+
# empty array if this plugin has nothing to add for a particular method.
|
|
187
|
+
#
|
|
188
|
+
# @example Registration
|
|
189
|
+
# Docscribe::Plugin::Registry.register(#{class_name}.new)
|
|
190
|
+
class #{class_name} < Docscribe::Plugin::Base::TagPlugin
|
|
191
|
+
# Generate additional YARD tags for a documented method.
|
|
192
|
+
#
|
|
193
|
+
# Available context attributes:
|
|
194
|
+
# context.node # Parser::AST::Node — the :def or :defs node
|
|
195
|
+
# context.container # String — e.g. "MyModule::MyClass"
|
|
196
|
+
# context.scope # Symbol — :instance or :class
|
|
197
|
+
# context.visibility # Symbol — :public, :protected, or :private
|
|
198
|
+
# context.method_name # Symbol — method name
|
|
199
|
+
# context.inferred_params # Hash — { "name" => "InferredType" }
|
|
200
|
+
# context.inferred_return # String — inferred return type
|
|
201
|
+
# context.source # String — raw method source text
|
|
202
|
+
#
|
|
203
|
+
# @param [Docscribe::Plugin::Context] context method context snapshot
|
|
204
|
+
# @return [Array<Docscribe::Plugin::Tag>]
|
|
205
|
+
def call(context)
|
|
206
|
+
# TODO: implement plugin logic
|
|
207
|
+
#
|
|
208
|
+
# Examples:
|
|
209
|
+
#
|
|
210
|
+
# Simple text tag:
|
|
211
|
+
# Docscribe::Plugin::Tag.new(name: 'since', text: '1.0.0')
|
|
212
|
+
# # => # @since 1.0.0
|
|
213
|
+
#
|
|
214
|
+
# Tag with types:
|
|
215
|
+
# Docscribe::Plugin::Tag.new(name: 'raise', types: ['ArgumentError'], text: 'if invalid')
|
|
216
|
+
# # => # @raise [ArgumentError] if invalid
|
|
217
|
+
#
|
|
218
|
+
# Conditional tag:
|
|
219
|
+
# return [] unless context.visibility == :public
|
|
220
|
+
# [Docscribe::Plugin::Tag.new(name: 'api', text: 'public')]
|
|
221
|
+
[]
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
RUBY
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Template for a CollectorPlugin.
|
|
228
|
+
#
|
|
229
|
+
# @private
|
|
230
|
+
# @param [String] class_name
|
|
231
|
+
# @return [String]
|
|
232
|
+
def collector_template(class_name)
|
|
233
|
+
<<~RUBY
|
|
234
|
+
# frozen_string_literal: true
|
|
235
|
+
|
|
236
|
+
require 'docscribe/plugin'
|
|
237
|
+
require 'docscribe/infer/ast_walk'
|
|
238
|
+
|
|
239
|
+
# #{class_name} — a Docscribe CollectorPlugin.
|
|
240
|
+
#
|
|
241
|
+
# CollectorPlugins receive the raw AST and source buffer for each file.
|
|
242
|
+
# They walk the tree independently and return insertion targets that
|
|
243
|
+
# Docscribe will document according to the selected strategy.
|
|
244
|
+
#
|
|
245
|
+
# Idempotency is handled automatically:
|
|
246
|
+
# :safe — skips insertion if a comment already exists above anchor_node
|
|
247
|
+
# :aggressive — removes the existing comment and inserts a fresh block
|
|
248
|
+
#
|
|
249
|
+
# Use this plugin type for non-standard constructs that Docscribe's
|
|
250
|
+
# built-in Collector does not recognize (DSL macros, define_method, etc.).
|
|
251
|
+
# For ordinary +def+ methods use TagPlugin instead.
|
|
252
|
+
#
|
|
253
|
+
# @example Registration
|
|
254
|
+
# Docscribe::Plugin::Registry.register(#{class_name}.new)
|
|
255
|
+
class #{class_name} < Docscribe::Plugin::Base::CollectorPlugin
|
|
256
|
+
# Walk the AST and return documentation insertion targets.
|
|
257
|
+
#
|
|
258
|
+
# Each result must be a Hash with:
|
|
259
|
+
# :anchor_node — Parser::AST::Node above which to insert the doc block
|
|
260
|
+
# :doc — String with the complete doc block (newlines included)
|
|
261
|
+
#
|
|
262
|
+
# Indentation is applied automatically from anchor_node — do not
|
|
263
|
+
# prefix lines manually.
|
|
264
|
+
#
|
|
265
|
+
# @param [Parser::AST::Node] ast root AST node of the file
|
|
266
|
+
# @param [Parser::Source::Buffer] buffer source buffer
|
|
267
|
+
# @return [Array<Hash>]
|
|
268
|
+
def collect(ast, buffer)
|
|
269
|
+
results = []
|
|
270
|
+
|
|
271
|
+
Docscribe::Infer::ASTWalk.walk(ast) do |node|
|
|
272
|
+
# TODO: replace with your target node detection
|
|
273
|
+
#
|
|
274
|
+
# Example — match bare send calls like `my_dsl_macro :name`:
|
|
275
|
+
#
|
|
276
|
+
# next unless node.type == :send
|
|
277
|
+
# recv, meth, name_node, *_rest = *node
|
|
278
|
+
# next unless recv.nil? && meth == :my_dsl_macro
|
|
279
|
+
# next unless name_node&.type == :sym
|
|
280
|
+
#
|
|
281
|
+
# macro_name = name_node.children.first
|
|
282
|
+
#
|
|
283
|
+
# results << {
|
|
284
|
+
# anchor_node: node,
|
|
285
|
+
# doc: "# \#{macro_name} — generated doc\\n# @return [void]\\n"
|
|
286
|
+
# }
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
results
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
RUBY
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Convert CamelCase to snake_case for file naming.
|
|
296
|
+
#
|
|
297
|
+
# @private
|
|
298
|
+
# @param [String] str
|
|
299
|
+
# @return [String]
|
|
300
|
+
def underscore(str)
|
|
301
|
+
str
|
|
302
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
303
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
304
|
+
.downcase
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
@@ -19,7 +19,8 @@ module Docscribe
|
|
|
19
19
|
rbs: false,
|
|
20
20
|
sig_dirs: [],
|
|
21
21
|
sorbet: false,
|
|
22
|
-
rbi_dirs: []
|
|
22
|
+
rbi_dirs: [],
|
|
23
|
+
rbs_collection: false
|
|
23
24
|
}.freeze
|
|
24
25
|
|
|
25
26
|
module_function
|
|
@@ -65,6 +66,7 @@ module Docscribe
|
|
|
65
66
|
--sig-dir DIR Add an RBS signature directory (repeatable). Implies `--rbs`.
|
|
66
67
|
--sorbet Use Sorbet signatures from inline sigs / RBI files when available
|
|
67
68
|
--rbi-dir DIR Add a Sorbet RBI directory (repeatable). Implies --sorbet.
|
|
69
|
+
--rbs-collection Auto-discover RBS collection from rbs_collection.lock.yaml. Implies --rbs.
|
|
68
70
|
|
|
69
71
|
Filtering:
|
|
70
72
|
--include PATTERN Include PATTERN (method id or file path; glob or /regex/)
|
|
@@ -115,6 +117,11 @@ module Docscribe
|
|
|
115
117
|
options[:rbi_dirs] << v
|
|
116
118
|
end
|
|
117
119
|
|
|
120
|
+
opts.on('--rbs-collection', 'Auto-discover RBS collection from rbs_collection.lock.yaml. Implies --rbs.') do
|
|
121
|
+
options[:rbs] = true
|
|
122
|
+
options[:rbs_collection] = true
|
|
123
|
+
end
|
|
124
|
+
|
|
118
125
|
opts.on('--include PATTERN', 'Include PATTERN (method id or file path; glob or /regex/)') do |v|
|
|
119
126
|
route_include_exclude(options, :include, v)
|
|
120
127
|
end
|
data/lib/docscribe/cli/run.rb
CHANGED
|
@@ -34,6 +34,7 @@ module Docscribe
|
|
|
34
34
|
def run(options:, argv:)
|
|
35
35
|
conf = Docscribe::Config.load(options[:config])
|
|
36
36
|
conf = Docscribe::CLI::ConfigBuilder.build(conf, options)
|
|
37
|
+
conf.load_plugins!
|
|
37
38
|
|
|
38
39
|
return run_stdin(options: options, conf: conf) if options[:mode] == :stdin
|
|
39
40
|
|
|
@@ -61,6 +62,7 @@ module Docscribe
|
|
|
61
62
|
code,
|
|
62
63
|
strategy: options[:strategy],
|
|
63
64
|
config: conf,
|
|
65
|
+
core_rbs_provider: conf.respond_to?(:core_rbs_provider) ? conf.core_rbs_provider : nil,
|
|
64
66
|
file: '(stdin)'
|
|
65
67
|
)
|
|
66
68
|
puts result[:output]
|
|
@@ -132,10 +134,10 @@ module Docscribe
|
|
|
132
134
|
|
|
133
135
|
private
|
|
134
136
|
|
|
135
|
-
#
|
|
137
|
+
# Initialize the shared state hash used throughout a run.
|
|
136
138
|
#
|
|
137
139
|
# @private
|
|
138
|
-
# @return [Hash]
|
|
140
|
+
# @return [Hash] initial state with counters and tracking arrays
|
|
139
141
|
def initial_run_state
|
|
140
142
|
{
|
|
141
143
|
changed: false,
|
|
@@ -150,15 +152,15 @@ module Docscribe
|
|
|
150
152
|
}
|
|
151
153
|
end
|
|
152
154
|
|
|
153
|
-
#
|
|
155
|
+
# Process a single file: read, rewrite, and dispatch to check/write handler.
|
|
154
156
|
#
|
|
155
157
|
# @private
|
|
156
|
-
# @param [
|
|
157
|
-
# @param [Hash] options
|
|
158
|
-
# @param [
|
|
159
|
-
# @param [
|
|
160
|
-
# @param [
|
|
161
|
-
# @return [
|
|
158
|
+
# @param [String] path file path
|
|
159
|
+
# @param [Hash] options CLI options
|
|
160
|
+
# @param [Docscribe::Config] conf configuration
|
|
161
|
+
# @param [Pathname] pwd current working directory
|
|
162
|
+
# @param [Hash] state shared processing state
|
|
163
|
+
# @return [void]
|
|
162
164
|
def process_one_file(path, options:, conf:, pwd:, state:)
|
|
163
165
|
display_path = display_path_for(path, pwd: pwd)
|
|
164
166
|
|
|
@@ -217,16 +219,15 @@ module Docscribe
|
|
|
217
219
|
File.basename(path.to_s)
|
|
218
220
|
end
|
|
219
221
|
|
|
220
|
-
#
|
|
222
|
+
# Read the source file and handle read errors.
|
|
221
223
|
#
|
|
222
224
|
# @private
|
|
223
|
-
# @param [
|
|
224
|
-
# @param [
|
|
225
|
-
# @param [Hash] options
|
|
226
|
-
# @param [
|
|
225
|
+
# @param [String] path file path to read
|
|
226
|
+
# @param [String] display_path path shown in CLI output
|
|
227
|
+
# @param [Hash] options CLI options
|
|
228
|
+
# @param [Hash] state shared processing state
|
|
227
229
|
# @raise [StandardError]
|
|
228
|
-
# @return [
|
|
229
|
-
# @return [nil] if StandardError
|
|
230
|
+
# @return [String, nil] file contents or nil on error
|
|
230
231
|
def read_source_for_path(path, display_path:, options:, state:)
|
|
231
232
|
File.read(path)
|
|
232
233
|
rescue StandardError => e
|
|
@@ -237,23 +238,24 @@ module Docscribe
|
|
|
237
238
|
nil
|
|
238
239
|
end
|
|
239
240
|
|
|
240
|
-
#
|
|
241
|
+
# Rewrite the source file using InlineRewriter and handle rewrite errors.
|
|
241
242
|
#
|
|
242
243
|
# @private
|
|
243
|
-
# @param [
|
|
244
|
-
# @param [
|
|
245
|
-
# @param [
|
|
246
|
-
# @param [
|
|
247
|
-
# @param [Hash] options
|
|
248
|
-
# @param [
|
|
244
|
+
# @param [String] path file path
|
|
245
|
+
# @param [String] src source code
|
|
246
|
+
# @param [Docscribe::Config] conf configuration
|
|
247
|
+
# @param [String] display_path path shown in CLI output
|
|
248
|
+
# @param [Hash] options CLI options
|
|
249
|
+
# @param [Hash] state shared processing state
|
|
249
250
|
# @raise [StandardError]
|
|
250
|
-
# @return [
|
|
251
|
-
# @return [nil] if StandardError
|
|
251
|
+
# @return [Hash, nil] rewrite result or nil on error
|
|
252
252
|
def rewrite_result_for_path(path, src:, conf:, display_path:, options:, state:)
|
|
253
|
+
core_rbs_provider = conf.respond_to?(:core_rbs_provider) ? conf.core_rbs_provider : nil
|
|
253
254
|
Docscribe::InlineRewriter.rewrite_with_report(
|
|
254
255
|
src,
|
|
255
256
|
strategy: options[:strategy],
|
|
256
257
|
config: conf,
|
|
258
|
+
core_rbs_provider: core_rbs_provider,
|
|
257
259
|
file: path
|
|
258
260
|
)
|
|
259
261
|
rescue StandardError => e
|
|
@@ -264,17 +266,17 @@ module Docscribe
|
|
|
264
266
|
nil
|
|
265
267
|
end
|
|
266
268
|
|
|
267
|
-
#
|
|
269
|
+
# Handle the result of an inspect (check) run.
|
|
268
270
|
#
|
|
269
271
|
# @private
|
|
270
|
-
# @param [
|
|
271
|
-
# @param [
|
|
272
|
-
# @param [
|
|
273
|
-
# @param [
|
|
274
|
-
# @param [
|
|
275
|
-
# @param [Hash] options
|
|
276
|
-
# @param [
|
|
277
|
-
# @return [
|
|
272
|
+
# @param [String] path file path
|
|
273
|
+
# @param [String] src original source code
|
|
274
|
+
# @param [String] out rewritten source code
|
|
275
|
+
# @param [Array<Hash>] file_changes structured change records
|
|
276
|
+
# @param [String] display_path path shown in CLI output
|
|
277
|
+
# @param [Hash] options CLI options
|
|
278
|
+
# @param [Hash] state shared processing state
|
|
279
|
+
# @return [void]
|
|
278
280
|
def handle_check_result(path, src:, out:, file_changes:, display_path:, options:, state:)
|
|
279
281
|
if out == src
|
|
280
282
|
options[:verbose] ? puts("OK #{display_path}") : print('.')
|
|
@@ -299,19 +301,18 @@ module Docscribe
|
|
|
299
301
|
state[:fail_changes][path] = file_changes
|
|
300
302
|
end
|
|
301
303
|
|
|
302
|
-
#
|
|
304
|
+
# Handle the result of an autocorrect (write) run.
|
|
303
305
|
#
|
|
304
306
|
# @private
|
|
305
|
-
# @param [
|
|
306
|
-
# @param [
|
|
307
|
-
# @param [
|
|
308
|
-
# @param [
|
|
309
|
-
# @param [
|
|
310
|
-
# @param [Hash] options
|
|
311
|
-
# @param [
|
|
307
|
+
# @param [String] path file path
|
|
308
|
+
# @param [String] src original source code
|
|
309
|
+
# @param [String] out rewritten source code
|
|
310
|
+
# @param [Array<Hash>] file_changes structured change records
|
|
311
|
+
# @param [String] display_path path shown in CLI output
|
|
312
|
+
# @param [Hash] options CLI options
|
|
313
|
+
# @param [Hash] state shared processing state
|
|
312
314
|
# @raise [StandardError]
|
|
313
|
-
# @return [
|
|
314
|
-
# @return [Object] if StandardError
|
|
315
|
+
# @return [void]
|
|
315
316
|
def handle_write_result(path, src:, out:, file_changes:, display_path:, options:, state:)
|
|
316
317
|
if out == src
|
|
317
318
|
options[:verbose] ? puts("OK #{display_path}") : print('.')
|
|
@@ -339,12 +340,12 @@ module Docscribe
|
|
|
339
340
|
options[:verbose] ? warn("ERR #{display_path}: #{state[:error_messages][path]}") : print('E')
|
|
340
341
|
end
|
|
341
342
|
|
|
342
|
-
#
|
|
343
|
+
# Print the check-mode summary (files OK / need updates / errors).
|
|
343
344
|
#
|
|
344
345
|
# @private
|
|
345
|
-
# @param [
|
|
346
|
-
# @param [Hash] options
|
|
347
|
-
# @return [
|
|
346
|
+
# @param [Hash] state shared processing state
|
|
347
|
+
# @param [Hash] options CLI options
|
|
348
|
+
# @return [void]
|
|
348
349
|
def print_check_summary(state:, options:)
|
|
349
350
|
puts
|
|
350
351
|
|
|
@@ -392,11 +393,11 @@ module Docscribe
|
|
|
392
393
|
end
|
|
393
394
|
end
|
|
394
395
|
|
|
395
|
-
#
|
|
396
|
+
# Print the write-mode summary (files corrected, errors).
|
|
396
397
|
#
|
|
397
398
|
# @private
|
|
398
|
-
# @param [
|
|
399
|
-
# @return [
|
|
399
|
+
# @param [Hash] state shared processing state
|
|
400
|
+
# @return [void]
|
|
400
401
|
def print_write_summary(state:)
|
|
401
402
|
puts
|
|
402
403
|
puts "Docscribe: updated #{state[:corrected]} file(s)" if state[:corrected].positive?
|
data/lib/docscribe/cli.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'docscribe/cli/init'
|
|
4
|
+
require 'docscribe/cli/generate'
|
|
4
5
|
require 'docscribe/cli/options'
|
|
5
6
|
require 'docscribe/cli/run'
|
|
6
7
|
|
|
@@ -10,7 +11,8 @@ module Docscribe
|
|
|
10
11
|
# Main CLI entry point.
|
|
11
12
|
#
|
|
12
13
|
# Dispatches:
|
|
13
|
-
# - `docscribe init ...`
|
|
14
|
+
# - `docscribe init ...` to the config-template generator
|
|
15
|
+
# - `docscribe generate ...` to the plugin skeleton generator
|
|
14
16
|
# - all other commands to the main option parser and runner
|
|
15
17
|
#
|
|
16
18
|
# @param [Array<String>] argv raw command-line arguments
|
|
@@ -18,9 +20,13 @@ module Docscribe
|
|
|
18
20
|
def run(argv)
|
|
19
21
|
argv = argv.dup
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
case argv.first
|
|
24
|
+
when 'init'
|
|
22
25
|
argv.shift
|
|
23
26
|
return Docscribe::CLI::Init.run(argv)
|
|
27
|
+
when 'generate'
|
|
28
|
+
argv.shift
|
|
29
|
+
return Docscribe::CLI::Generate.run(argv)
|
|
24
30
|
end
|
|
25
31
|
|
|
26
32
|
options = Docscribe::CLI::Options.parse!(argv)
|
|
@@ -14,7 +14,9 @@ module Docscribe
|
|
|
14
14
|
# - optional Sorbet integration
|
|
15
15
|
DEFAULT = {
|
|
16
16
|
'emit' => {
|
|
17
|
-
'header' =>
|
|
17
|
+
'header' => false,
|
|
18
|
+
'include_default_message' => true,
|
|
19
|
+
'include_param_documentation' => true,
|
|
18
20
|
'param_tags' => true,
|
|
19
21
|
'return_tag' => true,
|
|
20
22
|
'visibility_tags' => true,
|
|
@@ -53,11 +55,12 @@ module Docscribe
|
|
|
53
55
|
'exclude' => [],
|
|
54
56
|
'files' => {
|
|
55
57
|
'include' => [],
|
|
56
|
-
'exclude' => []
|
|
58
|
+
'exclude' => ['spec']
|
|
57
59
|
}
|
|
58
60
|
},
|
|
59
61
|
'rbs' => {
|
|
60
62
|
'enabled' => false,
|
|
63
|
+
'collection' => false,
|
|
61
64
|
'sig_dirs' => ['sig'],
|
|
62
65
|
'collapse_generics' => false
|
|
63
66
|
},
|
|
@@ -65,6 +68,9 @@ module Docscribe
|
|
|
65
68
|
'enabled' => false,
|
|
66
69
|
'rbi_dirs' => ['sorbet/rbi', 'rbi'],
|
|
67
70
|
'collapse_generics' => false
|
|
71
|
+
},
|
|
72
|
+
'plugins' => {
|
|
73
|
+
'require' => []
|
|
68
74
|
}
|
|
69
75
|
}.freeze
|
|
70
76
|
end
|
|
@@ -16,7 +16,7 @@ module Docscribe
|
|
|
16
16
|
exclude_patterns = normalize_file_patterns(files['exclude'])
|
|
17
17
|
|
|
18
18
|
rel = begin
|
|
19
|
-
Pathname.new(path).relative_path_from(Pathname.pwd).to_s
|
|
19
|
+
Pathname.new(path).expand_path.relative_path_from(Pathname.pwd).cleanpath.to_s
|
|
20
20
|
rescue StandardError
|
|
21
21
|
path
|
|
22
22
|
end
|
|
@@ -121,7 +121,7 @@ module Docscribe
|
|
|
121
121
|
patterns_to_try << pattern.gsub('/**/', '/') if pattern.include?('/**/')
|
|
122
122
|
|
|
123
123
|
patterns_to_try.any? do |pat|
|
|
124
|
-
File.fnmatch?(pat, path, File::FNM_EXTGLOB | File::FNM_PATHNAME)
|
|
124
|
+
File.fnmatch?(pat, path, File::FNM_EXTGLOB | File::FNM_DOTMATCH | File::FNM_PATHNAME)
|
|
125
125
|
end
|
|
126
126
|
end
|
|
127
127
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docscribe
|
|
4
|
+
class Config
|
|
5
|
+
# Load and register plugins declared under `plugins.require` in config.
|
|
6
|
+
#
|
|
7
|
+
# Each entry is expanded relative to the current working directory and
|
|
8
|
+
# passed to `require`. Registration is expected to happen inside the
|
|
9
|
+
# required file via {Docscribe::Plugin::Registry.register}.
|
|
10
|
+
#
|
|
11
|
+
# Loading failures are non-fatal: a warning is printed and the run
|
|
12
|
+
# continues without the plugin.
|
|
13
|
+
#
|
|
14
|
+
# @raise [LoadError]
|
|
15
|
+
# @return [void]
|
|
16
|
+
def load_plugins!
|
|
17
|
+
paths = Array(raw.dig('plugins', 'require')).compact
|
|
18
|
+
return if paths.empty?
|
|
19
|
+
|
|
20
|
+
require 'docscribe/plugin'
|
|
21
|
+
|
|
22
|
+
paths.each do |path|
|
|
23
|
+
require File.expand_path(path)
|
|
24
|
+
rescue LoadError => e
|
|
25
|
+
warn "Docscribe: could not load plugin #{path.inspect}: #{e.message}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|