rdoc-markdown 0.21.1 → 0.22.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 +9 -0
- data/README.md +9 -16
- data/lib/rdoc/discover.rb +3 -1
- data/lib/rdoc/generator/markdown/blocks.rb +268 -0
- data/lib/rdoc/generator/markdown/descriptions.rb +36 -60
- data/lib/rdoc/generator/markdown/formatter.rb +44 -0
- data/lib/rdoc/generator/markdown/html_input.rb +126 -0
- data/lib/rdoc/generator/markdown/index.rb +4 -4
- data/lib/rdoc/generator/markdown/inline.rb +173 -0
- data/lib/rdoc/generator/markdown/inline_html.rb +56 -0
- data/lib/rdoc/generator/markdown/markdown_links.rb +101 -0
- data/lib/rdoc/generator/markdown/paths.rb +10 -41
- data/lib/rdoc/generator/markdown/references.rb +164 -0
- data/lib/rdoc/generator/markdown/signatures.rb +10 -74
- data/lib/rdoc/generator/markdown/syntax.rb +135 -0
- data/lib/rdoc/generator/markdown.rb +28 -46
- data/lib/rdoc/markdown/version.rb +1 -1
- data/lib/templates/classfile.md.erb +10 -5
- metadata +19 -42
- data/lib/markdown.rb +0 -3
- data/lib/rdoc/generator/markdown/conversion.rb +0 -200
- data/lib/rdoc/generator/markdown/crossref.rb +0 -41
|
@@ -1,200 +0,0 @@
|
|
|
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, heading_level_offset)
|
|
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
|
-
markdown.gsub!(Regexp.union(anchor_aliases.keys), anchor_aliases)
|
|
18
|
-
normalize_definition_lists(markdown).rstrip
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
class << self
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
# Normalizes HTML constructs that reverse_markdown cannot preserve directly.
|
|
25
|
-
#
|
|
26
|
-
# @param input [String] RDoc HTML fragment.
|
|
27
|
-
# @param heading_level_offset [Integer] Heading levels to add to actual HTML headings.
|
|
28
|
-
#
|
|
29
|
-
# @return [Nokogiri::HTML4::DocumentFragment] Normalized fragment.
|
|
30
|
-
def normalized_html_fragment(input, heading_level_offset)
|
|
31
|
-
fragment = Nokogiri::HTML.fragment(input)
|
|
32
|
-
context_anchors, content_anchors = fragment.css("span.legacy-anchor[id]").partition do |span|
|
|
33
|
-
context_anchor?(span)
|
|
34
|
-
end
|
|
35
|
-
context_anchors.each(&:remove)
|
|
36
|
-
content_anchors.each do |span|
|
|
37
|
-
heading = span.next_element
|
|
38
|
-
heading.add_child(span) if heading&.name&.match?(/\Ah[1-6]\z/)
|
|
39
|
-
end
|
|
40
|
-
normalize_pre_blocks(fragment)
|
|
41
|
-
document = fragment.document
|
|
42
|
-
fragment.css("h1, h2, h3, h4, h5, h6").each do |heading|
|
|
43
|
-
normalize_heading(heading, document, heading_level_offset)
|
|
44
|
-
end
|
|
45
|
-
fragment
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Checks whether an anchor identifies the surrounding class or module.
|
|
49
|
-
#
|
|
50
|
-
# @param span [Nokogiri::XML::Element] Legacy anchor span.
|
|
51
|
-
#
|
|
52
|
-
# @return [Boolean] Whether the anchor belongs to a top-level context heading.
|
|
53
|
-
def context_anchor?(span)
|
|
54
|
-
span.next_element&.name == "h1" && span["id"].match?(/\A(?:class|module)-/)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Preserves simple language classes and literal preformatted text.
|
|
58
|
-
#
|
|
59
|
-
# @param fragment [Nokogiri::HTML4::DocumentFragment] HTML fragment.
|
|
60
|
-
#
|
|
61
|
-
# @return [void]
|
|
62
|
-
def normalize_pre_blocks(fragment)
|
|
63
|
-
fragment.css("pre").each do |pre|
|
|
64
|
-
language = pre["class"].to_s[/\A(?!highlight\z)[A-Za-z][A-Za-z0-9_+-]*\z/]
|
|
65
|
-
pre["class"] = "brush: #{language};" if language
|
|
66
|
-
pre.content = pre.text
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Normalizes one heading's level and leading fragment link.
|
|
71
|
-
#
|
|
72
|
-
# @param heading [Nokogiri::XML::Element] Heading element.
|
|
73
|
-
# @param document [Nokogiri::HTML4::Document] Owning document.
|
|
74
|
-
# @param heading_level_offset [Integer] Heading levels to add, capped at six.
|
|
75
|
-
#
|
|
76
|
-
# @return [void]
|
|
77
|
-
def normalize_heading(heading, document, heading_level_offset)
|
|
78
|
-
heading.name = "h#{[Integer(heading.name.delete_prefix("h")) + heading_level_offset, 6].min}"
|
|
79
|
-
link = heading.xpath("./a[starts-with(@href, '#') and string-length(@href) > 1]").find do |anchor|
|
|
80
|
-
leading_heading_link?(anchor)
|
|
81
|
-
end
|
|
82
|
-
return unless link
|
|
83
|
-
|
|
84
|
-
id = link["href"].delete_prefix("#")
|
|
85
|
-
link.replace(link.children)
|
|
86
|
-
return if id == RDoc::Text.to_anchor(heading.text)
|
|
87
|
-
|
|
88
|
-
heading.add_child(document.create_element("span", "class" => "legacy-anchor", "id" => id))
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Checks whether a link is the first visible content in its heading.
|
|
92
|
-
#
|
|
93
|
-
# @param anchor [Nokogiri::XML::Element] Candidate heading link.
|
|
94
|
-
#
|
|
95
|
-
# @return [Boolean] Whether no visible text precedes the link.
|
|
96
|
-
def leading_heading_link?(anchor)
|
|
97
|
-
anchor.text.match?(/\S/) &&
|
|
98
|
-
anchor.xpath("preceding-sibling::node()").none? { |sibling| sibling.text.match?(/\S/) }
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Replaces legacy anchors with tokens that survive reverse_markdown.
|
|
102
|
-
#
|
|
103
|
-
# @param anchors [Nokogiri::XML::NodeSet] Legacy anchor spans.
|
|
104
|
-
#
|
|
105
|
-
# @return [Hash{String => String}] Token-to-anchor HTML lookup.
|
|
106
|
-
def tokenize_legacy_anchors(anchors)
|
|
107
|
-
anchors.each.with_index.to_h do |span, index|
|
|
108
|
-
token = "RDocMarkdownAnchor#{index}End"
|
|
109
|
-
anchor = %(<a id="#{span["id"]}"></a>)
|
|
110
|
-
span.replace(token)
|
|
111
|
-
[token, anchor]
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Normalizes one HTML link.
|
|
116
|
-
#
|
|
117
|
-
# @param link [Nokogiri::XML::Element] Link element.
|
|
118
|
-
# @param document [Nokogiri::HTML4::Document] Owning document.
|
|
119
|
-
#
|
|
120
|
-
# @return [void]
|
|
121
|
-
def normalize_link(link, document)
|
|
122
|
-
receiver = link.text
|
|
123
|
-
href = link["href"].to_s
|
|
124
|
-
|
|
125
|
-
if index_reference?(receiver, href)
|
|
126
|
-
link.replace(document.create_element("code") { |code| code.content = "#{receiver}[#{href}]" })
|
|
127
|
-
elsif href.start_with?("www.")
|
|
128
|
-
link["href"] = "https://#{href}"
|
|
129
|
-
elsif !href.match?(/\A(?:https?:\/\/|mailto:|#)/i)
|
|
130
|
-
link["href"] = normalized_link_target(href)
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Checks whether RDoc encoded an indexing expression as a link.
|
|
135
|
-
#
|
|
136
|
-
# @param receiver [String] Visible receiver text.
|
|
137
|
-
# @param href [String] Link target.
|
|
138
|
-
#
|
|
139
|
-
# @return [Boolean] Whether the link represents Ruby indexing syntax.
|
|
140
|
-
def index_reference?(receiver, href)
|
|
141
|
-
receiver.match?(/\A(?:[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*|[a-z_][A-Za-z0-9_]*)\z/) &&
|
|
142
|
-
href.match?(/\A(?::.+|".+")\z/)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Rewrites an internal RDoc HTML target to its Markdown equivalent.
|
|
146
|
-
#
|
|
147
|
-
# @param href [String] Original link target.
|
|
148
|
-
#
|
|
149
|
-
# @return [String] Normalized target.
|
|
150
|
-
def normalized_link_target(href)
|
|
151
|
-
href.sub(/\.html(?=[?#]|\z)/i, ".md")
|
|
152
|
-
.sub(%r{\A/(?=.+\.md(?:[?#]|\z))}, "")
|
|
153
|
-
.sub(%r{\A((?:\.\./)*)(?:files|classes|modules)/(?=.+\.md(?:[?#]|\z))}, '\1')
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
# Converts RDoc definition-list code blocks into Markdown lists and separates
|
|
157
|
-
# raw HTML lists from following Markdown without rewriting fenced examples.
|
|
158
|
-
#
|
|
159
|
-
# @param markdown [String] Markdown content.
|
|
160
|
-
#
|
|
161
|
-
# @return [String] Markdown with definition lists normalized.
|
|
162
|
-
def normalize_definition_lists(markdown)
|
|
163
|
-
markdown.gsub(%r{```[^\n]*\n(.+?)\n```|</dl>\n(?=\S)}m) do
|
|
164
|
-
body = Regexp.last_match(1)
|
|
165
|
-
next "</dl>\n\n" unless body
|
|
166
|
-
|
|
167
|
-
converted = convert_definition_list_block(body)
|
|
168
|
-
converted || Regexp.last_match
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
# Converts a single definition-list code block.
|
|
173
|
-
#
|
|
174
|
-
# @param body [String] Code block body.
|
|
175
|
-
#
|
|
176
|
-
# @return [String, nil] Converted Markdown, or nil when the block is not a definition list.
|
|
177
|
-
def convert_definition_list_block(body)
|
|
178
|
-
lines = body.lines
|
|
179
|
-
return nil unless lines.all? { |line| definition_list_line?(line) }
|
|
180
|
-
|
|
181
|
-
lines.map do |line|
|
|
182
|
-
stripped = line.strip
|
|
183
|
-
next if stripped.empty?
|
|
184
|
-
next "#{stripped.sub(/::\z/, "")}:" if stripped.end_with?("::")
|
|
185
|
-
|
|
186
|
-
"- #{stripped.sub(/\A\*\s/, "")}"
|
|
187
|
-
end.join("\n")
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
# Checks whether a line can appear in a converted definition list.
|
|
191
|
-
#
|
|
192
|
-
# @param line [String] Markdown line.
|
|
193
|
-
#
|
|
194
|
-
# @return [Boolean] True when the line matches RDoc definition-list output.
|
|
195
|
-
def definition_list_line?(line)
|
|
196
|
-
stripped = line.strip
|
|
197
|
-
stripped.empty? || stripped.end_with?("::") || stripped.match?(/\A\*\s/)
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
end
|
|
@@ -1,41 +0,0 @@
|
|
|
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
|
-
# 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
|
|
18
|
-
|
|
19
|
-
# Renders a cross-reference only when its owning object is emitted.
|
|
20
|
-
#
|
|
21
|
-
# @param name [String, nil] Cross-reference target.
|
|
22
|
-
# @param text [String] Visible link text.
|
|
23
|
-
# @param code [Boolean] Whether to format code objects as code.
|
|
24
|
-
# @param rdoc_ref [Boolean] Whether the target uses the rdoc-ref scheme.
|
|
25
|
-
#
|
|
26
|
-
# @return [String] HTML link or unlinked text.
|
|
27
|
-
def link(name, text, code = true, rdoc_ref: false)
|
|
28
|
-
return super unless @markdown_cross_reference
|
|
29
|
-
|
|
30
|
-
ref = @markdown_cross_reference.resolve(name) if name
|
|
31
|
-
return super unless RDoc::CodeObject === ref
|
|
32
|
-
|
|
33
|
-
context = ref
|
|
34
|
-
context = context.parent until RDoc::ClassModule === context || RDoc::TopLevel === context
|
|
35
|
-
return super if @markdown_output_object_ids.include?(context.object_id)
|
|
36
|
-
|
|
37
|
-
return text if RDoc::TopLevel === ref || !code
|
|
38
|
-
|
|
39
|
-
"<code>#{text}</code>"
|
|
40
|
-
end
|
|
41
|
-
end
|