rdoc-markdown 0.6.0 → 0.8.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/.erb_lint.yml +40 -0
- data/.erb_linters/no_embedded_assets.rb +29 -0
- data/.erb_linters/non_raw_html.rb +29 -0
- data/.standard.yml +3 -0
- data/.yard-lint.yml +283 -0
- data/AGENTS.md +48 -0
- data/CHANGELOG.md +23 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +164 -34
- data/README.md +35 -4
- data/Rakefile +98 -16
- data/example/Bird.md +2 -2
- data/example/Duck.md +1 -1
- data/example/Object.md +1 -1
- data/example/Waterfowl.md +1 -1
- data/example/jekyll-seo-tag/Jekyll/SeoTag/AuthorDrop.md +40 -0
- data/example/jekyll-seo-tag/Jekyll/SeoTag/Drop.md +133 -0
- data/example/jekyll-seo-tag/Jekyll/SeoTag/Filters.md +9 -0
- data/example/jekyll-seo-tag/Jekyll/SeoTag/ImageDrop.md +33 -0
- data/example/jekyll-seo-tag/Jekyll/SeoTag/JSONLD.md +18 -0
- data/example/jekyll-seo-tag/Jekyll/SeoTag/JSONLDDrop.md +41 -0
- data/example/jekyll-seo-tag/Jekyll/SeoTag/UrlHelper.md +5 -0
- data/example/jekyll-seo-tag/Jekyll/SeoTag.md +54 -0
- data/example/jekyll-seo-tag/Jekyll.md +3 -0
- data/example/jekyll-seo-tag/Liquid/Tag.md +3 -0
- data/example/jekyll-seo-tag/Liquid.md +5 -0
- data/example/jekyll-seo-tag/index.csv +60 -0
- data/gemfiles/rdoc_head.gemfile +5 -0
- data/lib/markdown.rb +1 -0
- data/lib/rdoc/generator/markdown.rb +506 -240
- data/lib/rdoc/markdown/version.rb +4 -1
- data/lib/templates/classfile.md.erb +1 -0
- data/mutant.yml +15 -0
- data/rdoc-markdown.gemspec +27 -25
- metadata +53 -4
|
@@ -1,62 +1,127 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
gem
|
|
3
|
+
gem "rdoc"
|
|
4
4
|
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
5
|
+
require "erb"
|
|
6
|
+
require "reverse_markdown"
|
|
7
|
+
require "csv"
|
|
8
|
+
require "cgi"
|
|
9
|
+
require "optparse"
|
|
10
10
|
|
|
11
|
+
# Generates Markdown output and a CSV search index from an RDoc store.
|
|
11
12
|
class RDoc::Generator::Markdown
|
|
12
13
|
RDoc::RDoc.add_generator self
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
# Directory containing ERB templates.
|
|
16
|
+
TEMPLATE_DIR = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "templates"))
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
# Supported reverse_markdown unknown-tag modes.
|
|
19
|
+
MARKDOWN_UNKNOWN_TAGS = %i[pass_through drop bypass raise].freeze
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
# Adds rdoc-markdown generator configuration to RDoc's option object.
|
|
22
|
+
module OptionsExtension
|
|
23
|
+
# Initializes markdown generator options alongside RDoc's built-in options.
|
|
24
|
+
#
|
|
25
|
+
# @return [void]
|
|
26
|
+
def init_ivars
|
|
27
|
+
super
|
|
28
|
+
@markdown_unknown_tags = :pass_through
|
|
29
|
+
end
|
|
21
30
|
|
|
22
|
-
|
|
31
|
+
# Loads markdown generator options from serialized RDoc options.
|
|
32
|
+
#
|
|
33
|
+
# @param map [Psych::Coder] Serialized RDoc options.
|
|
34
|
+
#
|
|
35
|
+
# @return [void]
|
|
36
|
+
def init_with(map)
|
|
37
|
+
super
|
|
38
|
+
@markdown_unknown_tags = map["markdown_unknown_tags"] if map.map.key?("markdown_unknown_tags")
|
|
39
|
+
end
|
|
23
40
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
41
|
+
# Applies markdown generator options from a loaded .rdoc_options hash.
|
|
42
|
+
#
|
|
43
|
+
# @param map [Hash] Loaded RDoc options.
|
|
44
|
+
#
|
|
45
|
+
# @return [void]
|
|
46
|
+
def override(map)
|
|
47
|
+
super
|
|
48
|
+
@markdown_unknown_tags = map.fetch("markdown_unknown_tags") if map.key?("markdown_unknown_tags")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
27
51
|
|
|
28
|
-
|
|
29
|
-
#
|
|
30
|
-
#
|
|
52
|
+
# Registers markdown generator-specific RDoc options.
|
|
53
|
+
#
|
|
54
|
+
# @param rdoc_options [RDoc::Options] RDoc options object.
|
|
55
|
+
#
|
|
56
|
+
# @return [void]
|
|
57
|
+
def self.setup_options(rdoc_options)
|
|
58
|
+
rdoc_options.option_parser.on(
|
|
59
|
+
"--markdown-unknown-tags=MODE",
|
|
60
|
+
"How to handle unknown HTML tags: #{MARKDOWN_UNKNOWN_TAGS.join(", ")}."
|
|
61
|
+
) do |value|
|
|
62
|
+
rdoc_options.markdown_unknown_tags = value.to_sym
|
|
63
|
+
end
|
|
64
|
+
end
|
|
31
65
|
|
|
32
|
-
|
|
33
|
-
#
|
|
34
|
-
#
|
|
66
|
+
# Validates the configured reverse_markdown unknown-tag mode.
|
|
67
|
+
#
|
|
68
|
+
# @param value [Symbol] Unknown-tag mode.
|
|
69
|
+
#
|
|
70
|
+
# @return [Symbol] Validated unknown-tag mode.
|
|
71
|
+
def self.validate_markdown_unknown_tags(value)
|
|
72
|
+
return value if MARKDOWN_UNKNOWN_TAGS.include?(value)
|
|
35
73
|
|
|
36
|
-
|
|
37
|
-
|
|
74
|
+
expected = MARKDOWN_UNKNOWN_TAGS.map { |mode| ":#{mode}" }.join(", ")
|
|
75
|
+
raise OptionParser::InvalidArgument,
|
|
76
|
+
"invalid markdown_unknown_tags: #{value.inspect} (expected one of: #{expected})"
|
|
38
77
|
end
|
|
39
78
|
|
|
40
|
-
#
|
|
41
|
-
|
|
79
|
+
# Source store for generated content.
|
|
80
|
+
#
|
|
81
|
+
# @return [RDoc::Store]
|
|
82
|
+
attr_reader :store
|
|
42
83
|
|
|
43
|
-
|
|
44
|
-
#
|
|
84
|
+
# Working directory captured when the generator is created.
|
|
85
|
+
#
|
|
86
|
+
# @return [Pathname]
|
|
87
|
+
attr_reader :base_dir
|
|
45
88
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
89
|
+
# Classes and modules selected for output.
|
|
90
|
+
#
|
|
91
|
+
# @return [Array<RDoc::Context>, nil]
|
|
92
|
+
attr_reader :classes
|
|
49
93
|
|
|
50
|
-
|
|
94
|
+
# Text files selected for output.
|
|
95
|
+
#
|
|
96
|
+
# @return [Array<RDoc::TopLevel>, nil]
|
|
97
|
+
attr_reader :pages
|
|
51
98
|
|
|
52
|
-
|
|
99
|
+
# Required by RDoc's generator interface; markdown output has no class subdirectory.
|
|
100
|
+
#
|
|
101
|
+
# @return [nil]
|
|
102
|
+
def class_dir
|
|
53
103
|
end
|
|
54
104
|
|
|
55
|
-
|
|
56
|
-
|
|
105
|
+
# this alias is required for rdoc to work
|
|
106
|
+
alias_method :file_dir, :class_dir
|
|
57
107
|
|
|
108
|
+
# Creates a generator for an RDoc store and options.
|
|
109
|
+
#
|
|
110
|
+
# @param store [RDoc::Store] Source documentation store.
|
|
111
|
+
# @param rdoc_options [RDoc::Options] Generator options.
|
|
112
|
+
def initialize(store, rdoc_options)
|
|
113
|
+
@store = store
|
|
114
|
+
@options = rdoc_options
|
|
115
|
+
@markdown_unknown_tags = self.class.validate_markdown_unknown_tags(rdoc_options.markdown_unknown_tags)
|
|
116
|
+
|
|
117
|
+
@base_dir = Pathname.pwd
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Writes class files, page files, and the search index.
|
|
121
|
+
#
|
|
122
|
+
# @return [void]
|
|
58
123
|
def generate
|
|
59
|
-
debug("Setting things up
|
|
124
|
+
debug("Setting things up ")
|
|
60
125
|
|
|
61
126
|
setup
|
|
62
127
|
|
|
@@ -77,27 +142,30 @@ class RDoc::Generator::Markdown
|
|
|
77
142
|
|
|
78
143
|
attr_reader :options, :output_dir
|
|
79
144
|
|
|
80
|
-
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
145
|
+
# Prints a message when RDoc debug output is enabled.
|
|
146
|
+
#
|
|
147
|
+
# @param str [String] Message to print.
|
|
148
|
+
#
|
|
149
|
+
# @return [void]
|
|
150
|
+
def debug(str)
|
|
151
|
+
# RDoc exposes --debug through this global and does not mirror it on options.
|
|
152
|
+
# standard:disable Style/GlobalVars
|
|
84
153
|
return unless $DEBUG_RDOC
|
|
154
|
+
# standard:enable Style/GlobalVars
|
|
85
155
|
|
|
86
|
-
puts "[rdoc-markdown] #{str}"
|
|
87
|
-
yield if block_given?
|
|
156
|
+
puts "[rdoc-markdown] #{str}"
|
|
88
157
|
end
|
|
89
158
|
|
|
90
|
-
|
|
91
|
-
# This class emits a search index for generated documentation as sqlite database
|
|
159
|
+
# Writes a CSV search index for generated documentation.
|
|
92
160
|
#
|
|
161
|
+
# @return [void]
|
|
162
|
+
def emit_csv_index
|
|
163
|
+
filepath = "#{output_dir}/index.csv"
|
|
93
164
|
|
|
94
|
-
|
|
95
|
-
filepath = "#{output_dir}/#{name}"
|
|
96
|
-
|
|
97
|
-
CSV.open(filepath, 'wb') do |csv|
|
|
165
|
+
CSV.open(filepath, "wb") do |csv|
|
|
98
166
|
csv << %w[name type path]
|
|
99
167
|
|
|
100
|
-
@classes.
|
|
168
|
+
@classes.each do |klass|
|
|
101
169
|
csv << [
|
|
102
170
|
display_name(klass),
|
|
103
171
|
klass.type.capitalize,
|
|
@@ -107,7 +175,7 @@ class RDoc::Generator::Markdown
|
|
|
107
175
|
klass.method_list.select(&:display?).each do |method|
|
|
108
176
|
csv << [
|
|
109
177
|
"#{display_name(klass)}.#{method.name}",
|
|
110
|
-
|
|
178
|
+
"Method",
|
|
111
179
|
"#{output_path_for(klass)}##{method.aref}"
|
|
112
180
|
]
|
|
113
181
|
end
|
|
@@ -115,11 +183,11 @@ class RDoc::Generator::Markdown
|
|
|
115
183
|
klass
|
|
116
184
|
.constants
|
|
117
185
|
.select(&:display?)
|
|
118
|
-
.
|
|
186
|
+
.sort
|
|
119
187
|
.each do |const|
|
|
120
188
|
csv << [
|
|
121
189
|
"#{display_name(klass)}.#{const.name}",
|
|
122
|
-
|
|
190
|
+
"Constant",
|
|
123
191
|
"#{output_path_for(klass)}##{const.name}"
|
|
124
192
|
]
|
|
125
193
|
end
|
|
@@ -127,11 +195,11 @@ class RDoc::Generator::Markdown
|
|
|
127
195
|
klass
|
|
128
196
|
.attributes
|
|
129
197
|
.select(&:display?)
|
|
130
|
-
.
|
|
198
|
+
.sort
|
|
131
199
|
.each do |attr|
|
|
132
200
|
csv << [
|
|
133
201
|
"#{display_name(klass)}.#{attr.name}",
|
|
134
|
-
|
|
202
|
+
"Attribute",
|
|
135
203
|
"#{output_path_for(klass)}##{attr.aref}"
|
|
136
204
|
]
|
|
137
205
|
end
|
|
@@ -140,16 +208,19 @@ class RDoc::Generator::Markdown
|
|
|
140
208
|
@pages.each do |page|
|
|
141
209
|
csv << [
|
|
142
210
|
page.page_name,
|
|
143
|
-
|
|
211
|
+
"Page",
|
|
144
212
|
page_output_path(page)
|
|
145
213
|
]
|
|
146
214
|
end
|
|
147
215
|
end
|
|
148
216
|
end
|
|
149
217
|
|
|
218
|
+
# Writes one Markdown file per selected class or module.
|
|
219
|
+
#
|
|
220
|
+
# @return [void]
|
|
150
221
|
def emit_classfiles
|
|
151
|
-
template_content = File.read(File.join(TEMPLATE_DIR,
|
|
152
|
-
template = ERB.new(template_content, trim_mode:
|
|
222
|
+
template_content = File.read(File.join(TEMPLATE_DIR, "classfile.md.erb"))
|
|
223
|
+
template = ERB.new(template_content, trim_mode: "-")
|
|
153
224
|
|
|
154
225
|
@classes.each do |klass|
|
|
155
226
|
result = finalize_markdown(template.result(binding), current_output_path: output_path_for(klass))
|
|
@@ -166,73 +237,88 @@ class RDoc::Generator::Markdown
|
|
|
166
237
|
end
|
|
167
238
|
end
|
|
168
239
|
|
|
240
|
+
# Writes one Markdown file per selected text page.
|
|
241
|
+
#
|
|
242
|
+
# @return [void]
|
|
169
243
|
def emit_pagefiles
|
|
170
244
|
@pages.each do |page|
|
|
171
245
|
out_file = Pathname.new("#{output_dir}/#{page_output_path(page)}")
|
|
172
246
|
out_file.dirname.mkpath
|
|
173
247
|
|
|
174
|
-
content = markdownify(page.description
|
|
248
|
+
content = markdownify(page.description)
|
|
175
249
|
File.write(out_file, finalize_markdown(content, current_output_path: page_output_path(page)))
|
|
176
250
|
end
|
|
177
251
|
end
|
|
178
252
|
|
|
179
|
-
|
|
180
|
-
#
|
|
181
|
-
|
|
253
|
+
# Converts a qualified object name into a Markdown path.
|
|
254
|
+
#
|
|
255
|
+
# @param class_name [String] Qualified class or module name.
|
|
256
|
+
#
|
|
257
|
+
# @return [String] Relative Markdown path.
|
|
182
258
|
def turn_to_path(class_name)
|
|
183
|
-
"#{class_name.gsub(
|
|
259
|
+
"#{class_name.gsub("::", "/")}.md"
|
|
184
260
|
end
|
|
185
261
|
|
|
262
|
+
# Builds the Markdown output path for an RDoc page.
|
|
263
|
+
#
|
|
264
|
+
# @param page [RDoc::TopLevel] Page object to render.
|
|
265
|
+
#
|
|
266
|
+
# @return [String] Relative Markdown path.
|
|
186
267
|
def page_output_path(page)
|
|
187
|
-
source_path = normalize_input_path_for_output(page.relative_name
|
|
268
|
+
source_path = normalize_input_path_for_output(page.relative_name)
|
|
188
269
|
dirname = File.dirname(source_path)
|
|
189
|
-
basename = "#{File.basename(source_path).tr(
|
|
270
|
+
basename = "#{File.basename(source_path).tr(".", "_")}.md"
|
|
190
271
|
|
|
191
|
-
return basename if dirname ==
|
|
272
|
+
return basename if dirname == "."
|
|
192
273
|
|
|
193
274
|
"#{dirname}/#{basename}"
|
|
194
275
|
end
|
|
195
276
|
|
|
277
|
+
# Returns the normalized display name for a class or module.
|
|
278
|
+
#
|
|
279
|
+
# @param code_object [RDoc::Context] Class or module object.
|
|
280
|
+
#
|
|
281
|
+
# @return [String] Display name used in headings and the index.
|
|
196
282
|
def display_name(code_object)
|
|
197
|
-
|
|
198
|
-
class_doc ? class_doc[:display_name] : code_object.full_name
|
|
283
|
+
class_doc_for(code_object).fetch(:display_name)
|
|
199
284
|
end
|
|
200
285
|
|
|
286
|
+
# Returns the canonical Markdown path for a class or module.
|
|
287
|
+
#
|
|
288
|
+
# @param code_object [RDoc::Context] Class or module object.
|
|
289
|
+
#
|
|
290
|
+
# @return [String] Relative Markdown path.
|
|
201
291
|
def output_path_for(code_object)
|
|
202
|
-
|
|
203
|
-
class_doc ? class_doc[:output_path] : turn_to_path(code_object.full_name)
|
|
292
|
+
class_doc_for(code_object).fetch(:output_path)
|
|
204
293
|
end
|
|
205
294
|
|
|
295
|
+
# Returns compatibility paths that should mirror the canonical output.
|
|
296
|
+
#
|
|
297
|
+
# @param code_object [RDoc::Context] Class or module object.
|
|
298
|
+
#
|
|
299
|
+
# @return [Array<String>] Legacy Markdown paths.
|
|
206
300
|
def legacy_paths_for(code_object)
|
|
207
|
-
|
|
208
|
-
class_doc ? class_doc[:legacy_paths] : []
|
|
301
|
+
class_doc_for(code_object).fetch(:legacy_paths)
|
|
209
302
|
end
|
|
210
303
|
|
|
211
|
-
|
|
212
|
-
#
|
|
213
|
-
|
|
214
|
-
#
|
|
304
|
+
# Converts RDoc HTML into GitHub-flavored Markdown.
|
|
305
|
+
#
|
|
306
|
+
# @param input [String] RDoc HTML fragment.
|
|
307
|
+
#
|
|
308
|
+
# @return [String] Markdown with normalized links and no trailing whitespace.
|
|
215
309
|
def markdownify(input)
|
|
216
|
-
#
|
|
217
|
-
# Allowed parameters:
|
|
310
|
+
# ReverseMarkdown supports these unknown-tag modes:
|
|
218
311
|
# - pass_through - (default) Include the unknown tag completely into the result
|
|
219
312
|
# - drop - Drop the unknown tag and its content
|
|
220
313
|
# - bypass - Ignore the unknown tag but try to convert its content
|
|
221
314
|
# - raise - Raise an error to let you know
|
|
222
315
|
|
|
223
|
-
html = normalize_rdoc_pre_blocks(input
|
|
224
|
-
|
|
225
|
-
md = String.new(ReverseMarkdown.convert(html, unknown_tags: :bypass, github_flavored: true))
|
|
316
|
+
html = normalize_rdoc_pre_blocks(input)
|
|
226
317
|
|
|
227
|
-
|
|
228
|
-
md = unindent_text(md)
|
|
229
|
-
|
|
230
|
-
# Remove RDoc navigation links from generated headings.
|
|
231
|
-
md.gsub!(/(#+\s+[^\n]+?)\s*\[¶\]\([^)]+\)(?:\s*\[↑\]\(#top\))?/) { Regexp.last_match(1) }
|
|
232
|
-
md.gsub!(/\s+\[↑\]\(#top\)$/, '')
|
|
318
|
+
md = ReverseMarkdown.convert(html, github_flavored: true, unknown_tags: @markdown_unknown_tags).dup
|
|
233
319
|
|
|
234
320
|
# Flatten headings whose visible text is wrapped in a self-link.
|
|
235
|
-
md.gsub!(/^(#+)\s
|
|
321
|
+
md.gsub!(/^(#+)\s\[([^\]]+)\]\((?:#[^)]+)\)$/) { "#{Regexp.last_match(1)} #{Regexp.last_match(2)}" }
|
|
236
322
|
|
|
237
323
|
# Replace .html to .md extension in all local markdown links.
|
|
238
324
|
md.gsub!(%r{\]\((?!https?://|mailto:|#)([^)]+?)\.html((?:[?#][^)]+)?)\)}i) do
|
|
@@ -253,173 +339,320 @@ class RDoc::Generator::Markdown
|
|
|
253
339
|
"](#{Regexp.last_match(1)}#{Regexp.last_match(2)})"
|
|
254
340
|
end
|
|
255
341
|
|
|
256
|
-
md
|
|
257
|
-
md = normalize_definition_list_code_blocks(md)
|
|
258
|
-
md.lines.map(&:rstrip).join("\n").strip
|
|
342
|
+
normalize_definition_list_code_blocks(md).rstrip
|
|
259
343
|
end
|
|
260
344
|
|
|
261
|
-
#
|
|
262
|
-
|
|
345
|
+
# Short alias used by ERB templates.
|
|
346
|
+
alias_method :h, :markdownify
|
|
263
347
|
|
|
348
|
+
# Builds an HTML anchor tag.
|
|
349
|
+
#
|
|
350
|
+
# @param id [String] Fragment identifier for the generated anchor.
|
|
351
|
+
#
|
|
352
|
+
# @return [String] HTML anchor tag.
|
|
264
353
|
def anchor(id)
|
|
265
354
|
%(<a id="#{id}"></a>)
|
|
266
355
|
end
|
|
267
356
|
|
|
357
|
+
# Renders an RDoc object's description as Markdown.
|
|
358
|
+
#
|
|
359
|
+
# @param code_object [RDoc::CodeObject] Object with an RDoc description.
|
|
360
|
+
# @param fallback [String, nil] Text to use when the description is empty.
|
|
361
|
+
# @param heading_level_offset [Integer] Heading levels to add while rendering.
|
|
362
|
+
#
|
|
363
|
+
# @return [String] Rendered description or fallback text.
|
|
268
364
|
def describe(code_object, fallback: nil, heading_level_offset: 0)
|
|
269
|
-
description = code_object.description
|
|
270
|
-
return fallback.to_s if description.
|
|
365
|
+
description = code_object.description
|
|
366
|
+
return fallback.to_s if description.empty?
|
|
271
367
|
|
|
272
368
|
shift_headings(markdownify(description), heading_level_offset)
|
|
273
369
|
end
|
|
274
370
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
371
|
+
# Renders a section description as Markdown.
|
|
372
|
+
#
|
|
373
|
+
# @param section [RDoc::Context::Section] RDoc section whose description appears before grouped members.
|
|
374
|
+
# @param heading_level_offset [Integer] Heading levels to add while rendering.
|
|
375
|
+
#
|
|
376
|
+
# @return [String] Rendered section description.
|
|
377
|
+
def section_description(section, heading_level_offset:)
|
|
378
|
+
shift_headings(markdownify(section.description), heading_level_offset)
|
|
280
379
|
end
|
|
281
380
|
|
|
381
|
+
# Builds the visible method signature used in headings.
|
|
382
|
+
#
|
|
383
|
+
# @param method [RDoc::AnyMethod] Method object to render.
|
|
384
|
+
#
|
|
385
|
+
# @return [String] Normalized method signature.
|
|
282
386
|
def method_signature(method)
|
|
283
|
-
signature = method.param_seq
|
|
284
|
-
return
|
|
387
|
+
signature = method.param_seq
|
|
388
|
+
return "()" unless signature.match?(/\S/)
|
|
389
|
+
|
|
390
|
+
signature = signature.gsub("->", " -> ")
|
|
391
|
+
signature = signature.gsub(/\s+/, " ").strip
|
|
392
|
+
signature = " #{signature}" if signature.start_with?("->")
|
|
393
|
+
merge_method_signature_arguments(signature, method.params)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Merges RDoc parameter names into a type-only signature.
|
|
397
|
+
#
|
|
398
|
+
# @param signature [String] Method signature from RDoc call sequence.
|
|
399
|
+
# @param raw_params [String, nil] Method parameter list from RDoc.
|
|
400
|
+
#
|
|
401
|
+
# @return [String] Signature with names added when safe.
|
|
402
|
+
def merge_method_signature_arguments(signature, raw_params)
|
|
403
|
+
params = normalized_method_params(raw_params)
|
|
404
|
+
|
|
405
|
+
signature_args, signature_suffix = split_signature_arguments_and_suffix(signature)
|
|
406
|
+
return signature if signature_args.nil?
|
|
407
|
+
|
|
408
|
+
param_parts = split_signature_list(params)
|
|
409
|
+
signature_parts = split_signature_list(signature_args)
|
|
410
|
+
return signature unless param_parts.length.eql?(signature_parts.length)
|
|
411
|
+
|
|
412
|
+
param_names = param_parts.map { |part| extract_parameter_name(part) }
|
|
413
|
+
return signature if param_names.any?(&:nil?)
|
|
414
|
+
return signature if signature_parts.zip(param_names).all? { |part, name| signature_part_mentions_name?(part, name) }
|
|
285
415
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
416
|
+
merged_args = param_parts.zip(signature_parts).map do |param, type|
|
|
417
|
+
separator = param.end_with?(":") ? " " : ": "
|
|
418
|
+
"#{param}#{separator}#{type}"
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
"(#{merged_args.join(", ")})#{signature_suffix}"
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# Normalizes RDoc's raw parameter string.
|
|
425
|
+
#
|
|
426
|
+
# @param raw_params [String, nil] Parameter list from RDoc.
|
|
427
|
+
#
|
|
428
|
+
# @return [String] Parameter list without outer parentheses.
|
|
429
|
+
def normalized_method_params(raw_params)
|
|
430
|
+
params = raw_params.to_s.strip
|
|
431
|
+
params = params[1...-1] if params.start_with?("(") && params.end_with?(")")
|
|
432
|
+
|
|
433
|
+
params
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# Splits a parenthesized signature into arguments and suffix.
|
|
437
|
+
#
|
|
438
|
+
# @param signature [String] Method signature.
|
|
439
|
+
#
|
|
440
|
+
# @return [Array<String>, nil] Argument text and suffix, or nil when not parenthesized.
|
|
441
|
+
def split_signature_arguments_and_suffix(signature)
|
|
442
|
+
return unless signature.start_with?("(")
|
|
443
|
+
|
|
444
|
+
depth = 0
|
|
445
|
+
|
|
446
|
+
signature.each_char.with_index do |char, index|
|
|
447
|
+
depth += 1 if char == "("
|
|
448
|
+
|
|
449
|
+
next unless char == ")"
|
|
450
|
+
|
|
451
|
+
depth -= 1
|
|
452
|
+
return [signature[1...index], signature[(index + 1)..]] if depth.zero?
|
|
453
|
+
end
|
|
290
454
|
end
|
|
291
455
|
|
|
456
|
+
# Splits a comma-separated signature list while preserving nested groups.
|
|
457
|
+
#
|
|
458
|
+
# @param list [String] Signature argument list.
|
|
459
|
+
#
|
|
460
|
+
# @return [Array<String>] Signature parts.
|
|
461
|
+
def split_signature_list(list)
|
|
462
|
+
parts = []
|
|
463
|
+
current = +""
|
|
464
|
+
paren_depth = 0
|
|
465
|
+
bracket_depth = 0
|
|
466
|
+
brace_depth = 0
|
|
467
|
+
|
|
468
|
+
list.each_char do |char|
|
|
469
|
+
case char
|
|
470
|
+
when "("
|
|
471
|
+
paren_depth += 1
|
|
472
|
+
when ")"
|
|
473
|
+
paren_depth -= 1
|
|
474
|
+
when "["
|
|
475
|
+
bracket_depth += 1
|
|
476
|
+
when "]"
|
|
477
|
+
bracket_depth -= 1
|
|
478
|
+
when "{"
|
|
479
|
+
brace_depth += 1
|
|
480
|
+
when "}"
|
|
481
|
+
brace_depth -= 1
|
|
482
|
+
when ","
|
|
483
|
+
if paren_depth.zero? && bracket_depth.zero? && brace_depth.zero?
|
|
484
|
+
parts << current.strip
|
|
485
|
+
current = +""
|
|
486
|
+
next
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
current << char
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
parts << current.strip unless current.empty?
|
|
494
|
+
parts
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# Extracts a bare Ruby parameter name from a parameter fragment.
|
|
498
|
+
#
|
|
499
|
+
# @param parameter [String] Parameter fragment.
|
|
500
|
+
#
|
|
501
|
+
# @return [String, nil] Parameter name, or nil when invalid.
|
|
502
|
+
def extract_parameter_name(parameter)
|
|
503
|
+
match = parameter.match(/\A(?:\*\*|\*|&)?([a-z_]\w*):?\z/)
|
|
504
|
+
match && match[1]
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
# Checks whether a signature fragment already includes a parameter name.
|
|
508
|
+
#
|
|
509
|
+
# @param text [String] Signature fragment.
|
|
510
|
+
# @param name [String] Parameter name.
|
|
511
|
+
#
|
|
512
|
+
# @return [Boolean] True when the name appears as a standalone word.
|
|
513
|
+
def signature_part_mentions_name?(text, name)
|
|
514
|
+
text.match?(/(?<!\w)#{name}(?!\w)/)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# Renders a method description or an alias fallback.
|
|
518
|
+
#
|
|
519
|
+
# @param method [RDoc::AnyMethod] Method object to render.
|
|
520
|
+
# @param current_class [RDoc::Context] Class or module currently being rendered.
|
|
521
|
+
#
|
|
522
|
+
# @return [String] Rendered method description.
|
|
292
523
|
def method_description(method, current_class:)
|
|
293
|
-
text = describe(method,
|
|
524
|
+
text = describe(method, heading_level_offset: 4)
|
|
294
525
|
return text unless text.empty?
|
|
295
526
|
|
|
296
|
-
aliased_method = method.
|
|
297
|
-
return
|
|
527
|
+
aliased_method = method.is_alias_for
|
|
528
|
+
return "Not documented." unless aliased_method
|
|
298
529
|
|
|
299
530
|
"Alias for: [`#{aliased_method.name}`](#{method_link(aliased_method, current_class: current_class)})"
|
|
300
531
|
end
|
|
301
532
|
|
|
302
|
-
|
|
533
|
+
# Applies final whitespace and link normalization before writing Markdown.
|
|
534
|
+
#
|
|
535
|
+
# @param content [String] Markdown content.
|
|
536
|
+
# @param current_output_path [String] Output path for the file being written.
|
|
537
|
+
#
|
|
538
|
+
# @return [String] Final Markdown ending with one newline.
|
|
539
|
+
def finalize_markdown(content, current_output_path:)
|
|
303
540
|
output = content.lines.map(&:rstrip).join("\n")
|
|
304
|
-
output = normalize_internal_links(output, current_output_path: current_output_path)
|
|
305
|
-
output.
|
|
306
|
-
"#{output
|
|
541
|
+
output = normalize_internal_links(output, current_output_path: current_output_path)
|
|
542
|
+
output = output.sub(/\n{3,}/, "\n\n")
|
|
543
|
+
"#{output}\n"
|
|
307
544
|
end
|
|
308
545
|
|
|
546
|
+
# Increases Markdown heading levels without exceeding level six.
|
|
547
|
+
#
|
|
548
|
+
# @param markdown [String] Markdown content.
|
|
549
|
+
# @param heading_level_offset [Integer] Heading levels to add.
|
|
550
|
+
#
|
|
551
|
+
# @return [String] Markdown with shifted headings.
|
|
309
552
|
def shift_headings(markdown, heading_level_offset)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
markdown.gsub(/^(#+)(\s+)/) do
|
|
553
|
+
markdown.gsub(/^(#+)(\s)/) do
|
|
313
554
|
hashes = Regexp.last_match(1)
|
|
314
555
|
spaces = Regexp.last_match(2)
|
|
315
556
|
level = [hashes.length + heading_level_offset, 6].min
|
|
316
|
-
"#{
|
|
557
|
+
"#{"#" * level}#{spaces}"
|
|
317
558
|
end
|
|
318
559
|
end
|
|
319
560
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
section.description.to_s
|
|
328
|
-
rescue NoMethodError
|
|
329
|
-
comments = section.respond_to?(:comments) ? section.comments : nil
|
|
330
|
-
return '' if comments.nil? || comments.empty?
|
|
331
|
-
|
|
332
|
-
comments.map { |comment| comment.respond_to?(:text) ? comment.text : comment.to_s }.join("\n")
|
|
333
|
-
end
|
|
334
|
-
|
|
561
|
+
# Converts RDoc definition-list code blocks into Markdown lists.
|
|
562
|
+
#
|
|
563
|
+
# @param markdown [String] Markdown content.
|
|
564
|
+
#
|
|
565
|
+
# @return [String] Markdown with convertible blocks normalized.
|
|
335
566
|
def normalize_definition_list_code_blocks(markdown)
|
|
336
|
-
markdown.gsub(/```\n(
|
|
567
|
+
markdown.gsub(/```\n(.+?)\n```/m) do
|
|
337
568
|
body = Regexp.last_match(1)
|
|
338
569
|
converted = convert_definition_list_block(body)
|
|
339
|
-
converted.nil? ? Regexp.last_match
|
|
570
|
+
converted.nil? ? Regexp.last_match : converted
|
|
340
571
|
end
|
|
341
572
|
end
|
|
342
573
|
|
|
574
|
+
# Converts a single definition-list code block.
|
|
575
|
+
#
|
|
576
|
+
# @param body [String] Code block body.
|
|
577
|
+
#
|
|
578
|
+
# @return [String, nil] Converted Markdown, or nil when the block is not a definition list.
|
|
343
579
|
def convert_definition_list_block(body)
|
|
344
|
-
lines = body.lines
|
|
345
|
-
return nil if lines.empty?
|
|
346
|
-
return nil unless lines.any? { |line| line.strip.end_with?('::') }
|
|
580
|
+
lines = body.lines
|
|
347
581
|
return nil unless lines.all? { |line| definition_list_line?(line) }
|
|
348
582
|
|
|
349
|
-
lines.
|
|
583
|
+
lines.map do |line|
|
|
350
584
|
stripped = line.strip
|
|
351
|
-
next
|
|
352
|
-
next "#{stripped.sub(/::\z/,
|
|
585
|
+
next if stripped.empty?
|
|
586
|
+
next "#{stripped.sub(/::\z/, "")}:" if stripped.end_with?("::")
|
|
353
587
|
|
|
354
|
-
"- #{stripped.sub(
|
|
588
|
+
"- #{stripped.sub(/\A\*\s/, "")}"
|
|
355
589
|
end.join("\n")
|
|
356
590
|
end
|
|
357
591
|
|
|
592
|
+
# Checks whether a line can appear in a converted definition list.
|
|
593
|
+
#
|
|
594
|
+
# @param line [String] Markdown line.
|
|
595
|
+
#
|
|
596
|
+
# @return [Boolean] True when the line matches RDoc definition-list output.
|
|
358
597
|
def definition_list_line?(line)
|
|
359
598
|
stripped = line.strip
|
|
360
|
-
stripped.empty? || stripped.end_with?(
|
|
599
|
+
stripped.empty? || stripped.end_with?("::") || stripped.match?(/\A\*\s/)
|
|
361
600
|
end
|
|
362
601
|
|
|
602
|
+
# Builds a Markdown link target for an aliased method.
|
|
603
|
+
#
|
|
604
|
+
# @param method [RDoc::AnyMethod] Target method.
|
|
605
|
+
# @param current_class [RDoc::Context] Class or module currently being rendered.
|
|
606
|
+
#
|
|
607
|
+
# @return [String] Anchor or relative Markdown link target.
|
|
363
608
|
def method_link(method, current_class:)
|
|
364
609
|
target_parent = method.parent
|
|
365
610
|
return "##{method.aref}" if target_parent == current_class
|
|
366
611
|
|
|
367
|
-
|
|
368
|
-
current_path = output_path_for(current_class)
|
|
369
|
-
"#{relative_output_path(current_path, target_path)}##{method.aref}"
|
|
370
|
-
end
|
|
371
|
-
|
|
372
|
-
def relative_output_path(from_path, to_path)
|
|
373
|
-
from_dir = Pathname.new(from_path).dirname
|
|
374
|
-
Pathname.new(to_path).relative_path_from(from_dir).to_s
|
|
612
|
+
"#{output_path_for(target_parent)}##{method.aref}"
|
|
375
613
|
end
|
|
376
614
|
|
|
615
|
+
# Removes RDoc's generated HTML tags from verbatim pre blocks.
|
|
616
|
+
#
|
|
617
|
+
# @param html [String] RDoc HTML fragment.
|
|
618
|
+
#
|
|
619
|
+
# @return [String] HTML fragment with normalized pre blocks.
|
|
377
620
|
def normalize_rdoc_pre_blocks(html)
|
|
378
|
-
html.gsub(%r{<pre\b[^>]*>(
|
|
379
|
-
raw = Regexp.last_match(
|
|
380
|
-
text = raw
|
|
381
|
-
.gsub(%r{<br\s*/?>}i, "\n")
|
|
382
|
-
.gsub(/<[^>]+>/, '')
|
|
621
|
+
html.gsub(%r{<pre\b[^>]*>(?:.+?)</pre>}m) do
|
|
622
|
+
raw = Regexp.last_match(0)
|
|
623
|
+
text = raw.gsub(/<[^>]+>/, "")
|
|
383
624
|
"<pre>#{CGI.unescapeHTML(text)}</pre>"
|
|
384
625
|
end
|
|
385
626
|
end
|
|
386
627
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
end
|
|
394
|
-
|
|
628
|
+
# Rewrites local Markdown links relative to the current output file.
|
|
629
|
+
#
|
|
630
|
+
# @param markdown [String] Markdown content.
|
|
631
|
+
# @param current_output_path [String] Output path for the file being written.
|
|
632
|
+
#
|
|
633
|
+
# @return [String] Markdown with normalized internal links.
|
|
395
634
|
def normalize_internal_links(markdown, current_output_path:)
|
|
396
|
-
return markdown if @known_output_paths.nil? || @known_output_paths.empty?
|
|
397
|
-
|
|
398
635
|
current_dir = Pathname.new(current_output_path).dirname
|
|
399
636
|
|
|
400
|
-
markdown.gsub(%r{\]\((
|
|
637
|
+
markdown.gsub(%r{\]\(([^)]+)\)}) do
|
|
401
638
|
target = Regexp.last_match(1)
|
|
402
|
-
path = target.sub(/[?#].*\z/,
|
|
403
|
-
suffix = target[path.length..]
|
|
639
|
+
path = target.sub(/[?#].*\z/, "")
|
|
640
|
+
suffix = target[path.length..]
|
|
404
641
|
|
|
405
642
|
resolved = resolve_output_path(path, current_dir)
|
|
406
|
-
rewritten = resolved ? Pathname.new(resolved).relative_path_from(current_dir)
|
|
643
|
+
rewritten = resolved ? Pathname.new(resolved).relative_path_from(current_dir) : path
|
|
407
644
|
"](#{rewritten}#{suffix})"
|
|
408
645
|
end
|
|
409
646
|
end
|
|
410
647
|
|
|
648
|
+
# Resolves an internal link path against known generated outputs.
|
|
649
|
+
#
|
|
650
|
+
# @param path [String] Link path from Markdown content.
|
|
651
|
+
# @param current_dir [Pathname] Directory of the current output file.
|
|
652
|
+
#
|
|
653
|
+
# @return [String, nil] Resolved output path, or nil when unresolved.
|
|
411
654
|
def resolve_output_path(path, current_dir)
|
|
412
|
-
|
|
413
|
-
candidates = [normalized_path]
|
|
414
|
-
|
|
415
|
-
stripped = normalized_path.sub(%r{\A(?:files|classes|modules)/}, '')
|
|
416
|
-
candidates << stripped unless stripped == normalized_path
|
|
417
|
-
|
|
418
|
-
if @root_path_segment && stripped.start_with?("#{@root_path_segment}/")
|
|
419
|
-
candidates << stripped.delete_prefix("#{@root_path_segment}/")
|
|
420
|
-
end
|
|
421
|
-
|
|
422
|
-
candidates = candidates.flat_map { |candidate| candidate_with_parent_reductions(candidate) }.uniq
|
|
655
|
+
candidates = [path, path.delete_prefix("#{@root_path_segment}/")]
|
|
423
656
|
|
|
424
657
|
candidates.each do |candidate|
|
|
425
658
|
return candidate if @known_output_paths.include?(candidate)
|
|
@@ -433,37 +666,40 @@ class RDoc::Generator::Markdown
|
|
|
433
666
|
nil
|
|
434
667
|
end
|
|
435
668
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
reduced = reduced.delete_prefix('../')
|
|
442
|
-
reductions << reduced
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
reductions.uniq.reject(&:empty?)
|
|
446
|
-
end
|
|
447
|
-
|
|
669
|
+
# Normalizes an input filename into an output-relative source path.
|
|
670
|
+
#
|
|
671
|
+
# @param path [String] RDoc input path.
|
|
672
|
+
#
|
|
673
|
+
# @return [String] Normalized path without root prefixes.
|
|
448
674
|
def normalize_input_path_for_output(path)
|
|
449
|
-
normalized = path.
|
|
450
|
-
normalized = normalized.sub(%r{\A/}, '')
|
|
675
|
+
normalized = path.tr("\\", "/").sub(%r{\A\./}, "")
|
|
451
676
|
|
|
452
|
-
root = File.expand_path(@options.root
|
|
453
|
-
normalized = normalized.sub(%r{\A#{Regexp.escape(root)}/},
|
|
677
|
+
root = File.expand_path(@options.root.to_s)
|
|
678
|
+
normalized = normalized.sub(%r{\A#{Regexp.escape(root)}/}, "")
|
|
679
|
+
normalized = normalized.sub(%r{\A/}, "")
|
|
454
680
|
|
|
455
681
|
root_basename = File.basename(root)
|
|
456
|
-
normalized.sub(%r{\A#{Regexp.escape(root_basename)}/},
|
|
682
|
+
normalized.sub(%r{\A#{Regexp.escape(root_basename)}/}, "")
|
|
457
683
|
end
|
|
458
684
|
|
|
685
|
+
# Looks up resolved class documentation metadata.
|
|
686
|
+
#
|
|
687
|
+
# @param code_object [RDoc::Context] Class or module object.
|
|
688
|
+
#
|
|
689
|
+
# @return [Hash{Symbol => Object}] Metadata for rendering the object.
|
|
459
690
|
def class_doc_for(code_object)
|
|
460
|
-
@class_docs_by_object_id
|
|
691
|
+
@class_docs_by_object_id.fetch(code_object.object_id)
|
|
461
692
|
end
|
|
462
693
|
|
|
694
|
+
# Builds canonical class documentation metadata from RDoc objects.
|
|
695
|
+
#
|
|
696
|
+
# @param classes [Array<RDoc::Context>] Classes and modules to normalize.
|
|
697
|
+
#
|
|
698
|
+
# @return [Array<Hash{Symbol => Object}>] Metadata ordered by display name.
|
|
463
699
|
def build_class_docs(classes)
|
|
464
700
|
docs_by_name = {}
|
|
465
701
|
|
|
466
|
-
classes.each do |klass|
|
|
702
|
+
classes.select(&:display?).each do |klass|
|
|
467
703
|
display_name = normalized_full_name(klass.full_name)
|
|
468
704
|
output_path = turn_to_path(display_name)
|
|
469
705
|
legacy_path = turn_to_path(klass.full_name)
|
|
@@ -473,7 +709,7 @@ class RDoc::Generator::Markdown
|
|
|
473
709
|
klass: klass,
|
|
474
710
|
display_name: display_name,
|
|
475
711
|
output_path: output_path,
|
|
476
|
-
legacy_paths:
|
|
712
|
+
legacy_paths: [legacy_path],
|
|
477
713
|
score: score
|
|
478
714
|
}
|
|
479
715
|
|
|
@@ -481,44 +717,41 @@ class RDoc::Generator::Markdown
|
|
|
481
717
|
|
|
482
718
|
if existing.nil?
|
|
483
719
|
docs_by_name[display_name] = candidate
|
|
484
|
-
elsif candidate
|
|
485
|
-
if existing
|
|
486
|
-
candidate[:legacy_paths] |= existing
|
|
720
|
+
elsif candidate.fetch(:score) > existing.fetch(:score)
|
|
721
|
+
if existing.fetch(:score).positive?
|
|
722
|
+
candidate[:legacy_paths] |= existing.fetch(:legacy_paths)
|
|
487
723
|
end
|
|
488
724
|
docs_by_name[display_name] = candidate
|
|
489
|
-
elsif candidate
|
|
490
|
-
existing[:legacy_paths] |= candidate
|
|
725
|
+
elsif candidate.fetch(:score).positive?
|
|
726
|
+
existing[:legacy_paths] |= candidate.fetch(:legacy_paths)
|
|
491
727
|
end
|
|
492
728
|
end
|
|
493
729
|
|
|
494
730
|
docs_by_name.values
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
731
|
+
.select do |doc|
|
|
732
|
+
klass = doc.fetch(:klass)
|
|
733
|
+
|
|
734
|
+
doc.fetch(:score).positive? ||
|
|
735
|
+
(!class_has_raw_members?(klass) && !synthetic_full_name?(klass.full_name))
|
|
736
|
+
end
|
|
737
|
+
.sort_by { |doc| doc.fetch(:display_name) }
|
|
501
738
|
end
|
|
502
739
|
|
|
740
|
+
# Collapses repeated namespace segments from synthetic vendored names.
|
|
741
|
+
#
|
|
742
|
+
# @param full_name [String] Full RDoc object name.
|
|
743
|
+
#
|
|
744
|
+
# @return [String] Normalized object name.
|
|
503
745
|
def normalized_full_name(full_name)
|
|
504
|
-
normalized = full_name
|
|
746
|
+
normalized = full_name
|
|
505
747
|
|
|
506
748
|
loop do
|
|
507
|
-
break unless normalized
|
|
508
|
-
|
|
509
|
-
if normalized =~ /\A(.+?)::\1::(.+)\z/
|
|
510
|
-
normalized = "#{::Regexp.last_match(1)}::#{::Regexp.last_match(2)}"
|
|
511
|
-
next
|
|
512
|
-
end
|
|
513
|
-
|
|
514
749
|
if normalized =~ /\A([^:]+)(?:::[^:]+)+::\1::(.+)\z/
|
|
515
|
-
normalized = "#{
|
|
516
|
-
next
|
|
750
|
+
normalized = "#{Regexp.last_match(1)}::#{Regexp.last_match(2)}"
|
|
517
751
|
end
|
|
518
752
|
|
|
519
753
|
if normalized =~ /\A(.+?)::\1\z/
|
|
520
754
|
normalized = Regexp.last_match(1)
|
|
521
|
-
next
|
|
522
755
|
end
|
|
523
756
|
|
|
524
757
|
break
|
|
@@ -527,44 +760,77 @@ class RDoc::Generator::Markdown
|
|
|
527
760
|
normalized
|
|
528
761
|
end
|
|
529
762
|
|
|
763
|
+
# Scores how much owned content a class or module has.
|
|
764
|
+
#
|
|
765
|
+
# @param klass [RDoc::Context] Class or module object.
|
|
766
|
+
#
|
|
767
|
+
# @return [Integer] Content score used to choose duplicate docs.
|
|
530
768
|
def class_content_score(klass)
|
|
531
|
-
score = klass
|
|
532
|
-
score += 1 unless klass.description.
|
|
769
|
+
score = class_member_count(klass)
|
|
770
|
+
score += 1 unless klass.description.empty?
|
|
533
771
|
score
|
|
534
772
|
end
|
|
535
773
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
774
|
+
# Counts methods, constants, and attributes owned by a class or module.
|
|
775
|
+
#
|
|
776
|
+
# @param klass [RDoc::Context] Class or module object.
|
|
777
|
+
#
|
|
778
|
+
# @return [Integer] Number of owned members.
|
|
779
|
+
def class_member_count(klass)
|
|
780
|
+
klass.method_list.count(&:display?) + klass.constants.count(&:display?) + klass.attributes.count(&:display?)
|
|
781
|
+
end
|
|
539
782
|
|
|
783
|
+
# Checks whether a class or module owns any members before display filtering.
|
|
784
|
+
#
|
|
785
|
+
# @param klass [RDoc::Context] Class or module object.
|
|
786
|
+
#
|
|
787
|
+
# @return [Boolean] True when any owned member exists before display filtering.
|
|
788
|
+
def class_has_raw_members?(klass)
|
|
789
|
+
klass.method_list.any? || klass.constants.any? || klass.attributes.any?
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
# Checks whether a name appears to contain duplicated root namespaces.
|
|
793
|
+
#
|
|
794
|
+
# @param full_name [String] Full RDoc object name.
|
|
795
|
+
#
|
|
796
|
+
# @return [Boolean] True when the root namespace appears more than once.
|
|
797
|
+
def synthetic_full_name?(full_name)
|
|
798
|
+
parts = full_name.split("::")
|
|
540
799
|
root = parts.first
|
|
541
800
|
parts.count(root) > 1
|
|
542
801
|
end
|
|
543
802
|
|
|
544
|
-
|
|
545
|
-
#
|
|
546
|
-
#
|
|
547
|
-
|
|
803
|
+
# Prepares sorted objects and link lookup state for generation.
|
|
804
|
+
#
|
|
805
|
+
# @return [void]
|
|
548
806
|
def setup
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
return unless @store
|
|
807
|
+
@output_dir = @options.op_dir
|
|
808
|
+
unless @output_dir.instance_of?(String)
|
|
809
|
+
raise TypeError, "RDoc markdown output directory must be a String"
|
|
810
|
+
end
|
|
555
811
|
|
|
556
812
|
@class_docs = build_class_docs(@store.all_classes_and_modules.sort)
|
|
557
|
-
@class_docs_by_object_id = @class_docs.to_h { |doc| [doc
|
|
558
|
-
@classes = @class_docs.map { |doc| doc
|
|
813
|
+
@class_docs_by_object_id = @class_docs.to_h { |doc| [doc.fetch(:klass).object_id, doc] }
|
|
814
|
+
@classes = @class_docs.map { |doc| doc.fetch(:klass) }
|
|
559
815
|
@pages = @store.all_files.select(&:text?).select(&:display?).sort_by(&:base_name)
|
|
560
816
|
|
|
561
817
|
@known_output_paths = Set.new
|
|
562
818
|
@class_docs.each do |doc|
|
|
563
|
-
@known_output_paths << doc
|
|
564
|
-
doc
|
|
819
|
+
@known_output_paths << doc.fetch(:output_path)
|
|
820
|
+
doc.fetch(:legacy_paths).each { |path| @known_output_paths << path }
|
|
565
821
|
end
|
|
566
822
|
@pages.each { |page| @known_output_paths << page_output_path(page) }
|
|
567
823
|
|
|
568
|
-
@root_path_segment = Pathname.new(@options.root ||
|
|
824
|
+
@root_path_segment = Pathname.new(@options.root || ".").basename
|
|
569
825
|
end
|
|
570
826
|
end
|
|
827
|
+
|
|
828
|
+
# RDoc configuration extended with markdown generator options.
|
|
829
|
+
class RDoc::Options
|
|
830
|
+
prepend RDoc::Generator::Markdown::OptionsExtension
|
|
831
|
+
|
|
832
|
+
# Controls how reverse_markdown handles unknown HTML tags.
|
|
833
|
+
#
|
|
834
|
+
# @return [Symbol]
|
|
835
|
+
attr_accessor :markdown_unknown_tags
|
|
836
|
+
end
|