asciidoctor 2.0.13 → 2.0.17
Sign up to get free protection for your applications and to get access to all the features.
- 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
|