kramdown 2.0.0 → 2.3.1

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTERS +13 -3
  3. data/README.md +8 -2
  4. data/VERSION +1 -1
  5. data/bin/kramdown +48 -19
  6. data/lib/kramdown/converter/base.rb +2 -1
  7. data/lib/kramdown/converter/html.rb +36 -29
  8. data/lib/kramdown/converter/kramdown.rb +12 -7
  9. data/lib/kramdown/converter/latex.rb +2 -2
  10. data/lib/kramdown/converter/math_engine/mathjax.rb +7 -33
  11. data/lib/kramdown/converter/syntax_highlighter.rb +1 -1
  12. data/lib/kramdown/converter/syntax_highlighter/rouge.rb +20 -10
  13. data/lib/kramdown/element.rb +24 -0
  14. data/lib/kramdown/options.rb +53 -12
  15. data/lib/kramdown/parser/base.rb +3 -1
  16. data/lib/kramdown/parser/html.rb +8 -8
  17. data/lib/kramdown/parser/kramdown.rb +8 -1
  18. data/lib/kramdown/parser/kramdown/abbreviation.rb +1 -1
  19. data/lib/kramdown/parser/kramdown/autolink.rb +2 -2
  20. data/lib/kramdown/parser/kramdown/blank_line.rb +2 -2
  21. data/lib/kramdown/parser/kramdown/block_boundary.rb +3 -2
  22. data/lib/kramdown/parser/kramdown/codespan.rb +13 -3
  23. data/lib/kramdown/parser/kramdown/emphasis.rb +1 -1
  24. data/lib/kramdown/parser/kramdown/extensions.rb +13 -7
  25. data/lib/kramdown/parser/kramdown/header.rb +3 -2
  26. data/lib/kramdown/parser/kramdown/html.rb +4 -10
  27. data/lib/kramdown/parser/kramdown/link.rb +3 -2
  28. data/lib/kramdown/parser/kramdown/list.rb +64 -33
  29. data/lib/kramdown/parser/kramdown/math.rb +1 -1
  30. data/lib/kramdown/parser/kramdown/paragraph.rb +3 -3
  31. data/lib/kramdown/parser/kramdown/table.rb +3 -3
  32. data/lib/kramdown/utils/html.rb +9 -0
  33. data/lib/kramdown/version.rb +1 -1
  34. data/man/man1/kramdown.1 +22 -1
  35. data/test/test_files.rb +27 -18
  36. data/test/test_location.rb +2 -2
  37. data/test/test_string_scanner_kramdown.rb +1 -1
  38. data/test/testcases/block/03_paragraph/standalone_image.html +5 -0
  39. data/test/testcases/block/03_paragraph/standalone_image.text +3 -0
  40. data/test/testcases/block/04_header/atx_header.html +6 -0
  41. data/test/testcases/block/04_header/atx_header.text +6 -0
  42. data/test/testcases/block/06_codeblock/guess_lang_css_class.html +15 -0
  43. data/test/testcases/block/06_codeblock/guess_lang_css_class.options +2 -0
  44. data/test/testcases/block/06_codeblock/guess_lang_css_class.text +13 -0
  45. data/test/testcases/block/06_codeblock/rouge/multiple.html +1 -1
  46. data/test/testcases/block/06_codeblock/rouge/simple.html +1 -1
  47. data/test/testcases/block/09_html/processing_instruction.html +5 -6
  48. data/test/testcases/block/09_html/standalone_image_in_div.htmlinput +7 -0
  49. data/test/testcases/block/09_html/standalone_image_in_div.text +8 -0
  50. data/test/testcases/block/09_html/table.kramdown +8 -0
  51. data/test/testcases/block/09_html/table.text +7 -0
  52. data/test/testcases/block/12_extension/options.html +4 -4
  53. data/test/testcases/block/12_extension/options.text +2 -0
  54. data/test/testcases/block/12_extension/options2.html +4 -4
  55. data/test/testcases/block/14_table/table_with_footnote.html +4 -4
  56. data/test/testcases/block/15_math/gh_128.html +1 -2
  57. data/test/testcases/block/15_math/normal.html +16 -15
  58. data/test/testcases/block/16_toc/toc_with_footnotes.html +4 -4
  59. data/test/testcases/cjk-line-break.html +4 -0
  60. data/test/testcases/cjk-line-break.options +1 -0
  61. data/test/testcases/cjk-line-break.text +12 -0
  62. data/test/testcases/span/02_emphasis/normal.html +4 -0
  63. data/test/testcases/span/02_emphasis/normal.text +4 -0
  64. data/test/testcases/span/03_codespan/normal-css-class.html +1 -0
  65. data/test/testcases/span/03_codespan/normal-css-class.options +2 -0
  66. data/test/testcases/span/03_codespan/normal-css-class.text +1 -0
  67. data/test/testcases/span/03_codespan/normal.html +4 -0
  68. data/test/testcases/span/03_codespan/normal.text +4 -0
  69. data/test/testcases/span/04_footnote/backlink_inline.html +21 -21
  70. data/test/testcases/span/04_footnote/backlink_text.html +4 -4
  71. data/test/testcases/span/04_footnote/footnote_nr.html +6 -6
  72. data/test/testcases/span/04_footnote/footnote_prefix.html +6 -6
  73. data/test/testcases/span/04_footnote/inside_footnote.html +9 -9
  74. data/test/testcases/span/04_footnote/markers.html +16 -16
  75. data/test/testcases/span/04_footnote/placement.html +4 -4
  76. data/test/testcases/span/04_footnote/regexp_problem.html +4 -4
  77. data/test/testcases/span/04_footnote/without_backlink.html +3 -3
  78. data/test/testcases/span/05_html/normal.html +1 -1
  79. data/test/testcases/span/abbreviations/abbrev_in_html.html +9 -0
  80. data/test/testcases/span/abbreviations/abbrev_in_html.text +10 -0
  81. data/test/testcases/span/abbreviations/in_footnote.html +4 -4
  82. data/test/testcases/span/math/normal.html +4 -4
  83. data/test/testcases/span/text_substitutions/entities.html +1 -1
  84. data/test/testcases/span/text_substitutions/entities.text +1 -1
  85. metadata +40 -15
  86. data/test/testcases/block/15_math/mathjax_preview.html +0 -4
  87. data/test/testcases/block/15_math/mathjax_preview.options +0 -2
  88. data/test/testcases/block/15_math/mathjax_preview.text +0 -5
  89. data/test/testcases/block/15_math/mathjax_preview_as_code.html +0 -4
  90. data/test/testcases/block/15_math/mathjax_preview_as_code.options +0 -3
  91. data/test/testcases/block/15_math/mathjax_preview_as_code.text +0 -5
  92. data/test/testcases/block/15_math/mathjax_preview_simple.html +0 -4
  93. data/test/testcases/block/15_math/mathjax_preview_simple.options +0 -2
  94. data/test/testcases/block/15_math/mathjax_preview_simple.text +0 -5
@@ -42,7 +42,7 @@ module Kramdown
42
42
  #
43
43
  # == Special Implementation Details
44
44
  #
45
- # HTML converter:: If the syntax highlighter is used with a HTML converter, it should return
45
+ # HTML converter:: If the syntax highlighter is used with an HTML converter, it should return
46
46
  # :block type text correctly wrapped (i.e. normally inside a pre-tag, but may
47
47
  # also be a table-tag or just a div-tag) but :span type text *without* a
48
48
  # code-tag!
@@ -25,8 +25,10 @@ module Kramdown::Converter::SyntaxHighlighter
25
25
  opts = options(converter, type)
26
26
  call_opts[:default_lang] = opts[:default_lang]
27
27
  return nil unless lang || opts[:default_lang] || opts[:guess_lang]
28
+
28
29
  lexer = ::Rouge::Lexer.find_fancy(lang || opts[:default_lang], text)
29
- return nil if opts[:disable] || !lexer || lexer.tag == "plaintext"
30
+ return nil if opts[:disable] || !lexer || (lexer.tag == "plaintext" && !opts[:guess_lang])
31
+
30
32
  opts[:css_class] ||= 'highlight' # For backward compatibility when using Rouge 2.0
31
33
  formatter = formatter_class(opts).new(opts)
32
34
  formatter.format(lexer.lex(text))
@@ -43,24 +45,32 @@ module Kramdown::Converter::SyntaxHighlighter
43
45
  cache = converter.data[:syntax_highlighter_rouge] = {}
44
46
 
45
47
  opts = converter.options[:syntax_highlighter_opts].dup
46
- span_opts = (opts.delete(:span) || {}).dup
47
- block_opts = (opts.delete(:block) || {}).dup
48
- [span_opts, block_opts].each do |hash|
49
- hash.keys.each do |k|
50
- hash[k.kind_of?(String) ? Kramdown::Options.str_to_sym(k) : k] = hash.delete(k)
51
- end
52
- end
53
48
 
54
- cache[:span] = opts.merge(span_opts).update(wrap: false)
49
+ span_opts = opts.delete(:span)&.dup || {}
50
+ block_opts = opts.delete(:block)&.dup || {}
51
+ normalize_keys(span_opts)
52
+ normalize_keys(block_opts)
53
+
54
+ cache[:span] = opts.merge(span_opts)
55
+ cache[:span][:wrap] = false
56
+
55
57
  cache[:block] = opts.merge(block_opts)
56
58
  end
57
59
 
60
+ def self.normalize_keys(hash)
61
+ return if hash.empty?
62
+
63
+ hash.keys.each do |k|
64
+ hash[k.kind_of?(String) ? Kramdown::Options.str_to_sym(k) : k] = hash.delete(k)
65
+ end
66
+ end
67
+
58
68
  def self.formatter_class(opts = {})
59
69
  case formatter = opts[:formatter]
60
70
  when Class
61
71
  formatter
62
72
  when /\A[[:upper:]][[:alnum:]_]*\z/
63
- ::Rouge::Formatters.const_get(formatter)
73
+ ::Rouge::Formatters.const_get(formatter, false)
64
74
  else
65
75
  # Available in Rouge 2.0 or later
66
76
  ::Rouge::Formatters::HTMLLegacy
@@ -14,6 +14,14 @@ module Kramdown
14
14
  # kramdown only uses this one class for representing all available elements in an element tree
15
15
  # (paragraphs, headers, emphasis, ...). The type of element can be set via the #type accessor.
16
16
  #
17
+ # The root of a kramdown element tree has to be an element of type :root. It needs to have certain
18
+ # option keys set so that conversions work correctly. If only a part of a tree should be
19
+ # converted, duplicate the root node and assign the #children appropriately, e.g:
20
+ #
21
+ # root = doc.root
22
+ # new_root = root.dup
23
+ # new_root.children = [root.children[0]] # assign new array with elements to convert
24
+ #
17
25
  # Following is a description of all supported element types.
18
26
  #
19
27
  # Note that the option :location may contain the start line number of an element in the source
@@ -522,6 +530,22 @@ module Kramdown
522
530
  CATEGORY[el.type] || el.options[:category]
523
531
  end
524
532
 
533
+ # syntactic sugar to simplify calls such as +Kramdown::Element.category(el) == :block+ with
534
+ # +el.block?+.
535
+ #
536
+ # Returns boolean true or false.
537
+ def block?
538
+ (CATEGORY[type] || options[:category]) == :block
539
+ end
540
+
541
+ # syntactic sugar to simplify calls such as +Kramdown::Element.category(el) == :span+ with
542
+ # +el.span?+.
543
+ #
544
+ # Returns boolean true or false.
545
+ def span?
546
+ (CATEGORY[type] || options[:category]) == :span
547
+ end
548
+
525
549
  end
526
550
 
527
551
  end
@@ -39,6 +39,7 @@ module Kramdown
39
39
  ALLOWED_TYPES = [String, Integer, Float, Symbol, Boolean, Object]
40
40
 
41
41
  @options = {}
42
+ @cached_defaults = nil
42
43
 
43
44
  # Define a new option called +name+ (a Symbol) with the given +type+ (String, Integer, Float,
44
45
  # Symbol, Boolean, Object), default value +default+ and the description +desc+. If a block is
@@ -54,6 +55,7 @@ module Kramdown
54
55
  raise ArgumentError, "Invalid type for default value" if !(type === default) && !default.nil?
55
56
  raise ArgumentError, "Missing validator block" if type == Object && block.nil?
56
57
  @options[name] = Definition.new(name, type, default, desc, block)
58
+ @cached_defaults = nil
57
59
  end
58
60
 
59
61
  # Return all option definitions.
@@ -68,15 +70,17 @@ module Kramdown
68
70
 
69
71
  # Return a Hash with the default values for all options.
70
72
  def self.defaults
71
- temp = {}
72
- @options.each {|_n, o| temp[o.name] = o.default }
73
- temp
73
+ @cached_defaults ||= begin
74
+ temp = {}
75
+ @options.each {|_n, o| temp[o.name] = o.default }
76
+ temp.freeze
77
+ end
74
78
  end
75
79
 
76
80
  # Merge the #defaults Hash with the *parsed* options from the given Hash, i.e. only valid option
77
81
  # names are considered and their value is run through the #parse method.
78
82
  def self.merge(hash)
79
- temp = defaults
83
+ temp = defaults.dup
80
84
  hash.each do |k, v|
81
85
  k = k.to_sym
82
86
  temp[k] = @options.key?(k) ? parse(k, v) : v
@@ -328,7 +332,11 @@ module Kramdown
328
332
  Used by: HTML converter, kramdown converter
329
333
  EOF
330
334
 
331
- define(:toc_levels, Object, (1..6).to_a, <<~EOF) do |val|
335
+ TOC_LEVELS_RANGE = (1..6).freeze
336
+ TOC_LEVELS_ARRAY = TOC_LEVELS_RANGE.to_a.freeze
337
+ private_constant :TOC_LEVELS_RANGE, :TOC_LEVELS_ARRAY
338
+
339
+ define(:toc_levels, Object, TOC_LEVELS_ARRAY, <<~EOF) do |val|
332
340
  Defines the levels that are used for the table of contents
333
341
 
334
342
  The individual levels can be specified by separating them with commas
@@ -347,12 +355,20 @@ module Kramdown
347
355
  else
348
356
  raise Kramdown::Error, "Invalid syntax for option toc_levels"
349
357
  end
350
- when Array, Range
351
- val = val.map(&:to_i).uniq
358
+ when Array
359
+ unless val.eql?(TOC_LEVELS_ARRAY)
360
+ val = val.map(&:to_i).uniq
361
+ end
362
+ when Range
363
+ if val.eql?(TOC_LEVELS_RANGE)
364
+ val = TOC_LEVELS_ARRAY
365
+ else
366
+ val = val.map(&:to_i).uniq
367
+ end
352
368
  else
353
369
  raise Kramdown::Error, "Invalid type #{val.class} for option toc_levels"
354
370
  end
355
- if val.any? {|i| !(1..6).cover?(i) }
371
+ if val.any? {|i| !TOC_LEVELS_RANGE.cover?(i) }
356
372
  raise Kramdown::Error, "Level numbers for option toc_levels have to be integers from 1 to 6"
357
373
  end
358
374
  val
@@ -377,7 +393,11 @@ module Kramdown
377
393
  simple_array_validator(val, :latex_headers, 6)
378
394
  end
379
395
 
380
- define(:smart_quotes, Object, %w[lsquo rsquo ldquo rdquo], <<~EOF) do |val|
396
+ SMART_QUOTES_ENTITIES = %w[lsquo rsquo ldquo rdquo].freeze
397
+ SMART_QUOTES_STR = SMART_QUOTES_ENTITIES.join(',').freeze
398
+ private_constant :SMART_QUOTES_ENTITIES, :SMART_QUOTES_STR
399
+
400
+ define(:smart_quotes, Object, SMART_QUOTES_ENTITIES, <<~EOF) do |val|
381
401
  Defines the HTML entity names or code points for smart quote output
382
402
 
383
403
  The entities identified by entity name or code point that should be
@@ -388,9 +408,13 @@ module Kramdown
388
408
  Default: lsquo,rsquo,ldquo,rdquo
389
409
  Used by: HTML/Latex converter
390
410
  EOF
391
- val = simple_array_validator(val, :smart_quotes, 4)
392
- val.map! {|v| Integer(v) rescue v }
393
- val
411
+ if val == SMART_QUOTES_STR || val == SMART_QUOTES_ENTITIES
412
+ SMART_QUOTES_ENTITIES
413
+ else
414
+ val = simple_array_validator(val, :smart_quotes, 4)
415
+ val.map! {|v| Integer(v) rescue v }
416
+ val
417
+ end
394
418
  end
395
419
 
396
420
  define(:typographic_symbols, Object, {}, <<~EOF) do |val|
@@ -562,6 +586,23 @@ module Kramdown
562
586
  Used by: HTML
563
587
  EOF
564
588
 
589
+ define(:remove_line_breaks_for_cjk, Boolean, false, <<~EOF)
590
+ Specifies whether line breaks should be removed between CJK characters
591
+
592
+ Default: false
593
+ Used by: HTML converter
594
+ EOF
595
+
596
+ define(:forbidden_inline_options, Object, %w[template], <<~EOF) do |val|
597
+ Defines the options that may not be set using the {::options} extension
598
+
599
+ Default: template
600
+ Used by: HTML converter
601
+ EOF
602
+ val.map! {|item| item.kind_of?(String) ? str_to_sym(item) : item }
603
+ simple_array_validator(val, :forbidden_inline_options)
604
+ end
605
+
565
606
  end
566
607
 
567
608
  end
@@ -93,7 +93,9 @@ module Kramdown
93
93
  raise "The source text contains invalid characters for the used encoding #{source.encoding}"
94
94
  end
95
95
  source = source.encode('UTF-8')
96
- source.gsub(/\r\n?/, "\n").chomp + "\n"
96
+ source.gsub!(/\r\n?/, "\n")
97
+ source.chomp!
98
+ source << "\n"
97
99
  end
98
100
 
99
101
  # This helper method adds the given +text+ either to the last element in the +tree+ if it is a
@@ -16,7 +16,7 @@ module Kramdown
16
16
 
17
17
  module Parser
18
18
 
19
- # Used for parsing a HTML document.
19
+ # Used for parsing an HTML document.
20
20
  #
21
21
  # The parsing code is in the Parser module that can also be used by other parsers.
22
22
  class Html < Base
@@ -286,7 +286,7 @@ module Kramdown
286
286
  src = Kramdown::Utils::StringScanner.new(raw)
287
287
  result = []
288
288
  until src.eos?
289
- if (tmp = src.scan_until(/(?=#{HTML_ENTITY_RE})/))
289
+ if (tmp = src.scan_until(/(?=#{HTML_ENTITY_RE})/o))
290
290
  result << Element.new(:text, tmp)
291
291
  src.scan(HTML_ENTITY_RE)
292
292
  val = src[1] || (src[2]&.to_i) || src[3].hex
@@ -324,7 +324,7 @@ module Kramdown
324
324
  tmp = []
325
325
  last_is_p = false
326
326
  el.children.each do |c|
327
- if Element.category(c) != :block || c.type == :text
327
+ if !c.block? || c.type == :text
328
328
  unless last_is_p
329
329
  tmp << Element.new(:p, nil, nil, transparent: true)
330
330
  last_is_p = true
@@ -354,8 +354,8 @@ module Kramdown
354
354
  el.children = el.children.reject do |c|
355
355
  i += 1
356
356
  c.type == :text && c.value.strip.empty? &&
357
- (i == 0 || i == el.children.length - 1 || (Element.category(el.children[i - 1]) == :block &&
358
- Element.category(el.children[i + 1]) == :block))
357
+ (i == 0 || i == el.children.length - 1 || ((el.children[i - 1]).block? &&
358
+ (el.children[i + 1]).block?))
359
359
  end
360
360
  end
361
361
 
@@ -581,11 +581,11 @@ module Kramdown
581
581
  @src = Kramdown::Utils::StringScanner.new(adapt_source(source))
582
582
 
583
583
  while true
584
- if (result = @src.scan(/\s*#{HTML_INSTRUCTION_RE}/))
584
+ if (result = @src.scan(/\s*#{HTML_INSTRUCTION_RE}/o))
585
585
  @tree.children << Element.new(:xml_pi, result.strip, nil, category: :block)
586
- elsif (result = @src.scan(/\s*#{HTML_DOCTYPE_RE}/))
586
+ elsif (result = @src.scan(/\s*#{HTML_DOCTYPE_RE}/o))
587
587
  # ignore the doctype
588
- elsif (result = @src.scan(/\s*#{HTML_COMMENT_RE}/))
588
+ elsif (result = @src.scan(/\s*#{HTML_COMMENT_RE}/o))
589
589
  @tree.children << Element.new(:xml_comment, result.strip, nil, category: :block)
590
590
  else
591
591
  break
@@ -79,6 +79,8 @@ module Kramdown
79
79
  @span_parsers = [:emphasis, :codespan, :autolink, :span_html, :footnote_marker, :link,
80
80
  :smart_quotes, :inline_math, :span_extensions, :html_entity,
81
81
  :typographic_syms, :line_break, :escaped_chars]
82
+
83
+ @span_pattern_cache ||= Hash.new { |h, k| h[k] = {} }
82
84
  end
83
85
  private_class_method(:new, :allocate)
84
86
 
@@ -195,6 +197,11 @@ module Kramdown
195
197
  end.flatten!
196
198
  end
197
199
 
200
+ def span_pattern_cache(stop_re, span_start)
201
+ @span_pattern_cache[stop_re][span_start] ||= /(?=#{Regexp.union(stop_re, span_start)})/
202
+ end
203
+ private :span_pattern_cache
204
+
198
205
  # Parse all span-level elements in the source string of @src into +el+.
199
206
  #
200
207
  # If the parameter +stop_re+ (a regexp) is used, parsing is immediately stopped if the regexp
@@ -213,7 +220,7 @@ module Kramdown
213
220
  span_start, span_start_re = span_parser_regexps(parsers) if parsers
214
221
  parsers ||= @span_parsers
215
222
 
216
- used_re = (stop_re.nil? ? span_start_re : /(?=#{Regexp.union(stop_re, span_start)})/)
223
+ used_re = (stop_re.nil? ? span_start_re : span_pattern_cache(stop_re, span_start))
217
224
  stop_re_found = false
218
225
  while !@src.eos? && !stop_re_found
219
226
  if (result = @src.scan_until(used_re))
@@ -46,7 +46,7 @@ module Kramdown
46
46
  regexps << /(?=(?:\W|^)#{regexps.first}(?!\w))/ # regexp should only match on word boundaries
47
47
  end
48
48
  el.children.map! do |child|
49
- if child.type == :text
49
+ if child.type == :text && el.options[:content_model] != :raw
50
50
  if child.value =~ regexps.first
51
51
  result = []
52
52
  strscan = Kramdown::Utils::StringScanner.new(child.value, child.options[:location])
@@ -11,8 +11,8 @@ module Kramdown
11
11
  module Parser
12
12
  class Kramdown
13
13
 
14
- ACHARS = '[[:alnum:]]_'
15
- AUTOLINK_START_STR = "<((mailto|https?|ftps?):.+?|[-.#{ACHARS}]+@[-#{ACHARS}]+(?:\.[-#{ACHARS}]+)*\.[a-z]+)>"
14
+ ACHARS = '[[:alnum:]]-_.'
15
+ AUTOLINK_START_STR = "<((mailto|https?|ftps?):.+?|[#{ACHARS}]+?@[#{ACHARS}]+?)>"
16
16
  AUTOLINK_START = /#{AUTOLINK_START_STR}/u
17
17
 
18
18
  # Parse the autolink at the current location.
@@ -16,8 +16,8 @@ module Kramdown
16
16
  # Parse the blank line at the current postition.
17
17
  def parse_blank_line
18
18
  @src.pos += @src.matched_size
19
- if @tree.children.last && @tree.children.last.type == :blank
20
- @tree.children.last.value << @src.matched
19
+ if (last_child = @tree.children.last) && last_child.type == :blank
20
+ last_child.value << @src.matched
21
21
  else
22
22
  @tree.children << new_block_el(:blank, @src.matched)
23
23
  end
@@ -19,8 +19,9 @@ module Kramdown
19
19
 
20
20
  # Return +true+ if we are after a block boundary.
21
21
  def after_block_boundary?
22
- !@tree.children.last || @tree.children.last.type == :blank ||
23
- (@tree.children.last.type == :eob && @tree.children.last.value.nil?) || @block_ial
22
+ last_child = @tree.children.last
23
+ !last_child || last_child.type == :blank ||
24
+ (last_child.type == :eob && last_child.value.nil?) || @block_ial
24
25
  end
25
26
 
26
27
  # Return +true+ if we are before a block boundary.
@@ -20,13 +20,23 @@ module Kramdown
20
20
  simple = (result.length == 1)
21
21
  saved_pos = @src.save_pos
22
22
 
23
- if simple && @src.pre_match =~ /\s\Z/ && @src.match?(/\s/)
23
+ if simple && @src.pre_match =~ /\s\Z|\A\Z/ && @src.match?(/\s/)
24
24
  add_text(result)
25
25
  return
26
26
  end
27
27
 
28
- if (text = @src.scan_until(/#{result}/))
29
- text.sub!(/#{result}\Z/, '')
28
+ # assign static regex to avoid allocating the same on every instance
29
+ # where +result+ equals a single-backtick. Interpolate otherwise.
30
+ if result == '`'
31
+ scan_pattern = /`/
32
+ str_sub_pattern = /`\Z/
33
+ else
34
+ scan_pattern = /#{result}/
35
+ str_sub_pattern = /#{result}\Z/
36
+ end
37
+
38
+ if (text = @src.scan_until(scan_pattern))
39
+ text.sub!(str_sub_pattern, '')
30
40
  unless simple
31
41
  text = text[1..-1] if text[0..0] == ' '
32
42
  text = text[0..-2] if text[-1..-1] == ' '
@@ -22,7 +22,7 @@ module Kramdown
22
22
  element = (result.length == 2 ? :strong : :em)
23
23
  type = result[0..0]
24
24
 
25
- if (type == '_' && @src.pre_match =~ /[[:alpha:]-]\z/) || @src.check(/\s/) ||
25
+ if (type == '_' && @src.pre_match =~ /[[:alpha:]]-?[[:alpha:]]*\z/) || @src.check(/\s/) ||
26
26
  @tree.type == element || @stack.any? {|el, _| el.type == element }
27
27
  add_text(result)
28
28
  return
@@ -110,6 +110,12 @@ module Kramdown
110
110
  opts.select do |k, v|
111
111
  k = k.to_sym
112
112
  if Kramdown::Options.defined?(k)
113
+ if @options[:forbidden_inline_options].include?(k) ||
114
+ k == :forbidden_inline_options
115
+ warning("Option #{k} may not be set inline")
116
+ next false
117
+ end
118
+
113
119
  begin
114
120
  val = Kramdown::Options.parse(k, v)
115
121
  @options[k] = val
@@ -163,10 +169,10 @@ module Kramdown
163
169
  elsif @src.check(EXT_BLOCK_START)
164
170
  parse_extension_start_tag(:block)
165
171
  elsif @src.scan(IAL_BLOCK_START)
166
- if @tree.children.last && @tree.children.last.type != :blank &&
167
- (@tree.children.last.type != :eob ||
168
- [:link_def, :abbrev_def, :footnote_def].include?(@tree.children.last.value))
169
- parse_attribute_list(@src[1], @tree.children.last.options[:ial] ||= {})
172
+ if (last_child = @tree.children.last) && last_child.type != :blank &&
173
+ (last_child.type != :eob ||
174
+ [:link_def, :abbrev_def, :footnote_def].include?(last_child.value))
175
+ parse_attribute_list(@src[1], last_child.options[:ial] ||= {})
170
176
  @tree.children << new_block_el(:eob, :ial) unless @src.check(IAL_BLOCK_START)
171
177
  else
172
178
  parse_attribute_list(@src[1], @block_ial ||= {})
@@ -187,12 +193,12 @@ module Kramdown
187
193
  if @src.check(EXT_SPAN_START)
188
194
  parse_extension_start_tag(:span)
189
195
  elsif @src.check(IAL_SPAN_START)
190
- if @tree.children.last && @tree.children.last.type != :text
196
+ if (last_child = @tree.children.last) && last_child.type != :text
191
197
  @src.pos += @src.matched_size
192
198
  attr = {}
193
199
  parse_attribute_list(@src[1], attr)
194
- update_ial_with_ial(@tree.children.last.options[:ial] ||= {}, attr)
195
- update_attr_with_ial(@tree.children.last.attr, attr)
200
+ update_ial_with_ial(last_child.options[:ial] ||= {}, attr)
201
+ update_attr_with_ial(last_child.attr, attr)
196
202
  else
197
203
  warning("Found span IAL after text - ignoring it")
198
204
  add_text(@src.getch)