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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +196 -0
  3. data/LEGAL.rdoc +6 -0
  4. data/README.md +90 -7
  5. data/doc/markup_reference/markdown.md +558 -0
  6. data/doc/markup_reference/rdoc.rdoc +1169 -0
  7. data/lib/rdoc/code_object/any_method.rb +15 -7
  8. data/lib/rdoc/code_object/attr.rb +2 -1
  9. data/lib/rdoc/code_object/class_module.rb +62 -11
  10. data/lib/rdoc/code_object/constant.rb +9 -0
  11. data/lib/rdoc/code_object/context/section.rb +46 -9
  12. data/lib/rdoc/code_object/context.rb +15 -4
  13. data/lib/rdoc/code_object/method_attr.rb +13 -1
  14. data/lib/rdoc/code_object/mixin.rb +3 -0
  15. data/lib/rdoc/code_object/top_level.rb +15 -1
  16. data/lib/rdoc/comment.rb +1 -1
  17. data/lib/rdoc/cross_reference.rb +31 -24
  18. data/lib/rdoc/generator/aliki.rb +141 -0
  19. data/lib/rdoc/generator/darkfish.rb +3 -1
  20. data/lib/rdoc/generator/template/aliki/_footer.rhtml +1 -1
  21. data/lib/rdoc/generator/template/aliki/_head.rhtml +9 -4
  22. data/lib/rdoc/generator/template/aliki/_header.rhtml +4 -4
  23. data/lib/rdoc/generator/template/aliki/_sidebar_pages.rhtml +1 -1
  24. data/lib/rdoc/generator/template/aliki/_sidebar_search.rhtml +3 -3
  25. data/lib/rdoc/generator/template/aliki/class.rhtml +17 -17
  26. data/lib/rdoc/generator/template/aliki/css/rdoc.css +278 -60
  27. data/lib/rdoc/generator/template/aliki/index.rhtml +1 -1
  28. data/lib/rdoc/generator/template/aliki/js/aliki.js +51 -23
  29. data/lib/rdoc/generator/template/aliki/js/bash_highlighter.js +167 -0
  30. data/lib/rdoc/generator/template/aliki/js/c_highlighter.js +1 -1
  31. data/lib/rdoc/generator/template/aliki/js/{search.js → search_controller.js} +16 -7
  32. data/lib/rdoc/generator/template/aliki/js/search_navigation.js +105 -0
  33. data/lib/rdoc/generator/template/aliki/js/search_ranker.js +239 -0
  34. data/lib/rdoc/generator/template/aliki/page.rhtml +1 -1
  35. data/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml +1 -1
  36. data/lib/rdoc/generator/template/aliki/servlet_root.rhtml +1 -1
  37. data/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml +1 -1
  38. data/lib/rdoc/generator/template/darkfish/class.rhtml +11 -11
  39. data/lib/rdoc/generator/template/darkfish/css/rdoc.css +19 -0
  40. data/lib/rdoc/markdown.kpeg +22 -12
  41. data/lib/rdoc/markdown.rb +36 -26
  42. data/lib/rdoc/markup/blank_line.rb +25 -23
  43. data/lib/rdoc/markup/element.rb +21 -0
  44. data/lib/rdoc/markup/formatter.rb +129 -106
  45. data/lib/rdoc/markup/hard_break.rb +30 -27
  46. data/lib/rdoc/markup/heading.rb +166 -77
  47. data/lib/rdoc/markup/inline_parser.rb +312 -0
  48. data/lib/rdoc/markup/parser.rb +1 -1
  49. data/lib/rdoc/markup/raw.rb +52 -55
  50. data/lib/rdoc/markup/table.rb +48 -40
  51. data/lib/rdoc/markup/to_ansi.rb +51 -4
  52. data/lib/rdoc/markup/to_bs.rb +22 -42
  53. data/lib/rdoc/markup/to_html.rb +178 -183
  54. data/lib/rdoc/markup/to_html_crossref.rb +58 -79
  55. data/lib/rdoc/markup/to_html_snippet.rb +62 -62
  56. data/lib/rdoc/markup/to_label.rb +29 -20
  57. data/lib/rdoc/markup/to_markdown.rb +61 -37
  58. data/lib/rdoc/markup/to_rdoc.rb +86 -26
  59. data/lib/rdoc/markup/to_test.rb +9 -1
  60. data/lib/rdoc/markup/to_tt_only.rb +10 -16
  61. data/lib/rdoc/markup/verbatim.rb +1 -1
  62. data/lib/rdoc/markup.rb +11 -32
  63. data/lib/rdoc/options.rb +1 -1
  64. data/lib/rdoc/parser/changelog.rb +29 -0
  65. data/lib/rdoc/parser/prism_ruby.rb +44 -32
  66. data/lib/rdoc/parser/ruby.rb +1 -1
  67. data/lib/rdoc/rubygems_hook.rb +3 -3
  68. data/lib/rdoc/text.rb +44 -5
  69. data/lib/rdoc/token_stream.rb +4 -8
  70. data/lib/rdoc/version.rb +1 -1
  71. data/rdoc.gemspec +3 -3
  72. metadata +13 -15
  73. data/CONTRIBUTING.rdoc +0 -219
  74. data/ExampleMarkdown.md +0 -39
  75. data/ExampleRDoc.rdoc +0 -210
  76. data/lib/rdoc/markup/attr_changer.rb +0 -22
  77. data/lib/rdoc/markup/attr_span.rb +0 -35
  78. data/lib/rdoc/markup/attribute_manager.rb +0 -405
  79. data/lib/rdoc/markup/attributes.rb +0 -70
  80. data/lib/rdoc/markup/regexp_handling.rb +0 -40
@@ -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::Markup::Heading =
6
- Struct.new :level, :text do
7
-
8
- @to_html = nil
9
- @to_label = nil
10
-
11
- ##
12
- # A singleton RDoc::Markup::ToLabel formatter for headings.
13
-
14
- def self.to_label
15
- @to_label ||= RDoc::Markup::ToLabel.new
16
- end
17
-
18
- ##
19
- # A singleton plain HTML formatter for headings. Used for creating labels
20
- # for the Table of Contents
21
-
22
- def self.to_html
23
- return @to_html if @to_html
24
-
25
- markup = RDoc::Markup.new
26
- markup.add_regexp_handling RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF
27
-
28
- @to_html = RDoc::Markup::ToHtml.new nil
29
-
30
- def @to_html.handle_regexp_CROSSREF(target)
31
- target.text.sub(/^\\/, '')
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
- @to_html
35
- end
36
-
37
- ##
38
- # Calls #accept_heading on +visitor+
39
-
40
- def accept(visitor)
41
- visitor.accept_heading self
42
- end
43
-
44
- ##
45
- # An HTML-safe anchor reference for this header.
46
-
47
- def aref
48
- "label-#{self.class.to_label.convert text.dup}"
49
- end
50
-
51
- ##
52
- # Creates a fully-qualified label which will include the label from
53
- # +context+. This helps keep ids unique in HTML.
54
-
55
- def label(context = nil)
56
- label = aref
57
-
58
- label = [context.aref, label].compact.join '-' if
59
- context and context.respond_to? :aref
60
-
61
- label
62
- end
63
-
64
- ##
65
- # HTML markup of the text of this label without the surrounding header
66
- # element.
67
-
68
- def plain_html
69
- text = self.text.dup
70
-
71
- if matched = text.match(/rdoc-image:[^:]+:(.*)/)
72
- text = matched[1]
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
@@ -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::AttributeManager.
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.
@@ -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
- class RDoc::Markup::Raw
6
-
7
- ##
8
- # The component parts of the list
9
-
10
- attr_reader :parts
11
-
12
- ##
13
- # Creates a new Raw containing +parts+
14
-
15
- def initialize *parts
16
- @parts = []
17
- @parts.concat parts
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
- def <<(text)
24
- @parts << text
25
- end
17
+ # Appends +text+
18
+ #: (String) -> void
19
+ def <<(text)
20
+ @parts << text
21
+ end
26
22
 
27
- def ==(other) # :nodoc:
28
- self.class == other.class and @parts == other.parts
29
- end
23
+ #: (top) -> bool
24
+ def ==(other) # :nodoc:
25
+ self.class == other.class && @parts == other.parts
26
+ end
30
27
 
31
- ##
32
- # Calls #accept_raw+ on +visitor+
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
- def accept(visitor)
35
- visitor.accept_raw self
36
- end
35
+ # Appends +other+'s parts
36
+ #: (Raw) -> void
37
+ def merge(other)
38
+ @parts.concat(other.parts)
39
+ end
37
40
 
38
- ##
39
- # Appends +other+'s parts
41
+ # @override
42
+ #: (PP) -> void
43
+ def pretty_print(q) # :nodoc:
44
+ self.class.name =~ /.*::(\w{1,4})/i
40
45
 
41
- def merge(other)
42
- @parts.concat other.parts
43
- end
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
- def pretty_print(q) # :nodoc:
46
- self.class.name =~ /.*::(\w{1,4})/i
53
+ # Appends +texts+ onto this Paragraph
54
+ #: (*String) -> void
55
+ def push(*texts)
56
+ self.parts.concat(texts)
57
+ end
47
58
 
48
- q.group 2, "[#{$1.downcase}: ", ']' do
49
- q.seplist @parts do |part|
50
- q.pp part
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