rdoc 7.0.3 → 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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +70 -4
  3. data/doc/markup_reference/markdown.md +558 -0
  4. data/doc/markup_reference/rdoc.rdoc +1169 -0
  5. data/lib/rdoc/code_object/attr.rb +2 -1
  6. data/lib/rdoc/code_object/class_module.rb +24 -3
  7. data/lib/rdoc/code_object/context/section.rb +46 -9
  8. data/lib/rdoc/code_object/context.rb +15 -4
  9. data/lib/rdoc/code_object/mixin.rb +3 -0
  10. data/lib/rdoc/code_object/top_level.rb +2 -0
  11. data/lib/rdoc/comment.rb +1 -1
  12. data/lib/rdoc/cross_reference.rb +31 -24
  13. data/lib/rdoc/generator/template/aliki/_head.rhtml +5 -0
  14. data/lib/rdoc/generator/template/aliki/class.rhtml +8 -6
  15. data/lib/rdoc/generator/template/aliki/css/rdoc.css +48 -36
  16. data/lib/rdoc/generator/template/aliki/js/aliki.js +8 -2
  17. data/lib/rdoc/generator/template/aliki/js/bash_highlighter.js +167 -0
  18. data/lib/rdoc/generator/template/aliki/js/c_highlighter.js +1 -1
  19. data/lib/rdoc/generator/template/aliki/js/search_controller.js +1 -1
  20. data/lib/rdoc/generator/template/darkfish/class.rhtml +2 -0
  21. data/lib/rdoc/generator/template/darkfish/css/rdoc.css +19 -0
  22. data/lib/rdoc/markdown.kpeg +22 -12
  23. data/lib/rdoc/markdown.rb +36 -26
  24. data/lib/rdoc/markup/formatter.rb +129 -106
  25. data/lib/rdoc/markup/heading.rb +101 -29
  26. data/lib/rdoc/markup/inline_parser.rb +312 -0
  27. data/lib/rdoc/markup/parser.rb +1 -1
  28. data/lib/rdoc/markup/to_ansi.rb +51 -4
  29. data/lib/rdoc/markup/to_bs.rb +22 -42
  30. data/lib/rdoc/markup/to_html.rb +178 -183
  31. data/lib/rdoc/markup/to_html_crossref.rb +58 -79
  32. data/lib/rdoc/markup/to_html_snippet.rb +62 -62
  33. data/lib/rdoc/markup/to_label.rb +29 -20
  34. data/lib/rdoc/markup/to_markdown.rb +61 -37
  35. data/lib/rdoc/markup/to_rdoc.rb +86 -26
  36. data/lib/rdoc/markup/to_test.rb +9 -1
  37. data/lib/rdoc/markup/to_tt_only.rb +10 -16
  38. data/lib/rdoc/markup/verbatim.rb +1 -1
  39. data/lib/rdoc/markup.rb +10 -32
  40. data/lib/rdoc/parser/changelog.rb +29 -0
  41. data/lib/rdoc/parser/prism_ruby.rb +44 -32
  42. data/lib/rdoc/parser/ruby.rb +1 -1
  43. data/lib/rdoc/text.rb +44 -5
  44. data/lib/rdoc/token_stream.rb +4 -8
  45. data/lib/rdoc/version.rb +1 -1
  46. data/rdoc.gemspec +2 -2
  47. metadata +7 -12
  48. data/ExampleMarkdown.md +0 -39
  49. data/ExampleRDoc.rdoc +0 -210
  50. data/lib/rdoc/markup/attr_changer.rb +0 -22
  51. data/lib/rdoc/markup/attr_span.rb +0 -35
  52. data/lib/rdoc/markup/attribute_manager.rb +0 -405
  53. data/lib/rdoc/markup/attributes.rb +0 -70
  54. data/lib/rdoc/markup/regexp_handling.rb +0 -40
@@ -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.
@@ -19,10 +19,57 @@ class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc
19
19
  ##
20
20
  # Maps attributes to ANSI sequences
21
21
 
22
- def init_tags
23
- add_tag :BOLD, "\e[1m", "\e[m"
24
- add_tag :TT, "\e[7m", "\e[m"
25
- add_tag :EM, "\e[4m", "\e[m"
22
+ ANSI_STYLE_CODES_ON = {
23
+ BOLD: 1,
24
+ TT: 7,
25
+ EM: 4,
26
+ STRIKE: 9
27
+ }
28
+
29
+ ANSI_STYLE_CODES_OFF = {
30
+ BOLD: 22,
31
+ TT: 27,
32
+ EM: 24,
33
+ STRIKE: 29
34
+ }
35
+
36
+ # Apply the given attributes by emitting ANSI sequences.
37
+ # Emitting attribute changes are deferred until new text is added and applied in batch.
38
+ # This method computes the necessary ANSI codes to transition from the
39
+ # current set of applied attributes to the new set of +attributes+.
40
+
41
+ def apply_attributes(attributes)
42
+ before = @applied_attributes
43
+ after = attributes.sort
44
+ return if before == after
45
+
46
+ if after.empty?
47
+ emit_inline("\e[m")
48
+ elsif !before.empty? && before.size > (before & after).size + 1
49
+ codes = after.map {|attr| ANSI_STYLE_CODES_ON[attr] }.compact
50
+ emit_inline("\e[#{[0, *codes].join(';')}m")
51
+ else
52
+ off_codes = (before - after).map {|attr| ANSI_STYLE_CODES_OFF[attr] }.compact
53
+ on_codes = (after - before).map {|attr| ANSI_STYLE_CODES_ON[attr] }.compact
54
+ emit_inline("\e[#{(off_codes + on_codes).join(';')}m")
55
+ end
56
+ @applied_attributes = attributes
57
+ end
58
+
59
+ def add_text(text)
60
+ attrs = @attributes.keys
61
+ if @applied_attributes != attrs
62
+ apply_attributes(attrs)
63
+ end
64
+ emit_inline(text)
65
+ end
66
+
67
+ def handle_inline(text)
68
+ @applied_attributes = []
69
+ res = super
70
+ res << "\e[m" unless @applied_attributes.empty?
71
+ @applied_attributes = []
72
+ res
26
73
  end
27
74
 
28
75
  ##
@@ -17,14 +17,29 @@ class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc
17
17
  @in_em = false
18
18
  end
19
19
 
20
- ##
21
- # Sets a flag that is picked up by #annotate to do the right thing in
22
- # #convert_string
20
+ def handle_inline(text)
21
+ initial_style = []
22
+ initial_style << :BOLD if @in_b
23
+ initial_style << :EM if @in_em
24
+ super(text, initial_style)
25
+ end
23
26
 
24
- def init_tags
25
- add_tag :BOLD, '+b', '-b'
26
- add_tag :EM, '+_', '-_'
27
- add_tag :TT, '', '' # we need in_tt information maintained
27
+ def add_text(text)
28
+ attrs = @attributes.keys
29
+ if attrs.include? :BOLD
30
+ styled = +''
31
+ text.chars.each do |c|
32
+ styled << "#{c}\b#{c}"
33
+ end
34
+ text = styled
35
+ elsif attrs.include? :EM
36
+ styled = +''
37
+ text.chars.each do |c|
38
+ styled << "_\b#{c}"
39
+ end
40
+ text = styled
41
+ end
42
+ emit_inline(text)
28
43
  end
29
44
 
30
45
  ##
@@ -68,39 +83,4 @@ class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc
68
83
  def calculate_text_width(text)
69
84
  text.gsub(/_\x08/, '').gsub(/\x08./, '').size
70
85
  end
71
-
72
- ##
73
- # Turns on or off regexp handling for +convert_string+
74
-
75
- def annotate(tag)
76
- case tag
77
- when '+b' then @in_b = true
78
- when '-b' then @in_b = false
79
- when '+_' then @in_em = true
80
- when '-_' then @in_em = false
81
- end
82
- ''
83
- end
84
-
85
- ##
86
- # Calls convert_string on the result of convert_regexp_handling
87
-
88
- def convert_regexp_handling(target)
89
- convert_string super
90
- end
91
-
92
- ##
93
- # Adds bold or underline mixed with backspaces
94
-
95
- def convert_string(string)
96
- return string unless @in_b or @in_em
97
- chars = if @in_b then
98
- string.chars.map do |char| "#{char}\b#{char}" end
99
- elsif @in_em then
100
- string.chars.map do |char| "_\b#{char}" end
101
- end
102
-
103
- chars.join
104
- end
105
-
106
86
  end