rdoc-markdown 0.13.2 → 0.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8d5874e33560a773358b53ee711bb485e0daecde88e51ca7dd95c54046bcede
4
- data.tar.gz: 876f010a0efd3779ca28d93dd60567b100cc20e28e5aa9fc93446b07fb9be6fe
3
+ metadata.gz: 868f92bb775186af57b719c6aa821b0e69ba3cd27988ac3e3eb549c2e10600cb
4
+ data.tar.gz: 30fcdb498301cbdddf420b12792e53d2f9f427a30f9e16d5f67527dbf0137e6e
5
5
  SHA512:
6
- metadata.gz: d3bbc5ee66dabe686f3c984a2aeffc251c77c6315646c475b45a2076d039100e034d2cc482c6d1070c8ed49dde3e12a82b2c2350bd90b51103730568ccbf611e
7
- data.tar.gz: 9d7941c568619a5c149bc844519b90bbe9d25530714a386e4a275286a95226ef308dba5e2c30f6b25b78d989eb9d81270b5a4a4486823b315eb93277d67305c5
6
+ metadata.gz: 92914fc1b0e03ecb0fb09dd2fb1bca56c6222a27beb7c9eb63882a8644567ffa7de863ece0476d688fb91cefcc9b89fcc2949722299724ea90ea3bc768dc2d2d
7
+ data.tar.gz: 61672973b235fa35b149ff24ab9dc1fd22ccf2eb0d907bad436bde63f73c76ec1f116b143d009adb39d482c59529f2a69929f5786f2e6d48bb49b38ea7b8fc06
data/.erb_lint.yml CHANGED
@@ -30,10 +30,6 @@ linters:
30
30
  - Lint/EmptyExpression
31
31
  - Lint/SelfAssignment
32
32
  - Security/Eval
33
- rubocop_config:
34
- AllCops:
35
- TargetRubyVersion: 3.2
36
- NewCops: enable
37
33
  SpaceAroundErbTag:
38
34
  enabled: true
39
35
  TrailingWhitespace:
data/AGENTS.md CHANGED
@@ -39,10 +39,11 @@ the user.
39
39
 
40
40
  You are done when all of these are green and not reporting any issues:
41
41
 
42
- ```
42
+ ```bash
43
43
  bundle exec rake test
44
- bundle exec mutant run
44
+ # don't run again if fail-fast has already passed
45
+ bundle exec mutant run --fail-fast --since origin/main
45
46
  bundle exec rake markdown:validate
46
47
  bundle exec yard-lint
47
- bundle exec erb:lint
48
+ bundle exec rake erb:lint
48
49
  ```
data/CHANGELOG.md CHANGED
@@ -1,7 +1,14 @@
1
1
  # Changelog
2
2
 
3
+
3
4
  ## Unreleased
4
5
 
6
+ ## 0.14.0
7
+
8
+ - BREAKING: Removing RDoc 7 compatibility code. Support RDoc 8 only.
9
+ - Remove extra spacing between method group headings and content.
10
+ - Reworked documentation cross-linking. Tests will fail, if cross-linked document can't be found.
11
+
5
12
  ## 0.13.2
6
13
 
7
14
  - Don't add emtpy line break instead of missing metadata
data/Gemfile CHANGED
@@ -5,16 +5,9 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in rdoc-markdown.gemspec
6
6
  gemspec
7
7
 
8
- gem "rake", "~> 13.0"
9
-
10
- gem "appraisal"
11
8
  gem "erb_lint", require: false
12
- gem "minitest", "~> 5.0"
13
9
  gem "standard"
14
10
  gem "yard-lint"
15
11
 
16
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.3")
17
- gem "mutant"
18
- gem "mutant-minitest"
19
- gem "mutex_m"
20
- end
12
+ gem "mutant"
13
+ gem "mutant-minitest"
data/Gemfile.lock CHANGED
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rdoc-markdown (0.13.2)
4
+ rdoc-markdown (0.14.0)
5
5
  csv
6
6
  erb
7
- rdoc
7
+ rdoc (>= 8.0)
8
8
  reverse_markdown
9
9
 
10
10
  GEM
@@ -29,10 +29,6 @@ GEM
29
29
  securerandom (>= 0.3)
30
30
  tzinfo (~> 2.0, >= 2.0.5)
31
31
  uri (>= 0.13.1)
32
- appraisal (2.5.0)
33
- bundler
34
- rake
35
- thor (>= 0.14.0)
36
32
  ast (2.4.3)
37
33
  base64 (0.3.0)
38
34
  better_html (2.2.0)
@@ -49,11 +45,10 @@ GEM
49
45
  connection_pool (3.0.2)
50
46
  crass (1.0.6)
51
47
  csv (3.3.5)
52
- date (3.5.1)
53
48
  diff-lcs (2.0.0)
54
49
  docile (1.4.1)
55
50
  drb (2.2.3)
56
- erb (6.0.4)
51
+ erb (6.0.6)
57
52
  erb_lint (0.9.0)
58
53
  activesupport
59
54
  better_html (>= 2.0.1)
@@ -103,9 +98,6 @@ GEM
103
98
  prettyprint
104
99
  prettyprint (0.2.0)
105
100
  prism (1.9.0)
106
- psych (5.3.1)
107
- date
108
- stringio
109
101
  racc (1.8.1)
110
102
  rails-dom-testing (2.3.0)
111
103
  activesupport (>= 5.0.0)
@@ -121,9 +113,10 @@ GEM
121
113
  prism (>= 1.6.0)
122
114
  tsort
123
115
  rdiscount (2.2.7.4)
124
- rdoc (7.2.0)
116
+ rdoc (8.0.0)
125
117
  erb
126
- psych (>= 4.0.0)
118
+ prism (>= 1.6.0)
119
+ rbs (>= 4.0.0)
127
120
  tsort
128
121
  regexp_parser (2.12.0)
129
122
  reline (0.6.3)
@@ -170,8 +163,6 @@ GEM
170
163
  standard-performance (1.9.0)
171
164
  lint_roller (~> 1.1)
172
165
  rubocop-performance (~> 1.26.0)
173
- stringio (3.2.0)
174
- thor (1.5.0)
175
166
  tsort (0.2.0)
176
167
  tzinfo (2.0.6)
177
168
  concurrent-ruby (~> 1.0)
@@ -193,7 +184,6 @@ PLATFORMS
193
184
  x86_64-linux-gnu
194
185
 
195
186
  DEPENDENCIES
196
- appraisal
197
187
  bundler (>= 2.0)
198
188
  commonmarker
199
189
  erb_lint
@@ -201,9 +191,7 @@ DEPENDENCIES
201
191
  minitest-strict (~> 1.0)
202
192
  mutant
203
193
  mutant-minitest
204
- mutex_m
205
194
  rake (~> 13.0)
206
- rbs
207
195
  rdiscount (~> 2.0)
208
196
  rdoc-markdown!
209
197
  simplecov (~> 0.22)
data/README.md CHANGED
@@ -13,6 +13,8 @@ It's a pitty that rdoc can't output a proper markdown file. Somebody has to try
13
13
 
14
14
  ## Installation
15
15
 
16
+ rdoc-markdown requires Ruby 3.3 or newer and RDoc 8 or newer.
17
+
16
18
  Install gem and add to application's Gemfile by executing:
17
19
 
18
20
  $ bundle add rdoc-markdown
@@ -40,7 +42,7 @@ rdoc-markdown uses `reverse_markdown` to convert RDoc's HTML fragments to Markdo
40
42
  rdoc --format=markdown --markdown-unknown-tags=raise
41
43
  ```
42
44
 
43
- Accepted values are `pass_through`, `drop`, `bypass`, and `raise`. The default is `pass_through`, which matches `reverse_markdown`'s default behavior.
45
+ Accepted values are `pass_through`, `drop`, `bypass`, and `raise`. The default is `pass_through`.
44
46
 
45
47
  The same setting can be stored in RDoc's `.rdoc_options` file:
46
48
 
@@ -57,7 +59,6 @@ There are decent tools that offer search through structured plain-text files. Bu
57
59
  In my personal use-case, I use SQLite. All other databases seem to have a good support for CSV imports.
58
60
 
59
61
  ## Development
60
- Biggest issue is testing this locally, but that's not as hard to do these days.
61
62
 
62
63
  ```
63
64
  gem build rdoc-markdown.gemspec
@@ -78,8 +79,6 @@ Following command should run entire testsuit:
78
79
  ```
79
80
  rake test
80
81
  ```
81
- The test suite loads `minitest-strict`, so boolean and nil assertions only pass on exact `true`, `false`, and `nil` values.
82
- Testing is not excessive, just verifies that basic functionality is operational.
83
82
 
84
83
  To validate generated markdown against GitHub Flavored Markdown and check local links/anchors:
85
84
 
@@ -96,12 +95,6 @@ This task validates:
96
95
  - vendored rails docs (Active Support, Active Record, Action Pack, Railties slices).
97
96
 
98
97
  Validation artifacts are written to `tmp/markdown-validate/` inside this repository.
99
- To fail on every unresolved vendor local link/anchor, run with:
100
-
101
- ```
102
- MARKDOWN_VALIDATE_STRICT_VENDOR=1 rake markdown:validate
103
- ```
104
-
105
98
  This task is also executed in CI.
106
99
 
107
100
  To lint markdown ERB templates:
@@ -110,7 +103,7 @@ To lint markdown ERB templates:
110
103
  bundle exec rake erb:lint
111
104
  ```
112
105
 
113
- ### Integration harness: minitest
106
+ ### Integration: minitest
114
107
  To run the integration harness against minitest (aligned with docs.seattlerb.org/minitest):
115
108
 
116
109
  ```
@@ -120,7 +113,7 @@ bundle exec rake test
120
113
 
121
114
  The harness validates markdown formatting quality, generated page files, class/module paths, and search index output.
122
115
 
123
- ### Integration harness: rails
116
+ ### Integration: rails
124
117
  To run the rails harness (focused on Active Support + Active Record API docs with sdoc-like structure):
125
118
 
126
119
  ```
data/Rakefile CHANGED
@@ -31,7 +31,6 @@ end
31
31
  namespace :markdown do
32
32
  desc "Validate generated markdown as GitHub Flavored Markdown"
33
33
  task :validate do
34
- strict_vendor_links = ENV["MARKDOWN_VALIDATE_STRICT_VENDOR"] == "1"
35
34
  validation_root = File.expand_path("tmp/markdown-validate", __dir__)
36
35
  FileUtils.rm_rf(validation_root)
37
36
  FileUtils.mkdir_p(validation_root)
@@ -57,18 +56,14 @@ namespace :markdown do
57
56
  output: minitest_output,
58
57
  files: minitest_docs_files(minitest_root)
59
58
  )
60
- minitest_validator = MarkdownValidator.new(minitest_output, strict_links: strict_vendor_links)
61
- minitest_count = minitest_validator.validate!
59
+ minitest_count = MarkdownValidator.new(minitest_output).validate!
62
60
  puts "Validated #{minitest_count} markdown files in #{minitest_output}"
63
- puts "Skipped #{minitest_validator.unresolved_links} unresolved local links in vendored minitest docs"
64
61
 
65
62
  Rake::Task["vendor:setup:jekyll_seo_tag"].invoke
66
63
  jekyll_seo_tag_output = File.join(validation_root, JEKYLL_SEO_TAG_NAME)
67
64
  generate_jekyll_seo_tag_docs(output: jekyll_seo_tag_output)
68
- jekyll_seo_tag_validator = MarkdownValidator.new(jekyll_seo_tag_output, strict_links: strict_vendor_links)
69
- jekyll_seo_tag_count = jekyll_seo_tag_validator.validate!
65
+ jekyll_seo_tag_count = MarkdownValidator.new(jekyll_seo_tag_output).validate!
70
66
  puts "Validated #{jekyll_seo_tag_count} markdown files in #{jekyll_seo_tag_output}"
71
- puts "Skipped #{jekyll_seo_tag_validator.unresolved_links} unresolved local links in vendored jekyll-seo-tag docs"
72
67
 
73
68
  Rake::Task["vendor:setup:rails"].invoke
74
69
  rails_root = File.expand_path("vendor/rails", __dir__)
@@ -79,10 +74,8 @@ namespace :markdown do
79
74
  output: rails_output,
80
75
  files: rails_validation_files(rails_root)
81
76
  )
82
- rails_validator = MarkdownValidator.new(rails_output, strict_links: strict_vendor_links)
83
- rails_count = rails_validator.validate!
77
+ rails_count = MarkdownValidator.new(rails_output).validate!
84
78
  puts "Validated #{rails_count} markdown files in #{rails_output}"
85
- puts "Skipped #{rails_validator.unresolved_links} unresolved local links in vendored rails docs"
86
79
 
87
80
  puts "Markdown validation artifacts written to #{validation_root}"
88
81
  end
@@ -181,15 +174,10 @@ def rails_validation_files(root)
181
174
  files.concat(Dir[File.join(root, "actionpack/lib/**/*.rb")])
182
175
  files.concat(Dir[File.join(root, "railties/lib/**/*.rb")])
183
176
 
184
- [
185
- "activerecord/README.rdoc",
186
- "actionpack/README.rdoc",
187
- "railties/README.rdoc",
188
- "railties/RDOC_MAIN.md"
189
- ].each do |relative_path|
190
- file = File.join(root, relative_path)
191
- files << file if File.file?(file)
192
- end
177
+ files.concat(Dir[File.join(root, "{active*,action*,railties}/README.{rdoc,md,markdown}")])
178
+
179
+ main_page = File.join(root, "railties/RDOC_MAIN.md")
180
+ files << main_page if File.file?(main_page)
193
181
 
194
182
  files.uniq
195
183
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Prevents RDoc from linking to code objects omitted from Markdown output.
4
+ module RDoc::Generator::Markdown::CrossrefExtension
5
+ # Cross-reference resolver scoped to this formatter instance.
6
+ attr_writer :markdown_cross_reference
7
+
8
+ # Object IDs emitted by the active Markdown generator.
9
+ attr_writer :markdown_output_object_ids
10
+
11
+ # Renders a cross-reference only when its owning object is emitted.
12
+ #
13
+ # @param name [String, nil] Cross-reference target.
14
+ # @param text [String] Visible link text.
15
+ # @param code [Boolean] Whether to format code objects as code.
16
+ # @param rdoc_ref [Boolean] Whether the target uses the rdoc-ref scheme.
17
+ #
18
+ # @return [String] HTML link or unlinked text.
19
+ def link(name, text, code = true, rdoc_ref: false)
20
+ return super unless @markdown_cross_reference
21
+
22
+ ref = @markdown_cross_reference.resolve(name) if name
23
+ return super unless RDoc::CodeObject === ref
24
+
25
+ context = ref
26
+ context = context.parent until RDoc::ClassModule === context || RDoc::TopLevel === context
27
+ return super if @markdown_output_object_ids.include?(context.object_id)
28
+
29
+ return text if RDoc::TopLevel === ref || !code
30
+
31
+ "<code>#{text}</code>"
32
+ end
33
+ end
@@ -1,19 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
  # shareable_constant_value: literal
3
3
 
4
- gem "rdoc"
5
-
6
4
  require "erb"
7
5
  require "reverse_markdown"
8
6
  require "csv"
9
7
  require "optparse"
10
- require_relative "markdown/rdoc_markdown_pre"
11
8
 
12
9
  # Generates Markdown output and a CSV search index from an RDoc store.
13
10
  class RDoc::Generator::Markdown
14
11
  RDoc::RDoc.add_generator self
15
12
 
16
- require_relative "markdown/rbs_signature_index"
13
+ require_relative "markdown/crossref"
17
14
 
18
15
  # Supported reverse_markdown unknown-tag modes.
19
16
  MARKDOWN_UNKNOWN_TAGS = %i[pass_through drop bypass raise]
@@ -109,11 +106,6 @@ class RDoc::Generator::Markdown
109
106
  # @return [RDoc::Store]
110
107
  attr_reader :store
111
108
 
112
- # Working directory captured when the generator is created.
113
- #
114
- # @return [Pathname]
115
- attr_reader :base_dir
116
-
117
109
  # Classes and modules selected for output.
118
110
  #
119
111
  # @return [Array<RDoc::Context>, nil]
@@ -124,15 +116,6 @@ class RDoc::Generator::Markdown
124
116
  # @return [Array<RDoc::TopLevel>, nil]
125
117
  attr_reader :pages
126
118
 
127
- # Required by RDoc's generator interface; markdown output has no class subdirectory.
128
- #
129
- # @return [nil]
130
- def class_dir
131
- end
132
-
133
- # this alias is required for rdoc to work
134
- alias_method :file_dir, :class_dir
135
-
136
119
  # Creates a generator for an RDoc store and options.
137
120
  #
138
121
  # @param store [RDoc::Store] Source documentation store.
@@ -142,7 +125,6 @@ class RDoc::Generator::Markdown
142
125
  @options = rdoc_options
143
126
  @markdown_unknown_tags = self.class.validate_markdown_unknown_tags(rdoc_options.markdown_unknown_tags)
144
127
 
145
- @base_dir = Pathname.pwd
146
128
  @expanded_root = Pathname(@options.root.to_s).expand_path
147
129
  end
148
130
 
@@ -254,16 +236,9 @@ class RDoc::Generator::Markdown
254
236
  @classes.each do |klass|
255
237
  content = template.result(binding)
256
238
  output_path = output_path_for(klass)
257
-
258
- ([output_path] | legacy_paths_for(klass)).each do |destination_path|
259
- out_file = Pathname.new("#{output_dir}/#{destination_path}")
260
- out_file.dirname.mkpath
261
- File.write(out_file, finalize_markdown(
262
- content,
263
- canonical_output_path: output_path,
264
- current_output_path: destination_path
265
- ))
266
- end
239
+ out_file = Pathname.new("#{output_dir}/#{output_path}")
240
+ out_file.dirname.mkpath
241
+ File.write(out_file, finalize_markdown(content, current_output_path: output_path))
267
242
  end
268
243
  end
269
244
 
@@ -275,10 +250,9 @@ class RDoc::Generator::Markdown
275
250
  out_file = Pathname.new("#{output_dir}/#{page_output_path(page)}")
276
251
  out_file.dirname.mkpath
277
252
 
278
- content = markdownify(page.description)
253
+ content = markdownify(render_description(page))
279
254
  File.write(out_file, finalize_markdown(
280
255
  content,
281
- canonical_output_path: page_output_path(page),
282
256
  current_output_path: page_output_path(page)
283
257
  ))
284
258
  end
@@ -346,15 +320,6 @@ class RDoc::Generator::Markdown
346
320
  class_doc_for(code_object).fetch(:output_path)
347
321
  end
348
322
 
349
- # Returns compatibility paths that should mirror the canonical output.
350
- #
351
- # @param code_object [RDoc::Context] Class or module object.
352
- #
353
- # @return [Array<String>] Legacy Markdown paths.
354
- def legacy_paths_for(code_object)
355
- class_doc_for(code_object).fetch(:legacy_paths)
356
- end
357
-
358
323
  # Renders a class or module reference, linking it when its documentation is emitted.
359
324
  #
360
325
  # @param target [RDoc::ClassModule, String] Resolved RDoc object or unresolved name.
@@ -391,28 +356,61 @@ class RDoc::Generator::Markdown
391
356
  # - bypass - Ignore the unknown tag but try to convert its content
392
357
  # - raise - Raise an error to let you know
393
358
 
394
- md = ReverseMarkdown.convert(input, github_flavored: true, unknown_tags: @markdown_unknown_tags).dup
395
-
396
- # Flatten headings whose visible text is wrapped in a self-link.
397
- md.gsub!(/^(#+)\s\[([^\]]+)\]\((?:#[^)]+)\)$/) { "#{Regexp.last_match(1)} #{Regexp.last_match(2)}" }
359
+ fragment = Nokogiri::HTML.fragment(input)
398
360
 
399
- # Replace .html to .md extension in all local markdown links.
400
- md.gsub!(%r{\]\((?!https?://|mailto:|#)([^)]+?)\.html((?:[?#][^)]+)?)\)}i) do
401
- "](#{Regexp.last_match(1)}.md#{Regexp.last_match(2)})"
361
+ fragment.css("pre").each do |pre|
362
+ language = pre["class"].to_s[/\A(?!highlight\z)[A-Za-z][A-Za-z0-9_+-]*\z/]
363
+ pre["class"] = "brush: #{language};" if language
364
+ pre.inner_html = pre.text
402
365
  end
403
366
 
404
- # Turn site-root markdown links into relative links.
405
- md.gsub!(%r{\]\(/([^)]+?\.md(?:[?#][^)]+)?)\)}) { "](#{Regexp.last_match(1)})" }
367
+ fragment.css("h1, h2, h3, h4, h5, h6").each do |heading|
368
+ link = heading.xpath("./a[starts-with(@href, '#') and string-length(@href) > 1]").find do |anchor|
369
+ anchor.text.match?(/\S/) &&
370
+ anchor.xpath("preceding-sibling::node()").none? { |sibling| sibling.text.match?(/\S/) }
371
+ end
372
+ next unless link
373
+
374
+ id = link["href"].delete_prefix("#")
375
+ link.replace(link.children)
376
+ next if id == RDoc::Text.to_anchor(heading.text)
377
+
378
+ heading.add_child(fragment.document.create_element("span", "class" => "legacy-anchor", "id" => id))
379
+ end
406
380
 
407
- # Strip RDoc structural path segments from internal links.
408
- md.gsub!(%r{\]\(((?:\.\./)*)files/([^)]+?\.md(?:[?#][^)]+)?)\)}) do
409
- "](#{Regexp.last_match(1)}#{Regexp.last_match(2)})"
381
+ anchor_aliases = fragment.css("span.legacy-anchor[id]").map.with_index do |span, index|
382
+ token = "RDocMarkdownAnchor#{index}End"
383
+ id = span["id"]
384
+ span.replace(token)
385
+ [token, id]
410
386
  end
411
- md.gsub!(%r{\]\(((?:\.\./)*)classes/([^)]+?\.md(?:[?#][^)]+)?)\)}) do
412
- "](#{Regexp.last_match(1)}#{Regexp.last_match(2)})"
387
+
388
+ fragment.css("a").each do |link|
389
+ receiver = link.text
390
+ href = link["href"].to_s
391
+
392
+ if receiver.match?(/\A(?:[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*|[a-z_][A-Za-z0-9_]*)\z/) &&
393
+ href.match?(/\A(?::.+|".+")\z/)
394
+ link.replace(fragment.document.create_element("code") { |code| code.content = "#{receiver}[#{href}]" })
395
+ elsif href.start_with?("www.")
396
+ link["href"] = "https://#{href}"
397
+ elsif !href.match?(/\A(?:https?:\/\/|mailto:|#)/i)
398
+ href = href.sub(/\.html(?=[?#]|\z)/i, ".md")
399
+ href = href.sub(%r{\A/(?=.+\.md(?:[?#]|\z))}, "")
400
+ href = href.sub(%r{\A((?:\.\./)*)(?:files|classes|modules)/(?=.+\.md(?:[?#]|\z))}, '\1')
401
+ link["href"] = href
402
+ end
413
403
  end
414
- md.gsub!(%r{\]\(((?:\.\./)*)modules/([^)]+?\.md(?:[?#][^)]+)?)\)}) do
415
- "](#{Regexp.last_match(1)}#{Regexp.last_match(2)})"
404
+
405
+ md = ReverseMarkdown.convert(
406
+ fragment,
407
+ github_flavored: true,
408
+ unknown_tags: @markdown_unknown_tags
409
+ ).dup
410
+ anchor_aliases.each do |token, id|
411
+ anchor = %(<a id="#{id}"></a>)
412
+ md.gsub!("#{token}\n\n#", "#{anchor}\n#")
413
+ md.gsub!(token, anchor)
416
414
  end
417
415
 
418
416
  normalize_definition_list_code_blocks(md).rstrip
@@ -430,6 +428,25 @@ class RDoc::Generator::Markdown
430
428
  %(<a id="#{id}"></a>)
431
429
  end
432
430
 
431
+ # Renders an RDoc description with links limited to emitted objects.
432
+ #
433
+ # @param code_object [RDoc::CodeObject, RDoc::Context::Section] Object whose description is rendered.
434
+ #
435
+ # @return [String, nil] HTML description, or nil for an empty section.
436
+ def render_description(code_object)
437
+ return if RDoc::Context::Section === code_object && code_object.comments.empty?
438
+
439
+ formatter = code_object.formatter
440
+ formatter.extend(CrossrefExtension)
441
+ begin
442
+ formatter.markdown_cross_reference = RDoc::CrossReference.new(formatter.context)
443
+ formatter.markdown_output_object_ids = @markdown_output_object_ids
444
+ code_object.description
445
+ ensure
446
+ formatter.markdown_cross_reference = nil
447
+ end
448
+ end
449
+
433
450
  # Renders an RDoc object's description as Markdown.
434
451
  #
435
452
  # @param code_object [RDoc::CodeObject] Object with an RDoc description.
@@ -438,7 +455,7 @@ class RDoc::Generator::Markdown
438
455
  #
439
456
  # @return [String] Rendered description or fallback text.
440
457
  def describe(code_object, fallback: nil, heading_level_offset: 0)
441
- description = code_object.description
458
+ description = render_description(code_object)
442
459
  return fallback.to_s if description.empty?
443
460
 
444
461
  shift_headings(markdownify(description), heading_level_offset)
@@ -451,7 +468,7 @@ class RDoc::Generator::Markdown
451
468
  #
452
469
  # @return [String] Rendered section description.
453
470
  def section_description(section, heading_level_offset:)
454
- shift_headings(markdownify(section.description), heading_level_offset)
471
+ shift_headings(markdownify(render_description(section)), heading_level_offset)
455
472
  end
456
473
 
457
474
  # Builds the visible method signature used in headings.
@@ -460,8 +477,7 @@ class RDoc::Generator::Markdown
460
477
  #
461
478
  # @return [String] Normalized method signature.
462
479
  def method_signature(method)
463
- signatures = @rbs_method_signatures.signature_lines_for(method)
464
- signatures = [method.param_seq] if signatures.empty?
480
+ signatures = method.type_signature_lines || @store.rbs_signature_for(method) || [method.param_seq]
465
481
 
466
482
  signatures = signatures.filter_map do |signature|
467
483
  next unless signature&.match?(/\S/)
@@ -617,17 +633,12 @@ class RDoc::Generator::Markdown
617
633
  # Applies final whitespace and link normalization before writing Markdown.
618
634
  #
619
635
  # @param content [String] Markdown content.
620
- # @param canonical_output_path [String] Canonical output path used to resolve links.
621
636
  # @param current_output_path [String] Output path for the file being written.
622
637
  #
623
638
  # @return [String] Final Markdown ending with one newline.
624
- def finalize_markdown(content, canonical_output_path:, current_output_path:)
639
+ def finalize_markdown(content, current_output_path:)
625
640
  output = content.lines.map(&:rstrip).join("\n")
626
- output = normalize_internal_links(
627
- output,
628
- canonical_output_path: canonical_output_path,
629
- current_output_path: current_output_path
630
- )
641
+ output = normalize_internal_links(output, current_output_path: current_output_path)
631
642
  output = output.sub(/\n{3,}/, "\n\n")
632
643
  "#{output}\n"
633
644
  end
@@ -704,12 +715,10 @@ class RDoc::Generator::Markdown
704
715
  # Rewrites local Markdown links relative to the current output file.
705
716
  #
706
717
  # @param markdown [String] Markdown content.
707
- # @param canonical_output_path [String] Canonical output path used to resolve links.
708
718
  # @param current_output_path [String] Output path for the file being written.
709
719
  #
710
720
  # @return [String] Markdown with normalized internal links.
711
- def normalize_internal_links(markdown, canonical_output_path:, current_output_path:)
712
- canonical_dir = Pathname.new(canonical_output_path).dirname
721
+ def normalize_internal_links(markdown, current_output_path:)
713
722
  current_dir = Pathname.new(current_output_path).dirname
714
723
 
715
724
  markdown.gsub(%r{\]\(([^)]+)\)}) do
@@ -717,7 +726,7 @@ class RDoc::Generator::Markdown
717
726
  path = target.sub(/[?#].*\z/, "")
718
727
  suffix = target[path.length..]
719
728
 
720
- resolved = resolve_output_path(path, canonical_dir)
729
+ resolved = resolve_output_path(path, current_dir)
721
730
  rewritten = resolved ? Pathname.new(resolved).relative_path_from(current_dir) : path
722
731
  "](#{rewritten}#{suffix})"
723
732
  end
@@ -780,14 +789,12 @@ class RDoc::Generator::Markdown
780
789
  classes.select(&:display?).each do |klass|
781
790
  display_name = normalized_full_name(klass.full_name)
782
791
  output_path = turn_to_path(display_name)
783
- legacy_path = turn_to_path(klass.full_name)
784
792
  score = class_content_score(klass)
785
793
 
786
794
  candidate = {
787
795
  klass: klass,
788
796
  display_name: display_name,
789
797
  output_path: output_path,
790
- legacy_paths: [legacy_path],
791
798
  score: score
792
799
  }
793
800
 
@@ -796,12 +803,7 @@ class RDoc::Generator::Markdown
796
803
  if existing.nil?
797
804
  docs_by_name[display_name] = candidate
798
805
  elsif candidate.fetch(:score) > existing.fetch(:score)
799
- if existing.fetch(:score).positive?
800
- candidate[:legacy_paths] |= existing.fetch(:legacy_paths)
801
- end
802
806
  docs_by_name[display_name] = candidate
803
- elsif candidate.fetch(:score).positive?
804
- existing[:legacy_paths] |= candidate.fetch(:legacy_paths)
805
807
  end
806
808
  end
807
809
 
@@ -892,13 +894,8 @@ class RDoc::Generator::Markdown
892
894
  @class_docs_by_name = @class_docs.to_h { |doc| [doc.fetch(:display_name), doc] }
893
895
  @classes = @class_docs.map { |doc| doc.fetch(:klass) }
894
896
  @pages = @store.all_files.select(&:text?).select(&:display?).sort_by(&:base_name)
895
- @rbs_method_signatures = RbsSignatureIndex.build(Array(@options.files), @base_dir, @store)
896
-
897
- @known_output_paths = Set.new
898
- @class_docs.each do |doc|
899
- @known_output_paths << doc.fetch(:output_path)
900
- doc.fetch(:legacy_paths).each { |path| @known_output_paths << path }
901
- end
897
+ @markdown_output_object_ids = (@classes + @pages).map(&:object_id)
898
+ @known_output_paths = @class_docs.map { |doc| doc.fetch(:output_path) }
902
899
  @pages.each { |page| @known_output_paths << page_output_path(page) }
903
900
 
904
901
  @root_path_segment = Pathname.new(@options.root || ".").basename
@@ -5,6 +5,6 @@ module Rdoc
5
5
  # @private
6
6
  module Markdown
7
7
  # @private
8
- VERSION = "0.13.2"
8
+ VERSION = "0.14.0"
9
9
  end
10
10
  end
@@ -58,7 +58,6 @@
58
58
 
59
59
  ### <%= visibility.capitalize %> <%= type.capitalize %> Methods
60
60
  <% methods.each do |method| -%>
61
-
62
61
  #### `<%= method.name %><%= method_signature(method) %>`<%= anchor(method.aref) %>
63
62
  <%= method_description(method, current_class: klass) %>
64
63
  <% end -%>
data/mutant.yml CHANGED
@@ -13,4 +13,3 @@ requires:
13
13
  matcher:
14
14
  subjects:
15
15
  - RDoc::Generator::Markdown*
16
- - ReverseMarkdown::Converters::RDocMarkdownPre*
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.summary = "RDoc plugin to generate markdown documentation "
13
13
  spec.description = "RDoc plugin to generate markdown documentation and search index as sqlite database for entire content."
14
14
  spec.homepage = "https://poshtui.com"
15
- spec.required_ruby_version = ">= 2.7.0"
15
+ spec.required_ruby_version = ">= 3.3.0"
16
16
 
17
17
  # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
18
18
 
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
33
33
 
34
34
  spec.add_dependency "csv"
35
35
  spec.add_dependency "erb"
36
- spec.add_dependency "rdoc"
36
+ spec.add_dependency "rdoc", ">= 8.0"
37
37
  spec.add_dependency "reverse_markdown"
38
38
 
39
39
  spec.add_development_dependency "bundler", ">= 2.0"
@@ -41,7 +41,6 @@ Gem::Specification.new do |spec|
41
41
  spec.add_development_dependency "minitest", "~> 5.0"
42
42
  spec.add_development_dependency "minitest-strict", "~> 1.0"
43
43
  spec.add_development_dependency "rake", "~> 13.0"
44
- spec.add_development_dependency "rbs"
45
44
  spec.add_development_dependency "rdiscount", "~> 2.0"
46
45
  spec.add_development_dependency "simplecov", "~> 0.22"
47
46
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdoc-markdown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.2
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stanislav (Stas) Katkov
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: '0'
46
+ version: '8.0'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: '0'
53
+ version: '8.0'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: reverse_markdown
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -135,20 +135,6 @@ dependencies:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
137
  version: '13.0'
138
- - !ruby/object:Gem::Dependency
139
- name: rbs
140
- requirement: !ruby/object:Gem::Requirement
141
- requirements:
142
- - - ">="
143
- - !ruby/object:Gem::Version
144
- version: '0'
145
- type: :development
146
- prerelease: false
147
- version_requirements: !ruby/object:Gem::Requirement
148
- requirements:
149
- - - ">="
150
- - !ruby/object:Gem::Version
151
- version: '0'
152
138
  - !ruby/object:Gem::Dependency
153
139
  name: rdiscount
154
140
  requirement: !ruby/object:Gem::Requirement
@@ -192,7 +178,6 @@ files:
192
178
  - ".standard.yml"
193
179
  - ".yard-lint.yml"
194
180
  - AGENTS.md
195
- - Appraisals
196
181
  - CHANGELOG.md
197
182
  - CODE_OF_CONDUCT.md
198
183
  - Gemfile
@@ -217,14 +202,10 @@ files:
217
202
  - example/jekyll-seo-tag/Liquid.md
218
203
  - example/jekyll-seo-tag/Liquid/Tag.md
219
204
  - example/jekyll-seo-tag/index.csv
220
- - gemfiles/rdoc_7.gemfile
221
- - gemfiles/rdoc_8.gemfile
222
- - gemfiles/rdoc_head.gemfile
223
205
  - lib/markdown.rb
224
206
  - lib/rdoc/discover.rb
225
207
  - lib/rdoc/generator/markdown.rb
226
- - lib/rdoc/generator/markdown/rbs_signature_index.rb
227
- - lib/rdoc/generator/markdown/rdoc_markdown_pre.rb
208
+ - lib/rdoc/generator/markdown/crossref.rb
228
209
  - lib/rdoc/markdown/version.rb
229
210
  - lib/templates/classfile.md.erb
230
211
  - mutant.yml
@@ -244,7 +225,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
244
225
  requirements:
245
226
  - - ">="
246
227
  - !ruby/object:Gem::Version
247
- version: 2.7.0
228
+ version: 3.3.0
248
229
  required_rubygems_version: !ruby/object:Gem::Requirement
249
230
  requirements:
250
231
  - - ">="
data/Appraisals DELETED
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- appraise "rdoc_7" do
4
- gem "rdoc", "~> 7.0"
5
- end
6
-
7
- appraise "rdoc_8" do
8
- gem "rdoc", "~> 8.0"
9
- end
10
-
11
- appraise "rdoc_head" do
12
- gem "rdoc", git: "https://github.com/ruby/rdoc.git", branch: "master"
13
- end
@@ -1,20 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rake", "~> 13.0"
6
- gem "appraisal"
7
- gem "erb_lint", require: false
8
- gem "minitest", "~> 5.0"
9
- gem "standard"
10
- gem "yard-lint"
11
-
12
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.3")
13
- gem "mutant"
14
- gem "mutant-minitest"
15
- gem "mutex_m"
16
- end
17
-
18
- gem "rdoc", "~> 7.0"
19
-
20
- gemspec path: "../"
@@ -1,20 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rake", "~> 13.0"
6
- gem "appraisal"
7
- gem "erb_lint", require: false
8
- gem "minitest", "~> 5.0"
9
- gem "standard"
10
- gem "yard-lint"
11
-
12
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.3")
13
- gem "mutant"
14
- gem "mutant-minitest"
15
- gem "mutex_m"
16
- end
17
-
18
- gem "rdoc", "~> 8.0"
19
-
20
- gemspec path: "../"
@@ -1,20 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "rake", "~> 13.0"
6
- gem "appraisal"
7
- gem "erb_lint", require: false
8
- gem "minitest", "~> 5.0"
9
- gem "standard"
10
- gem "yard-lint"
11
-
12
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.3")
13
- gem "mutant"
14
- gem "mutant-minitest"
15
- gem "mutex_m"
16
- end
17
-
18
- gem "rdoc", git: "https://github.com/ruby/rdoc.git", branch: "master"
19
-
20
- gemspec path: "../"
@@ -1,187 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Optional lookup of method signatures parsed from RBS files.
4
- class RDoc::Generator::Markdown::RbsSignatureIndex
5
- # Builds a signature index from RBS files included in an RDoc run.
6
- #
7
- # @param files [Array<String>] Input files passed to RDoc.
8
- # @param base_dir [String, Pathname, nil] Directory where RDoc started.
9
- # @param store [RDoc::Store, nil] Store containing Ruby code objects and RDoc 8 sidecar signatures.
10
- #
11
- # @return [RDoc::Generator::Markdown::RbsSignatureIndex] Signature index.
12
- def self.build(files, base_dir = nil, store = nil)
13
- rbs_files = files.select { |file| File.extname(file) == ".rbs" }
14
- new(signatures_from_store(store).merge(signatures_from(rbs_files, base_dir)))
15
- end
16
-
17
- # Builds signatures by reusing RBS's own RDoc parser.
18
- #
19
- # @param files [Array<String>] RBS files to parse.
20
- # @param base_dir [String, Pathname, nil] Directory where RDoc started.
21
- #
22
- # @return [Hash{Array => Array<String>}] Signature lookup keyed by class and method.
23
- def self.signatures_from(files, base_dir)
24
- files.each_with_object({}) do |file, signatures|
25
- parsed_classes(file, base_dir).each do |klass|
26
- klass.method_list.each do |method|
27
- add_method_signature_lines(signatures, klass: klass, method: method, lines: rbs_signature_lines_from_method(method))
28
- end
29
- end
30
- end
31
- end
32
-
33
- # Builds signatures already merged into an RDoc store.
34
- #
35
- # @param store [RDoc::Store, nil] Store containing Ruby code objects and RDoc 8 sidecar signatures.
36
- #
37
- # @return [Hash{Array => Array<String>}] Signature lookup keyed by class and method.
38
- def self.signatures_from_store(store)
39
- return {} unless store
40
-
41
- store.all_classes_and_modules.each_with_object({}) do |klass, signatures|
42
- klass.method_list.each do |method|
43
- add_method_signature_lines(signatures, klass: klass, method: method, lines: store_signature_lines_from_method(method, store))
44
- end
45
- end
46
- end
47
-
48
- # Parses one RBS file into RDoc class/module objects.
49
- #
50
- # @param file [String] RBS file path.
51
- # @param base_dir [String, Pathname, nil] Directory where RDoc started.
52
- #
53
- # @return [Array<RDoc::Context>] Classes and modules parsed from RBS.
54
- def self.parsed_classes(file, base_dir)
55
- file_path = rbs_file_path(file, base_dir)
56
- store = RDoc::Store.new(RDoc::Options.new)
57
- top_level = store.add_file(file_path)
58
- parser = RDoc::Parser.for(top_level, File.read(file_path), store.options, nil)
59
- parser.scan
60
- store.all_classes_and_modules
61
- end
62
-
63
- # Resolves an RBS file path against the directory RDoc started from.
64
- # RDoc changes into the output directory before generators run.
65
- #
66
- # @param file [String] RBS file path passed to RDoc.
67
- # @param base_dir [String, Pathname, nil] Directory where RDoc started.
68
- #
69
- # @return [String] Absolute or already absolute RBS file path.
70
- def self.rbs_file_path(file, base_dir)
71
- Pathname.new(file).expand_path(base_dir).to_s
72
- end
73
-
74
- # Adds resolved method signature lines to the lookup.
75
- #
76
- # @param signatures [Hash{Array => Array<String>}] Signature lookup being populated.
77
- # @param klass [RDoc::Context] Method owner.
78
- # @param method [RDoc::AnyMethod] Method object.
79
- # @param lines [Array<String>] Signature text lines.
80
- #
81
- # @return [void]
82
- def self.add_method_signature_lines(signatures, klass:, method:, lines:)
83
- return if lines.empty?
84
-
85
- signatures[[klass.full_name, method.singleton, method.name]] = lines
86
-
87
- return unless method.name == "initialize" && !method.singleton
88
-
89
- signatures[[klass.full_name, true, "new"]] = lines
90
- end
91
-
92
- # Extracts explicit RBS method signature lines from RDoc parser output.
93
- # RDoc 7 exposes parsed RBS signatures through `param_seq`; RDoc 8 stores
94
- # them as `type_signature_lines`.
95
- #
96
- # @param method [RDoc::AnyMethod] Method parsed by RDoc's RBS parser.
97
- #
98
- # @return [Array<String>] RBS signature lines.
99
- def self.rbs_signature_lines_from_method(method)
100
- lines = method_type_signature_lines(method)
101
- lines = method.param_seq if lines.empty?
102
- nonblank_lines(lines)
103
- end
104
-
105
- # Extracts RBS method signature lines already merged into a Ruby store.
106
- # Unlike explicit RBS parser output, ordinary Ruby `param_seq` is not a type
107
- # signature and must not populate the index.
108
- #
109
- # @param method [RDoc::AnyMethod] Method object from the Ruby store.
110
- # @param store [RDoc::Store] Store with sidecar RBS signatures.
111
- #
112
- # @return [Array<String>] RBS signature lines.
113
- def self.store_signature_lines_from_method(method, store)
114
- lines = method_type_signature_lines(method)
115
- lines = store_type_signature_lines(method, store) if lines.empty?
116
- lines
117
- end
118
-
119
- # Extracts RDoc 8 inline type signatures from a method.
120
- #
121
- # @param method [RDoc::AnyMethod] Method object.
122
- #
123
- # @return [Array<String>] Signature lines.
124
- def self.method_type_signature_lines(method)
125
- return [] unless method.respond_to?(:type_signature_lines)
126
-
127
- nonblank_lines(method.type_signature_lines)
128
- end
129
-
130
- # Extracts RDoc 8 sidecar signatures from a store.
131
- #
132
- # @param method [RDoc::AnyMethod] Method object.
133
- # @param store [RDoc::Store, nil] Optional RDoc store with sidecar RBS signatures.
134
- #
135
- # @return [Array<String>] Signature lines.
136
- def self.store_type_signature_lines(method, store)
137
- return [] unless store.respond_to?(:rbs_signature_for)
138
-
139
- nonblank_lines(store.rbs_signature_for(method))
140
- end
141
-
142
- # Normalizes a signature-line value to non-blank strings.
143
- #
144
- # @param lines [Array<String>, String, nil] Signature text lines.
145
- #
146
- # @return [Array<String>] Non-blank signature lines.
147
- def self.nonblank_lines(lines)
148
- Array(lines).select { |line| line&.match?(/\S/) }
149
- end
150
-
151
- # Creates an immutable signature index.
152
- #
153
- # @param signatures [Hash{Array => Array<String>}] Signature lookup.
154
- #
155
- # @return [void]
156
- def initialize(signatures)
157
- @signatures = signatures
158
- end
159
-
160
- # Looks up the RBS signature lines for an RDoc method.
161
- #
162
- # @param method [RDoc::AnyMethod] Method object to render.
163
- #
164
- # @return [Array<String>] RBS method type lines when available.
165
- def signature_lines_for(method)
166
- @signatures.fetch([method.parent.full_name, method.singleton, method.name], [])
167
- end
168
-
169
- # Checks whether any RBS signatures were parsed.
170
- #
171
- # @return [Boolean] True when type signatures are available.
172
- def any?
173
- @signatures.any?
174
- end
175
-
176
- private_class_method \
177
- :signatures_from,
178
- :signatures_from_store,
179
- :parsed_classes,
180
- :rbs_file_path,
181
- :add_method_signature_lines,
182
- :rbs_signature_lines_from_method,
183
- :store_signature_lines_from_method,
184
- :method_type_signature_lines,
185
- :store_type_signature_lines,
186
- :nonblank_lines
187
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Preserves simple <pre class="ruby"> language metadata emitted by RDoc.
4
- class ReverseMarkdown::Converters::RDocMarkdownPre < ReverseMarkdown::Converters::Pre
5
- # Matches RDoc's plain language class names on pre blocks.
6
- LANGUAGE_CLASS = /\A(?!highlight\z)[A-Za-z][A-Za-z0-9_+-]*\z/
7
-
8
- # Converts an RDoc pre block into a GitHub-flavored Markdown fence.
9
- #
10
- # @param node [Nokogiri::XML::Node] RDoc pre node.
11
- # @param _state [Hash] reverse_markdown converter state.
12
- #
13
- # @return [String] Markdown code fence.
14
- def convert(node, _state)
15
- content = treat_children(rdoc_pre_node(node), {})
16
- "\n```#{language(node)}\n" << content << "\n```\n"
17
- end
18
-
19
- private
20
-
21
- # Rebuilds a plain pre node from RDoc-highlighted text.
22
- #
23
- # @param node [Nokogiri::XML::Node] RDoc pre node.
24
- #
25
- # @return [Nokogiri::XML::Node] Plain pre node.
26
- def rdoc_pre_node(node)
27
- Nokogiri::HTML.fragment("<pre>#{node.text}</pre>").at("pre")
28
- end
29
-
30
- # Extracts the Markdown fence language.
31
- #
32
- # @param node [Nokogiri::XML::Node] RDoc pre node.
33
- #
34
- # @return [String, nil] Language name, or nil when no language is known.
35
- def language(node)
36
- node["class"].to_s[LANGUAGE_CLASS] || super
37
- end
38
- end
39
-
40
- ReverseMarkdown::Converters.register :pre, ReverseMarkdown::Converters::RDocMarkdownPre.new