docscribe 1.4.2 → 1.5.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +601 -139
  3. data/exe/docscribe-client +105 -0
  4. data/lib/docscribe/cli/check_for_comments.rb +183 -0
  5. data/lib/docscribe/cli/config_builder.rb +107 -53
  6. data/lib/docscribe/cli/formatters/json.rb +294 -0
  7. data/lib/docscribe/cli/formatters/sarif.rb +235 -0
  8. data/lib/docscribe/cli/formatters/text.rb +208 -0
  9. data/lib/docscribe/cli/formatters.rb +26 -0
  10. data/lib/docscribe/cli/generate.rb +56 -62
  11. data/lib/docscribe/cli/init.rb +14 -6
  12. data/lib/docscribe/cli/options.rb +206 -89
  13. data/lib/docscribe/cli/rbs_gen.rb +529 -0
  14. data/lib/docscribe/cli/run.rb +433 -154
  15. data/lib/docscribe/cli/server.rb +135 -0
  16. data/lib/docscribe/cli/sigs.rb +366 -0
  17. data/lib/docscribe/cli/update_types.rb +103 -0
  18. data/lib/docscribe/cli.rb +21 -24
  19. data/lib/docscribe/config/defaults.rb +7 -2
  20. data/lib/docscribe/config/emit.rb +17 -0
  21. data/lib/docscribe/config/filtering.rb +17 -24
  22. data/lib/docscribe/config/loader.rb +19 -17
  23. data/lib/docscribe/config/plugin.rb +1 -1
  24. data/lib/docscribe/config/rbs.rb +39 -7
  25. data/lib/docscribe/config/sorbet.rb +22 -16
  26. data/lib/docscribe/config/sorting.rb +1 -1
  27. data/lib/docscribe/config/template.rb +10 -1
  28. data/lib/docscribe/config/utils.rb +11 -9
  29. data/lib/docscribe/config.rb +10 -6
  30. data/lib/docscribe/infer/ast_walk.rb +1 -1
  31. data/lib/docscribe/infer/literals.rb +6 -11
  32. data/lib/docscribe/infer/names.rb +2 -3
  33. data/lib/docscribe/infer/params.rb +14 -16
  34. data/lib/docscribe/infer/raises.rb +3 -5
  35. data/lib/docscribe/infer/returns.rb +615 -151
  36. data/lib/docscribe/infer.rb +29 -26
  37. data/lib/docscribe/inline_rewriter/collector.rb +159 -164
  38. data/lib/docscribe/inline_rewriter/doc_block.rb +145 -115
  39. data/lib/docscribe/inline_rewriter/doc_builder.rb +1032 -723
  40. data/lib/docscribe/inline_rewriter/source_helpers.rb +48 -48
  41. data/lib/docscribe/inline_rewriter/tag_sorter.rb +82 -85
  42. data/lib/docscribe/inline_rewriter.rb +485 -488
  43. data/lib/docscribe/lru_cache.rb +49 -0
  44. data/lib/docscribe/parsing.rb +28 -9
  45. data/lib/docscribe/plugin/base/collector_plugin.rb +2 -1
  46. data/lib/docscribe/plugin/base/tag_plugin.rb +0 -1
  47. data/lib/docscribe/plugin/context.rb +28 -18
  48. data/lib/docscribe/plugin/registry.rb +25 -26
  49. data/lib/docscribe/plugin/tag.rb +9 -14
  50. data/lib/docscribe/plugin.rb +17 -16
  51. data/lib/docscribe/server.rb +608 -0
  52. data/lib/docscribe/types/provider_chain.rb +4 -2
  53. data/lib/docscribe/types/rbs/collection_loader.rb +2 -2
  54. data/lib/docscribe/types/rbs/provider.rb +177 -51
  55. data/lib/docscribe/types/rbs/type_formatter.rb +224 -83
  56. data/lib/docscribe/types/signature.rb +22 -42
  57. data/lib/docscribe/types/sorbet/base_provider.rb +29 -21
  58. data/lib/docscribe/types/sorbet/rbi_provider.rb +6 -5
  59. data/lib/docscribe/types/sorbet/source_provider.rb +6 -4
  60. data/lib/docscribe/types/yard/formatter.rb +100 -0
  61. data/lib/docscribe/types/yard/parser.rb +240 -0
  62. data/lib/docscribe/types/yard/types.rb +52 -0
  63. data/lib/docscribe/version.rb +1 -1
  64. metadata +38 -1
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docscribe
4
+ module CLI
5
+ module Formatters
6
+ # Text output formatter preserving the original CLI output format.
7
+ class Text
8
+ # Format and print check summary.
9
+ #
10
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
11
+ # @param [Docscribe::CLI::Formatters::opts] options runtime options hash
12
+ # @return [void]
13
+ def format_check_summary(state:, options:)
14
+ puts
15
+ format_fail_paths(state, options)
16
+ format_check_status_line(state)
17
+ format_type_mismatch_paths(state, options)
18
+ format_error_paths(state)
19
+ end
20
+
21
+ # Format and print write summary.
22
+ #
23
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
24
+ # @param [Docscribe::CLI::Formatters::opts] options runtime options hash
25
+ # @return [void]
26
+ def format_write_summary(state:, options:)
27
+ puts
28
+ puts "Docscribe: updated #{state[:corrected]} file(s)" if state[:corrected].positive?
29
+ format_corrected_paths(state, options)
30
+
31
+ return unless state[:had_errors]
32
+
33
+ warn "Docscribe: #{state[:error_paths].size} file(s) had errors"
34
+ format_error_paths(state)
35
+ end
36
+
37
+ # Print files needing updates.
38
+ #
39
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
40
+ # @param [Docscribe::CLI::Formatters::opts] options runtime options hash
41
+ # @return [void]
42
+ def format_fail_paths(state, options)
43
+ state[:fail_paths].each do |p|
44
+ puts "Would update: #{p}"
45
+
46
+ next if options[:verbose] || options[:quiet]
47
+
48
+ Array(state[:fail_changes][p]).each do |change|
49
+ puts " - #{format_change_reason(change)}"
50
+ end
51
+ end
52
+ end
53
+
54
+ # Print check status line.
55
+ #
56
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
57
+ # @return [void]
58
+ def format_check_status_line(state)
59
+ checked_error = state[:error_paths].size
60
+ type_mismatch_count = state[:type_mismatch_paths].size
61
+
62
+ if all_fine?(state, checked_error, type_mismatch_count)
63
+ puts "Docscribe: OK (#{state[:checked_ok]} files checked)"
64
+ elsif mismatch_only?(state, checked_error)
65
+ puts "Docscribe: OK (#{state[:checked_ok]} files checked, #{type_mismatch_count} with type mismatches)"
66
+ else
67
+ puts failure_line(state, type_mismatch_count, checked_error)
68
+ end
69
+ end
70
+
71
+ # Print type mismatch warnings.
72
+ #
73
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
74
+ # @param [Docscribe::CLI::Formatters::opts] options runtime options hash
75
+ # @return [void]
76
+ def format_type_mismatch_paths(state, options)
77
+ return if options[:quiet]
78
+ return unless options[:verbose] || options[:explain]
79
+
80
+ state[:type_mismatch_paths].each do |p|
81
+ warn "Type mismatches: #{p}"
82
+ Array(state[:type_mismatch_changes][p]).each do |change|
83
+ warn " - #{format_change_reason(change)}"
84
+ end
85
+ end
86
+ end
87
+
88
+ # Print updated file paths.
89
+ #
90
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
91
+ # @param [Docscribe::CLI::Formatters::opts] options runtime options hash
92
+ # @return [void]
93
+ def format_corrected_paths(state, options)
94
+ state[:corrected_paths].each do |p|
95
+ puts "Updated: #{p}"
96
+
97
+ next if options[:verbose] || options[:quiet]
98
+
99
+ Array(state[:corrected_changes][p]).each do |change|
100
+ puts " - #{format_change_reason(change)}"
101
+ end
102
+ end
103
+ end
104
+
105
+ # Print error file messages.
106
+ #
107
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
108
+ # @return [void]
109
+ def format_error_paths(state)
110
+ return if state[:error_paths].empty?
111
+
112
+ warn ''
113
+ state[:error_paths].each do |p|
114
+ warn "Error processing: #{p}"
115
+ warn " #{state[:error_messages][p]}" if state[:error_messages][p]
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ # Check if all files passed.
122
+ #
123
+ # @private
124
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
125
+ # @param [Integer] checked_error count of error files
126
+ # @param [Integer] type_mismatch_count count of type mismatches
127
+ # @return [Boolean]
128
+ def all_fine?(state, checked_error, type_mismatch_count)
129
+ state[:checked_fail].zero? && checked_error.zero? && type_mismatch_count.zero?
130
+ end
131
+
132
+ # Check only type mismatches.
133
+ #
134
+ # @private
135
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
136
+ # @param [Integer] checked_error count of error files
137
+ # @return [Boolean]
138
+ def mismatch_only?(state, checked_error)
139
+ state[:checked_fail].zero? && checked_error.zero?
140
+ end
141
+
142
+ # Build failure status line.
143
+ #
144
+ # @private
145
+ # @param [Docscribe::CLI::Formatters::state] state formatter state hash
146
+ # @param [Integer] type_mismatch_count count of type mismatches
147
+ # @param [Integer] checked_error count of error files
148
+ # @return [String]
149
+ def failure_line(state, type_mismatch_count, checked_error)
150
+ parts = ["#{state[:checked_fail]} need updates"]
151
+ parts << "#{type_mismatch_count} type mismatches" if type_mismatch_count.positive?
152
+ parts << "#{checked_error} errors"
153
+ parts << "#{state[:checked_ok]} ok"
154
+ "Docscribe: FAILED (#{parts.join(', ')})"
155
+ end
156
+
157
+ # Format change reason string.
158
+ #
159
+ # @private
160
+ # @param [Docscribe::CLI::Formatters::change] change change info hash
161
+ # @return [String]
162
+ def format_change_reason(change)
163
+ line = change_line_suffix(change)
164
+ method = change_method_suffix(change)
165
+
166
+ return "unsorted tags#{line}" if change[:type] == :unsorted_tags
167
+ return "#{change[:message]}#{method}#{line}" if direct_message_change?(change)
168
+
169
+ "#{change[:message] || change[:type].to_s.tr('_', ' ')}#{method}#{line}"
170
+ end
171
+
172
+ # Build change line suffix.
173
+ #
174
+ # @private
175
+ # @param [Docscribe::CLI::Formatters::change] change change info hash
176
+ # @return [String]
177
+ def change_line_suffix(change)
178
+ change[:line] ? " at line #{change[:line]}" : ''
179
+ end
180
+
181
+ # Build change method suffix.
182
+ #
183
+ # @private
184
+ # @param [Docscribe::CLI::Formatters::change] change change info hash
185
+ # @return [String]
186
+ def change_method_suffix(change)
187
+ change[:method] ? " for #{change[:method]}" : ''
188
+ end
189
+
190
+ # Check direct message type.
191
+ #
192
+ # @private
193
+ # @param [Docscribe::CLI::Formatters::change] change change info hash
194
+ # @return [Boolean]
195
+ def direct_message_change?(change)
196
+ %i[
197
+ missing_param
198
+ missing_return
199
+ missing_raise
200
+ missing_visibility
201
+ missing_module_function_note
202
+ insert_full_doc_block
203
+ ].include?(change[:type])
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docscribe
4
+ module CLI
5
+ # Factory for output formatters.
6
+ module Formatters
7
+ # Select formatter by format type.
8
+ #
9
+ # @param [Docscribe::CLI::Formatters::format] format output format symbol
10
+ # @raise [ArgumentError]
11
+ # @return [Docscribe::CLI::Formatters::Text, Docscribe::CLI::Formatters::Json, Docscribe::CLI::Formatters::Sarif]
12
+ def self.for(format)
13
+ case format
14
+ when :text then Text.new
15
+ when :json then Json.new
16
+ when :sarif then Sarif.new
17
+ else raise ArgumentError, "Unknown format: #{format}"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ require_relative 'formatters/text'
25
+ require_relative 'formatters/json'
26
+ require_relative 'formatters/sarif'
@@ -12,6 +12,16 @@ module Docscribe
12
12
  # docscribe generate tag MyPlugin --output lib/docscribe_plugins
13
13
  # docscribe generate tag MyPlugin --stdout
14
14
  module Generate
15
+ BANNER = <<~TEXT
16
+ Usage: docscribe generate <type> <PluginName> [options]
17
+
18
+ Types:
19
+ tag Generate a TagPlugin skeleton
20
+ collector Generate a CollectorPlugin skeleton
21
+
22
+ Options:
23
+ TEXT
24
+
15
25
  PLUGIN_TYPES = %w[tag collector].freeze
16
26
 
17
27
  NEXT_STEPS_TEMPLATE = <<~TEXT
@@ -34,8 +44,7 @@ module Docscribe
34
44
  class << self
35
45
  # Run the `generate` subcommand.
36
46
  #
37
- # @param [Array<String>] argv
38
- # @raise [OptionParser::InvalidOption]
47
+ # @param [Array<String>] argv command line arguments
39
48
  # @return [Integer] exit code
40
49
  def run(argv)
41
50
  opts, parser = parse_generate_options(argv)
@@ -54,9 +63,9 @@ module Docscribe
54
63
  # Parse options for the generate subcommand.
55
64
  #
56
65
  # @private
57
- # @param [Array<String>] argv
66
+ # @param [Array<String>] argv command line arguments
58
67
  # @raise [OptionParser::InvalidOption]
59
- # @return [Array(Hash, OptionParser)]
68
+ # @return [(Hash<Symbol, Object>, OptionParser)]
60
69
  def parse_generate_options(argv)
61
70
  opts = { output: nil, stdout: false, help: false }
62
71
  parser = build_option_parser(opts)
@@ -74,8 +83,8 @@ module Docscribe
74
83
  # Extract plugin_type and class_name from remaining argv.
75
84
  #
76
85
  # @private
77
- # @param [Array<String>] argv
78
- # @return [Array(String, String)]
86
+ # @param [Array<String>] argv command line arguments
87
+ # @return [(String?, String?)]
79
88
  def extract_generate_args(argv)
80
89
  [argv.shift, argv.shift]
81
90
  end
@@ -83,9 +92,9 @@ module Docscribe
83
92
  # Validate generate arguments and return exit code on failure.
84
93
  #
85
94
  # @private
86
- # @param [String, nil] plugin_type
87
- # @param [String, nil] class_name
88
- # @param [OptionParser] parser
95
+ # @param [String?] plugin_type 'tag' or 'collector'
96
+ # @param [String?] class_name CamelCase plugin class name
97
+ # @param [OptionParser] parser option parser instance
89
98
  # @return [Integer, nil] exit code or nil if valid
90
99
  def validate_generate_args(plugin_type, class_name, parser)
91
100
  return 1 unless args_provided?(plugin_type, class_name, parser)
@@ -98,9 +107,9 @@ module Docscribe
98
107
  # Render plugin boilerplate for the given type and class name.
99
108
  #
100
109
  # @private
101
- # @param [String] plugin_type 'tag' or 'collector'
102
- # @param [String] class_name CamelCase plugin class name
103
- # @return [String]
110
+ # @param [String?] plugin_type 'tag' or 'collector'
111
+ # @param [String?] class_name CamelCase plugin class name
112
+ # @return [String?]
104
113
  def render(plugin_type, class_name)
105
114
  case plugin_type
106
115
  when 'tag' then tag_template(class_name)
@@ -111,7 +120,7 @@ module Docscribe
111
120
  # Template for a TagPlugin.
112
121
  #
113
122
  # @private
114
- # @param [String] class_name
123
+ # @param [String?] class_name CamelCase plugin class name
115
124
  # @return [String]
116
125
  def tag_template(class_name)
117
126
  <<~RUBY
@@ -169,7 +178,7 @@ module Docscribe
169
178
  # Template for a CollectorPlugin.
170
179
  #
171
180
  # @private
172
- # @param [String] class_name
181
+ # @param [String?] class_name CamelCase plugin class name
173
182
  # @return [String]
174
183
  def collector_template(class_name)
175
184
  <<~RUBY
@@ -237,10 +246,10 @@ module Docscribe
237
246
  # Write generated plugin to a file or print to STDOUT based on options.
238
247
  #
239
248
  # @private
240
- # @param [String] content generated plugin source code
241
- # @param [String] plugin_type 'tag' or 'collector'
242
- # @param [String] class_name CamelCase plugin class name
243
- # @param [Hash] opts parsed options hash
249
+ # @param [String?] content generated plugin source code
250
+ # @param [String?] plugin_type 'tag' or 'collector'
251
+ # @param [String?] class_name CamelCase plugin class name
252
+ # @param [Hash<Symbol, Object>] opts parsed options hash
244
253
  # @return [Integer] exit code
245
254
  def dispatch_output(content, plugin_type, class_name, opts)
246
255
  if opts[:stdout]
@@ -254,10 +263,10 @@ module Docscribe
254
263
  # Write the generated content to a file.
255
264
  #
256
265
  # @private
257
- # @param [String] content
258
- # @param [String] plugin_type
259
- # @param [String] class_name
260
- # @param [String] output_dir
266
+ # @param [String?] content file content to write
267
+ # @param [String?] plugin_type 'tag' or 'collector'
268
+ # @param [String?] class_name CamelCase plugin class name
269
+ # @param [String?] output_dir output directory path
261
270
  # @return [Integer] exit code
262
271
  def write_plugin(content, plugin_type:, class_name:, output_dir:)
263
272
  path = plugin_path(class_name, output_dir)
@@ -272,38 +281,22 @@ module Docscribe
272
281
  # Build the OptionParser for the generate subcommand.
273
282
  #
274
283
  # @private
275
- # @param [Hash] opts mutable parsed options hash
284
+ # @param [Hash<Symbol, Object>] opts mutable parsed options hash
276
285
  # @return [OptionParser]
277
286
  def build_option_parser(opts)
278
287
  OptionParser.new do |opt|
279
- opt.banner = parser_banner
288
+ opt.banner = BANNER
280
289
  register_output_option(opt, opts)
281
290
  register_stdout_option(opt, opts)
282
291
  register_help_option(opt, opts)
283
292
  end
284
293
  end
285
294
 
286
- # Return the usage banner for the generate subcommand parser.
287
- #
288
- # @private
289
- # @return [String]
290
- def parser_banner
291
- <<~TEXT
292
- Usage: docscribe generate <type> <PluginName> [options]
293
-
294
- Types:
295
- tag Generate a TagPlugin skeleton
296
- collector Generate a CollectorPlugin skeleton
297
-
298
- Options:
299
- TEXT
300
- end
301
-
302
295
  # Register the --output option on the OptionParser.
303
296
  #
304
297
  # @private
305
- # @param [OptionParser] opt
306
- # @param [Hash] opts mutable parsed options hash
298
+ # @param [OptionParser] opt option parser instance
299
+ # @param [Hash<Symbol, Object>] opts mutable parsed options hash
307
300
  # @return [void]
308
301
  def register_output_option(opt, opts)
309
302
  opt.on('--output DIR', 'Directory to write the plugin file (default: .)') { |v| opts[:output] = v }
@@ -312,8 +305,8 @@ module Docscribe
312
305
  # Register the --stdout option on the OptionParser.
313
306
  #
314
307
  # @private
315
- # @param [OptionParser] opt
316
- # @param [Hash] opts mutable parsed options hash
308
+ # @param [OptionParser] opt option parser instance
309
+ # @param [Hash<Symbol, Object>] opts mutable parsed options hash
317
310
  # @return [void]
318
311
  def register_stdout_option(opt, opts)
319
312
  opt.on('--stdout', 'Print the generated plugin to STDOUT instead of writing a file') { opts[:stdout] = true }
@@ -322,11 +315,12 @@ module Docscribe
322
315
  # Register the -h/--help option on the OptionParser.
323
316
  #
324
317
  # @private
325
- # @param [OptionParser] opt
326
- # @param [Hash] opts mutable parsed options hash
318
+ # @param [OptionParser] opt option parser instance
319
+ # @param [Hash<Symbol, Object>] opts mutable parsed options hash
327
320
  # @return [void]
328
321
  def register_help_option(opt, opts)
329
322
  opt.on('-h', '--help', 'Show this help') do
323
+ puts opt
330
324
  opts[:help] = true
331
325
  end
332
326
  end
@@ -334,9 +328,9 @@ module Docscribe
334
328
  # Validate that both plugin_type and class_name arguments were provided.
335
329
  #
336
330
  # @private
337
- # @param [String, nil] plugin_type plugin type argument
338
- # @param [String, nil] class_name plugin class name argument
339
- # @param [OptionParser] parser
331
+ # @param [String?] plugin_type plugin type argument
332
+ # @param [String?] class_name plugin class name argument
333
+ # @param [OptionParser] parser option parser instance
340
334
  # @return [Boolean]
341
335
  def args_provided?(plugin_type, class_name, parser)
342
336
  return true if plugin_type && class_name
@@ -349,7 +343,7 @@ module Docscribe
349
343
  # Validate that the plugin type is one of the recognized types.
350
344
  #
351
345
  # @private
352
- # @param [String] plugin_type plugin type to validate
346
+ # @param [String?] plugin_type plugin type to validate
353
347
  # @return [Boolean]
354
348
  def known_type?(plugin_type)
355
349
  return true if PLUGIN_TYPES.include?(plugin_type)
@@ -361,7 +355,7 @@ module Docscribe
361
355
  # Validate that the class name is a valid Ruby constant name.
362
356
  #
363
357
  # @private
364
- # @param [String] class_name class name to validate
358
+ # @param [String?] class_name class name to validate
365
359
  # @return [Boolean]
366
360
  def valid_name?(class_name)
367
361
  return true if valid_constant?(class_name)
@@ -373,7 +367,7 @@ module Docscribe
373
367
  # Check whether a string is a valid Ruby constant name.
374
368
  #
375
369
  # @private
376
- # @param [String] str
370
+ # @param [String?] str string constant to validate
377
371
  # @return [Boolean]
378
372
  def valid_constant?(str)
379
373
  !!(str =~ /\A[A-Z][A-Za-z0-9]*(?:::[A-Z][A-Za-z0-9]*)*\z/)
@@ -382,8 +376,8 @@ module Docscribe
382
376
  # Build the file path for the generated plugin.
383
377
  #
384
378
  # @private
385
- # @param [String] class_name CamelCase plugin class name
386
- # @param [String] output_dir output directory
379
+ # @param [String?] class_name CamelCase plugin class name
380
+ # @param [String?] output_dir output directory
387
381
  # @return [String] full file path
388
382
  def plugin_path(class_name, output_dir)
389
383
  File.join(output_dir || '.', "#{underscore(class_name || '')}.rb")
@@ -392,7 +386,7 @@ module Docscribe
392
386
  # Convert CamelCase to snake_case for file naming.
393
387
  #
394
388
  # @private
395
- # @param [String] str
389
+ # @param [String] str CamelCase string to convert
396
390
  # @return [String]
397
391
  def underscore(str)
398
392
  str
@@ -416,9 +410,9 @@ module Docscribe
416
410
  # Create the output directory and write the plugin file.
417
411
  #
418
412
  # @private
419
- # @param [String] output_dir output directory path
413
+ # @param [String?] output_dir output directory path
420
414
  # @param [String] path full plugin file path
421
- # @param [String] content file content to write
415
+ # @param [String?] content file content to write
422
416
  # @return [void]
423
417
  def write_to_file(output_dir, path, content)
424
418
  require 'fileutils'
@@ -429,7 +423,7 @@ module Docscribe
429
423
  # Print the creation message and next steps after generating a plugin.
430
424
  #
431
425
  # @private
432
- # @param [String] plugin_type 'tag' or 'collector'
426
+ # @param [String?] plugin_type 'tag' or 'collector'
433
427
  # @param [String] path file path of the created plugin
434
428
  # @return [void]
435
429
  def print_created(plugin_type, path)
@@ -441,8 +435,8 @@ module Docscribe
441
435
  # Print registration instructions after file creation.
442
436
  #
443
437
  # @private
444
- # @param [String] plugin_type
445
- # @param [String] path
438
+ # @param [String?] plugin_type 'tag' or 'collector'
439
+ # @param [String] path file path
446
440
  # @return [String]
447
441
  def next_steps(plugin_type, path)
448
442
  format(NEXT_STEPS_TEMPLATE,
@@ -464,8 +458,8 @@ module Docscribe
464
458
  # Generate an implementation hint string for the given plugin type.
465
459
  #
466
460
  # @private
467
- # @param [String] plugin_type 'tag' or 'collector'
468
- # @return [String] hint text
461
+ # @param [String?] plugin_type 'tag' or 'collector'
462
+ # @return [String, nil] hint text
469
463
  def generate_implement_hint(plugin_type)
470
464
  case plugin_type
471
465
  when 'tag'
@@ -7,6 +7,14 @@ module Docscribe
7
7
  module CLI
8
8
  # Generate starter Docscribe configuration.
9
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
+
10
18
  class << self
11
19
  # Create or print a starter Docscribe configuration file.
12
20
  #
@@ -37,8 +45,8 @@ module Docscribe
37
45
  # Parse CLI options for `docscribe init`.
38
46
  #
39
47
  # @private
40
- # @param [Array<String>] argv
41
- # @return [Hash] parsed options
48
+ # @param [Array<String>] argv command-line arguments for `docscribe init`
49
+ # @return [Hash<Symbol, Object>] parsed options
42
50
  def parse_init_options(argv)
43
51
  opts = default_init_options
44
52
  build_init_parser(opts).parse!(argv)
@@ -48,7 +56,7 @@ module Docscribe
48
56
  # Return the default options hash for the init command.
49
57
  #
50
58
  # @private
51
- # @return [Hash]
59
+ # @return [Hash<Symbol, String, Boolean>]
52
60
  def default_init_options
53
61
  { config: 'docscribe.yml', force: false, stdout: false, help: false }
54
62
  end
@@ -56,11 +64,11 @@ module Docscribe
56
64
  # Build and return an OptionParser for the init command.
57
65
  #
58
66
  # @private
59
- # @param [Hash] opts options hash that the parser populates
67
+ # @param [Hash<Symbol, Object>] opts options hash that the parser populates
60
68
  # @return [OptionParser]
61
69
  def build_init_parser(opts)
62
70
  OptionParser.new do |o|
63
- o.banner = 'Usage: docscribe init [options]'
71
+ o.banner = BANNER
64
72
  o.on('--config PATH', 'Where to write the config (default: docscribe.yml)') { |v| opts[:config] = v }
65
73
  o.on('-f', '--force', 'Overwrite if the file already exists') { opts[:force] = true }
66
74
  o.on('--stdout', 'Print config template to STDOUT instead of writing a file') { opts[:stdout] = true }
@@ -74,7 +82,7 @@ module Docscribe
74
82
  # Write the config template to a file.
75
83
  #
76
84
  # @private
77
- # @param [Hash] opts parsed options
85
+ # @param [Hash<Symbol, Object>] opts parsed options
78
86
  # @param [String] yaml config template content
79
87
  # @return [Integer] exit code
80
88
  def write_init_config(opts, yaml)