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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +19 -82
- data/lib/markdown.rb +0 -4
- data/lib/rdoc/discover.rb +1 -7
- data/lib/rdoc/generator/markdown/conversion.rb +196 -0
- data/lib/rdoc/generator/markdown/crossref.rb +13 -5
- data/lib/rdoc/generator/markdown/descriptions.rb +163 -0
- data/lib/rdoc/generator/markdown/index.rb +47 -0
- data/lib/rdoc/generator/markdown/paths.rb +118 -0
- data/lib/rdoc/generator/markdown/selection.rb +39 -0
- data/lib/rdoc/generator/markdown/signatures.rb +174 -0
- data/lib/rdoc/generator/markdown.rb +80 -681
- data/lib/rdoc/markdown/version.rb +1 -1
- metadata +19 -43
- data/.editorconfig +0 -13
- data/.erb_lint.yml +0 -36
- data/.erb_linters/no_embedded_assets.rb +0 -29
- data/.erb_linters/non_raw_html.rb +0 -29
- data/.standard.yml +0 -3
- data/.yard-lint.yml +0 -290
- data/AGENTS.md +0 -50
- data/CODE_OF_CONDUCT.md +0 -84
- data/Gemfile +0 -13
- data/Gemfile.lock +0 -202
- data/Rakefile +0 -296
- data/example/Bird.md +0 -20
- data/example/Duck.md +0 -45
- data/example/Object.md +0 -9
- data/example/Waterfowl.md +0 -10
- data/example/index.csv +0 -16
- data/example/jekyll-seo-tag/Jekyll/SeoTag/AuthorDrop.md +0 -31
- data/example/jekyll-seo-tag/Jekyll/SeoTag/Drop.md +0 -64
- data/example/jekyll-seo-tag/Jekyll/SeoTag/Filters.md +0 -10
- data/example/jekyll-seo-tag/Jekyll/SeoTag/ImageDrop.md +0 -28
- data/example/jekyll-seo-tag/Jekyll/SeoTag/JSONLD.md +0 -14
- data/example/jekyll-seo-tag/Jekyll/SeoTag/JSONLDDrop.md +0 -23
- data/example/jekyll-seo-tag/Jekyll/SeoTag/UrlHelper.md +0 -7
- data/example/jekyll-seo-tag/Jekyll/SeoTag.md +0 -40
- data/example/jekyll-seo-tag/Jekyll.md +0 -5
- data/example/jekyll-seo-tag/Liquid/Tag.md +0 -6
- data/example/jekyll-seo-tag/Liquid.md +0 -7
- data/example/jekyll-seo-tag/index.csv +0 -60
- data/mutant.yml +0 -15
- data/rdoc-markdown.gemspec +0 -46
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7c5f77f880bb2c5dee60d22aaec8c1ed37e2525234afab66983549482406e321
|
|
4
|
+
data.tar.gz: c395b71591e6c71e16ef766cd6b389eb47a3c520dd859c6605b3540b30e98b83
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 39fca2d4e12f36652b1991d6bbada634b1f7b3d2280c1d39c9a0dce6398aa7210384d1451ff43797f9fd09bdf897fc14ff92b0b2c20159bf74653c2a27da24c6
|
|
7
|
+
data.tar.gz: 90fb4b652886f2f8da0637ac102ec728bd457c9eb49b77b27e542afe945b5da211a5b57a3b860c4edc711f57a6a5ce8a121bff73ae542daad051a57356ac348f
|
data/CHANGELOG.md
CHANGED
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
|
|
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
|
-
>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
45
|
+
or you can do the same through a Gemfile:
|
|
74
46
|
|
|
75
47
|
```
|
|
76
|
-
gem 'rdoc-markdown
|
|
48
|
+
gem 'rdoc-markdown', path: "../rdoc-markdown/"
|
|
77
49
|
```
|
|
78
50
|
|
|
79
51
|
## Testing
|
|
80
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
data/lib/rdoc/discover.rb
CHANGED
|
@@ -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
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# Object IDs emitted by the
|
|
9
|
-
|
|
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
|