rdoc 6.17.0 → 7.2.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/CONTRIBUTING.md +196 -0
- data/LEGAL.rdoc +6 -0
- data/README.md +90 -7
- data/doc/markup_reference/markdown.md +558 -0
- data/doc/markup_reference/rdoc.rdoc +1169 -0
- data/lib/rdoc/code_object/any_method.rb +15 -7
- data/lib/rdoc/code_object/attr.rb +2 -1
- data/lib/rdoc/code_object/class_module.rb +62 -11
- data/lib/rdoc/code_object/constant.rb +9 -0
- data/lib/rdoc/code_object/context/section.rb +46 -9
- data/lib/rdoc/code_object/context.rb +15 -4
- data/lib/rdoc/code_object/method_attr.rb +13 -1
- data/lib/rdoc/code_object/mixin.rb +3 -0
- data/lib/rdoc/code_object/top_level.rb +15 -1
- data/lib/rdoc/comment.rb +1 -1
- data/lib/rdoc/cross_reference.rb +31 -24
- data/lib/rdoc/generator/aliki.rb +141 -0
- data/lib/rdoc/generator/darkfish.rb +3 -1
- data/lib/rdoc/generator/template/aliki/_footer.rhtml +1 -1
- data/lib/rdoc/generator/template/aliki/_head.rhtml +9 -4
- data/lib/rdoc/generator/template/aliki/_header.rhtml +4 -4
- data/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml +1 -1
- data/lib/rdoc/generator/template/aliki/_sidebar_search.rhtml +3 -3
- data/lib/rdoc/generator/template/aliki/class.rhtml +17 -17
- data/lib/rdoc/generator/template/aliki/css/rdoc.css +278 -60
- data/lib/rdoc/generator/template/aliki/index.rhtml +1 -1
- data/lib/rdoc/generator/template/aliki/js/aliki.js +51 -23
- data/lib/rdoc/generator/template/aliki/js/bash_highlighter.js +167 -0
- data/lib/rdoc/generator/template/aliki/js/c_highlighter.js +1 -1
- data/lib/rdoc/generator/template/aliki/js/{search.js → search_controller.js} +16 -7
- data/lib/rdoc/generator/template/aliki/js/search_navigation.js +105 -0
- data/lib/rdoc/generator/template/aliki/js/search_ranker.js +239 -0
- data/lib/rdoc/generator/template/aliki/page.rhtml +1 -1
- data/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml +1 -1
- data/lib/rdoc/generator/template/aliki/servlet_root.rhtml +1 -1
- data/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml +1 -1
- data/lib/rdoc/generator/template/darkfish/class.rhtml +11 -11
- data/lib/rdoc/generator/template/darkfish/css/rdoc.css +19 -0
- data/lib/rdoc/markdown.kpeg +22 -12
- data/lib/rdoc/markdown.rb +36 -26
- data/lib/rdoc/markup/blank_line.rb +25 -23
- data/lib/rdoc/markup/element.rb +21 -0
- data/lib/rdoc/markup/formatter.rb +129 -106
- data/lib/rdoc/markup/hard_break.rb +30 -27
- data/lib/rdoc/markup/heading.rb +166 -77
- data/lib/rdoc/markup/inline_parser.rb +312 -0
- data/lib/rdoc/markup/parser.rb +1 -1
- data/lib/rdoc/markup/raw.rb +52 -55
- data/lib/rdoc/markup/table.rb +48 -40
- data/lib/rdoc/markup/to_ansi.rb +51 -4
- data/lib/rdoc/markup/to_bs.rb +22 -42
- data/lib/rdoc/markup/to_html.rb +178 -183
- data/lib/rdoc/markup/to_html_crossref.rb +58 -79
- data/lib/rdoc/markup/to_html_snippet.rb +62 -62
- data/lib/rdoc/markup/to_label.rb +29 -20
- data/lib/rdoc/markup/to_markdown.rb +61 -37
- data/lib/rdoc/markup/to_rdoc.rb +86 -26
- data/lib/rdoc/markup/to_test.rb +9 -1
- data/lib/rdoc/markup/to_tt_only.rb +10 -16
- data/lib/rdoc/markup/verbatim.rb +1 -1
- data/lib/rdoc/markup.rb +11 -32
- data/lib/rdoc/options.rb +1 -1
- data/lib/rdoc/parser/changelog.rb +29 -0
- data/lib/rdoc/parser/prism_ruby.rb +44 -32
- data/lib/rdoc/parser/ruby.rb +1 -1
- data/lib/rdoc/rubygems_hook.rb +3 -3
- data/lib/rdoc/text.rb +44 -5
- data/lib/rdoc/token_stream.rb +4 -8
- data/lib/rdoc/version.rb +1 -1
- data/rdoc.gemspec +3 -3
- metadata +13 -15
- data/CONTRIBUTING.rdoc +0 -219
- data/ExampleMarkdown.md +0 -39
- data/ExampleRDoc.rdoc +0 -210
- data/lib/rdoc/markup/attr_changer.rb +0 -22
- data/lib/rdoc/markup/attr_span.rb +0 -35
- data/lib/rdoc/markup/attribute_manager.rb +0 -405
- data/lib/rdoc/markup/attributes.rb +0 -70
- data/lib/rdoc/markup/regexp_handling.rb +0 -40
data/lib/rdoc/markup/heading.rb
CHANGED
|
@@ -1,84 +1,173 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
##
|
|
3
|
-
# A heading with a level (1-6) and text
|
|
4
2
|
|
|
5
|
-
RDoc
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
3
|
+
module RDoc
|
|
4
|
+
class Markup
|
|
5
|
+
# IMPORTANT! This weird workaround is required to ensure that RDoc can correctly deserializing Marshal data from
|
|
6
|
+
# older rubies. Older rubies have `Heading` as a struct, so if we change it to a class, deserialization fails
|
|
7
|
+
if RUBY_VERSION.start_with?("4.")
|
|
8
|
+
class Heading < Element
|
|
9
|
+
#: String
|
|
10
|
+
attr_reader :text
|
|
11
|
+
|
|
12
|
+
#: Integer
|
|
13
|
+
attr_accessor :level
|
|
14
|
+
|
|
15
|
+
#: (Integer, String) -> void
|
|
16
|
+
def initialize(level, text)
|
|
17
|
+
super()
|
|
18
|
+
|
|
19
|
+
@level = level
|
|
20
|
+
@text = text
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#: (Object) -> bool
|
|
24
|
+
def ==(other)
|
|
25
|
+
other.is_a?(Heading) && other.level == @level && other.text == @text
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
Heading = Struct.new(:level, :text)
|
|
32
30
|
end
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
32
|
+
# A heading with a level (1-6) and text
|
|
33
|
+
#
|
|
34
|
+
# RDoc syntax:
|
|
35
|
+
# = Heading 1
|
|
36
|
+
# == Heading 2
|
|
37
|
+
# === Heading 3
|
|
38
|
+
#
|
|
39
|
+
# Markdown syntax:
|
|
40
|
+
# # Heading 1
|
|
41
|
+
# ## Heading 2
|
|
42
|
+
# ### Heading 3
|
|
43
|
+
#
|
|
44
|
+
class Heading
|
|
45
|
+
# A singleton RDoc::Markup::ToLabel formatter for headings.
|
|
46
|
+
#: () -> RDoc::Markup::ToLabel
|
|
47
|
+
def self.to_label
|
|
48
|
+
@to_label ||= Markup::ToLabel.new
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# A singleton plain HTML formatter for headings. Used for creating labels for the Table of Contents
|
|
52
|
+
#: () -> RDoc::Markup::ToHtml
|
|
53
|
+
def self.to_html
|
|
54
|
+
@to_html ||= begin
|
|
55
|
+
markup = Markup.new
|
|
56
|
+
markup.add_regexp_handling CrossReference::CROSSREF_REGEXP, :CROSSREF
|
|
57
|
+
|
|
58
|
+
to_html = Markup::ToHtml.new nil
|
|
59
|
+
|
|
60
|
+
def to_html.handle_regexp_CROSSREF(text)
|
|
61
|
+
text.sub(/^\\/, '')
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
to_html
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @override
|
|
69
|
+
#: (untyped) -> void
|
|
70
|
+
def accept(visitor)
|
|
71
|
+
visitor.accept_heading(self)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# An HTML-safe anchor reference for this header using GitHub-style formatting:
|
|
75
|
+
# - Lowercase
|
|
76
|
+
# - Spaces converted to hyphens
|
|
77
|
+
# - Special characters removed (except hyphens)
|
|
78
|
+
#
|
|
79
|
+
# Examples:
|
|
80
|
+
# "Hello" -> "hello"
|
|
81
|
+
# "Hello World" -> "hello-world"
|
|
82
|
+
# "Foo Bar Baz" -> "foo-bar-baz"
|
|
83
|
+
#
|
|
84
|
+
#: () -> String
|
|
85
|
+
def aref
|
|
86
|
+
self.class.to_label.convert text.dup
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# An HTML-safe anchor reference using legacy RDoc formatting:
|
|
90
|
+
# - Prefixed with "label-"
|
|
91
|
+
# - Original case preserved
|
|
92
|
+
# - Spaces converted to + (URL encoding style)
|
|
93
|
+
# - Special characters percent-encoded
|
|
94
|
+
#
|
|
95
|
+
# Returns nil if it would be the same as the GitHub-style aref (no alias needed).
|
|
96
|
+
#
|
|
97
|
+
# Examples:
|
|
98
|
+
# "hello" -> "label-hello" (different due to label- prefix)
|
|
99
|
+
# "Hello" -> "label-Hello"
|
|
100
|
+
# "Hello World" -> "label-Hello+World"
|
|
101
|
+
# "Foo Bar Baz" -> "label-Foo+Bar+Baz"
|
|
102
|
+
#
|
|
103
|
+
#: () -> String?
|
|
104
|
+
def legacy_aref
|
|
105
|
+
"label-#{self.class.to_label.convert_legacy text.dup}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Creates a fully-qualified label (GitHub-style) which includes the context's aref prefix.
|
|
109
|
+
# This helps keep IDs unique in HTML when headings appear within class/method documentation.
|
|
110
|
+
#
|
|
111
|
+
# Examples (without context):
|
|
112
|
+
# "Hello World" -> "hello-world"
|
|
113
|
+
#
|
|
114
|
+
# Examples (with context being class Foo):
|
|
115
|
+
# "Hello World" -> "class-foo-hello-world"
|
|
116
|
+
#
|
|
117
|
+
# Examples (with context being method #bar):
|
|
118
|
+
# "Hello World" -> "method-i-bar-hello-world"
|
|
119
|
+
#
|
|
120
|
+
#: (RDoc::Context?) -> String
|
|
121
|
+
def label(context = nil)
|
|
122
|
+
result = +""
|
|
123
|
+
result << "#{context.aref}-" if context&.respond_to?(:aref)
|
|
124
|
+
result << aref
|
|
125
|
+
result
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Creates a fully-qualified legacy label for backward compatibility.
|
|
129
|
+
# This is used to generate a secondary ID attribute on the heading's inner anchor,
|
|
130
|
+
# allowing old-style links (e.g., #label-Hello+World) to continue working.
|
|
131
|
+
#
|
|
132
|
+
# Examples (without context):
|
|
133
|
+
# "hello" -> "label-hello"
|
|
134
|
+
# "Hello World" -> "label-Hello+World"
|
|
135
|
+
#
|
|
136
|
+
# Examples (with context being class Foo):
|
|
137
|
+
# "hello" -> "class-Foo-label-hello"
|
|
138
|
+
# "Hello World" -> "class-Foo-label-Hello+World"
|
|
139
|
+
#
|
|
140
|
+
#: (RDoc::Context?) -> String
|
|
141
|
+
def legacy_label(context = nil)
|
|
142
|
+
result = +""
|
|
143
|
+
if context&.respond_to?(:legacy_aref)
|
|
144
|
+
result << "#{context.legacy_aref}-"
|
|
145
|
+
elsif context&.respond_to?(:aref)
|
|
146
|
+
result << "#{context.aref}-"
|
|
147
|
+
end
|
|
148
|
+
result << legacy_aref
|
|
149
|
+
result
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# HTML markup of the text of this label without the surrounding header element.
|
|
153
|
+
#: () -> String
|
|
154
|
+
def plain_html
|
|
155
|
+
no_image_text = text
|
|
156
|
+
|
|
157
|
+
if matched = no_image_text.match(/rdoc-image:[^:]+:(.*)/)
|
|
158
|
+
no_image_text = matched[1]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
self.class.to_html.to_html(no_image_text)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# @override
|
|
165
|
+
#: (PP) -> void
|
|
166
|
+
def pretty_print(q)
|
|
167
|
+
q.group 2, "[head: #{level} ", ']' do
|
|
168
|
+
q.pp text
|
|
169
|
+
end
|
|
170
|
+
end
|
|
73
171
|
end
|
|
74
|
-
|
|
75
|
-
self.class.to_html.to_html(text)
|
|
76
172
|
end
|
|
77
|
-
|
|
78
|
-
def pretty_print(q) # :nodoc:
|
|
79
|
-
q.group 2, "[head: #{level} ", ']' do
|
|
80
|
-
q.pp text
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
173
|
end
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
require 'strscan'
|
|
5
|
+
|
|
6
|
+
# Parses inline markup in RDoc text.
|
|
7
|
+
# This parser handles em, bold, strike, tt, hard break, and tidylink.
|
|
8
|
+
# Block-level constructs are handled in RDoc::Markup::Parser.
|
|
9
|
+
|
|
10
|
+
class RDoc::Markup::InlineParser
|
|
11
|
+
|
|
12
|
+
# TT, BOLD_WORD, EM_WORD: regexp-handling(example: crossref) is disabled
|
|
13
|
+
WORD_PAIRS = {
|
|
14
|
+
'*' => :BOLD_WORD,
|
|
15
|
+
'**' => :BOLD_WORD,
|
|
16
|
+
'_' => :EM_WORD,
|
|
17
|
+
'__' => :EM_WORD,
|
|
18
|
+
'+' => :TT,
|
|
19
|
+
'++' => :TT,
|
|
20
|
+
'`' => :TT,
|
|
21
|
+
'``' => :TT
|
|
22
|
+
} # :nodoc:
|
|
23
|
+
|
|
24
|
+
# Other types: regexp-handling(example: crossref) is enabled
|
|
25
|
+
TAGS = {
|
|
26
|
+
'em' => :EM,
|
|
27
|
+
'i' => :EM,
|
|
28
|
+
'b' => :BOLD,
|
|
29
|
+
's' => :STRIKE,
|
|
30
|
+
'del' => :STRIKE,
|
|
31
|
+
} # :nodoc:
|
|
32
|
+
|
|
33
|
+
STANDALONE_TAGS = { 'br' => :HARD_BREAK } # :nodoc:
|
|
34
|
+
|
|
35
|
+
CODEBLOCK_TAGS = %w[tt code] # :nodoc:
|
|
36
|
+
|
|
37
|
+
TOKENS = {
|
|
38
|
+
**WORD_PAIRS.transform_values { [:word_pair, nil] },
|
|
39
|
+
**TAGS.keys.to_h {|tag| ["<#{tag}>", [:open_tag, tag]] },
|
|
40
|
+
**TAGS.keys.to_h {|tag| ["</#{tag}>", [:close_tag, tag]] },
|
|
41
|
+
**CODEBLOCK_TAGS.to_h {|tag| ["<#{tag}>", [:code_start, tag]] },
|
|
42
|
+
**STANDALONE_TAGS.keys.to_h {|tag| ["<#{tag}>", [:standalone_tag, tag]] },
|
|
43
|
+
'{' => [:tidylink_start, nil],
|
|
44
|
+
'}' => [:tidylink_mid, nil],
|
|
45
|
+
'\\' => [:escape, nil],
|
|
46
|
+
'[' => nil # To make `label[url]` scan as separate tokens
|
|
47
|
+
} # :nodoc:
|
|
48
|
+
|
|
49
|
+
multi_char_tokens_regexp = Regexp.union(TOKENS.keys.select {|s| s.size > 1 }).source
|
|
50
|
+
token_starts_regexp = TOKENS.keys.map {|s| s[0] }.uniq.map {|s| Regexp.escape(s) }.join
|
|
51
|
+
|
|
52
|
+
SCANNER_REGEXP =
|
|
53
|
+
/(?:
|
|
54
|
+
#{multi_char_tokens_regexp}
|
|
55
|
+
|[^#{token_starts_regexp}\sa-zA-Z0-9\.]+ # chunk of normal text
|
|
56
|
+
|\s+|[a-zA-Z0-9\.]+|.
|
|
57
|
+
)/x # :nodoc:
|
|
58
|
+
|
|
59
|
+
# Characters that can be escaped with backslash.
|
|
60
|
+
ESCAPING_CHARS = '\\*_+`{}[]<>' # :nodoc:
|
|
61
|
+
|
|
62
|
+
# Pattern to match code block content until <code></tt></code> or <tt></code></tt>.
|
|
63
|
+
CODEBLOCK_REGEXPS = CODEBLOCK_TAGS.to_h {|name| [name, /((?:\\.|[^\\])*?)<\/#{name}>/] } # :nodoc:
|
|
64
|
+
|
|
65
|
+
# Word contains alphanumeric and <tt>_./:[]-</tt> characters.
|
|
66
|
+
# Word may start with <tt>#</tt> and may end with any non-space character. (e.g. <tt>#eql?</tt>).
|
|
67
|
+
# Underscore delimiter have special rules.
|
|
68
|
+
WORD_REGEXPS = {
|
|
69
|
+
# Words including _, longest match.
|
|
70
|
+
# Example: `_::A_` `_-42_` `_A::B::C.foo_bar[baz]_` `_kwarg:_`
|
|
71
|
+
# Content must not include _ followed by non-alphanumeric character
|
|
72
|
+
# Example: `_host_:_port_` will be `_host_` + `:` + `_port_`
|
|
73
|
+
'_' => /#?([a-zA-Z0-9.\/:\[\]-]|_+[a-zA-Z0-9])+[^\s]?_(?=[^a-zA-Z0-9_]|\z)/,
|
|
74
|
+
# Words allowing _ but not allowing __
|
|
75
|
+
'__' => /#?[a-zA-Z0-9.\/:\[\]-]*(_[a-zA-Z0-9.\/:\[\]-]+)*[^\s]?__(?=[^a-zA-Z0-9]|\z)/,
|
|
76
|
+
**%w[* ** + ++ ` ``].to_h do |s|
|
|
77
|
+
# normal words that can be used within +word+ or *word*
|
|
78
|
+
[s, /#?[a-zA-Z0-9_.\/:\[\]-]+[^\s]?#{Regexp.escape(s)}(?=[^a-zA-Z0-9]|\z)/]
|
|
79
|
+
end
|
|
80
|
+
} # :nodoc:
|
|
81
|
+
|
|
82
|
+
def initialize(string)
|
|
83
|
+
@scanner = StringScanner.new(string)
|
|
84
|
+
@last_match = nil
|
|
85
|
+
@scanner_negative_cache = Set.new
|
|
86
|
+
@stack = []
|
|
87
|
+
@delimiters = {}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Return the current parsing node on <tt>@stack</tt>.
|
|
91
|
+
|
|
92
|
+
def current
|
|
93
|
+
@stack.last
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Parse and return an array of nodes.
|
|
97
|
+
# Node format:
|
|
98
|
+
# {
|
|
99
|
+
# type: :EM | :BOLD | :BOLD_WORD | :EM_WORD | :TT | :STRIKE | :HARD_BREAK | :TIDYLINK,
|
|
100
|
+
# url: string # only for :TIDYLINK
|
|
101
|
+
# children: [string_or_node, ...]
|
|
102
|
+
# }
|
|
103
|
+
|
|
104
|
+
def parse
|
|
105
|
+
stack_push(:root, nil)
|
|
106
|
+
while true
|
|
107
|
+
type, token, value = scan_token
|
|
108
|
+
close = nil
|
|
109
|
+
tidylink_url = nil
|
|
110
|
+
case type
|
|
111
|
+
when :node
|
|
112
|
+
current[:children] << value
|
|
113
|
+
invalidate_open_tidylinks if value[:type] == :TIDYLINK
|
|
114
|
+
when :eof
|
|
115
|
+
close = :root
|
|
116
|
+
when :tidylink_open
|
|
117
|
+
stack_push(:tidylink, token)
|
|
118
|
+
when :tidylink_close
|
|
119
|
+
close = :tidylink
|
|
120
|
+
if value
|
|
121
|
+
tidylink_url = value
|
|
122
|
+
else
|
|
123
|
+
# Tidylink closing brace without URL part. Treat opening and closing braces as normal text
|
|
124
|
+
# `{labelnodes}...` case.
|
|
125
|
+
current[:children] << token
|
|
126
|
+
end
|
|
127
|
+
when :invalidated_tidylink_close
|
|
128
|
+
# `{...{label}[url]...}` case. Nested tidylink invalidates outer one. The last `}` closes the invalidated tidylink.
|
|
129
|
+
current[:children] << token
|
|
130
|
+
close = :invalidated_tidylink
|
|
131
|
+
when :text
|
|
132
|
+
current[:children] << token
|
|
133
|
+
when :open
|
|
134
|
+
stack_push(value, token)
|
|
135
|
+
when :close
|
|
136
|
+
if @delimiters[value]
|
|
137
|
+
close = value
|
|
138
|
+
else
|
|
139
|
+
# closing tag without matching opening tag. Treat as normal text.
|
|
140
|
+
current[:children] << token
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
next unless close
|
|
145
|
+
|
|
146
|
+
while current[:delimiter] != close
|
|
147
|
+
children = current[:children]
|
|
148
|
+
open_token = current[:token]
|
|
149
|
+
stack_pop
|
|
150
|
+
current[:children] << open_token if open_token
|
|
151
|
+
current[:children].concat(children)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
token = current[:token]
|
|
155
|
+
children = compact_string(current[:children])
|
|
156
|
+
stack_pop
|
|
157
|
+
|
|
158
|
+
return children if close == :root
|
|
159
|
+
|
|
160
|
+
if close == :tidylink || close == :invalidated_tidylink
|
|
161
|
+
if tidylink_url
|
|
162
|
+
current[:children] << { type: :TIDYLINK, children: children, url: tidylink_url }
|
|
163
|
+
invalidate_open_tidylinks
|
|
164
|
+
else
|
|
165
|
+
current[:children] << token
|
|
166
|
+
current[:children].concat(children)
|
|
167
|
+
end
|
|
168
|
+
else
|
|
169
|
+
current[:children] << { type: TAGS[close], children: children }
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
private
|
|
175
|
+
|
|
176
|
+
# When a valid tidylink node is encountered, invalidate all nested tidylinks.
|
|
177
|
+
|
|
178
|
+
def invalidate_open_tidylinks
|
|
179
|
+
return unless @delimiters[:tidylink]
|
|
180
|
+
|
|
181
|
+
@delimiters[:invalidated_tidylink] ||= []
|
|
182
|
+
@delimiters[:tidylink].each do |idx|
|
|
183
|
+
@delimiters[:invalidated_tidylink] << idx
|
|
184
|
+
@stack[idx][:delimiter] = :invalidated_tidylink
|
|
185
|
+
end
|
|
186
|
+
@delimiters.delete(:tidylink)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Pop the top node off the stack when node is closed by a closing delimiter or an error.
|
|
190
|
+
|
|
191
|
+
def stack_pop
|
|
192
|
+
delimiter = current[:delimiter]
|
|
193
|
+
@delimiters[delimiter].pop
|
|
194
|
+
@delimiters.delete(delimiter) if @delimiters[delimiter].empty?
|
|
195
|
+
@stack.pop
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Push a new node onto the stack when encountering an opening delimiter.
|
|
199
|
+
|
|
200
|
+
def stack_push(delimiter, token)
|
|
201
|
+
node = { delimiter: delimiter, token: token, children: [] }
|
|
202
|
+
(@delimiters[delimiter] ||= []) << @stack.size
|
|
203
|
+
@stack << node
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Compacts adjacent strings in +nodes+ into a single string.
|
|
207
|
+
|
|
208
|
+
def compact_string(nodes)
|
|
209
|
+
nodes.chunk {|e| String === e }.flat_map do |is_str, elems|
|
|
210
|
+
is_str ? elems.join : elems
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Scan from StringScanner with +pattern+
|
|
215
|
+
# If +negative_cache+ is true, caches scan failure result. <tt>scan(pattern, negative_cache: true)</tt> return nil when it is called again after a failure.
|
|
216
|
+
# Be careful to use +negative_cache+ with a pattern and position that does not match after previous failure.
|
|
217
|
+
|
|
218
|
+
def strscan(pattern, negative_cache: false)
|
|
219
|
+
return if negative_cache && @scanner_negative_cache.include?(pattern)
|
|
220
|
+
|
|
221
|
+
string = @scanner.scan(pattern)
|
|
222
|
+
@last_match = string if string
|
|
223
|
+
@scanner_negative_cache << pattern if !string && negative_cache
|
|
224
|
+
string
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Scan and return the next token for parsing.
|
|
228
|
+
# Returns <tt>[token_type, token_string_or_nil, extra_info]</tt>
|
|
229
|
+
|
|
230
|
+
def scan_token
|
|
231
|
+
last_match = @last_match
|
|
232
|
+
token = strscan(SCANNER_REGEXP)
|
|
233
|
+
type, name = TOKENS[token]
|
|
234
|
+
|
|
235
|
+
case type
|
|
236
|
+
when :word_pair
|
|
237
|
+
# If the character before word pair delimiter is alphanumeric, do not treat as word pair.
|
|
238
|
+
word_pair = strscan(WORD_REGEXPS[token]) unless /[a-zA-Z0-9]\z/.match?(last_match)
|
|
239
|
+
|
|
240
|
+
if word_pair.nil?
|
|
241
|
+
[:text, token, nil]
|
|
242
|
+
elsif token == '__' && word_pair.match?(/\A[a-zA-Z]+__\z/)
|
|
243
|
+
# Special exception: __FILE__, __LINE__, __send__ should be treated as normal text.
|
|
244
|
+
[:text, "#{token}#{word_pair}", nil]
|
|
245
|
+
else
|
|
246
|
+
[:node, nil, { type: WORD_PAIRS[token], children: [word_pair.delete_suffix(token)] }]
|
|
247
|
+
end
|
|
248
|
+
when :open_tag
|
|
249
|
+
[:open, token, name]
|
|
250
|
+
when :close_tag
|
|
251
|
+
[:close, token, name]
|
|
252
|
+
when :code_start
|
|
253
|
+
if (codeblock = strscan(CODEBLOCK_REGEXPS[name], negative_cache: true))
|
|
254
|
+
# Need to unescape `\\` and `\<`.
|
|
255
|
+
# RDoc also unescapes backslash + word separators, but this is not really necessary.
|
|
256
|
+
content = codeblock.delete_suffix("</#{name}>").gsub(/\\(.)/) { '\\<*+_`'.include?($1) ? $1 : $& }
|
|
257
|
+
[:node, nil, { type: :TT, children: content.empty? ? [] : [content] }]
|
|
258
|
+
else
|
|
259
|
+
[:text, token, nil]
|
|
260
|
+
end
|
|
261
|
+
when :standalone_tag
|
|
262
|
+
[:node, nil, { type: STANDALONE_TAGS[name], children: [] }]
|
|
263
|
+
when :tidylink_start
|
|
264
|
+
[:tidylink_open, token, nil]
|
|
265
|
+
when :tidylink_mid
|
|
266
|
+
if @delimiters[:tidylink]
|
|
267
|
+
if (url = read_tidylink_url)
|
|
268
|
+
[:tidylink_close, nil, url]
|
|
269
|
+
else
|
|
270
|
+
[:tidylink_close, token, nil]
|
|
271
|
+
end
|
|
272
|
+
elsif @delimiters[:invalidated_tidylink]
|
|
273
|
+
[:invalidated_tidylink_close, token, nil]
|
|
274
|
+
else
|
|
275
|
+
[:text, token, nil]
|
|
276
|
+
end
|
|
277
|
+
when :escape
|
|
278
|
+
next_char = strscan(/./)
|
|
279
|
+
if next_char.nil?
|
|
280
|
+
# backslash at end of string
|
|
281
|
+
[:text, '\\', nil]
|
|
282
|
+
elsif next_char && ESCAPING_CHARS.include?(next_char)
|
|
283
|
+
# escaped character
|
|
284
|
+
[:text, next_char, nil]
|
|
285
|
+
else
|
|
286
|
+
# If next_char not an escaping character, it is treated as text token with backslash + next_char
|
|
287
|
+
# For example, backslash of `\Ruby` (suppressed crossref) remains.
|
|
288
|
+
[:text, "\\#{next_char}", nil]
|
|
289
|
+
end
|
|
290
|
+
else
|
|
291
|
+
if token.nil?
|
|
292
|
+
[:eof, nil, nil]
|
|
293
|
+
elsif token.match?(/\A[A-Za-z0-9]*\z/) && (url = read_tidylink_url)
|
|
294
|
+
# Simplified tidylink: label[url]
|
|
295
|
+
[:node, nil, { type: :TIDYLINK, children: [token], url: url }]
|
|
296
|
+
else
|
|
297
|
+
[:text, token, nil]
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Read the URL part of a tidylink from the current position.
|
|
303
|
+
# Returns nil if no valid URL part is found.
|
|
304
|
+
# URL part is enclosed in square brackets and may contain escaped brackets.
|
|
305
|
+
# Example: <tt>[http://example.com/?q=\[\]]</tt> represents <tt>http://example.com/?q=[]</tt>.
|
|
306
|
+
# If we're accepting rdoc-style links in markdown, url may include <tt>*+<_</tt> with backslash escape.
|
|
307
|
+
|
|
308
|
+
def read_tidylink_url
|
|
309
|
+
bracketed_url = strscan(/\[([^\s\[\]\\]|\\[\[\]\\*+<_])+\]/)
|
|
310
|
+
bracketed_url[1...-1].gsub(/\\(.)/, '\1') if bracketed_url
|
|
311
|
+
end
|
|
312
|
+
end
|
data/lib/rdoc/markup/parser.rb
CHANGED
|
@@ -11,7 +11,7 @@ require 'strscan'
|
|
|
11
11
|
# The parser only handles the block-level constructs Paragraph, List,
|
|
12
12
|
# ListItem, Heading, Verbatim, BlankLine, Rule and BlockQuote.
|
|
13
13
|
# Inline markup such as <tt>\+blah\+</tt> is handled separately by
|
|
14
|
-
# RDoc::Markup::
|
|
14
|
+
# RDoc::Markup::InlineParser.
|
|
15
15
|
#
|
|
16
16
|
# To see what markup the Parser implements read RDoc. To see how to use
|
|
17
17
|
# RDoc markup to format text in your program read RDoc::Markup.
|
data/lib/rdoc/markup/raw.rb
CHANGED
|
@@ -1,69 +1,66 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
##
|
|
3
|
-
# A section of text that is added to the output document as-is
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
##
|
|
21
|
-
# Appends +text+
|
|
3
|
+
module RDoc
|
|
4
|
+
class Markup
|
|
5
|
+
# A section of text that is added to the output document as-is
|
|
6
|
+
class Raw
|
|
7
|
+
# The component parts of the list
|
|
8
|
+
#: Array[String]
|
|
9
|
+
attr_reader :parts
|
|
10
|
+
|
|
11
|
+
# Creates a new Raw containing +parts+
|
|
12
|
+
#: (*String) -> void
|
|
13
|
+
def initialize(*parts)
|
|
14
|
+
@parts = parts
|
|
15
|
+
end
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
# Appends +text+
|
|
18
|
+
#: (String) -> void
|
|
19
|
+
def <<(text)
|
|
20
|
+
@parts << text
|
|
21
|
+
end
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
#: (top) -> bool
|
|
24
|
+
def ==(other) # :nodoc:
|
|
25
|
+
self.class == other.class && @parts == other.parts
|
|
26
|
+
end
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
# Calls #accept_raw+ on +visitor+
|
|
29
|
+
# @override
|
|
30
|
+
#: (untyped) -> void
|
|
31
|
+
def accept(visitor)
|
|
32
|
+
visitor.accept_raw(self)
|
|
33
|
+
end
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
# Appends +other+'s parts
|
|
36
|
+
#: (Raw) -> void
|
|
37
|
+
def merge(other)
|
|
38
|
+
@parts.concat(other.parts)
|
|
39
|
+
end
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
# @override
|
|
42
|
+
#: (PP) -> void
|
|
43
|
+
def pretty_print(q) # :nodoc:
|
|
44
|
+
self.class.name =~ /.*::(\w{1,4})/i
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
q.group(2, "[#{$1.downcase}: ", ']') do
|
|
47
|
+
q.seplist(@parts) do |part|
|
|
48
|
+
q.pp(part)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
# Appends +texts+ onto this Paragraph
|
|
54
|
+
#: (*String) -> void
|
|
55
|
+
def push(*texts)
|
|
56
|
+
self.parts.concat(texts)
|
|
57
|
+
end
|
|
47
58
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
# The raw text
|
|
60
|
+
#: () -> String
|
|
61
|
+
def text
|
|
62
|
+
@parts.join(" ")
|
|
51
63
|
end
|
|
52
64
|
end
|
|
53
65
|
end
|
|
54
|
-
|
|
55
|
-
##
|
|
56
|
-
# Appends +texts+ onto this Paragraph
|
|
57
|
-
|
|
58
|
-
def push *texts
|
|
59
|
-
self.parts.concat texts
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
##
|
|
63
|
-
# The raw text
|
|
64
|
-
|
|
65
|
-
def text
|
|
66
|
-
@parts.join ' '
|
|
67
|
-
end
|
|
68
|
-
|
|
69
66
|
end
|