asciidoctor 2.0.9 → 2.0.14

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +193 -16
  3. data/LICENSE +1 -1
  4. data/README-de.adoc +12 -13
  5. data/README-fr.adoc +11 -15
  6. data/README-jp.adoc +242 -185
  7. data/README-zh_CN.adoc +17 -18
  8. data/README.adoc +133 -131
  9. data/asciidoctor.gemspec +6 -6
  10. data/data/locale/attributes-ar.adoc +4 -3
  11. data/data/locale/attributes-be.adoc +23 -0
  12. data/data/locale/attributes-bg.adoc +4 -3
  13. data/data/locale/attributes-ca.adoc +6 -5
  14. data/data/locale/attributes-cs.adoc +4 -3
  15. data/data/locale/attributes-da.adoc +6 -5
  16. data/data/locale/attributes-de.adoc +4 -4
  17. data/data/locale/attributes-en.adoc +4 -4
  18. data/data/locale/attributes-es.adoc +6 -5
  19. data/data/locale/attributes-fa.adoc +4 -3
  20. data/data/locale/attributes-fi.adoc +4 -3
  21. data/data/locale/attributes-fr.adoc +6 -5
  22. data/data/locale/attributes-hu.adoc +4 -3
  23. data/data/locale/attributes-id.adoc +4 -3
  24. data/data/locale/attributes-it.adoc +6 -5
  25. data/data/locale/attributes-ja.adoc +4 -3
  26. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  27. data/data/locale/attributes-nb.adoc +4 -3
  28. data/data/locale/attributes-nl.adoc +6 -5
  29. data/data/locale/attributes-nn.adoc +4 -3
  30. data/data/locale/attributes-pl.adoc +8 -7
  31. data/data/locale/attributes-pt.adoc +6 -5
  32. data/data/locale/attributes-pt_BR.adoc +6 -5
  33. data/data/locale/attributes-ro.adoc +4 -3
  34. data/data/locale/attributes-ru.adoc +6 -5
  35. data/data/locale/attributes-sr.adoc +4 -4
  36. data/data/locale/attributes-sr_Latn.adoc +4 -4
  37. data/data/locale/attributes-sv.adoc +4 -4
  38. data/data/locale/attributes-tr.adoc +4 -3
  39. data/data/locale/attributes-uk.adoc +6 -5
  40. data/data/locale/attributes-zh_CN.adoc +4 -3
  41. data/data/locale/attributes-zh_TW.adoc +4 -3
  42. data/data/reference/syntax.adoc +14 -7
  43. data/data/stylesheets/asciidoctor-default.css +30 -30
  44. data/lib/asciidoctor.rb +40 -14
  45. data/lib/asciidoctor/abstract_block.rb +9 -4
  46. data/lib/asciidoctor/abstract_node.rb +16 -6
  47. data/lib/asciidoctor/attribute_list.rb +63 -71
  48. data/lib/asciidoctor/cli/invoker.rb +2 -0
  49. data/lib/asciidoctor/cli/options.rb +10 -9
  50. data/lib/asciidoctor/convert.rb +167 -162
  51. data/lib/asciidoctor/converter.rb +13 -12
  52. data/lib/asciidoctor/converter/docbook5.rb +5 -9
  53. data/lib/asciidoctor/converter/html5.rb +58 -45
  54. data/lib/asciidoctor/converter/manpage.rb +61 -38
  55. data/lib/asciidoctor/converter/template.rb +3 -0
  56. data/lib/asciidoctor/document.rb +44 -51
  57. data/lib/asciidoctor/extensions.rb +2 -4
  58. data/lib/asciidoctor/helpers.rb +20 -15
  59. data/lib/asciidoctor/load.rb +102 -101
  60. data/lib/asciidoctor/parser.rb +40 -32
  61. data/lib/asciidoctor/path_resolver.rb +14 -12
  62. data/lib/asciidoctor/reader.rb +20 -13
  63. data/lib/asciidoctor/rx.rb +7 -6
  64. data/lib/asciidoctor/substitutors.rb +69 -50
  65. data/lib/asciidoctor/syntax_highlighter.rb +15 -7
  66. data/lib/asciidoctor/syntax_highlighter/coderay.rb +1 -1
  67. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +12 -4
  68. data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
  69. data/lib/asciidoctor/syntax_highlighter/pygments.rb +6 -7
  70. data/lib/asciidoctor/syntax_highlighter/rouge.rb +33 -19
  71. data/lib/asciidoctor/table.rb +52 -23
  72. data/lib/asciidoctor/version.rb +1 -1
  73. data/man/asciidoctor.1 +8 -8
  74. data/man/asciidoctor.adoc +4 -4
  75. metadata +16 -15
data/lib/asciidoctor.rb CHANGED
@@ -136,8 +136,8 @@ module Asciidoctor
136
136
  # Compliance value: true
137
137
  define :underline_style_section_titles, true
138
138
 
139
- # Asciidoctor will unwrap the content in a preamble
140
- # if the document has a title and no sections.
139
+ # Asciidoctor will unwrap the content in a preamble if the document has a
140
+ # title and no sections, then discard the empty preamble.
141
141
  # Compliance value: false
142
142
  define :unwrap_standalone_preamble, true
143
143
 
@@ -148,7 +148,7 @@ module Asciidoctor
148
148
  # Compliance value: 'drop-line'
149
149
  define :attribute_missing, 'skip'
150
150
 
151
- # AsciiDoc drops lines that contain an attribute unassignemnt.
151
+ # AsciiDoc drops lines that contain an attribute unassignment.
152
152
  # This behavior may need to be tuned depending on the circumstances.
153
153
  # Compliance value: 'drop-line'
154
154
  define :attribute_undefined, 'drop-line'
@@ -296,7 +296,7 @@ module Asciidoctor
296
296
  DELIMITED_BLOCK_TAILS = {}.tap {|accum| DELIMITED_BLOCKS.each_key {|k| accum[k] = k[k.length - 1] if k.length == 4 } }
297
297
 
298
298
  # NOTE the 'figure' key as a string is historical and used by image blocks
299
- CAPTION_ATTR_NAMES = { example: 'example-caption', 'figure' => 'figure-caption', listing: 'listing-caption', table: 'table-caption' }
299
+ CAPTION_ATTRIBUTE_NAMES = { example: 'example-caption', 'figure' => 'figure-caption', listing: 'listing-caption', table: 'table-caption' }
300
300
 
301
301
  LAYOUT_BREAK_CHARS = {
302
302
  '\'' => :thematic_break,
@@ -332,7 +332,7 @@ module Asciidoctor
332
332
 
333
333
  LIST_CONTINUATION = '+'
334
334
 
335
- # NOTE AsciiDoc Python allows + to be preceded by TAB; Asciidoctor does not
335
+ # NOTE AsciiDoc.py allows + to be preceded by TAB; Asciidoctor does not
336
336
  HARD_LINE_BREAK = ' +'
337
337
 
338
338
  LINE_CONTINUATION = ' \\'
@@ -357,9 +357,35 @@ module Asciidoctor
357
357
 
358
358
  FONT_AWESOME_VERSION = '4.7.0'
359
359
 
360
- HIGHLIGHT_JS_VERSION = '9.15.6'
361
-
362
- MATHJAX_VERSION = '2.7.5'
360
+ HIGHLIGHT_JS_VERSION = '9.18.3'
361
+
362
+ MATHJAX_VERSION = '2.7.9'
363
+
364
+ DEFAULT_ATTRIBUTES = {
365
+ 'appendix-caption' => 'Appendix',
366
+ 'appendix-refsig' => 'Appendix',
367
+ 'caution-caption' => 'Caution',
368
+ 'chapter-refsig' => 'Chapter',
369
+ #'encoding' => 'UTF-8',
370
+ 'example-caption' => 'Example',
371
+ 'figure-caption' => 'Figure',
372
+ 'important-caption' => 'Important',
373
+ 'last-update-label' => 'Last updated',
374
+ #'listing-caption' => 'Listing',
375
+ 'note-caption' => 'Note',
376
+ 'part-refsig' => 'Part',
377
+ #'preface-title' => 'Preface',
378
+ 'prewrap' => '',
379
+ 'sectids' => '',
380
+ 'section-refsig' => 'Section',
381
+ 'table-caption' => 'Table',
382
+ 'tip-caption' => 'Tip',
383
+ 'toc-placement' => 'auto',
384
+ 'toc-title' => 'Table of Contents',
385
+ 'untitled-label' => 'Untitled',
386
+ 'version-label' => 'Version',
387
+ 'warning-caption' => 'Warning',
388
+ }
363
389
 
364
390
  # attributes which be changed throughout the flow of the document (e.g., sectnums)
365
391
  FLEXIBLE_ATTRIBUTES = ['sectnums']
@@ -433,9 +459,9 @@ module Asciidoctor
433
459
  [:emphasis, :unconstrained, /\\?(?:\[([^\]]+)\])?__(#{CC_ALL}+?)__/m],
434
460
  # _emphasis_
435
461
  [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?_(\S|\S#{CC_ALL}*?\S)_(?!#{CG_WORD})/m],
436
- # ##mark## (referred to in AsciiDoc Python as unquoted)
462
+ # ##mark## (referred to in AsciiDoc.py as unquoted)
437
463
  [:mark, :unconstrained, /\\?(?:\[([^\]]+)\])?##(#{CC_ALL}+?)##/m],
438
- # #mark# (referred to in AsciiDoc Python as unquoted)
464
+ # #mark# (referred to in AsciiDoc.py as unquoted)
439
465
  [:mark, :constrained, /(^|[^#{CC_WORD}&;:}])(?:\[([^\]]+)\])?#(\S|\S#{CC_ALL}*?\S)#(?!#{CG_WORD})/m],
440
466
  # ^superscript^
441
467
  [:superscript, :unconstrained, /\\?(?:\[([^\]]+)\])?\^(\S+?)\^/],
@@ -469,8 +495,8 @@ module Asciidoctor
469
495
  # (TM)
470
496
  [/\\?\(TM\)/, '™', :none],
471
497
  # foo -- bar (where either space character can be a newline)
472
- # NOTE this necessarily drops the newline if it appears at end of line
473
- [/(^|\n| |\\)--( |\n|$)/, ' — ', :none],
498
+ # NOTE this necessarily drops the newline if replacement appears at end of line
499
+ [/(?: |\n|^|\\)--(?: |\n|$)/, ' — ', :none],
474
500
  # foo--bar
475
501
  [/(#{CG_WORD})\\?--(?=#{CG_WORD})/, '—​', :leading],
476
502
  # ellipsis
@@ -511,8 +537,8 @@ module Asciidoctor
511
537
  end unless RUBY_ENGINE == 'opal'
512
538
 
513
539
  unless RUBY_ENGINE == 'opal'
514
- autoload :SyntaxHighlighter, %(#{LIB_DIR}/asciidoctor/syntax_highlighter)
515
- autoload :Timings, %(#{LIB_DIR}/asciidoctor/timings)
540
+ autoload :SyntaxHighlighter, %(#{__dir__}/asciidoctor/syntax_highlighter)
541
+ autoload :Timings, %(#{__dir__}/asciidoctor/timings)
516
542
  end
517
543
  end
518
544
 
@@ -141,6 +141,11 @@ class AbstractBlock < AbstractNode
141
141
  (Integer @numeral) rescue @numeral
142
142
  end
143
143
 
144
+ # Deprecated: Legacy property to set the numeral of this section by coercing the value to a String.
145
+ def number= val
146
+ @numeral = val.to_s
147
+ end
148
+
144
149
  # Public: Walk the document tree and find all block-level nodes that match the specified selector (context, style, id,
145
150
  # role, and/or custom filter).
146
151
  #
@@ -338,17 +343,17 @@ class AbstractBlock < AbstractNode
338
343
  if (val = reftext) && !val.empty?
339
344
  val
340
345
  # NOTE xrefstyle only applies to blocks with a title and a caption or number
341
- elsif xrefstyle && @title && @caption
346
+ elsif xrefstyle && @title && !@caption.nil_or_empty?
342
347
  case xrefstyle
343
348
  when 'full'
344
349
  quoted_title = sub_placeholder (sub_quotes @document.compat_mode ? %q(``%s'') : '"`%s`"'), title
345
- if @numeral && (caption_attr_name = CAPTION_ATTR_NAMES[@context]) && (prefix = @document.attributes[caption_attr_name])
350
+ if @numeral && (caption_attr_name = CAPTION_ATTRIBUTE_NAMES[@context]) && (prefix = @document.attributes[caption_attr_name])
346
351
  %(#{prefix} #{@numeral}, #{quoted_title})
347
352
  else
348
353
  %(#{@caption.chomp '. '}, #{quoted_title})
349
354
  end
350
355
  when 'short'
351
- if @numeral && (caption_attr_name = CAPTION_ATTR_NAMES[@context]) && (prefix = @document.attributes[caption_attr_name])
356
+ if @numeral && (caption_attr_name = CAPTION_ATTRIBUTE_NAMES[@context]) && (prefix = @document.attributes[caption_attr_name])
352
357
  %(#{prefix} #{@numeral})
353
358
  else
354
359
  @caption.chomp '. '
@@ -380,7 +385,7 @@ class AbstractBlock < AbstractNode
380
385
  # Returns nothing.
381
386
  def assign_caption value, caption_context = @context
382
387
  unless @caption || !@title || (@caption = value || @document.attributes['caption'])
383
- if (attr_name = CAPTION_ATTR_NAMES[caption_context]) && (prefix = @document.attributes[attr_name])
388
+ if (attr_name = CAPTION_ATTRIBUTE_NAMES[caption_context]) && (prefix = @document.attributes[attr_name])
384
389
  @caption = %(#{prefix} #{@numeral = @document.increment_and_store_counter %(#{caption_context}-number), self}. )
385
390
  nil
386
391
  end
@@ -65,7 +65,7 @@ class AbstractNode
65
65
  #
66
66
  # parent - The Block to set as the parent of this Block
67
67
  #
68
- # Returns the new parent Block associated with this Block
68
+ # Returns the value of the parent argument
69
69
  def parent= parent
70
70
  @parent, @document = parent, parent.document
71
71
  end
@@ -216,6 +216,15 @@ class AbstractNode
216
216
  (val = @attributes['role']) ? (%( #{val} ).include? %( #{name} )) : false
217
217
  end
218
218
 
219
+ # Public: Sets the value of the role attribute on this node.
220
+ #
221
+ # names - A single role name, a space-separated String of role names, or an Array of role names
222
+ #
223
+ # Returns the value of the names argument
224
+ def role= names
225
+ @attributes['role'] = (::Array === names) ? (names.join ' ') : names
226
+ end
227
+
219
228
  # Public: Adds the given role directly to this node.
220
229
  #
221
230
  # Returns a [Boolean] indicating whether the role was added.
@@ -514,6 +523,7 @@ class AbstractNode
514
523
  # * :normalize a Boolean that indicates whether the data should be normalized (default: false)
515
524
  # * :start the String relative base path to use when resolving the target (default: nil)
516
525
  # * :warn_on_failure a Boolean that indicates whether warnings are issued if the target cannot be read (default: true)
526
+ # * :warn_if_empty a Boolean that indicates whether a warning is issued if contents of target is empty (default: false)
517
527
  # Returns the contents of the resolved target or nil if the resolved target cannot be read
518
528
  # --
519
529
  # TODO refactor other methods in this class to use this method were possible (repurposing if necessary)
@@ -525,22 +535,22 @@ class AbstractNode
525
535
  Helpers.require_library 'open-uri/cached', 'open-uri-cached' if doc.attr? 'cache-uri'
526
536
  begin
527
537
  if opts[:normalize]
528
- (Helpers.prepare_source_string ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }).join LF
538
+ contents = (Helpers.prepare_source_string ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }).join LF
529
539
  else
530
- ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }
540
+ contents = ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }
531
541
  end
532
542
  rescue
533
543
  logger.warn %(could not retrieve contents of #{opts[:label] || 'asset'} at URI: #{target}) if opts.fetch :warn_on_failure, true
534
- return
535
544
  end
536
545
  else
537
546
  logger.warn %(cannot retrieve contents of #{opts[:label] || 'asset'} at URI: #{target} (allow-uri-read attribute not enabled)) if opts.fetch :warn_on_failure, true
538
- return
539
547
  end
540
548
  else
541
549
  target = normalize_system_path target, opts[:start], nil, target_name: (opts[:label] || 'asset')
542
- read_asset target, normalize: opts[:normalize], warn_on_failure: (opts.fetch :warn_on_failure, true), label: opts[:label]
550
+ contents = read_asset target, normalize: opts[:normalize], warn_on_failure: (opts.fetch :warn_on_failure, true), label: opts[:label]
543
551
  end
552
+ logger.warn %(contents of #{opts[:label] || 'asset'} is empty: #{target}) if contents && opts[:warn_if_empty] && contents.empty?
553
+ contents
544
554
  end
545
555
 
546
556
  # Deprecated: Check whether the specified String is a URI by
@@ -22,19 +22,20 @@ module Asciidoctor
22
22
  # => { 'style' => 'quote', 'attribution' => 'Famous Person', 'citetitle' => 'Famous Book (2001)' }
23
23
  #
24
24
  class AttributeList
25
- BACKSLASH = '\\'
26
25
  APOS = '\''
26
+ BACKSLASH = '\\'
27
+ QUOT = '"'
27
28
 
28
29
  # Public: Regular expressions for detecting the boundary of a value
29
- BoundaryRxs = {
30
- '"' => /.*?[^\\](?=")/,
30
+ BoundaryRx = {
31
+ QUOT => /.*?[^\\](?=")/,
31
32
  APOS => /.*?[^\\](?=')/,
32
33
  ',' => /.*?(?=[ \t]*(,|$))/
33
34
  }
34
35
 
35
36
  # Public: Regular expressions for unescaping quoted characters
36
37
  EscapedQuotes = {
37
- '"' => '\\"',
38
+ QUOT => '\\"',
38
39
  APOS => '\\\''
39
40
  }
40
41
 
@@ -45,14 +46,16 @@ class AttributeList
45
46
  BlankRx = /[ \t]+/
46
47
 
47
48
  # Public: Regular expressions for skipping delimiters
48
- SkipRxs = { ',' => /[ \t]*(,|$)/ }
49
+ SkipRx = {
50
+ ',' => /[ \t]*(,|$)/
51
+ }
49
52
 
50
53
  def initialize source, block = nil, delimiter = ','
51
54
  @scanner = ::StringScanner.new source
52
55
  @block = block
53
56
  @delimiter = delimiter
54
- @delimiter_skip_pattern = SkipRxs[delimiter]
55
- @delimiter_boundary_pattern = BoundaryRxs[delimiter]
57
+ @delimiter_skip_pattern = SkipRx[delimiter]
58
+ @delimiter_boundary_pattern = BoundaryRx[delimiter]
56
59
  @attributes = nil
57
60
  end
58
61
 
@@ -65,8 +68,6 @@ class AttributeList
65
68
  return @attributes if @attributes
66
69
 
67
70
  @attributes = {}
68
- # QUESTION do we want to store the attribute list as the zero-index attribute?
69
- #attributes[0] = @scanner.string
70
71
  index = 0
71
72
 
72
73
  while parse_attribute index, positional_attrs
@@ -83,75 +84,73 @@ class AttributeList
83
84
  end
84
85
 
85
86
  def self.rekey attributes, positional_attrs
86
- index = 0
87
- positional_attrs.each do |key|
88
- index += 1
89
- if (val = attributes[index])
87
+ positional_attrs.each_with_index do |key, index|
88
+ if key && (val = attributes[index + 1])
90
89
  # QUESTION should we delete the positional key?
91
90
  attributes[key] = val
92
- end if key
91
+ end
93
92
  end
94
93
  attributes
95
94
  end
96
95
 
97
96
  private
98
97
 
99
- def parse_attribute index = 0, positional_attrs = []
100
- single_quoted_value = false
98
+ def parse_attribute index, positional_attrs
99
+ continue = true
101
100
  skip_blank
102
- # example: "quote"
103
- if (first = @scanner.peek(1)) == '"'
101
+ case @scanner.peek 1
102
+ # example: "quote" || "foo
103
+ when QUOT
104
104
  name = parse_attribute_value @scanner.get_byte
105
- value = nil
106
- # example: 'quote'
107
- elsif first == APOS
105
+ # example: 'quote' || 'foo
106
+ when APOS
108
107
  name = parse_attribute_value @scanner.get_byte
109
- value = nil
110
- single_quoted_value = true unless name.start_with? APOS
108
+ single_quoted = true unless name.start_with? APOS
111
109
  else
112
- name = scan_name
113
-
114
- skipped = 0
115
- c = nil
110
+ skipped = ((name = scan_name) && skip_blank) || 0
116
111
  if @scanner.eos?
117
- return false unless name
118
- else
119
- skipped = skip_blank || 0
120
- c = @scanner.get_byte
121
- end
122
-
123
- # example: quote
124
- if !c || c == @delimiter
125
- value = nil
126
- # example: Sherlock Holmes || =foo=
127
- elsif c != '=' || !name
128
- name = %(#{name}#{' ' * skipped}#{c}#{scan_to_delimiter})
129
- value = nil
130
- else
131
- skip_blank
132
- if @scanner.peek(1)
133
- # example: foo="bar" || foo="ba\"zaar"
134
- if (c = @scanner.get_byte) == '"'
112
+ return unless name || (@scanner.string.rstrip.end_with? @delimiter)
113
+ # example: quote (at eos)
114
+ continue = nil
115
+ # example: quote,
116
+ elsif (c = @scanner.get_byte) == @delimiter
117
+ @scanner.unscan
118
+ elsif name
119
+ # example: foo=...
120
+ if c == '='
121
+ skip_blank
122
+ case (c = @scanner.get_byte)
123
+ # example: foo="bar" || foo="ba\"zaar" || foo="bar
124
+ when QUOT
135
125
  value = parse_attribute_value c
136
- # example: foo='bar' || foo='ba\'zaar' || foo='ba"zaar'
137
- elsif c == APOS
126
+ # example: foo='bar' || foo='ba\'zaar' || foo='ba"zaar' || foo='bar
127
+ when APOS
138
128
  value = parse_attribute_value c
139
- single_quoted_value = true unless value.start_with? APOS
129
+ single_quoted = true unless value.start_with? APOS
140
130
  # example: foo=,
141
- elsif c == @delimiter
131
+ when @delimiter
132
+ value = ''
133
+ @scanner.unscan
134
+ # example: foo= (at eos)
135
+ when nil
142
136
  value = ''
143
- # example: foo=bar (all spaces ignored)
137
+ # example: foo=bar || foo=None
144
138
  else
145
139
  value = %(#{c}#{scan_to_delimiter})
146
140
  return true if value == 'None'
147
141
  end
142
+ # example: foo bar
143
+ else
144
+ name = %(#{name}#{' ' * skipped}#{c}#{scan_to_delimiter})
148
145
  end
146
+ # example: =foo= || !foo
147
+ else
148
+ name = %(#{c}#{scan_to_delimiter})
149
149
  end
150
150
  end
151
151
 
152
152
  if value
153
- # example: options="opt1,opt2,opt3"
154
- # opts is an alias for options
153
+ # example: options="opt1,opt2,opt3" || opts="opts1,opt2,opt3"
155
154
  case name
156
155
  when 'options', 'opts'
157
156
  if value.include? ','
@@ -161,7 +160,7 @@ class AttributeList
161
160
  @attributes[%(#{value}-option)] = '' unless value.empty?
162
161
  end
163
162
  else
164
- if single_quoted_value && @block
163
+ if single_quoted && @block
165
164
  case name
166
165
  when 'title', 'reftext'
167
166
  @attributes[name] = value
@@ -173,33 +172,26 @@ class AttributeList
173
172
  end
174
173
  end
175
174
  else
176
- resolved_name = single_quoted_value && @block ? (@block.apply_subs name) : name
175
+ name = @block.apply_subs name if single_quoted && @block
177
176
  if (positional_attr_name = positional_attrs[index])
178
- @attributes[positional_attr_name] = resolved_name
177
+ @attributes[positional_attr_name] = name
179
178
  end
180
- # QUESTION should we always assign the positional key?
181
- @attributes[index + 1] = resolved_name
182
- # QUESTION should we assign the resolved name as an attribute?
183
- #@attributes[resolved_name] = nil
179
+ # QUESTION should we assign the positional key even when it's claimed by a positional attribute?
180
+ @attributes[index + 1] = name
184
181
  end
185
182
 
186
- true
183
+ continue
187
184
  end
188
185
 
189
186
  def parse_attribute_value quote
190
187
  # empty quoted value
191
- if @scanner.peek(1) == quote
188
+ if (@scanner.peek 1) == quote
192
189
  @scanner.get_byte
193
- return ''
194
- end
195
-
196
- if (value = scan_to_quote quote)
190
+ ''
191
+ elsif (value = scan_to_quote quote)
197
192
  @scanner.get_byte
198
- if value.include? BACKSLASH
199
- value.gsub EscapedQuotes[quote], quote
200
- else
201
- value
202
- end
193
+ (value.include? BACKSLASH) ? (value.gsub EscapedQuotes[quote], quote) : value
194
+ # leading quote only
203
195
  else
204
196
  %(#{quote}#{scan_to_delimiter})
205
197
  end
@@ -222,7 +214,7 @@ class AttributeList
222
214
  end
223
215
 
224
216
  def scan_to_quote quote
225
- @scanner.scan BoundaryRxs[quote]
217
+ @scanner.scan BoundaryRx[quote]
226
218
  end
227
219
  end
228
220
  end
@@ -42,6 +42,8 @@ module Asciidoctor
42
42
  non_posix_env = ::File::ALT_SEPARATOR == RS
43
43
  err = @err || $stderr
44
44
  show_timings = false
45
+ # NOTE in Ruby 2.7, RubyGems sets SOURCE_DATE_EPOCH if it's not set
46
+ ::ENV.delete 'SOURCE_DATE_EPOCH' if (::ENV.key? 'IGNORE_SOURCE_DATE_EPOCH') && (::Gem.respond_to? :source_date_epoch)
45
47
 
46
48
  @options.map do |key, val|
47
49
  case key