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