kramdown 2.0.0 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
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)