asciidoctor 1.5.2 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of asciidoctor might be problematic. Click here for more details.

Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +107 -1
  3. data/LICENSE.adoc +1 -1
  4. data/README.adoc +155 -230
  5. data/Rakefile +2 -1
  6. data/bin/asciidoctor +5 -1
  7. data/data/stylesheets/asciidoctor-default.css +37 -29
  8. data/data/stylesheets/coderay-asciidoctor.css +3 -3
  9. data/features/text_formatting.feature +2 -0
  10. data/lib/asciidoctor.rb +46 -21
  11. data/lib/asciidoctor/abstract_block.rb +14 -8
  12. data/lib/asciidoctor/abstract_node.rb +77 -24
  13. data/lib/asciidoctor/attribute_list.rb +1 -1
  14. data/lib/asciidoctor/block.rb +2 -3
  15. data/lib/asciidoctor/cli/options.rb +14 -15
  16. data/lib/asciidoctor/converter/docbook45.rb +8 -8
  17. data/lib/asciidoctor/converter/docbook5.rb +25 -17
  18. data/lib/asciidoctor/converter/factory.rb +6 -1
  19. data/lib/asciidoctor/converter/html5.rb +159 -117
  20. data/lib/asciidoctor/converter/manpage.rb +671 -0
  21. data/lib/asciidoctor/converter/template.rb +24 -17
  22. data/lib/asciidoctor/document.rb +89 -47
  23. data/lib/asciidoctor/extensions.rb +22 -21
  24. data/lib/asciidoctor/helpers.rb +73 -16
  25. data/lib/asciidoctor/list.rb +26 -5
  26. data/lib/asciidoctor/parser.rb +179 -122
  27. data/lib/asciidoctor/path_resolver.rb +6 -10
  28. data/lib/asciidoctor/reader.rb +37 -34
  29. data/lib/asciidoctor/stylesheets.rb +16 -10
  30. data/lib/asciidoctor/substitutors.rb +98 -21
  31. data/lib/asciidoctor/table.rb +21 -17
  32. data/lib/asciidoctor/timings.rb +3 -3
  33. data/lib/asciidoctor/version.rb +1 -1
  34. data/man/asciidoctor.1 +155 -89
  35. data/man/asciidoctor.adoc +19 -11
  36. data/test/attributes_test.rb +86 -0
  37. data/test/blocks_test.rb +203 -15
  38. data/test/converter_test.rb +15 -2
  39. data/test/document_test.rb +290 -36
  40. data/test/extensions_test.rb +22 -3
  41. data/test/fixtures/circle.svg +8 -0
  42. data/test/fixtures/subs-docinfo.html +2 -0
  43. data/test/fixtures/subs.adoc +7 -0
  44. data/test/invoker_test.rb +25 -0
  45. data/test/links_test.rb +17 -0
  46. data/test/lists_test.rb +173 -0
  47. data/test/options_test.rb +2 -2
  48. data/test/paragraphs_test.rb +2 -2
  49. data/test/parser_test.rb +56 -13
  50. data/test/reader_test.rb +35 -3
  51. data/test/sections_test.rb +59 -0
  52. data/test/substitutions_test.rb +53 -14
  53. data/test/tables_test.rb +158 -2
  54. data/test/test_helper.rb +7 -2
  55. metadata +22 -11
  56. data/benchmark/benchmark.rb +0 -129
  57. data/benchmark/sample-data/mdbasics.adoc +0 -334
  58. data/lib/asciidoctor/opal_ext.rb +0 -26
  59. data/lib/asciidoctor/opal_ext/comparable.rb +0 -38
  60. data/lib/asciidoctor/opal_ext/dir.rb +0 -13
  61. data/lib/asciidoctor/opal_ext/error.rb +0 -2
  62. data/lib/asciidoctor/opal_ext/file.rb +0 -145
@@ -303,7 +303,6 @@ class PathResolver
303
303
  # any parent references resolved and self references removed and enforces
304
304
  # that the resolved path be contained within the jail, if provided
305
305
  def system_path target, start, jail = nil, opts = {}
306
- recover = opts.fetch :recover, true
307
306
  if jail
308
307
  unless is_root? jail
309
308
  raise ::SecurityError, %(Jail is not an absolute path: #{jail})
@@ -325,7 +324,7 @@ class PathResolver
325
324
  return expand_path start
326
325
  end
327
326
  else
328
- return system_path start, jail, jail
327
+ return system_path start, jail, jail, opts
329
328
  end
330
329
  end
331
330
 
@@ -343,7 +342,7 @@ class PathResolver
343
342
  elsif is_root? start
344
343
  start = posixfy start
345
344
  else
346
- start = system_path start, jail, jail
345
+ start = system_path start, jail, jail, opts
347
346
  end
348
347
 
349
348
  # both jail and start have been posixfied at this point
@@ -374,7 +373,7 @@ class PathResolver
374
373
  if jail
375
374
  if resolved_segments.length > jail_segments.length
376
375
  resolved_segments.pop
377
- elsif !recover
376
+ elsif !(recover ||= (opts.fetch :recover, true))
378
377
  raise ::SecurityError, %(#{opts[:target_name] || 'path'} #{target} refers to location outside jail: #{jail} (disallowed in safe mode))
379
378
  elsif !warned
380
379
  warn %(asciidoctor: WARNING: #{opts[:target_name] || 'path'} has illegal reference to ancestor of jail, auto-recovering)
@@ -411,21 +410,18 @@ class PathResolver
411
410
 
412
411
  unless start.nil_or_empty? || (is_web_root? target)
413
412
  target = %(#{start}#{SLASH}#{target})
414
- if (target.include? ':') && UriSniffRx =~ target
415
- uri_prefix = $~[0]
413
+ if (uri_prefix = Helpers.uri_prefix target)
416
414
  target = target[uri_prefix.length..-1]
417
415
  end
418
416
  end
419
417
 
420
418
  # use this logic instead if we want to normalize target if it contains a URI
421
419
  #unless is_web_root? target
422
- # if preserve_uri_target && (target.include? ':') && UriSniffRx =~ target
423
- # uri_prefix = $~[0]
420
+ # if preserve_uri_target && (uri_prefix = Helpers.uri_prefix target)
424
421
  # target = target[uri_prefix.length..-1]
425
422
  # elsif !start.nil_or_empty?
426
423
  # target = %(#{start}#{SLASH}#{target})
427
- # if (target.include? ':') && UriSniffRx =~ target
428
- # uri_prefix = $~[0]
424
+ # if (uri_prefix = Helpers.uri_prefix target)
429
425
  # target = target[uri_prefix.length..-1]
430
426
  # end
431
427
  # end
@@ -438,7 +438,7 @@ class Reader
438
438
  break_on_blank_lines = options[:break_on_blank_lines]
439
439
  break_on_list_continuation = options[:break_on_list_continuation]
440
440
  end
441
- skip_line_comments = options[:skip_line_comments]
441
+ skip_comments = options[:skip_line_comments]
442
442
  line_read = false
443
443
  line_restored = false
444
444
 
@@ -466,7 +466,7 @@ class Reader
466
466
  line_restored = true
467
467
  end
468
468
  else
469
- unless skip_line_comments && line.start_with?('//') && CommentLineRx =~ line
469
+ unless skip_comments && line.start_with?('//') && CommentLineRx =~ line
470
470
  result << line
471
471
  line_read = true
472
472
  end
@@ -578,8 +578,8 @@ class PreprocessorReader < Reader
578
578
  result.pop while (last = result[-1]) && last.empty?
579
579
  end
580
580
 
581
- if (indent = opts.fetch(:indent, nil))
582
- Parser.reset_block_indent! result, indent.to_i
581
+ if opts[:indent]
582
+ Parser.adjust_indentation! result, opts[:indent], (@document.attr 'tabsize')
583
583
  end
584
584
 
585
585
  result
@@ -753,11 +753,14 @@ class PreprocessorReader < Reader
753
753
  end
754
754
 
755
755
  lhs = resolve_expr_val expr_match[1]
756
- # regex enforces a restrict set of math-related operations
757
- op = expr_match[2]
758
756
  rhs = resolve_expr_val expr_match[3]
759
757
 
760
- skip = !(lhs.send op.to_sym, rhs)
758
+ # regex enforces a restricted set of math-related operations
759
+ if (op = expr_match[2]) == '!='
760
+ skip = lhs.send :==, rhs
761
+ else
762
+ skip = !(lhs.send op.to_sym, rhs)
763
+ end
761
764
  end
762
765
  end
763
766
 
@@ -838,7 +841,7 @@ class PreprocessorReader < Reader
838
841
  else
839
842
  ::File.join @dir, target
840
843
  end
841
- elsif target.include?(':') && UriSniffRx =~ target
844
+ elsif Helpers.uriish? target
842
845
  unless @document.attributes.has_key? 'allow-uri-read'
843
846
  replace_line %(link:#{target}[])
844
847
  return true
@@ -849,7 +852,7 @@ class PreprocessorReader < Reader
849
852
  if @document.attributes.has_key? 'cache-uri'
850
853
  # caching requires the open-uri-cached gem to be installed
851
854
  # processing will be automatically aborted if these libraries can't be opened
852
- Helpers.require_library 'open-uri/cached', 'open-uri-cached'
855
+ Helpers.require_library 'open-uri/cached', 'open-uri-cached' unless defined? ::OpenURI::Cache
853
856
  elsif !::RUBY_ENGINE_OPAL
854
857
  # autoload open-uri
855
858
  ::OpenURI
@@ -1126,65 +1129,65 @@ class PreprocessorReader < Reader
1126
1129
  # Examples
1127
1130
  #
1128
1131
  # expr = '"value"'
1129
- # resolve_expr_val(expr)
1132
+ # resolve_expr_val expr
1130
1133
  # # => "value"
1131
1134
  #
1132
1135
  # expr = '"value'
1133
- # resolve_expr_val(expr)
1136
+ # resolve_expr_val expr
1134
1137
  # # => "\"value"
1135
1138
  #
1136
1139
  # expr = '"{undefined}"'
1137
- # resolve_expr_val(expr)
1140
+ # resolve_expr_val expr
1138
1141
  # # => ""
1139
1142
  #
1140
1143
  # expr = '{undefined}'
1141
- # resolve_expr_val(expr)
1144
+ # resolve_expr_val expr
1142
1145
  # # => nil
1143
1146
  #
1144
1147
  # expr = '2'
1145
- # resolve_expr_val(expr)
1148
+ # resolve_expr_val expr
1146
1149
  # # => 2
1147
1150
  #
1148
1151
  # @document.attributes['name'] = 'value'
1149
1152
  # expr = '"{name}"'
1150
- # resolve_expr_val(expr)
1153
+ # resolve_expr_val expr
1151
1154
  # # => "value"
1152
1155
  #
1153
1156
  # Returns The value of the expression, coerced to the appropriate type
1154
- def resolve_expr_val(str)
1155
- val = str
1156
- type = nil
1157
-
1158
- if val.start_with?('"') && val.end_with?('"') ||
1159
- val.start_with?('\'') && val.end_with?('\'')
1160
- type = :string
1157
+ def resolve_expr_val val
1158
+ if ((val.start_with? '"') && (val.end_with? '"')) ||
1159
+ ((val.start_with? '\'') && (val.end_with? '\''))
1160
+ quoted = true
1161
1161
  val = val[1...-1]
1162
+ else
1163
+ quoted = false
1162
1164
  end
1163
1165
 
1164
1166
  # QUESTION should we substitute first?
1167
+ # QUESTION should we also require string to be single quoted (like block attribute values?)
1165
1168
  if val.include? '{'
1166
- val = @document.sub_attributes val
1169
+ val = @document.sub_attributes val, :attribute_missing => 'drop'
1167
1170
  end
1168
1171
 
1169
- unless type == :string
1172
+ if quoted
1173
+ val
1174
+ else
1170
1175
  if val.empty?
1171
- val = nil
1172
- elsif val.strip.empty?
1173
- val = ' '
1176
+ nil
1174
1177
  elsif val == 'true'
1175
- val = true
1178
+ true
1176
1179
  elsif val == 'false'
1177
- val = false
1178
- elsif val.include?('.')
1179
- val = val.to_f
1180
+ false
1181
+ elsif val.rstrip.empty?
1182
+ ' '
1183
+ elsif val.include? '.'
1184
+ val.to_f
1180
1185
  else
1181
1186
  # fallback to coercing to integer, since we
1182
1187
  # require string values to be explicitly quoted
1183
- val = val.to_i
1188
+ val.to_i
1184
1189
  end
1185
1190
  end
1186
-
1187
- val
1188
1191
  end
1189
1192
 
1190
1193
  def include_processors?
@@ -6,7 +6,6 @@ module Asciidoctor
6
6
  # QUESTION create method for user stylesheet?
7
7
  class Stylesheets
8
8
  DEFAULT_STYLESHEET_NAME = 'asciidoctor.css'
9
- #DEFAULT_CODERAY_STYLE = 'asciidoctor'
10
9
  DEFAULT_PYGMENTS_STYLE = 'default'
11
10
  STYLESHEETS_DATA_PATH = ::File.join DATA_PATH, 'stylesheets'
12
11
 
@@ -45,9 +44,10 @@ class Stylesheets
45
44
  #
46
45
  # returns the [String] CodeRay stylesheet data
47
46
  def coderay_stylesheet_data
48
- # NOTE use the following two lines to load a built-in theme instead
49
- # Helpers.require_library 'coderay'
50
- # ::CodeRay::Encoders[:html]::CSS.new(:default).stylesheet
47
+ # NOTE use the following lines to load a built-in theme instead
48
+ # unless load_coderay.nil?
49
+ # ::CodeRay::Encoders[:html]::CSS.new(:default).stylesheet
50
+ # end
51
51
  @coderay_stylesheet_data ||= ::IO.read(::File.join(STYLESHEETS_DATA_PATH, 'coderay-asciidoctor.css')).chomp
52
52
  end
53
53
 
@@ -62,16 +62,19 @@ class Stylesheets
62
62
  end
63
63
 
64
64
  def pygments_stylesheet_name style = nil
65
- style ||= DEFAULT_PYGMENTS_STYLE
66
- %(pygments-#{style}.css)
65
+ %(pygments-#{style || DEFAULT_PYGMENTS_STYLE}.css)
67
66
  end
68
67
 
69
68
  # Public: Generate the Pygments stylesheet with the specified style.
70
69
  #
71
70
  # returns the [String] Pygments stylesheet data
72
71
  def pygments_stylesheet_data style = nil
73
- style ||= DEFAULT_PYGMENTS_STYLE
74
- (@pygments_stylesheet_data ||= load_pygments)[style] ||= ::Pygments.css '.listingblock .pygments', :classprefix => 'tok-', :style => style
72
+ if load_pygments
73
+ (@pygments_stylesheet_data ||= {})[style || DEFAULT_PYGMENTS_STYLE] ||=
74
+ ::Pygments.css '.listingblock .pygments', :classprefix => 'tok-', :style => (style || DEFAULT_PYGMENTS_STYLE)
75
+ else
76
+ '/* Pygments styles disabled. Pygments is not available. */'
77
+ end
75
78
  end
76
79
 
77
80
  def embed_pygments_stylesheet style = nil
@@ -84,9 +87,12 @@ class Stylesheets
84
87
  ::File.open(::File.join(target_dir, pygments_stylesheet_name(style)), 'w') {|f| f.write pygments_stylesheet_data(style) }
85
88
  end
86
89
 
90
+ #def load_coderay
91
+ # (defined? ::CodeRay) ? true : !(Helpers.require_library 'coderay', true, :ignore).nil?
92
+ #end
93
+
87
94
  def load_pygments
88
- Helpers.require_library 'pygments', 'pygments.rb' unless defined? ::Pygments
89
- {}
95
+ (defined? ::Pygments) ? true : !(Helpers.require_library 'pygments', 'pygments.rb', :ignore).nil?
90
96
  end
91
97
  end
92
98
  end
@@ -80,7 +80,7 @@ module Substitutors
80
80
  elsif subs == :normal
81
81
  subs = SUBS[:normal]
82
82
  elsif expand
83
- if subs.is_a? ::Symbol
83
+ if ::Symbol === subs
84
84
  subs = COMPOSITE_SUBS[subs] || [subs]
85
85
  else
86
86
  effective_subs = []
@@ -98,7 +98,7 @@ module Substitutors
98
98
 
99
99
  return source if subs.empty?
100
100
 
101
- text = (multiline = source.is_a? ::Array) ? (source * EOL) : source
101
+ text = (multiline = ::Array === source) ? source * EOL : source
102
102
 
103
103
  if (has_passthroughs = subs.include? :macros)
104
104
  text = extract_passthroughs text
@@ -108,7 +108,7 @@ module Substitutors
108
108
  subs.each do |type|
109
109
  case type
110
110
  when :specialcharacters
111
- text = sub_specialcharacters text
111
+ text = sub_specialchars text
112
112
  when :quotes
113
113
  text = sub_quotes text
114
114
  when :attributes
@@ -138,7 +138,7 @@ module Substitutors
138
138
  #
139
139
  # returns - A String with normal substitutions performed
140
140
  def apply_normal_subs(lines)
141
- apply_subs lines.is_a?(::Array) ? (lines * EOL) : lines
141
+ apply_subs(::Array === lines ? lines * EOL : lines)
142
142
  end
143
143
 
144
144
  # Public: Apply substitutions for titles.
@@ -356,12 +356,12 @@ module Substitutors
356
356
  # text - The String text to process
357
357
  #
358
358
  # returns The String text with special characters replaced
359
- def sub_specialcharacters(text)
359
+ def sub_specialchars(text)
360
360
  SUPPORTS_GSUB_RESULT_HASH ?
361
361
  text.gsub(SPECIAL_CHARS_PATTERN, SPECIAL_CHARS) :
362
362
  text.gsub(SPECIAL_CHARS_PATTERN) { SPECIAL_CHARS[$&] }
363
363
  end
364
- alias :sub_specialchars :sub_specialcharacters
364
+ alias :sub_specialcharacters :sub_specialchars
365
365
 
366
366
  # Public: Substitute quoted text (includes emphasis, strong, monospaced, etc)
367
367
  #
@@ -448,7 +448,7 @@ module Substitutors
448
448
  return data if data.nil_or_empty?
449
449
 
450
450
  # normalizes data type to an array (string becomes single-element array)
451
- if (string_data = String === data)
451
+ if (string_data = ::String === data)
452
452
  data = [data]
453
453
  end
454
454
 
@@ -520,7 +520,7 @@ module Substitutors
520
520
  result << line unless reject || (reject_if_empty && line.empty?)
521
521
  end
522
522
 
523
- string_data ? (result * EOL) : result
523
+ string_data ? result * EOL : result
524
524
  end
525
525
 
526
526
  # Public: Substitute inline macros (e.g., links, images, etc)
@@ -632,7 +632,7 @@ module Substitutors
632
632
  # TODO this handling needs some cleanup
633
633
  if (extensions = @document.extensions) && extensions.inline_macros? # && found[:macroish]
634
634
  extensions.inline_macros.each do |extension|
635
- result = result.gsub(extension.config[:regexp]) {
635
+ result = result.gsub(extension.instance.regexp) {
636
636
  # alias match for Ruby 1.8.7 compat
637
637
  m = $~
638
638
  # honor the escape
@@ -678,7 +678,7 @@ module Substitutors
678
678
  @document.register(:images, target)
679
679
  end
680
680
  attrs = parse_attributes(raw_attrs, posattrs)
681
- attrs['alt'] ||= File.basename(target, File.extname(target))
681
+ attrs['alt'] ||= Helpers.basename(target, true).tr('_-', ' ')
682
682
  Inline.new(self, :image, nil, :type => type, :target => target, :attributes => attrs).convert
683
683
  }
684
684
  end
@@ -828,10 +828,10 @@ module Substitutors
828
828
  end
829
829
 
830
830
  if text.empty?
831
- text = if @document.attr? 'hide-uri-scheme'
832
- target.sub UriSniffRx, ''
831
+ if @document.attr? 'hide-uri-scheme'
832
+ text = target.sub UriSniffRx, ''
833
833
  else
834
- target
834
+ text = target
835
835
  end
836
836
 
837
837
  if attrs
@@ -1069,6 +1069,9 @@ module Substitutors
1069
1069
 
1070
1070
  if id.include? '#'
1071
1071
  path, fragment = id.split('#')
1072
+ # QUESTION perform this check and throw it back if it fails?
1073
+ #elsif (start_chr = id.chr) == '.' || start_chr == '/'
1074
+ # next m[0][1..-1]
1072
1075
  else
1073
1076
  path = nil
1074
1077
  fragment = id
@@ -1318,8 +1321,7 @@ module Substitutors
1318
1321
  return [] if subs.nil_or_empty?
1319
1322
  candidates = nil
1320
1323
  modifiers_present = SubModifierSniffRx =~ subs
1321
- subs.split(',').each do |val|
1322
- key = val.strip
1324
+ subs.tr(' ', '').split(',').each do |key|
1323
1325
  modifier_operation = nil
1324
1326
  if modifiers_present
1325
1327
  if (first = key.chr) == '+'
@@ -1394,10 +1396,34 @@ module Substitutors
1394
1396
  # process_callouts - a Boolean flag indicating whether callout marks should be substituted
1395
1397
  #
1396
1398
  # returns the highlighted source code, if a source highlighter is defined
1397
- # on the document, otherwise the unprocessed text
1398
- def highlight_source(source, process_callouts, highlighter = nil)
1399
- highlighter ||= @document.attributes['source-highlighter']
1400
- Helpers.require_library highlighter, (highlighter == 'pygments' ? 'pygments.rb' : highlighter)
1399
+ # on the document, otherwise the source with verbatim substituions applied
1400
+ def highlight_source source, process_callouts, highlighter = nil
1401
+ case (highlighter ||= @document.attributes['source-highlighter'])
1402
+ when 'coderay'
1403
+ unless (highlighter_loaded = defined? ::CodeRay) || @document.attributes['coderay-unavailable']
1404
+ if (Helpers.require_library 'coderay', true, :warn).nil?
1405
+ # prevent further attempts to load CodeRay
1406
+ @document.set_attr 'coderay-unavailable', true
1407
+ else
1408
+ highlighter_loaded = true
1409
+ end
1410
+ end
1411
+ when 'pygments'
1412
+ unless (highlighter_loaded = defined? ::Pygments) || @document.attributes['pygments-unavailable']
1413
+ if (Helpers.require_library 'pygments', 'pygments.rb', :warn).nil?
1414
+ # prevent further attempts to load Pygments
1415
+ @document.set_attr 'pygments-unavailable', true
1416
+ else
1417
+ highlighter_loaded = true
1418
+ end
1419
+ end
1420
+ else
1421
+ # unknown highlighting library (something is misconfigured if we arrive here)
1422
+ highlighter_loaded = false
1423
+ end
1424
+
1425
+ return sub_source source, process_callouts unless highlighter_loaded
1426
+
1401
1427
  lineno = 0
1402
1428
  callout_on_last = false
1403
1429
  if process_callouts
@@ -1428,13 +1454,21 @@ module Substitutors
1428
1454
  end
1429
1455
 
1430
1456
  linenums_mode = nil
1457
+ highlight_lines = nil
1431
1458
 
1432
1459
  case highlighter
1433
1460
  when 'coderay'
1461
+ if (linenums_mode = (attr? 'linenums') ? (@document.attributes['coderay-linenums-mode'] || :table).to_sym : nil)
1462
+ if attr? 'highlight', nil, false
1463
+ highlight_lines = resolve_lines_to_highlight(attr 'highlight', nil, false)
1464
+ end
1465
+ end
1434
1466
  result = ::CodeRay::Duo[attr('language', :text, false).to_sym, :html, {
1435
1467
  :css => (@document.attributes['coderay-css'] || :class).to_sym,
1436
- :line_numbers => (linenums_mode = ((attr? 'linenums') ? (@document.attributes['coderay-linenums-mode'] || :table).to_sym : nil)),
1437
- :line_number_anchors => false}].highlight source
1468
+ :line_numbers => linenums_mode,
1469
+ :line_number_anchors => false,
1470
+ :highlight_lines => highlight_lines,
1471
+ :bold_every => false}].highlight source
1438
1472
  when 'pygments'
1439
1473
  lexer = ::Pygments::Lexer[attr('language', nil, false)] || ::Pygments::Lexer['text']
1440
1474
  opts = { :cssclass => 'pyhl', :classprefix => 'tok-', :nobackground => true }
@@ -1442,6 +1476,11 @@ module Substitutors
1442
1476
  opts[:noclasses] = true
1443
1477
  opts[:style] = (@document.attributes['pygments-style'] || Stylesheets::DEFAULT_PYGMENTS_STYLE)
1444
1478
  end
1479
+ if attr? 'highlight', nil, false
1480
+ unless (highlight_lines = resolve_lines_to_highlight(attr 'highlight', nil, false)).empty?
1481
+ opts[:hl_lines] = highlight_lines * ' '
1482
+ end
1483
+ end
1445
1484
  if attr? 'linenums'
1446
1485
  # TODO we could add the line numbers in ourselves instead of having to strip out the junk
1447
1486
  # FIXME move these regular expressions into constants
@@ -1505,6 +1544,44 @@ module Substitutors
1505
1544
  end
1506
1545
  end
1507
1546
 
1547
+ # e.g., highlight="1-5, !2, 10" or highlight=1-5;!2,10
1548
+ def resolve_lines_to_highlight spec
1549
+ lines = []
1550
+ spec.delete(' ').split(DataDelimiterRx).map do |entry|
1551
+ negate = false
1552
+ if entry.start_with? '!'
1553
+ entry = entry[1..-1]
1554
+ negate = true
1555
+ end
1556
+ if entry.include? '-'
1557
+ s, e = entry.split '-', 2
1558
+ line_nums = (s.to_i..e.to_i).to_a
1559
+ if negate
1560
+ lines -= line_nums
1561
+ else
1562
+ lines.concat line_nums
1563
+ end
1564
+ else
1565
+ if negate
1566
+ lines.delete entry.to_i
1567
+ else
1568
+ lines << entry.to_i
1569
+ end
1570
+ end
1571
+ end
1572
+ lines.sort.uniq
1573
+ end
1574
+
1575
+ # Public: Apply verbatim substitutions on source (for use when highlighting is disabled).
1576
+ #
1577
+ # source - the source code String on which to apply verbatim substitutions
1578
+ # process_callouts - a Boolean flag indicating whether callout marks should be substituted
1579
+ #
1580
+ # returns the substituted source
1581
+ def sub_source source, process_callouts
1582
+ return process_callouts ? sub_callouts(sub_specialchars(source)) : sub_specialchars(source)
1583
+ end
1584
+
1508
1585
  # Internal: Lock-in the substitutions for this block
1509
1586
  #
1510
1587
  # Looks for an attribute named "subs". If present, resolves the