asciidoctor 1.5.7.1 → 1.5.8

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/CHANGELOG.adoc +95 -5
  3. data/Gemfile +23 -13
  4. data/README-de.adoc +482 -0
  5. data/README-fr.adoc +128 -119
  6. data/README-jp.adoc +2 -3
  7. data/README-zh_CN.adoc +2 -3
  8. data/README.adoc +131 -106
  9. data/asciidoctor.gemspec +9 -7
  10. data/data/locale/attributes-ar.adoc +1 -1
  11. data/data/locale/attributes-bg.adoc +1 -1
  12. data/data/locale/attributes-ca.adoc +1 -1
  13. data/data/locale/attributes-cs.adoc +1 -1
  14. data/data/locale/attributes-da.adoc +1 -1
  15. data/data/locale/attributes-de.adoc +1 -1
  16. data/data/locale/attributes-en.adoc +1 -1
  17. data/data/locale/attributes-es.adoc +1 -1
  18. data/data/locale/attributes-fa.adoc +1 -1
  19. data/data/locale/attributes-fi.adoc +1 -1
  20. data/data/locale/attributes-fr.adoc +1 -1
  21. data/data/locale/attributes-hu.adoc +1 -1
  22. data/data/locale/attributes-id.adoc +1 -1
  23. data/data/locale/attributes-it.adoc +1 -1
  24. data/data/locale/attributes-ja.adoc +1 -1
  25. data/data/locale/attributes-kr.adoc +1 -1
  26. data/data/locale/attributes-nb.adoc +1 -1
  27. data/data/locale/attributes-nl.adoc +1 -1
  28. data/data/locale/attributes-nn.adoc +1 -1
  29. data/data/locale/attributes-pl.adoc +1 -1
  30. data/data/locale/attributes-pt.adoc +1 -1
  31. data/data/locale/attributes-pt_BR.adoc +1 -1
  32. data/data/locale/attributes-ro.adoc +1 -1
  33. data/data/locale/attributes-ru.adoc +1 -1
  34. data/data/locale/attributes-sr.adoc +5 -4
  35. data/data/locale/attributes-sr_Latn.adoc +5 -4
  36. data/data/locale/attributes-sv.adoc +23 -0
  37. data/data/locale/attributes-tr.adoc +1 -1
  38. data/data/locale/attributes-uk.adoc +1 -1
  39. data/data/locale/attributes-zh_CN.adoc +1 -1
  40. data/data/locale/attributes-zh_TW.adoc +1 -1
  41. data/data/stylesheets/asciidoctor-default.css +23 -23
  42. data/lib/asciidoctor.rb +110 -104
  43. data/lib/asciidoctor/abstract_block.rb +55 -32
  44. data/lib/asciidoctor/abstract_node.rb +32 -17
  45. data/lib/asciidoctor/attribute_list.rb +8 -7
  46. data/lib/asciidoctor/block.rb +5 -7
  47. data/lib/asciidoctor/cli/options.rb +5 -9
  48. data/lib/asciidoctor/converter.rb +2 -2
  49. data/lib/asciidoctor/converter/docbook45.rb +7 -20
  50. data/lib/asciidoctor/converter/docbook5.rb +36 -37
  51. data/lib/asciidoctor/converter/factory.rb +10 -8
  52. data/lib/asciidoctor/converter/html5.rb +90 -65
  53. data/lib/asciidoctor/converter/manpage.rb +72 -62
  54. data/lib/asciidoctor/converter/template.rb +8 -6
  55. data/lib/asciidoctor/core_ext/1.8.7/concurrent/hash.rb +5 -0
  56. data/lib/asciidoctor/document.rb +62 -10
  57. data/lib/asciidoctor/extensions.rb +74 -16
  58. data/lib/asciidoctor/helpers.rb +11 -14
  59. data/lib/asciidoctor/list.rb +2 -2
  60. data/lib/asciidoctor/parser.rb +223 -195
  61. data/lib/asciidoctor/path_resolver.rb +15 -7
  62. data/lib/asciidoctor/reader.rb +65 -36
  63. data/lib/asciidoctor/section.rb +6 -4
  64. data/lib/asciidoctor/substitutors.rb +170 -149
  65. data/lib/asciidoctor/table.rb +16 -8
  66. data/lib/asciidoctor/version.rb +1 -1
  67. data/man/asciidoctor.1 +6 -5
  68. data/man/asciidoctor.adoc +3 -2
  69. data/test/api_test.rb +236 -0
  70. data/test/attribute_list_test.rb +242 -0
  71. data/test/attributes_test.rb +65 -52
  72. data/test/blocks_test.rb +408 -260
  73. data/test/converter_test.rb +7 -7
  74. data/test/document_test.rb +60 -54
  75. data/test/extensions_test.rb +218 -32
  76. data/test/fixtures/doctime-localtime.adoc +2 -0
  77. data/test/fixtures/section-a.adoc +4 -0
  78. data/test/fixtures/subs.adoc +0 -1
  79. data/test/invoker_test.rb +56 -18
  80. data/test/links_test.rb +105 -81
  81. data/test/lists_test.rb +636 -265
  82. data/test/logger_test.rb +1 -1
  83. data/test/manpage_test.rb +140 -3
  84. data/test/paragraphs_test.rb +42 -42
  85. data/test/parser_test.rb +63 -183
  86. data/test/paths_test.rb +21 -4
  87. data/test/preamble_test.rb +9 -9
  88. data/test/reader_test.rb +78 -28
  89. data/test/sections_test.rb +273 -151
  90. data/test/substitutions_test.rb +53 -19
  91. data/test/tables_test.rb +286 -163
  92. data/test/test_helper.rb +4 -3
  93. data/test/text_test.rb +65 -65
  94. metadata +16 -21
@@ -159,6 +159,29 @@ module Extensions
159
159
  Block.new parent, context, { :source => source, :attributes => attrs }.merge(opts)
160
160
  end
161
161
 
162
+ # Public: Creates a list node and links it to the specified parent.
163
+ #
164
+ # parent - The parent Block (Block, Section, or Document) of this new list block.
165
+ # context - The list context (e.g., :ulist, :olist, :colist, :dlist)
166
+ # attrs - A Hash of attributes to set on this list block
167
+ #
168
+ # Returns a [List] node with all properties properly initialized.
169
+ def create_list parent, context, attrs = nil
170
+ list = List.new parent, context
171
+ list.update_attributes attrs if attrs
172
+ list
173
+ end
174
+
175
+ # Public: Creates a list item node and links it to the specified parent.
176
+ #
177
+ # parent - The parent List of this new list item block.
178
+ # text - The text of the list item.
179
+ #
180
+ # Returns a [ListItem] node with all properties properly initialized.
181
+ def create_list_item parent, text = nil
182
+ ListItem.new parent, text
183
+ end
184
+
162
185
  # Public: Creates an image block node and links it to the specified parent.
163
186
  #
164
187
  # parent - The parent Block (Block, Section, or Document) of this new image block.
@@ -173,7 +196,13 @@ module Extensions
173
196
  raise ::ArgumentError, 'Unable to create an image block, target attribute is required'
174
197
  end
175
198
  attrs['alt'] ||= (attrs['default-alt'] = Helpers.basename(target, true).tr('_-', ' '))
176
- create_block parent, :image, nil, attrs, opts
199
+ title = (attrs.key? 'title') ? (attrs.delete 'title') : nil
200
+ block = create_block parent, :image, nil, attrs, opts
201
+ if title
202
+ block.title = title
203
+ block.assign_caption((attrs.delete 'caption'), (opts[:caption_context] || 'figure'))
204
+ end
205
+ block
177
206
  end
178
207
 
179
208
  def create_inline parent, context, text, opts = {}
@@ -243,7 +272,15 @@ module Extensions
243
272
  end
244
273
  end
245
274
 
246
- module SyntaxDsl
275
+ module DocumentProcessorDsl
276
+ include ProcessorDsl
277
+
278
+ def prefer
279
+ option :position, :>>
280
+ end
281
+ end
282
+
283
+ module SyntaxProcessorDsl
247
284
  include ProcessorDsl
248
285
 
249
286
  def named value
@@ -343,7 +380,7 @@ module Extensions
343
380
  raise ::NotImplementedError, %(Asciidoctor::Extensions::Preprocessor subclass must implement ##{__method__} method)
344
381
  end
345
382
  end
346
- Preprocessor::DSL = ProcessorDsl
383
+ Preprocessor::DSL = DocumentProcessorDsl
347
384
 
348
385
  # Public: TreeProcessors are run on the Document after the source has been
349
386
  # parsed into an abstract syntax tree (AST), as represented by the Document
@@ -360,7 +397,7 @@ module Extensions
360
397
  raise ::NotImplementedError, %(Asciidoctor::Extensions::TreeProcessor subclass must implement ##{__method__} method)
361
398
  end
362
399
  end
363
- TreeProcessor::DSL = ProcessorDsl
400
+ TreeProcessor::DSL = DocumentProcessorDsl
364
401
 
365
402
  # Alias deprecated class name for backwards compatibility
366
403
  Treeprocessor = TreeProcessor
@@ -385,7 +422,7 @@ module Extensions
385
422
  raise ::NotImplementedError, %(Asciidoctor::Extensions::Postprocessor subclass must implement ##{__method__} method)
386
423
  end
387
424
  end
388
- Postprocessor::DSL = ProcessorDsl
425
+ Postprocessor::DSL = DocumentProcessorDsl
389
426
 
390
427
  # Public: IncludeProcessors are used to process `include::<target>[]`
391
428
  # directives in the source document.
@@ -409,7 +446,7 @@ module Extensions
409
446
  end
410
447
 
411
448
  module IncludeProcessorDsl
412
- include ProcessorDsl
449
+ include DocumentProcessorDsl
413
450
 
414
451
  def handles? *args, &block
415
452
  if block_given?
@@ -451,7 +488,7 @@ module Extensions
451
488
  end
452
489
 
453
490
  module DocinfoProcessorDsl
454
- include ProcessorDsl
491
+ include DocumentProcessorDsl
455
492
 
456
493
  def at_location value
457
494
  option :location, value
@@ -507,7 +544,7 @@ module Extensions
507
544
  end
508
545
 
509
546
  module BlockProcessorDsl
510
- include SyntaxDsl
547
+ include SyntaxProcessorDsl
511
548
 
512
549
  def contexts *value
513
550
  option :contexts, value.flatten.to_set
@@ -533,7 +570,7 @@ module Extensions
533
570
  end
534
571
 
535
572
  module MacroProcessorDsl
536
- include SyntaxDsl
573
+ include SyntaxProcessorDsl
537
574
 
538
575
  def resolves_attributes *args
539
576
  if args.size == 1 && !args[0]
@@ -552,6 +589,10 @@ module Extensions
552
589
  #
553
590
  # BlockMacroProcessor implementations must extend BlockMacroProcessor.
554
591
  class BlockMacroProcessor < MacroProcessor
592
+ def name
593
+ raise ::ArgumentError, %(invalid name for block macro: #{@name}) unless MacroNameRx.match? @name.to_s
594
+ @name
595
+ end
555
596
  end
556
597
  BlockMacroProcessor::DSL = MacroProcessorDsl
557
598
 
@@ -1253,6 +1294,26 @@ module Extensions
1253
1294
  @inline_macro_extensions.values
1254
1295
  end
1255
1296
 
1297
+ # Public: Inserts the document processor {Extension} instance as the first
1298
+ # processor of its kind in the extension registry.
1299
+ #
1300
+ # Examples
1301
+ #
1302
+ # prefer :include_processor do
1303
+ # process do |document, reader, target, attrs|
1304
+ # ...
1305
+ # end
1306
+ # end
1307
+ #
1308
+ # Returns the [Extension] stored in the registry that proxies the instance
1309
+ # of this processor.
1310
+ def prefer *args, &block
1311
+ extension = ProcessorExtension === (arg0 = args.shift) ? arg0 : (send arg0, *args, &block)
1312
+ extensions_store = instance_variable_get(%(@#{extension.kind}_extensions).to_sym)
1313
+ extensions_store.unshift extensions_store.delete extension
1314
+ extension
1315
+ end
1316
+
1256
1317
  private
1257
1318
 
1258
1319
  def add_document_processor kind, args, &block
@@ -1298,11 +1359,8 @@ module Extensions
1298
1359
  end
1299
1360
  end
1300
1361
 
1301
- if extension.config[:position] == :>>
1302
- kind_store.unshift extension
1303
- else
1304
- kind_store << extension
1305
- end
1362
+ extension.config[:position] == :>> ? (kind_store.unshift extension) : (kind_store << extension)
1363
+ extension
1306
1364
  end
1307
1365
 
1308
1366
  def add_syntax_processor kind, args, &block
@@ -1490,7 +1548,7 @@ module Extensions
1490
1548
  # Public: Resolves the Class object for the qualified name.
1491
1549
  #
1492
1550
  # Returns Class
1493
- if RUBY_MIN_VERSION_2
1551
+ if ::RUBY_MIN_VERSION_2
1494
1552
  def class_for_name qualified_name
1495
1553
  resolved = ::Object.const_get qualified_name, false
1496
1554
  raise unless ::Class === resolved
@@ -1498,7 +1556,7 @@ module Extensions
1498
1556
  rescue
1499
1557
  raise ::NameError, %(Could not resolve class for name: #{qualified_name})
1500
1558
  end
1501
- elsif RUBY_MIN_VERSION_1_9
1559
+ elsif ::RUBY_MIN_VERSION_1_9
1502
1560
  def class_for_name qualified_name
1503
1561
  resolved = (qualified_name.split '::').reduce ::Object do |current, name|
1504
1562
  name.empty? ? current : (current.const_get name, false)
@@ -70,21 +70,20 @@ module Helpers
70
70
  if COERCE_ENCODING
71
71
  utf8 = ::Encoding::UTF_8
72
72
  if (leading_2_bytes = leading_bytes.slice 0, 2) == BOM_BYTES_UTF_16LE
73
- # HACK Ruby messes up trailing whitespace on UTF-16LE, so take a different route
74
- return ((data.join.force_encoding ::Encoding::UTF_16LE)[1..-1].encode utf8).each_line.map {|line| line.rstrip }
73
+ # HACK Ruby messes up trailing whitespace on UTF-16LE, so reencode whole document first
74
+ data = data.join
75
+ return (((data.force_encoding ::Encoding::UTF_16LE).slice 1, data.length).encode utf8).each_line.map {|line| line.rstrip }
75
76
  elsif leading_2_bytes == BOM_BYTES_UTF_16BE
76
- data[0] = (first_line.force_encoding ::Encoding::UTF_16BE)[1..-1]
77
- return data.map {|line| %(#{((line.force_encoding ::Encoding::UTF_16BE).encode utf8).rstrip}) }
77
+ data[0] = (first_line.force_encoding ::Encoding::UTF_16BE).slice 1, first_line.length
78
+ return data.map {|line| ((line.force_encoding ::Encoding::UTF_16BE).encode utf8).rstrip }
78
79
  elsif leading_bytes == BOM_BYTES_UTF_8
79
- data[0] = (first_line.force_encoding utf8)[1..-1]
80
+ data[0] = (first_line.force_encoding utf8).slice 1, first_line.length
80
81
  end
81
82
 
82
83
  data.map {|line| line.encoding == utf8 ? line.rstrip : (line.force_encoding utf8).rstrip }
83
84
  else
84
85
  # Ruby 1.8 has no built-in re-encoding, so no point in removing the UTF-16 BOMs
85
- if leading_bytes == BOM_BYTES_UTF_8
86
- data[0] = first_line[3..-1]
87
- end
86
+ data[0] = first_line.slice 3, first_line.length if leading_bytes == BOM_BYTES_UTF_8
88
87
  data.map {|line| line.rstrip }
89
88
  end
90
89
  end
@@ -107,19 +106,17 @@ module Helpers
107
106
  if COERCE_ENCODING
108
107
  utf8 = ::Encoding::UTF_8
109
108
  if (leading_2_bytes = leading_bytes.slice 0, 2) == BOM_BYTES_UTF_16LE
110
- data = (data.force_encoding ::Encoding::UTF_16LE)[1..-1].encode utf8
109
+ data = ((data.force_encoding ::Encoding::UTF_16LE).slice 1, data.length).encode utf8
111
110
  elsif leading_2_bytes == BOM_BYTES_UTF_16BE
112
- data = (data.force_encoding ::Encoding::UTF_16BE)[1..-1].encode utf8
111
+ data = ((data.force_encoding ::Encoding::UTF_16BE).slice 1, data.length).encode utf8
113
112
  elsif leading_bytes == BOM_BYTES_UTF_8
114
- data = data.encoding == utf8 ? data[1..-1] : (data.force_encoding utf8)[1..-1]
113
+ data = data.encoding == utf8 ? (data.slice 1, data.length) : ((data.force_encoding utf8).slice 1, data.length)
115
114
  else
116
115
  data = data.force_encoding utf8 unless data.encoding == utf8
117
116
  end
118
117
  else
119
118
  # Ruby 1.8 has no built-in re-encoding, so no point in removing the UTF-16 BOMs
120
- if leading_bytes == BOM_BYTES_UTF_8
121
- data = data[3..-1]
122
- end
119
+ data = data.slice 3, data.length if leading_bytes == BOM_BYTES_UTF_8
123
120
  end
124
121
  data.each_line.map {|line| line.rstrip }
125
122
  end
@@ -10,7 +10,7 @@ class List < AbstractBlock
10
10
  # Public: Create alias to check if this list has blocks
11
11
  alias items? blocks?
12
12
 
13
- def initialize parent, context
13
+ def initialize parent, context, opts = {}
14
14
  super
15
15
  end
16
16
 
@@ -57,7 +57,7 @@ class ListItem < AbstractBlock
57
57
  super parent, :list_item
58
58
  @text = text
59
59
  @level = parent.level
60
- @subs = NORMAL_SUBS.dup
60
+ @subs = NORMAL_SUBS.drop 0
61
61
  end
62
62
 
63
63
  # Public: A convenience method that checks whether the text of this list item
@@ -92,13 +92,16 @@ class Parser
92
92
  def self.parse(reader, document, options = {})
93
93
  block_attributes = parse_document_header(reader, document)
94
94
 
95
- while reader.has_more_lines?
96
- new_section, block_attributes = next_section(reader, document, block_attributes)
97
- if new_section
98
- document.assign_numeral new_section
99
- document.blocks << new_section
95
+ # NOTE don't use a postfix conditional here as it's known to confuse JRuby in certain circumstances
96
+ unless options[:header_only]
97
+ while reader.has_more_lines?
98
+ new_section, block_attributes = next_section(reader, document, block_attributes)
99
+ if new_section
100
+ document.assign_numeral new_section
101
+ document.blocks << new_section
102
+ end
100
103
  end
101
- end unless options[:header_only]
104
+ end
102
105
 
103
106
  document
104
107
  end
@@ -169,7 +172,7 @@ class Parser
169
172
  doc_attrs['doctitle'] = assigned_doctitle if assigned_doctitle
170
173
 
171
174
  # parse title and consume name section of manpage document
172
- parse_manpage_header(reader, document) if document.doctype == 'manpage'
175
+ parse_manpage_header(reader, document, block_attrs) if document.doctype == 'manpage'
173
176
 
174
177
  # NOTE block_attrs are the block-level attributes (not document attributes) that
175
178
  # precede the first line of content (document title, first section or first block)
@@ -179,47 +182,70 @@ class Parser
179
182
  # Public: Parses the manpage header of the AsciiDoc source read from the Reader
180
183
  #
181
184
  # returns Nothing
182
- def self.parse_manpage_header(reader, document)
183
- if ManpageTitleVolnumRx =~ document.attributes['doctitle']
184
- document.attributes['mantitle'] = (($1.include? ATTR_REF_HEAD) ? (document.sub_attributes $1) : $1).downcase
185
- document.attributes['manvolnum'] = $2
185
+ def self.parse_manpage_header(reader, document, block_attributes)
186
+ if ManpageTitleVolnumRx =~ (doc_attrs = document.attributes)['doctitle']
187
+ doc_attrs['manvolnum'] = manvolnum = $2
188
+ doc_attrs['mantitle'] = (((mantitle = $1).include? ATTR_REF_HEAD) ? (document.sub_attributes mantitle) : mantitle).downcase
186
189
  else
187
- logger.error message_with_context 'malformed manpage title', :source_location => reader.cursor_at_prev_line
190
+ logger.error message_with_context 'non-conforming manpage title', :source_location => (reader.cursor_at_line 1)
188
191
  # provide sensible fallbacks
189
- document.attributes['mantitle'] = document.attributes['doctitle']
190
- document.attributes['manvolnum'] = '1'
192
+ doc_attrs['mantitle'] = doc_attrs['doctitle'] || doc_attrs['docname'] || 'command'
193
+ doc_attrs['manvolnum'] = manvolnum = '1'
191
194
  end
192
-
193
- reader.skip_blank_lines
194
-
195
- if is_next_line_section? reader, {}
196
- name_section = initialize_section reader, document, {}
197
- if name_section.level == 1
198
- name_section_buffer = (reader.read_lines_until :break_on_blank_lines => true, :skip_line_comments => true) * ' '
199
- if ManpageNamePurposeRx =~ name_section_buffer
200
- document.attributes['manname-title'] ||= name_section.title
201
- document.attributes['manname-id'] = name_section.id if name_section.id
202
- document.attributes['manpurpose'] = $2
203
- if (manname = ($1.include? ATTR_REF_HEAD) ? (document.sub_attributes $1) : $1).include? ','
204
- manname = (mannames = (manname.split ',').map {|n| n.lstrip })[0]
195
+ if (manname = doc_attrs['manname']) && doc_attrs['manpurpose']
196
+ doc_attrs['manname-title'] ||= 'Name'
197
+ doc_attrs['mannames'] = [manname]
198
+ if document.backend == 'manpage'
199
+ doc_attrs['docname'] = manname
200
+ doc_attrs['outfilesuffix'] = %(.#{manvolnum})
201
+ end
202
+ else
203
+ reader.skip_blank_lines
204
+ reader.save
205
+ block_attributes.update parse_block_metadata_lines reader, document
206
+ if (name_section_level = is_next_line_section? reader, {})
207
+ if name_section_level == 1
208
+ name_section = initialize_section reader, document, {}
209
+ name_section_buffer = (reader.read_lines_until :break_on_blank_lines => true, :skip_line_comments => true).map(&:lstrip).join ' '
210
+ if ManpageNamePurposeRx =~ name_section_buffer
211
+ doc_attrs['manname-title'] ||= name_section.title
212
+ doc_attrs['manname-id'] = name_section.id if name_section.id
213
+ doc_attrs['manpurpose'] = $2
214
+ if (manname = $1).include? ATTR_REF_HEAD
215
+ manname = document.sub_attributes manname
216
+ end
217
+ if manname.include? ','
218
+ manname = (mannames = (manname.split ',').map {|n| n.lstrip })[0]
219
+ else
220
+ mannames = [manname]
221
+ end
222
+ doc_attrs['manname'] = manname
223
+ doc_attrs['mannames'] = mannames
224
+ if document.backend == 'manpage'
225
+ doc_attrs['docname'] = manname
226
+ doc_attrs['outfilesuffix'] = %(.#{manvolnum})
227
+ end
205
228
  else
206
- mannames = [manname]
207
- end
208
- document.attributes['manname'] = manname
209
- document.attributes['mannames'] = mannames
210
-
211
- if document.backend == 'manpage'
212
- document.attributes['docname'] = manname
213
- document.attributes['outfilesuffix'] = %(.#{document.attributes['manvolnum']})
229
+ error_msg = 'non-conforming name section body'
214
230
  end
215
231
  else
216
- logger.error message_with_context 'malformed name section body', :source_location => reader.cursor_at_prev_line
232
+ error_msg = 'name section must be at level 1'
217
233
  end
218
234
  else
219
- logger.error message_with_context 'name section title must be at level 1', :source_location => reader.cursor_at_prev_line
235
+ error_msg = 'name section expected'
236
+ end
237
+ if error_msg
238
+ reader.restore_save
239
+ logger.error message_with_context error_msg, :source_location => reader.cursor
240
+ doc_attrs['manname'] = (manname = doc_attrs['docname'] || 'command')
241
+ doc_attrs['mannames'] = [manname]
242
+ if document.backend == 'manpage'
243
+ doc_attrs['docname'] = manname
244
+ doc_attrs['outfilesuffix'] = %(.#{manvolnum})
245
+ end
246
+ else
247
+ reader.discard_save
220
248
  end
221
- else
222
- logger.error message_with_context 'name section expected', :source_location => reader.cursor_at_prev_line
223
249
  end
224
250
  nil
225
251
  end
@@ -489,7 +515,7 @@ class Parser
489
515
  end
490
516
  end
491
517
 
492
- # this loop is used for flow control; it only executes once, and only when delimited_block is set
518
+ # this loop is used for flow control; it only executes once, and only when delimited_block is not set
493
519
  # break once a block is found or at end of loop
494
520
  # returns nil if the line should be dropped
495
521
  while true
@@ -542,7 +568,7 @@ class Parser
542
568
  else # :image
543
569
  posattrs = ['alt', 'width', 'height']
544
570
  end
545
- block.parse_attributes blk_attrs, posattrs, :sub_input => true, :sub_result => false, :into => attributes
571
+ block.parse_attributes blk_attrs, posattrs, :sub_input => true, :into => attributes
546
572
  end
547
573
  # style doesn't have special meaning for media macros
548
574
  attributes.delete 'style' if attributes.key? 'style'
@@ -557,31 +583,36 @@ class Parser
557
583
  end
558
584
  end
559
585
  if blk_ctx == :image
560
- document.register :images, target
586
+ document.register :images, [target, (attributes['imagesdir'] = doc_attrs['imagesdir'])]
561
587
  # NOTE style is the value of the first positional attribute in the block attribute line
562
588
  attributes['alt'] ||= style || (attributes['default-alt'] = Helpers.basename(target, true).tr('_-', ' '))
563
589
  unless (scaledwidth = attributes.delete 'scaledwidth').nil_or_empty?
564
590
  # NOTE assume % units if not specified
565
591
  attributes['scaledwidth'] = (TrailingDigitsRx.match? scaledwidth) ? %(#{scaledwidth}%) : scaledwidth
566
592
  end
567
- block.title = attributes.delete 'title'
568
- block.assign_caption((attributes.delete 'caption'), 'figure')
593
+ if attributes.key? 'title'
594
+ block.title = attributes.delete 'title'
595
+ block.assign_caption((attributes.delete 'caption'), 'figure')
596
+ end
569
597
  end
570
598
  attributes['target'] = target
571
599
  break
572
600
 
573
601
  elsif ch0 == 't' && (this_line.start_with? 'toc:') && BlockTocMacroRx =~ this_line
574
602
  block = Block.new parent, :toc, :content_model => :empty
575
- block.parse_attributes($1, [], :sub_result => false, :into => attributes) if $1
603
+ block.parse_attributes $1, [], :into => attributes if $1
576
604
  break
577
605
 
578
606
  elsif block_macro_extensions && CustomBlockMacroRx =~ this_line &&
579
607
  (extension = extensions.registered_for_block_macro? $1)
580
608
  target, content = $2, $3
609
+ if (target.include? ATTR_REF_HEAD) && (target = parent.sub_attributes target).empty? &&
610
+ (doc_attrs['attribute-missing'] || Compliance.attribute_missing) == 'drop-line'
611
+ attributes.clear
612
+ return
613
+ end
581
614
  if extension.config[:content_model] == :attributes
582
- if content
583
- document.parse_attributes content, extension.config[:pos_attrs] || [], :sub_input => true, :sub_result => false, :into => attributes
584
- end
615
+ document.parse_attributes content, extension.config[:pos_attrs] || [], :sub_input => true, :into => attributes if content
585
616
  else
586
617
  attributes['text'] = content || ''
587
618
  end
@@ -601,61 +632,27 @@ class Parser
601
632
  end
602
633
 
603
634
  # haven't found anything yet, continue
604
- if !indented && CALLOUT_LIST_HEADS.include?(ch0 ||= this_line.chr) &&
605
- (CalloutListSniffRx.match? this_line) && (match = CalloutListRx.match this_line)
606
- block = List.new(parent, :colist)
607
- attributes['style'] = 'arabic'
635
+ if !indented && (ch0 ||= this_line.chr) == '<' && CalloutListRx =~ this_line
608
636
  reader.unshift_line this_line
609
- next_index = 1
610
- # NOTE skip the match on the first time through as we've already done it (emulates begin...while)
611
- while match || ((match = CalloutListRx.match reader.peek_line) && reader.mark)
612
- # might want to move this check to a validate method
613
- unless match[1] == next_index.to_s
614
- logger.warn message_with_context %(callout list item index: expected #{next_index}, got #{match[1]}), :source_location => reader.cursor_at_mark
615
- end
616
- if (list_item = next_list_item reader, block, match)
617
- block.items << list_item
618
- if (coids = document.callouts.callout_ids block.items.size).empty?
619
- logger.warn message_with_context %(no callout found for <#{block.items.size}>), :source_location => reader.cursor_at_mark
620
- else
621
- list_item.attributes['coids'] = coids
622
- end
623
- end
624
- next_index += 1
625
- match = nil
626
- end
627
-
628
- document.callouts.next_list
637
+ block = parse_callout_list(reader, $~, parent, document.callouts)
638
+ attributes['style'] = 'arabic'
629
639
  break
630
640
 
631
641
  elsif UnorderedListRx.match? this_line
632
642
  reader.unshift_line this_line
633
- if Section === parent && parent.sectname == 'bibliography'
634
- style = attributes['style'] = 'bibliography'
635
- end unless style
636
- block = next_list(reader, :ulist, parent, style)
643
+ attributes['style'] = (style = 'bibliography') if !style && Section === parent && parent.sectname == 'bibliography'
644
+ block = parse_list(reader, :ulist, parent, style)
637
645
  break
638
646
 
639
647
  elsif (match = OrderedListRx.match(this_line))
640
648
  reader.unshift_line this_line
641
- block = next_list(reader, :olist, parent)
642
- # FIXME move this logic into next_list
643
- unless style
644
- marker = block.items[0].marker
645
- if marker.start_with? '.'
646
- # first one makes more sense, but second one is AsciiDoc-compliant
647
- # TODO control behavior using a compliance setting
648
- #attributes['style'] = (ORDERED_LIST_STYLES[block.level - 1] || 'arabic').to_s
649
- attributes['style'] = (ORDERED_LIST_STYLES[marker.length - 1] || 'arabic').to_s
650
- else
651
- attributes['style'] = (ORDERED_LIST_STYLES.find {|s| OrderedListMarkerRxMap[s].match? marker } || 'arabic').to_s
652
- end
653
- end
649
+ block = parse_list(reader, :olist, parent, style)
650
+ attributes['style'] = block.style if block.style
654
651
  break
655
652
 
656
653
  elsif (match = DescriptionListRx.match(this_line))
657
654
  reader.unshift_line this_line
658
- block = next_description_list(reader, match, parent)
655
+ block = parse_description_list(reader, match, parent)
659
656
  break
660
657
 
661
658
  elsif (style == 'float' || style == 'discrete') && (Compliance.underline_style_section_titles ?
@@ -725,9 +722,9 @@ class Parser
725
722
  attributes['textlabel'] = (attributes.delete 'caption') || doc_attrs[%(#{admonition_name}-caption)]
726
723
  block = Block.new(parent, :admonition, :content_model => :simple, :source => lines, :attributes => attributes)
727
724
  elsif md_syntax && ch0 == '>' && this_line.start_with?('> ')
728
- lines.map! {|line| line == '>' ? line[1..-1] : ((line.start_with? '> ') ? line[2..-1] : line) }
725
+ lines.map! {|line| line == '>' ? (line.slice 1, line.length) : ((line.start_with? '> ') ? (line.slice 2, line.length) : line) }
729
726
  if lines[-1].start_with? '-- '
730
- credit_line = (credit_line = lines.pop[3..-1])
727
+ credit_line = (credit_line = lines.pop).slice 3, credit_line.length
731
728
  lines.pop while lines[-1].empty?
732
729
  end
733
730
  attributes['style'] = 'quote'
@@ -741,10 +738,10 @@ class Parser
741
738
  attributes['citetitle'] = citetitle if citetitle
742
739
  end
743
740
  elsif ch0 == '"' && lines.size > 1 && (lines[-1].start_with? '-- ') && (lines[-2].end_with? '"')
744
- lines[0] = this_line[1..-1] # strip leading quote
745
- credit_line = (credit_line = lines.pop).slice(3, credit_line.length)
741
+ lines[0] = this_line.slice 1, this_line.length # strip leading quote
742
+ credit_line = (credit_line = lines.pop).slice 3, credit_line.length
746
743
  lines.pop while lines[-1].empty?
747
- lines[-1] = lines[-1].chop # strip trailing quote
744
+ lines << lines.pop.chop # strip trailing quote
748
745
  attributes['style'] = 'quote'
749
746
  block = Block.new(parent, :quote, :content_model => :simple, :source => lines, :attributes => attributes)
750
747
  attribution, citetitle = (block.apply_subs credit_line).split ', ', 2
@@ -757,7 +754,7 @@ class Parser
757
754
  block = Block.new(parent, :paragraph, :content_model => :simple, :source => lines, :attributes => attributes)
758
755
  end
759
756
 
760
- catalog_inline_anchors lines * LF, block, document
757
+ catalog_inline_anchors((lines.join LF), block, document, reader)
761
758
  end
762
759
 
763
760
  break # forbid loop from executing more than once
@@ -848,7 +845,7 @@ class Parser
848
845
  # NOTE infer dsv once all other format hint chars are ruled out
849
846
  attributes['format'] ||= (terminator.start_with? ',') ? 'csv' : 'dsv'
850
847
  end
851
- block = next_table(block_reader, parent, attributes)
848
+ block = parse_table(block_reader, parent, attributes)
852
849
 
853
850
  when :quote, :verse
854
851
  AttributeList.rekey(attributes, [nil, 'attribution', 'citetitle'])
@@ -966,7 +963,7 @@ class Parser
966
963
  else
967
964
  true
968
965
  end
969
- elsif %(#{tip}#{tip[-1..-1] * (line_len - tl)}) == line
966
+ elsif %(#{tip}#{tip.slice(-1, 1) * (line_len - tl)}) == line
970
967
  if return_match_data
971
968
  context, masq = DELIMITED_BLOCKS[tip]
972
969
  BlockMatchData.new(context, masq, tip, line)
@@ -1091,45 +1088,15 @@ class Parser
1091
1088
  # style - The block style assigned to this list (optional, default: nil)
1092
1089
  #
1093
1090
  # Returns the Block encapsulating the parsed unordered or ordered list
1094
- def self.next_list(reader, list_type, parent, style = nil)
1091
+ def self.parse_list(reader, list_type, parent, style)
1095
1092
  list_block = List.new(parent, list_type)
1096
- list_block.level = parent.context == list_type ? (parent.level + 1) : 1
1097
-
1098
- while reader.has_more_lines? && ListRxMap[list_type] =~ reader.peek_line
1099
- match, marker = $~, resolve_list_marker(list_type, $1)
1100
-
1101
- # if we are moving to the next item, and the marker is different
1102
- # determine if we are moving up or down in nesting
1103
- if list_block.items? && marker != list_block.items[0].marker
1104
- # assume list is nested by default, but then check to see if we are
1105
- # popping out of a nested list by matching an ancestor's list marker
1106
- this_item_level = list_block.level + 1
1107
- ancestor = parent
1108
- while ancestor.context == list_type
1109
- if marker == ancestor.items[0].marker
1110
- this_item_level = ancestor.level
1111
- break
1112
- end
1113
- ancestor = ancestor.parent
1114
- end
1115
- else
1116
- this_item_level = list_block.level
1117
- end
1118
1093
 
1119
- if !list_block.items? || this_item_level == list_block.level
1120
- list_item = next_list_item(reader, list_block, match, nil, style)
1121
- elsif this_item_level < list_block.level
1122
- # leave this block
1123
- break
1124
- elsif this_item_level > list_block.level
1125
- # If this next list level is down one from the
1126
- # current Block's, append it to content of the current list item
1127
- list_block.items[-1] << next_block(reader, list_block)
1094
+ while reader.has_more_lines? && (list_rx ||= ListRxMap[list_type]) =~ reader.peek_line
1095
+ # NOTE parse_list_item will stop at sibling item or end of list; never sees ancestor items
1096
+ if (list_item = parse_list_item reader, list_block, $~, $1, style)
1097
+ list_block.items << list_item
1128
1098
  end
1129
1099
 
1130
- list_block.items << list_item if list_item
1131
- list_item = nil
1132
-
1133
1100
  reader.skip_blank_lines || break
1134
1101
  end
1135
1102
 
@@ -1144,10 +1111,11 @@ class Parser
1144
1111
  # Returns A Boolean indicating whether callouts were found
1145
1112
  def self.catalog_callouts(text, document)
1146
1113
  found = false
1114
+ autonum = 0
1147
1115
  text.scan(CalloutScanRx) {
1148
1116
  # lead with assignments for Ruby 1.8.7 compat
1149
1117
  captured, num = $&, $2
1150
- document.callouts.register num unless captured.start_with? '\\'
1118
+ document.callouts.register num == '.' ? (autonum += 1).to_s : num unless captured.start_with? '\\'
1151
1119
  # we have to mark as found even if it's escaped so it can be unescaped
1152
1120
  found = true
1153
1121
  } if text.include? '<'
@@ -1180,8 +1148,10 @@ class Parser
1180
1148
  # document - The current Document on which the references are stored
1181
1149
  #
1182
1150
  # Returns nothing
1183
- def self.catalog_inline_anchors text, block, document
1151
+ def self.catalog_inline_anchors text, block, document, reader
1184
1152
  text.scan(InlineAnchorScanRx) do
1153
+ # alias match for Ruby 1.8.7 compat
1154
+ m = $~
1185
1155
  if (id = $1)
1186
1156
  if (reftext = $2)
1187
1157
  next if (reftext.include? ATTR_REF_HEAD) && (reftext = document.sub_attributes reftext).empty?
@@ -1194,7 +1164,11 @@ class Parser
1194
1164
  end
1195
1165
  end
1196
1166
  unless document.register :refs, [id, (Inline.new block, :anchor, reftext, :type => :ref, :id => id), reftext]
1197
- logger.warn message_with_context %(id assigned to anchor already in use: #{id}), :source_location => document.reader.cursor_at_prev_line
1167
+ location = reader.cursor_at_mark
1168
+ if (offset = (m.pre_match.count LF) + ((m[0].start_with? LF) ? 1 : 0)) > 0
1169
+ (location = location.dup).advance offset
1170
+ end
1171
+ logger.warn message_with_context %(id assigned to anchor already in use: #{id}), :source_location => location
1198
1172
  end
1199
1173
  end if (text.include? '[[') || (text.include? 'or:')
1200
1174
  nil
@@ -1223,7 +1197,7 @@ class Parser
1223
1197
  # parent - The parent Block to which this description list belongs
1224
1198
  #
1225
1199
  # Returns the Block encapsulating the parsed description list
1226
- def self.next_description_list(reader, match, parent)
1200
+ def self.parse_description_list(reader, match, parent)
1227
1201
  list_block = List.new(parent, :dlist)
1228
1202
  previous_pair = nil
1229
1203
  # allows us to capture until we find a description item
@@ -1232,7 +1206,7 @@ class Parser
1232
1206
 
1233
1207
  # NOTE skip the match on the first time through as we've already done it (emulates begin...while)
1234
1208
  while match || (reader.has_more_lines? && (match = sibling_pattern.match(reader.peek_line)))
1235
- term, item = next_list_item(reader, list_block, match, sibling_pattern)
1209
+ term, item = parse_list_item(reader, list_block, match, sibling_pattern)
1236
1210
  if previous_pair && !previous_pair[1]
1237
1211
  previous_pair[0] << term
1238
1212
  previous_pair[1] = item
@@ -1245,25 +1219,63 @@ class Parser
1245
1219
  list_block
1246
1220
  end
1247
1221
 
1248
- # Internal: Parse and construct the next ListItem for the current list Block
1249
- # (unordered, ordered, or callout list) or the term ListItem and description
1250
- # ListItem pair for the description list Block.
1222
+ # Internal: Parse and construct a callout list Block from the current position of the Reader and
1223
+ # advance the document callouts catalog to the next list.
1251
1224
  #
1252
- # First collect and process all the lines that constitute the next list
1253
- # item for the parent list (according to its type). Next, parse those lines
1254
- # into blocks and associate them with the ListItem (in the case of a
1255
- # description list, the description ListItem). Finally, fold the first block
1256
- # into the item's text attribute according to rules described in ListItem.
1225
+ # reader - The Reader from which to retrieve the callout list.
1226
+ # match - The Regexp match containing the head of the list.
1227
+ # parent - The parent Block to which this callout list belongs.
1228
+ # callouts - The document callouts catalog.
1229
+ #
1230
+ # Returns the Block that represents the parsed callout list.
1231
+ def self.parse_callout_list reader, match, parent, callouts
1232
+ list_block = List.new(parent, :colist)
1233
+ next_index = 1
1234
+ autonum = 0
1235
+ # NOTE skip the match on the first time through as we've already done it (emulates begin...while)
1236
+ while match || ((match = CalloutListRx.match reader.peek_line) && reader.mark)
1237
+ if (num = match[1]) == '.'
1238
+ num = (autonum += 1).to_s
1239
+ end
1240
+ # might want to move this check to a validate method
1241
+ unless num == next_index.to_s
1242
+ logger.warn message_with_context %(callout list item index: expected #{next_index}, got #{num}), :source_location => reader.cursor_at_mark
1243
+ end
1244
+ if (list_item = parse_list_item reader, list_block, match, '<1>')
1245
+ list_block.items << list_item
1246
+ if (coids = callouts.callout_ids list_block.items.size).empty?
1247
+ logger.warn message_with_context %(no callout found for <#{list_block.items.size}>), :source_location => reader.cursor_at_mark
1248
+ else
1249
+ list_item.attributes['coids'] = coids
1250
+ end
1251
+ end
1252
+ next_index += 1
1253
+ match = nil
1254
+ end
1255
+
1256
+ callouts.next_list
1257
+ list_block
1258
+ end
1259
+
1260
+ # Internal: Parse and construct the next ListItem (unordered, ordered, or callout list) or next
1261
+ # term ListItem and description ListItem pair (description list) for the specified list Block.
1262
+ #
1263
+ # First, collect and process all the lines that constitute the next list item for the specified
1264
+ # list (according to its type). Next, create a ListItem (in the case of a description list, a
1265
+ # description ListItem), parse the lines into blocks, and associate those blocks with that
1266
+ # ListItem. Finally, fold the first block into the item's text attribute according to rules
1267
+ # described in ListItem.
1257
1268
  #
1258
1269
  # reader - The Reader from which to retrieve the next list item
1259
- # list_block - The parent list Block of this ListItem. Also provides access to the list type.
1260
- # match - The match Array which contains the marker and text (first-line) of the ListItem
1261
- # sibling_trait - The list marker or the Regexp to match a sibling item (optional, default: nil)
1270
+ # list_block - The parent list Block for this ListItem. Also provides access to the list type.
1271
+ # match - The MatchData that contains the list item marker and first line text of the ListItem
1272
+ # sibling_trait - The trait to match a sibling list item. For ordered and unordered lists, this is
1273
+ # a String marker (e.g., '**' or 'ii)'). For description lists, this is a Regexp
1274
+ # marker pattern.
1262
1275
  # style - The block style assigned to this list (optional, default: nil)
1263
1276
  #
1264
- # Returns the next ListItem or ListItem pair (depending on the list type)
1265
- # for the parent list Block.
1266
- def self.next_list_item(reader, list_block, match, sibling_trait = nil, style = nil)
1277
+ # Returns the next ListItem or ListItem pair (description list) for the parent list Block.
1278
+ def self.parse_list_item(reader, list_block, match, sibling_trait, style = nil)
1267
1279
  if (list_type = list_block.context) == :dlist
1268
1280
  dlist = true
1269
1281
  list_term = ListItem.new(list_block, (term_text = match[1]))
@@ -1285,7 +1297,7 @@ class Parser
1285
1297
  list_item = ListItem.new(list_block, (item_text = match[2]))
1286
1298
  list_item.source_location = reader.cursor if list_block.document.sourcemap
1287
1299
  if list_type == :ulist
1288
- list_item.marker = (sibling_trait ||= match[1])
1300
+ list_item.marker = sibling_trait
1289
1301
  if item_text.start_with?('[')
1290
1302
  if style && style == 'bibliography'
1291
1303
  if InlineBiblioAnchorRx =~ item_text
@@ -1305,9 +1317,18 @@ class Parser
1305
1317
  end
1306
1318
  end
1307
1319
  elsif list_type == :olist
1308
- list_item.marker = (sibling_trait ||= resolve_ordered_list_marker(match[1], list_block.items.size, true, reader))
1320
+ sibling_trait, implicit_style = resolve_ordered_list_marker(sibling_trait, (ordinal = list_block.items.size), true, reader)
1321
+ list_item.marker = sibling_trait
1322
+ if ordinal == 0 && !style
1323
+ # using list level makes more sense, but we don't track it
1324
+ # basing style on marker level is compliant with AsciiDoc Python
1325
+ list_block.style = implicit_style || ((ORDERED_LIST_STYLES[sibling_trait.length - 1] || 'arabic').to_s)
1326
+ end
1327
+ if item_text.start_with?('[[') && LeadingInlineAnchorRx =~ item_text
1328
+ catalog_inline_anchor $1, $2, list_item, reader
1329
+ end
1309
1330
  else # :colist
1310
- list_item.marker = (sibling_trait ||= '<1>')
1331
+ list_item.marker = sibling_trait
1311
1332
  end
1312
1333
  end
1313
1334
 
@@ -1540,7 +1561,7 @@ class Parser
1540
1561
  # a blank line would have served the same purpose in the document
1541
1562
  buffer.pop if !buffer.empty? && buffer[-1] == LIST_CONTINUATION
1542
1563
 
1543
- #warn "BUFFER[#{list_type},#{sibling_trait}]>#{buffer * LF}<BUFFER"
1564
+ #warn "BUFFER[#{list_type},#{sibling_trait}]>#{buffer.join LF}<BUFFER"
1544
1565
  #warn "BUFFER[#{list_type},#{sibling_trait}]>#{buffer.inspect}<BUFFER"
1545
1566
 
1546
1567
  buffer
@@ -1784,23 +1805,25 @@ class Parser
1784
1805
  # # => {'author' => 'Author Name', 'firstname' => 'Author', 'lastname' => 'Name', 'email' => 'author@example.org',
1785
1806
  # # 'revnumber' => '1.0', 'revdate' => '2012-12-21', 'revremark' => 'Coincide w/ end of world.'}
1786
1807
  def self.parse_header_metadata(reader, document = nil)
1808
+ doc_attrs = document && document.attributes
1787
1809
  # NOTE this will discard any comment lines, but not skip blank lines
1788
1810
  process_attribute_entries reader, document
1789
1811
 
1790
- metadata, implicit_author, implicit_authors = {}, nil, nil
1812
+ metadata, implicit_author, implicit_authorinitials = implicit_authors = {}, nil, nil
1791
1813
 
1792
1814
  if reader.has_more_lines? && !reader.next_line_empty?
1793
1815
  unless (author_metadata = process_authors reader.read_line).empty?
1794
1816
  if document
1795
1817
  # apply header subs and assign to document
1796
1818
  author_metadata.each do |key, val|
1797
- unless document.attributes.key? key
1798
- document.attributes[key] = ::String === val ? (document.apply_header_subs val) : val
1819
+ unless doc_attrs.key? key
1820
+ doc_attrs[key] = ::String === val ? (document.apply_header_subs val) : val
1799
1821
  end
1800
1822
  end
1801
1823
 
1802
- implicit_author = document.attributes['author']
1803
- implicit_authors = document.attributes['authors']
1824
+ implicit_author = doc_attrs['author']
1825
+ implicit_authorinitials = doc_attrs['authorinitials']
1826
+ implicit_authors = doc_attrs['authors']
1804
1827
  end
1805
1828
 
1806
1829
  metadata = author_metadata
@@ -1818,7 +1841,7 @@ class Parser
1818
1841
  unless (component = match[2].strip).empty?
1819
1842
  # version must begin with 'v' if date is absent
1820
1843
  if !match[1] && (component.start_with? 'v')
1821
- rev_metadata['revnumber'] = component[1..-1]
1844
+ rev_metadata['revnumber'] = component.slice 1, component.length
1822
1845
  else
1823
1846
  rev_metadata['revdate'] = component
1824
1847
  end
@@ -1834,8 +1857,8 @@ class Parser
1834
1857
  if document
1835
1858
  # apply header subs and assign to document
1836
1859
  rev_metadata.each do |key, val|
1837
- unless document.attributes.key? key
1838
- document.attributes[key] = document.apply_header_subs(val)
1860
+ unless doc_attrs.key? key
1861
+ doc_attrs[key] = document.apply_header_subs val
1839
1862
  end
1840
1863
  end
1841
1864
  end
@@ -1853,18 +1876,19 @@ class Parser
1853
1876
 
1854
1877
  # process author attribute entries that override (or stand in for) the implicit author line
1855
1878
  if document
1856
- if document.attributes.key?('author') && (author_line = document.attributes['author']) != implicit_author
1879
+ if doc_attrs.key?('author') && (author_line = doc_attrs['author']) != implicit_author
1857
1880
  # do not allow multiple, process as names only
1858
1881
  author_metadata = process_authors author_line, true, false
1859
- elsif document.attributes.key?('authors') && (author_line = document.attributes['authors']) != implicit_authors
1882
+ author_metadata.delete 'authorinitials' if doc_attrs['authorinitials'] != implicit_authorinitials
1883
+ elsif doc_attrs.key?('authors') && (author_line = doc_attrs['authors']) != implicit_authors
1860
1884
  # allow multiple, process as names only
1861
1885
  author_metadata = process_authors author_line, true
1862
1886
  else
1863
1887
  authors, author_idx, author_key, explicit, sparse = [], 1, 'author_1', false, false
1864
- while document.attributes.key? author_key
1888
+ while doc_attrs.key? author_key
1865
1889
  # only use indexed author attribute if value is different
1866
1890
  # leaves corner case if line matches with underscores converted to spaces; use double space to force
1867
- if (author_override = document.attributes[author_key]) == author_metadata[author_key]
1891
+ if (author_override = doc_attrs[author_key]) == author_metadata[author_key]
1868
1892
  authors << nil
1869
1893
  sparse = true
1870
1894
  else
@@ -1881,7 +1905,7 @@ class Parser
1881
1905
  author_metadata[%(firstname_#{name_idx = idx + 1})],
1882
1906
  author_metadata[%(middlename_#{name_idx})],
1883
1907
  author_metadata[%(lastname_#{name_idx})]
1884
- ].compact.map {|it| it.tr ' ', '_' } * ' '
1908
+ ].compact.map {|it| it.tr ' ', '_' }.join ' '
1885
1909
  end
1886
1910
  end if sparse
1887
1911
  # process as names only
@@ -1892,13 +1916,13 @@ class Parser
1892
1916
  end
1893
1917
 
1894
1918
  if author_metadata.empty?
1895
- metadata['authorcount'] ||= (document.attributes['authorcount'] = 0)
1919
+ metadata['authorcount'] ||= (doc_attrs['authorcount'] = 0)
1896
1920
  else
1897
- document.attributes.update author_metadata
1921
+ doc_attrs.update author_metadata
1898
1922
 
1899
1923
  # special case
1900
- if !document.attributes.key?('email') && document.attributes.key?('email_1')
1901
- document.attributes['email'] = document.attributes['email_1']
1924
+ if !doc_attrs.key?('email') && doc_attrs.key?('email_1')
1925
+ doc_attrs['email'] = doc_attrs['email_1']
1902
1926
  end
1903
1927
  end
1904
1928
  end
@@ -2045,7 +2069,7 @@ class Parser
2045
2069
  elsif (next_line.end_with? ']') && BlockAttributeListRx =~ next_line
2046
2070
  current_style = attributes[1]
2047
2071
  # extract id, role, and options from first positional attribute and remove, if present
2048
- if (document.parse_attributes $1, [], :sub_input => true, :into => attributes)[1]
2072
+ if (document.parse_attributes $1, [], :sub_input => true, :sub_result => true, :into => attributes)[1]
2049
2073
  attributes[1] = (parse_style_attribute attributes, reader) || current_style
2050
2074
  end
2051
2075
  return true
@@ -2093,11 +2117,10 @@ class Parser
2093
2117
  if (value = match[2]).nil_or_empty?
2094
2118
  value = ''
2095
2119
  elsif value.end_with? LINE_CONTINUATION, LINE_CONTINUATION_LEGACY
2096
- con, value = value.slice(-2, 2), value.slice(0, value.length - 2).rstrip
2097
- while reader.advance && !(next_line = reader.peek_line.lstrip).empty?
2098
- if (keep_open = next_line.end_with? con)
2099
- next_line = (next_line.slice 0, next_line.length - 2).rstrip
2100
- end
2120
+ con, value = value.slice(-2, 2), (value.slice 0, value.length - 2).rstrip
2121
+ while reader.advance && !(next_line = reader.peek_line || '').empty?
2122
+ next_line = next_line.lstrip
2123
+ next_line = (next_line.slice 0, next_line.length - 2).rstrip if (keep_open = next_line.end_with? con)
2101
2124
  value = %(#{value}#{(value.end_with? HARD_LINE_BREAK) ? LF : ' '}#{next_line})
2102
2125
  break unless keep_open
2103
2126
  end
@@ -2136,9 +2159,9 @@ class Parser
2136
2159
  if name == 'leveloffset'
2137
2160
  # support relative leveloffset values
2138
2161
  if value.start_with? '+'
2139
- value = ((doc.attr 'leveloffset', 0).to_i + (value[1..-1] || 0).to_i).to_s
2162
+ value = ((doc.attr 'leveloffset', 0).to_i + (value.slice 1, value.length).to_i).to_s
2140
2163
  elsif value.start_with? '-'
2141
- value = ((doc.attr 'leveloffset', 0).to_i - (value[1..-1] || 0).to_i).to_s
2164
+ value = ((doc.attr 'leveloffset', 0).to_i - (value.slice 1, value.length).to_i).to_s
2142
2165
  end
2143
2166
  end
2144
2167
  # QUESTION should we set value to locked value if set_attribute returns false?
@@ -2176,7 +2199,7 @@ class Parser
2176
2199
  if list_type == :ulist
2177
2200
  marker
2178
2201
  elsif list_type == :olist
2179
- resolve_ordered_list_marker(marker, ordinal, validate, reader)
2202
+ resolve_ordered_list_marker(marker, ordinal, validate, reader)[0]
2180
2203
  else # :colist
2181
2204
  '<1>'
2182
2205
  end
@@ -2199,14 +2222,19 @@ class Parser
2199
2222
  # Examples
2200
2223
  #
2201
2224
  # marker = 'B.'
2202
- # Parser.resolve_ordered_list_marker(marker, 1, true)
2203
- # # => 'A.'
2225
+ # Parser.resolve_ordered_list_marker(marker, 1, true, reader)
2226
+ # # => ['A.', :upperalpha]
2227
+ #
2228
+ # marker = '.'
2229
+ # Parser.resolve_ordered_list_marker(marker, 1, true, reader)
2230
+ # # => ['.']
2204
2231
  #
2205
- # Returns the String of the first marker in this number series
2232
+ # Returns a tuple that contains the String of the first marker in this number
2233
+ # series and the implicit list style, if applicable
2206
2234
  def self.resolve_ordered_list_marker(marker, ordinal = 0, validate = false, reader = nil)
2207
- return marker if marker.start_with? '.'
2208
- expected = actual = nil
2209
- case ORDERED_LIST_STYLES.find {|s| OrderedListMarkerRxMap[s].match? marker }
2235
+ return [marker] if marker.start_with? '.'
2236
+ # NOTE case statement is guaranteed to match one of the conditions
2237
+ case (style = ORDERED_LIST_STYLES.find {|s| OrderedListMarkerRxMap[s].match? marker })
2210
2238
  when :arabic
2211
2239
  if validate
2212
2240
  expected = ordinal + 1
@@ -2243,7 +2271,7 @@ class Parser
2243
2271
  logger.warn message_with_context %(list item index: expected #{expected}, got #{actual}), :source_location => reader.cursor
2244
2272
  end
2245
2273
 
2246
- marker
2274
+ [marker, style]
2247
2275
  end
2248
2276
 
2249
2277
  # Internal: Determine whether the this line is a sibling list item
@@ -2277,7 +2305,7 @@ class Parser
2277
2305
  # attributes - attributes captured from above this Block
2278
2306
  #
2279
2307
  # returns an instance of Asciidoctor::Table parsed from the provided reader
2280
- def self.next_table(table_reader, parent, attributes)
2308
+ def self.parse_table(table_reader, parent, attributes)
2281
2309
  table = Table.new(parent, attributes)
2282
2310
  if attributes.key? 'title'
2283
2311
  table.title = attributes.delete 'title'
@@ -2432,7 +2460,7 @@ class Parser
2432
2460
 
2433
2461
  specs = []
2434
2462
  # NOTE -1 argument ensures we don't drop empty records
2435
- records.split(',', -1).each {|record|
2463
+ ((records.include? ',') ? (records.split ',', -1) : (records.split ';', -1)).each do |record|
2436
2464
  if record.empty?
2437
2465
  specs << { 'width' => 1 }
2438
2466
  # TODO might want to use scan rather than this mega-regexp
@@ -2469,7 +2497,7 @@ class Parser
2469
2497
  specs << spec
2470
2498
  end
2471
2499
  end
2472
- }
2500
+ end
2473
2501
  specs
2474
2502
  end
2475
2503
 
@@ -2658,7 +2686,7 @@ class Parser
2658
2686
  # source.split "\n"
2659
2687
  # # => [" def names", " @names.split", " end"]
2660
2688
  #
2661
- # puts Parser.adjust_indentation!(source.split "\n") * "\n"
2689
+ # puts Parser.adjust_indentation!(source.split "\n").join "\n"
2662
2690
  # # => def names
2663
2691
  # # => @names.split
2664
2692
  # # => end
@@ -2723,12 +2751,12 @@ class Parser
2723
2751
  # NOTE gutter_width is > 0 if not nil
2724
2752
  if indent == 0
2725
2753
  if gutter_width
2726
- lines.map! {|line| line.empty? ? line : line[gutter_width..-1] }
2754
+ lines.map! {|line| line.empty? ? line : (line.slice gutter_width, line.length) }
2727
2755
  end
2728
2756
  else
2729
2757
  padding = ' ' * indent
2730
2758
  if gutter_width
2731
- lines.map! {|line| line.empty? ? line : padding + line[gutter_width..-1] }
2759
+ lines.map! {|line| line.empty? ? line : padding + (line.slice gutter_width, line.length) }
2732
2760
  else
2733
2761
  lines.map! {|line| line.empty? ? line : padding + line }
2734
2762
  end