asciidoctor 2.0.6 → 2.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +159 -6
  3. data/LICENSE +2 -1
  4. data/README-de.adoc +5 -5
  5. data/README-fr.adoc +4 -4
  6. data/README-jp.adoc +248 -183
  7. data/README-zh_CN.adoc +6 -6
  8. data/README.adoc +17 -11
  9. data/asciidoctor.gemspec +8 -8
  10. data/data/locale/attributes-ar.adoc +4 -3
  11. data/data/locale/attributes-bg.adoc +4 -3
  12. data/data/locale/attributes-ca.adoc +6 -5
  13. data/data/locale/attributes-cs.adoc +4 -3
  14. data/data/locale/attributes-da.adoc +6 -5
  15. data/data/locale/attributes-de.adoc +4 -4
  16. data/data/locale/attributes-en.adoc +4 -4
  17. data/data/locale/attributes-es.adoc +6 -5
  18. data/data/locale/attributes-fa.adoc +4 -3
  19. data/data/locale/attributes-fi.adoc +4 -3
  20. data/data/locale/attributes-fr.adoc +6 -5
  21. data/data/locale/attributes-hu.adoc +4 -3
  22. data/data/locale/attributes-id.adoc +4 -3
  23. data/data/locale/attributes-it.adoc +4 -3
  24. data/data/locale/attributes-ja.adoc +4 -3
  25. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  26. data/data/locale/attributes-nb.adoc +4 -3
  27. data/data/locale/attributes-nl.adoc +4 -3
  28. data/data/locale/attributes-nn.adoc +4 -3
  29. data/data/locale/attributes-pl.adoc +8 -7
  30. data/data/locale/attributes-pt.adoc +6 -5
  31. data/data/locale/attributes-pt_BR.adoc +6 -5
  32. data/data/locale/attributes-ro.adoc +4 -3
  33. data/data/locale/attributes-ru.adoc +6 -5
  34. data/data/locale/attributes-sr.adoc +4 -4
  35. data/data/locale/attributes-sr_Latn.adoc +4 -4
  36. data/data/locale/attributes-sv.adoc +4 -4
  37. data/data/locale/attributes-tr.adoc +4 -3
  38. data/data/locale/attributes-uk.adoc +6 -5
  39. data/data/locale/attributes-zh_CN.adoc +4 -3
  40. data/data/locale/attributes-zh_TW.adoc +4 -3
  41. data/data/stylesheets/asciidoctor-default.css +29 -26
  42. data/lib/asciidoctor.rb +94 -1098
  43. data/lib/asciidoctor/abstract_block.rb +19 -11
  44. data/lib/asciidoctor/abstract_node.rb +21 -15
  45. data/lib/asciidoctor/attribute_list.rb +59 -67
  46. data/lib/asciidoctor/cli/invoker.rb +2 -0
  47. data/lib/asciidoctor/cli/options.rb +8 -8
  48. data/lib/asciidoctor/convert.rb +198 -0
  49. data/lib/asciidoctor/converter.rb +14 -13
  50. data/lib/asciidoctor/converter/docbook5.rb +9 -25
  51. data/lib/asciidoctor/converter/html5.rb +65 -42
  52. data/lib/asciidoctor/converter/manpage.rb +13 -12
  53. data/lib/asciidoctor/converter/template.rb +6 -3
  54. data/lib/asciidoctor/document.rb +40 -48
  55. data/lib/asciidoctor/extensions.rb +3 -3
  56. data/lib/asciidoctor/helpers.rb +38 -39
  57. data/lib/asciidoctor/inline.rb +1 -1
  58. data/lib/asciidoctor/load.rb +117 -0
  59. data/lib/asciidoctor/parser.rb +29 -25
  60. data/lib/asciidoctor/path_resolver.rb +35 -25
  61. data/lib/asciidoctor/reader.rb +14 -7
  62. data/lib/asciidoctor/rx.rb +722 -0
  63. data/lib/asciidoctor/substitutors.rb +62 -40
  64. data/lib/asciidoctor/syntax_highlighter.rb +22 -8
  65. data/lib/asciidoctor/syntax_highlighter/coderay.rb +1 -1
  66. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +12 -4
  67. data/lib/asciidoctor/syntax_highlighter/prettify.rb +7 -4
  68. data/lib/asciidoctor/syntax_highlighter/pygments.rb +2 -3
  69. data/lib/asciidoctor/syntax_highlighter/rouge.rb +18 -11
  70. data/lib/asciidoctor/table.rb +49 -20
  71. data/lib/asciidoctor/version.rb +1 -1
  72. data/man/asciidoctor.1 +17 -17
  73. data/man/asciidoctor.adoc +15 -14
  74. metadata +12 -9
@@ -148,7 +148,7 @@ module Asciidoctor
148
148
  # Compliance value: 'drop-line'
149
149
  define :attribute_missing, 'skip'
150
150
 
151
- # AsciiDoc drops lines that contain an attribute unassignemnt.
151
+ # AsciiDoc drops lines that contain an attribute unassignment.
152
152
  # This behavior may need to be tuned depending on the circumstances.
153
153
  # Compliance value: 'drop-line'
154
154
  define :attribute_undefined, 'drop-line'
@@ -230,7 +230,7 @@ module Asciidoctor
230
230
 
231
231
  # Pointers to the preferred version for a given backend.
232
232
  BACKEND_ALIASES = {
233
- 'html' => 'html5',
233
+ 'html' => 'html5',
234
234
  'docbook' => 'docbook5'
235
235
  }
236
236
 
@@ -270,14 +270,14 @@ module Asciidoctor
270
270
 
271
271
  ADMONITION_STYLES = ['NOTE', 'TIP', 'IMPORTANT', 'WARNING', 'CAUTION'].to_set
272
272
 
273
- ADMONITION_STYLE_HEADS = ['N', 'T', 'I', 'W', 'C'].to_set
273
+ ADMONITION_STYLE_HEADS = ::Set.new.tap {|accum| ADMONITION_STYLES.each {|s| accum << s.chr } }
274
274
 
275
275
  PARAGRAPH_STYLES = ['comment', 'example', 'literal', 'listing', 'normal', 'open', 'pass', 'quote', 'sidebar', 'source', 'verse', 'abstract', 'partintro'].to_set
276
276
 
277
277
  VERBATIM_STYLES = ['literal', 'listing', 'source', 'verse'].to_set
278
278
 
279
279
  DELIMITED_BLOCKS = {
280
- '--' => [:open, ['comment', 'example', 'literal', 'listing', 'pass', 'quote', 'sidebar', 'source', 'verse', 'admonition', 'abstract', 'partintro'].to_set],
280
+ '--' => [:open, ['comment', 'example', 'literal', 'listing', 'pass', 'quote', 'sidebar', 'source', 'verse', 'admonition', 'abstract', 'partintro'].to_set],
281
281
  '----' => [:listing, ['literal', 'source'].to_set],
282
282
  '....' => [:literal, ['listing', 'source'].to_set],
283
283
  '====' => [:example, ['admonition'].to_set],
@@ -289,24 +289,24 @@ module Asciidoctor
289
289
  ':===' => [:table, ::Set.new],
290
290
  '!===' => [:table, ::Set.new],
291
291
  '////' => [:comment, ::Set.new],
292
- '```' => [:fenced_code, ::Set.new]
292
+ '```' => [:fenced_code, ::Set.new]
293
293
  }
294
294
 
295
295
  DELIMITED_BLOCK_HEADS = {}.tap {|accum| DELIMITED_BLOCKS.each_key {|k| accum[k.slice 0, 2] = true } }
296
296
  DELIMITED_BLOCK_TAILS = {}.tap {|accum| DELIMITED_BLOCKS.each_key {|k| accum[k] = k[k.length - 1] if k.length == 4 } }
297
297
 
298
298
  # NOTE the 'figure' key as a string is historical and used by image blocks
299
- CAPTION_ATTR_NAMES = { example: 'example-caption', 'figure' => 'figure-caption', listing: 'listing-caption', table: 'table-caption' }
299
+ CAPTION_ATTRIBUTE_NAMES = { example: 'example-caption', 'figure' => 'figure-caption', listing: 'listing-caption', table: 'table-caption' }
300
300
 
301
301
  LAYOUT_BREAK_CHARS = {
302
302
  '\'' => :thematic_break,
303
- '<' => :page_break
303
+ '<' => :page_break
304
304
  }
305
305
 
306
306
  MARKDOWN_THEMATIC_BREAK_CHARS = {
307
- '-' => :thematic_break,
308
- '*' => :thematic_break,
309
- '_' => :thematic_break
307
+ '-' => :thematic_break,
308
+ '*' => :thematic_break,
309
+ '_' => :thematic_break
310
310
  }
311
311
 
312
312
  HYBRID_LAYOUT_BREAK_CHARS = LAYOUT_BREAK_CHARS.merge MARKDOWN_THEMATIC_BREAK_CHARS
@@ -319,8 +319,8 @@ module Asciidoctor
319
319
  ORDERED_LIST_STYLES = [:arabic, :loweralpha, :lowerroman, :upperalpha, :upperroman] #, :lowergreek]
320
320
 
321
321
  ORDERED_LIST_KEYWORDS = {
322
- #'arabic' => '1',
323
- #'decimal' => '1',
322
+ #'arabic' => '1',
323
+ #'decimal' => '1',
324
324
  'loweralpha' => 'a',
325
325
  'lowerroman' => 'i',
326
326
  #'lowergreek' => 'a',
@@ -357,789 +357,86 @@ module Asciidoctor
357
357
 
358
358
  FONT_AWESOME_VERSION = '4.7.0'
359
359
 
360
- HIGHLIGHT_JS_VERSION = '9.15.6'
361
-
362
- MATHJAX_VERSION = '2.7.5'
360
+ HIGHLIGHT_JS_VERSION = '9.18.3'
361
+
362
+ MATHJAX_VERSION = '2.7.9'
363
+
364
+ DEFAULT_ATTRIBUTES = {
365
+ 'appendix-caption' => 'Appendix',
366
+ 'appendix-refsig' => 'Appendix',
367
+ 'caution-caption' => 'Caution',
368
+ 'chapter-refsig' => 'Chapter',
369
+ #'encoding' => 'UTF-8',
370
+ 'example-caption' => 'Example',
371
+ 'figure-caption' => 'Figure',
372
+ 'important-caption' => 'Important',
373
+ 'last-update-label' => 'Last updated',
374
+ #'listing-caption' => 'Listing',
375
+ 'note-caption' => 'Note',
376
+ 'part-refsig' => 'Part',
377
+ #'preface-title' => 'Preface',
378
+ 'prewrap' => '',
379
+ 'sectids' => '',
380
+ 'section-refsig' => 'Section',
381
+ 'table-caption' => 'Table',
382
+ 'tip-caption' => 'Tip',
383
+ 'toc-placement' => 'auto',
384
+ 'toc-title' => 'Table of Contents',
385
+ 'untitled-label' => 'Untitled',
386
+ 'version-label' => 'Version',
387
+ 'warning-caption' => 'Warning',
388
+ }
363
389
 
364
- # attributes which be changed within the content of the document (but not
365
- # header) because it has semantic meaning; ex. sectnums
390
+ # attributes which be changed throughout the flow of the document (e.g., sectnums)
366
391
  FLEXIBLE_ATTRIBUTES = ['sectnums']
367
392
 
368
- # A collection of regular expressions used by the parser.
369
- #
370
- # NOTE The following pattern, which appears frequently, captures the
371
- # contents between square brackets, ignoring escaped closing brackets
372
- # (closing brackets prefixed with a backslash '\' character)
373
- #
374
- # Pattern: \[(|#{CC_ALL}*?[^\\])\]
375
- # Matches: [enclosed text] and [enclosed [text\]], not [enclosed text \\] or [\\] (as these require a trailing space)
376
- #
377
- # NOTE \w only matches ASCII word characters, whereas [[:word:]] or \p{Word} matches any character in the Unicode word category.
378
- #(pseudo)module Rx
379
-
380
- ## Regular expression character classes (to ensure regexp compatibility between Ruby and JavaScript)
381
- ## CC stands for "character class", CG stands for "character class group"
382
-
383
- unless RUBY_ENGINE == 'opal'
384
- # CC_ALL is any character, including newlines (must be accompanied by multiline regexp flag)
385
- CC_ALL = '.'
386
- # CC_ANY is any character except newlines
387
- CC_ANY = '.'
388
- CC_EOL = '$'
389
- CC_ALPHA = CG_ALPHA = '\p{Alpha}'
390
- CC_ALNUM = CG_ALNUM = '\p{Alnum}'
391
- CG_BLANK = '\p{Blank}'
392
- CC_WORD = CG_WORD = '\p{Word}'
393
- end
394
-
395
- ## Document header
396
-
397
- # Matches the author info line immediately following the document title.
398
- #
399
- # Examples
400
- #
401
- # Doc Writer <doc@example.com>
402
- # Mary_Sue Brontë
403
- #
404
- AuthorInfoLineRx = /^(#{CG_WORD}[#{CC_WORD}\-'.]*)(?: +(#{CG_WORD}[#{CC_WORD}\-'.]*))?(?: +(#{CG_WORD}[#{CC_WORD}\-'.]*))?(?: +<([^>]+)>)?$/
405
-
406
- # Matches the delimiter that separates multiple authors.
407
- #
408
- # Examples
409
- #
410
- # Doc Writer; Junior Writer
411
- #
412
- AuthorDelimiterRx = /;(?: |$)/
413
-
414
- # Matches the revision info line, which appears immediately following
415
- # the author info line beneath the document title.
416
- #
417
- # Examples
418
- #
419
- # v1.0
420
- # 2013-01-01
421
- # v1.0, 2013-01-01: Ring in the new year release
422
- # 1.0, Jan 01, 2013
423
- #
424
- RevisionInfoLineRx = /^(?:[^\d{]*(#{CC_ANY}*?),)? *(?!:)(#{CC_ANY}*?)(?: *(?!^),?: *(#{CC_ANY}*))?$/
425
-
426
- # Matches the title and volnum in the manpage doctype.
427
- #
428
- # Examples
429
- #
430
- # = asciidoctor(1)
431
- # = asciidoctor ( 1 )
432
- #
433
- ManpageTitleVolnumRx = /^(#{CC_ANY}+?) *\( *(#{CC_ANY}+?) *\)$/
434
-
435
- # Matches the name and purpose in the manpage doctype.
436
- #
437
- # Examples
438
- #
439
- # asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats
440
- #
441
- ManpageNamePurposeRx = /^(#{CC_ANY}+?) +- +(#{CC_ANY}+)$/
442
-
443
- ## Preprocessor directives
444
-
445
- # Matches a conditional preprocessor directive (e.g., ifdef, ifndef, ifeval and endif).
446
- #
447
- # Examples
448
- #
449
- # ifdef::basebackend-html[]
450
- # ifndef::theme[]
451
- # ifeval::["{asciidoctor-version}" >= "0.1.0"]
452
- # ifdef::asciidoctor[Asciidoctor!]
453
- # endif::theme[]
454
- # endif::basebackend-html[]
455
- # endif::[]
456
- #
457
- ConditionalDirectiveRx = /^(\\)?(ifdef|ifndef|ifeval|endif)::(\S*?(?:([,+])\S*?)?)\[(#{CC_ANY}+)?\]$/
458
-
459
- # Matches a restricted (read as safe) eval expression.
460
- #
461
- # Examples
462
- #
463
- # "{asciidoctor-version}" >= "0.1.0"
464
- #
465
- EvalExpressionRx = /^(#{CC_ANY}+?) *([=!><]=|[><]) *(#{CC_ANY}+)$/
466
-
467
- # Matches an include preprocessor directive.
468
- #
469
- # Examples
470
- #
471
- # include::chapter1.ad[]
472
- # include::example.txt[lines=1;2;5..10]
473
- #
474
- IncludeDirectiveRx = /^(\\)?include::([^\[][^\[]*)\[(#{CC_ANY}+)?\]$/
475
-
476
- # Matches a trailing tag directive in an include file.
477
- #
478
- # Examples
479
- #
480
- # // tag::try-catch[]
481
- # try {
482
- # someMethod();
483
- # catch (Exception e) {
484
- # log(e);
485
- # }
486
- # // end::try-catch[]
487
- # NOTE m flag is required for Asciidoctor.js
488
- TagDirectiveRx = /\b(?:tag|(e)nd)::(\S+?)\[\](?=$|[ \r])/m
489
-
490
- ## Attribute entries and references
491
-
492
- # Matches a document attribute entry.
493
- #
494
- # Examples
495
- #
496
- # :foo: bar
497
- # :First Name: Dan
498
- # :sectnums!:
499
- # :!toc:
500
- # :long-entry: Attribute value lines ending in ' \' \
501
- # are joined together as a single value, \
502
- # collapsing the line breaks and indentation to \
503
- # a single space.
504
- #
505
- AttributeEntryRx = /^:(!?#{CG_WORD}[^:]*):(?:[ \t]+(#{CC_ANY}*))?$/
506
-
507
- # Matches invalid characters in an attribute name.
508
- InvalidAttributeNameCharsRx = /[^-#{CC_WORD}]/
509
-
510
- # Matches a pass inline macro that surrounds the value of an attribute
511
- # entry once it has been parsed.
512
- #
513
- # Examples
514
- #
515
- # pass:[text]
516
- # pass:a[{a} {b} {c}]
517
- #
518
- if RUBY_ENGINE == 'opal'
519
- # NOTE In JavaScript, ^ and $ match the boundaries of the string when the m flag is not set
520
- AttributeEntryPassMacroRx = /^pass:([a-z]+(?:,[a-z-]+)*)?\[(#{CC_ALL}*)\]$/
521
- else
522
- AttributeEntryPassMacroRx = /\Apass:([a-z]+(?:,[a-z-]+)*)?\[(.*)\]\Z/m
523
- end
524
-
525
- # Matches an inline attribute reference.
526
- #
527
- # Examples
528
- #
529
- # {foobar} or {app_name} or {product-version}
530
- # {counter:sequence-name:1}
531
- # {set:foo:bar}
532
- # {set:name!}
533
- #
534
- AttributeReferenceRx = /(\\)?\{(#{CG_WORD}[-#{CC_WORD}]*|(set|counter2?):#{CC_ANY}+?)(\\)?\}/
535
-
536
- ## Paragraphs and delimited blocks
537
-
538
- # Matches an anchor (i.e., id + optional reference text) on a line above a block.
539
- #
540
- # Examples
541
- #
542
- # [[idname]]
543
- # [[idname,Reference Text]]
544
- #
545
- BlockAnchorRx = /^\[\[(?:|([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)(?:, *(#{CC_ANY}+))?)\]\]$/
546
-
547
- # Matches an attribute list above a block element.
548
- #
549
- # Examples
550
- #
551
- # # strictly positional
552
- # [quote, Adam Smith, Wealth of Nations]
553
- #
554
- # # name/value pairs
555
- # [NOTE, caption="Good to know"]
556
- #
557
- # # as attribute reference
558
- # [{lead}]
559
- #
560
- BlockAttributeListRx = /^\[(|[#{CC_WORD}.#%{,"']#{CC_ANY}*)\]$/
561
-
562
- # A combined pattern that matches either a block anchor or a block attribute list.
563
- #
564
- # TODO this one gets hit a lot, should be optimized as much as possible
565
- BlockAttributeLineRx = /^\[(?:|[#{CC_WORD}.#%{,"']#{CC_ANY}*|\[(?:|[#{CC_ALPHA}_:][#{CC_WORD}:.-]*(?:, *#{CC_ANY}+)?)\])\]$/
566
-
567
- # Matches a title above a block.
568
- #
569
- # Examples
570
- #
571
- # .Title goes here
572
- #
573
- BlockTitleRx = /^\.(\.?[^ \t.]#{CC_ANY}*)$/
574
-
575
- # Matches an admonition label at the start of a paragraph.
576
- #
577
- # Examples
578
- #
579
- # NOTE: Just a little note.
580
- # TIP: Don't forget!
581
- #
582
- AdmonitionParagraphRx = /^(#{ADMONITION_STYLES.to_a.join '|'}):[ \t]+/
583
-
584
- # Matches a literal paragraph, which is a line of text preceded by at least one space.
585
- #
586
- # Examples
587
- #
588
- # <SPACE>Foo
589
- # <TAB>Foo
590
- LiteralParagraphRx = /^([ \t]+#{CC_ANY}*)$/
591
-
592
- # Matches a comment block.
593
- #
594
- # Examples
595
- #
596
- # ////
597
- # This is a block comment.
598
- # It can span one or more lines.
599
- # ////
600
- #CommentBlockRx = %r(^/{4,}$)
601
-
602
- # Matches a comment line.
603
- #
604
- # Examples
605
- #
606
- # // note to author
607
- #
608
- #CommentLineRx = %r(^//(?=[^/]|$))
609
-
610
- ## Section titles
611
-
612
- # Matches an Atx (single-line) section title.
613
- #
614
- # Examples
615
- #
616
- # == Foo
617
- # // ^ a level 1 (h2) section title
618
- #
619
- # == Foo ==
620
- # // ^ also a level 1 (h2) section title
621
- #
622
- AtxSectionTitleRx = /^(=={0,5})[ \t]+(#{CC_ANY}+?)(?:[ \t]+\1)?$/
623
-
624
- # Matches an extended Atx section title that includes support for the Markdown variant.
625
- ExtAtxSectionTitleRx = /^(=={0,5}|#\#{0,5})[ \t]+(#{CC_ANY}+?)(?:[ \t]+\1)?$/
626
-
627
- # Matches the title only (first line) of an Setext (two-line) section title.
628
- # The title cannot begin with a dot and must have at least one alphanumeric character.
629
- SetextSectionTitleRx = /^((?!\.)#{CC_ANY}*?#{CG_ALNUM}#{CC_ANY}*)$/
630
-
631
- # Matches an anchor (i.e., id + optional reference text) inside a section title.
632
- #
633
- # Examples
634
- #
635
- # Section Title [[idname]]
636
- # Section Title [[idname,Reference Text]]
637
- #
638
- InlineSectionAnchorRx = / (\\)?\[\[([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)(?:, *(#{CC_ANY}+))?\]\]$/
639
-
640
- # Matches invalid ID characters in a section title.
641
- #
642
- # NOTE uppercase chars not included since expression is only run on a lowercase string
643
- InvalidSectionIdCharsRx = /<[^>]+>|&(?:[a-z][a-z]+\d{0,2}|#\d\d\d{0,4}|#x[\da-f][\da-f][\da-f]{0,3});|[^ #{CC_WORD}\-.]+?/
644
-
645
- # Matches an explicit section level style like sect1
646
- #
647
- SectionLevelStyleRx = /^sect\d$/
648
-
649
- ## Lists
650
-
651
- # Detects the start of any list item.
652
- #
653
- # NOTE we only have to check as far as the blank character because we know it means non-whitespace follows.
654
- # IMPORTANT if this regexp does not agree with the regexp for each list type, the parser will hang.
655
- AnyListRx = %r(^(?:[ \t]*(?:-|\*\**|\.\.*|\u2022|\d+\.|[a-zA-Z]\.|[IVXivx]+\))[ \t]|(?!//[^/])[ \t]*[^ \t]#{CC_ANY}*?(?::::{0,2}|;;)(?:$|[ \t])|<?\d+>[ \t]))
656
-
657
- # Matches an unordered list item (one level for hyphens, up to 5 levels for asterisks).
658
- #
659
- # Examples
660
- #
661
- # * Foo
662
- # - Foo
663
- #
664
- # NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
665
- UnorderedListRx = /^[ \t]*(-|\*\**|\u2022)[ \t]+(#{CC_ANY}*)$/
666
-
667
- # Matches an ordered list item (explicit numbering or up to 5 consecutive dots).
668
- #
669
- # Examples
670
- #
671
- # . Foo
672
- # .. Foo
673
- # 1. Foo (arabic, default)
674
- # a. Foo (loweralpha)
675
- # A. Foo (upperalpha)
676
- # i. Foo (lowerroman)
677
- # I. Foo (upperroman)
678
- #
679
- # NOTE leading space match is not always necessary, but is used for list reader
680
- # NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
681
- OrderedListRx = /^[ \t]*(\.\.*|\d+\.|[a-zA-Z]\.|[IVXivx]+\))[ \t]+(#{CC_ANY}*)$/
682
-
683
- # Matches the ordinals for each type of ordered list.
684
- OrderedListMarkerRxMap = {
685
- arabic: /\d+\./,
686
- loweralpha: /[a-z]\./,
687
- lowerroman: /[ivx]+\)/,
688
- upperalpha: /[A-Z]\./,
689
- upperroman: /[IVX]+\)/,
690
- #lowergreek: /[a-z]\]/,
691
- }
692
-
693
- # Matches a description list entry.
694
- #
695
- # Examples
696
- #
697
- # foo::
698
- # bar:::
699
- # baz::::
700
- # blah;;
701
- #
702
- # # the term may be followed by a description on the same line...
703
- #
704
- # foo:: The metasyntactic variable that commonly accompanies 'bar' (see also, <<bar>>).
705
- #
706
- # # ...or on a separate line, which may optionally be indented
707
- #
708
- # foo::
709
- # The metasyntactic variable that commonly accompanies 'bar' (see also, <<bar>>).
710
- #
711
- # # attribute references may be used in both the term and the description
712
- #
713
- # {foo-term}:: {foo-desc}
714
- #
715
- # NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
716
- # NOTE must skip line comment when looking for next list item inside list
717
- DescriptionListRx = %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?)(:::{0,2}|;;)(?:$|[ \t]+(#{CC_ANY}*)$))
718
-
719
- # Matches a sibling description list item (excluding the delimiter specified by the key).
720
- # NOTE must skip line comment when looking for sibling list item
721
- DescriptionListSiblingRx = {
722
- '::' => %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?[^:]|[^ \t:])(::)(?:$|[ \t]+(#{CC_ANY}*)$)),
723
- ':::' => %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?[^:]|[^ \t:])(:::)(?:$|[ \t]+(#{CC_ANY}*)$)),
724
- '::::' => %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?[^:]|[^ \t:])(::::)(?:$|[ \t]+(#{CC_ANY}*)$)),
725
- ';;' => %r(^(?!//[^/])[ \t]*([^ \t]#{CC_ANY}*?)(;;)(?:$|[ \t]+(#{CC_ANY}*)$))
726
- }
727
-
728
- # Matches a callout list item.
729
- #
730
- # Examples
731
- #
732
- # <1> Explanation
733
- #
734
- # or
735
- #
736
- # <.> Explanation with automatic number
737
- #
738
- # NOTE we know trailing (.*) will match at least one character because we strip trailing spaces
739
- CalloutListRx = /^<(\d+|\.)>[ \t]+(#{CC_ANY}*)$/
740
-
741
- # Matches a callout reference inside literal text.
742
- #
743
- # Examples
744
- # <1> (optionally prefixed by //, #, -- or ;; line comment chars)
745
- # <1> <2> (multiple callouts on one line)
746
- # <!--1--> (for XML-based languages)
747
- # <.> (auto-numbered)
748
- #
749
- # NOTE extract regexps are applied line-by-line, so we can use $ as end-of-line char
750
- CalloutExtractRx = %r(((?://|#|--|;;) ?)?(\\)?<!?(|--)(\d+|\.)\3>(?=(?: ?\\?<!?\3(?:\d+|\.)\3>)*$))
751
- CalloutExtractRxt = '(\\\\)?<()(\\d+|\\.)>(?=(?: ?\\\\?<(?:\\d+|\\.)>)*$)'
752
- CalloutExtractRxMap = ::Hash.new {|h, k| h[k] = /(#{k.empty? ? '' : "#{::Regexp.escape k} ?"})?#{CalloutExtractRxt}/ }
753
- # NOTE special characters have not been replaced when scanning
754
- CalloutScanRx = /\\?<!?(|--)(\d+|\.)\1>(?=(?: ?\\?<!?\1(?:\d+|\.)\1>)*#{CC_EOL})/
755
- # NOTE special characters have already been replaced when converting to an SGML format
756
- CalloutSourceRx = %r(((?://|#|--|;;) ?)?(\\)?&lt;!?(|--)(\d+|\.)\3&gt;(?=(?: ?\\?&lt;!?\3(?:\d+|\.)\3&gt;)*#{CC_EOL}))
757
- CalloutSourceRxt = "(\\\\)?&lt;()(\\d+|\\.)&gt;(?=(?: ?\\\\?&lt;(?:\\d+|\\.)&gt;)*#{CC_EOL})"
758
- CalloutSourceRxMap = ::Hash.new {|h, k| h[k] = /(#{k.empty? ? '' : "#{::Regexp.escape k} ?"})?#{CalloutSourceRxt}/ }
759
-
760
- # A Hash of regexps for lists used for dynamic access.
761
- ListRxMap = {
762
- ulist: UnorderedListRx,
763
- olist: OrderedListRx,
764
- dlist: DescriptionListRx,
765
- colist: CalloutListRx,
766
- }
767
-
768
- ## Tables
769
-
770
- # Parses the column spec (i.e., colspec) for a table.
771
- #
772
- # Examples
773
- #
774
- # 1*h,2*,^3e
775
- #
776
- ColumnSpecRx = /^(?:(\d+)\*)?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?(\d+%?|~)?([a-z])?$/
777
-
778
- # Parses the start and end of a cell spec (i.e., cellspec) for a table.
779
- #
780
- # Examples
781
- #
782
- # 2.3+<.>m
783
- #
784
- # FIXME use step-wise scan (or treetop) rather than this mega-regexp
785
- CellSpecStartRx = /^[ \t]*(?:(\d+(?:\.\d*)?|(?:\d*\.)?\d+)([*+]))?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?([a-z])?$/
786
- CellSpecEndRx = /[ \t]+(?:(\d+(?:\.\d*)?|(?:\d*\.)?\d+)([*+]))?([<^>](?:\.[<^>]?)?|(?:[<^>]?\.)?[<^>])?([a-z])?$/
787
-
788
- # Block macros
789
-
790
- # Matches the custom block macro pattern.
791
- #
792
- # Examples
793
- #
794
- # gist::123456[]
795
- #
796
- #--
797
- # NOTE we've relaxed the match for target to accomodate the short format (e.g., name::[attrlist])
798
- CustomBlockMacroRx = /^(#{CG_WORD}[-#{CC_WORD}]*)::(|\S|\S#{CC_ANY}*?\S)\[(#{CC_ANY}+)?\]$/
799
-
800
- # Matches an image, video or audio block macro.
801
- #
802
- # Examples
803
- #
804
- # image::filename.png[Caption]
805
- # video::http://youtube.com/12345[Cats vs Dogs]
806
- #
807
- BlockMediaMacroRx = /^(image|video|audio)::(\S|\S#{CC_ANY}*?\S)\[(#{CC_ANY}+)?\]$/
808
-
809
- # Matches the TOC block macro.
810
- #
811
- # Examples
812
- #
813
- # toc::[]
814
- # toc::[levels=2]
815
- #
816
- BlockTocMacroRx = /^toc::\[(#{CC_ANY}+)?\]$/
817
-
818
- ## Inline macros
819
-
820
- # Matches an anchor (i.e., id + optional reference text) in the flow of text.
821
- #
822
- # Examples
823
- #
824
- # [[idname]]
825
- # [[idname,Reference Text]]
826
- # anchor:idname[]
827
- # anchor:idname[Reference Text]
828
- #
829
- InlineAnchorRx = /(\\)?(?:\[\[([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)(?:, *(#{CC_ANY}+?))?\]\]|anchor:([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)\[(?:\]|(#{CC_ANY}*?[^\\])\]))/
830
-
831
- # Scans for a non-escaped anchor (i.e., id + optional reference text) in the flow of text.
832
- InlineAnchorScanRx = /(?:^|[^\\\[])\[\[([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)(?:, *(#{CC_ANY}+?))?\]\]|(?:^|[^\\])anchor:([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)\[(?:\]|(#{CC_ANY}*?[^\\])\])/
833
-
834
- # Scans for a leading, non-escaped anchor (i.e., id + optional reference text).
835
- LeadingInlineAnchorRx = /^\[\[([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)(?:, *(#{CC_ANY}+?))?\]\]/
836
-
837
- # Matches a bibliography anchor at the start of the list item text (in a bibliography list).
838
- #
839
- # Examples
840
- #
841
- # [[[Fowler_1997]]] Fowler M. ...
842
- #
843
- InlineBiblioAnchorRx = /^\[\[\[([#{CC_ALPHA}_:][#{CC_WORD}:.-]*)(?:, *(#{CC_ANY}+?))?\]\]\]/
844
-
845
- # Matches an inline e-mail address.
846
- #
847
- # doc.writer@example.com
848
- #
849
- InlineEmailRx = %r(([\\>:/])?#{CG_WORD}(?:&amp;|[#{CC_WORD}.%+-])*@#{CG_ALNUM}[#{CC_ALNUM}_.-]*\.[a-zA-Z]{2,5}\b)
850
-
851
- # Matches an inline footnote macro, which is allowed to span multiple lines.
852
- #
853
- # Examples
854
- # footnote:[text] (not referenceable)
855
- # footnote:id[text] (referenceable)
856
- # footnote:id[] (reference)
857
- # footnoteref:[id,text] (legacy)
858
- # footnoteref:[id] (legacy)
859
- #
860
- InlineFootnoteMacroRx = /\\?footnote(?:(ref):|:([\w-]+)?)\[(?:|(#{CC_ALL}*?[^\\]))\]/m
861
-
862
- # Matches an image or icon inline macro.
863
- #
864
- # Examples
865
- #
866
- # image:filename.png[Alt Text]
867
- # image:http://example.com/images/filename.png[Alt Text]
868
- # image:filename.png[More [Alt\] Text] (alt text becomes "More [Alt] Text")
869
- # icon:github[large]
870
- #
871
- # NOTE be as non-greedy as possible by not allowing newline or left square bracket in target
872
- InlineImageMacroRx = /\\?i(?:mage|con):([^:\s\[](?:[^\n\[]*[^\s\[])?)\[(|#{CC_ALL}*?[^\\])\]/m
873
-
874
- # Matches an indexterm inline macro, which may span multiple lines.
875
- #
876
- # Examples
877
- #
878
- # indexterm:[Tigers,Big cats]
879
- # (((Tigers,Big cats)))
880
- # indexterm2:[Tigers]
881
- # ((Tigers))
882
- #
883
- InlineIndextermMacroRx = /\\?(?:(indexterm2?):\[(#{CC_ALL}*?[^\\])\]|\(\((#{CC_ALL}+?)\)\)(?!\)))/m
884
-
885
- # Matches either the kbd or btn inline macro.
886
- #
887
- # Examples
888
- #
889
- # kbd:[F3]
890
- # kbd:[Ctrl+Shift+T]
891
- # kbd:[Ctrl+\]]
892
- # kbd:[Ctrl,T]
893
- # btn:[Save]
894
- #
895
- InlineKbdBtnMacroRx = /(\\)?(kbd|btn):\[(#{CC_ALL}*?[^\\])\]/m
896
-
897
- # Matches an implicit link and some of the link inline macro.
898
- #
899
- # Examples
900
- #
901
- # https://github.com
902
- # https://github.com[GitHub]
903
- # <https://github.com>
904
- # link:https://github.com[]
905
- #
906
- # FIXME revisit! the main issue is we need different rules for implicit vs explicit
907
- InlineLinkRx = %r((^|link:|#{CG_BLANK}|&lt;|[>\(\)\[\];])(\\?(?:https?|file|ftp|irc)://[^\s\[\]<]*([^\s.,\[\]<]))(?:\[(|#{CC_ALL}*?[^\\])\])?)m
908
-
909
- # Match a link or e-mail inline macro.
910
- #
911
- # Examples
912
- #
913
- # link:path[label]
914
- # mailto:doc.writer@example.com[]
915
- #
916
- # NOTE be as non-greedy as possible by not allowing space or left square bracket in target
917
- InlineLinkMacroRx = /\\?(?:link|(mailto)):(|[^:\s\[][^\s\[]*)\[(|#{CC_ALL}*?[^\\])\]/m
918
-
919
- # Matches the name of a macro.
920
- #
921
- MacroNameRx = /^#{CG_WORD}[-#{CC_WORD}]*$/
922
-
923
- # Matches a stem (and alternatives, asciimath and latexmath) inline macro, which may span multiple lines.
924
- #
925
- # Examples
926
- #
927
- # stem:[x != 0]
928
- # asciimath:[x != 0]
929
- # latexmath:[\sqrt{4} = 2]
930
- #
931
- InlineStemMacroRx = /\\?(stem|(?:latex|ascii)math):([a-z]+(?:,[a-z-]+)*)?\[(#{CC_ALL}*?[^\\])\]/m
932
-
933
- # Matches a menu inline macro.
934
- #
935
- # Examples
936
- #
937
- # menu:File[Save As...]
938
- # menu:View[Page Style > No Style]
939
- # menu:View[Page Style, No Style]
940
- #
941
- InlineMenuMacroRx = /\\?menu:(#{CG_WORD}|[#{CC_WORD}&][^\n\[]*[^\s\[])\[ *(#{CC_ALL}*?[^\\])?\]/m
942
-
943
- # Matches an implicit menu inline macro.
944
- #
945
- # Examples
946
- #
947
- # "File > New..."
948
- #
949
- InlineMenuRx = /\\?"([#{CC_WORD}&][^"]*?[ \n]+&gt;[ \n]+[^"]*)"/
950
-
951
- # Matches an inline passthrough, which may span multiple lines.
952
- #
953
- # Examples
954
- #
955
- # +text+
956
- # `text` (compat)
957
- #
958
- # NOTE we always capture the attributes so we know when to use compatible (i.e., legacy) behavior
959
- InlinePassRx = {
960
- false => ['+', '`', /(^|[^#{CC_WORD};:])(?:\[([^\]]+)\])?(\\?(\+|`)(\S|\S#{CC_ALL}*?\S)\4)(?!#{CG_WORD})/m],
961
- true => ['`', nil, /(^|[^`#{CC_WORD}])(?:\[([^\]]+)\])?(\\?(`)([^`\s]|[^`\s]#{CC_ALL}*?\S)\4)(?![`#{CC_WORD}])/m]
962
- }
963
-
964
- # Matches an inline plus passthrough spanning multiple lines, but only when it occurs directly
965
- # inside constrained monospaced formatting in non-compat mode.
966
- #
967
- # Examples
968
- #
969
- # +text+
970
- #
971
- SinglePlusInlinePassRx = /^(\\)?\+(\S|\S#{CC_ALL}*?\S)\+$/m
972
-
973
- # Matches several variants of the passthrough inline macro, which may span multiple lines.
974
- #
975
- # Examples
976
- #
977
- # +++text+++
978
- # $$text$$
979
- # pass:quotes[text]
980
- #
981
- # NOTE we have to support an empty pass:[] for compatibility with AsciiDoc Python
982
- InlinePassMacroRx = /(?:(?:(\\?)\[([^\]]+)\])?(\\{0,2})(\+\+\+?|\$\$)(#{CC_ALL}*?)\4|(\\?)pass:([a-z]+(?:,[a-z-]+)*)?\[(|#{CC_ALL}*?[^\\])\])/m
983
-
984
- # Matches an xref (i.e., cross-reference) inline macro, which may span multiple lines.
985
- #
986
- # Examples
987
- #
988
- # <<id,reftext>>
989
- # xref:id[reftext]
990
- #
991
- # NOTE special characters have already been escaped, hence the entity references
992
- # NOTE { is included in start characters to support target that begins with attribute reference in title content
993
- InlineXrefMacroRx = %r(\\?(?:&lt;&lt;([#{CC_WORD}#/.:{]#{CC_ALL}*?)&gt;&gt;|xref:([#{CC_WORD}#/.:{]#{CC_ALL}*?)\[(?:\]|(#{CC_ALL}*?[^\\])\])))m
994
-
995
- ## Layout
996
-
997
- # Matches a trailing + preceded by at least one space character,
998
- # which forces a hard line break (<br> tag in HTML output).
999
- #
1000
- # NOTE AsciiDoc Python allows + to be preceded by TAB; Asciidoctor does not
1001
- #
1002
- # Examples
1003
- #
1004
- # Humpty Dumpty sat on a wall, +
1005
- # Humpty Dumpty had a great fall.
1006
- #
1007
- if RUBY_ENGINE == 'opal'
1008
- # NOTE In JavaScript, ^ and $ only match the start and end of line if the multiline flag is present
1009
- HardLineBreakRx = /^(#{CC_ANY}*) \+$/m
1010
- else
1011
- # NOTE In Ruby, ^ and $ always match start and end of line
1012
- HardLineBreakRx = /^(.*) \+$/
1013
- end
1014
-
1015
- # Matches a Markdown horizontal rule.
1016
- #
1017
- # Examples
1018
- #
1019
- # --- or - - -
1020
- # *** or * * *
1021
- # ___ or _ _ _
1022
- #
1023
- MarkdownThematicBreakRx = /^ {0,3}([-*_])( *)\1\2\1$/
1024
-
1025
- # Matches an AsciiDoc or Markdown horizontal rule or AsciiDoc page break.
1026
- #
1027
- # Examples
1028
- #
1029
- # ''' (horizontal rule)
1030
- # <<< (page break)
1031
- # --- or - - - (horizontal rule, Markdown)
1032
- # *** or * * * (horizontal rule, Markdown)
1033
- # ___ or _ _ _ (horizontal rule, Markdown)
1034
- #
1035
- ExtLayoutBreakRx = /^(?:'{3,}|<{3,}|([-*_])( *)\1\2\1)$/
1036
-
1037
- ## General
1038
-
1039
- # Matches consecutive blank lines.
1040
- #
1041
- # Examples
1042
- #
1043
- # one
1044
- #
1045
- # two
1046
- #
1047
- BlankLineRx = /\n{2,}/
1048
-
1049
- # Matches a comma or semi-colon delimiter.
1050
- #
1051
- # Examples
1052
- #
1053
- # one,two
1054
- # three;four
1055
- #
1056
- #DataDelimiterRx = /[,;]/
1057
-
1058
- # Matches whitespace (space, tab, newline) escaped by a backslash.
1059
- #
1060
- # Examples
1061
- #
1062
- # three\ blind\ mice
1063
- #
1064
- EscapedSpaceRx = /\\([ \t\n])/
1065
-
1066
- # Detects if text is a possible candidate for the replacements substitution.
1067
- #
1068
- ReplaceableTextRx = /[&']|--|\.\.\.|\([CRT]M?\)/
1069
-
1070
- # Matches a whitespace delimiter, a sequence of spaces, tabs, and/or newlines.
1071
- # Matches the parsing rules of %w strings in Ruby.
1072
- #
1073
- # Examples
1074
- #
1075
- # one two three four
1076
- # five six
1077
- #
1078
- # TODO change to /(?<!\\)[ \t\n]+/ once lookbehind assertions are implemented in all modern browsers
1079
- SpaceDelimiterRx = /([^\\])[ \t\n]+/
1080
-
1081
- # Matches a + or - modifier in a subs list
1082
- #
1083
- SubModifierSniffRx = /[+-]/
1084
-
1085
- # Matches one or more consecutive digits at the end of a line.
1086
- #
1087
- # Examples
1088
- #
1089
- # docbook5
1090
- # html5
1091
- #
1092
- TrailingDigitsRx = /\d+$/
1093
-
1094
- # Detects strings that resemble URIs.
1095
- #
1096
- # Examples
1097
- # http://domain
1098
- # https://domain
1099
- # file:///path
1100
- # data:info
1101
- #
1102
- # not c:/sample.adoc or c:\sample.adoc
1103
- #
1104
- UriSniffRx = %r(^#{CG_ALPHA}[#{CC_ALNUM}.+-]+:/{0,2})
1105
-
1106
- # Detects XML tags
1107
- XmlSanitizeRx = /<[^>]+>/
1108
- #end
1109
-
1110
393
  INTRINSIC_ATTRIBUTES = {
1111
- 'startsb' => '[',
1112
- 'endsb' => ']',
1113
- 'vbar' => '|',
1114
- 'caret' => '^',
1115
- 'asterisk' => '*',
1116
- 'tilde' => '~',
1117
- 'plus' => '&#43;',
1118
- 'backslash' => '\\',
1119
- 'backtick' => '`',
1120
- 'blank' => '',
1121
- 'empty' => '',
1122
- 'sp' => ' ',
394
+ 'startsb' => '[',
395
+ 'endsb' => ']',
396
+ 'vbar' => '|',
397
+ 'caret' => '^',
398
+ 'asterisk' => '*',
399
+ 'tilde' => '~',
400
+ 'plus' => '&#43;',
401
+ 'backslash' => '\\',
402
+ 'backtick' => '`',
403
+ 'blank' => '',
404
+ 'empty' => '',
405
+ 'sp' => ' ',
1123
406
  'two-colons' => '::',
1124
407
  'two-semicolons' => ';;',
1125
- 'nbsp' => '&#160;',
1126
- 'deg' => '&#176;',
1127
- 'zwsp' => '&#8203;',
1128
- 'quot' => '&#34;',
1129
- 'apos' => '&#39;',
1130
- 'lsquo' => '&#8216;',
1131
- 'rsquo' => '&#8217;',
1132
- 'ldquo' => '&#8220;',
1133
- 'rdquo' => '&#8221;',
1134
- 'wj' => '&#8288;',
1135
- 'brvbar' => '&#166;',
1136
- 'pp' => '&#43;&#43;',
1137
- 'cpp' => 'C&#43;&#43;',
1138
- 'amp' => '&',
1139
- 'lt' => '<',
1140
- 'gt' => '>'
408
+ 'nbsp' => '&#160;',
409
+ 'deg' => '&#176;',
410
+ 'zwsp' => '&#8203;',
411
+ 'quot' => '&#34;',
412
+ 'apos' => '&#39;',
413
+ 'lsquo' => '&#8216;',
414
+ 'rsquo' => '&#8217;',
415
+ 'ldquo' => '&#8220;',
416
+ 'rdquo' => '&#8221;',
417
+ 'wj' => '&#8288;',
418
+ 'brvbar' => '&#166;',
419
+ 'pp' => '&#43;&#43;',
420
+ 'cpp' => 'C&#43;&#43;',
421
+ 'amp' => '&',
422
+ 'lt' => '<',
423
+ 'gt' => '>'
1141
424
  }
1142
425
 
426
+ # Regular expression character classes (to ensure regexp compatibility between Ruby and JavaScript)
427
+ # CC stands for "character class", CG stands for "character class group"
428
+ unless RUBY_ENGINE == 'opal'
429
+ # CC_ALL is any character, including newlines (must be accompanied by multiline regexp flag)
430
+ CC_ALL = '.'
431
+ # CC_ANY is any character except newlines
432
+ CC_ANY = '.'
433
+ CC_EOL = '$'
434
+ CC_ALPHA = CG_ALPHA = '\p{Alpha}'
435
+ CC_ALNUM = CG_ALNUM = '\p{Alnum}'
436
+ CG_BLANK = '\p{Blank}'
437
+ CC_WORD = CG_WORD = '\p{Word}'
438
+ end
439
+
1143
440
  QUOTE_SUBS = {}.tap do |accum|
1144
441
  # unconstrained quotes:: can appear anywhere
1145
442
  # constrained quotes:: must be bordered by non-word characters
@@ -1198,8 +495,8 @@ module Asciidoctor
1198
495
  # (TM)
1199
496
  [/\\?\(TM\)/, '&#8482;', :none],
1200
497
  # foo -- bar (where either space character can be a newline)
1201
- # NOTE this necessarily drops the newline if it appears at end of line
1202
- [/(^|\n| |\\)--( |\n|$)/, '&#8201;&#8212;&#8201;', :none],
498
+ # NOTE this necessarily drops the newline if replacement appears at end of line
499
+ [/(?: |\n|^|\\)--(?: |\n|$)/, '&#8201;&#8212;&#8201;', :none],
1203
500
  # foo--bar
1204
501
  [/(#{CG_WORD})\\?--(?=#{CG_WORD})/, '&#8212;&#8203;', :leading],
1205
502
  # ellipsis
@@ -1220,310 +517,6 @@ module Asciidoctor
1220
517
  [/\\?(&)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]
1221
518
  ]
1222
519
 
1223
- class << self
1224
-
1225
- # Public: Parse the AsciiDoc source input into a {Document}
1226
- #
1227
- # Accepts input as an IO (or StringIO), String or String Array object. If the
1228
- # input is a File, the object is expected to be opened for reading and is not
1229
- # closed afterwards by this method. Information about the file (filename,
1230
- # directory name, etc) gets assigned to attributes on the Document object.
1231
- #
1232
- # input - the AsciiDoc source as a IO, String or Array.
1233
- # options - a String, Array or Hash of options to control processing (default: {})
1234
- # String and Array values are converted into a Hash.
1235
- # See {Document#initialize} for details about these options.
1236
- #
1237
- # Returns the Document
1238
- def load input, options = {}
1239
- options = options.merge
1240
-
1241
- if (timings = options[:timings])
1242
- timings.start :read
1243
- end
1244
-
1245
- if (logger = options[:logger]) && logger != LoggerManager.logger
1246
- LoggerManager.logger = logger
1247
- end
1248
-
1249
- if !(attrs = options[:attributes])
1250
- attrs = {}
1251
- elsif ::Hash === attrs
1252
- attrs = attrs.merge
1253
- elsif (defined? ::Java::JavaUtil::Map) && ::Java::JavaUtil::Map === attrs
1254
- attrs = attrs.dup
1255
- elsif ::Array === attrs
1256
- attrs = {}.tap do |accum|
1257
- attrs.each do |entry|
1258
- k, _, v = entry.partition '='
1259
- accum[k] = v
1260
- end
1261
- end
1262
- elsif ::String === attrs
1263
- # condense and convert non-escaped spaces to null, unescape escaped spaces, then split on null
1264
- attrs = {}.tap do |accum|
1265
- attrs.gsub(SpaceDelimiterRx, '\1' + NULL).gsub(EscapedSpaceRx, '\1').split(NULL).each do |entry|
1266
- k, _, v = entry.partition '='
1267
- accum[k] = v
1268
- end
1269
- end
1270
- elsif (attrs.respond_to? :keys) && (attrs.respond_to? :[])
1271
- # coerce attrs to a real Hash
1272
- attrs = {}.tap {|accum| attrs.keys.each {|k| accum[k] = attrs[k] } }
1273
- else
1274
- raise ::ArgumentError, %(illegal type for attributes option: #{attrs.class.ancestors.join ' < '})
1275
- end
1276
-
1277
- if ::File === input
1278
- options[:input_mtime] = input.mtime
1279
- # NOTE defer setting infile and indir until we get a better sense of their purpose
1280
- # TODO cli checks if input path can be read and is file, but might want to add check to API too
1281
- attrs['docfile'] = input_path = ::File.absolute_path input.path
1282
- attrs['docdir'] = ::File.dirname input_path
1283
- attrs['docname'] = Helpers.basename input_path, (attrs['docfilesuffix'] = Helpers.extname input_path)
1284
- source = input.read
1285
- elsif input.respond_to? :read
1286
- # NOTE tty, pipes & sockets can't be rewound, but can't be sniffed easily either
1287
- # just fail the rewind operation silently to handle all cases
1288
- input.rewind rescue nil
1289
- source = input.read
1290
- elsif ::String === input
1291
- source = input
1292
- elsif ::Array === input
1293
- source = input.drop 0
1294
- elsif input
1295
- raise ::ArgumentError, %(unsupported input type: #{input.class})
1296
- end
1297
-
1298
- if timings
1299
- timings.record :read
1300
- timings.start :parse
1301
- end
1302
-
1303
- options[:attributes] = attrs
1304
- doc = options[:parse] == false ? (Document.new source, options) : (Document.new source, options).parse
1305
-
1306
- timings.record :parse if timings
1307
- doc
1308
- rescue => ex
1309
- begin
1310
- context = %(asciidoctor: FAILED: #{attrs['docfile'] || '<stdin>'}: Failed to load AsciiDoc document)
1311
- if ex.respond_to? :exception
1312
- # The original message must be explicitly preserved when wrapping a Ruby exception
1313
- wrapped_ex = ex.exception %(#{context} - #{ex.message})
1314
- # JRuby automatically sets backtrace; MRI did not until 2.6
1315
- wrapped_ex.set_backtrace ex.backtrace
1316
- else
1317
- # Likely a Java exception class
1318
- wrapped_ex = ex.class.new context, ex
1319
- wrapped_ex.stack_trace = ex.stack_trace
1320
- end
1321
- rescue
1322
- wrapped_ex = ex
1323
- end
1324
- raise wrapped_ex
1325
- end
1326
-
1327
- # Public: Parse the contents of the AsciiDoc source file into an Asciidoctor::Document
1328
- #
1329
- # input - the String AsciiDoc source filename
1330
- # options - a String, Array or Hash of options to control processing (default: {})
1331
- # String and Array values are converted into a Hash.
1332
- # See Asciidoctor::Document#initialize for details about options.
1333
- #
1334
- # Returns the Asciidoctor::Document
1335
- def load_file filename, options = {}
1336
- ::File.open(filename, FILE_READ_MODE) {|file| self.load file, options }
1337
- end
1338
-
1339
- # Public: Parse the AsciiDoc source input into an Asciidoctor::Document and
1340
- # convert it to the specified backend format.
1341
- #
1342
- # Accepts input as an IO (or StringIO), String or String Array object. If the
1343
- # input is a File, the object is expected to be opened for reading and is not
1344
- # closed afterwards by this method. Information about the file (filename,
1345
- # directory name, etc) gets assigned to attributes on the Document object.
1346
- #
1347
- # If the :to_file option is true, and the input is a File, the output is
1348
- # written to a file adjacent to the input file, having an extension that
1349
- # corresponds to the backend format. Otherwise, if the :to_file option is
1350
- # specified, the file is written to that file. If :to_file is not an absolute
1351
- # path, it is resolved relative to :to_dir, if given, otherwise the
1352
- # Document#base_dir. If the target directory does not exist, it will not be
1353
- # created unless the :mkdirs option is set to true. If the file cannot be
1354
- # written because the target directory does not exist, or because it falls
1355
- # outside of the Document#base_dir in safe mode, an IOError is raised.
1356
- #
1357
- # If the output is going to be written to a file, the header and footer are
1358
- # included unless specified otherwise (writing to a file implies creating a
1359
- # standalone document). Otherwise, the header and footer are not included by
1360
- # default and the converted result is returned.
1361
- #
1362
- # input - the String AsciiDoc source filename
1363
- # options - a String, Array or Hash of options to control processing (default: {})
1364
- # String and Array values are converted into a Hash.
1365
- # See Asciidoctor::Document#initialize for details about options.
1366
- #
1367
- # Returns the Document object if the converted String is written to a
1368
- # file, otherwise the converted String
1369
- def convert input, options = {}
1370
- (options = options.merge).delete :parse
1371
- to_dir = options.delete :to_dir
1372
- mkdirs = options.delete :mkdirs
1373
-
1374
- case (to_file = options.delete :to_file)
1375
- when true, nil
1376
- unless (write_to_target = to_dir)
1377
- sibling_path = ::File.absolute_path input.path if ::File === input
1378
- end
1379
- to_file = nil
1380
- when false
1381
- to_file = nil
1382
- when '/dev/null'
1383
- return self.load input, options
1384
- else
1385
- options[:to_file] = write_to_target = to_file unless (stream_output = to_file.respond_to? :write)
1386
- end
1387
-
1388
- unless options.key? :standalone
1389
- if sibling_path || write_to_target
1390
- options[:standalone] = true
1391
- elsif options.key? :header_footer
1392
- options[:standalone] = options[:header_footer]
1393
- end
1394
- end
1395
-
1396
- # NOTE outfile may be controlled by document attributes, so resolve outfile after loading
1397
- if sibling_path
1398
- options[:to_dir] = outdir = ::File.dirname sibling_path
1399
- elsif write_to_target
1400
- if to_dir
1401
- if to_file
1402
- options[:to_dir] = ::File.dirname ::File.expand_path ::File.join to_dir, to_file
1403
- else
1404
- options[:to_dir] = ::File.expand_path to_dir
1405
- end
1406
- elsif to_file
1407
- options[:to_dir] = ::File.dirname ::File.expand_path to_file
1408
- end
1409
- end
1410
-
1411
- # NOTE :to_dir is always set when outputting to a file
1412
- # NOTE :to_file option only passed if assigned an explicit path
1413
- doc = self.load input, options
1414
-
1415
- if sibling_path # write to file in same directory
1416
- outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
1417
- raise ::IOError, %(input file and output file cannot be the same: #{outfile}) if outfile == sibling_path
1418
- elsif write_to_target # write to explicit file or directory
1419
- working_dir = (options.key? :base_dir) ? (::File.expand_path options[:base_dir]) : ::Dir.pwd
1420
- # QUESTION should the jail be the working_dir or doc.base_dir???
1421
- jail = doc.safe >= SafeMode::SAFE ? working_dir : nil
1422
- if to_dir
1423
- outdir = doc.normalize_system_path(to_dir, working_dir, jail, target_name: 'to_dir', recover: false)
1424
- if to_file
1425
- outfile = doc.normalize_system_path(to_file, outdir, nil, target_name: 'to_dir', recover: false)
1426
- # reestablish outdir as the final target directory (in the case to_file had directory segments)
1427
- outdir = ::File.dirname outfile
1428
- else
1429
- outfile = ::File.join outdir, %(#{doc.attributes['docname']}#{doc.outfilesuffix})
1430
- end
1431
- elsif to_file
1432
- outfile = doc.normalize_system_path(to_file, working_dir, jail, target_name: 'to_dir', recover: false)
1433
- # establish outdir as the final target directory (in the case to_file had directory segments)
1434
- outdir = ::File.dirname outfile
1435
- end
1436
-
1437
- if ::File === input && outfile == (::File.absolute_path input.path)
1438
- raise ::IOError, %(input file and output file cannot be the same: #{outfile})
1439
- end
1440
-
1441
- if mkdirs
1442
- Helpers.mkdir_p outdir
1443
- else
1444
- # NOTE we intentionally refer to the directory as it was passed to the API
1445
- raise ::IOError, %(target directory does not exist: #{to_dir} (hint: set :mkdirs option)) unless ::File.directory? outdir
1446
- end
1447
- else # write to stream
1448
- outfile = to_file
1449
- outdir = nil
1450
- end
1451
-
1452
- if outfile && !stream_output
1453
- output = doc.convert 'outfile' => outfile, 'outdir' => outdir
1454
- else
1455
- output = doc.convert
1456
- end
1457
-
1458
- if outfile
1459
- doc.write output, outfile
1460
-
1461
- # NOTE document cannot control this behavior if safe >= SafeMode::SERVER
1462
- # NOTE skip if stylesdir is a URI
1463
- if !stream_output && doc.safe < SafeMode::SECURE && (doc.attr? 'linkcss') && (doc.attr? 'copycss') &&
1464
- (doc.basebackend? 'html') && !((stylesdir = (doc.attr 'stylesdir')) && (Helpers.uriish? stylesdir))
1465
- if (stylesheet = doc.attr 'stylesheet')
1466
- if DEFAULT_STYLESHEET_KEYS.include? stylesheet
1467
- copy_asciidoctor_stylesheet = true
1468
- elsif !(Helpers.uriish? stylesheet)
1469
- copy_user_stylesheet = true
1470
- end
1471
- end
1472
- copy_syntax_hl_stylesheet = (syntax_hl = doc.syntax_highlighter) && (syntax_hl.write_stylesheet? doc)
1473
- if copy_asciidoctor_stylesheet || copy_user_stylesheet || copy_syntax_hl_stylesheet
1474
- stylesoutdir = doc.normalize_system_path(stylesdir, outdir, doc.safe >= SafeMode::SAFE ? outdir : nil)
1475
- if mkdirs
1476
- Helpers.mkdir_p stylesoutdir
1477
- else
1478
- raise ::IOError, %(target stylesheet directory does not exist: #{stylesoutdir} (hint: set :mkdirs option)) unless ::File.directory? stylesoutdir
1479
- end
1480
-
1481
- if copy_asciidoctor_stylesheet
1482
- Stylesheets.instance.write_primary_stylesheet stylesoutdir
1483
- # FIXME should Stylesheets also handle the user stylesheet?
1484
- elsif copy_user_stylesheet
1485
- if (stylesheet_src = doc.attr 'copycss').empty?
1486
- stylesheet_src = doc.normalize_system_path stylesheet
1487
- else
1488
- # NOTE in this case, copycss is a source location (but cannot be a URI)
1489
- stylesheet_src = doc.normalize_system_path stylesheet_src
1490
- end
1491
- stylesheet_dest = doc.normalize_system_path stylesheet, stylesoutdir, (doc.safe >= SafeMode::SAFE ? outdir : nil)
1492
- # NOTE don't warn if src can't be read and dest already exists (see #2323)
1493
- if stylesheet_src != stylesheet_dest && (stylesheet_data = doc.read_asset stylesheet_src,
1494
- warn_on_failure: !(::File.file? stylesheet_dest), label: 'stylesheet')
1495
- ::File.write stylesheet_dest, stylesheet_data, mode: FILE_WRITE_MODE
1496
- end
1497
- end
1498
- syntax_hl.write_stylesheet doc, stylesoutdir if copy_syntax_hl_stylesheet
1499
- end
1500
- end
1501
- doc
1502
- else
1503
- output
1504
- end
1505
- end
1506
-
1507
- # Deprecated: Use {Asciidoctor.convert} instead.
1508
- alias render convert
1509
-
1510
- # Public: Parse the contents of the AsciiDoc source file into an
1511
- # Asciidoctor::Document and convert it to the specified backend format.
1512
- #
1513
- # input - the String AsciiDoc source filename
1514
- # options - a String, Array or Hash of options to control processing (default: {})
1515
- # String and Array values are converted into a Hash.
1516
- # See Asciidoctor::Document#initialize for details about options.
1517
- #
1518
- # Returns the Document object if the converted String is written to a
1519
- # file, otherwise the converted String
1520
- def convert_file filename, options = {}
1521
- ::File.open(filename, FILE_READ_MODE) {|file| self.convert file, options }
1522
- end
1523
-
1524
- # Deprecated: Use {Asciidoctor.convert_file} instead.
1525
- alias render_file convert_file
1526
-
1527
520
  # Internal: Automatically load the Asciidoctor::Extensions module.
1528
521
  #
1529
522
  # Requires the Asciidoctor::Extensions module if the name is :Extensions.
@@ -1534,7 +527,7 @@ module Asciidoctor
1534
527
  # defined prior to it being loaded.
1535
528
  #
1536
529
  # Returns the resolved constant, if resolved, otherwise nothing.
1537
- def const_missing name
530
+ def self.const_missing name
1538
531
  if name == :Extensions
1539
532
  require_relative 'asciidoctor/extensions'
1540
533
  Extensions
@@ -1543,11 +536,9 @@ module Asciidoctor
1543
536
  end
1544
537
  end unless RUBY_ENGINE == 'opal'
1545
538
 
1546
- end
1547
-
1548
539
  unless RUBY_ENGINE == 'opal'
1549
- autoload :SyntaxHighlighter, %(#{LIB_DIR}/asciidoctor/syntax_highlighter)
1550
- autoload :Timings, %(#{LIB_DIR}/asciidoctor/timings)
540
+ autoload :SyntaxHighlighter, %(#{__dir__}/asciidoctor/syntax_highlighter)
541
+ autoload :Timings, %(#{__dir__}/asciidoctor/timings)
1551
542
  end
1552
543
  end
1553
544
 
@@ -1557,6 +548,7 @@ require_relative 'asciidoctor/core_ext'
1557
548
  # modules and helpers
1558
549
  require_relative 'asciidoctor/helpers'
1559
550
  require_relative 'asciidoctor/logging'
551
+ require_relative 'asciidoctor/rx'
1560
552
  require_relative 'asciidoctor/substitutors'
1561
553
  require_relative 'asciidoctor/version'
1562
554
 
@@ -1580,6 +572,10 @@ require_relative 'asciidoctor/stylesheets'
1580
572
  require_relative 'asciidoctor/table'
1581
573
  require_relative 'asciidoctor/writer'
1582
574
 
575
+ # main API entry points
576
+ require_relative 'asciidoctor/load'
577
+ require_relative 'asciidoctor/convert'
578
+
1583
579
  if RUBY_ENGINE == 'opal'
1584
580
  require_relative 'asciidoctor/syntax_highlighter'
1585
581
  require_relative 'asciidoctor/timings'