docscribe 1.4.1 → 1.5.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/README.md +588 -104
- data/lib/docscribe/cli/check_for_comments.rb +183 -0
- data/lib/docscribe/cli/config_builder.rb +180 -36
- data/lib/docscribe/cli/formatters/json.rb +294 -0
- data/lib/docscribe/cli/formatters/sarif.rb +235 -0
- data/lib/docscribe/cli/formatters/text.rb +208 -0
- data/lib/docscribe/cli/formatters.rb +26 -0
- data/lib/docscribe/cli/generate.rb +296 -125
- data/lib/docscribe/cli/init.rb +58 -14
- data/lib/docscribe/cli/options.rb +410 -133
- data/lib/docscribe/cli/rbs_gen.rb +529 -0
- data/lib/docscribe/cli/run.rb +503 -189
- data/lib/docscribe/cli/sigs.rb +366 -0
- data/lib/docscribe/cli/update_types.rb +103 -0
- data/lib/docscribe/cli.rb +35 -9
- data/lib/docscribe/config/defaults.rb +16 -12
- data/lib/docscribe/config/emit.rb +18 -0
- data/lib/docscribe/config/filtering.rb +37 -31
- data/lib/docscribe/config/loader.rb +20 -13
- data/lib/docscribe/config/plugin.rb +2 -1
- data/lib/docscribe/config/rbs.rb +68 -27
- data/lib/docscribe/config/sorbet.rb +40 -17
- data/lib/docscribe/config/sorting.rb +2 -1
- data/lib/docscribe/config/template.rb +10 -1
- data/lib/docscribe/config/utils.rb +12 -9
- data/lib/docscribe/config.rb +3 -4
- data/lib/docscribe/infer/ast_walk.rb +1 -1
- data/lib/docscribe/infer/constants.rb +15 -0
- data/lib/docscribe/infer/literals.rb +39 -26
- data/lib/docscribe/infer/names.rb +24 -16
- data/lib/docscribe/infer/params.rb +57 -13
- data/lib/docscribe/infer/raises.rb +23 -15
- data/lib/docscribe/infer/returns.rb +784 -199
- data/lib/docscribe/infer.rb +28 -28
- data/lib/docscribe/inline_rewriter/collector.rb +816 -430
- data/lib/docscribe/inline_rewriter/doc_block.rb +323 -150
- data/lib/docscribe/inline_rewriter/doc_builder.rb +1837 -648
- data/lib/docscribe/inline_rewriter/source_helpers.rb +119 -71
- data/lib/docscribe/inline_rewriter/tag_sorter.rb +165 -107
- data/lib/docscribe/inline_rewriter.rb +1144 -727
- data/lib/docscribe/parsing.rb +29 -10
- data/lib/docscribe/plugin/base/collector_plugin.rb +3 -3
- data/lib/docscribe/plugin/base/tag_plugin.rb +1 -2
- data/lib/docscribe/plugin/context.rb +28 -18
- data/lib/docscribe/plugin/registry.rb +49 -23
- data/lib/docscribe/plugin/tag.rb +9 -14
- data/lib/docscribe/plugin.rb +54 -22
- data/lib/docscribe/types/provider_chain.rb +4 -2
- data/lib/docscribe/types/rbs/collection_loader.rb +2 -3
- data/lib/docscribe/types/rbs/provider.rb +127 -62
- data/lib/docscribe/types/rbs/type_formatter.rb +286 -77
- data/lib/docscribe/types/signature.rb +22 -42
- data/lib/docscribe/types/sorbet/base_provider.rb +51 -27
- data/lib/docscribe/types/sorbet/rbi_provider.rb +3 -3
- data/lib/docscribe/types/sorbet/source_provider.rb +3 -2
- data/lib/docscribe/types/yard/formatter.rb +100 -0
- data/lib/docscribe/types/yard/parser.rb +240 -0
- data/lib/docscribe/types/yard/types.rb +52 -0
- data/lib/docscribe/version.rb +1 -1
- metadata +34 -2
|
@@ -14,91 +14,92 @@ module Docscribe
|
|
|
14
14
|
module Generate
|
|
15
15
|
PLUGIN_TYPES = %w[tag collector].freeze
|
|
16
16
|
|
|
17
|
+
NEXT_STEPS_TEMPLATE = <<~TEXT
|
|
18
|
+
Next steps:
|
|
19
|
+
1. Open %<path>s and implement the plugin logic.
|
|
20
|
+
%<hint>s
|
|
21
|
+
|
|
22
|
+
2. Register the plugin in your docscribe_plugins.rb:
|
|
23
|
+
|
|
24
|
+
require_relative '%<require_path>s'
|
|
25
|
+
Docscribe::Plugin::Registry.register(%<base_name>s.new)
|
|
26
|
+
|
|
27
|
+
3. Add the file to docscribe.yml:
|
|
28
|
+
|
|
29
|
+
plugins:
|
|
30
|
+
require:
|
|
31
|
+
- ./docscribe_plugins
|
|
32
|
+
TEXT
|
|
33
|
+
|
|
17
34
|
class << self
|
|
18
35
|
# Run the `generate` subcommand.
|
|
19
36
|
#
|
|
20
|
-
# @param [Array<String>] argv
|
|
21
|
-
# @raise [OptionParser::InvalidOption]
|
|
37
|
+
# @param [Array<String>] argv command line arguments
|
|
22
38
|
# @return [Integer] exit code
|
|
23
39
|
def run(argv)
|
|
24
|
-
opts =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
end
|
|
40
|
+
opts, parser = parse_generate_options(argv)
|
|
41
|
+
return 0 if opts[:help]
|
|
42
|
+
|
|
43
|
+
plugin_type, class_name = extract_generate_args(argv)
|
|
44
|
+
result = validate_generate_args(plugin_type, class_name, parser)
|
|
45
|
+
return result if result
|
|
46
|
+
|
|
47
|
+
content = render(plugin_type, class_name)
|
|
48
|
+
dispatch_output(content, plugin_type, class_name, opts)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# Parse options for the generate subcommand.
|
|
54
|
+
#
|
|
55
|
+
# @private
|
|
56
|
+
# @param [Array<String>] argv command line arguments
|
|
57
|
+
# @raise [OptionParser::InvalidOption]
|
|
58
|
+
# @return [(Hash<Symbol, Object>, OptionParser)]
|
|
59
|
+
def parse_generate_options(argv)
|
|
60
|
+
opts = { output: nil, stdout: false, help: false }
|
|
61
|
+
parser = build_option_parser(opts)
|
|
47
62
|
|
|
48
63
|
begin
|
|
49
64
|
parser.parse!(argv)
|
|
50
65
|
rescue OptionParser::InvalidOption => e
|
|
51
66
|
warn e.message
|
|
52
67
|
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
68
|
end
|
|
69
69
|
|
|
70
|
-
|
|
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] || '.')
|
|
70
|
+
[opts, parser]
|
|
83
71
|
end
|
|
84
72
|
|
|
85
|
-
|
|
73
|
+
# Extract plugin_type and class_name from remaining argv.
|
|
74
|
+
#
|
|
75
|
+
# @private
|
|
76
|
+
# @param [Array<String>] argv command line arguments
|
|
77
|
+
# @return [(String?, String?)]
|
|
78
|
+
def extract_generate_args(argv)
|
|
79
|
+
[argv.shift, argv.shift]
|
|
80
|
+
end
|
|
86
81
|
|
|
87
|
-
#
|
|
82
|
+
# Validate generate arguments and return exit code on failure.
|
|
88
83
|
#
|
|
89
84
|
# @private
|
|
90
|
-
# @param [String]
|
|
91
|
-
# @
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
# @param [String?] plugin_type 'tag' or 'collector'
|
|
86
|
+
# @param [String?] class_name CamelCase plugin class name
|
|
87
|
+
# @param [OptionParser] parser option parser instance
|
|
88
|
+
# @return [Integer, nil] exit code or nil if valid
|
|
89
|
+
def validate_generate_args(plugin_type, class_name, parser)
|
|
90
|
+
return 1 unless args_provided?(plugin_type, class_name, parser)
|
|
91
|
+
return 1 unless known_type?(plugin_type)
|
|
92
|
+
return 1 unless valid_name?(class_name)
|
|
93
|
+
|
|
94
|
+
nil
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
# Render plugin boilerplate for the given type and class name.
|
|
97
98
|
#
|
|
98
99
|
# @private
|
|
99
|
-
# @param [String] plugin_type 'tag' or 'collector'
|
|
100
|
-
# @param [String] class_name
|
|
101
|
-
# @return [String]
|
|
100
|
+
# @param [String?] plugin_type 'tag' or 'collector'
|
|
101
|
+
# @param [String?] class_name CamelCase plugin class name
|
|
102
|
+
# @return [String?]
|
|
102
103
|
def render(plugin_type, class_name)
|
|
103
104
|
case plugin_type
|
|
104
105
|
when 'tag' then tag_template(class_name)
|
|
@@ -106,70 +107,10 @@ module Docscribe
|
|
|
106
107
|
end
|
|
107
108
|
end
|
|
108
109
|
|
|
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
110
|
# Template for a TagPlugin.
|
|
170
111
|
#
|
|
171
112
|
# @private
|
|
172
|
-
# @param [String] class_name
|
|
113
|
+
# @param [String?] class_name CamelCase plugin class name
|
|
173
114
|
# @return [String]
|
|
174
115
|
def tag_template(class_name)
|
|
175
116
|
<<~RUBY
|
|
@@ -227,7 +168,7 @@ module Docscribe
|
|
|
227
168
|
# Template for a CollectorPlugin.
|
|
228
169
|
#
|
|
229
170
|
# @private
|
|
230
|
-
# @param [String] class_name
|
|
171
|
+
# @param [String?] class_name CamelCase plugin class name
|
|
231
172
|
# @return [String]
|
|
232
173
|
def collector_template(class_name)
|
|
233
174
|
<<~RUBY
|
|
@@ -292,10 +233,166 @@ module Docscribe
|
|
|
292
233
|
RUBY
|
|
293
234
|
end
|
|
294
235
|
|
|
236
|
+
# Write generated plugin to a file or print to STDOUT based on options.
|
|
237
|
+
#
|
|
238
|
+
# @private
|
|
239
|
+
# @param [String?] content generated plugin source code
|
|
240
|
+
# @param [String?] plugin_type 'tag' or 'collector'
|
|
241
|
+
# @param [String?] class_name CamelCase plugin class name
|
|
242
|
+
# @param [Hash<Symbol, Object>] opts parsed options hash
|
|
243
|
+
# @return [Integer] exit code
|
|
244
|
+
def dispatch_output(content, plugin_type, class_name, opts)
|
|
245
|
+
if opts[:stdout]
|
|
246
|
+
puts content
|
|
247
|
+
return 0
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
write_plugin(content, plugin_type: plugin_type, class_name: class_name, output_dir: opts[:output] || '.')
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Write the generated content to a file.
|
|
254
|
+
#
|
|
255
|
+
# @private
|
|
256
|
+
# @param [String?] content file content to write
|
|
257
|
+
# @param [String?] plugin_type 'tag' or 'collector'
|
|
258
|
+
# @param [String?] class_name CamelCase plugin class name
|
|
259
|
+
# @param [String?] output_dir output directory path
|
|
260
|
+
# @return [Integer] exit code
|
|
261
|
+
def write_plugin(content, plugin_type:, class_name:, output_dir:)
|
|
262
|
+
path = plugin_path(class_name, output_dir)
|
|
263
|
+
|
|
264
|
+
return 1 if file_exists?(path)
|
|
265
|
+
|
|
266
|
+
write_to_file(output_dir, path, content)
|
|
267
|
+
print_created(plugin_type, path)
|
|
268
|
+
0
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Build the OptionParser for the generate subcommand.
|
|
272
|
+
#
|
|
273
|
+
# @private
|
|
274
|
+
# @param [Hash<Symbol, Object>] opts mutable parsed options hash
|
|
275
|
+
# @return [OptionParser]
|
|
276
|
+
def build_option_parser(opts)
|
|
277
|
+
OptionParser.new do |opt|
|
|
278
|
+
opt.banner = parser_banner
|
|
279
|
+
register_output_option(opt, opts)
|
|
280
|
+
register_stdout_option(opt, opts)
|
|
281
|
+
register_help_option(opt, opts)
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Return the usage banner for the generate subcommand parser.
|
|
286
|
+
#
|
|
287
|
+
# @private
|
|
288
|
+
# @return [String]
|
|
289
|
+
def parser_banner
|
|
290
|
+
<<~TEXT
|
|
291
|
+
Usage: docscribe generate <type> <PluginName> [options]
|
|
292
|
+
|
|
293
|
+
Types:
|
|
294
|
+
tag Generate a TagPlugin skeleton
|
|
295
|
+
collector Generate a CollectorPlugin skeleton
|
|
296
|
+
|
|
297
|
+
Options:
|
|
298
|
+
TEXT
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Register the --output option on the OptionParser.
|
|
302
|
+
#
|
|
303
|
+
# @private
|
|
304
|
+
# @param [OptionParser] opt option parser instance
|
|
305
|
+
# @param [Hash<Symbol, Object>] opts mutable parsed options hash
|
|
306
|
+
# @return [void]
|
|
307
|
+
def register_output_option(opt, opts)
|
|
308
|
+
opt.on('--output DIR', 'Directory to write the plugin file (default: .)') { |v| opts[:output] = v }
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Register the --stdout option on the OptionParser.
|
|
312
|
+
#
|
|
313
|
+
# @private
|
|
314
|
+
# @param [OptionParser] opt option parser instance
|
|
315
|
+
# @param [Hash<Symbol, Object>] opts mutable parsed options hash
|
|
316
|
+
# @return [void]
|
|
317
|
+
def register_stdout_option(opt, opts)
|
|
318
|
+
opt.on('--stdout', 'Print the generated plugin to STDOUT instead of writing a file') { opts[:stdout] = true }
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Register the -h/--help option on the OptionParser.
|
|
322
|
+
#
|
|
323
|
+
# @private
|
|
324
|
+
# @param [OptionParser] opt option parser instance
|
|
325
|
+
# @param [Hash<Symbol, Object>] opts mutable parsed options hash
|
|
326
|
+
# @return [void]
|
|
327
|
+
def register_help_option(opt, opts)
|
|
328
|
+
opt.on('-h', '--help', 'Show this help') do
|
|
329
|
+
puts opt
|
|
330
|
+
opts[:help] = true
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Validate that both plugin_type and class_name arguments were provided.
|
|
335
|
+
#
|
|
336
|
+
# @private
|
|
337
|
+
# @param [String?] plugin_type plugin type argument
|
|
338
|
+
# @param [String?] class_name plugin class name argument
|
|
339
|
+
# @param [OptionParser] parser option parser instance
|
|
340
|
+
# @return [Boolean]
|
|
341
|
+
def args_provided?(plugin_type, class_name, parser)
|
|
342
|
+
return true if plugin_type && class_name
|
|
343
|
+
|
|
344
|
+
warn 'Error: both <type> and <PluginName> are required.'
|
|
345
|
+
warn parser
|
|
346
|
+
false
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Validate that the plugin type is one of the recognized types.
|
|
350
|
+
#
|
|
351
|
+
# @private
|
|
352
|
+
# @param [String?] plugin_type plugin type to validate
|
|
353
|
+
# @return [Boolean]
|
|
354
|
+
def known_type?(plugin_type)
|
|
355
|
+
return true if PLUGIN_TYPES.include?(plugin_type)
|
|
356
|
+
|
|
357
|
+
warn "Error: unknown type #{plugin_type.inspect}. Must be one of: #{PLUGIN_TYPES.join(', ')}."
|
|
358
|
+
false
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Validate that the class name is a valid Ruby constant name.
|
|
362
|
+
#
|
|
363
|
+
# @private
|
|
364
|
+
# @param [String?] class_name class name to validate
|
|
365
|
+
# @return [Boolean]
|
|
366
|
+
def valid_name?(class_name)
|
|
367
|
+
return true if valid_constant?(class_name)
|
|
368
|
+
|
|
369
|
+
warn "Error: #{class_name.inspect} is not a valid Ruby constant name."
|
|
370
|
+
false
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Check whether a string is a valid Ruby constant name.
|
|
374
|
+
#
|
|
375
|
+
# @private
|
|
376
|
+
# @param [String?] str string constant to validate
|
|
377
|
+
# @return [Boolean]
|
|
378
|
+
def valid_constant?(str)
|
|
379
|
+
!!(str =~ /\A[A-Z][A-Za-z0-9]*(?:::[A-Z][A-Za-z0-9]*)*\z/)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# Build the file path for the generated plugin.
|
|
383
|
+
#
|
|
384
|
+
# @private
|
|
385
|
+
# @param [String?] class_name CamelCase plugin class name
|
|
386
|
+
# @param [String?] output_dir output directory
|
|
387
|
+
# @return [String] full file path
|
|
388
|
+
def plugin_path(class_name, output_dir)
|
|
389
|
+
File.join(output_dir || '.', "#{underscore(class_name || '')}.rb")
|
|
390
|
+
end
|
|
391
|
+
|
|
295
392
|
# Convert CamelCase to snake_case for file naming.
|
|
296
393
|
#
|
|
297
394
|
# @private
|
|
298
|
-
# @param [String] str
|
|
395
|
+
# @param [String] str CamelCase string to convert
|
|
299
396
|
# @return [String]
|
|
300
397
|
def underscore(str)
|
|
301
398
|
str
|
|
@@ -303,6 +400,80 @@ module Docscribe
|
|
|
303
400
|
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
304
401
|
.downcase
|
|
305
402
|
end
|
|
403
|
+
|
|
404
|
+
# Check whether the target plugin file already exists and warn if so.
|
|
405
|
+
#
|
|
406
|
+
# @private
|
|
407
|
+
# @param [String] path file path to check
|
|
408
|
+
# @return [Boolean]
|
|
409
|
+
def file_exists?(path)
|
|
410
|
+
return false unless File.exist?(path)
|
|
411
|
+
|
|
412
|
+
warn "Error: #{path} already exists. Remove it first or use --stdout."
|
|
413
|
+
true
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Create the output directory and write the plugin file.
|
|
417
|
+
#
|
|
418
|
+
# @private
|
|
419
|
+
# @param [String?] output_dir output directory path
|
|
420
|
+
# @param [String] path full plugin file path
|
|
421
|
+
# @param [String?] content file content to write
|
|
422
|
+
# @return [void]
|
|
423
|
+
def write_to_file(output_dir, path, content)
|
|
424
|
+
require 'fileutils'
|
|
425
|
+
FileUtils.mkdir_p(output_dir || '.')
|
|
426
|
+
File.write(path, content)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Print the creation message and next steps after generating a plugin.
|
|
430
|
+
#
|
|
431
|
+
# @private
|
|
432
|
+
# @param [String?] plugin_type 'tag' or 'collector'
|
|
433
|
+
# @param [String] path file path of the created plugin
|
|
434
|
+
# @return [void]
|
|
435
|
+
def print_created(plugin_type, path)
|
|
436
|
+
puts "Created: #{path}"
|
|
437
|
+
puts
|
|
438
|
+
puts next_steps(plugin_type, path)
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Print registration instructions after file creation.
|
|
442
|
+
#
|
|
443
|
+
# @private
|
|
444
|
+
# @param [String?] plugin_type 'tag' or 'collector'
|
|
445
|
+
# @param [String] path file path
|
|
446
|
+
# @return [String]
|
|
447
|
+
def next_steps(plugin_type, path)
|
|
448
|
+
format(NEXT_STEPS_TEMPLATE,
|
|
449
|
+
path: path,
|
|
450
|
+
hint: generate_implement_hint(plugin_type),
|
|
451
|
+
require_path: path.delete_suffix('.rb'),
|
|
452
|
+
base_name: plugin_base_name(path))
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Derive a CamelCase base name from a snake_case file path.
|
|
456
|
+
#
|
|
457
|
+
# @private
|
|
458
|
+
# @param [String] path file path
|
|
459
|
+
# @return [String] CamelCase class name
|
|
460
|
+
def plugin_base_name(path)
|
|
461
|
+
File.basename(path, '.rb').split('_').map(&:capitalize).join
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Generate an implementation hint string for the given plugin type.
|
|
465
|
+
#
|
|
466
|
+
# @private
|
|
467
|
+
# @param [String?] plugin_type 'tag' or 'collector'
|
|
468
|
+
# @return [String, nil] hint text
|
|
469
|
+
def generate_implement_hint(plugin_type)
|
|
470
|
+
case plugin_type
|
|
471
|
+
when 'tag'
|
|
472
|
+
'Implement the #call method to return Array<Docscribe::Plugin::Tag>.'
|
|
473
|
+
when 'collector'
|
|
474
|
+
'Implement the #collect method to return Array<{anchor_node:, doc:}>.'
|
|
475
|
+
end
|
|
476
|
+
end
|
|
306
477
|
end
|
|
307
478
|
end
|
|
308
479
|
end
|
data/lib/docscribe/cli/init.rb
CHANGED
|
@@ -5,7 +5,16 @@ require 'docscribe/config'
|
|
|
5
5
|
|
|
6
6
|
module Docscribe
|
|
7
7
|
module CLI
|
|
8
|
+
# Generate starter Docscribe configuration.
|
|
8
9
|
module Init
|
|
10
|
+
BANNER = <<~TEXT
|
|
11
|
+
Usage: docscribe init [options]
|
|
12
|
+
|
|
13
|
+
Generate a starter docscribe.yml configuration file.
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
TEXT
|
|
17
|
+
|
|
9
18
|
class << self
|
|
10
19
|
# Create or print a starter Docscribe configuration file.
|
|
11
20
|
#
|
|
@@ -18,30 +27,65 @@ module Docscribe
|
|
|
18
27
|
# @param [Array<String>] argv command-line arguments for `docscribe init`
|
|
19
28
|
# @return [Integer] process exit code
|
|
20
29
|
def run(argv)
|
|
21
|
-
opts =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
30
|
+
opts = parse_init_options(argv)
|
|
31
|
+
return 0 if opts[:help]
|
|
32
|
+
|
|
33
|
+
yaml = Docscribe::Config.default_yaml
|
|
26
34
|
|
|
35
|
+
if opts[:stdout]
|
|
36
|
+
puts yaml
|
|
37
|
+
return 0
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
write_init_config(opts, yaml)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Parse CLI options for `docscribe init`.
|
|
46
|
+
#
|
|
47
|
+
# @private
|
|
48
|
+
# @param [Array<String>] argv command-line arguments for `docscribe init`
|
|
49
|
+
# @return [Hash<Symbol, Object>] parsed options
|
|
50
|
+
def parse_init_options(argv)
|
|
51
|
+
opts = default_init_options
|
|
52
|
+
build_init_parser(opts).parse!(argv)
|
|
53
|
+
opts
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Return the default options hash for the init command.
|
|
57
|
+
#
|
|
58
|
+
# @private
|
|
59
|
+
# @return [Hash<Symbol, String, Boolean>]
|
|
60
|
+
def default_init_options
|
|
61
|
+
{ config: 'docscribe.yml', force: false, stdout: false, help: false }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Build and return an OptionParser for the init command.
|
|
65
|
+
#
|
|
66
|
+
# @private
|
|
67
|
+
# @param [Hash<Symbol, Object>] opts options hash that the parser populates
|
|
68
|
+
# @return [OptionParser]
|
|
69
|
+
def build_init_parser(opts)
|
|
27
70
|
OptionParser.new do |o|
|
|
28
|
-
o.banner =
|
|
71
|
+
o.banner = BANNER
|
|
29
72
|
o.on('--config PATH', 'Where to write the config (default: docscribe.yml)') { |v| opts[:config] = v }
|
|
30
73
|
o.on('-f', '--force', 'Overwrite if the file already exists') { opts[:force] = true }
|
|
31
74
|
o.on('--stdout', 'Print config template to STDOUT instead of writing a file') { opts[:stdout] = true }
|
|
32
75
|
o.on('-h', '--help', 'Show this help') do
|
|
76
|
+
opts[:help] = true
|
|
33
77
|
puts o
|
|
34
|
-
return 0
|
|
35
78
|
end
|
|
36
|
-
end.parse!(argv)
|
|
37
|
-
|
|
38
|
-
yaml = Docscribe::Config.default_yaml
|
|
39
|
-
|
|
40
|
-
if opts[:stdout]
|
|
41
|
-
puts yaml
|
|
42
|
-
return 0
|
|
43
79
|
end
|
|
80
|
+
end
|
|
44
81
|
|
|
82
|
+
# Write the config template to a file.
|
|
83
|
+
#
|
|
84
|
+
# @private
|
|
85
|
+
# @param [Hash<Symbol, Object>] opts parsed options
|
|
86
|
+
# @param [String] yaml config template content
|
|
87
|
+
# @return [Integer] exit code
|
|
88
|
+
def write_init_config(opts, yaml)
|
|
45
89
|
path = opts[:config]
|
|
46
90
|
if File.exist?(path) && !opts[:force]
|
|
47
91
|
warn "Config already exists: #{path} (use --force to overwrite)"
|