asciidoctor 2.0.15 → 2.0.16

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +38 -2
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +3 -3
  5. data/README-fr.adoc +3 -3
  6. data/README-jp.adoc +3 -3
  7. data/README-zh_CN.adoc +3 -3
  8. data/README.adoc +2 -2
  9. data/asciidoctor.gemspec +1 -8
  10. data/data/locale/attributes-th.adoc +23 -0
  11. data/data/locale/attributes-vi.adoc +23 -0
  12. data/data/stylesheets/asciidoctor-default.css +50 -48
  13. data/lib/asciidoctor.rb +7 -7
  14. data/lib/asciidoctor/abstract_block.rb +4 -4
  15. data/lib/asciidoctor/abstract_node.rb +9 -8
  16. data/lib/asciidoctor/block.rb +6 -6
  17. data/lib/asciidoctor/cli/invoker.rb +0 -1
  18. data/lib/asciidoctor/cli/options.rb +22 -22
  19. data/lib/asciidoctor/convert.rb +1 -0
  20. data/lib/asciidoctor/converter.rb +5 -3
  21. data/lib/asciidoctor/converter/docbook5.rb +20 -22
  22. data/lib/asciidoctor/converter/html5.rb +70 -60
  23. data/lib/asciidoctor/converter/manpage.rb +61 -52
  24. data/lib/asciidoctor/converter/template.rb +11 -12
  25. data/lib/asciidoctor/document.rb +22 -37
  26. data/lib/asciidoctor/extensions.rb +10 -10
  27. data/lib/asciidoctor/list.rb +2 -6
  28. data/lib/asciidoctor/load.rb +10 -9
  29. data/lib/asciidoctor/logging.rb +10 -8
  30. data/lib/asciidoctor/parser.rb +122 -141
  31. data/lib/asciidoctor/path_resolver.rb +3 -3
  32. data/lib/asciidoctor/reader.rb +67 -68
  33. data/lib/asciidoctor/rx.rb +2 -1
  34. data/lib/asciidoctor/substitutors.rb +97 -99
  35. data/lib/asciidoctor/syntax_highlighter.rb +8 -11
  36. data/lib/asciidoctor/syntax_highlighter/coderay.rb +2 -1
  37. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +1 -1
  38. data/lib/asciidoctor/syntax_highlighter/pygments.rb +2 -1
  39. data/lib/asciidoctor/syntax_highlighter/rouge.rb +2 -1
  40. data/lib/asciidoctor/table.rb +17 -19
  41. data/lib/asciidoctor/timings.rb +3 -3
  42. data/lib/asciidoctor/version.rb +1 -1
  43. data/man/asciidoctor.1 +8 -9
  44. data/man/asciidoctor.adoc +7 -6
  45. metadata +7 -61
@@ -109,7 +109,7 @@ class PathResolver
109
109
  SLASH = '/'
110
110
  BACKSLASH = '\\'
111
111
  DOUBLE_SLASH = '//'
112
- WindowsRootRx = /^(?:[a-zA-Z]:)?[\\\/]/
112
+ WindowsRootRx = %r(^(?:[a-zA-Z]:)?[\\/])
113
113
 
114
114
  attr_accessor :file_separator
115
115
  attr_accessor :working_dir
@@ -290,8 +290,8 @@ class PathResolver
290
290
  # ex. ./sample/path
291
291
  elsif posix_path.start_with? DOT_SLASH
292
292
  root = DOT_SLASH
293
- # else ex. sample/path
294
293
  end
294
+ # otherwise ex. sample/path
295
295
  elsif root? posix_path
296
296
  # ex. //sample/path
297
297
  if unc? posix_path
@@ -306,8 +306,8 @@ class PathResolver
306
306
  # ex. ./sample/path
307
307
  elsif posix_path.start_with? DOT_SLASH
308
308
  root = DOT_SLASH
309
- # else ex. sample/path
310
309
  end
310
+ # otherwise ex. sample/path
311
311
 
312
312
  path_segments = (root ? (posix_path.slice root.length, posix_path.length) : posix_path).split SLASH
313
313
  # strip out all dot entries
@@ -59,8 +59,7 @@ class Reader
59
59
  end
60
60
  @lineno = cursor.lineno || 1
61
61
  end
62
- @lines = prepare_lines data, opts
63
- @source_lines = @lines.drop 0
62
+ @lines = (@source_lines = prepare_lines data, opts).reverse
64
63
  @mark = nil
65
64
  @look_ahead = 0
66
65
  @process_lines = true
@@ -127,7 +126,7 @@ class Reader
127
126
  # Returns nothing if there is no more data.
128
127
  def peek_line direct = false
129
128
  if direct || @look_ahead > 0
130
- @unescape_next_line ? ((line = @lines[0]).slice 1, line.length) : @lines[0]
129
+ @unescape_next_line ? ((line = @lines[-1]).slice 1, line.length) : @lines[-1]
131
130
  elsif @lines.empty?
132
131
  @look_ahead = 0
133
132
  nil
@@ -135,7 +134,7 @@ class Reader
135
134
  # FIXME the problem with this approach is that we aren't
136
135
  # retaining the modified line (hence the @unescape_next_line tweak)
137
136
  # perhaps we need a stack of proxied lines
138
- (line = process_line @lines[0]) ? line : peek_line
137
+ (process_line @lines[-1]) || peek_line
139
138
  end
140
139
  end
141
140
 
@@ -192,9 +191,7 @@ class Reader
192
191
  def read_lines
193
192
  lines = []
194
193
  # has_more_lines? triggers preprocessor
195
- while has_more_lines?
196
- lines << shift
197
- end
194
+ lines << shift while has_more_lines?
198
195
  lines
199
196
  end
200
197
  alias readlines read_lines
@@ -241,7 +238,6 @@ class Reader
241
238
  # Returns nothing.
242
239
  def unshift_lines lines_to_restore
243
240
  unshift_all lines_to_restore
244
- nil
245
241
  end
246
242
  alias restore_lines unshift_lines
247
243
 
@@ -334,7 +330,7 @@ class Reader
334
330
  comment_lines = []
335
331
  # optimized code for shortest execution path
336
332
  while (next_line = peek_line) && !next_line.empty?
337
- if (next_line.start_with? '//')
333
+ if next_line.start_with? '//'
338
334
  comment_lines << shift
339
335
  else
340
336
  break
@@ -407,34 +403,22 @@ class Reader
407
403
  break_on_list_continuation = options[:break_on_list_continuation]
408
404
  end
409
405
  skip_comments = options[:skip_line_comments]
410
- complete = line_read = line_restored = nil
406
+ line_read = line_restored = nil
411
407
  shift if options[:skip_first_line]
412
- while !complete && (line = read_line)
413
- complete = while true
414
- break true if terminator && line == terminator
415
- # QUESTION: can we get away with line.empty? here?
416
- break true if break_on_blank_lines && line.empty?
417
- if break_on_list_continuation && line_read && line == LIST_CONTINUATION
418
- options[:preserve_last_line] = true
419
- break true
420
- end
421
- break true if block_given? && (yield line)
422
- break false
423
- end
424
- if complete
425
- if options[:read_last_line]
426
- result << line
427
- line_read = true
428
- end
408
+ while (line = read_line)
409
+ if terminator ? line == terminator : ((break_on_blank_lines && line.empty?) ||
410
+ (break_on_list_continuation && line_read && line == LIST_CONTINUATION && (options[:preserve_last_line] = true)) ||
411
+ (block_given? && (yield line)))
412
+ result << line if options[:read_last_line]
429
413
  if options[:preserve_last_line]
430
414
  unshift line
431
415
  line_restored = true
432
416
  end
433
- else
434
- unless skip_comments && (line.start_with? '//') && !(line.start_with? '///')
435
- result << line
436
- line_read = true
437
- end
417
+ break
418
+ end
419
+ unless skip_comments && (line.start_with? '//') && !(line.start_with? '///')
420
+ result << line
421
+ line_read = true
438
422
  end
439
423
  end
440
424
  if restore_process_lines
@@ -459,21 +443,37 @@ class Reader
459
443
  def shift
460
444
  @lineno += 1
461
445
  @look_ahead -= 1 unless @look_ahead == 0
462
- @lines.shift
446
+ @lines.pop
463
447
  end
464
448
 
465
449
  # Internal: Restore the line to the stack and decrement the lineno
466
450
  def unshift line
467
451
  @lineno -= 1
468
452
  @look_ahead += 1
469
- @lines.unshift line
453
+ @lines.push line
454
+ nil
470
455
  end
471
456
 
472
- # Internal: Restore the lines to the stack and decrement the lineno
473
- def unshift_all lines
474
- @lineno -= lines.size
475
- @look_ahead += lines.size
476
- @lines.unshift(*lines)
457
+ if ::RUBY_ENGINE == 'jruby'
458
+ # Internal: Restore the lines to the stack and decrement the lineno
459
+ def unshift_all lines_to_restore
460
+ @lineno -= lines_to_restore.size
461
+ @look_ahead += lines_to_restore.size
462
+ if lines_to_restore.respond_to? :reverse
463
+ @lines.push(*lines_to_restore.reverse)
464
+ else
465
+ lines_to_restore.reverse_each {|it| @lines.push it }
466
+ end
467
+ nil
468
+ end
469
+ else
470
+ # Internal: Restore the lines to the stack and decrement the lineno
471
+ def unshift_all lines_to_restore
472
+ @lineno -= lines_to_restore.size
473
+ @look_ahead += lines_to_restore.size
474
+ @lines.push(*lines_to_restore.reverse)
475
+ nil
476
+ end
477
477
  end
478
478
 
479
479
  def cursor
@@ -516,12 +516,12 @@ class Reader
516
516
  #
517
517
  # Returns A copy of the String Array of lines remaining in this Reader
518
518
  def lines
519
- @lines.drop 0
519
+ @lines.reverse
520
520
  end
521
521
 
522
522
  # Public: Get a copy of the remaining lines managed by this Reader joined as a String
523
523
  def string
524
- @lines.join LF
524
+ @lines.reverse.join LF
525
525
  end
526
526
 
527
527
  # Public: Get the source lines for this Reader joined as a String
@@ -576,8 +576,7 @@ class Reader
576
576
  # Returns A String Array of source lines. If the source data is an Array, this method returns a copy.
577
577
  def prepare_lines data, opts = {}
578
578
  if (normalize = opts[:normalize])
579
- trim_end = normalize == :chomp ? false : true
580
- ::Array === data ? (Helpers.prepare_source_array data, trim_end) : (Helpers.prepare_source_string data, trim_end)
579
+ ::Array === data ? (Helpers.prepare_source_array data, normalize != :chomp) : (Helpers.prepare_source_string data, normalize != :chomp)
581
580
  elsif ::Array === data
582
581
  data.drop 0
583
582
  elsif data
@@ -725,16 +724,11 @@ class PreprocessorReader < Reader
725
724
  else
726
725
  # FIXME we eventually want to handle leveloffset without affecting the lines
727
726
  if attributes.key? 'leveloffset'
728
- @lines.unshift ''
729
- @lines.unshift %(:leveloffset: #{attributes['leveloffset']})
730
- @lines << ''
731
- if (old_leveloffset = @document.attr 'leveloffset')
732
- @lines << %(:leveloffset: #{old_leveloffset})
733
- else
734
- @lines << ':leveloffset!:'
735
- end
736
- # compensate for these extra lines
727
+ @lines = [((leveloffset = @document.attr 'leveloffset') ? %(:leveloffset: #{leveloffset}) : ':leveloffset!:'), ''] + @lines.reverse + ['', %(:leveloffset: #{attributes['leveloffset']})]
728
+ # compensate for these extra lines at the top
737
729
  @lineno -= 2
730
+ else
731
+ @lines.reverse!
738
732
  end
739
733
 
740
734
  # FIXME kind of a hack
@@ -804,10 +798,8 @@ class PreprocessorReader < Reader
804
798
  result = super
805
799
 
806
800
  # QUESTION should this work for AsciiDoc table cell content? Currently it does not.
807
- if @document && @document.attributes['skip-front-matter']
808
- if (front_matter = skip_front_matter! result)
809
- @document.attributes['front-matter'] = front_matter.join LF
810
- end
801
+ if @document && @document.attributes['skip-front-matter'] && (front_matter = skip_front_matter! result)
802
+ @document.attributes['front-matter'] = front_matter.join LF
811
803
  end
812
804
 
813
805
  if opts.fetch :condense, true
@@ -957,11 +949,12 @@ class PreprocessorReader < Reader
957
949
  if no_target
958
950
  # the text in brackets must match a conditional expression
959
951
  if text && EvalExpressionRx =~ text.strip
952
+ # NOTE assignments must happen before call to resolve_expr_val for compatiblity with Opal
960
953
  lhs = $1
954
+ # regex enforces a restricted set of math-related operations (==, !=, <=, >=, <, >)
961
955
  op = $2
962
956
  rhs = $3
963
- # regex enforces a restricted set of math-related operations (==, !=, <=, >=, <, >)
964
- skip = ((resolve_expr_val lhs).send op, (resolve_expr_val rhs)) ? false : true
957
+ skip = ((resolve_expr_val lhs).send op, (resolve_expr_val rhs)) ? false : true rescue true
965
958
  else
966
959
  logger.error message_with_context %(malformed preprocessor directive - #{text ? 'invalid expression' : 'missing expression'}: ifeval::[#{text}]), source_location: cursor
967
960
  return true
@@ -1051,10 +1044,11 @@ class PreprocessorReader < Reader
1051
1044
 
1052
1045
  parsed_attrs = doc.parse_attributes attrlist, [], sub_input: true
1053
1046
  inc_path, target_type, relpath = resolve_include_path expanded_target, attrlist, parsed_attrs
1054
- if target_type == :file
1047
+ case target_type
1048
+ when :file
1055
1049
  reader = ::File.method :open
1056
1050
  read_mode = FILE_READ_MODE
1057
- elsif target_type == :uri
1051
+ when :uri
1058
1052
  reader = ::OpenURI.method :open_uri
1059
1053
  read_mode = URI_READ_MODE
1060
1054
  else
@@ -1075,7 +1069,7 @@ class PreprocessorReader < Reader
1075
1069
  (split_delimited_value parsed_attrs['lines']).each do |linedef|
1076
1070
  if linedef.include? '..'
1077
1071
  from, _, to = linedef.partition '..'
1078
- inc_linenos += (to.empty? || (to = to.to_i) < 0) ? [from.to_i, 1.0/0.0] : (from.to_i..to).to_a
1072
+ inc_linenos += (to.empty? || (to = to.to_i) < 0) ? [from.to_i, ::Float::INFINITY] : (from.to_i..to).to_a
1079
1073
  else
1080
1074
  inc_linenos << linedef.to_i
1081
1075
  end
@@ -1133,16 +1127,21 @@ class PreprocessorReader < Reader
1133
1127
  elsif inc_tags
1134
1128
  inc_lines, inc_offset, inc_lineno, tag_stack, tags_used, active_tag = [], nil, 0, [], ::Set.new, nil
1135
1129
  if inc_tags.key? '**'
1130
+ select = base_select = inc_tags.delete '**'
1136
1131
  if inc_tags.key? '*'
1137
- select = base_select = inc_tags.delete '**'
1138
1132
  wildcard = inc_tags.delete '*'
1139
- else
1140
- select = base_select = wildcard = inc_tags.delete '**'
1133
+ elsif !select && inc_tags.values.first == false
1134
+ wildcard = true
1141
1135
  end
1142
1136
  elsif inc_tags.key? '*'
1143
- select = base_select = !(wildcard = inc_tags.delete '*')
1137
+ if inc_tags.keys.first == '*'
1138
+ select = base_select = !(wildcard = inc_tags.delete '*')
1139
+ else
1140
+ select = base_select = false
1141
+ wildcard = inc_tags.delete '*'
1142
+ end
1144
1143
  else
1145
- select = base_select = false
1144
+ select = base_select = !(inc_tags.value? true)
1146
1145
  end
1147
1146
  begin
1148
1147
  reader.call inc_path, read_mode do |f|
@@ -1193,7 +1192,7 @@ class PreprocessorReader < Reader
1193
1192
  end
1194
1193
  shift
1195
1194
  if inc_offset
1196
- parsed_attrs['partial-option'] = '' unless base_select && wildcard && inc_tags.empty?
1195
+ parsed_attrs['partial-option'] = '' unless base_select && wildcard != false && inc_tags.empty?
1197
1196
  # FIXME not accounting for skipped lines in reader line numbering
1198
1197
  push_include inc_lines, inc_path, relpath, inc_offset, parsed_attrs
1199
1198
  end
@@ -1264,7 +1263,7 @@ class PreprocessorReader < Reader
1264
1263
  end
1265
1264
 
1266
1265
  def pop_include
1267
- if @include_stack.size > 0
1266
+ unless @include_stack.empty?
1268
1267
  @lines, @file, @dir, @path, @lineno, @maxdepth, @process_lines = @include_stack.pop
1269
1268
  # FIXME kind of a hack
1270
1269
  #Document::AttributeEntry.new('infile', @file).save_to_next_block @document
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Asciidoctor
2
3
  # A collection of regular expression constants used by the parser. (For speed, these are not defined in the Rx module,
3
4
  # but rather directly in the Asciidoctor module).
@@ -469,7 +470,7 @@ module Asciidoctor
469
470
  # footnoteref:[id,text] (legacy)
470
471
  # footnoteref:[id] (legacy)
471
472
  #
472
- InlineFootnoteMacroRx = /\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\](?!<\/a>)/m
473
+ InlineFootnoteMacroRx = %r(\\?footnote(?:(ref):|:([#{CC_WORD}-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\](?!</a>))m
473
474
 
474
475
  # Matches an image or icon inline macro.
475
476
  #
@@ -330,13 +330,13 @@ module Substitutors
330
330
  # NOTE for convenience, map content (unparsed attrlist) to target when format is short
331
331
  target ||= ext_config[:format] == :short ? content : target
332
332
  end
333
- if (Inline === (replacement = extension.process_method[self, target, attributes]))
333
+ if Inline === (replacement = extension.process_method[self, target, attributes])
334
334
  if (inline_subs = replacement.attributes.delete 'subs') && (inline_subs = expand_subs inline_subs, 'custom inline macro')
335
335
  replacement.text = apply_subs replacement.text, inline_subs
336
336
  end
337
337
  replacement.convert
338
338
  elsif replacement
339
- logger.info %(expected substitution value for custom inline macro to be of type Inline; got #{replacement.class}: #{match})
339
+ logger.info { %(expected substitution value for custom inline macro to be of type Inline; got #{replacement.class}: #{match}) }
340
340
  replacement
341
341
  else
342
342
  ''
@@ -445,23 +445,16 @@ module Substitutors
445
445
  # indexterm:[Tigers,Big cats]
446
446
  if (attrlist = normalize_text $2, true, true).include? '='
447
447
  if (primary = (attrs = (AttributeList.new attrlist, self).parse)[1])
448
- attrs['terms'] = terms = [primary]
449
- if (secondary = attrs[2])
450
- terms << secondary
451
- if (tertiary = attrs[3])
452
- terms << tertiary
453
- end
454
- end
448
+ attrs['terms'] = [primary]
455
449
  if (see_also = attrs['see-also'])
456
450
  attrs['see-also'] = (see_also.include? ',') ? (see_also.split ',').map {|it| it.lstrip } : [see_also]
457
451
  end
458
452
  else
459
- attrs = { 'terms' => (terms = attrlist) }
453
+ attrs = { 'terms' => attrlist }
460
454
  end
461
455
  else
462
- attrs = { 'terms' => (terms = split_simple_csv attrlist) }
456
+ attrs = { 'terms' => (split_simple_csv attrlist) }
463
457
  end
464
- #doc.register :indexterms, terms
465
458
  (Inline.new self, :indexterm, nil, attributes: attrs).convert
466
459
  when 'indexterm2'
467
460
  # honor the escape
@@ -474,34 +467,33 @@ module Substitutors
474
467
  attrs['see-also'] = (see_also.include? ',') ? (see_also.split ',').map {|it| it.lstrip } : [see_also]
475
468
  end
476
469
  end
477
- #doc.register :indexterms, [term]
478
470
  (Inline.new self, :indexterm, term, attributes: attrs, type: :visible).convert
479
471
  else
480
- text = $3
472
+ encl_text = $3
481
473
  # honor the escape
482
474
  if $&.start_with? RS
483
475
  # escape concealed index term, but process nested flow index term
484
- if (text.start_with? '(') && (text.end_with? ')')
485
- text = text.slice 1, text.length - 2
476
+ if (encl_text.start_with? '(') && (encl_text.end_with? ')')
477
+ encl_text = encl_text.slice 1, encl_text.length - 2
486
478
  visible, before, after = true, '(', ')'
487
479
  else
488
480
  next $&.slice 1, $&.length
489
481
  end
490
482
  else
491
483
  visible = true
492
- if text.start_with? '('
493
- if text.end_with? ')'
494
- text, visible = (text.slice 1, text.length - 2), false
484
+ if encl_text.start_with? '('
485
+ if encl_text.end_with? ')'
486
+ encl_text, visible = (encl_text.slice 1, encl_text.length - 2), false
495
487
  else
496
- text, before, after = (text.slice 1, text.length), '(', ''
488
+ encl_text, before, after = (encl_text.slice 1, encl_text.length), '(', ''
497
489
  end
498
- elsif text.end_with? ')'
499
- text, before, after = text.chop, '', ')'
490
+ elsif encl_text.end_with? ')'
491
+ encl_text, before, after = encl_text.chop, '', ')'
500
492
  end
501
493
  end
502
494
  if visible
503
495
  # ((Tigers))
504
- if (term = normalize_text text, true).include? ';&'
496
+ if (term = normalize_text encl_text, true).include? ';&'
505
497
  if term.include? ' &gt;&gt; '
506
498
  term, _, see = term.partition ' &gt;&gt; '
507
499
  attrs = { 'see' => see }
@@ -510,12 +502,11 @@ module Substitutors
510
502
  attrs = { 'see-also' => see_also }
511
503
  end
512
504
  end
513
- #doc.register :indexterms, [term]
514
505
  subbed_term = (Inline.new self, :indexterm, term, attributes: attrs, type: :visible).convert
515
506
  else
516
507
  # (((Tigers,Big cats)))
517
508
  attrs = {}
518
- if (terms = normalize_text text, true).include? ';&'
509
+ if (terms = normalize_text encl_text, true).include? ';&'
519
510
  if terms.include? ' &gt;&gt; '
520
511
  terms, _, see = terms.partition ' &gt;&gt; '
521
512
  attrs['see'] = see
@@ -524,8 +515,7 @@ module Substitutors
524
515
  attrs['see-also'] = see_also
525
516
  end
526
517
  end
527
- attrs['terms'] = terms = split_simple_csv terms
528
- #doc.register :indexterms, terms
518
+ attrs['terms'] = split_simple_csv terms
529
519
  subbed_term = (Inline.new self, :indexterm, nil, attributes: attrs).convert
530
520
  end
531
521
  before ? %(#{before}#{subbed_term}#{after}) : subbed_term
@@ -545,7 +535,7 @@ module Substitutors
545
535
  # NOTE if $4 is set, we're looking at a formal macro (e.g., https://example.org[])
546
536
  if $4
547
537
  prefix = '' if prefix == 'link:'
548
- text = $4
538
+ link_text = nil if (link_text = $4).empty?
549
539
  else
550
540
  # invalid macro syntax (link: prefix w/o trailing square brackets or enclosed in double quotes)
551
541
  # FIXME we probably shouldn't even get here when the link: prefix is present; the regex is doing too much
@@ -553,7 +543,6 @@ module Substitutors
553
543
  when 'link:', ?", ?'
554
544
  next $&
555
545
  end
556
- text = ''
557
546
  case $3
558
547
  when ')', '?', '!'
559
548
  target = target.chop
@@ -593,27 +582,37 @@ module Substitutors
593
582
  end
594
583
 
595
584
  attrs, link_opts = nil, { type: :link }
596
- unless text.empty?
597
- text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
598
- if !doc.compat_mode && (text.include? '=')
599
- # NOTE if an equals sign (=) is present, extract attributes from text
600
- text, attrs = extract_attributes_from_text text, ''
585
+
586
+ if link_text
587
+ new_link_text = link_text = link_text.gsub ESC_R_SB, R_SB if link_text.include? R_SB
588
+ if !doc.compat_mode && (link_text.include? '=')
589
+ # NOTE if an equals sign (=) is present, extract attributes from link text
590
+ link_text, attrs = extract_attributes_from_text link_text, ''
591
+ new_link_text = link_text
601
592
  link_opts[:id] = attrs['id']
602
593
  end
603
594
 
604
- if text.end_with? '^'
605
- text = text.chop
595
+ if link_text.end_with? '^'
596
+ new_link_text = link_text = link_text.chop
606
597
  if attrs
607
598
  attrs['window'] ||= '_blank'
608
599
  else
609
600
  attrs = { 'window' => '_blank' }
610
601
  end
611
602
  end
612
- end
613
603
 
614
- if text.empty?
604
+ if new_link_text && new_link_text.empty?
605
+ # NOTE it's not possible for the URI scheme to be bare in this case
606
+ link_text = (doc_attrs.key? 'hide-uri-scheme') ? (target.sub UriSniffRx, '') : target
607
+ bare = true
608
+ end
609
+ else
615
610
  # NOTE it's not possible for the URI scheme to be bare in this case
616
- text = (doc_attrs.key? 'hide-uri-scheme') ? (target.sub UriSniffRx, '') : target
611
+ link_text = (doc_attrs.key? 'hide-uri-scheme') ? (target.sub UriSniffRx, '') : target
612
+ bare = true
613
+ end
614
+
615
+ if bare
617
616
  if attrs
618
617
  attrs['role'] = (attrs.key? 'role') ? %(bare #{attrs['role']}) : 'bare'
619
618
  else
@@ -623,7 +622,7 @@ module Substitutors
623
622
 
624
623
  doc.register :links, (link_opts[:target] = target)
625
624
  link_opts[:attributes] = attrs if attrs
626
- %(#{prefix}#{(Inline.new self, :anchor, text, link_opts).convert}#{suffix})
625
+ %(#{prefix}#{(Inline.new self, :anchor, link_text, link_opts).convert}#{suffix})
627
626
  end
628
627
  end
629
628
 
@@ -639,12 +638,12 @@ module Substitutors
639
638
  target = $2
640
639
  end
641
640
  attrs, link_opts = nil, { type: :link }
642
- unless (text = $3).empty?
643
- text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
641
+ unless (link_text = $3).empty?
642
+ link_text = link_text.gsub ESC_R_SB, R_SB if link_text.include? R_SB
644
643
  if mailto
645
- if !doc.compat_mode && (text.include? ',')
646
- # NOTE if a comma (,) is present, extract attributes from text
647
- text, attrs = extract_attributes_from_text text, ''
644
+ if !doc.compat_mode && (link_text.include? ',')
645
+ # NOTE if a comma (,) is present, extract attributes from link text
646
+ link_text, attrs = extract_attributes_from_text link_text, ''
648
647
  link_opts[:id] = attrs['id']
649
648
  if attrs.key? 2
650
649
  if attrs.key? 3
@@ -654,14 +653,14 @@ module Substitutors
654
653
  end
655
654
  end
656
655
  end
657
- elsif !doc.compat_mode && (text.include? '=')
658
- # NOTE if an equals sign (=) is present, extract attributes from text
659
- text, attrs = extract_attributes_from_text text, ''
656
+ elsif !doc.compat_mode && (link_text.include? '=')
657
+ # NOTE if an equals sign (=) is present, extract attributes from link text
658
+ link_text, attrs = extract_attributes_from_text link_text, ''
660
659
  link_opts[:id] = attrs['id']
661
660
  end
662
661
 
663
- if text.end_with? '^'
664
- text = text.chop
662
+ if link_text.end_with? '^'
663
+ link_text = link_text.chop
665
664
  if attrs
666
665
  attrs['window'] ||= '_blank'
667
666
  else
@@ -670,17 +669,17 @@ module Substitutors
670
669
  end
671
670
  end
672
671
 
673
- if text.empty?
672
+ if link_text.empty?
674
673
  # mailto is a special case, already processed
675
674
  if mailto
676
- text = mailto_text
675
+ link_text = mailto_text
677
676
  else
678
677
  if doc_attrs.key? 'hide-uri-scheme'
679
- if (text = target.sub UriSniffRx, '').empty?
680
- text = target
678
+ if (link_text = target.sub UriSniffRx, '').empty?
679
+ link_text = target
681
680
  end
682
681
  else
683
- text = target
682
+ link_text = target
684
683
  end
685
684
  if attrs
686
685
  attrs['role'] = (attrs.key? 'role') ? %(bare #{attrs['role']}) : 'bare'
@@ -693,7 +692,7 @@ module Substitutors
693
692
  # QUESTION should a mailto be registered as an e-mail address?
694
693
  doc.register :links, (link_opts[:target] = target)
695
694
  link_opts[:attributes] = attrs if attrs
696
- Inline.new(self, :anchor, text, link_opts).convert
695
+ Inline.new(self, :anchor, link_text, link_opts).convert
697
696
  end
698
697
  end
699
698
 
@@ -740,15 +739,17 @@ module Substitutors
740
739
 
741
740
  attrs = {}
742
741
  if (refid = $1)
743
- refid, text = refid.split ',', 2
744
- text = text.lstrip if text
742
+ if refid.include? ','
743
+ refid, _, link_text = refid.partition ','
744
+ link_text = nil if (link_text = link_text.lstrip).empty?
745
+ end
745
746
  else
746
747
  macro = true
747
748
  refid = $2
748
- if (text = $3)
749
- text = text.gsub ESC_R_SB, R_SB if text.include? R_SB
750
- # NOTE if an equals sign (=) is present, extract attributes from text
751
- text, attrs = extract_attributes_from_text text if !doc.compat_mode && (text.include? '=')
749
+ if (link_text = $3)
750
+ link_text = link_text.gsub ESC_R_SB, R_SB if link_text.include? R_SB
751
+ # NOTE if an equals sign (=) is present, extract attributes from link text
752
+ link_text, attrs = extract_attributes_from_text link_text if !doc.compat_mode && (link_text.include? '=')
752
753
  end
753
754
  end
754
755
 
@@ -827,7 +828,7 @@ module Substitutors
827
828
  attrs['path'] = path
828
829
  attrs['fragment'] = fragment
829
830
  attrs['refid'] = refid
830
- Inline.new(self, :anchor, text, type: :xref, target: target, attributes: attrs).convert
831
+ Inline.new(self, :anchor, link_text, type: :xref, target: target, attributes: attrs).convert
831
832
  end
832
833
  end
833
834
 
@@ -839,7 +840,7 @@ module Substitutors
839
840
  # footnoteref
840
841
  if $1
841
842
  if $3
842
- id, text = $3.split ',', 2
843
+ id, content = $3.split ',', 2
843
844
  logger.warn %(found deprecated footnoteref macro: #{$&}; use footnote macro with target instead) unless doc.compat_mode
844
845
  else
845
846
  next $&
@@ -847,31 +848,31 @@ module Substitutors
847
848
  # footnote
848
849
  else
849
850
  id = $2
850
- text = $3
851
+ content = $3
851
852
  end
852
853
 
853
854
  if id
854
855
  if (footnote = doc.footnotes.find {|candidate| candidate.id == id })
855
- index, text = footnote.index, footnote.text
856
+ index, content = footnote.index, footnote.text
856
857
  type, target, id = :xref, id, nil
857
- elsif text
858
- text = restore_passthroughs(normalize_text text, true, true)
858
+ elsif content
859
+ content = restore_passthroughs(normalize_text content, true, true)
859
860
  index = doc.counter('footnote-number')
860
- doc.register(:footnotes, Document::Footnote.new(index, id, text))
861
+ doc.register(:footnotes, Document::Footnote.new(index, id, content))
861
862
  type, target = :ref, nil
862
863
  else
863
864
  logger.warn %(invalid footnote reference: #{id})
864
- type, target, text, id = :xref, id, id, nil
865
+ type, target, content, id = :xref, id, id, nil
865
866
  end
866
- elsif text
867
- text = restore_passthroughs(normalize_text text, true, true)
867
+ elsif content
868
+ content = restore_passthroughs(normalize_text content, true, true)
868
869
  index = doc.counter('footnote-number')
869
- doc.register(:footnotes, Document::Footnote.new(index, id, text))
870
+ doc.register(:footnotes, Document::Footnote.new(index, id, content))
870
871
  type = target = nil
871
872
  else
872
873
  next $&
873
874
  end
874
- Inline.new(self, :footnote, text, attributes: { 'index' => index }, id: id, target: target, type: type).convert
875
+ Inline.new(self, :footnote, content, attributes: { 'index' => index }, id: id, target: target, type: type).convert
875
876
  end
876
877
  end
877
878
 
@@ -948,8 +949,9 @@ module Substitutors
948
949
 
949
950
  doc_attrs = @document.attributes
950
951
  syntax_hl_name = syntax_hl.name
951
- if (linenums_mode = (attr? 'linenums') ? (doc_attrs[%(#{syntax_hl_name}-linenums-mode)] || :table).to_sym : nil)
952
- start_line_number = 1 if (start_line_number = (attr 'start', 1).to_i) < 1
952
+ if (linenums_mode = (attr? 'linenums') ? (doc_attrs[%(#{syntax_hl_name}-linenums-mode)] || :table).to_sym : nil) &&
953
+ (start_line_number = (attr 'start', 1).to_i) < 1
954
+ start_line_number = 1
953
955
  end
954
956
  highlight_lines = resolve_lines_to_highlight source, (attr 'highlight'), start_line_number if attr? 'highlight'
955
957
 
@@ -986,7 +988,7 @@ module Substitutors
986
988
  negate = true
987
989
  end
988
990
  if (delim = (entry.include? '..') ? '..' : ((entry.include? '-') ? '-' : nil))
989
- from, delim, to = entry.partition delim
991
+ from, _, to = entry.partition delim
990
992
  to = (source.count LF) + 1 if to.empty? || (to = to.to_i) < 0
991
993
  if negate
992
994
  lines -= (from.to_i..to).to_a
@@ -1241,9 +1243,10 @@ module Substitutors
1241
1243
  #
1242
1244
  # Returns a Symbol Array of substitutions to pass to apply_subs or nil if no substitutions were resolved.
1243
1245
  def expand_subs subs, subject = nil
1244
- if ::Symbol === subs
1246
+ case subs
1247
+ when ::Symbol
1245
1248
  subs == :none ? nil : SUB_GROUPS[subs] || [subs]
1246
- elsif ::Array === subs
1249
+ when ::Array
1247
1250
  expanded_subs = []
1248
1251
  subs.each do |key|
1249
1252
  unless key == :none
@@ -1481,26 +1484,21 @@ module Substitutors
1481
1484
  if (str = str.strip).empty?
1482
1485
  {}
1483
1486
  elsif (str.start_with? '.', '#') && Compliance.shorthand_property_syntax
1484
- segments = str.split '#', 2
1485
-
1486
- if segments.size > 1
1487
- id, *more_roles = segments[1].split('.')
1487
+ before, _, after = str.partition '#'
1488
+ attrs = {}
1489
+ if after.empty?
1490
+ attrs['role'] = (before.tr '.', ' ').lstrip if before.length > 1
1488
1491
  else
1489
- more_roles = []
1490
- end
1491
-
1492
- roles = segments[0].empty? ? [] : segments[0].split('.')
1493
- if roles.size > 1
1494
- roles.shift
1495
- end
1496
-
1497
- if more_roles.size > 0
1498
- roles.concat more_roles
1492
+ id, _, roles = after.partition '.'
1493
+ attrs['id'] = id unless id.empty?
1494
+ if roles.empty?
1495
+ attrs['role'] = (before.tr '.', ' ').lstrip if before.length > 1
1496
+ elsif before.length > 1
1497
+ attrs['role'] = ((before + '.' + roles).tr '.', ' ').lstrip
1498
+ else
1499
+ attrs['role'] = roles.tr '.', ' '
1500
+ end
1499
1501
  end
1500
-
1501
- attrs = {}
1502
- attrs['id'] = id if id
1503
- attrs['role'] = roles.join ' ' unless roles.empty?
1504
1502
  attrs
1505
1503
  else
1506
1504
  { 'role' => str }
@@ -1534,7 +1532,7 @@ module Substitutors
1534
1532
  case c
1535
1533
  when ','
1536
1534
  if quote_open
1537
- accum = accum + c
1535
+ accum += c
1538
1536
  else
1539
1537
  values << accum.strip
1540
1538
  accum = ''
@@ -1542,7 +1540,7 @@ module Substitutors
1542
1540
  when '"'
1543
1541
  quote_open = !quote_open
1544
1542
  else
1545
- accum = accum + c
1543
+ accum += c
1546
1544
  end
1547
1545
  end
1548
1546
  values << accum.strip