asciidoctor 2.0.15 → 2.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +86 -11
- data/LICENSE +1 -1
- data/README-de.adoc +9 -12
- data/README-fr.adoc +9 -12
- data/README-jp.adoc +10 -13
- data/README-zh_CN.adoc +9 -12
- data/README.adoc +33 -18
- data/asciidoctor.gemspec +2 -9
- data/data/locale/attributes-fr.adoc +2 -2
- data/data/locale/attributes-th.adoc +23 -0
- data/data/locale/attributes-vi.adoc +23 -0
- data/data/stylesheets/asciidoctor-default.css +54 -53
- data/data/stylesheets/coderay-asciidoctor.css +9 -9
- data/lib/asciidoctor/abstract_block.rb +11 -9
- data/lib/asciidoctor/abstract_node.rb +9 -8
- data/lib/asciidoctor/block.rb +6 -6
- data/lib/asciidoctor/cli/invoker.rb +1 -2
- data/lib/asciidoctor/cli/options.rb +25 -25
- data/lib/asciidoctor/convert.rb +1 -0
- data/lib/asciidoctor/converter/docbook5.rb +20 -22
- data/lib/asciidoctor/converter/html5.rb +112 -94
- data/lib/asciidoctor/converter/manpage.rb +61 -52
- data/lib/asciidoctor/converter/template.rb +12 -13
- data/lib/asciidoctor/converter.rb +6 -4
- data/lib/asciidoctor/core_ext/hash/merge.rb +1 -1
- data/lib/asciidoctor/document.rb +39 -41
- data/lib/asciidoctor/extensions.rb +20 -12
- data/lib/asciidoctor/list.rb +2 -6
- data/lib/asciidoctor/load.rb +10 -9
- data/lib/asciidoctor/logging.rb +10 -8
- data/lib/asciidoctor/parser.rb +172 -189
- data/lib/asciidoctor/path_resolver.rb +3 -3
- data/lib/asciidoctor/reader.rb +71 -72
- data/lib/asciidoctor/rx.rb +3 -2
- data/lib/asciidoctor/section.rb +7 -0
- data/lib/asciidoctor/substitutors.rb +101 -103
- data/lib/asciidoctor/syntax_highlighter/coderay.rb +2 -1
- data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +1 -1
- data/lib/asciidoctor/syntax_highlighter/pygments.rb +14 -5
- data/lib/asciidoctor/syntax_highlighter/rouge.rb +2 -1
- data/lib/asciidoctor/syntax_highlighter.rb +8 -11
- data/lib/asciidoctor/table.rb +18 -20
- data/lib/asciidoctor/timings.rb +3 -3
- data/lib/asciidoctor/version.rb +1 -1
- data/lib/asciidoctor.rb +7 -7
- data/man/asciidoctor.1 +26 -28
- data/man/asciidoctor.adoc +33 -27
- metadata +8 -62
data/lib/asciidoctor/parser.rb
CHANGED
@@ -89,10 +89,10 @@ class Parser
|
|
89
89
|
#
|
90
90
|
# returns the Document object
|
91
91
|
def self.parse(reader, document, options = {})
|
92
|
-
block_attributes = parse_document_header(reader, document)
|
92
|
+
block_attributes = parse_document_header(reader, document, (header_only = options[:header_only]))
|
93
93
|
|
94
94
|
# NOTE don't use a postfix conditional here as it's known to confuse JRuby in certain circumstances
|
95
|
-
unless
|
95
|
+
unless header_only
|
96
96
|
while reader.has_more_lines?
|
97
97
|
new_section, block_attributes = next_section(reader, document, block_attributes)
|
98
98
|
if new_section
|
@@ -117,7 +117,7 @@ class Parser
|
|
117
117
|
# which are automatically removed by the reader.
|
118
118
|
#
|
119
119
|
# returns the Hash of orphan block attributes captured above the header
|
120
|
-
def self.parse_document_header(reader, document)
|
120
|
+
def self.parse_document_header(reader, document, header_only = false)
|
121
121
|
# capture lines of block-level metadata and plow away comment lines that precede first block
|
122
122
|
block_attrs = reader.skip_blank_lines ? (parse_block_metadata_lines reader, document) : {}
|
123
123
|
doc_attrs = document.attributes
|
@@ -125,6 +125,7 @@ class Parser
|
|
125
125
|
# special case, block title is not allowed above document title,
|
126
126
|
# carry attributes over to the document body
|
127
127
|
if (implicit_doctitle = is_next_line_doctitle? reader, block_attrs, doc_attrs['leveloffset']) && block_attrs['title']
|
128
|
+
doc_attrs['authorcount'] = 0
|
128
129
|
return document.finalize_header block_attrs, false
|
129
130
|
end
|
130
131
|
|
@@ -168,7 +169,7 @@ class Parser
|
|
168
169
|
end
|
169
170
|
block_attrs.clear
|
170
171
|
(modified_attrs = document.instance_variable_get :@attributes_modified).delete 'doctitle'
|
171
|
-
parse_header_metadata reader, document
|
172
|
+
parse_header_metadata reader, document, nil
|
172
173
|
if modified_attrs.include? 'doctitle'
|
173
174
|
if (val = doc_attrs['doctitle']).nil_or_empty? || val == doctitle_attr_val
|
174
175
|
doc_attrs['doctitle'] = doctitle_attr_val
|
@@ -179,10 +180,19 @@ class Parser
|
|
179
180
|
modified_attrs << 'doctitle'
|
180
181
|
end
|
181
182
|
document.register :refs, [doc_id, document] if doc_id
|
183
|
+
elsif (author = doc_attrs['author'])
|
184
|
+
author_metadata = process_authors author, true, false
|
185
|
+
author_metadata.delete 'authorinitials' if doc_attrs['authorinitials']
|
186
|
+
doc_attrs.update author_metadata
|
187
|
+
elsif (author = doc_attrs['authors'])
|
188
|
+
author_metadata = process_authors author, true
|
189
|
+
doc_attrs.update author_metadata
|
190
|
+
else
|
191
|
+
doc_attrs['authorcount'] = 0
|
182
192
|
end
|
183
193
|
|
184
194
|
# parse title and consume name section of manpage document
|
185
|
-
parse_manpage_header reader, document, block_attrs if document.doctype == 'manpage'
|
195
|
+
parse_manpage_header reader, document, block_attrs, header_only if document.doctype == 'manpage'
|
186
196
|
|
187
197
|
# NOTE block_attrs are the block-level attributes (not document attributes) that
|
188
198
|
# precede the first line of content (document title, first section or first block)
|
@@ -192,7 +202,7 @@ class Parser
|
|
192
202
|
# Public: Parses the manpage header of the AsciiDoc source read from the Reader
|
193
203
|
#
|
194
204
|
# returns Nothing
|
195
|
-
def self.parse_manpage_header(reader, document, block_attributes)
|
205
|
+
def self.parse_manpage_header(reader, document, block_attributes, header_only = false)
|
196
206
|
if ManpageTitleVolnumRx =~ (doc_attrs = document.attributes)['doctitle']
|
197
207
|
doc_attrs['manvolnum'] = manvolnum = $2
|
198
208
|
doc_attrs['mantitle'] = (((mantitle = $1).include? ATTR_REF_HEAD) ? (document.sub_attributes mantitle) : mantitle).downcase
|
@@ -209,6 +219,8 @@ class Parser
|
|
209
219
|
doc_attrs['docname'] = manname
|
210
220
|
doc_attrs['outfilesuffix'] = %(.#{manvolnum})
|
211
221
|
end
|
222
|
+
elsif header_only
|
223
|
+
# done
|
212
224
|
else
|
213
225
|
reader.skip_blank_lines
|
214
226
|
reader.save
|
@@ -421,9 +433,6 @@ class Parser
|
|
421
433
|
|
422
434
|
(intro || section).blocks << new_block
|
423
435
|
attributes.clear
|
424
|
-
#else
|
425
|
-
# # don't clear attributes if we don't find a block because they may
|
426
|
-
# # be trailing attributes that didn't get associated with a block
|
427
436
|
end
|
428
437
|
end
|
429
438
|
|
@@ -455,10 +464,10 @@ class Parser
|
|
455
464
|
end
|
456
465
|
|
457
466
|
# The attributes returned here are orphaned attributes that fall at the end
|
458
|
-
# of a section that need to get
|
467
|
+
# of a section that need to get transferred to the next section
|
459
468
|
# see "trailing block attributes transfer to the following section" in
|
460
469
|
# test/attributes_test.rb for an example
|
461
|
-
[section
|
470
|
+
[section == parent ? nil : section, attributes.merge]
|
462
471
|
end
|
463
472
|
|
464
473
|
# Public: Parse and return the next Block at the Reader's current location
|
@@ -650,7 +659,7 @@ class Parser
|
|
650
659
|
if (default_attrs = ext_config[:default_attrs])
|
651
660
|
attributes.update(default_attrs) {|_, old_v| old_v }
|
652
661
|
end
|
653
|
-
if (block = extension.process_method[parent, target, attributes])
|
662
|
+
if (block = extension.process_method[parent, target, attributes]) && block != parent
|
654
663
|
attributes.replace block.attributes
|
655
664
|
break
|
656
665
|
else
|
@@ -826,8 +835,8 @@ class Parser
|
|
826
835
|
if comma_idx > 0
|
827
836
|
language = (language.slice 0, comma_idx).strip
|
828
837
|
attributes['linenums'] = '' if comma_idx < ll - 4
|
829
|
-
|
830
|
-
attributes['linenums'] = ''
|
838
|
+
elsif ll > 4
|
839
|
+
attributes['linenums'] = ''
|
831
840
|
end
|
832
841
|
else
|
833
842
|
language = language.lstrip
|
@@ -908,7 +917,7 @@ class Parser
|
|
908
917
|
# FIXME title and caption should be assigned when block is constructed (though we need to handle all cases)
|
909
918
|
if attributes['title']
|
910
919
|
block.title = block_title = attributes.delete 'title'
|
911
|
-
block.assign_caption
|
920
|
+
block.assign_caption attributes.delete 'caption' if CAPTION_ATTRIBUTE_NAMES[block.context]
|
912
921
|
end
|
913
922
|
# TODO eventually remove the style attribute from the attributes hash
|
914
923
|
#block.style = attributes.delete 'style'
|
@@ -970,17 +979,12 @@ class Parser
|
|
970
979
|
# special case for fenced code blocks
|
971
980
|
if Compliance.markdown_syntax && (tip.start_with? '`')
|
972
981
|
if tip_len == 4
|
973
|
-
if tip == '````'
|
974
|
-
return
|
975
|
-
elsif (tip = tip.chop) == '```'
|
976
|
-
line = tip
|
977
|
-
line_len = tip_len = 3
|
978
|
-
else
|
982
|
+
if tip == '````' || (tip = tip.chop) != '```'
|
979
983
|
return
|
980
984
|
end
|
981
|
-
|
982
|
-
|
983
|
-
|
985
|
+
line = tip
|
986
|
+
line_len = tip_len = 3
|
987
|
+
elsif tip != '```'
|
984
988
|
return
|
985
989
|
end
|
986
990
|
elsif tip_len == 3
|
@@ -998,9 +1002,10 @@ class Parser
|
|
998
1002
|
# if terminator is false, that means the all the lines in the reader should be parsed
|
999
1003
|
# NOTE could invoke filter in here, before and after parsing
|
1000
1004
|
def self.build_block(block_context, content_model, terminator, parent, reader, attributes, options = {})
|
1001
|
-
|
1005
|
+
case content_model
|
1006
|
+
when :skip
|
1002
1007
|
skip_processing, parse_as_content_model = true, :simple
|
1003
|
-
|
1008
|
+
when :raw
|
1004
1009
|
skip_processing, parse_as_content_model = false, :simple
|
1005
1010
|
else
|
1006
1011
|
skip_processing, parse_as_content_model = false, content_model
|
@@ -1029,14 +1034,15 @@ class Parser
|
|
1029
1034
|
block_reader = Reader.new reader.read_lines_until(terminator: terminator, skip_processing: skip_processing, context: block_context, cursor: :at_mark), block_cursor
|
1030
1035
|
end
|
1031
1036
|
|
1032
|
-
|
1037
|
+
case content_model
|
1038
|
+
when :verbatim
|
1033
1039
|
tab_size = (attributes['tabsize'] || parent.document.attributes['tabsize']).to_i
|
1034
1040
|
if (indent = attributes['indent'])
|
1035
1041
|
adjust_indentation! lines, indent.to_i, tab_size
|
1036
1042
|
elsif tab_size > 0
|
1037
1043
|
adjust_indentation! lines, -1, tab_size
|
1038
1044
|
end
|
1039
|
-
|
1045
|
+
when :skip
|
1040
1046
|
# QUESTION should we still invoke process method if extension is specified?
|
1041
1047
|
return
|
1042
1048
|
end
|
@@ -1044,12 +1050,12 @@ class Parser
|
|
1044
1050
|
if (extension = options[:extension])
|
1045
1051
|
# QUESTION do we want to delete the style?
|
1046
1052
|
attributes.delete('style')
|
1047
|
-
if (block = extension.process_method[parent, block_reader || (Reader.new lines), attributes.merge])
|
1053
|
+
if (block = extension.process_method[parent, block_reader || (Reader.new lines), attributes.merge]) && block != parent
|
1048
1054
|
attributes.replace block.attributes
|
1049
1055
|
# FIXME if the content model is set to compound, but we only have simple in this context, then
|
1050
1056
|
# forcefully set the content_model to simple to prevent parsing blocks from children
|
1051
1057
|
# TODO document this behavior!!
|
1052
|
-
if block.content_model == :compound && !(lines = block.lines).empty?
|
1058
|
+
if block.content_model == :compound && Block === block && !(lines = block.lines).empty?
|
1053
1059
|
content_model = :compound
|
1054
1060
|
block_reader = Reader.new lines
|
1055
1061
|
end
|
@@ -1297,7 +1303,8 @@ class Parser
|
|
1297
1303
|
has_text = true
|
1298
1304
|
list_item = ListItem.new(list_block, (item_text = match[2]))
|
1299
1305
|
list_item.source_location = reader.cursor if list_block.document.sourcemap
|
1300
|
-
|
1306
|
+
case list_type
|
1307
|
+
when :ulist
|
1301
1308
|
list_item.marker = sibling_trait
|
1302
1309
|
if item_text.start_with?('[')
|
1303
1310
|
if style && style == 'bibliography'
|
@@ -1315,13 +1322,13 @@ class Parser
|
|
1315
1322
|
list_item.text = item_text.slice(4, item_text.length)
|
1316
1323
|
end
|
1317
1324
|
end
|
1318
|
-
|
1325
|
+
when :olist
|
1319
1326
|
sibling_trait, implicit_style = resolve_ordered_list_marker(sibling_trait, (ordinal = list_block.items.size), true, reader)
|
1320
1327
|
list_item.marker = sibling_trait
|
1321
1328
|
if ordinal == 0 && !style
|
1322
1329
|
# using list level makes more sense, but we don't track it
|
1323
1330
|
# basing style on marker level is compliant with AsciiDoc.py
|
1324
|
-
list_block.style = implicit_style || (
|
1331
|
+
list_block.style = implicit_style || (ORDERED_LIST_STYLES[sibling_trait.length - 1] || 'arabic').to_s
|
1325
1332
|
end
|
1326
1333
|
if item_text.start_with?('[[') && LeadingInlineAnchorRx =~ item_text
|
1327
1334
|
catalog_inline_anchor $1, $2, list_item, reader
|
@@ -1447,95 +1454,89 @@ class Parser
|
|
1447
1454
|
# FIXME to be AsciiDoc compliant, we shouldn't break if style in attribute line is "literal" (i.e., [literal])
|
1448
1455
|
elsif dlist && continuation != :active && (BlockAttributeLineRx.match? this_line)
|
1449
1456
|
break
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
buffer.concat reader.read_lines_until(preserve_last_line: true, break_on_blank_lines: true, break_on_list_continuation: true) {|line| is_sibling_list_item? line, list_type, sibling_trait }
|
1462
|
-
else
|
1463
|
-
buffer.concat reader.read_lines_until(preserve_last_line: true, break_on_blank_lines: true, break_on_list_continuation: true)
|
1464
|
-
end
|
1465
|
-
continuation = :inactive
|
1466
|
-
# let block metadata play out until we find the block
|
1467
|
-
elsif (BlockTitleRx.match? this_line) || (BlockAttributeLineRx.match? this_line) || (AttributeEntryRx.match? this_line)
|
1468
|
-
buffer << this_line
|
1457
|
+
elsif continuation == :active && !this_line.empty?
|
1458
|
+
# literal paragraphs have special considerations (and this is one of
|
1459
|
+
# two entry points into one)
|
1460
|
+
# if we don't process it as a whole, then a line in it that looks like a
|
1461
|
+
# list item will throw off the exit from it
|
1462
|
+
if LiteralParagraphRx.match? this_line
|
1463
|
+
reader.unshift_line this_line
|
1464
|
+
if dlist
|
1465
|
+
# we may be in an indented list disguised as a literal paragraph
|
1466
|
+
# so we need to make sure we don't slurp up a legitimate sibling
|
1467
|
+
buffer.concat reader.read_lines_until(preserve_last_line: true, break_on_blank_lines: true, break_on_list_continuation: true) {|line| is_sibling_list_item? line, list_type, sibling_trait }
|
1469
1468
|
else
|
1470
|
-
|
1471
|
-
within_nested_list = true
|
1472
|
-
if nested_list_type == :dlist && $3.nil_or_empty?
|
1473
|
-
# get greedy again
|
1474
|
-
has_text = false
|
1475
|
-
end
|
1476
|
-
end
|
1477
|
-
buffer << this_line
|
1478
|
-
continuation = :inactive
|
1469
|
+
buffer.concat reader.read_lines_until(preserve_last_line: true, break_on_blank_lines: true, break_on_list_continuation: true)
|
1479
1470
|
end
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1471
|
+
continuation = :inactive
|
1472
|
+
# let block metadata play out until we find the block
|
1473
|
+
elsif (BlockTitleRx.match? this_line) || (BlockAttributeLineRx.match? this_line) || (AttributeEntryRx.match? this_line)
|
1474
|
+
buffer << this_line
|
1475
|
+
else
|
1476
|
+
if (nested_list_type = (within_nested_list ? [:dlist] : NESTABLE_LIST_CONTEXTS).find {|ctx| ListRxMap[ctx].match? this_line })
|
1477
|
+
within_nested_list = true
|
1478
|
+
if nested_list_type == :dlist && $3.nil_or_empty?
|
1479
|
+
# get greedy again
|
1480
|
+
has_text = false
|
1481
|
+
end
|
1487
1482
|
end
|
1483
|
+
buffer << this_line
|
1484
|
+
continuation = :inactive
|
1485
|
+
end
|
1486
|
+
elsif prev_line && prev_line.empty?
|
1487
|
+
# advance to the next line of content
|
1488
|
+
if this_line.empty?
|
1489
|
+
# stop reading if we reach eof
|
1490
|
+
break unless (this_line = reader.skip_blank_lines && reader.read_line)
|
1491
|
+
# stop reading if we hit a sibling list item
|
1492
|
+
break if is_sibling_list_item? this_line, list_type, sibling_trait
|
1493
|
+
end
|
1488
1494
|
|
1489
|
-
|
1490
|
-
|
1495
|
+
if this_line == LIST_CONTINUATION
|
1496
|
+
detached_continuation = buffer.size
|
1497
|
+
buffer << this_line
|
1498
|
+
elsif has_text # has_text only relevant for dlist, which is more greedy until it has text for an item; has_text is always true for all other lists
|
1499
|
+
# in this block, we have to see whether we stay in the list
|
1500
|
+
# TODO any way to combine this with the check after skipping blank lines?
|
1501
|
+
if is_sibling_list_item?(this_line, list_type, sibling_trait)
|
1502
|
+
break
|
1503
|
+
elsif (nested_list_type = NESTABLE_LIST_CONTEXTS.find {|ctx| ListRxMap[ctx] =~ this_line })
|
1491
1504
|
buffer << this_line
|
1492
|
-
else
|
1493
|
-
# has_text is only relevant for dlist, which is more greedy until it has text for an item
|
1494
|
-
# for all other lists, has_text is always true
|
1495
|
-
# in this block, we have to see whether we stay in the list
|
1496
|
-
if has_text
|
1497
|
-
# TODO any way to combine this with the check after skipping blank lines?
|
1498
|
-
if is_sibling_list_item?(this_line, list_type, sibling_trait)
|
1499
|
-
break
|
1500
|
-
elsif nested_list_type = NESTABLE_LIST_CONTEXTS.find {|ctx| ListRxMap[ctx] =~ this_line }
|
1501
|
-
buffer << this_line
|
1502
|
-
within_nested_list = true
|
1503
|
-
if nested_list_type == :dlist && $3.nil_or_empty?
|
1504
|
-
# get greedy again
|
1505
|
-
has_text = false
|
1506
|
-
end
|
1507
|
-
# slurp up any literal paragraph offset by blank lines
|
1508
|
-
# NOTE we have to check for indented list items first
|
1509
|
-
elsif LiteralParagraphRx.match? this_line
|
1510
|
-
reader.unshift_line this_line
|
1511
|
-
if dlist
|
1512
|
-
# we may be in an indented list disguised as a literal paragraph
|
1513
|
-
# so we need to make sure we don't slurp up a legitimate sibling
|
1514
|
-
buffer.concat reader.read_lines_until(preserve_last_line: true, break_on_blank_lines: true, break_on_list_continuation: true) {|line| is_sibling_list_item? line, list_type, sibling_trait }
|
1515
|
-
else
|
1516
|
-
buffer.concat reader.read_lines_until(preserve_last_line: true, break_on_blank_lines: true, break_on_list_continuation: true)
|
1517
|
-
end
|
1518
|
-
else
|
1519
|
-
break
|
1520
|
-
end
|
1521
|
-
else # only dlist in need of item text, so slurp it up!
|
1522
|
-
# pop the blank line so it's not interpretted as a list continuation
|
1523
|
-
buffer.pop unless within_nested_list
|
1524
|
-
buffer << this_line
|
1525
|
-
has_text = true
|
1526
|
-
end
|
1527
|
-
end
|
1528
|
-
else
|
1529
|
-
has_text = true unless this_line.empty?
|
1530
|
-
if nested_list_type = (within_nested_list ? [:dlist] : NESTABLE_LIST_CONTEXTS).find {|ctx| ListRxMap[ctx] =~ this_line }
|
1531
1505
|
within_nested_list = true
|
1532
1506
|
if nested_list_type == :dlist && $3.nil_or_empty?
|
1533
1507
|
# get greedy again
|
1534
1508
|
has_text = false
|
1535
1509
|
end
|
1510
|
+
# slurp up any literal paragraph offset by blank lines
|
1511
|
+
# NOTE we have to check for indented list items first
|
1512
|
+
elsif LiteralParagraphRx.match? this_line
|
1513
|
+
reader.unshift_line this_line
|
1514
|
+
if dlist
|
1515
|
+
# we may be in an indented list disguised as a literal paragraph
|
1516
|
+
# so we need to make sure we don't slurp up a legitimate sibling
|
1517
|
+
buffer.concat reader.read_lines_until(preserve_last_line: true, break_on_blank_lines: true, break_on_list_continuation: true) {|line| is_sibling_list_item? line, list_type, sibling_trait }
|
1518
|
+
else
|
1519
|
+
buffer.concat reader.read_lines_until(preserve_last_line: true, break_on_blank_lines: true, break_on_list_continuation: true)
|
1520
|
+
end
|
1521
|
+
else
|
1522
|
+
break
|
1536
1523
|
end
|
1524
|
+
else # only dlist in need of item text, so slurp it up!
|
1525
|
+
# pop the blank line so it's not interpreted as a list continuation
|
1526
|
+
buffer.pop unless within_nested_list
|
1537
1527
|
buffer << this_line
|
1528
|
+
has_text = true
|
1529
|
+
end
|
1530
|
+
else
|
1531
|
+
has_text = true unless this_line.empty?
|
1532
|
+
if (nested_list_type = (within_nested_list ? [:dlist] : NESTABLE_LIST_CONTEXTS).find {|ctx| ListRxMap[ctx] =~ this_line })
|
1533
|
+
within_nested_list = true
|
1534
|
+
if nested_list_type == :dlist && $3.nil_or_empty?
|
1535
|
+
# get greedy again
|
1536
|
+
has_text = false
|
1537
|
+
end
|
1538
1538
|
end
|
1539
|
+
buffer << this_line
|
1539
1540
|
end
|
1540
1541
|
this_line = nil
|
1541
1542
|
end
|
@@ -1576,12 +1577,6 @@ class Parser
|
|
1576
1577
|
sect_style = attributes[1]
|
1577
1578
|
sect_id, sect_reftext, sect_title, sect_level, sect_atx = parse_section_title reader, document, attributes['id']
|
1578
1579
|
|
1579
|
-
if sect_reftext
|
1580
|
-
attributes['reftext'] = sect_reftext
|
1581
|
-
else
|
1582
|
-
sect_reftext = attributes['reftext']
|
1583
|
-
end
|
1584
|
-
|
1585
1580
|
if sect_style
|
1586
1581
|
if book && sect_style == 'abstract'
|
1587
1582
|
sect_name, sect_level = 'chapter', 1
|
@@ -1600,6 +1595,7 @@ class Parser
|
|
1600
1595
|
sect_name = 'section'
|
1601
1596
|
end
|
1602
1597
|
|
1598
|
+
attributes['reftext'] = sect_reftext if sect_reftext
|
1603
1599
|
section = Section.new parent, sect_level
|
1604
1600
|
section.id, section.title, section.sectname, section.source_location = sect_id, sect_title, sect_name, source_location
|
1605
1601
|
if sect_special
|
@@ -1607,7 +1603,7 @@ class Parser
|
|
1607
1603
|
if sect_numbered
|
1608
1604
|
section.numbered = true
|
1609
1605
|
elsif document.attributes['sectnums'] == 'all'
|
1610
|
-
section.numbered = book && sect_level == 1 ? :chapter : true
|
1606
|
+
section.numbered = (book && sect_level == 1 ? :chapter : true)
|
1611
1607
|
end
|
1612
1608
|
elsif document.attributes['sectnums'] && sect_level > 0
|
1613
1609
|
# NOTE a special section here is guaranteed to be nested in another section
|
@@ -1619,7 +1615,7 @@ class Parser
|
|
1619
1615
|
# generate an ID if one was not embedded or specified as anchor above section title
|
1620
1616
|
if (id = section.id || (section.id = (document.attributes.key? 'sectids') ? (generated_id = Section.generate_id section.title, document) : nil))
|
1621
1617
|
# convert title to resolve attributes while in scope
|
1622
|
-
section.title
|
1618
|
+
section.title unless generated_id || !(sect_title.include? ATTR_REF_HEAD)
|
1623
1619
|
unless document.register :refs, [id, section]
|
1624
1620
|
logger.warn message_with_context %(id assigned to section already in use: #{id}), source_location: (reader.cursor_at_line reader.lineno - (sect_atx ? 1 : 2))
|
1625
1621
|
end
|
@@ -1638,9 +1634,8 @@ class Parser
|
|
1638
1634
|
#
|
1639
1635
|
# Returns the Integer section level if the Reader is positioned at a section title or nil otherwise
|
1640
1636
|
def self.is_next_line_section?(reader, attributes)
|
1641
|
-
if (style = attributes[1]) && (style == 'discrete' || style == 'float')
|
1642
|
-
|
1643
|
-
elsif Compliance.underline_style_section_titles
|
1637
|
+
return if (style = attributes[1]) && (style == 'discrete' || style == 'float')
|
1638
|
+
if Compliance.underline_style_section_titles
|
1644
1639
|
next_lines = reader.peek_lines 2, style && style == 'comment'
|
1645
1640
|
is_section_title?(next_lines[0] || '', next_lines[1])
|
1646
1641
|
else
|
@@ -1786,38 +1781,31 @@ class Parser
|
|
1786
1781
|
# parse_header_metadata(Reader.new data, nil, normalize: true)
|
1787
1782
|
# # => { 'author' => 'Author Name', 'firstname' => 'Author', 'lastname' => 'Name', 'email' => 'author@example.org',
|
1788
1783
|
# # 'revnumber' => '1.0', 'revdate' => '2012-12-21', 'revremark' => 'Coincide w/ end of world.' }
|
1789
|
-
def self.parse_header_metadata
|
1784
|
+
def self.parse_header_metadata reader, document = nil, retrieve = true
|
1790
1785
|
doc_attrs = document && document.attributes
|
1791
1786
|
# NOTE this will discard any comment lines, but not skip blank lines
|
1792
1787
|
process_attribute_entries reader, document
|
1793
1788
|
|
1794
|
-
metadata, implicit_author, implicit_authorinitials = implicit_authors = {}, nil, nil
|
1795
|
-
|
1796
1789
|
if reader.has_more_lines? && !reader.next_line_empty?
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
doc_attrs[key] = ::String === val ? (document.apply_header_subs val) : val unless doc_attrs.key? key
|
1803
|
-
end
|
1804
|
-
|
1805
|
-
implicit_author = doc_attrs['author']
|
1806
|
-
implicit_authorinitials = doc_attrs['authorinitials']
|
1807
|
-
implicit_authors = doc_attrs['authors']
|
1790
|
+
authorcount = (implicit_author_metadata = process_authors reader.read_line).delete 'authorcount'
|
1791
|
+
if document && (doc_attrs['authorcount'] = authorcount) > 0
|
1792
|
+
implicit_author_metadata.each do |key, val|
|
1793
|
+
# apply header subs and assign to document; attributes substitution only relevant for email
|
1794
|
+
doc_attrs[key] = document.apply_header_subs val unless doc_attrs.key? key
|
1808
1795
|
end
|
1809
|
-
|
1810
|
-
|
1796
|
+
implicit_author = doc_attrs['author']
|
1797
|
+
implicit_authorinitials = doc_attrs['authorinitials']
|
1798
|
+
implicit_authors = doc_attrs['authors']
|
1811
1799
|
end
|
1800
|
+
implicit_author_metadata['authorcount'] = authorcount
|
1812
1801
|
|
1813
1802
|
# NOTE this will discard any comment lines, but not skip blank lines
|
1814
1803
|
process_attribute_entries reader, document
|
1815
1804
|
|
1816
|
-
rev_metadata = {}
|
1817
|
-
|
1818
1805
|
if reader.has_more_lines? && !reader.next_line_empty?
|
1819
1806
|
rev_line = reader.read_line
|
1820
|
-
if (match = RevisionInfoLineRx.match
|
1807
|
+
if (match = RevisionInfoLineRx.match rev_line)
|
1808
|
+
rev_metadata = {}
|
1821
1809
|
rev_metadata['revnumber'] = match[1].rstrip if match[1]
|
1822
1810
|
unless (component = match[2].strip).empty?
|
1823
1811
|
# version must begin with 'v' if date is absent
|
@@ -1828,31 +1816,24 @@ class Parser
|
|
1828
1816
|
end
|
1829
1817
|
end
|
1830
1818
|
rev_metadata['revremark'] = match[3].rstrip if match[3]
|
1819
|
+
if document && !rev_metadata.empty?
|
1820
|
+
# apply header subs and assign to document
|
1821
|
+
rev_metadata.each do |key, val|
|
1822
|
+
doc_attrs[key] = document.apply_header_subs val unless doc_attrs.key? key
|
1823
|
+
end
|
1824
|
+
end
|
1831
1825
|
else
|
1832
1826
|
# throw it back
|
1833
1827
|
reader.unshift_line rev_line
|
1834
1828
|
end
|
1835
1829
|
end
|
1836
1830
|
|
1837
|
-
unless rev_metadata.empty?
|
1838
|
-
if document
|
1839
|
-
# apply header subs and assign to document
|
1840
|
-
rev_metadata.each do |key, val|
|
1841
|
-
unless doc_attrs.key? key
|
1842
|
-
doc_attrs[key] = document.apply_header_subs val
|
1843
|
-
end
|
1844
|
-
end
|
1845
|
-
end
|
1846
|
-
|
1847
|
-
metadata.update rev_metadata
|
1848
|
-
end
|
1849
|
-
|
1850
1831
|
# NOTE this will discard any comment lines, but not skip blank lines
|
1851
1832
|
process_attribute_entries reader, document
|
1852
1833
|
|
1853
1834
|
reader.skip_blank_lines
|
1854
1835
|
else
|
1855
|
-
|
1836
|
+
implicit_author_metadata = {}
|
1856
1837
|
end
|
1857
1838
|
|
1858
1839
|
# process author attribute entries that override (or stand in for) the implicit author line
|
@@ -1869,7 +1850,7 @@ class Parser
|
|
1869
1850
|
while doc_attrs.key? author_key
|
1870
1851
|
# only use indexed author attribute if value is different
|
1871
1852
|
# leaves corner case if line matches with underscores converted to spaces; use double space to force
|
1872
|
-
if (author_override = doc_attrs[author_key]) ==
|
1853
|
+
if (author_override = doc_attrs[author_key]) == implicit_author_metadata[author_key]
|
1873
1854
|
authors << nil
|
1874
1855
|
sparse = true
|
1875
1856
|
else
|
@@ -1881,23 +1862,26 @@ class Parser
|
|
1881
1862
|
if explicit
|
1882
1863
|
# rebuild implicit author names to reparse
|
1883
1864
|
authors.each_with_index do |author, idx|
|
1884
|
-
|
1885
|
-
|
1886
|
-
|
1887
|
-
|
1888
|
-
|
1889
|
-
|
1890
|
-
end
|
1865
|
+
next if author
|
1866
|
+
authors[idx] = [
|
1867
|
+
implicit_author_metadata[%(firstname_#{name_idx = idx + 1})],
|
1868
|
+
implicit_author_metadata[%(middlename_#{name_idx})],
|
1869
|
+
implicit_author_metadata[%(lastname_#{name_idx})]
|
1870
|
+
].compact.map {|it| it.tr ' ', '_' }.join ' '
|
1891
1871
|
end if sparse
|
1892
1872
|
# process as names only
|
1893
1873
|
author_metadata = process_authors authors, true, false
|
1894
1874
|
else
|
1895
|
-
author_metadata = {}
|
1875
|
+
author_metadata = { 'authorcount' => 0 }
|
1896
1876
|
end
|
1897
1877
|
end
|
1898
1878
|
|
1899
|
-
if author_metadata
|
1900
|
-
|
1879
|
+
if author_metadata['authorcount'] == 0
|
1880
|
+
if authorcount
|
1881
|
+
author_metadata = nil
|
1882
|
+
else
|
1883
|
+
doc_attrs['authorcount'] = 0
|
1884
|
+
end
|
1901
1885
|
else
|
1902
1886
|
doc_attrs.update author_metadata
|
1903
1887
|
|
@@ -1908,7 +1892,7 @@ class Parser
|
|
1908
1892
|
end
|
1909
1893
|
end
|
1910
1894
|
|
1911
|
-
|
1895
|
+
implicit_author_metadata.merge rev_metadata.to_h, author_metadata.to_h if retrieve
|
1912
1896
|
end
|
1913
1897
|
|
1914
1898
|
# Internal: Parse the author line into a Hash of author metadata
|
@@ -2070,6 +2054,7 @@ class Parser
|
|
2070
2054
|
return true
|
2071
2055
|
end
|
2072
2056
|
end
|
2057
|
+
nil
|
2073
2058
|
end
|
2074
2059
|
|
2075
2060
|
# Process consecutive attribute entry lines, ignoring adjacent line comments and comment blocks.
|
@@ -2174,9 +2159,10 @@ class Parser
|
|
2174
2159
|
#
|
2175
2160
|
# Returns the String 0-index marker for this list item
|
2176
2161
|
def self.resolve_list_marker(list_type, marker, ordinal = 0, validate = false, reader = nil)
|
2177
|
-
|
2162
|
+
case list_type
|
2163
|
+
when :ulist
|
2178
2164
|
marker
|
2179
|
-
|
2165
|
+
when :olist
|
2180
2166
|
resolve_ordered_list_marker(marker, ordinal, validate, reader)[0]
|
2181
2167
|
else # :colist
|
2182
2168
|
'<1>'
|
@@ -2479,7 +2465,7 @@ class Parser
|
|
2479
2465
|
|
2480
2466
|
if pos == :start
|
2481
2467
|
if line.include? delimiter
|
2482
|
-
spec_part,
|
2468
|
+
spec_part, _, rest = line.partition delimiter
|
2483
2469
|
if (m = CellSpecStartRx.match spec_part)
|
2484
2470
|
return [{}, rest] if m[0].empty?
|
2485
2471
|
else
|
@@ -2488,14 +2474,12 @@ class Parser
|
|
2488
2474
|
else
|
2489
2475
|
return [nil, line]
|
2490
2476
|
end
|
2491
|
-
|
2492
|
-
if
|
2493
|
-
|
2494
|
-
|
2495
|
-
|
2496
|
-
|
2497
|
-
return [{}, line]
|
2498
|
-
end
|
2477
|
+
elsif (m = CellSpecEndRx.match line) # when pos == :end
|
2478
|
+
# NOTE return the line stripped of trailing whitespace if no cellspec is found in this case
|
2479
|
+
return [{}, line.rstrip] if m[0].lstrip.empty?
|
2480
|
+
rest = m.pre_match
|
2481
|
+
else
|
2482
|
+
return [{}, line]
|
2499
2483
|
end
|
2500
2484
|
|
2501
2485
|
spec = {}
|
@@ -2503,10 +2487,11 @@ class Parser
|
|
2503
2487
|
colspec, rowspec = m[1].split '.'
|
2504
2488
|
colspec = colspec.nil_or_empty? ? 1 : colspec.to_i
|
2505
2489
|
rowspec = rowspec.nil_or_empty? ? 1 : rowspec.to_i
|
2506
|
-
|
2490
|
+
case m[2]
|
2491
|
+
when '+'
|
2507
2492
|
spec['colspan'] = colspec unless colspec == 1
|
2508
2493
|
spec['rowspan'] = rowspec unless rowspec == 1
|
2509
|
-
|
2494
|
+
when '*'
|
2510
2495
|
spec['repeatcol'] = colspec unless colspec == 1
|
2511
2496
|
end
|
2512
2497
|
end
|
@@ -2531,7 +2516,7 @@ class Parser
|
|
2531
2516
|
# Public: Parse the first positional attribute and assign named attributes
|
2532
2517
|
#
|
2533
2518
|
# Parse the first positional attribute to extract the style, role and id
|
2534
|
-
# parts, assign the values to their
|
2519
|
+
# parts, assign the values to their corresponding attribute keys and return
|
2535
2520
|
# the parsed style from the first positional attribute.
|
2536
2521
|
#
|
2537
2522
|
# attributes - The Hash of attributes to process and update
|
@@ -2571,7 +2556,7 @@ class Parser
|
|
2571
2556
|
accum = ''
|
2572
2557
|
name = :option
|
2573
2558
|
else
|
2574
|
-
accum
|
2559
|
+
accum += c
|
2575
2560
|
end
|
2576
2561
|
end
|
2577
2562
|
|
@@ -2662,9 +2647,9 @@ class Parser
|
|
2662
2647
|
if tab_size > 0 && lines.any? {|line| line.include? TAB }
|
2663
2648
|
full_tab_space = ' ' * tab_size
|
2664
2649
|
lines.map! do |line|
|
2665
|
-
if line.empty?
|
2650
|
+
if line.empty? || (tab_idx = line.index TAB).nil?
|
2666
2651
|
line
|
2667
|
-
|
2652
|
+
else
|
2668
2653
|
if tab_idx == 0
|
2669
2654
|
leading_tabs = 0
|
2670
2655
|
line.each_byte do |b|
|
@@ -2682,22 +2667,20 @@ class Parser
|
|
2682
2667
|
if c == TAB
|
2683
2668
|
# calculate how many spaces this tab represents, then replace tab with spaces
|
2684
2669
|
if (offset = idx + spaces_added) % tab_size == 0
|
2685
|
-
spaces_added +=
|
2686
|
-
result
|
2670
|
+
spaces_added += tab_size - 1
|
2671
|
+
result += full_tab_space
|
2687
2672
|
else
|
2688
2673
|
unless (spaces = tab_size - offset % tab_size) == 1
|
2689
|
-
spaces_added +=
|
2674
|
+
spaces_added += spaces - 1
|
2690
2675
|
end
|
2691
|
-
result
|
2676
|
+
result += ' ' * spaces
|
2692
2677
|
end
|
2693
2678
|
else
|
2694
|
-
result
|
2679
|
+
result += c
|
2695
2680
|
end
|
2696
2681
|
idx += 1
|
2697
2682
|
end
|
2698
2683
|
result
|
2699
|
-
else
|
2700
|
-
line
|
2701
2684
|
end
|
2702
2685
|
end
|
2703
2686
|
end
|