docscribe 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +692 -180
- data/exe/docscribe +2 -74
- data/lib/docscribe/cli/config_builder.rb +62 -0
- data/lib/docscribe/cli/init.rb +58 -0
- data/lib/docscribe/cli/options.rb +204 -0
- data/lib/docscribe/cli/run.rb +415 -0
- data/lib/docscribe/cli.rb +31 -0
- data/lib/docscribe/config/defaults.rb +71 -0
- data/lib/docscribe/config/emit.rb +126 -0
- data/lib/docscribe/config/filtering.rb +160 -0
- data/lib/docscribe/config/loader.rb +59 -0
- data/lib/docscribe/config/rbs.rb +51 -0
- data/lib/docscribe/config/sorbet.rb +87 -0
- data/lib/docscribe/config/sorting.rb +23 -0
- data/lib/docscribe/config/template.rb +176 -0
- data/lib/docscribe/config/utils.rb +102 -0
- data/lib/docscribe/config.rb +20 -230
- data/lib/docscribe/infer/ast_walk.rb +28 -0
- data/lib/docscribe/infer/constants.rb +11 -0
- data/lib/docscribe/infer/literals.rb +55 -0
- data/lib/docscribe/infer/names.rb +43 -0
- data/lib/docscribe/infer/params.rb +62 -0
- data/lib/docscribe/infer/raises.rb +68 -0
- data/lib/docscribe/infer/returns.rb +171 -0
- data/lib/docscribe/infer.rb +110 -259
- data/lib/docscribe/inline_rewriter/collector.rb +845 -0
- data/lib/docscribe/inline_rewriter/doc_block.rb +383 -0
- data/lib/docscribe/inline_rewriter/doc_builder.rb +605 -0
- data/lib/docscribe/inline_rewriter/source_helpers.rb +228 -0
- data/lib/docscribe/inline_rewriter/tag_sorter.rb +244 -0
- data/lib/docscribe/inline_rewriter.rb +604 -425
- data/lib/docscribe/parsing.rb +120 -0
- data/lib/docscribe/types/provider_chain.rb +37 -0
- data/lib/docscribe/types/rbs/provider.rb +213 -0
- data/lib/docscribe/types/rbs/type_formatter.rb +132 -0
- data/lib/docscribe/types/signature.rb +65 -0
- data/lib/docscribe/types/sorbet/base_provider.rb +217 -0
- data/lib/docscribe/types/sorbet/rbi_provider.rb +35 -0
- data/lib/docscribe/types/sorbet/source_provider.rb +25 -0
- data/lib/docscribe/version.rb +1 -1
- data/lib/docscribe.rb +1 -0
- metadata +85 -17
- data/.rspec +0 -3
- data/.rubocop.yml +0 -11
- data/.rubocop_todo.yml +0 -73
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -73
- data/Rakefile +0 -12
- data/rakelib/docs.rake +0 -73
- data/stingray_docs_internal.gemspec +0 -41
data/exe/docscribe
CHANGED
|
@@ -1,77 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require '
|
|
5
|
-
|
|
6
|
-
require 'docscribe/inline_rewriter'
|
|
7
|
-
|
|
8
|
-
options = {
|
|
9
|
-
stdin: false,
|
|
10
|
-
write: false, # rewrite files in place
|
|
11
|
-
check: false, # dry-run (exit 1 if any file would change)
|
|
12
|
-
rewrite: false, # replace existing comment blocks when inserting
|
|
13
|
-
config: nil
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
parser = OptionParser.new do |opts|
|
|
17
|
-
opts.banner = 'Usage: docscribe [options] [files...]'
|
|
18
|
-
opts.on('--stdin', 'Read code from STDIN and print with docs inserted') { options[:stdin] = true }
|
|
19
|
-
opts.on('--write', 'Rewrite files in place') { options[:write] = true }
|
|
20
|
-
opts.on('--check', 'Dry-run: exit 1 if any file would change') { options[:check] = true }
|
|
21
|
-
opts.on('--rewrite', 'Replace existing comment blocks above methods') { options[:rewrite] = true }
|
|
22
|
-
opts.on('--config PATH', 'Path to config YAML (default: docscribe.yml)') { |v| options[:config] = v }
|
|
23
|
-
opts.on('--version', 'Print version and exit') do
|
|
24
|
-
require 'docscribe/version'
|
|
25
|
-
puts Docscribe::VERSION
|
|
26
|
-
exit
|
|
27
|
-
end
|
|
28
|
-
opts.on('-h', '--help', 'Show this help') do
|
|
29
|
-
puts opts
|
|
30
|
-
exit
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
parser.parse!(ARGV)
|
|
35
|
-
|
|
36
|
-
conf = Docscribe::Config.load(options[:config])
|
|
37
|
-
|
|
38
|
-
def transform(code, replace:, config:)
|
|
39
|
-
Docscribe::InlineRewriter.insert_comments(code, rewrite: replace, config: config)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def rewrite(code, replace:)
|
|
43
|
-
Docscribe::InlineRewriter.insert_comments(code, rewrite: replace)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
if options[:stdin]
|
|
47
|
-
code = $stdin.read
|
|
48
|
-
puts rewrite(code, replace: options[:rewrite])
|
|
49
|
-
exit 0
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
if ARGV.empty?
|
|
53
|
-
warn 'No input. Use --stdin or pass file paths. See --help.'
|
|
54
|
-
exit 1
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
changed = false
|
|
58
|
-
|
|
59
|
-
ARGV.each do |path|
|
|
60
|
-
src = File.read(path)
|
|
61
|
-
out = transform(src, replace: options[:rewrite], config: conf)
|
|
62
|
-
if options[:check]
|
|
63
|
-
if out != src
|
|
64
|
-
warn "Would change: #{path}"
|
|
65
|
-
changed = true
|
|
66
|
-
end
|
|
67
|
-
elsif options[:write]
|
|
68
|
-
if out != src
|
|
69
|
-
File.write(path, out)
|
|
70
|
-
puts "Updated: #{path}"
|
|
71
|
-
end
|
|
72
|
-
else
|
|
73
|
-
puts out
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
exit(options[:check] && changed ? 1 : 0)
|
|
4
|
+
require 'docscribe/cli'
|
|
5
|
+
exit Docscribe::CLI.run(ARGV)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'docscribe/config'
|
|
4
|
+
|
|
5
|
+
module Docscribe
|
|
6
|
+
module CLI
|
|
7
|
+
module ConfigBuilder
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
# Build an effective config by applying CLI overrides on top of a base config.
|
|
11
|
+
#
|
|
12
|
+
# CLI overrides currently affect:
|
|
13
|
+
# - method/file include and exclude filters
|
|
14
|
+
# - RBS enablement and additional signature directories
|
|
15
|
+
# - Sorbet enablement and RBI directories
|
|
16
|
+
#
|
|
17
|
+
# If no relevant CLI override is present, the original config is returned unchanged.
|
|
18
|
+
#
|
|
19
|
+
# @note module_function: when included, also defines #build (instance visibility: private)
|
|
20
|
+
# @param [Docscribe::Config] base base config loaded from YAML/defaults
|
|
21
|
+
# @param [Hash] options parsed CLI options
|
|
22
|
+
# @return [Docscribe::Config] merged effective config
|
|
23
|
+
def build(base, options)
|
|
24
|
+
needs_override =
|
|
25
|
+
options[:include].any? ||
|
|
26
|
+
options[:exclude].any? ||
|
|
27
|
+
options[:include_file].any? ||
|
|
28
|
+
options[:exclude_file].any? ||
|
|
29
|
+
options[:rbs] ||
|
|
30
|
+
options[:sig_dirs].any? ||
|
|
31
|
+
options[:sorbet] ||
|
|
32
|
+
options[:rbi_dirs].any?
|
|
33
|
+
|
|
34
|
+
return base unless needs_override
|
|
35
|
+
|
|
36
|
+
raw = Marshal.load(Marshal.dump(base.raw))
|
|
37
|
+
|
|
38
|
+
raw['filter'] ||= {}
|
|
39
|
+
raw['filter']['include'] = Array(raw['filter']['include']) + options[:include]
|
|
40
|
+
raw['filter']['exclude'] = Array(raw['filter']['exclude']) + options[:exclude]
|
|
41
|
+
|
|
42
|
+
raw['filter']['files'] ||= {}
|
|
43
|
+
raw['filter']['files']['include'] = Array(raw['filter']['files']['include']) + options[:include_file]
|
|
44
|
+
raw['filter']['files']['exclude'] = Array(raw['filter']['files']['exclude']) + options[:exclude_file]
|
|
45
|
+
|
|
46
|
+
if options[:rbs] || options[:sig_dirs].any?
|
|
47
|
+
raw['rbs'] ||= {}
|
|
48
|
+
raw['rbs']['enabled'] = true
|
|
49
|
+
raw['rbs']['sig_dirs'] = Array(raw['rbs']['sig_dirs']) + options[:sig_dirs] if options[:sig_dirs].any?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if options[:sorbet] || options[:rbi_dirs].any?
|
|
53
|
+
raw['sorbet'] ||= {}
|
|
54
|
+
raw['sorbet']['enabled'] = true
|
|
55
|
+
raw['sorbet']['rbi_dirs'] = Array(raw['sorbet']['rbi_dirs']) + options[:rbi_dirs] if options[:rbi_dirs].any?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Docscribe::Config.new(raw)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'docscribe/config'
|
|
5
|
+
|
|
6
|
+
module Docscribe
|
|
7
|
+
module CLI
|
|
8
|
+
module Init
|
|
9
|
+
class << self
|
|
10
|
+
# Create or print a starter Docscribe configuration file.
|
|
11
|
+
#
|
|
12
|
+
# Supported behaviors:
|
|
13
|
+
# - write `docscribe.yml` (default)
|
|
14
|
+
# - write to a custom path via `--config`
|
|
15
|
+
# - overwrite an existing file via `--force`
|
|
16
|
+
# - print the template to STDOUT via `--stdout`
|
|
17
|
+
#
|
|
18
|
+
# @param [Array<String>] argv command-line arguments for `docscribe init`
|
|
19
|
+
# @return [Integer] process exit code
|
|
20
|
+
def run(argv)
|
|
21
|
+
opts = {
|
|
22
|
+
config: 'docscribe.yml',
|
|
23
|
+
force: false,
|
|
24
|
+
stdout: false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
OptionParser.new do |o|
|
|
28
|
+
o.banner = 'Usage: docscribe init [options]'
|
|
29
|
+
o.on('--config PATH', 'Where to write the config (default: docscribe.yml)') { |v| opts[:config] = v }
|
|
30
|
+
o.on('-f', '--force', 'Overwrite if the file already exists') { opts[:force] = true }
|
|
31
|
+
o.on('--stdout', 'Print config template to STDOUT instead of writing a file') { opts[:stdout] = true }
|
|
32
|
+
o.on('-h', '--help', 'Show this help') do
|
|
33
|
+
puts o
|
|
34
|
+
return 0
|
|
35
|
+
end
|
|
36
|
+
end.parse!(argv)
|
|
37
|
+
|
|
38
|
+
yaml = Docscribe::Config.default_yaml
|
|
39
|
+
|
|
40
|
+
if opts[:stdout]
|
|
41
|
+
puts yaml
|
|
42
|
+
return 0
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
path = opts[:config]
|
|
46
|
+
if File.exist?(path) && !opts[:force]
|
|
47
|
+
warn "Config already exists: #{path} (use --force to overwrite)"
|
|
48
|
+
return 1
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
File.write(path, yaml)
|
|
52
|
+
puts "Created: #{path}"
|
|
53
|
+
0
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
|
|
5
|
+
module Docscribe
|
|
6
|
+
module CLI
|
|
7
|
+
module Options
|
|
8
|
+
DEFAULT = {
|
|
9
|
+
stdin: false,
|
|
10
|
+
mode: :check, # :check, :write, :stdin
|
|
11
|
+
strategy: :safe, # :safe, :aggressive
|
|
12
|
+
verbose: false,
|
|
13
|
+
explain: false,
|
|
14
|
+
config: nil,
|
|
15
|
+
include: [],
|
|
16
|
+
exclude: [],
|
|
17
|
+
include_file: [],
|
|
18
|
+
exclude_file: [],
|
|
19
|
+
rbs: false,
|
|
20
|
+
sig_dirs: [],
|
|
21
|
+
sorbet: false,
|
|
22
|
+
rbi_dirs: []
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# Parse CLI arguments into normalized Docscribe runtime options.
|
|
28
|
+
#
|
|
29
|
+
# CLI behavior model:
|
|
30
|
+
# - default: inspect mode using the safe strategy
|
|
31
|
+
# - `-a` / `--autocorrect`: write mode using the safe strategy
|
|
32
|
+
# - `-A` / `--autocorrect-all`: write mode using the aggressive strategy
|
|
33
|
+
# - `--stdin`: stdin mode using the selected strategy (safe by default)
|
|
34
|
+
#
|
|
35
|
+
# Filtering, config, verbosity, and external type options are applied
|
|
36
|
+
# orthogonally.
|
|
37
|
+
#
|
|
38
|
+
# @note module_function: when included, also defines #parse! (instance visibility: private)
|
|
39
|
+
# @param [Array<String>] argv raw CLI arguments
|
|
40
|
+
# @return [Hash] normalized runtime options
|
|
41
|
+
def parse!(argv)
|
|
42
|
+
options = Marshal.load(Marshal.dump(DEFAULT))
|
|
43
|
+
autocorrect_mode = nil
|
|
44
|
+
|
|
45
|
+
parser = OptionParser.new do |opts|
|
|
46
|
+
opts.banner = <<~TEXT
|
|
47
|
+
Usage: docscribe [options] [files...]
|
|
48
|
+
|
|
49
|
+
Default behavior:
|
|
50
|
+
Inspect files and report what safe doc updates would be applied.
|
|
51
|
+
|
|
52
|
+
Autocorrect:
|
|
53
|
+
-a, --autocorrect Apply safe doc updates in place
|
|
54
|
+
(insert missing docs, merge existing doc-like blocks,
|
|
55
|
+
normalize tag order)
|
|
56
|
+
-A, --autocorrect-all Apply aggressive doc updates in place
|
|
57
|
+
(rebuild existing doc blocks)
|
|
58
|
+
|
|
59
|
+
Input / config:
|
|
60
|
+
--stdin Read code from STDIN and print rewritten output
|
|
61
|
+
-C, --config PATH Path to config YAML (default: docscribe.yml)
|
|
62
|
+
|
|
63
|
+
Type information:
|
|
64
|
+
--rbs Use RBS signatures for @param/@return when available
|
|
65
|
+
--sig-dir DIR Add an RBS signature directory (repeatable). Implies `--rbs`.
|
|
66
|
+
--sorbet Use Sorbet signatures from inline sigs / RBI files when available
|
|
67
|
+
--rbi-dir DIR Add a Sorbet RBI directory (repeatable). Implies --sorbet.
|
|
68
|
+
|
|
69
|
+
Filtering:
|
|
70
|
+
--include PATTERN Include PATTERN (method id or file path; glob or /regex/)
|
|
71
|
+
--exclude PATTERN Exclude PATTERN (method id or file path; glob or /regex/)
|
|
72
|
+
--include-file PATTERN Only process files matching PATTERN (glob or /regex/)
|
|
73
|
+
--exclude-file PATTERN Skip files matching PATTERN (glob or /regex/)
|
|
74
|
+
|
|
75
|
+
Output:
|
|
76
|
+
--verbose Print per-file actions
|
|
77
|
+
-e, --explain Show detailed reasons for changes
|
|
78
|
+
|
|
79
|
+
Other:
|
|
80
|
+
-v, --version Print version and exit
|
|
81
|
+
-h, --help Show this help
|
|
82
|
+
TEXT
|
|
83
|
+
|
|
84
|
+
opts.on('-a', '--autocorrect', 'Apply safe doc updates in place') do
|
|
85
|
+
autocorrect_mode = :safe
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
opts.on('-A', '--autocorrect-all', 'Apply aggressive doc updates in place') do
|
|
89
|
+
autocorrect_mode = :aggressive
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
opts.on('--stdin', 'Read code from STDIN and print rewritten output') do
|
|
93
|
+
options[:stdin] = true
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
opts.on('-C', '--config PATH', 'Path to config YAML (default: docscribe.yml)') do |v|
|
|
97
|
+
options[:config] = v
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
opts.on('--rbs', 'Use RBS signatures for @param/@return when available (falls back to inference)') do
|
|
101
|
+
options[:rbs] = true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
opts.on('--sig-dir DIR', 'Add an RBS signature directory (repeatable). Implies --rbs.') do |v|
|
|
105
|
+
options[:rbs] = true
|
|
106
|
+
options[:sig_dirs] << v
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
opts.on('--sorbet', 'Use Sorbet signatures from inline sigs / RBI files when available') do
|
|
110
|
+
options[:sorbet] = true
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
opts.on('--rbi-dir DIR', 'Add a Sorbet RBI directory (repeatable). Implies --sorbet.') do |v|
|
|
114
|
+
options[:sorbet] = true
|
|
115
|
+
options[:rbi_dirs] << v
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
opts.on('--include PATTERN', 'Include PATTERN (method id or file path; glob or /regex/)') do |v|
|
|
119
|
+
route_include_exclude(options, :include, v)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
opts.on('--exclude PATTERN',
|
|
123
|
+
'Exclude PATTERN (method id or file path; glob or /regex/). Exclude wins.') do |v|
|
|
124
|
+
route_include_exclude(options, :exclude, v)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
opts.on('--include-file PATTERN', 'Only process files matching PATTERN (glob or /regex/)') do |v|
|
|
128
|
+
options[:include_file] << v
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
opts.on('--exclude-file PATTERN', 'Skip files matching PATTERN (glob or /regex/). Exclude wins.') do |v|
|
|
132
|
+
options[:exclude_file] << v
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
opts.on('--verbose', 'Print per-file actions') do
|
|
136
|
+
options[:verbose] = true
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
opts.on('-e', '--explain', 'Show detailed reasons for changes') do
|
|
140
|
+
options[:explain] = true
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
opts.on('-v', '--version', 'Print version and exit') do
|
|
144
|
+
require 'docscribe/version'
|
|
145
|
+
puts Docscribe::VERSION
|
|
146
|
+
exit 0
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
opts.on('-h', '--help', 'Show this help') do
|
|
150
|
+
puts opts
|
|
151
|
+
exit 0
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
parser.parse!(argv)
|
|
156
|
+
|
|
157
|
+
if options[:stdin]
|
|
158
|
+
options[:mode] = :stdin
|
|
159
|
+
options[:strategy] = autocorrect_mode || :safe
|
|
160
|
+
elsif autocorrect_mode
|
|
161
|
+
options[:mode] = :write
|
|
162
|
+
options[:strategy] = autocorrect_mode
|
|
163
|
+
else
|
|
164
|
+
options[:mode] = :check
|
|
165
|
+
options[:strategy] = :safe
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
options
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Route an include/exclude pattern into method filters or file filters.
|
|
172
|
+
#
|
|
173
|
+
# Regex-looking patterns (`/…/`) are treated as method-id filters.
|
|
174
|
+
# File-like patterns are routed into `*_file`.
|
|
175
|
+
#
|
|
176
|
+
# @note module_function: when included, also defines #route_include_exclude (instance visibility: private)
|
|
177
|
+
# @param [Hash] options mutable parsed options hash
|
|
178
|
+
# @param [Symbol] kind either :include or :exclude
|
|
179
|
+
# @param [String] value raw pattern from the CLI
|
|
180
|
+
# @return [void]
|
|
181
|
+
def route_include_exclude(options, kind, value)
|
|
182
|
+
if looks_like_file_pattern?(value)
|
|
183
|
+
options[:"#{kind}_file"] << value
|
|
184
|
+
else
|
|
185
|
+
options[kind] << value
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Heuristically decide whether a pattern looks like a file path or file glob.
|
|
190
|
+
#
|
|
191
|
+
# Regex syntax (`/.../`) is intentionally treated as a method-id pattern,
|
|
192
|
+
# not a file pattern.
|
|
193
|
+
#
|
|
194
|
+
# @note module_function: when included, also defines #looks_like_file_pattern? (instance visibility: private)
|
|
195
|
+
# @param [String] pat pattern passed via CLI
|
|
196
|
+
# @return [Boolean]
|
|
197
|
+
def looks_like_file_pattern?(pat)
|
|
198
|
+
return false if pat.start_with?('/') && pat.end_with?('/') && pat.length >= 2
|
|
199
|
+
|
|
200
|
+
pat.include?('/') || pat.include?('**') || pat.end_with?('.rb')
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|