rdoc-markdown 0.18.0 → 0.19.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/README.md +19 -82
  4. data/lib/markdown.rb +0 -4
  5. data/lib/rdoc/discover.rb +1 -7
  6. data/lib/rdoc/generator/markdown/conversion.rb +196 -0
  7. data/lib/rdoc/generator/markdown/crossref.rb +13 -5
  8. data/lib/rdoc/generator/markdown/descriptions.rb +163 -0
  9. data/lib/rdoc/generator/markdown/index.rb +47 -0
  10. data/lib/rdoc/generator/markdown/paths.rb +118 -0
  11. data/lib/rdoc/generator/markdown/selection.rb +39 -0
  12. data/lib/rdoc/generator/markdown/signatures.rb +174 -0
  13. data/lib/rdoc/generator/markdown.rb +80 -681
  14. data/lib/rdoc/markdown/version.rb +1 -1
  15. metadata +19 -43
  16. data/.editorconfig +0 -13
  17. data/.erb_lint.yml +0 -36
  18. data/.erb_linters/no_embedded_assets.rb +0 -29
  19. data/.erb_linters/non_raw_html.rb +0 -29
  20. data/.standard.yml +0 -3
  21. data/.yard-lint.yml +0 -290
  22. data/AGENTS.md +0 -50
  23. data/CODE_OF_CONDUCT.md +0 -84
  24. data/Gemfile +0 -13
  25. data/Gemfile.lock +0 -202
  26. data/Rakefile +0 -296
  27. data/example/Bird.md +0 -20
  28. data/example/Duck.md +0 -45
  29. data/example/Object.md +0 -9
  30. data/example/Waterfowl.md +0 -10
  31. data/example/index.csv +0 -16
  32. data/example/jekyll-seo-tag/Jekyll/SeoTag/AuthorDrop.md +0 -31
  33. data/example/jekyll-seo-tag/Jekyll/SeoTag/Drop.md +0 -64
  34. data/example/jekyll-seo-tag/Jekyll/SeoTag/Filters.md +0 -10
  35. data/example/jekyll-seo-tag/Jekyll/SeoTag/ImageDrop.md +0 -28
  36. data/example/jekyll-seo-tag/Jekyll/SeoTag/JSONLD.md +0 -14
  37. data/example/jekyll-seo-tag/Jekyll/SeoTag/JSONLDDrop.md +0 -23
  38. data/example/jekyll-seo-tag/Jekyll/SeoTag/UrlHelper.md +0 -7
  39. data/example/jekyll-seo-tag/Jekyll/SeoTag.md +0 -40
  40. data/example/jekyll-seo-tag/Jekyll.md +0 -5
  41. data/example/jekyll-seo-tag/Liquid/Tag.md +0 -6
  42. data/example/jekyll-seo-tag/Liquid.md +0 -7
  43. data/example/jekyll-seo-tag/index.csv +0 -60
  44. data/mutant.yml +0 -15
  45. data/rdoc-markdown.gemspec +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6769180d689a0858bfe6af579ea16ae9bbe1a4aa8ff6471bdf0e145731a3621b
4
- data.tar.gz: 4e9c43e702b50a7e894f1cea99418c1c3e0d9c1070ec9b5b41fa8dc654e97df6
3
+ metadata.gz: 7c5f77f880bb2c5dee60d22aaec8c1ed37e2525234afab66983549482406e321
4
+ data.tar.gz: c395b71591e6c71e16ef766cd6b389eb47a3c520dd859c6605b3540b30e98b83
5
5
  SHA512:
6
- metadata.gz: 17214dddbe75a0f4496e3ab7d818b005ebfc800bb6d9c11b6c8e946f36d409054b898024f332dddb7b99025cf6704a429ad723e5d0205c34a5d264c4cf019e2f
7
- data.tar.gz: 180c0790a847153dd2368c273e721a92ce6cea08119cd2d2445d5a0a88c609ae7d5a074753916a56e4f61307ad370d7f2dcc0345983de9c008bc1c6c7e0cefd4
6
+ metadata.gz: 39fca2d4e12f36652b1991d6bbada634b1f7b3d2280c1d39c9a0dce6398aa7210384d1451ff43797f9fd09bdf897fc14ff92b0b2c20159bf74653c2a27da24c6
7
+ data.tar.gz: 90fb4b652886f2f8da0637ac102ec728bd457c9eb49b77b27e542afe945b5da211a5b57a3b860c4edc711f57a6a5ce8a121bff73ae542daad051a57356ac348f
data/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.19.0
6
+
7
+ - Remove the `markdown_unknown_tags` option and use reverse_markdown's default handling.
8
+ - Preserve source file extensions in search-index names.
9
+
5
10
  ## 0.18.0
6
11
 
7
12
  - Rework heading for the document
data/README.md CHANGED
@@ -2,63 +2,35 @@
2
2
  RDoc plugin to generate markdown documentation and search index file (CSV).
3
3
 
4
4
  > [!CAUTION]
5
- > This gem relies on multiple hacks to generate "plausible" markdown documentation. This is **NOT PRODUCTION READY**, use at your own risk.
5
+ > This gem relies on multiple hacks to generate "plausible" markdown documentation. This is **NOT PRODUCTION READY** use at your own risk.
6
6
  >
7
- > rdoc maintainers are actively working on markdown support, things will improve with time...
7
+ > RDoc maintainers are actively working on markdown support, things will improve with time...
8
8
 
9
9
  ## Motivation
10
- Markdown has become the de-facto documentation standard. We can render markdown file on any device, possibly on thermometer with a screen. And everyone knows markdown...
10
+ Markdown has become the de facto documentation standard. We can render markdown files on any device, possibly on a thermometer with a screen. And everyone knows markdown...
11
11
 
12
- It's a pitty that rdoc can't output a proper markdown file. Somebody has to try and build it.
12
+ It's a pity that RDoc can't output a proper markdown file. Somebody has to try to build it.
13
13
 
14
14
  ## Installation
15
15
 
16
16
  rdoc-markdown requires Ruby 3.3 or newer and RDoc 8 or newer.
17
17
 
18
- Install gem and add to application's Gemfile by executing:
18
+ Install the gem and add it to your application's Gemfile by executing:
19
19
 
20
20
  $ bundle add rdoc-markdown
21
21
 
22
- If bundler is not being used to manage dependencies, install the gem by executing:
22
+ If Bundler is not being used to manage dependencies, install the gem by executing:
23
23
 
24
24
  $ gem install rdoc-markdown
25
- ## Examples
26
- Find examples in [/example](/example/) folder. You can regenerate examples by running `./bin/generate.sh`; it produces the sample docs from `test/data/example.rb` and a pinned `jekyll-seo-tag` example from `vendor/jekyll-seo-tag`.
27
25
 
28
26
 
29
27
  ## Usage
30
- RDoc will auto-detect rdoc-markdown plugin if it was installed. You just need to instruct RDoc to produce markdown output instead of standard HTML through `format` parameter.
28
+ RDoc will auto-detect the rdoc-markdown plugin if it is installed. You just need to instruct RDoc to produce markdown output instead of standard HTML through the `format` parameter.
31
29
 
32
- Run following command in directory with ruby source code:
30
+ Run the following command in a directory with Ruby source code:
33
31
 
34
32
  `rdoc --format=markdown`
35
33
 
36
- This will produce a tree of markdown documents and search index in `/doc` folder. Every class in library will have it's own markdown file.
37
-
38
- Input `.md` and `.markdown` pages are copied unchanged to the same relative path. RDoc pages and Ruby documentation are converted to Markdown.
39
-
40
- ### Unknown HTML tags
41
- rdoc-markdown uses `reverse_markdown` to convert RDoc's HTML fragments to Markdown. You can configure how unknown HTML tags are handled with:
42
-
43
- ```sh
44
- rdoc --format=markdown --markdown-unknown-tags=raise
45
- ```
46
-
47
- Accepted values are `pass_through`, `drop`, `bypass`, and `raise`. The default is `pass_through`.
48
-
49
- The same setting can be stored in RDoc's `.rdoc_options` file:
50
-
51
- ```yaml
52
- ---
53
- markdown_unknown_tags: :raise
54
- ```
55
-
56
- ## Note on index.csv file
57
- This gem emits index of all markdown files in a index.csv file.
58
-
59
- There are decent tools that offer search through structured plain-text files. But my expectation is that nobody will use CSV as an actual search index, but rather import it into something that performs this function better.
60
-
61
- In my personal use-case, I use SQLite. All other databases seem to have a good support for CSV imports.
62
34
 
63
35
  ## Development
64
36
 
@@ -70,14 +42,14 @@ gem build rdoc-markdown.gemspec
70
42
  gem install <path-to>/rdoc-markdown-0.1.2.gem
71
43
  ```
72
44
 
73
- or you can do the same, but through gemfile:
45
+ or you can do the same through a Gemfile:
74
46
 
75
47
  ```
76
- gem 'rdoc-markdown`, path: "../rdoc-markdown/`
48
+ gem 'rdoc-markdown', path: "../rdoc-markdown/"
77
49
  ```
78
50
 
79
51
  ## Testing
80
- Following command should run entire testsuit:
52
+ The following command should run the entire test suite:
81
53
  ```
82
54
  rake test
83
55
  ```
@@ -88,72 +60,37 @@ To validate generated markdown against GitHub Flavored Markdown and check local
88
60
  rake markdown:validate
89
61
  ```
90
62
 
91
- This task validates:
92
-
93
- - generated sample docs,
94
- - checked-in `example/` docs,
95
- - generated `jekyll-seo-tag` docs,
96
- - vendored minitest docs,
97
- - vendored rails docs (Active Support, Active Record, Action Pack, Railties slices).
98
-
99
- Validation artifacts are written to `tmp/markdown-validate/` inside this repository.
100
- This task is also executed in CI.
101
-
102
63
  To lint markdown ERB templates:
103
64
 
104
65
  ```
105
66
  bundle exec rake erb:lint
106
67
  ```
107
68
 
108
- ### Integration: minitest
109
- To run the integration harness against minitest (aligned with docs.seattlerb.org/minitest):
110
-
111
- ```
112
- rake vendor:setup:minitest
113
- bundle exec rake test
114
- ```
115
-
116
- The harness validates markdown formatting quality, generated page files, class/module paths, and search index output.
117
-
118
- ### Integration: rails
119
- To run the rails harness (focused on Active Support + Active Record API docs with sdoc-like structure):
120
-
121
- ```
122
- rake vendor:setup:rails
123
- bundle exec rake test
124
- ```
125
-
126
- The rails harness validates alias rendering, preserved code blocks, file/page links rewritten to markdown, and omission of fabricated external namespaces.
127
-
128
69
  ### Generate vendored docs
129
70
  Use rake tasks to generate markdown output for vendored projects:
130
71
 
72
+ `./bin/generate.sh`
73
+
74
+ or generate only some vendor docs:
75
+
131
76
  ```
132
- rake vendor:setup
133
77
  rake vendor:docs:jekyll_seo_tag
134
78
  rake vendor:docs:minitest
135
79
  rake vendor:docs:reverse_markdown
136
80
  rake vendor:docs:rails
137
- # or generate all
138
- rake vendor:docs
139
81
  ```
140
82
 
141
- `rake vendor:setup` also checks out the dependency-aligned `reverse_markdown` source in `vendor/reverse_markdown`.
142
-
143
- Output is written to:
83
+ Find examples in the [/example](/example/) folder.
144
84
 
145
- - `vendor/docs/minitest`
146
- - `vendor/docs/jekyll-seo-tag`
147
- - `vendor/docs/reverse_markdown`
148
- - `vendor/docs/rails`
149
85
 
150
86
  ## Release
87
+
151
88
  ```
152
89
  gem build rdoc-markdown.gemspec
153
90
  gem push rdoc-markdown-0.1.2.gem
154
91
  ```
155
92
 
156
- There is `./bin/publish.sh` script that does that. But it assumes, that you have [gum library](https://github.com/charmbracelet/gum) installed.
93
+ There is a `./bin/publish.sh` script that does that. However, it assumes that you have the [gum library](https://github.com/charmbracelet/gum) installed.
157
94
 
158
95
  ## Contributing
159
96
 
@@ -161,4 +98,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/skatko
161
98
 
162
99
  ## Code of Conduct
163
100
 
164
- Everyone interacting in the Rdoc::Markdown project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/skatkov/rdoc-markdown/blob/master/CODE_OF_CONDUCT.md).
101
+ Everyone interacting in the RDoc::Markdown project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/skatkov/rdoc-markdown/blob/master/CODE_OF_CONDUCT.md).
data/lib/markdown.rb CHANGED
@@ -1,7 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rdoc/generator/markdown"
4
-
5
- # Compatibility namespace loaded by RDoc's generator discovery.
6
- class RDoc::Markdown
7
- end
data/lib/rdoc/discover.rb CHANGED
@@ -1,7 +1 @@
1
- begin
2
- require "markdown"
3
- rescue LoadError => error
4
- puts error
5
- end
6
-
7
- puts "rdoc-markdown was discovered" if $DEBUG
1
+ require "markdown"
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Converts RDoc HTML and normalizes generated Markdown.
4
+ class RDoc::Generator::Markdown::Conversion
5
+ # Converts RDoc HTML into GitHub-flavored Markdown.
6
+ #
7
+ # @param input [String] RDoc HTML fragment.
8
+ # @param heading_level_offset [Integer] Heading levels to add.
9
+ #
10
+ # @return [String] Markdown with normalized links and no trailing whitespace.
11
+ def self.markdownify(input, heading_level_offset: 0)
12
+ fragment = normalized_html_fragment(input)
13
+ anchor_aliases = tokenize_legacy_anchors(fragment.css("span.legacy-anchor[id]"))
14
+ document = fragment.document
15
+ fragment.css("a").each { |link| normalize_link(link, document) }
16
+ markdown = ReverseMarkdown.convert(fragment, github_flavored: true).dup
17
+ anchor_aliases.each { |token, id| markdown.gsub!(token, %(<a id="#{id}"></a>)) }
18
+ normalize_definition_list_code_blocks(markdown).rstrip.gsub(/^(#+)(\s)/) do
19
+ "#{"#" * [Regexp.last_match(1).length + heading_level_offset, 6].min}#{Regexp.last_match(2)}"
20
+ end
21
+ end
22
+
23
+ class << self
24
+ private
25
+
26
+ # Normalizes HTML constructs that reverse_markdown cannot preserve directly.
27
+ #
28
+ # @param input [String] RDoc HTML fragment.
29
+ #
30
+ # @return [Nokogiri::HTML4::DocumentFragment] Normalized fragment.
31
+ def normalized_html_fragment(input)
32
+ fragment = Nokogiri::HTML.fragment(input)
33
+ context_anchors, content_anchors = fragment.css("span.legacy-anchor[id]").partition do |span|
34
+ context_anchor?(span)
35
+ end
36
+ context_anchors.each(&:remove)
37
+ content_anchors.each do |span|
38
+ heading = span.next_element
39
+ heading.add_child(span) if heading&.name&.match?(/\Ah[1-6]\z/)
40
+ end
41
+ normalize_pre_blocks(fragment)
42
+ document = fragment.document
43
+ fragment.css("h1, h2, h3, h4, h5, h6").each do |heading|
44
+ normalize_heading_link(heading, document)
45
+ end
46
+ fragment
47
+ end
48
+
49
+ # Checks whether an anchor identifies the surrounding class or module.
50
+ #
51
+ # @param span [Nokogiri::XML::Element] Legacy anchor span.
52
+ #
53
+ # @return [Boolean] Whether the anchor belongs to a top-level context heading.
54
+ def context_anchor?(span)
55
+ span.next_element&.name == "h1" && span["id"].match?(/\A(?:class|module)-/)
56
+ end
57
+
58
+ # Preserves simple language classes and literal preformatted text.
59
+ #
60
+ # @param fragment [Nokogiri::HTML4::DocumentFragment] HTML fragment.
61
+ #
62
+ # @return [void]
63
+ def normalize_pre_blocks(fragment)
64
+ fragment.css("pre").each do |pre|
65
+ language = pre["class"].to_s[/\A(?!highlight\z)[A-Za-z][A-Za-z0-9_+-]*\z/]
66
+ pre["class"] = "brush: #{language};" if language
67
+ pre.inner_html = pre.text
68
+ end
69
+ end
70
+
71
+ # Normalizes one heading's leading fragment link.
72
+ #
73
+ # @param heading [Nokogiri::XML::Element] Heading element.
74
+ # @param document [Nokogiri::HTML4::Document] Owning document.
75
+ #
76
+ # @return [void]
77
+ def normalize_heading_link(heading, document)
78
+ link = heading.xpath("./a[starts-with(@href, '#') and string-length(@href) > 1]").find do |anchor|
79
+ leading_heading_link?(anchor)
80
+ end
81
+ return unless link
82
+
83
+ id = link["href"].delete_prefix("#")
84
+ link.replace(link.children)
85
+ return if id == RDoc::Text.to_anchor(heading.text)
86
+
87
+ heading.add_child(document.create_element("span", "class" => "legacy-anchor", "id" => id))
88
+ end
89
+
90
+ # Checks whether a link is the first visible content in its heading.
91
+ #
92
+ # @param anchor [Nokogiri::XML::Element] Candidate heading link.
93
+ #
94
+ # @return [Boolean] Whether no visible text precedes the link.
95
+ def leading_heading_link?(anchor)
96
+ anchor.text.match?(/\S/) &&
97
+ anchor.xpath("preceding-sibling::node()").none? { |sibling| sibling.text.match?(/\S/) }
98
+ end
99
+
100
+ # Replaces legacy anchors with tokens that survive reverse_markdown.
101
+ #
102
+ # @param anchors [Nokogiri::XML::NodeSet] Legacy anchor spans.
103
+ #
104
+ # @return [Array<Array<String>>] Token and anchor ID pairs.
105
+ def tokenize_legacy_anchors(anchors)
106
+ anchors.map.with_index do |span, index|
107
+ token = "RDocMarkdownAnchor#{index}End"
108
+ id = span["id"]
109
+ span.replace(token)
110
+ [token, id]
111
+ end
112
+ end
113
+
114
+ # Normalizes one HTML link.
115
+ #
116
+ # @param link [Nokogiri::XML::Element] Link element.
117
+ # @param document [Nokogiri::HTML4::Document] Owning document.
118
+ #
119
+ # @return [void]
120
+ def normalize_link(link, document)
121
+ receiver = link.text
122
+ href = link["href"].to_s
123
+
124
+ if index_reference?(receiver, href)
125
+ link.replace(document.create_element("code") { |code| code.content = "#{receiver}[#{href}]" })
126
+ elsif href.start_with?("www.")
127
+ link["href"] = "https://#{href}"
128
+ elsif !href.match?(/\A(?:https?:\/\/|mailto:|#)/i)
129
+ link["href"] = normalized_link_target(href)
130
+ end
131
+ end
132
+
133
+ # Checks whether RDoc encoded an indexing expression as a link.
134
+ #
135
+ # @param receiver [String] Visible receiver text.
136
+ # @param href [String] Link target.
137
+ #
138
+ # @return [Boolean] Whether the link represents Ruby indexing syntax.
139
+ def index_reference?(receiver, href)
140
+ receiver.match?(/\A(?:[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*|[a-z_][A-Za-z0-9_]*)\z/) &&
141
+ href.match?(/\A(?::.+|".+")\z/)
142
+ end
143
+
144
+ # Rewrites an internal RDoc HTML target to its Markdown equivalent.
145
+ #
146
+ # @param href [String] Original link target.
147
+ #
148
+ # @return [String] Normalized target.
149
+ def normalized_link_target(href)
150
+ href.sub(/\.html(?=[?#]|\z)/i, ".md")
151
+ .sub(%r{\A/(?=.+\.md(?:[?#]|\z))}, "")
152
+ .sub(%r{\A((?:\.\./)*)(?:files|classes|modules)/(?=.+\.md(?:[?#]|\z))}, '\1')
153
+ end
154
+
155
+ # Converts RDoc definition-list code blocks into Markdown lists.
156
+ #
157
+ # @param markdown [String] Markdown content.
158
+ #
159
+ # @return [String] Markdown with convertible blocks normalized.
160
+ def normalize_definition_list_code_blocks(markdown)
161
+ markdown.gsub(/```[^\n]*\n(.+?)\n```/m) do
162
+ body = Regexp.last_match(1)
163
+ converted = convert_definition_list_block(body)
164
+ converted || Regexp.last_match
165
+ end
166
+ end
167
+
168
+ # Converts a single definition-list code block.
169
+ #
170
+ # @param body [String] Code block body.
171
+ #
172
+ # @return [String, nil] Converted Markdown, or nil when the block is not a definition list.
173
+ def convert_definition_list_block(body)
174
+ lines = body.lines
175
+ return nil unless lines.all? { |line| definition_list_line?(line) }
176
+
177
+ lines.map do |line|
178
+ stripped = line.strip
179
+ next if stripped.empty?
180
+ next "#{stripped.sub(/::\z/, "")}:" if stripped.end_with?("::")
181
+
182
+ "- #{stripped.sub(/\A\*\s/, "")}"
183
+ end.join("\n")
184
+ end
185
+
186
+ # Checks whether a line can appear in a converted definition list.
187
+ #
188
+ # @param line [String] Markdown line.
189
+ #
190
+ # @return [Boolean] True when the line matches RDoc definition-list output.
191
+ def definition_list_line?(line)
192
+ stripped = line.strip
193
+ stripped.empty? || stripped.end_with?("::") || stripped.match?(/\A\*\s/)
194
+ end
195
+ end
196
+ end
@@ -2,11 +2,19 @@
2
2
 
3
3
  # Prevents RDoc from linking to code objects omitted from Markdown output.
4
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
5
+ # Applies Markdown cross-reference state while rendering a description.
6
+ #
7
+ # @param cross_reference [RDoc::CrossReference] Resolver scoped to the formatter.
8
+ # @param output_object_ids [Set<Integer>] Object IDs emitted by the generator.
9
+ #
10
+ # @return [Object] Value returned by the block.
11
+ def with_markdown_cross_references(cross_reference, output_object_ids)
12
+ @markdown_cross_reference = cross_reference
13
+ @markdown_output_object_ids = output_object_ids
14
+ yield
15
+ ensure
16
+ @markdown_cross_reference = nil
17
+ end
10
18
 
11
19
  # Renders a cross-reference only when its owning object is emitted.
12
20
  #
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Renders RDoc descriptions and metadata for Markdown templates.
4
+ module RDoc::Generator::Markdown::Descriptions
5
+ private
6
+
7
+ # Renders a class or module reference, linking it when its documentation is emitted.
8
+ #
9
+ # @param target [RDoc::ClassModule, String] Resolved RDoc object or unresolved name.
10
+ # @param label [String] Visible reference text.
11
+ #
12
+ # @return [String] Markdown text or link.
13
+ def metadata_reference(target, label)
14
+ cell = metadata_table_cell(label)
15
+ return cell unless RDoc::ClassModule === target
16
+
17
+ output_path = generation_state.class_output_paths[target.full_name]
18
+ return cell unless output_path
19
+
20
+ "[#{cell}](#{output_path})"
21
+ end
22
+
23
+ # Escapes text for a Markdown table cell.
24
+ #
25
+ # @param value [String] Metadata text.
26
+ #
27
+ # @return [String] GFM table-safe Markdown text.
28
+ def metadata_table_cell(value)
29
+ value.gsub(/[[:blank:]]*\R[[:blank:]]*/, " ")
30
+ .gsub(/[\\|]/) { |character| "\\#{character}" }
31
+ end
32
+
33
+ # Renders an RDoc description with links limited to emitted objects.
34
+ #
35
+ # @param code_object [RDoc::CodeObject, RDoc::Context::Section] Object whose description is rendered.
36
+ #
37
+ # @return [String] HTML description.
38
+ def render_description(code_object)
39
+ formatter = description_formatter(code_object)
40
+ formatter.extend(RDoc::Generator::Markdown::CrossrefExtension)
41
+ formatter.with_markdown_cross_references(
42
+ RDoc::CrossReference.new(formatter.context),
43
+ generation_state.markdown_output_object_ids
44
+ ) do
45
+ render_formatted_description(code_object, formatter, options.locale)
46
+ end
47
+ end
48
+
49
+ # Returns the formatter appropriate for an object or section.
50
+ #
51
+ # @param code_object [RDoc::CodeObject, RDoc::Context::Section] Described object.
52
+ #
53
+ # @return [RDoc::Markup::Formatter] Formatter configured for the object.
54
+ def description_formatter(code_object)
55
+ return code_object.formatter unless RDoc::Context::Section === code_object
56
+
57
+ code_object.parent.formatter.dup.tap { |copy| copy.code_object = code_object }
58
+ end
59
+
60
+ # Renders an object through its configured formatter.
61
+ #
62
+ # @param code_object [RDoc::CodeObject, RDoc::Context::Section] Described object.
63
+ # @param formatter [RDoc::Markup::Formatter] Formatter receiving parsed markup.
64
+ # @param locale [RDoc::I18n::Locale, nil] Translation locale.
65
+ #
66
+ # @return [String] Formatted HTML description.
67
+ def render_formatted_description(code_object, formatter, locale)
68
+ return code_object.description unless RDoc::Context::Section === code_object
69
+
70
+ documents = code_object.comments.map { |comment| localized_comment_document(comment, locale) }
71
+ RDoc::Markup::Document.new(*documents).accept(formatter)
72
+ end
73
+
74
+ # Parses one section comment, translating a copy when needed.
75
+ #
76
+ # @param comment [RDoc::Comment] Section comment.
77
+ # @param locale [RDoc::I18n::Locale, nil] Translation locale.
78
+ #
79
+ # @return [RDoc::Markup::Document] Parsed comment document.
80
+ def localized_comment_document(comment, locale)
81
+ return comment.parse unless locale && !comment.text.empty?
82
+
83
+ translated = comment.dup
84
+ translated.text = RDoc::I18n::Text.new(translated).translate(locale)
85
+ translated.parse
86
+ end
87
+
88
+ # Renders an RDoc object's description as Markdown.
89
+ #
90
+ # @param code_object [RDoc::CodeObject] Object with an RDoc description.
91
+ # @param fallback [String, nil] Text to use when the description is empty.
92
+ # @param heading_level_offset [Integer] Heading levels to add while rendering.
93
+ #
94
+ # @return [String] Rendered description or fallback text.
95
+ def describe(code_object, fallback: nil, heading_level_offset: 0)
96
+ description = render_description(code_object)
97
+ return fallback.to_s if description.empty?
98
+
99
+ RDoc::Generator::Markdown::Conversion.markdownify(description, heading_level_offset: heading_level_offset)
100
+ end
101
+
102
+ # Renders a section description as Markdown.
103
+ #
104
+ # @param section [RDoc::Context::Section] RDoc section whose description appears before grouped members.
105
+ # @param heading_level_offset [Integer] Heading levels to add while rendering.
106
+ #
107
+ # @return [String] Rendered section description.
108
+ def section_description(section, heading_level_offset:)
109
+ RDoc::Generator::Markdown::Conversion.markdownify(
110
+ render_description(section),
111
+ heading_level_offset: heading_level_offset
112
+ )
113
+ end
114
+
115
+ # Renders a method description or an alias fallback.
116
+ #
117
+ # @param method [RDoc::AnyMethod] Method object to render.
118
+ # @param current_class [RDoc::Context] Class or module currently being rendered.
119
+ # @param heading_level_offset [Integer] Heading levels to add while rendering.
120
+ #
121
+ # @return [String] Rendered method description.
122
+ def method_description(method, current_class:, heading_level_offset:)
123
+ text = describe(method, heading_level_offset: heading_level_offset)
124
+ return text unless text.empty?
125
+
126
+ aliased_method = method.is_alias_for
127
+ return "Not documented." unless aliased_method
128
+
129
+ alias_description(aliased_method, method_link(aliased_method, current_class: current_class))
130
+ end
131
+
132
+ # Formats an alias fallback with an optional target link.
133
+ #
134
+ # @param aliased_method [RDoc::AnyMethod] Alias target.
135
+ # @param link [String, nil] Generated link target.
136
+ #
137
+ # @return [String] Alias description.
138
+ def alias_description(aliased_method, link)
139
+ name = aliased_method.name
140
+ return "Alias for: `#{name}`" unless link
141
+
142
+ "Alias for: [`#{name}`](#{link})"
143
+ end
144
+
145
+ # Builds a Markdown link target for an aliased method.
146
+ #
147
+ # @param method [RDoc::AnyMethod] Target method.
148
+ # @param current_class [RDoc::Context] Class or module currently being rendered.
149
+ #
150
+ # @return [String, nil] Anchor or relative Markdown link target, or nil when the target page is omitted.
151
+ def method_link(method, current_class:)
152
+ return unless method.display?
153
+
154
+ target_parent = method.parent
155
+ target_path = generation_state.class_output_paths[target_parent.full_name]
156
+ return unless target_path
157
+
158
+ anchor = method.aref
159
+ return "##{anchor}" if target_parent == current_class
160
+
161
+ "#{target_path}##{anchor}"
162
+ end
163
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Writes the generated documentation search index.
4
+ module RDoc::Generator::Markdown::Index
5
+ private
6
+
7
+ # Writes a CSV search index for generated documentation.
8
+ #
9
+ # @return [void]
10
+ def emit_csv_index
11
+ CSV.open("#{output_dir}/index.csv", "wb") { |csv| write_index(csv) }
12
+ end
13
+
14
+ # Writes all search-index rows.
15
+ #
16
+ # @param csv [CSV] Open CSV writer.
17
+ #
18
+ # @return [void]
19
+ def write_index(csv)
20
+ csv << %w[name type path]
21
+ classes.each { |klass| write_class_rows(csv, klass, output_path_for(klass)) }
22
+ pages.each { |page| csv << [File.basename(normalize_input_path_for_output(page.relative_name)), "File", page_output_path(page)] }
23
+ end
24
+
25
+ # Writes one class and its visible member rows.
26
+ #
27
+ # @param csv [CSV] Open CSV writer.
28
+ # @param klass [RDoc::Context] Class or module to index.
29
+ # @param output_path [String] Generated Markdown path.
30
+ #
31
+ # @return [void]
32
+ def write_class_rows(csv, klass, output_path)
33
+ class_name = klass.full_name
34
+ csv << [class_name, klass.type.capitalize, output_path]
35
+
36
+ klass.method_list.select(&:display?).each do |method|
37
+ csv << ["#{class_name}.#{method.name}", "Method", "#{output_path}##{method.aref}"]
38
+ end
39
+ klass.constants.select(&:display?).sort.each do |const|
40
+ name = const.name
41
+ csv << ["#{class_name}.#{name}", "Constant", "#{output_path}##{name}"]
42
+ end
43
+ klass.attributes.select(&:display?).sort.each do |attr|
44
+ csv << ["#{class_name}.#{attr.name}", "Attribute", "#{output_path}##{attr.aref}"]
45
+ end
46
+ end
47
+ end