asciidoctor 1.5.5 → 1.5.6

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

Potentially problematic release.


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

Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +216 -1
  3. data/CONTRIBUTING.adoc +2 -2
  4. data/Gemfile +20 -1
  5. data/LICENSE.adoc +1 -1
  6. data/README-fr.adoc +4 -3
  7. data/README-jp.adoc +11 -10
  8. data/README-zh_CN.adoc +4 -3
  9. data/README.adoc +17 -202
  10. data/Rakefile +41 -25
  11. data/asciidoctor.gemspec +9 -10
  12. data/data/locale/attributes.adoc +216 -34
  13. data/data/stylesheets/asciidoctor-default.css +23 -16
  14. data/features/step_definitions.rb +15 -19
  15. data/features/xref.feature +584 -20
  16. data/lib/asciidoctor.rb +292 -278
  17. data/lib/asciidoctor/abstract_block.rb +155 -94
  18. data/lib/asciidoctor/abstract_node.rb +108 -94
  19. data/lib/asciidoctor/attribute_list.rb +30 -22
  20. data/lib/asciidoctor/block.rb +7 -7
  21. data/lib/asciidoctor/cli/invoker.rb +47 -34
  22. data/lib/asciidoctor/cli/options.rb +22 -11
  23. data/lib/asciidoctor/converter.rb +3 -3
  24. data/lib/asciidoctor/converter/base.rb +2 -2
  25. data/lib/asciidoctor/converter/composite.rb +1 -1
  26. data/lib/asciidoctor/converter/docbook45.rb +2 -2
  27. data/lib/asciidoctor/converter/docbook5.rb +132 -87
  28. data/lib/asciidoctor/converter/factory.rb +0 -1
  29. data/lib/asciidoctor/converter/html5.rb +116 -98
  30. data/lib/asciidoctor/converter/manpage.rb +51 -52
  31. data/lib/asciidoctor/converter/template.rb +47 -36
  32. data/lib/asciidoctor/core_ext.rb +8 -2
  33. data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +4 -0
  34. data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +6 -0
  35. data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +5 -0
  36. data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +1 -1
  37. data/lib/asciidoctor/core_ext/1.8.7/string/{limit.rb → limit_bytesize.rb} +7 -6
  38. data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +6 -0
  39. data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +1 -1
  40. data/lib/asciidoctor/core_ext/nil_or_empty.rb +5 -5
  41. data/lib/asciidoctor/core_ext/regexp/is_match.rb +3 -0
  42. data/lib/asciidoctor/core_ext/string/{limit.rb → limit_bytesize.rb} +2 -2
  43. data/lib/asciidoctor/document.rb +216 -213
  44. data/lib/asciidoctor/extensions.rb +318 -185
  45. data/lib/asciidoctor/helpers.rb +35 -35
  46. data/lib/asciidoctor/inline.rb +32 -1
  47. data/lib/asciidoctor/list.rb +22 -6
  48. data/lib/asciidoctor/parser.rb +1008 -1038
  49. data/lib/asciidoctor/path_resolver.rb +46 -50
  50. data/lib/asciidoctor/reader.rb +275 -251
  51. data/lib/asciidoctor/section.rb +86 -58
  52. data/lib/asciidoctor/stylesheets.rb +6 -6
  53. data/lib/asciidoctor/substitutors.rb +567 -649
  54. data/lib/asciidoctor/table.rb +163 -108
  55. data/lib/asciidoctor/version.rb +1 -1
  56. data/man/asciidoctor.1 +18 -16
  57. data/man/asciidoctor.adoc +15 -13
  58. data/test/attributes_test.rb +138 -22
  59. data/test/blocks_test.rb +377 -97
  60. data/test/converter_test.rb +13 -0
  61. data/test/document_test.rb +244 -34
  62. data/test/extensions_test.rb +409 -42
  63. data/test/fixtures/asciidoc_index.txt +521 -0
  64. data/test/fixtures/basic-docinfo-footer.html +6 -0
  65. data/test/fixtures/basic-docinfo-footer.xml +8 -0
  66. data/test/fixtures/basic-docinfo.html +1 -0
  67. data/test/fixtures/basic-docinfo.xml +4 -0
  68. data/test/fixtures/basic.asciidoc +5 -0
  69. data/test/fixtures/chapter-a.adoc +3 -0
  70. data/test/fixtures/child-include.adoc +5 -0
  71. data/test/fixtures/circle.svg +9 -0
  72. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
  73. data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +6 -0
  74. data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +1 -0
  75. data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +3 -0
  76. data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +5 -0
  77. data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +6 -0
  78. data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +3 -0
  79. data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +5 -0
  80. data/test/fixtures/custom-docinfodir/basic-docinfo.html +1 -0
  81. data/test/fixtures/custom-docinfodir/docinfo.html +1 -0
  82. data/test/fixtures/docinfo-footer.html +1 -0
  83. data/test/fixtures/docinfo-footer.xml +9 -0
  84. data/test/fixtures/docinfo.html +1 -0
  85. data/test/fixtures/docinfo.xml +3 -0
  86. data/test/fixtures/dot.gif +0 -0
  87. data/test/fixtures/encoding.asciidoc +13 -0
  88. data/test/fixtures/grandchild-include.adoc +3 -0
  89. data/test/fixtures/hello-asciidoctor.pdf +69 -0
  90. data/test/fixtures/include-file.asciidoc +24 -0
  91. data/test/fixtures/include-file.ml +3 -0
  92. data/test/fixtures/include-file.xml +5 -0
  93. data/test/fixtures/master.adoc +5 -0
  94. data/test/fixtures/mismatched-end-tag.adoc +7 -0
  95. data/test/fixtures/parent-include-restricted.adoc +5 -0
  96. data/test/fixtures/parent-include.adoc +5 -0
  97. data/test/fixtures/sample.asciidoc +26 -0
  98. data/test/fixtures/stylesheets/custom.css +3 -0
  99. data/test/fixtures/subs-docinfo.html +2 -0
  100. data/test/fixtures/subs.adoc +7 -0
  101. data/test/fixtures/tagged-class-enclosed.rb +26 -0
  102. data/test/fixtures/tagged-class.rb +23 -0
  103. data/test/fixtures/tip.gif +0 -0
  104. data/test/invoker_test.rb +82 -4
  105. data/test/links_test.rb +312 -37
  106. data/test/lists_test.rb +204 -25
  107. data/test/manpage_test.rb +191 -4
  108. data/test/options_test.rb +18 -1
  109. data/test/paragraphs_test.rb +32 -7
  110. data/test/parser_test.rb +150 -30
  111. data/test/paths_test.rb +47 -13
  112. data/test/preamble_test.rb +1 -1
  113. data/test/reader_test.rb +366 -126
  114. data/test/sections_test.rb +203 -56
  115. data/test/substitutions_test.rb +339 -131
  116. data/test/tables_test.rb +315 -15
  117. data/test/test_helper.rb +400 -0
  118. data/test/text_test.rb +5 -5
  119. metadata +110 -22
@@ -7,10 +7,10 @@ RUBY_MIN_VERSION_2 = (RUBY_VERSION >= '2')
7
7
 
8
8
  require 'set'
9
9
 
10
- # NOTE RUBY_ENGINE == 'opal' conditional blocks are filtered by the Opal preprocessor
10
+ # NOTE RUBY_ENGINE == 'opal' conditional blocks like this are filtered by the Opal preprocessor
11
11
  if RUBY_ENGINE == 'opal'
12
- # NOTE asciidoctor/opal_ext is supplied by the Asciidoctor.js build
13
- require 'asciidoctor/opal_ext'
12
+ # this require is satisfied by the Asciidoctor.js build; it augments the Ruby environment for Asciidoctor.js
13
+ require 'asciidoctor/js'
14
14
  else
15
15
  autoload :Base64, 'base64'
16
16
  autoload :OpenURI, 'open-uri'
@@ -62,7 +62,7 @@ module Asciidoctor
62
62
 
63
63
  # A safe mode level that closely parallels safe mode in AsciiDoc. This value
64
64
  # prevents access to files which reside outside of the parent directory of
65
- # the source file and disables any macro other than the include::[] macro.
65
+ # the source file and disables any macro other than the include::[] directive.
66
66
  SAFE = 1;
67
67
 
68
68
  # A safe mode level that disallows the document from setting attributes
@@ -76,7 +76,7 @@ module Asciidoctor
76
76
  # A safe mode level that disallows the document from attempting to read
77
77
  # files from the file system and including the contents of them into the
78
78
  # document, in additional to all the security features of SafeMode::SERVER.
79
- # For instance, this level disallows use of the include::[] macro and the
79
+ # For instance, this level disallows use of the include::[] directive and the
80
80
  # embedding of binary content (data uri), stylesheets and JavaScripts
81
81
  # referenced by the document.(Asciidoctor and trusted extensions may still
82
82
  # be allowed to embed trusted content into the document).
@@ -93,13 +93,28 @@ module Asciidoctor
93
93
  # enforced)!
94
94
  #PARANOID = 100;
95
95
 
96
+ rec = {}
97
+ constants.each {|sym| rec[const_get sym] = sym.to_s.downcase }
98
+ @names_by_value = rec
99
+
100
+ def self.value_for_name name
101
+ const_get name.upcase
102
+ end
103
+
104
+ def self.name_for_value value
105
+ @names_by_value[value]
106
+ end
107
+
108
+ def self.names
109
+ @names_by_value.values
110
+ end
96
111
  end
97
112
 
98
113
  # Flags to control compliance with the behavior of AsciiDoc
99
114
  module Compliance
100
115
  @keys = ::Set.new
101
116
  class << self
102
- attr :keys
117
+ attr_reader :keys
103
118
  end
104
119
 
105
120
  # Defines a new compliance key and assigns an initial value.
@@ -117,8 +132,8 @@ module Asciidoctor
117
132
  # Compliance value: true
118
133
  define :block_terminates_paragraph, true
119
134
 
120
- # AsciiDoc does not treat paragraphs labeled with a verbatim style
121
- # (literal, listing, source, verse) as verbatim
135
+ # AsciiDoc does not parse paragraphs with a verbatim style
136
+ # (i.e., literal, listing, source, verse) as verbatim content.
122
137
  # This options allows this behavior to be modified
123
138
  # Compliance value: false
124
139
  define :strict_verbatim_paragraphs, true
@@ -172,7 +187,7 @@ module Asciidoctor
172
187
  ROOT_PATH = ::File.dirname ::File.dirname ::File.expand_path __FILE__
173
188
 
174
189
  # The absolute lib path of the Asciidoctor RubyGem
175
- LIB_PATH = ::File.join ROOT_PATH, 'lib'
190
+ #LIB_PATH = ::File.join ROOT_PATH, 'lib'
176
191
 
177
192
  # The absolute data path of the Asciidoctor RubyGem
178
193
  DATA_PATH = ::File.join ROOT_PATH, 'data'
@@ -195,19 +210,15 @@ module Asciidoctor
195
210
  FORCE_ENCODING = COERCE_ENCODING && ::Encoding.default_external != ::Encoding::UTF_8
196
211
 
197
212
  # Byte arrays for UTF-* Byte Order Marks
198
- # hex escape sequence used for Ruby 1.8 compatibility
199
- BOM_BYTES_UTF_8 = "\xef\xbb\xbf".bytes.to_a
200
- BOM_BYTES_UTF_16LE = "\xff\xfe".bytes.to_a
201
- BOM_BYTES_UTF_16BE = "\xfe\xff".bytes.to_a
213
+ BOM_BYTES_UTF_8 = [0xef, 0xbb, 0xbf]
214
+ BOM_BYTES_UTF_16LE = [0xff, 0xfe]
215
+ BOM_BYTES_UTF_16BE = [0xfe, 0xff]
202
216
 
203
217
  # Flag to indicate that line length should be calculated using a unicode mode hint
204
218
  FORCE_UNICODE_LINE_LENGTH = !::RUBY_MIN_VERSION_1_9
205
219
 
206
- # Flag to indicate whether gsub can use a Hash to map matches to replacements
207
- SUPPORTS_GSUB_RESULT_HASH = ::RUBY_MIN_VERSION_1_9 && !::RUBY_ENGINE_OPAL
208
-
209
220
  # The endline character used for output; stored in constant table as an optimization
210
- EOL = "\n"
221
+ LF = EOL = "\n"
211
222
 
212
223
  # The null character to use for splitting attribute values
213
224
  NULL = "\0"
@@ -243,6 +254,7 @@ module Asciidoctor
243
254
  'docbook' => '.xml',
244
255
  'pdf' => '.pdf',
245
256
  'epub' => '.epub',
257
+ 'manpage' => '.man',
246
258
  'asciidoc' => '.adoc'
247
259
  }
248
260
 
@@ -256,7 +268,7 @@ module Asciidoctor
256
268
  '.txt' => true
257
269
  }
258
270
 
259
- SECTION_LEVELS = {
271
+ SETEXT_SECTION_LEVELS = {
260
272
  '=' => 0,
261
273
  '-' => 1,
262
274
  '~' => 2,
@@ -266,6 +278,10 @@ module Asciidoctor
266
278
 
267
279
  ADMONITION_STYLES = ['NOTE', 'TIP', 'IMPORTANT', 'WARNING', 'CAUTION'].to_set
268
280
 
281
+ ADMONITION_STYLE_LEADERS = ['N', 'T', 'I', 'W', 'C'].to_set
282
+
283
+ CALLOUT_LIST_LEADERS = ['<', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'].to_set
284
+
269
285
  PARAGRAPH_STYLES = ['comment', 'example', 'literal', 'listing', 'normal', 'pass', 'quote', 'sidebar', 'source', 'verse', 'abstract', 'partintro'].to_set
270
286
 
271
287
  VERBATIM_STYLES = ['literal', 'listing', 'source', 'verse'].to_set
@@ -287,16 +303,21 @@ module Asciidoctor
287
303
  '```' => [:fenced_code, ::Set.new]
288
304
  }
289
305
 
290
- DELIMITED_BLOCK_LEADERS = DELIMITED_BLOCKS.keys.map {|key| key[0..1] }.to_set
306
+ DELIMITED_BLOCK_LEADERS = DELIMITED_BLOCKS.keys.map {|key| key.slice 0, 2 }.to_set
291
307
 
292
- LAYOUT_BREAK_LINES = {
308
+ LAYOUT_BREAK_CHARS = {
293
309
  '\'' => :thematic_break,
310
+ '<' => :page_break
311
+ }
312
+
313
+ MARKDOWN_THEMATIC_BREAK_CHARS = {
294
314
  '-' => :thematic_break,
295
315
  '*' => :thematic_break,
296
- '_' => :thematic_break,
297
- '<' => :page_break
316
+ '_' => :thematic_break
298
317
  }
299
318
 
319
+ HYBRID_LAYOUT_BREAK_CHARS = LAYOUT_BREAK_CHARS.merge MARKDOWN_THEMATIC_BREAK_CHARS
320
+
300
321
  #LIST_CONTEXTS = [:ulist, :olist, :dlist, :colist]
301
322
 
302
323
  NESTABLE_LIST_CONTEXTS = [:ulist, :olist, :dlist]
@@ -305,37 +326,48 @@ module Asciidoctor
305
326
  ORDERED_LIST_STYLES = [:arabic, :loweralpha, :lowerroman, :upperalpha, :upperroman] #, :lowergreek]
306
327
 
307
328
  ORDERED_LIST_KEYWORDS = {
329
+ #'arabic' => '1',
330
+ #'decimal' => '1',
308
331
  'loweralpha' => 'a',
309
332
  'lowerroman' => 'i',
333
+ #'lowergreek' => 'a',
310
334
  'upperalpha' => 'A',
311
335
  'upperroman' => 'I'
312
- #'lowergreek' => 'a'
313
- #'arabic' => '1'
314
- #'decimal' => '1'
315
336
  }
316
337
 
317
338
  LIST_CONTINUATION = '+'
318
339
 
319
- # NOTE AsciiDoc Python recognizes both a preceding TAB and a space
320
- LINE_BREAK = ' +'
340
+ # NOTE AsciiDoc Python allows + to be preceded by TAB; Asciidoctor does not
341
+ HARD_LINE_BREAK = ' +'
321
342
 
322
343
  LINE_CONTINUATION = ' \\'
323
344
 
324
345
  LINE_CONTINUATION_LEGACY = ' +'
325
346
 
326
347
  BLOCK_MATH_DELIMITERS = {
327
- :asciimath => ['\\$', '\\$'],
328
- :latexmath => ['\\[', '\\]'],
348
+ :asciimath => ['\$', '\$'],
349
+ :latexmath => ['\[', '\]'],
329
350
  }
330
351
 
331
352
  INLINE_MATH_DELIMITERS = {
332
- :asciimath => ['\\$', '\\$'],
333
- :latexmath => ['\\(', '\\)'],
353
+ :asciimath => ['\$', '\$'],
354
+ :latexmath => ['\(', '\)'],
334
355
  }
335
356
 
336
357
  # attributes which be changed within the content of the document (but not
337
358
  # header) because it has semantic meaning; ex. sectnums
338
- FLEXIBLE_ATTRIBUTES = %w(sectnums)
359
+ FLEXIBLE_ATTRIBUTES = ['sectnums']
360
+
361
+ # map of file extension to comment affixes for languages that only support circumfix comments
362
+ CIRCUMFIX_COMMENTS = {
363
+ ['/*', '*/'] => ['.css'],
364
+ ['(*', '*)'] => ['.ml', '.mli', '.nb'],
365
+ ['<!--', '-->'] => ['.html', '.xhtml', '.xml', '.xsl'],
366
+ ['<%--', '--%>'] => ['.asp', '.jsp']
367
+ }.inject({}) {|accum, (affixes, exts)|
368
+ exts.each {|ext| accum[ext] = { :prefix => affixes[0], :suffix => affixes[-1] } }
369
+ accum
370
+ }
339
371
 
340
372
  # A collection of regular expressions used by the parser.
341
373
  #
@@ -343,8 +375,8 @@ module Asciidoctor
343
375
  # contents between square brackets, ignoring escaped closing brackets
344
376
  # (closing brackets prefixed with a backslash '\' character)
345
377
  #
346
- # Pattern: (?:\[((?:\\\]|[^\]])*?)\])
347
- # Matches: [enclosed text here] or [enclosed [text\] here]
378
+ # Pattern: \[(|.*?[^\\])\]
379
+ # Matches: [enclosed text] and [enclosed [text\]], not [enclosed text \\] or [\\] (as these require a trailing space)
348
380
  #
349
381
  #(pseudo)module Rx
350
382
 
@@ -353,26 +385,13 @@ module Asciidoctor
353
385
 
354
386
  # NOTE \w matches only the ASCII word characters, whereas [[:word:]] or \p{Word} matches any character in the Unicode word category.
355
387
 
356
- # character classes for the Regexp engine(s) in JavaScript
357
- if RUBY_ENGINE == 'opal'
358
- CC_ALPHA = 'a-zA-Z'
359
- CG_ALPHA = '[a-zA-Z]'
360
- CC_ALNUM = 'a-zA-Z0-9'
361
- CG_ALNUM = '[a-zA-Z0-9]'
362
- CG_BLANK = '[ \\t]'
363
- CC_EOL = '(?=\\n|$)'
364
- CG_GRAPH = '[\\x21-\\x7E]' # non-blank character
365
- CC_ALL = '[\s\S]' # any character, including newlines (alternatively, [^])
366
- CC_WORD = 'a-zA-Z0-9_'
367
- CG_WORD = '[a-zA-Z0-9_]'
368
388
  # character classes for the Regexp engine in Ruby >= 2 (Ruby 1.9 supports \p{} but has problems w/ encoding)
369
- elsif ::RUBY_MIN_VERSION_2
389
+ if ::RUBY_MIN_VERSION_2
370
390
  CC_ALPHA = CG_ALPHA = '\p{Alpha}'
371
391
  CC_ALNUM = CG_ALNUM = '\p{Alnum}'
372
392
  CC_ALL = '.'
373
393
  CG_BLANK = '\p{Blank}'
374
394
  CC_EOL = '$'
375
- CG_GRAPH = '\p{Graph}'
376
395
  CC_WORD = CG_WORD = '\p{Word}'
377
396
  # character classes for the Regexp engine in Ruby < 2
378
397
  else
@@ -381,18 +400,18 @@ module Asciidoctor
381
400
  CC_ALL = '.'
382
401
  CC_ALNUM = '[:alnum:]'
383
402
  CG_ALNUM = '[[:alnum:]]'
384
- CG_BLANK = '[[:blank:]]'
385
403
  CC_EOL = '$'
386
- CG_GRAPH = '[[:graph:]]' # non-blank character
387
404
  if ::RUBY_MIN_VERSION_1_9
405
+ CG_BLANK = '[[:blank:]]'
388
406
  CC_WORD = '[:word:]'
389
407
  CG_WORD = '[[:word:]]'
390
408
  else
391
409
  # NOTE Ruby 1.8 cannot match word characters beyond the ASCII range; if you need this feature, upgrade!
410
+ CG_BLANK = '[ \t]'
392
411
  CC_WORD = '[:alnum:]_'
393
412
  CG_WORD = '[[:alnum:]_]'
394
413
  end
395
- end
414
+ end unless RUBY_ENGINE == 'opal'
396
415
 
397
416
  ## Document header
398
417
 
@@ -415,15 +434,16 @@ module Asciidoctor
415
434
  # v1.0, 2013-01-01: Ring in the new year release
416
435
  # 1.0, Jan 01, 2013
417
436
  #
418
- RevisionInfoLineRx = /^(?:\D*(.*?),)?(?:\s*(?!:)(.*?))(?:\s*(?!^):\s*(.*))?$/
437
+ RevisionInfoLineRx = /^(?:\D*(.*?),)?(?: *(?!:)(.*?))(?: *(?!^): *(.*))?$/
419
438
 
420
439
  # Matches the title and volnum in the manpage doctype.
421
440
  #
422
441
  # Examples
423
442
  #
443
+ # = asciidoctor(1)
424
444
  # = asciidoctor ( 1 )
425
445
  #
426
- ManpageTitleVolnumRx = /^(.*)\((.*)\)$/
446
+ ManpageTitleVolnumRx = /^(.+?) *\( *(.+?) *\)$/
427
447
 
428
448
  # Matches the name and purpose in the manpage doctype.
429
449
  #
@@ -431,7 +451,7 @@ module Asciidoctor
431
451
  #
432
452
  # asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats
433
453
  #
434
- ManpageNamePurposeRx = /^(.*?)#{CG_BLANK}+-#{CG_BLANK}+(.*)$/
454
+ ManpageNamePurposeRx = /^(.+?) +- +(.+)$/
435
455
 
436
456
  ## Preprocessor directives
437
457
 
@@ -447,7 +467,7 @@ module Asciidoctor
447
467
  # endif::basebackend-html[]
448
468
  # endif::[]
449
469
  #
450
- ConditionalDirectiveRx = /^\\?(ifdef|ifndef|ifeval|endif)::(\S*?(?:([,\+])\S+?)?)\[(.+)?\]$/
470
+ ConditionalDirectiveRx = /^(\\)?(ifdef|ifndef|ifeval|endif)::(\S*?(?:([,+])\S*?)?)\[(.+)?\]$/
451
471
 
452
472
  # Matches a restricted (read as safe) eval expression.
453
473
  #
@@ -455,7 +475,7 @@ module Asciidoctor
455
475
  #
456
476
  # "{asciidoctor-version}" >= "0.1.0"
457
477
  #
458
- EvalExpressionRx = /^(\S.*?)#{CG_BLANK}*(==|!=|<=|>=|<|>)#{CG_BLANK}*(\S.*)$/
478
+ EvalExpressionRx = /^(.+?) *([=!><]=|[><]) *(.+)$/
459
479
 
460
480
  # Matches an include preprocessor directive.
461
481
  #
@@ -464,7 +484,7 @@ module Asciidoctor
464
484
  # include::chapter1.ad[]
465
485
  # include::example.txt[lines=1;2;5..10]
466
486
  #
467
- IncludeDirectiveRx = /^\\?include::([^\[]+)\[(.*?)\]$/
487
+ IncludeDirectiveRx = /^(\\)?include::([^\[][^\[]*)\[(.*)\]$/
468
488
 
469
489
  # Matches a trailing tag directive in an include file.
470
490
  #
@@ -477,7 +497,7 @@ module Asciidoctor
477
497
  # log(e);
478
498
  # }
479
499
  # // end::try-catch[]
480
- TagDirectiveRx = /\b(?:tag|end)::\S+\[\]$/
500
+ TagDirectiveRx = /\b(?:tag|(end))::(\S+)\[\]$/
481
501
 
482
502
  ## Attribute entries and references
483
503
 
@@ -489,34 +509,41 @@ module Asciidoctor
489
509
  # :First Name: Dan
490
510
  # :sectnums!:
491
511
  # :!toc:
492
- # :long-entry: Attribute value lines ending in ' +'
493
- # are joined together as a single value,
494
- # collapsing the line breaks and indentation to
512
+ # :long-entry: Attribute value lines ending in ' \' \
513
+ # are joined together as a single value, \
514
+ # collapsing the line breaks and indentation to \
495
515
  # a single space.
496
516
  #
497
- AttributeEntryRx = /^:(!?\w.*?):(?:#{CG_BLANK}+(.*))?$/
517
+ AttributeEntryRx = /^:(!?\w.*?):(?:[ \t]+(.*))?$/
498
518
 
499
519
  # Matches invalid characters in an attribute name.
500
520
  InvalidAttributeNameCharsRx = /[^\w\-]/
501
521
 
502
- # Matches the pass inline macro allowed in value of attribute assignment.
522
+ # Matches a pass inline macro that surrounds the value of an attribute
523
+ # entry once it has been parsed.
503
524
  #
504
525
  # Examples
505
526
  #
506
527
  # pass:[text]
528
+ # pass:a[{a} {b} {c}]
507
529
  #
508
- AttributeEntryPassMacroRx = /^pass:([a-z,]*)\[(.*)\]$/
530
+ if RUBY_ENGINE == 'opal'
531
+ # In JavaScript, ^ and $ match the boundaries of the string when the m flag is not set
532
+ AttributeEntryPassMacroRx = /^pass:([a-z]+(?:,[a-z]+)*)?\[([\S\s]*)\]$/
533
+ else
534
+ AttributeEntryPassMacroRx = /\Apass:([a-z]+(?:,[a-z]+)*)?\[(.*)\]\Z/m
535
+ end
509
536
 
510
537
  # Matches an inline attribute reference.
511
538
  #
512
539
  # Examples
513
540
  #
514
- # {foo}
515
- # {counter:pcount:1}
541
+ # {foobar} or {app_name} or {product-version}
542
+ # {counter:sequence-name:1}
516
543
  # {set:foo:bar}
517
544
  # {set:name!}
518
545
  #
519
- AttributeReferenceRx = /(\\)?\{((set|counter2?):.+?|\w+(?:[\-]\w+)*)(\\)?\}/
546
+ AttributeReferenceRx = /(\\)?\{(\w+[-\w]*|(set|counter2?):.+?)(\\)?\}/
520
547
 
521
548
  ## Paragraphs and delimited blocks
522
549
 
@@ -527,7 +554,7 @@ module Asciidoctor
527
554
  # [[idname]]
528
555
  # [[idname,Reference Text]]
529
556
  #
530
- BlockAnchorRx = /^\[\[(?:|([#{CC_ALPHA}:_][#{CC_WORD}:.-]*)(?:,#{CG_BLANK}*(\S.*))?)\]\]$/
557
+ BlockAnchorRx = /^\[\[(?:|([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)(?:, *(.+))?)\]\]$/
531
558
 
532
559
  # Matches an attribute list above a block element.
533
560
  #
@@ -542,12 +569,12 @@ module Asciidoctor
542
569
  # # as attribute reference
543
570
  # [{lead}]
544
571
  #
545
- BlockAttributeListRx = /^\[(|#{CG_BLANK}*[#{CC_WORD}\{,.#"'%].*)\]$/
572
+ BlockAttributeListRx = /^\[(|[#{CC_WORD}.#%{,"'].*)\]$/
546
573
 
547
574
  # A combined pattern that matches either a block anchor or a block attribute list.
548
575
  #
549
576
  # TODO this one gets hit a lot, should be optimized as much as possible
550
- BlockAttributeLineRx = /^\[(|#{CG_BLANK}*[#{CC_WORD}\{,.#"'%].*|\[(?:|[#{CC_ALPHA}:_][#{CC_WORD}:.-]*(?:,#{CG_BLANK}*\S.*)?)\])\]$/
577
+ BlockAttributeLineRx = /^\[(?:|[#{CC_WORD}.#%{,"'].*|\[(?:|[#{CC_ALPHA}_:][#{CC_WORD}:.-]*(?:, *.+)?)\])\]$/
551
578
 
552
579
  # Matches a title above a block.
553
580
  #
@@ -555,7 +582,7 @@ module Asciidoctor
555
582
  #
556
583
  # .Title goes here
557
584
  #
558
- BlockTitleRx = /^\.([^\s.].*)$/
585
+ BlockTitleRx = /^\.([^ \t.].*)$/
559
586
 
560
587
  # Matches an admonition label at the start of a paragraph.
561
588
  #
@@ -564,7 +591,7 @@ module Asciidoctor
564
591
  # NOTE: Just a little note.
565
592
  # TIP: Don't forget!
566
593
  #
567
- AdmonitionParagraphRx = /^(#{ADMONITION_STYLES.to_a * '|'}):#{CG_BLANK}/
594
+ AdmonitionParagraphRx = /^(#{ADMONITION_STYLES.to_a * '|'}):[ \t]+/
568
595
 
569
596
  # Matches a literal paragraph, which is a line of text preceded by at least one space.
570
597
  #
@@ -572,7 +599,7 @@ module Asciidoctor
572
599
  #
573
600
  # <SPACE>Foo
574
601
  # <TAB>Foo
575
- LiteralParagraphRx = /^(#{CG_BLANK}+.*)$/
602
+ LiteralParagraphRx = /^([ \t]+.*)$/
576
603
 
577
604
  # Matches a comment block.
578
605
  #
@@ -582,7 +609,7 @@ module Asciidoctor
582
609
  # This is a block comment.
583
610
  # It can span one or more lines.
584
611
  # ////
585
- CommentBlockRx = %r{^/{4,}$}
612
+ CommentBlockRx = %r(^/{4,}$)
586
613
 
587
614
  # Matches a comment line.
588
615
  #
@@ -590,7 +617,7 @@ module Asciidoctor
590
617
  #
591
618
  # // an then whatever
592
619
  #
593
- CommentLineRx = %r{^//(?:[^/]|$)}
620
+ CommentLineRx = %r(^//(?=[^/]|$))
594
621
 
595
622
  ## Section titles
596
623
 
@@ -607,20 +634,12 @@ module Asciidoctor
607
634
  # match[1] is the delimiter, whose length determines the level
608
635
  # match[2] is the title itself
609
636
  # match[3] is an inline anchor, which becomes the section id
610
- AtxSectionRx = /^((?:=|#){1,6})#{CG_BLANK}+(\S.*?)(?:#{CG_BLANK}+\1)?$/
637
+ AtxSectionRx = /^(=={0,5}|#\#{0,5})[ \t]+(.+?)(?:[ \t]+\1)?$/
611
638
 
612
639
  # Matches the restricted section name for a two-line (Setext-style) section title.
613
640
  # The name cannot begin with a dot and has at least one alphanumeric character.
614
641
  SetextSectionTitleRx = /^((?=.*#{CG_WORD}+.*)[^.].*?)$/
615
642
 
616
- # Matches the underline in a two-line (Setext-style) section title.
617
- #
618
- # Examples
619
- #
620
- # ====== || ------ || ~~~~~~ || ^^^^^^ || ++++++
621
- #
622
- SetextSectionLineRx = /^(?:=|-|~|\^|\+)+$/
623
-
624
643
  # Matches an anchor (i.e., id + optional reference text) inside a section title.
625
644
  #
626
645
  # Examples
@@ -628,10 +647,12 @@ module Asciidoctor
628
647
  # Section Title [[idname]]
629
648
  # Section Title [[idname,Reference Text]]
630
649
  #
631
- InlineSectionAnchorRx = /^(.*?)#{CG_BLANK}+(\\)?\[\[([#{CC_ALPHA}:_][#{CC_WORD}:.-]*)(?:,#{CG_BLANK}*(\S.*?))?\]\]$/
650
+ InlineSectionAnchorRx = / (\\)?\[\[([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)(?:, *(.+))?\]\]$/
632
651
 
633
652
  # Matches invalid characters in a section id.
634
- InvalidSectionIdCharsRx = /&(?:[a-zA-Z]{2,}|#\d{2,6}|#x[a-fA-F0-9]{2,5});|[^#{CC_WORD}]+?/
653
+ #
654
+ # NOTE uppercase chars are not included since the expression is used on a lowercased string
655
+ InvalidSectionIdCharsRx = /&(?:[a-z][a-z]+\d{0,2}|#\d\d\d{0,4}|#x[\da-f][\da-f][\da-f]{0,3});|[^#{CC_WORD}]+?/
635
656
 
636
657
  # Matches the block style used to designate a section title as a floating title.
637
658
  #
@@ -647,7 +668,7 @@ module Asciidoctor
647
668
  # Detects the start of any list item.
648
669
  #
649
670
  # NOTE we only have to check as far as the blank character because we know it means non-whitespace follows.
650
- AnyListRx = /^(?:#{CG_BLANK}*(?:-|([*.\u2022])\1{0,4}|\d+\.|[a-zA-Z]\.|[IVXivx]+\))#{CG_BLANK}|#{CG_BLANK}*.*?(?::{2,4}|;;)(?:$|#{CG_BLANK})|<?\d+>#{CG_BLANK})/
671
+ AnyListRx = /^(?:[ \t]*(?:-|\*\*{0,4}|\.\.{0,4}|\u2022\u2022{0,4}|\d+\.|[a-zA-Z]\.|[IVXivx]+\))[ \t]|[ \t]*.*?(?::{2,4}|;;)(?:$|[ \t])|<?\d+>[ \t])/
651
672
 
652
673
  # Matches an unordered list item (one level for hyphens, up to 5 levels for asterisks).
653
674
  #
@@ -657,8 +678,7 @@ module Asciidoctor
657
678
  # - Foo
658
679
  #
659
680
  # NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
660
- # NOTE I want to use (-|([*\u2022])\2{0,4}) but breaks the parser since it relies on fixed match positions
661
- UnorderedListRx = /^#{CG_BLANK}*(-|\*{1,5}|\u2022{1,5})#{CG_BLANK}+(.*)$/
681
+ UnorderedListRx = /^[ \t]*(-|\*\*{0,4}|\u2022\u2022{0,4})[ \t]+(.*)$/
662
682
 
663
683
  # Matches an ordered list item (explicit numbering or up to 5 consecutive dots).
664
684
  #
@@ -674,11 +694,11 @@ module Asciidoctor
674
694
  #
675
695
  # NOTE leading space match is not always necessary, but is used for list reader
676
696
  # NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
677
- OrderedListRx = /^#{CG_BLANK}*(\.{1,5}|\d+\.|[a-zA-Z]\.|[IVXivx]+\))#{CG_BLANK}+(.*)$/
697
+ OrderedListRx = /^[ \t]*(\.\.{0,4}|\d+\.|[a-zA-Z]\.|[IVXivx]+\))[ \t]+(.*)$/
678
698
 
679
699
  # Matches the ordinals for each type of ordered list.
680
700
  OrderedListMarkerRxMap = {
681
- :arabic => /\d+[.>]/,
701
+ :arabic => /\d+\./,
682
702
  :loweralpha => /[a-z]\./,
683
703
  :lowerroman => /[ivx]+\)/,
684
704
  :upperalpha => /[A-Z]\./,
@@ -711,15 +731,15 @@ module Asciidoctor
711
731
  # NOTE negative match for comment line is intentional since that isn't handled when looking for next list item
712
732
  # TODO check for line comment when scanning lines instead of in regex
713
733
  #
714
- DescriptionListRx = /^(?!\/\/)#{CG_BLANK}*(.*?)(:{2,4}|;;)(?:#{CG_BLANK}+(.*))?$/
734
+ DescriptionListRx = %r(^(?!//)[ \t]*(.*?)(:{2,4}|;;)(?:[ \t]+(.*))?$)
715
735
 
716
736
  # Matches a sibling description list item (which does not include the type in the key).
717
737
  DescriptionListSiblingRx = {
718
738
  # (?:.*?[^:])? - a non-capturing group which grabs longest sequence of characters that doesn't end w/ colon
719
- '::' => /^(?!\/\/)#{CG_BLANK}*((?:.*[^:])?)(::)(?:#{CG_BLANK}+(.*))?$/,
720
- ':::' => /^(?!\/\/)#{CG_BLANK}*((?:.*[^:])?)(:::)(?:#{CG_BLANK}+(.*))?$/,
721
- '::::' => /^(?!\/\/)#{CG_BLANK}*((?:.*[^:])?)(::::)(?:#{CG_BLANK}+(.*))?$/,
722
- ';;' => /^(?!\/\/)#{CG_BLANK}*(.*)(;;)(?:#{CG_BLANK}+(.*))?$/
739
+ '::' => %r(^(?!//)[ \t]*((?:.*[^:])?)(::)(?:[ \t]+(.*))?$),
740
+ ':::' => %r(^(?!//)[ \t]*((?:.*[^:])?)(:::)(?:[ \t]+(.*))?$),
741
+ '::::' => %r(^(?!//)[ \t]*((?:.*[^:])?)(::::)(?:[ \t]+(.*))?$),
742
+ ';;' => %r(^(?!//)[ \t]*(.*)(;;)(?:[ \t]+(.*))?$)
723
743
  }
724
744
 
725
745
  # Matches a callout list item.
@@ -729,7 +749,10 @@ module Asciidoctor
729
749
  # <1> Foo
730
750
  #
731
751
  # NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
732
- CalloutListRx = /^<?(\d+)>#{CG_BLANK}+(.*)$/
752
+ CalloutListRx = /^<?(\d+)>[ \t]+(.*)$/
753
+
754
+ # Detects a potential callout list item.
755
+ CalloutListSniffRx = /^<?\d+>/
733
756
 
734
757
  # Matches a callout reference inside literal text.
735
758
  #
@@ -739,12 +762,12 @@ module Asciidoctor
739
762
  # <!--1--> (for XML-based languages)
740
763
  #
741
764
  # NOTE extract regexps are applied line-by-line, so we can use $ as end-of-line char
742
- CalloutExtractRx = /(?:(?:\/\/|#|--|;;) ?)?(\\)?<!?(--|)(\d+)\2>(?=(?: ?\\?<!?\2\d+\2>)*$)/
765
+ CalloutExtractRx = %r((?:(?://|#|--|;;) ?)?(\\)?<!?(|--)(\d+)\2>(?=(?: ?\\?<!?\2\d+\2>)*$))
743
766
  CalloutExtractRxt = '(\\\\)?<()(\\d+)>(?=(?: ?\\\\?<\\d+>)*$)'
744
767
  # NOTE special characters have not been replaced when scanning
745
- CalloutQuickScanRx = /\\?<!?(--|)(\d+)\1>(?=(?: ?\\?<!?\1\d+\1>)*#{CC_EOL})/
768
+ CalloutScanRx = /\\?<!?(|--)(\d+)\1>(?=(?: ?\\?<!?\1\d+\1>)*#{CC_EOL})/
746
769
  # NOTE special characters have already been replaced when converting to an SGML format
747
- CalloutSourceRx = /(?:(?:\/\/|#|--|;;) ?)?(\\)?&lt;!?(--|)(\d+)\2&gt;(?=(?: ?\\?&lt;!?\2\d+\2&gt;)*#{CC_EOL})/
770
+ CalloutSourceRx = %r((?:(?://|#|--|;;) ?)?(\\)?&lt;!?(|--)(\d+)\2&gt;(?=(?: ?\\?&lt;!?\2\d+\2&gt;)*#{CC_EOL}))
748
771
  CalloutSourceRxt = "(\\\\)?&lt;()(\\d+)&gt;(?=(?: ?\\\\?&lt;\\d+&gt;)*#{CC_EOL})"
749
772
 
750
773
  # A Hash of regexps for lists used for dynamic access.
@@ -772,12 +795,12 @@ module Asciidoctor
772
795
  # 2.3+<.>m
773
796
  #
774
797
  # FIXME use step-wise scan (or treetop) rather than this mega-regexp
775
- CellSpecStartRx = /^#{CG_BLANK}*(?:(\d+(?:\.\d*)?|(?:\d*\.)?\d+)([*+]))?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?([a-z])?$/
776
- CellSpecEndRx = /#{CG_BLANK}+(?:(\d+(?:\.\d*)?|(?:\d*\.)?\d+)([*+]))?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?([a-z])?$/
798
+ CellSpecStartRx = /^[ \t]*(?:(\d+(?:\.\d*)?|(?:\d*\.)?\d+)([*+]))?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?([a-z])?$/
799
+ CellSpecEndRx = /[ \t]+(?:(\d+(?:\.\d*)?|(?:\d*\.)?\d+)([*+]))?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?([a-z])?$/
777
800
 
778
801
  # Block macros
779
802
 
780
- # Matches the general block macro pattern.
803
+ # Matches the custom block macro pattern.
781
804
  #
782
805
  # Examples
783
806
  #
@@ -785,7 +808,7 @@ module Asciidoctor
785
808
  #
786
809
  #--
787
810
  # NOTE we've relaxed the match for target to accomodate the short format (e.g., name::[attrlist])
788
- GenericBlockMacroRx = /^(#{CG_WORD}+)::(\S*?)\[((?:\\\]|[^\]])*?)\]$/
811
+ CustomBlockMacroRx = /^(#{CG_WORD}+)::(|\S|\S.*?\S)\[(.*)\]$/
789
812
 
790
813
  # Matches an image, video or audio block macro.
791
814
  #
@@ -794,7 +817,7 @@ module Asciidoctor
794
817
  # image::filename.png[Caption]
795
818
  # video::http://youtube.com/12345[Cats vs Dogs]
796
819
  #
797
- MediaBlockMacroRx = /^(image|video|audio)::(\S+?)\[((?:\\\]|[^\]])*?)\]$/
820
+ BlockMediaMacroRx = /^(image|video|audio)::(\S|\S.*?\S)\[(.*)\]$/
798
821
 
799
822
  # Matches the TOC block macro.
800
823
  #
@@ -803,7 +826,7 @@ module Asciidoctor
803
826
  # toc::[]
804
827
  # toc::[levels=2]
805
828
  #
806
- TocBlockMacroRx = /^toc::\[(.*?)\]$/
829
+ BlockTocMacroRx = /^toc::\[(.*)\]$/
807
830
 
808
831
  ## Inline macros
809
832
 
@@ -816,21 +839,24 @@ module Asciidoctor
816
839
  # anchor:idname[]
817
840
  # anchor:idname[Reference Text]
818
841
  #
819
- InlineAnchorRx = /\\?(?:\[\[([#{CC_ALPHA}:_][#{CC_WORD}:.-]*)(?:,#{CG_BLANK}*(\S.*?))?\]\]|anchor:(\S+)\[(.*?[^\\])?\])/
842
+ InlineAnchorRx = /(\\)?(?:\[\[([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)(?:, *(.+?))?\]\]|anchor:([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)\[(?:\]|(.*?[^\\])\]))/
843
+
844
+ # Scans for a non-escaped anchor (i.e., id + optional reference text) in the flow of text.
845
+ InlineAnchorScanRx = /(?:^|[^\\\[])\[\[([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)(?:, *(.+?))?\]\]|(?:^|[^\\])anchor:([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)\[(?:\]|(.*?[^\\])\])/
820
846
 
821
- # Matches a bibliography anchor anywhere inline.
847
+ # Matches a bibliography anchor at the start of the list item text (in a bibliography list).
822
848
  #
823
849
  # Examples
824
850
  #
825
- # [[[Foo]]]
851
+ # [[[Fowler_1997]]] Fowler M. ...
826
852
  #
827
- InlineBiblioAnchorRx = /\\?\[\[\[([#{CC_WORD}:][#{CC_WORD}:.-]*?)\]\]\]/
853
+ InlineBiblioAnchorRx = /^\[\[\[([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)(?:, *(.+?))?\]\]\]/
828
854
 
829
855
  # Matches an inline e-mail address.
830
856
  #
831
857
  # doc.writer@example.com
832
858
  #
833
- EmailInlineMacroRx = /([\\>:\/])?#{CG_WORD}[#{CC_WORD}.%+-]*@#{CG_ALNUM}[#{CC_ALNUM}.-]*\.#{CG_ALPHA}{2,4}\b/
859
+ EmailInlineRx = %r(([\\>:/])?#{CG_WORD}[#{CC_WORD}.%+-]*@#{CG_ALNUM}[#{CC_ALNUM}.-]*\.#{CG_ALPHA}{2,4}\b)
834
860
 
835
861
  # Matches an inline footnote macro, which is allowed to span multiple lines.
836
862
  #
@@ -839,7 +865,7 @@ module Asciidoctor
839
865
  # footnoteref:[id,text]
840
866
  # footnoteref:[id]
841
867
  #
842
- FootnoteInlineMacroRx = /\\?(footnote(?:ref)?):\[(#{CC_ALL}*?[^\\])\]/m
868
+ InlineFootnoteMacroRx = /\\?(footnote(?:ref)?):\[(#{CC_ALL}*?[^\\])\]/m
843
869
 
844
870
  # Matches an image or icon inline macro.
845
871
  #
@@ -850,7 +876,8 @@ module Asciidoctor
850
876
  # image:filename.png[More [Alt\] Text] (alt text becomes "More [Alt] Text")
851
877
  # icon:github[large]
852
878
  #
853
- ImageInlineMacroRx = /\\?(?:image|icon):([^:\[][^\[]*)\[((?:\\\]|[^\]])*?)\]/
879
+ # NOTE be as non-greedy as possible by not allowing endline or left square bracket in target
880
+ InlineImageMacroRx = /\\?i(?:mage|con):([^:\s\[](?:[^\n\[]*[^\s\[])?)\[(|#{CC_ALL}*?[^\\])\]/m
854
881
 
855
882
  # Matches an indexterm inline macro, which may span multiple lines.
856
883
  #
@@ -861,7 +888,7 @@ module Asciidoctor
861
888
  # indexterm2:[Tigers]
862
889
  # ((Tigers))
863
890
  #
864
- IndextermInlineMacroRx = /\\?(?:(indexterm2?):\[(#{CC_ALL}*?[^\\])\]|\(\((#{CC_ALL}+?)\)\)(?!\)))/m
891
+ InlineIndextermMacroRx = /\\?(?:(indexterm2?):\[(#{CC_ALL}*?[^\\])\]|\(\((#{CC_ALL}+?)\)\)(?!\)))/m
865
892
 
866
893
  # Matches either the kbd or btn inline macro.
867
894
  #
@@ -873,26 +900,19 @@ module Asciidoctor
873
900
  # kbd:[Ctrl,T]
874
901
  # btn:[Save]
875
902
  #
876
- KbdBtnInlineMacroRx = /\\?(?:kbd|btn):\[((?:\\\]|[^\]])+?)\]/
877
-
878
- # Matches the delimiter used for kbd value.
879
- #
880
- # Examples
881
- #
882
- # Ctrl + Alt+T
883
- # Ctrl,T
884
- #
885
- KbdDelimiterRx = /(?:\+|,)(?=#{CG_BLANK}*[^\1])/
903
+ InlineKbdBtnMacroRx = /(\\)?(kbd|btn):\[(#{CC_ALL}*?[^\\])\]/m
886
904
 
887
905
  # Matches an implicit link and some of the link inline macro.
888
906
  #
889
907
  # Examples
890
908
  #
891
- # http://github.com
892
- # http://github.com[GitHub]
909
+ # https://github.com
910
+ # https://github.com[GitHub]
911
+ # <https://github.com>
912
+ # link:https://github.com[]
893
913
  #
894
914
  # FIXME revisit! the main issue is we need different rules for implicit vs explicit
895
- LinkInlineRx = %r{(^|link:|&lt;|[\s>\(\)\[\];])(\\?(?:https?|file|ftp|irc)://[^\s\[\]<]*[^\s.,\[\]<])(?:\[((?:\\\]|[^\]])*?)\])?}
915
+ LinkInlineRx = %r((^|link:|#{CG_BLANK}|&lt;|[>\(\)\[\];])(\\?(?:https?|file|ftp|irc)://[^\s\[\]<]*[^\s.,\[\]<])(?:\[(|#{CC_ALL}*?[^\\])\])?)m
896
916
 
897
917
  # Match a link or e-mail inline macro.
898
918
  #
@@ -901,7 +921,12 @@ module Asciidoctor
901
921
  # link:path[label]
902
922
  # mailto:doc.writer@example.com[]
903
923
  #
904
- LinkInlineMacroRx = /\\?(?:link|mailto):([^\s\[]+)(?:\[((?:\\\]|[^\]])*?)\])/
924
+ # NOTE be as non-greedy as possible by not allowing space or left square bracket in target
925
+ InlineLinkMacroRx = /\\?(?:link|(mailto)):(|[^:\s\[][^\s\[]*)\[(|#{CC_ALL}*?[^\\])\]/m
926
+
927
+ # Matches the name of a macro.
928
+ #
929
+ MacroNameRx = /^#{CG_WORD}+$/
905
930
 
906
931
  # Matches a stem (and alternatives, asciimath and latexmath) inline macro, which may span multiple lines.
907
932
  #
@@ -911,17 +936,17 @@ module Asciidoctor
911
936
  # asciimath:[x != 0]
912
937
  # latexmath:[\sqrt{4} = 2]
913
938
  #
914
- StemInlineMacroRx = /\\?(stem|(?:latex|ascii)math):([a-z,]*)\[(#{CC_ALL}*?[^\\])\]/m
939
+ InlineStemMacroRx = /\\?(stem|(?:latex|ascii)math):([a-z]+(?:,[a-z]+)*)?\[(#{CC_ALL}*?[^\\])\]/m
915
940
 
916
941
  # Matches a menu inline macro.
917
942
  #
918
943
  # Examples
919
944
  #
920
- # menu:File[New...]
945
+ # menu:File[Save As...]
921
946
  # menu:View[Page Style > No Style]
922
947
  # menu:View[Page Style, No Style]
923
948
  #
924
- MenuInlineMacroRx = /\\?menu:(#{CG_WORD}|#{CG_WORD}.*?\S)\[#{CG_BLANK}*(.+?)?\]/
949
+ InlineMenuMacroRx = /\\?menu:(#{CG_WORD}|[#{CC_WORD}&][^\n\[]*[^\s\[])\[ *(#{CC_ALL}*?[^\\])?\]/m
925
950
 
926
951
  # Matches an implicit menu inline macro.
927
952
  #
@@ -929,7 +954,7 @@ module Asciidoctor
929
954
  #
930
955
  # "File > New..."
931
956
  #
932
- MenuInlineRx = /\\?"(#{CG_WORD}[^"]*?#{CG_BLANK}*&gt;#{CG_BLANK}*[^" \t][^"]*)"/
957
+ MenuInlineRx = /\\?"([#{CC_WORD}&][^"]*?[ \n]+&gt;[ \n]+[^"]*)"/
933
958
 
934
959
  # Matches an inline passthrough value, which may span multiple lines.
935
960
  #
@@ -940,8 +965,8 @@ module Asciidoctor
940
965
  #
941
966
  # NOTE we always capture the attributes so we know when to use compatible (i.e., legacy) behavior
942
967
  PassInlineRx = {
943
- false => ['+', '`', /(^|[^#{CC_WORD};:])(?:\[([^\]]+?)\])?(\\?(\+|`)(\S|\S#{CC_ALL}*?\S)\4)(?!#{CG_WORD})/m],
944
- true => ['`', nil, /(^|[^`#{CC_WORD}])(?:\[([^\]]+?)\])?(\\?(`)([^`\s]|[^`\s]#{CC_ALL}*?\S)\4)(?![`#{CC_WORD}])/m]
968
+ false => ['+', '`', /(^|[^#{CC_WORD};:])(?:\[([^\]]+)\])?(\\?(\+|`)(\S|\S#{CC_ALL}*?\S)\4)(?!#{CG_WORD})/m],
969
+ true => ['`', nil, /(^|[^`#{CC_WORD}])(?:\[([^\]]+)\])?(\\?(`)([^`\s]|[^`\s]#{CC_ALL}*?\S)\4)(?![`#{CC_WORD}])/m]
945
970
  }
946
971
 
947
972
  # Matches several variants of the passthrough inline macro, which may span multiple lines.
@@ -952,7 +977,8 @@ module Asciidoctor
952
977
  # $$text$$
953
978
  # pass:quotes[text]
954
979
  #
955
- PassInlineMacroRx = /(?:(?:(\\?)\[([^\]]+?)\])?(\\{0,2})(\+{2,3}|\${2})(#{CC_ALL}*?)\4|(\\?)pass:([a-z,]*)\[(#{CC_ALL}*?[^\\])\])/m
980
+ # NOTE we have to support an empty pass:[] for compatibility with AsciiDoc Python
981
+ InlinePassMacroRx = /(?:(?:(\\?)\[([^\]]+)\])?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m
956
982
 
957
983
  # Matches an xref (i.e., cross-reference) inline macro, which may span multiple lines.
958
984
  #
@@ -962,78 +988,70 @@ module Asciidoctor
962
988
  # xref:id[reftext]
963
989
  #
964
990
  # NOTE special characters have already been escaped, hence the entity references
965
- XrefInlineMacroRx = /\\?(?:&lt;&lt;([#{CC_WORD}":.\/]#{CC_ALL}*?)&gt;&gt;|xref:([#{CC_WORD}":.\/]#{CC_ALL}*?)\[(#{CC_ALL}*?)\])/m
991
+ # NOTE { is included in start characters to support target that begins with attribute reference in title content
992
+ InlineXrefMacroRx = %r(\\?(?:&lt;&lt;([#{CC_WORD}#/.:{]#{CC_ALL}*?)&gt;&gt;|xref:([#{CC_WORD}#/.:{]#{CC_ALL}*?)\[(#{CC_ALL}*?[^\\])?\]))m
966
993
 
967
994
  ## Layout
968
995
 
969
996
  # Matches a trailing + preceded by at least one space character,
970
997
  # which forces a hard line break (<br> tag in HTML output).
971
998
  #
999
+ # NOTE AsciiDoc Python allows + to be preceded by TAB; Asciidoctor does not
1000
+ #
972
1001
  # Examples
973
1002
  #
974
1003
  # Humpty Dumpty sat on a wall, +
975
1004
  # Humpty Dumpty had a great fall.
976
1005
  #
977
1006
  if RUBY_ENGINE == 'opal'
978
- # NOTE JavaScript only treats ^ and $ as line boundaries in multiline regexp; . won't match newlines
979
- LineBreakRx = /^(.*)[ \t]\+$/m
1007
+ # NOTE In Ruby, ^ and $ always match start and end of line, respectively; JavaScript only does so in multiline mode
1008
+ HardLineBreakRx = /^(.*) \+$/m
980
1009
  else
981
- LineBreakRx = /^(.*)[[:blank:]]\+$/
1010
+ HardLineBreakRx = /^(.*) \+$/
982
1011
  end
983
1012
 
984
- # Matches an AsciiDoc horizontal rule or AsciiDoc page break.
1013
+ # Matches a Markdown horizontal rule.
985
1014
  #
986
1015
  # Examples
987
1016
  #
988
- # ''' (horizontal rule)
989
- # <<< (page break)
1017
+ # --- or - - -
1018
+ # *** or * * *
1019
+ # ___ or _ _ _
990
1020
  #
991
- LayoutBreakLineRx = /^('|<){3,}$/
1021
+ MarkdownThematicBreakRx = /^ {0,3}([-*_])( *)\1\2\1$/
992
1022
 
993
1023
  # Matches an AsciiDoc or Markdown horizontal rule or AsciiDoc page break.
994
1024
  #
995
1025
  # Examples
996
1026
  #
997
- # ''' or ' ' ' (horizontal rule)
998
- # --- or - - - (horizontal rule)
999
- # *** or * * * (horizontal rule)
1027
+ # ''' (horizontal rule)
1000
1028
  # <<< (page break)
1029
+ # --- or - - - (horizontal rule, Markdown)
1030
+ # *** or * * * (horizontal rule, Markdown)
1031
+ # ___ or _ _ _ (horizontal rule, Markdown)
1001
1032
  #
1002
- LayoutBreakLinePlusRx = /^(?:'|<){3,}$|^ {0,3}([-\*_])( *)\1\2\1$/
1033
+ HybridLayoutBreakRx = /^(?:'{3,}|<{3,}|([-*_])( *)\1\2\1)$/
1003
1034
 
1004
1035
  ## General
1005
1036
 
1006
- # Matches a blank line.
1007
- #
1008
- # NOTE allows for empty space in line as it could be left by the template engine
1009
- BlankLineRx = /^#{CG_BLANK}*\n/
1010
-
1011
- # Matches a comma or semi-colon delimiter.
1037
+ # Matches consecutive blank lines.
1012
1038
  #
1013
1039
  # Examples
1014
1040
  #
1015
- # one,two
1016
- # three;four
1017
- #
1018
- DataDelimiterRx = /,|;/
1019
-
1020
- # Matches a single-line of text enclosed in double quotes, capturing the quote char and text.
1021
- #
1022
- # Examples
1041
+ # one
1023
1042
  #
1024
- # "Who goes there?"
1043
+ # two
1025
1044
  #
1026
- DoubleQuotedRx = /^("|)(.*)\1$/
1045
+ BlankLineRx = /\n{2,}/
1027
1046
 
1028
- # Matches multiple lines of text enclosed in double quotes, capturing the quote char and text.
1047
+ # Matches a comma or semi-colon delimiter.
1029
1048
  #
1030
1049
  # Examples
1031
1050
  #
1032
- # "I am a run-on sentence and I like
1033
- # to take up multiple lines and I
1034
- # still want to be matched."
1051
+ # one,two
1052
+ # three;four
1035
1053
  #
1036
- DoubleQuotedMultiRx = /^("|)(#{CC_ALL}*)\1$/m
1054
+ DataDelimiterRx = /[,;]/
1037
1055
 
1038
1056
  # Matches one or more consecutive digits at the end of a line.
1039
1057
  #
@@ -1044,21 +1062,28 @@ module Asciidoctor
1044
1062
  #
1045
1063
  TrailingDigitsRx = /\d+$/
1046
1064
 
1047
- # Matches a space escaped by a backslash.
1065
+ # Matches whitespace (space, tab, newline) escaped by a backslash.
1048
1066
  #
1049
1067
  # Examples
1050
1068
  #
1051
- # one\ two\ three
1069
+ # three\ blind\ mice
1070
+ #
1071
+ EscapedSpaceRx = /\\([ \t\n])/
1072
+
1073
+ # Detects if text is a possible candidate for the replacements substitution.
1052
1074
  #
1053
- EscapedSpaceRx = /\\(#{CG_BLANK})/
1075
+ ReplaceableTextRx = /[&']|--|\.\.\.|\([CRT]M?\)/
1054
1076
 
1055
- # Matches a space delimiter that's not escaped.
1077
+ # Matches a whitespace delimiter, a sequence of spaces, tabs, and/or newlines.
1078
+ # Matches the parsing rules of %w strings in Ruby.
1056
1079
  #
1057
1080
  # Examples
1058
1081
  #
1059
- # one two three four
1082
+ # one two three four
1083
+ # five six
1060
1084
  #
1061
- SpaceDelimiterRx = /([^\\])#{CG_BLANK}+/
1085
+ # TODO change to /(?<!\\)[ \t\n]+/ after dropping support for Ruby 1.8.7
1086
+ SpaceDelimiterRx = /([^\\])[ \t\n]+/
1062
1087
 
1063
1088
  # Matches a + or - modifier in a subs list
1064
1089
  #
@@ -1066,10 +1091,8 @@ module Asciidoctor
1066
1091
 
1067
1092
  # Matches any character with multibyte support explicitly enabled (length of multibyte char = 1)
1068
1093
  #
1069
- # NOTE If necessary to hide use of the language modifier (u) from JavaScript, use (Regexp.new '.', false, 'u')
1070
- #
1071
- UnicodeCharScanRx = unless RUBY_ENGINE == 'opal'
1072
- FORCE_UNICODE_LINE_LENGTH ? /./u : nil
1094
+ unless RUBY_ENGINE == 'opal'
1095
+ UnicodeCharScanRx = /./u if FORCE_UNICODE_LINE_LENGTH
1073
1096
  end
1074
1097
 
1075
1098
  # Detects strings that resemble URIs.
@@ -1082,7 +1105,7 @@ module Asciidoctor
1082
1105
  #
1083
1106
  # not c:/sample.adoc or c:\sample.adoc
1084
1107
  #
1085
- UriSniffRx = %r{^#{CG_ALPHA}[#{CC_ALNUM}.+-]+:/{0,2}}
1108
+ UriSniffRx = %r(^#{CG_ALPHA}[#{CC_ALNUM}.+-]+:/{0,2})
1086
1109
 
1087
1110
  # Detects the end of an implicit URI in the text
1088
1111
  #
@@ -1092,36 +1115,10 @@ module Asciidoctor
1092
1115
  # &gt;http://google.com&lt;
1093
1116
  # (See http://google.com):
1094
1117
  #
1095
- UriTerminator = /[);:]$/
1118
+ UriTerminatorRx = /[);:]$/
1096
1119
 
1097
1120
  # Detects XML tags
1098
1121
  XmlSanitizeRx = /<[^>]+>/
1099
-
1100
- # Unused
1101
-
1102
- # Detects any fenced block delimiter, including:
1103
- # listing, literal, example, sidebar, quote, passthrough, table and fenced code
1104
- # Does not match open blocks or air quotes
1105
- # TIP position the most common blocks towards the front of the pattern
1106
- #BlockDelimiterRx = %r{^(?:(?:-|\.|=|\*|_|\+|/){4,}|[\|,;!]={3,}|(?:`|~){3,}.*)$}
1107
-
1108
- # Matches an escaped single quote within a word
1109
- #
1110
- # Examples
1111
- #
1112
- # Here\'s Johnny!
1113
- #
1114
- #EscapedSingleQuoteRx = /(#{CG_WORD})\\'(#{CG_WORD})/
1115
- # an alternative if our backend generates single-quoted html/xml attributes
1116
- #EscapedSingleQuoteRx = /(#{CG_WORD}|=)\\'(#{CG_WORD})/
1117
-
1118
- # Matches whitespace at the beginning of the line
1119
- #LeadingSpacesRx = /^(#{CG_BLANK}*)/
1120
-
1121
- # Matches parent directory references at the beginning of a path
1122
- #LeadingParentDirsRx = /^(?:\.\.\/)*/
1123
-
1124
- #StripLineWise = /\A(?:\s*\n)?(#{CC_ALL}*?)\s*\z/m
1125
1122
  #end
1126
1123
 
1127
1124
  INTRINSIC_ATTRIBUTES = {
@@ -1162,57 +1159,57 @@ module Asciidoctor
1162
1159
  # the order in which they are replaced is important
1163
1160
  quote_subs = [
1164
1161
  # **strong**
1165
- [:strong, :unconstrained, /\\?(?:\[([^\]]+?)\])?\*\*(#{CC_ALL}+?)\*\*/m],
1162
+ [:strong, :unconstrained, /\\?(?:\[([^\]]+)\])?\*\*(#{CC_ALL}+?)\*\*/m],
1166
1163
 
1167
1164
  # *strong*
1168
- [:strong, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?\*(\S|\S#{CC_ALL}*?\S)\*(?!#{CG_WORD})/m],
1165
+ [:strong, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?\*(\S|\S#{CC_ALL}*?\S)\*(?!#{CG_WORD})/m],
1169
1166
 
1170
1167
  # "`double-quoted`"
1171
- [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?"`(\S|\S#{CC_ALL}*?\S)`"(?!#{CG_WORD})/m],
1168
+ [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?"`(\S|\S#{CC_ALL}*?\S)`"(?!#{CG_WORD})/m],
1172
1169
 
1173
1170
  # '`single-quoted`'
1174
- [:single, :constrained, /(^|[^#{CC_WORD};:`}])(?:\[([^\]]+?)\])?'`(\S|\S#{CC_ALL}*?\S)`'(?!#{CG_WORD})/m],
1171
+ [:single, :constrained, /(^|[^#{CC_WORD};:`}])(?:\[([^\]]+)\])?'`(\S|\S#{CC_ALL}*?\S)`'(?!#{CG_WORD})/m],
1175
1172
 
1176
1173
  # ``monospaced``
1177
- [:monospaced, :unconstrained, /\\?(?:\[([^\]]+?)\])?``(#{CC_ALL}+?)``/m],
1174
+ [:monospaced, :unconstrained, /\\?(?:\[([^\]]+)\])?``(#{CC_ALL}+?)``/m],
1178
1175
 
1179
1176
  # `monospaced`
1180
- [:monospaced, :constrained, /(^|[^#{CC_WORD};:"'`}])(?:\[([^\]]+?)\])?`(\S|\S#{CC_ALL}*?\S)`(?![#{CC_WORD}"'`])/m],
1177
+ [:monospaced, :constrained, /(^|[^#{CC_WORD};:"'`}])(?:\[([^\]]+)\])?`(\S|\S#{CC_ALL}*?\S)`(?![#{CC_WORD}"'`])/m],
1181
1178
 
1182
1179
  # __emphasis__
1183
- [:emphasis, :unconstrained, /\\?(?:\[([^\]]+?)\])?__(#{CC_ALL}+?)__/m],
1180
+ [:emphasis, :unconstrained, /\\?(?:\[([^\]]+)\])?__(#{CC_ALL}+?)__/m],
1184
1181
 
1185
1182
  # _emphasis_
1186
- [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?_(\S|\S#{CC_ALL}*?\S)_(?!#{CG_WORD})/m],
1183
+ [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?_(\S|\S#{CC_ALL}*?\S)_(?!#{CG_WORD})/m],
1187
1184
 
1188
1185
  # ##mark## (referred to in AsciiDoc Python as unquoted)
1189
- [:mark, :unconstrained, /\\?(?:\[([^\]]+?)\])?##(#{CC_ALL}+?)##/m],
1186
+ [:mark, :unconstrained, /\\?(?:\[([^\]]+)\])?##(#{CC_ALL}+?)##/m],
1190
1187
 
1191
1188
  # #mark# (referred to in AsciiDoc Python as unquoted)
1192
- [:mark, :constrained, /(^|[^#{CC_WORD}&;:}])(?:\[([^\]]+?)\])?#(\S|\S#{CC_ALL}*?\S)#(?!#{CG_WORD})/m],
1189
+ [:mark, :constrained, /(^|[^#{CC_WORD}&;:}])(?:\[([^\]]+)\])?#(\S|\S#{CC_ALL}*?\S)#(?!#{CG_WORD})/m],
1193
1190
 
1194
1191
  # ^superscript^
1195
- [:superscript, :unconstrained, /\\?(?:\[([^\]]+?)\])?\^(\S+?)\^/],
1192
+ [:superscript, :unconstrained, /\\?(?:\[([^\]]+)\])?\^(\S+?)\^/],
1196
1193
 
1197
1194
  # ~subscript~
1198
- [:subscript, :unconstrained, /\\?(?:\[([^\]]+?)\])?~(\S+?)~/]
1195
+ [:subscript, :unconstrained, /\\?(?:\[([^\]]+)\])?~(\S+?)~/]
1199
1196
  ]
1200
1197
 
1201
1198
  compat_quote_subs = quote_subs.dup
1202
1199
  # ``quoted''
1203
- compat_quote_subs[2] = [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?``(\S|\S#{CC_ALL}*?\S)''(?!#{CG_WORD})/m]
1200
+ compat_quote_subs[2] = [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?``(\S|\S#{CC_ALL}*?\S)''(?!#{CG_WORD})/m]
1204
1201
  # `quoted'
1205
- compat_quote_subs[3] = [:single, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?`(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m]
1202
+ compat_quote_subs[3] = [:single, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?`(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m]
1206
1203
  # ++monospaced++
1207
- compat_quote_subs[4] = [:monospaced, :unconstrained, /\\?(?:\[([^\]]+?)\])?\+\+(#{CC_ALL}+?)\+\+/m]
1204
+ compat_quote_subs[4] = [:monospaced, :unconstrained, /\\?(?:\[([^\]]+)\])?\+\+(#{CC_ALL}+?)\+\+/m]
1208
1205
  # +monospaced+
1209
- compat_quote_subs[5] = [:monospaced, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?\+(\S|\S#{CC_ALL}*?\S)\+(?!#{CG_WORD})/m]
1206
+ compat_quote_subs[5] = [:monospaced, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?\+(\S|\S#{CC_ALL}*?\S)\+(?!#{CG_WORD})/m]
1210
1207
  # #unquoted#
1211
1208
  #compat_quote_subs[8] = [:unquoted, *compat_quote_subs[8][1..-1]]
1212
1209
  # ##unquoted##
1213
1210
  #compat_quote_subs[9] = [:unquoted, *compat_quote_subs[9][1..-1]]
1214
1211
  # 'emphasis'
1215
- compat_quote_subs.insert 3, [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?'(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m]
1212
+ compat_quote_subs.insert 3, [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+)\])?'(\S|\S#{CC_ALL}*?\S)'(?!#{CG_WORD})/m]
1216
1213
 
1217
1214
  QUOTE_SUBS = {
1218
1215
  false => quote_subs,
@@ -1251,7 +1248,7 @@ module Asciidoctor
1251
1248
  # left double arrow <=
1252
1249
  [/\\?&lt;=/, '&#8656;', :none],
1253
1250
  # restore entities
1254
- [/\\?(&)amp;((?:[a-zA-Z]{2,}|#\d{2,6}|#x[a-fA-F0-9]{2,5});)/, '', :bounding]
1251
+ [/\\?(&)amp;((?:[a-zA-Z][a-zA-Z]+\d{0,2}|#\d\d\d{0,4}|#x[\da-fA-F][\da-fA-F][\da-fA-F]{0,3});)/, '', :bounding]
1255
1252
  ]
1256
1253
 
1257
1254
  class << self
@@ -1274,36 +1271,28 @@ module Asciidoctor
1274
1271
  timings.start :read
1275
1272
  end
1276
1273
 
1277
- attributes = options[:attributes] = if !(attrs = options[:attributes])
1278
- {}
1274
+ if !(attrs = options[:attributes])
1275
+ attrs = {}
1279
1276
  elsif ::Hash === attrs || (::RUBY_ENGINE_JRUBY && ::Java::JavaUtil::Map === attrs)
1280
- attrs.dup
1277
+ attrs = attrs.dup
1281
1278
  elsif ::Array === attrs
1282
- attrs.inject({}) do |accum, entry|
1279
+ attrs, attrs_arr = {}, attrs
1280
+ attrs_arr.each do |entry|
1283
1281
  k, v = entry.split '=', 2
1284
- accum[k] = v || ''
1285
- accum
1282
+ attrs[k] = v || ''
1286
1283
  end
1287
1284
  elsif ::String === attrs
1288
- # convert non-escaped spaces into null character, so we split on the
1289
- # correct spaces chars, and restore escaped spaces
1290
- capture_1 = '\1'
1291
- attrs = attrs.gsub(SpaceDelimiterRx, %(#{capture_1}#{NULL})).gsub(EscapedSpaceRx, capture_1)
1292
- attrs.split(NULL).inject({}) do |accum, entry|
1285
+ # condense and convert non-escaped spaces to null, unescape escaped spaces, then split on null
1286
+ attrs, attrs_arr = {}, attrs.gsub(SpaceDelimiterRx, %(\\1#{NULL})).gsub(EscapedSpaceRx, '\1').split(NULL)
1287
+ attrs_arr.each do |entry|
1293
1288
  k, v = entry.split '=', 2
1294
- accum[k] = v || ''
1295
- accum
1289
+ attrs[k] = v || ''
1296
1290
  end
1297
1291
  elsif (attrs.respond_to? :keys) && (attrs.respond_to? :[])
1298
1292
  # convert it to a Hash as we know it
1299
- original_attrs = attrs
1300
- attrs = {}
1301
- original_attrs.keys.each do |key|
1302
- attrs[key] = original_attrs[key]
1303
- end
1304
- attrs
1293
+ attrs = ::Hash[attrs.keys.map {|k| [k, attrs[k]] }]
1305
1294
  else
1306
- raise ::ArgumentError, %(illegal type for attributes option: #{attrs.class.ancestors})
1295
+ raise ::ArgumentError, %(illegal type for attributes option: #{attrs.class.ancestors * ' < '})
1307
1296
  end
1308
1297
 
1309
1298
  lines = nil
@@ -1311,15 +1300,21 @@ module Asciidoctor
1311
1300
  # TODO cli checks if input path can be read and is file, but might want to add check to API
1312
1301
  input_path = ::File.expand_path input.path
1313
1302
  # See https://reproducible-builds.org/specs/source-date-epoch/
1314
- input_mtime = ::ENV['SOURCE_DATE_EPOCH'] ? (::Time.at ::ENV['SOURCE_DATE_EPOCH'].to_i).utc : input.mtime
1303
+ # NOTE Opal can't call key? on ENV
1304
+ input_mtime = ::ENV['SOURCE_DATE_EPOCH'] ? ::Time.at(Integer ::ENV['SOURCE_DATE_EPOCH']).utc : input.mtime
1315
1305
  lines = input.readlines
1316
1306
  # hold off on setting infile and indir until we get a better sense of their purpose
1317
- attributes['docfile'] = input_path
1318
- attributes['docdir'] = ::File.dirname input_path
1319
- attributes['docname'] = Helpers.basename input_path, true
1320
- docdate = (attributes['docdate'] ||= input_mtime.strftime('%Y-%m-%d'))
1321
- doctime = (attributes['doctime'] ||= input_mtime.strftime('%H:%M:%S %Z'))
1322
- attributes['docdatetime'] = %(#{docdate} #{doctime})
1307
+ attrs['docfile'] = input_path
1308
+ attrs['docdir'] = ::File.dirname input_path
1309
+ attrs['docname'] = Helpers.basename input_path, (attrs['docfilesuffix'] = ::File.extname input_path)
1310
+ if (docdate = attrs['docdate'])
1311
+ attrs['docyear'] ||= ((docdate.index '-') == 4 ? (docdate.slice 0, 4) : nil)
1312
+ else
1313
+ docdate = attrs['docdate'] = (input_mtime.strftime '%Y-%m-%d')
1314
+ attrs['docyear'] ||= input_mtime.year.to_s
1315
+ end
1316
+ doctime = (attrs['doctime'] ||= input_mtime.strftime('%H:%M:%S %Z'))
1317
+ attrs['docdatetime'] = %(#{docdate} #{doctime})
1323
1318
  elsif input.respond_to? :readlines
1324
1319
  # NOTE tty, pipes & sockets can't be rewound, but can't be sniffed easily either
1325
1320
  # just fail the rewind operation silently to handle all cases
@@ -1329,7 +1324,7 @@ module Asciidoctor
1329
1324
  end
1330
1325
  lines = input.readlines
1331
1326
  elsif ::String === input
1332
- lines = input.lines.entries
1327
+ lines = ::RUBY_MIN_VERSION_2 ? input.lines : input.each_line.to_a
1333
1328
  elsif ::Array === input
1334
1329
  lines = input.dup
1335
1330
  else
@@ -1341,17 +1336,14 @@ module Asciidoctor
1341
1336
  timings.start :parse
1342
1337
  end
1343
1338
 
1344
- if options[:parse] == false
1345
- doc = Document.new lines, options
1346
- else
1347
- doc = (Document.new lines, options).parse
1348
- end
1339
+ options[:attributes] = attrs
1340
+ doc = options[:parse] == false ? (Document.new lines, options) : (Document.new lines, options).parse
1349
1341
 
1350
1342
  timings.record :parse if timings
1351
1343
  doc
1352
1344
  rescue => ex
1353
1345
  begin
1354
- context = %(asciidoctor: FAILED: #{attributes['docfile'] || '<stdin>'}: Failed to load AsciiDoc document)
1346
+ context = %(asciidoctor: FAILED: #{attrs['docfile'] || '<stdin>'}: Failed to load AsciiDoc document)
1355
1347
  if ex.respond_to? :exception
1356
1348
  # The original message must be explicitely preserved when wrapping a Ruby exception
1357
1349
  wrapped_ex = ex.exception %(#{context} - #{ex.message})
@@ -1381,7 +1373,7 @@ module Asciidoctor
1381
1373
  #
1382
1374
  # Returns the Asciidoctor::Document
1383
1375
  def load_file filename, options = {}
1384
- self.load ::File.new(filename || ''), options
1376
+ ::File.open(filename) {|file| self.load file, options }
1385
1377
  end
1386
1378
 
1387
1379
  # Public: Parse the AsciiDoc source input into an Asciidoctor::Document and
@@ -1436,27 +1428,26 @@ module Asciidoctor
1436
1428
  return self.load input, options
1437
1429
  else
1438
1430
  write_to_same_dir = false
1439
- stream_output = to_file.respond_to? :write
1440
- write_to_target = stream_output ? false : to_file
1431
+ write_to_target = (stream_output = to_file.respond_to? :write) ? false : to_file
1441
1432
  end
1442
1433
 
1443
1434
  unless options.key? :header_footer
1444
1435
  options[:header_footer] = true if write_to_same_dir || write_to_target
1445
1436
  end
1446
1437
 
1447
- # NOTE at least make intended target directory available, if there is one
1438
+ # NOTE outfile may be controlled by document attributes, so resolve outfile after loading
1448
1439
  if write_to_same_dir
1449
1440
  input_path = ::File.expand_path input.path
1450
1441
  options[:to_dir] = (outdir = ::File.dirname input_path)
1451
1442
  elsif write_to_target
1452
1443
  if to_dir
1453
1444
  if to_file
1454
- options[:to_dir] = ::File.dirname ::File.expand_path(::File.join to_dir, to_file)
1445
+ options[:to_dir] = ::File.expand_path ::File.join to_dir, to_file, '..'
1455
1446
  else
1456
1447
  options[:to_dir] = ::File.expand_path to_dir
1457
1448
  end
1458
1449
  elsif to_file
1459
- options[:to_dir] = ::File.dirname ::File.expand_path to_file
1450
+ options[:to_dir] = ::File.expand_path to_file, '..'
1460
1451
  end
1461
1452
  else
1462
1453
  options[:to_dir] = nil
@@ -1464,13 +1455,13 @@ module Asciidoctor
1464
1455
 
1465
1456
  doc = self.load input, options
1466
1457
 
1467
- if write_to_same_dir
1458
+ if write_to_same_dir # write to file in same directory
1468
1459
  outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
1469
1460
  if outfile == input_path
1470
1461
  raise ::IOError, %(input file and output file cannot be the same: #{outfile})
1471
1462
  end
1472
- elsif write_to_target
1473
- working_dir = options.has_key?(:base_dir) ? ::File.expand_path(options[:base_dir]) : ::File.expand_path(::Dir.pwd)
1463
+ elsif write_to_target # write to explicit file or directory
1464
+ working_dir = (options.key? :base_dir) ? (::File.expand_path options[:base_dir]) : (::File.expand_path ::Dir.pwd)
1474
1465
  # QUESTION should the jail be the working_dir or doc.base_dir???
1475
1466
  jail = doc.safe >= SafeMode::SAFE ? working_dir : nil
1476
1467
  if to_dir
@@ -1488,6 +1479,10 @@ module Asciidoctor
1488
1479
  outdir = ::File.dirname outfile
1489
1480
  end
1490
1481
 
1482
+ if ::File === input && outfile == (::File.expand_path input.path)
1483
+ raise ::IOError, %(input file and output file cannot be the same: #{outfile})
1484
+ end
1485
+
1491
1486
  unless ::File.directory? outdir
1492
1487
  if mkdirs
1493
1488
  Helpers.mkdir_p outdir
@@ -1496,7 +1491,7 @@ module Asciidoctor
1496
1491
  raise ::IOError, %(target directory does not exist: #{to_dir})
1497
1492
  end
1498
1493
  end
1499
- else
1494
+ else # write to stream
1500
1495
  outfile = to_file
1501
1496
  outdir = nil
1502
1497
  end
@@ -1542,10 +1537,8 @@ module Asciidoctor
1542
1537
  stylesheet_src = doc.normalize_system_path stylesheet_src
1543
1538
  end
1544
1539
  stylesheet_dst = doc.normalize_system_path stylesheet, stylesoutdir, (doc.safe >= SafeMode::SAFE ? outdir : nil)
1545
- unless stylesheet_src == stylesheet_dst || (stylesheet_content = doc.read_asset stylesheet_src).nil?
1546
- ::File.open(stylesheet_dst, 'w') {|f|
1547
- f.write stylesheet_content
1548
- }
1540
+ if stylesheet_src != stylesheet_dst && (stylesheet_content = doc.read_asset stylesheet_src, :warn_on_failure => true, :label => 'stylesheet')
1541
+ ::IO.write stylesheet_dst, stylesheet_content
1549
1542
  end
1550
1543
  end
1551
1544
 
@@ -1563,7 +1556,7 @@ module Asciidoctor
1563
1556
  end
1564
1557
 
1565
1558
  # Alias render to convert to maintain backwards compatibility
1566
- alias :render :convert
1559
+ alias render convert
1567
1560
 
1568
1561
  # Public: Parse the contents of the AsciiDoc source file into an
1569
1562
  # Asciidoctor::Document and convert it to the specified backend format.
@@ -1576,20 +1569,39 @@ module Asciidoctor
1576
1569
  # Returns the Document object if the converted String is written to a
1577
1570
  # file, otherwise the converted String
1578
1571
  def convert_file filename, options = {}
1579
- self.convert ::File.new(filename || ''), options
1572
+ ::File.open(filename) {|file| self.convert file, options }
1580
1573
  end
1581
1574
 
1582
1575
  # Alias render_file to convert_file to maintain backwards compatibility
1583
- alias :render_file :convert_file
1576
+ alias render_file convert_file
1577
+
1578
+ # Internal: Automatically load the Asciidoctor::Extensions module.
1579
+ #
1580
+ # Requires the Asciidoctor::Extensions module if the name is :Extensions.
1581
+ # Otherwise, delegates to the super method.
1582
+ #
1583
+ # This method provides the same functionality as using autoload on
1584
+ # Asciidoctor::Extensions, except that the constant isn't recognized as
1585
+ # defined prior to it being loaded.
1586
+ #
1587
+ # Returns the resolved constant, if resolved, otherwise nothing.
1588
+ def const_missing name
1589
+ if name == :Extensions
1590
+ require 'asciidoctor/extensions'
1591
+ Extensions
1592
+ else
1593
+ super
1594
+ end
1595
+ end unless RUBY_ENGINE == 'opal'
1584
1596
 
1585
1597
  end
1586
1598
 
1587
1599
  if RUBY_ENGINE == 'opal'
1588
- require 'asciidoctor/version'
1589
1600
  require 'asciidoctor/timings'
1601
+ require 'asciidoctor/version'
1590
1602
  else
1591
- autoload :VERSION, 'asciidoctor/version'
1592
1603
  autoload :Timings, 'asciidoctor/timings'
1604
+ autoload :VERSION, 'asciidoctor/version'
1593
1605
  end
1594
1606
  end
1595
1607
 
@@ -1609,7 +1621,6 @@ require 'asciidoctor/attribute_list'
1609
1621
  require 'asciidoctor/block'
1610
1622
  require 'asciidoctor/callouts'
1611
1623
  require 'asciidoctor/converter'
1612
- require 'asciidoctor/converter/html5' if RUBY_ENGINE_OPAL
1613
1624
  require 'asciidoctor/document'
1614
1625
  require 'asciidoctor/inline'
1615
1626
  require 'asciidoctor/list'
@@ -1619,3 +1630,6 @@ require 'asciidoctor/reader'
1619
1630
  require 'asciidoctor/section'
1620
1631
  require 'asciidoctor/stylesheets'
1621
1632
  require 'asciidoctor/table'
1633
+
1634
+ # this require is satisfied by the Asciidoctor.js build; it supplies compile and runtime overrides for Asciidoctor.js
1635
+ require 'asciidoctor/js/postscript' if RUBY_ENGINE == 'opal'