asciidoctor 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of asciidoctor might be problematic. Click here for more details.
- data/README.asciidoc +11 -2
- data/asciidoctor.gemspec +3 -2
- data/lib/asciidoctor.rb +95 -62
- data/lib/asciidoctor/abstract_block.rb +7 -5
- data/lib/asciidoctor/abstract_node.rb +63 -15
- data/lib/asciidoctor/attribute_list.rb +3 -1
- data/lib/asciidoctor/backends/base_template.rb +17 -7
- data/lib/asciidoctor/backends/docbook45.rb +182 -150
- data/lib/asciidoctor/backends/html5.rb +138 -110
- data/lib/asciidoctor/block.rb +21 -18
- data/lib/asciidoctor/callouts.rb +3 -1
- data/lib/asciidoctor/cli/invoker.rb +3 -3
- data/lib/asciidoctor/cli/options.rb +6 -6
- data/lib/asciidoctor/debug.rb +7 -6
- data/lib/asciidoctor/document.rb +197 -25
- data/lib/asciidoctor/errors.rb +1 -1
- data/lib/asciidoctor/helpers.rb +29 -0
- data/lib/asciidoctor/inline.rb +11 -4
- data/lib/asciidoctor/lexer.rb +338 -182
- data/lib/asciidoctor/list_item.rb +14 -12
- data/lib/asciidoctor/reader.rb +423 -206
- data/lib/asciidoctor/renderer.rb +59 -15
- data/lib/asciidoctor/section.rb +7 -4
- data/lib/asciidoctor/substituters.rb +536 -511
- data/lib/asciidoctor/table.rb +473 -472
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +23 -14
- data/man/asciidoctor.ad +13 -7
- data/test/attributes_test.rb +42 -8
- data/test/blocks_test.rb +161 -1
- data/test/document_test.rb +134 -16
- data/test/invoker_test.rb +14 -6
- data/test/lexer_test.rb +45 -18
- data/test/lists_test.rb +79 -0
- data/test/paragraphs_test.rb +9 -1
- data/test/reader_test.rb +456 -19
- data/test/sections_test.rb +19 -0
- data/test/substitutions_test.rb +14 -12
- data/test/tables_test.rb +10 -10
- metadata +3 -5
data/lib/asciidoctor/errors.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Asciidoctor
|
2
|
+
module Helpers
|
3
|
+
# Internal: Prior to invoking Kernel#require, issues a warning urging a
|
4
|
+
# manual require if running in a threaded environment.
|
5
|
+
#
|
6
|
+
# name - the String name of the library to require.
|
7
|
+
#
|
8
|
+
# returns false if the library is detected on the load path or the return
|
9
|
+
# value of delegating to Kernel#require
|
10
|
+
def self.require_library(name)
|
11
|
+
if Thread.list.size > 1
|
12
|
+
main_script = "#{name}.rb"
|
13
|
+
main_script_path_segment = "/#{name}.rb"
|
14
|
+
if !$LOADED_FEATURES.detect {|p| p == main_script || p.end_with?(main_script_path_segment) }.nil?
|
15
|
+
return false
|
16
|
+
else
|
17
|
+
warn "WARN: asciidoctor is autoloading '#{name}' in threaded environment. " +
|
18
|
+
"The use of an explicit require '#{name}' statement is recommended."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
require name
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: A generic capture output routine to be used in templates
|
25
|
+
#def self.capture_output(*args, &block)
|
26
|
+
# Proc.new { block.call(*args) }
|
27
|
+
#end
|
28
|
+
end
|
29
|
+
end
|
data/lib/asciidoctor/inline.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
module Asciidoctor
|
1
2
|
# Public: Methods for managing inline elements in AsciiDoc block
|
2
|
-
class
|
3
|
+
class Inline < AbstractNode
|
3
4
|
# Public: Get the text of this inline element
|
4
5
|
attr_reader :text
|
5
6
|
|
@@ -13,9 +14,14 @@ class Asciidoctor::Inline < Asciidoctor::AbstractNode
|
|
13
14
|
super(parent, context)
|
14
15
|
|
15
16
|
@text = text
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
|
18
|
+
#@id = opts[:id] if opts.has_key?(:id)
|
19
|
+
#@type = opts[:type] if opts.has_key?(:type)
|
20
|
+
#@target = opts[:target] if opts.has_key?(:target)
|
21
|
+
|
22
|
+
@id = opts[:id]
|
23
|
+
@type = opts[:type]
|
24
|
+
@target = opts[:target]
|
19
25
|
|
20
26
|
if opts.has_key?(:attributes) && (attributes = opts[:attributes]).is_a?(Hash)
|
21
27
|
update_attributes(opts[:attributes]) unless attributes.empty?
|
@@ -27,3 +33,4 @@ class Asciidoctor::Inline < Asciidoctor::AbstractNode
|
|
27
33
|
end
|
28
34
|
|
29
35
|
end
|
36
|
+
end
|
data/lib/asciidoctor/lexer.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
module Asciidoctor
|
1
2
|
# Public: Methods to parse lines of AsciiDoc into an object hierarchy
|
2
3
|
# representing the structure of the document. All methods are class methods and
|
3
4
|
# should be invoked from the Lexer class. The main entry point is ::next_block.
|
@@ -20,9 +21,9 @@
|
|
20
21
|
# block = Lexer.next_block(reader, doc)
|
21
22
|
# block.class
|
22
23
|
# # => Asciidoctor::Block
|
23
|
-
class
|
24
|
+
class Lexer
|
24
25
|
|
25
|
-
|
26
|
+
BlockMatchData = Struct.new(:name, :tip, :terminator)
|
26
27
|
|
27
28
|
# Public: Make sure the Lexer object doesn't get initialized.
|
28
29
|
#
|
@@ -40,28 +41,60 @@ class Asciidoctor::Lexer
|
|
40
41
|
#
|
41
42
|
# reader - the Reader holding the source lines of the document
|
42
43
|
# document - the empty Document into which the lines will be parsed
|
44
|
+
# options - a Hash of options to control processing
|
43
45
|
#
|
44
46
|
# returns the Document object
|
45
|
-
def self.parse(reader, document)
|
46
|
-
|
47
|
-
# we can get at the document title, if present, then begin parsing blocks
|
48
|
-
reader.skip_blank_lines
|
49
|
-
attributes = parse_block_metadata_lines(reader, document)
|
47
|
+
def self.parse(reader, document, options = {})
|
48
|
+
block_attributes = parse_document_header(reader, document)
|
50
49
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
parse_header_metadata(reader, document)
|
50
|
+
unless options[:header_only]
|
51
|
+
while reader.has_more_lines?
|
52
|
+
new_section, block_attributes = next_section(reader, document, block_attributes)
|
53
|
+
document << new_section unless new_section.nil?
|
54
|
+
end
|
57
55
|
end
|
58
56
|
|
59
|
-
|
60
|
-
|
61
|
-
|
57
|
+
document
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: Parses the document header of the AsciiDoc source read from the Reader
|
61
|
+
#
|
62
|
+
# Reads the AsciiDoc source from the Reader until the end of the document
|
63
|
+
# header is reached. The Document object is populated with information from
|
64
|
+
# the header (document title, document attributes, etc). The document
|
65
|
+
# attributes are then saved to establish a save point to which to rollback
|
66
|
+
# after parsing is complete.
|
67
|
+
#
|
68
|
+
# This method assumes that there are no blank lines at the start of the document,
|
69
|
+
# which are automatically removed by the reader.
|
70
|
+
#
|
71
|
+
# returns the Hash of orphan block attributes captured above the header
|
72
|
+
def self.parse_document_header(reader, document)
|
73
|
+
# capture any lines of block-level metadata and plow away any comment lines
|
74
|
+
# that precede first block
|
75
|
+
block_attributes = parse_block_metadata_lines(reader, document)
|
76
|
+
|
77
|
+
# check if the first line is the document title
|
78
|
+
# if so, add a header to the document and parse the header metadata
|
79
|
+
if is_next_line_document_title?(reader, block_attributes)
|
80
|
+
document.id, document.title, _, _ = parse_section_title(reader)
|
81
|
+
# QUESTION: should this be encapsulated in document?
|
82
|
+
if document.id.nil? && block_attributes.has_key?('id')
|
83
|
+
document.id = block_attributes.delete('id')
|
84
|
+
end
|
85
|
+
parse_header_metadata(reader, document)
|
62
86
|
end
|
63
87
|
|
64
|
-
document
|
88
|
+
if document.attributes.has_key? 'doctitle'
|
89
|
+
document.title = document.attributes['doctitle']
|
90
|
+
end
|
91
|
+
|
92
|
+
document.clear_playback_attributes block_attributes
|
93
|
+
document.save_attributes
|
94
|
+
|
95
|
+
# NOTE these are the block-level attributes (not document attributes) that
|
96
|
+
# precede the first line of content (document title, first section or first block)
|
97
|
+
block_attributes
|
65
98
|
end
|
66
99
|
|
67
100
|
# Public: Return the next section from the Reader.
|
@@ -145,18 +178,18 @@ class Asciidoctor::Lexer
|
|
145
178
|
#
|
146
179
|
# We have to parse all the metadata lines before continuing with the loop,
|
147
180
|
# otherwise subsequent metadata lines get interpreted as block content
|
148
|
-
while reader.
|
181
|
+
while reader.has_more_lines?
|
149
182
|
parse_block_metadata_lines(reader, section, attributes)
|
150
183
|
|
151
184
|
next_level = is_next_line_section? reader, attributes
|
152
185
|
if next_level
|
153
186
|
doctype = parent.document.doctype
|
154
187
|
if next_level == 0 && doctype != 'book'
|
155
|
-
puts "asciidoctor: ERROR: only book doctypes can contain level 0 sections"
|
188
|
+
puts "asciidoctor: ERROR: line #{reader.lineno + 1}: only book doctypes can contain level 0 sections"
|
156
189
|
end
|
157
190
|
if next_level > current_level || (section.is_a?(Document) && next_level == 0)
|
158
191
|
unless expected_next_levels.nil? || expected_next_levels.include?(next_level)
|
159
|
-
puts "asciidoctor: WARNING: section title out of sequence: " +
|
192
|
+
puts "asciidoctor: WARNING: line #{reader.lineno + 1}: section title out of sequence: " +
|
160
193
|
"expected #{expected_next_levels.size > 1 ? 'levels' : 'level'} #{expected_next_levels * ' or '}, " +
|
161
194
|
"got level #{next_level}"
|
162
195
|
end
|
@@ -210,23 +243,20 @@ class Asciidoctor::Lexer
|
|
210
243
|
# Returns a Section or Block object holding the parsed content of the processed lines
|
211
244
|
def self.next_block(reader, parent, attributes = {}, options = {})
|
212
245
|
# Skip ahead to the block content
|
213
|
-
skipped = reader.
|
246
|
+
skipped = reader.skip_blank_lines
|
214
247
|
|
215
248
|
# bail if we've reached the end of the section content
|
216
|
-
return nil unless reader.
|
249
|
+
return nil unless reader.has_more_lines?
|
217
250
|
|
218
251
|
if options[:text] && skipped > 0
|
219
252
|
options.delete(:text)
|
220
253
|
end
|
221
254
|
|
222
|
-
|
255
|
+
Debug.debug {
|
223
256
|
msg = []
|
224
257
|
msg << '/' * 64
|
225
258
|
msg << 'next_block() - First two lines are:'
|
226
|
-
msg
|
227
|
-
tmp_line = reader.get_line
|
228
|
-
msg << reader.peek_line
|
229
|
-
reader.unshift tmp_line
|
259
|
+
msg.concat reader.peek_lines(2)
|
230
260
|
msg << '/' * 64
|
231
261
|
msg * "\n"
|
232
262
|
}
|
@@ -238,9 +268,9 @@ class Asciidoctor::Lexer
|
|
238
268
|
context = parent.is_a?(Block) ? parent.context : nil
|
239
269
|
block = nil
|
240
270
|
|
241
|
-
while reader.
|
271
|
+
while reader.has_more_lines? && block.nil?
|
242
272
|
if parse_metadata && parse_block_metadata_line(reader, document, attributes, options)
|
243
|
-
reader.
|
273
|
+
reader.advance
|
244
274
|
next
|
245
275
|
elsif parse_sections && context.nil? && is_next_line_section?(reader, attributes)
|
246
276
|
block, attributes = next_section(reader, parent, attributes)
|
@@ -249,24 +279,19 @@ class Asciidoctor::Lexer
|
|
249
279
|
|
250
280
|
this_line = reader.get_line
|
251
281
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
# reader.skip_blank
|
259
|
-
# # NOTE we should break here because we have found a block, it
|
260
|
-
# # just happens to be nil...if we keep going we potentially overrun
|
261
|
-
# # a section heading which is not processed in this anymore
|
262
|
-
# break
|
282
|
+
block_context = nil
|
283
|
+
terminator = nil
|
284
|
+
if delimited_blk_match = is_delimited_block?(this_line, true)
|
285
|
+
block_context = delimited_blk_match.name
|
286
|
+
terminator = delimited_blk_match.terminator
|
287
|
+
end
|
263
288
|
|
264
|
-
# NOTE we're letting ruler have attributes
|
265
|
-
if !options[:text] && this_line.match(REGEXP[:
|
266
|
-
block = Block.new(parent,
|
267
|
-
reader.
|
289
|
+
# NOTE we're letting break lines (ruler, page_break, etc) have attributes
|
290
|
+
if !options[:text] && block_context.nil? && (match = this_line.match(REGEXP[:break_line]))
|
291
|
+
block = Block.new(parent, BREAK_LINES[match[0][0..2]])
|
292
|
+
reader.skip_blank_lines
|
268
293
|
|
269
|
-
elsif !options[:text] && (match = this_line.match(REGEXP[:image_blk]))
|
294
|
+
elsif !options[:text] && block_context.nil? && (match = this_line.match(REGEXP[:image_blk]))
|
270
295
|
block = Block.new(parent, :image)
|
271
296
|
AttributeList.new(document.sub_attributes(match[2])).parse_into(attributes, ['alt', 'width', 'height'])
|
272
297
|
target = block.sub_attributes(match[1])
|
@@ -274,57 +299,56 @@ class Asciidoctor::Lexer
|
|
274
299
|
attributes['target'] = target
|
275
300
|
document.register(:images, target)
|
276
301
|
attributes['alt'] ||= File.basename(target, File.extname(target))
|
277
|
-
# hmmm, this assignment seems like a one-off
|
278
302
|
block.title = attributes['title']
|
279
|
-
if block.title? && attributes
|
280
|
-
|
303
|
+
if block.title? && !attributes.has_key?('caption') && !block.attr?('caption')
|
304
|
+
number = document.counter('figure-number')
|
305
|
+
attributes['caption'] = "#{document.attributes['figure-caption']} #{number}. "
|
306
|
+
Document::AttributeEntry.new('figure-number', number).save_to(attributes)
|
281
307
|
end
|
282
308
|
else
|
283
309
|
# drop the line if target resolves to nothing
|
284
310
|
block = nil
|
285
311
|
end
|
286
|
-
reader.
|
312
|
+
reader.skip_blank_lines
|
287
313
|
|
288
|
-
elsif
|
314
|
+
elsif block_context == :open
|
289
315
|
# an open block is surrounded by '--' lines and has zero or more blocks inside
|
290
|
-
terminator = match[0]
|
291
316
|
buffer = Reader.new reader.grab_lines_until(:terminator => terminator)
|
292
317
|
|
293
318
|
# Strip lines off end of block - not implemented yet
|
294
|
-
# while buffer.
|
319
|
+
# while buffer.has_more_lines? && buffer.last.strip.empty?
|
295
320
|
# buffer.pop
|
296
321
|
# end
|
297
322
|
|
298
|
-
block = Block.new(parent,
|
299
|
-
while buffer.
|
323
|
+
block = Block.new(parent, block_context)
|
324
|
+
while buffer.has_more_lines?
|
300
325
|
new_block = next_block(buffer, block)
|
301
326
|
block.blocks << new_block unless new_block.nil?
|
302
327
|
end
|
303
328
|
|
304
329
|
# needs to come before list detection
|
305
|
-
elsif
|
330
|
+
elsif block_context == :sidebar
|
306
331
|
# sidebar is surrounded by '****' (4 or more '*' chars) lines
|
307
|
-
terminator = match[0]
|
308
332
|
# FIXME violates DRY because it's a duplication of quote parsing
|
309
|
-
block = Block.new(parent,
|
333
|
+
block = Block.new(parent, block_context)
|
310
334
|
buffer = Reader.new reader.grab_lines_until(:terminator => terminator)
|
311
335
|
|
312
|
-
while buffer.
|
336
|
+
while buffer.has_more_lines?
|
313
337
|
new_block = next_block(buffer, block)
|
314
338
|
block.blocks << new_block unless new_block.nil?
|
315
339
|
end
|
316
340
|
|
317
|
-
elsif match = this_line.match(REGEXP[:colist])
|
341
|
+
elsif block_context.nil? && (match = this_line.match(REGEXP[:colist]))
|
318
342
|
block = Block.new(parent, :colist)
|
319
343
|
attributes['style'] = 'arabic'
|
320
344
|
items = []
|
321
345
|
block.buffer = items
|
322
|
-
reader.
|
346
|
+
reader.unshift_line this_line
|
323
347
|
expected_index = 1
|
324
348
|
begin
|
325
349
|
# might want to move this check to a validate method
|
326
350
|
if match[1].to_i != expected_index
|
327
|
-
puts "asciidoctor: WARNING: callout list item index: expected #{expected_index} got #{match[1]}"
|
351
|
+
puts "asciidoctor: WARNING: line #{reader.lineno + 1}: callout list item index: expected #{expected_index} got #{match[1]}"
|
328
352
|
end
|
329
353
|
list_item = next_list_item(reader, block, match)
|
330
354
|
expected_index += 1
|
@@ -334,21 +358,21 @@ class Asciidoctor::Lexer
|
|
334
358
|
if !coids.empty?
|
335
359
|
list_item.attributes['coids'] = coids
|
336
360
|
else
|
337
|
-
puts
|
361
|
+
puts "asciidoctor: WARNING: line #{reader.lineno}: no callouts refer to list item #{items.size}"
|
338
362
|
end
|
339
363
|
end
|
340
|
-
end while reader.
|
364
|
+
end while reader.has_more_lines? && match = reader.peek_line.match(REGEXP[:colist])
|
341
365
|
|
342
366
|
document.callouts.next_list
|
343
367
|
|
344
|
-
elsif match = this_line.match(REGEXP[:ulist])
|
368
|
+
elsif block_context.nil? && (match = this_line.match(REGEXP[:ulist]))
|
345
369
|
AttributeList.rekey(attributes, ['style'])
|
346
|
-
reader.
|
370
|
+
reader.unshift_line this_line
|
347
371
|
block = next_outline_list(reader, :ulist, parent)
|
348
372
|
|
349
|
-
elsif match = this_line.match(REGEXP[:olist])
|
373
|
+
elsif block_context.nil? && (match = this_line.match(REGEXP[:olist]))
|
350
374
|
AttributeList.rekey(attributes, ['style'])
|
351
|
-
reader.
|
375
|
+
reader.unshift_line this_line
|
352
376
|
block = next_outline_list(reader, :olist, parent)
|
353
377
|
# QUESTION move this logic to next_outline_list?
|
354
378
|
if !(attributes.has_key? 'style') && !(block.attributes.has_key? 'style')
|
@@ -363,68 +387,80 @@ class Asciidoctor::Lexer
|
|
363
387
|
end
|
364
388
|
end
|
365
389
|
|
366
|
-
elsif match = this_line.match(REGEXP[:dlist])
|
367
|
-
reader.
|
390
|
+
elsif block_context.nil? && (match = this_line.match(REGEXP[:dlist]))
|
391
|
+
reader.unshift_line this_line
|
368
392
|
block = next_labeled_list(reader, match, parent)
|
369
393
|
AttributeList.rekey(attributes, ['style'])
|
370
394
|
|
371
|
-
elsif
|
395
|
+
elsif block_context == :table
|
372
396
|
# table is surrounded by lines starting with a | followed by 3 or more '=' chars
|
373
|
-
terminator = match[0]
|
374
397
|
AttributeList.rekey(attributes, ['style'])
|
375
398
|
table_reader = Reader.new reader.grab_lines_until(:terminator => terminator, :skip_line_comments => true)
|
376
399
|
block = next_table(table_reader, parent, attributes)
|
377
|
-
# hmmm, this assignment seems like a one-off
|
378
400
|
block.title = attributes['title']
|
379
|
-
if block.title? && attributes
|
380
|
-
|
401
|
+
if block.title? && !attributes.has_key?('caption') && !block.attr?('caption')
|
402
|
+
number = document.counter('table-number')
|
403
|
+
attributes['caption'] = "#{document.attributes['table-caption']} #{number}. "
|
404
|
+
Document::AttributeEntry.new('table-number', number).save_to(attributes)
|
381
405
|
end
|
382
406
|
|
383
407
|
# FIXME violates DRY because it's a duplication of other block parsing
|
384
|
-
elsif
|
408
|
+
elsif block_context == :example
|
385
409
|
# example is surrounded by lines with 4 or more '=' chars
|
386
|
-
terminator = match[0]
|
387
410
|
AttributeList.rekey(attributes, ['style'])
|
388
411
|
if admonition_style = ADMONITION_STYLES.detect {|s| attributes['style'] == s}
|
389
412
|
block = Block.new(parent, :admonition)
|
390
|
-
attributes['name'] = admonition_style.downcase
|
391
|
-
attributes['caption'] ||=
|
413
|
+
attributes['name'] = admonition_name = admonition_style.downcase
|
414
|
+
attributes['caption'] ||= document.attributes["#{admonition_name}-caption"]
|
392
415
|
else
|
393
|
-
block = Block.new(parent,
|
394
|
-
# hmmm, this assignment seems like a one-off
|
416
|
+
block = Block.new(parent, block_context)
|
395
417
|
block.title = attributes['title']
|
396
|
-
if block.title? && attributes
|
397
|
-
|
418
|
+
if block.title? && !attributes.has_key?('caption') && !block.attr?('caption')
|
419
|
+
number = document.counter('example-number')
|
420
|
+
attributes['caption'] = "#{document.attributes['example-caption']} #{number}. "
|
421
|
+
Document::AttributeEntry.new('example-number', number).save_to(attributes)
|
398
422
|
end
|
399
423
|
end
|
400
424
|
buffer = Reader.new reader.grab_lines_until(:terminator => terminator)
|
401
425
|
|
402
|
-
while buffer.
|
426
|
+
while buffer.has_more_lines?
|
403
427
|
new_block = next_block(buffer, block)
|
404
428
|
block.blocks << new_block unless new_block.nil?
|
405
429
|
end
|
406
430
|
|
407
431
|
# FIXME violates DRY w/ non-delimited block listing
|
408
|
-
elsif
|
409
|
-
|
410
|
-
|
432
|
+
elsif block_context == :listing || block_context == :fenced_code
|
433
|
+
if block_context == :fenced_code
|
434
|
+
attributes['style'] = 'source'
|
435
|
+
lang = this_line[3..-1].strip
|
436
|
+
attributes['language'] = lang unless lang.empty?
|
437
|
+
terminator = terminator[0..2] if terminator.length > 3
|
438
|
+
else
|
439
|
+
AttributeList.rekey(attributes, ['style', 'language', 'linenums'])
|
440
|
+
end
|
411
441
|
buffer = reader.grab_lines_until(:terminator => terminator)
|
412
442
|
buffer.last.chomp! unless buffer.empty?
|
413
443
|
block = Block.new(parent, :listing, buffer)
|
444
|
+
block.title = attributes['title']
|
445
|
+
if document.attributes.has_key?('listing-caption') &&
|
446
|
+
block.title? && !attributes.has_key?('caption') && !block.attr?('caption')
|
447
|
+
number = document.counter('listing-number')
|
448
|
+
attributes['caption'] = "#{document.attributes['listing-caption']} #{number}. "
|
449
|
+
Document::AttributeEntry.new('listing-number', number).save_to(attributes)
|
450
|
+
end
|
414
451
|
|
415
|
-
elsif
|
452
|
+
elsif block_context == :quote
|
416
453
|
# multi-line verse or quote is surrounded by a block delimiter
|
417
|
-
terminator = match[0]
|
418
454
|
AttributeList.rekey(attributes, ['style', 'attribution', 'citetitle'])
|
419
455
|
quote_context = (attributes['style'] == 'verse' ? :verse : :quote)
|
420
456
|
block_reader = Reader.new reader.grab_lines_until(:terminator => terminator)
|
421
457
|
|
422
|
-
# only quote can have other section elements (as
|
458
|
+
# only quote can have other section elements (as section block)
|
423
459
|
section_body = (quote_context == :quote)
|
424
460
|
|
425
461
|
if section_body
|
426
462
|
block = Block.new(parent, quote_context)
|
427
|
-
while block_reader.
|
463
|
+
while block_reader.has_more_lines?
|
428
464
|
new_block = next_block(block_reader, block)
|
429
465
|
block.blocks << new_block unless new_block.nil?
|
430
466
|
end
|
@@ -433,29 +469,28 @@ class Asciidoctor::Lexer
|
|
433
469
|
block = Block.new(parent, quote_context, block_reader.lines)
|
434
470
|
end
|
435
471
|
|
436
|
-
elsif
|
472
|
+
elsif block_context == :literal || block_context == :pass
|
437
473
|
# literal is surrounded by '....' (4 or more '.' chars) lines
|
438
474
|
# pass is surrounded by '++++' (4 or more '+' chars) lines
|
439
|
-
terminator = $~[0]
|
440
475
|
buffer = reader.grab_lines_until(:terminator => terminator)
|
441
476
|
buffer.last.chomp! unless buffer.empty?
|
442
477
|
# a literal can masquerade as a listing
|
443
478
|
if attributes[1] == 'listing'
|
444
|
-
|
479
|
+
block_context = :listing
|
445
480
|
end
|
446
|
-
block = Block.new(parent,
|
481
|
+
block = Block.new(parent, block_context, buffer)
|
447
482
|
|
448
483
|
elsif this_line.match(REGEXP[:lit_par])
|
449
484
|
# literal paragraph is contiguous lines starting with
|
450
485
|
# one or more space or tab characters
|
451
486
|
|
452
487
|
# So we need to actually include this one in the grab_lines group
|
453
|
-
reader.
|
488
|
+
reader.unshift_line this_line
|
454
489
|
buffer = reader.grab_lines_until(:preserve_last_line => true, :break_on_blank_lines => true) {|line|
|
455
490
|
# labeled list terms can be indented, but a preceding blank indicates
|
456
491
|
# we are in a list continuation and therefore literals should be strictly literal
|
457
492
|
(context == :dlist && skipped == 0 && line.match(REGEXP[:dlist])) ||
|
458
|
-
|
493
|
+
is_delimited_block?(line)
|
459
494
|
}
|
460
495
|
|
461
496
|
# trim off the indentation equivalent to the size of the least indented line
|
@@ -477,27 +512,35 @@ class Asciidoctor::Lexer
|
|
477
512
|
|
478
513
|
## these switches based on style need to come immediately before the else ##
|
479
514
|
|
480
|
-
elsif attributes[1] == 'source'
|
481
|
-
|
482
|
-
|
515
|
+
elsif attributes[1] == 'source' || attributes[1] == 'listing'
|
516
|
+
if attributes[1] == 'source'
|
517
|
+
AttributeList.rekey(attributes, ['style', 'language', 'linenums'])
|
518
|
+
end
|
519
|
+
reader.unshift_line this_line
|
483
520
|
buffer = reader.grab_lines_until(:break_on_blank_lines => true)
|
484
521
|
buffer.last.chomp! unless buffer.empty?
|
485
522
|
block = Block.new(parent, :listing, buffer)
|
486
523
|
|
524
|
+
elsif attributes[1] == 'literal'
|
525
|
+
reader.unshift_line this_line
|
526
|
+
buffer = reader.grab_lines_until(:break_on_blank_lines => true)
|
527
|
+
buffer.last.chomp! unless buffer.empty?
|
528
|
+
block = Block.new(parent, :literal, buffer)
|
529
|
+
|
487
530
|
elsif admonition_style = ADMONITION_STYLES.detect{|s| attributes[1] == s}
|
488
531
|
# an admonition preceded by [<TYPE>] and lasts until a blank line
|
489
|
-
reader.
|
532
|
+
reader.unshift_line this_line
|
490
533
|
buffer = reader.grab_lines_until(:break_on_blank_lines => true)
|
491
534
|
buffer.last.chomp! unless buffer.empty?
|
492
535
|
block = Block.new(parent, :admonition, buffer)
|
493
536
|
attributes['style'] = admonition_style
|
494
|
-
attributes['name'] = admonition_style.downcase
|
495
|
-
attributes['caption'] ||=
|
537
|
+
attributes['name'] = admonition_name = admonition_style.downcase
|
538
|
+
attributes['caption'] ||= document.attributes["#{admonition_name}-caption"]
|
496
539
|
|
497
540
|
elsif quote_context = [:quote, :verse].detect{|s| attributes[1] == s.to_s}
|
498
541
|
# single-paragraph verse or quote is preceded by [verse] or [quote], respectively, and lasts until a blank line
|
499
542
|
AttributeList.rekey(attributes, ['style', 'attribution', 'citetitle'])
|
500
|
-
reader.
|
543
|
+
reader.unshift_line this_line
|
501
544
|
buffer = reader.grab_lines_until(:break_on_blank_lines => true)
|
502
545
|
buffer.last.chomp! unless buffer.empty?
|
503
546
|
block = Block.new(parent, quote_context, buffer)
|
@@ -505,7 +548,7 @@ class Asciidoctor::Lexer
|
|
505
548
|
# a floating (i.e., discrete) title
|
506
549
|
elsif ['float', 'discrete'].include?(attributes[1]) && is_section_title?(this_line, reader.peek_line)
|
507
550
|
attributes['style'] = attributes[1]
|
508
|
-
reader.
|
551
|
+
reader.unshift_line this_line
|
509
552
|
float_id, float_title, float_level, _ = parse_section_title reader
|
510
553
|
block = Block.new(parent, :floating_title)
|
511
554
|
if float_id.nil? || float_id.empty?
|
@@ -522,9 +565,9 @@ class Asciidoctor::Lexer
|
|
522
565
|
|
523
566
|
# a paragraph - contiguous nonblank/noncontinuation lines
|
524
567
|
else
|
525
|
-
reader.
|
568
|
+
reader.unshift_line this_line
|
526
569
|
buffer = reader.grab_lines_until(:break_on_blank_lines => true, :preserve_last_line => true, :skip_line_comments => true) {|line|
|
527
|
-
|
570
|
+
is_delimited_block?(line) || line.match(REGEXP[:attr_line]) ||
|
528
571
|
# next list item can be directly adjacent to paragraph of previous list item
|
529
572
|
context == :dlist && line.match(REGEXP[:dlist])
|
530
573
|
# not sure if there are any cases when we need this check for other list types
|
@@ -544,8 +587,8 @@ class Asciidoctor::Lexer
|
|
544
587
|
buffer[0] = admonition.post_match
|
545
588
|
block = Block.new(parent, :admonition, buffer)
|
546
589
|
attributes['style'] = admonition[1]
|
547
|
-
attributes['name'] = admonition[1].downcase
|
548
|
-
attributes['caption'] ||=
|
590
|
+
attributes['name'] = admonition_name = admonition[1].downcase
|
591
|
+
attributes['caption'] ||= document.attributes["#{admonition_name}-caption"]
|
549
592
|
else
|
550
593
|
buffer.last.chomp!
|
551
594
|
block = Block.new(parent, :paragraph, buffer)
|
@@ -578,17 +621,37 @@ class Asciidoctor::Lexer
|
|
578
621
|
# Public: Determines whether this line is the start of any of the delimited blocks
|
579
622
|
#
|
580
623
|
# returns the match data if this line is the first line of a delimited block or nil if not
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
624
|
+
def self.is_delimited_block?(line, return_match_data = false)
|
625
|
+
line_len = line.length
|
626
|
+
# optimized for best performance
|
627
|
+
if line_len > 2
|
628
|
+
if line_len == 3
|
629
|
+
tip = line.chop
|
630
|
+
tl = 2
|
631
|
+
else
|
632
|
+
tip = line[0..3]
|
633
|
+
tl = 4
|
634
|
+
|
635
|
+
# special case for fenced code blocks
|
636
|
+
tip_alt = tip.chop
|
637
|
+
if tip_alt == '```' || tip_alt == '~~~'
|
638
|
+
tip = tip_alt
|
639
|
+
tl = 3
|
640
|
+
end
|
641
|
+
end
|
642
|
+
|
643
|
+
if DELIMITED_BLOCKS.has_key? tip
|
644
|
+
# if tip is the full line
|
645
|
+
if tl == line_len - 1
|
646
|
+
return_match_data ? BlockMatchData.new(DELIMITED_BLOCKS[tip], tip, tip) : true
|
647
|
+
elsif match = line.match(REGEXP[:any_blk])
|
648
|
+
return_match_data ? BlockMatchData.new(DELIMITED_BLOCKS[tip], tip, match[0]) : true
|
649
|
+
else
|
650
|
+
nil
|
651
|
+
end
|
652
|
+
else
|
653
|
+
nil
|
654
|
+
end
|
592
655
|
else
|
593
656
|
nil
|
594
657
|
end
|
@@ -610,9 +673,9 @@ class Asciidoctor::Lexer
|
|
610
673
|
else
|
611
674
|
list_block.level = 1
|
612
675
|
end
|
613
|
-
|
676
|
+
Debug.debug { "Created #{list_type} block: #{list_block}" }
|
614
677
|
|
615
|
-
while reader.
|
678
|
+
while reader.has_more_lines? && (match = reader.peek_line.match(REGEXP[list_type]))
|
616
679
|
|
617
680
|
marker = resolve_list_marker(list_type, match[1])
|
618
681
|
|
@@ -648,7 +711,7 @@ class Asciidoctor::Lexer
|
|
648
711
|
items << list_item unless list_item.nil?
|
649
712
|
list_item = nil
|
650
713
|
|
651
|
-
reader.
|
714
|
+
reader.skip_blank_lines
|
652
715
|
end
|
653
716
|
|
654
717
|
list_block
|
@@ -707,7 +770,7 @@ class Asciidoctor::Lexer
|
|
707
770
|
|
708
771
|
begin
|
709
772
|
pairs << next_list_item(reader, block, match, sibling_pattern)
|
710
|
-
end while reader.
|
773
|
+
end while reader.has_more_lines? && match = reader.peek_line.match(sibling_pattern)
|
711
774
|
|
712
775
|
block
|
713
776
|
end
|
@@ -750,14 +813,19 @@ class Asciidoctor::Lexer
|
|
750
813
|
# first skip the line with the marker / term
|
751
814
|
reader.get_line
|
752
815
|
list_item_reader = Reader.new grab_lines_for_list_item(reader, list_type, sibling_trait, has_text)
|
753
|
-
if list_item_reader.
|
816
|
+
if list_item_reader.has_more_lines?
|
754
817
|
comment_lines = list_item_reader.consume_line_comments
|
755
818
|
subsequent_line = list_item_reader.peek_line
|
756
819
|
list_item_reader.unshift(*comment_lines) unless comment_lines.empty?
|
757
820
|
|
758
821
|
if !subsequent_line.nil?
|
759
822
|
continuation_connects_first_block = (subsequent_line == "\n")
|
760
|
-
|
823
|
+
# if there's no continuation connecting the first block, then
|
824
|
+
# treat the lines as paragraph text (activated when has_text = false)
|
825
|
+
if !continuation_connects_first_block && list_type != :dlist
|
826
|
+
has_text = false
|
827
|
+
end
|
828
|
+
content_adjacent = !subsequent_line.chomp.empty?
|
761
829
|
else
|
762
830
|
continuation_connects_first_block = false
|
763
831
|
content_adjacent = false
|
@@ -766,7 +834,7 @@ class Asciidoctor::Lexer
|
|
766
834
|
# only relevant for :dlist
|
767
835
|
options = {:text => !has_text}
|
768
836
|
|
769
|
-
while list_item_reader.
|
837
|
+
while list_item_reader.has_more_lines?
|
770
838
|
new_block = next_block(list_item_reader, list_block, {}, options)
|
771
839
|
list_item.blocks << new_block unless new_block.nil?
|
772
840
|
end
|
@@ -815,7 +883,7 @@ class Asciidoctor::Lexer
|
|
815
883
|
# it gets associated with the outermost block
|
816
884
|
detached_continuation = nil
|
817
885
|
|
818
|
-
while reader.
|
886
|
+
while reader.has_more_lines?
|
819
887
|
this_line = reader.get_line
|
820
888
|
|
821
889
|
# if we've arrived at a sibling item in this list, we've captured
|
@@ -846,13 +914,12 @@ class Asciidoctor::Lexer
|
|
846
914
|
|
847
915
|
# a delimited block immediately breaks the list unless preceded
|
848
916
|
# by a list continuation (they are harsh like that ;0)
|
849
|
-
if match =
|
917
|
+
if match = is_delimited_block?(this_line, true)
|
850
918
|
if continuation == :active
|
851
919
|
buffer << this_line
|
852
920
|
# grab all the lines in the block, leaving the delimiters in place
|
853
921
|
# we're being more strict here about the terminator, but I think that's a good thing
|
854
|
-
terminator
|
855
|
-
buffer.concat reader.grab_lines_until(:terminator => terminator, :grab_last_line => true)
|
922
|
+
buffer.concat reader.grab_lines_until(:terminator => match.terminator, :grab_last_line => true)
|
856
923
|
continuation = :inactive
|
857
924
|
else
|
858
925
|
break
|
@@ -868,7 +935,7 @@ class Asciidoctor::Lexer
|
|
868
935
|
# if we don't process it as a whole, then a line in it that looks like a
|
869
936
|
# list item will throw off the exit from it
|
870
937
|
if this_line.match(REGEXP[:lit_par])
|
871
|
-
reader.
|
938
|
+
reader.unshift_line this_line
|
872
939
|
buffer.concat reader.grab_lines_until(
|
873
940
|
:preserve_last_line => true,
|
874
941
|
:break_on_blank_lines => true,
|
@@ -879,7 +946,7 @@ class Asciidoctor::Lexer
|
|
879
946
|
}
|
880
947
|
continuation = :inactive
|
881
948
|
# let block metadata play out until we find the block
|
882
|
-
elsif this_line.match(REGEXP[:blk_title]) || this_line.match(REGEXP[:attr_line])
|
949
|
+
elsif this_line.match(REGEXP[:blk_title]) || this_line.match(REGEXP[:attr_line]) || this_line.match(REGEXP[:attr_entry])
|
883
950
|
buffer << this_line
|
884
951
|
else
|
885
952
|
if nested_list_type = (within_nested_list ? [:dlist] : NESTABLE_LIST_CONTEXTS).detect {|ctx| this_line.match(REGEXP[ctx]) }
|
@@ -911,7 +978,7 @@ class Asciidoctor::Lexer
|
|
911
978
|
if has_text
|
912
979
|
# slurp up any literal paragraph offset by blank lines
|
913
980
|
if this_line.match(REGEXP[:lit_par])
|
914
|
-
reader.
|
981
|
+
reader.unshift_line this_line
|
915
982
|
buffer.concat reader.grab_lines_until(
|
916
983
|
:preserve_last_line => true,
|
917
984
|
:break_on_blank_lines => true,
|
@@ -955,20 +1022,21 @@ class Asciidoctor::Lexer
|
|
955
1022
|
this_line = nil
|
956
1023
|
end
|
957
1024
|
|
958
|
-
reader.
|
1025
|
+
reader.unshift_line this_line if !this_line.nil?
|
959
1026
|
|
960
1027
|
if detached_continuation
|
961
1028
|
buffer.delete_at detached_continuation
|
962
1029
|
end
|
963
1030
|
|
964
1031
|
# strip trailing blank lines to prevent empty blocks
|
965
|
-
buffer.pop while !buffer.empty? && buffer.last.
|
1032
|
+
buffer.pop while !buffer.empty? && buffer.last.chomp.empty?
|
966
1033
|
|
967
1034
|
# We do need to replace the optional trailing continuation
|
968
1035
|
# a blank line would have served the same purpose in the document
|
969
1036
|
if !buffer.empty? && buffer.last.chomp == LIST_CONTINUATION
|
970
1037
|
buffer.pop
|
971
1038
|
end
|
1039
|
+
|
972
1040
|
#puts "BUFFER[#{list_type},#{sibling_trait}]>#{buffer.join}<BUFFER"
|
973
1041
|
#puts "BUFFER[#{list_type},#{sibling_trait}]>#{buffer}<BUFFER"
|
974
1042
|
|
@@ -997,14 +1065,19 @@ class Asciidoctor::Lexer
|
|
997
1065
|
if attributes[1]
|
998
1066
|
section.sectname = attributes[1]
|
999
1067
|
section.special = true
|
1000
|
-
|
1001
|
-
|
1068
|
+
document = parent.document
|
1069
|
+
if section.sectname == 'appendix' &&
|
1070
|
+
!attributes.has_key?('caption') &&
|
1071
|
+
!document.attributes.has_key?('caption')
|
1072
|
+
number = document.counter('appendix-number', 'A')
|
1073
|
+
attributes['caption'] = "#{document.attributes['appendix-caption']} #{number}: "
|
1074
|
+
Document::AttributeEntry.new('appendix-number', number).save_to(attributes)
|
1002
1075
|
end
|
1003
1076
|
else
|
1004
1077
|
section.sectname = "sect#{section.level}"
|
1005
1078
|
end
|
1006
1079
|
section.update_attributes(attributes)
|
1007
|
-
reader.
|
1080
|
+
reader.skip_blank_lines
|
1008
1081
|
|
1009
1082
|
section
|
1010
1083
|
end
|
@@ -1014,7 +1087,7 @@ class Asciidoctor::Lexer
|
|
1014
1087
|
#
|
1015
1088
|
# line - the String line from under the section title.
|
1016
1089
|
def self.section_level(line)
|
1017
|
-
char = line.
|
1090
|
+
char = line.chomp.chars.to_a.uniq
|
1018
1091
|
case char
|
1019
1092
|
when ['=']; 0
|
1020
1093
|
when ['-']; 1
|
@@ -1032,21 +1105,25 @@ class Asciidoctor::Lexer
|
|
1032
1105
|
|
1033
1106
|
# Internal: Checks if the next line on the Reader is a section title
|
1034
1107
|
#
|
1035
|
-
# reader
|
1108
|
+
# reader - the source Reader
|
1109
|
+
# attributes - a Hash of attributes collected above the current line
|
1036
1110
|
#
|
1037
1111
|
# returns the section level if the Reader is positioned at a section title,
|
1038
1112
|
# false otherwise
|
1039
1113
|
def self.is_next_line_section?(reader, attributes)
|
1040
1114
|
return false if !attributes[1].nil? && ['float', 'discrete'].include?(attributes[1])
|
1041
|
-
if reader.
|
1042
|
-
|
1043
|
-
|
1044
|
-
reader.unshift line1
|
1045
|
-
else
|
1046
|
-
return false
|
1047
|
-
end
|
1115
|
+
return false if !reader.has_more_lines?
|
1116
|
+
is_section_title?(*reader.peek_lines(2))
|
1117
|
+
end
|
1048
1118
|
|
1049
|
-
|
1119
|
+
# Internal: Convenience API for checking if the next line on the Reader is the document title
|
1120
|
+
#
|
1121
|
+
# reader - the source Reader
|
1122
|
+
# attributes - a Hash of attributes collected above the current line
|
1123
|
+
#
|
1124
|
+
# returns true if the Reader is positioned at the document title, false otherwise
|
1125
|
+
def self.is_next_line_document_title?(reader, attributes)
|
1126
|
+
is_next_line_section?(reader, attributes) == 0
|
1050
1127
|
end
|
1051
1128
|
|
1052
1129
|
# Public: Checks if these lines are a section title
|
@@ -1172,15 +1249,14 @@ class Asciidoctor::Lexer
|
|
1172
1249
|
# # => {'author' => 'Author Name', 'firstname' => 'Author', 'lastname' => 'Name', 'email' => 'author@example.org',
|
1173
1250
|
# # 'revnumber' => '1.0', 'revdate' => '2012-12-21', 'revremark' => 'Coincide w/ end of world.'}
|
1174
1251
|
def self.parse_header_metadata(reader, document = nil)
|
1175
|
-
#
|
1176
|
-
|
1252
|
+
# NOTE this will discard away any comment lines, but not skip blank lines
|
1253
|
+
process_attribute_entries(reader, document)
|
1254
|
+
|
1255
|
+
metadata = {}
|
1177
1256
|
|
1178
|
-
|
1179
|
-
author_initials = metadata['authorinitials']
|
1180
|
-
if reader.has_lines? && !reader.peek_line.strip.empty?
|
1257
|
+
if reader.has_more_lines? && !reader.peek_line.chomp.empty?
|
1181
1258
|
author_line = reader.get_line
|
1182
|
-
match = author_line.match(REGEXP[:author_info])
|
1183
|
-
if match
|
1259
|
+
if match = author_line.match(REGEXP[:author_info])
|
1184
1260
|
metadata['firstname'] = fname = match[1].tr('_', ' ')
|
1185
1261
|
metadata['author'] = fname
|
1186
1262
|
metadata['authorinitials'] = fname[0, 1]
|
@@ -1200,28 +1276,36 @@ class Asciidoctor::Lexer
|
|
1200
1276
|
metadata['authorinitials'] = metadata['firstname'][0, 1]
|
1201
1277
|
end
|
1202
1278
|
|
1203
|
-
#
|
1204
|
-
|
1279
|
+
# NOTE this will discard away any comment lines, but not skip blank lines
|
1280
|
+
process_attribute_entries(reader, document)
|
1205
1281
|
|
1206
|
-
|
1207
|
-
comment_lines += reader.consume_comments
|
1208
|
-
|
1209
|
-
if reader.has_lines? && !reader.peek_line.strip.empty?
|
1282
|
+
if reader.has_more_lines? && !reader.peek_line.chomp.empty?
|
1210
1283
|
rev_line = reader.get_line
|
1211
|
-
match = rev_line.match(REGEXP[:revision_info])
|
1212
|
-
|
1213
|
-
metadata['
|
1214
|
-
metadata['
|
1215
|
-
metadata['revremark'] = match[3] unless match[3].nil?
|
1284
|
+
if match = rev_line.match(REGEXP[:revision_info])
|
1285
|
+
metadata['revdate'] = match[2].strip
|
1286
|
+
metadata['revnumber'] = match[1].rstrip unless match[1].nil?
|
1287
|
+
metadata['revremark'] = match[3].rstrip unless match[3].nil?
|
1216
1288
|
else
|
1217
|
-
|
1289
|
+
# throw it back
|
1290
|
+
reader.unshift_line rev_line
|
1218
1291
|
end
|
1219
1292
|
end
|
1220
1293
|
|
1221
|
-
|
1294
|
+
# NOTE this will discard away any comment lines, but not skip blank lines
|
1295
|
+
process_attribute_entries(reader, document)
|
1296
|
+
|
1297
|
+
reader.skip_blank_lines
|
1298
|
+
|
1299
|
+
# apply header subs and assign to document
|
1300
|
+
if !document.nil?
|
1301
|
+
metadata.map do |key, val|
|
1302
|
+
val = document.apply_header_subs(val)
|
1303
|
+
document.attributes[key] = val if !document.attributes.has_key?(key)
|
1304
|
+
val
|
1305
|
+
end
|
1306
|
+
end
|
1222
1307
|
end
|
1223
1308
|
|
1224
|
-
reader.unshift(*comment_lines)
|
1225
1309
|
metadata
|
1226
1310
|
end
|
1227
1311
|
|
@@ -1241,7 +1325,7 @@ class Asciidoctor::Lexer
|
|
1241
1325
|
def self.parse_block_metadata_lines(reader, parent, attributes = {}, options = {})
|
1242
1326
|
while parse_block_metadata_line(reader, parent, attributes, options)
|
1243
1327
|
# discard the line just processed
|
1244
|
-
reader.
|
1328
|
+
reader.advance
|
1245
1329
|
reader.skip_blank_lines
|
1246
1330
|
end
|
1247
1331
|
attributes
|
@@ -1268,15 +1352,15 @@ class Asciidoctor::Lexer
|
|
1268
1352
|
#
|
1269
1353
|
# returns true if the line contains metadata, otherwise false
|
1270
1354
|
def self.parse_block_metadata_line(reader, parent, attributes, options = {})
|
1271
|
-
return false if !reader.
|
1355
|
+
return false if !reader.has_more_lines?
|
1272
1356
|
next_line = reader.peek_line
|
1273
|
-
if next_line.match(REGEXP[:
|
1274
|
-
# do nothing, we'll skip it
|
1275
|
-
# QUESTION should we parse block comments here instead of next_block?
|
1276
|
-
# disable until we can agree what the current line is coming in
|
1277
|
-
elsif match = next_line.match(REGEXP[:comment_blk])
|
1357
|
+
if (commentish = next_line.start_with?('//')) && (match = next_line.match(REGEXP[:comment_blk]))
|
1278
1358
|
terminator = match[0]
|
1279
|
-
reader.grab_lines_until(:skip_first_line => true, :preserve_last_line => true, :terminator => terminator)
|
1359
|
+
reader.grab_lines_until(:skip_first_line => true, :preserve_last_line => true, :terminator => terminator, :preprocess => false)
|
1360
|
+
elsif commentish && next_line.match(REGEXP[:comment])
|
1361
|
+
# do nothing, we'll skip it
|
1362
|
+
elsif !options[:text] && (match = next_line.match(REGEXP[:attr_entry]))
|
1363
|
+
process_attribute_entry(reader, parent, attributes, match)
|
1280
1364
|
elsif match = next_line.match(REGEXP[:anchor])
|
1281
1365
|
id, reftext = match[1].split(',')
|
1282
1366
|
attributes['id'] = id
|
@@ -1290,7 +1374,6 @@ class Asciidoctor::Lexer
|
|
1290
1374
|
elsif match = next_line.match(REGEXP[:blk_attr_list])
|
1291
1375
|
AttributeList.new(parent.document.sub_attributes(match[1]), parent.document).parse_into(attributes)
|
1292
1376
|
# NOTE title doesn't apply to section, but we need to stash it for the first block
|
1293
|
-
# TODO need test for this getting passed on to first block after section if found above section
|
1294
1377
|
# TODO should issue an error if this is found above the document title
|
1295
1378
|
elsif !options[:text] && (match = next_line.match(REGEXP[:blk_title]))
|
1296
1379
|
attributes['title'] = match[1]
|
@@ -1301,6 +1384,57 @@ class Asciidoctor::Lexer
|
|
1301
1384
|
true
|
1302
1385
|
end
|
1303
1386
|
|
1387
|
+
def self.process_attribute_entries(reader, parent, attributes = nil)
|
1388
|
+
reader.skip_comment_lines
|
1389
|
+
while process_attribute_entry(reader, parent, attributes)
|
1390
|
+
# discard line just processed
|
1391
|
+
reader.advance
|
1392
|
+
reader.skip_comment_lines
|
1393
|
+
end
|
1394
|
+
end
|
1395
|
+
|
1396
|
+
def self.process_attribute_entry(reader, parent, attributes = nil, match = nil)
|
1397
|
+
match ||= reader.has_more_lines? ? reader.peek_line.match(REGEXP[:attr_entry]) : nil
|
1398
|
+
if match
|
1399
|
+
name = match[1]
|
1400
|
+
value = match[2].nil? ? '' : match[2]
|
1401
|
+
if value.end_with? LINE_BREAK
|
1402
|
+
value.chop!.rstrip!
|
1403
|
+
while reader.advance
|
1404
|
+
next_line = reader.peek_line.strip
|
1405
|
+
break if next_line.empty?
|
1406
|
+
if next_line.end_with? LINE_BREAK
|
1407
|
+
value = "#{value} #{next_line.chop.rstrip}"
|
1408
|
+
else
|
1409
|
+
value = "#{value} #{next_line}"
|
1410
|
+
break
|
1411
|
+
end
|
1412
|
+
end
|
1413
|
+
end
|
1414
|
+
|
1415
|
+
if name.end_with?('!')
|
1416
|
+
# a nil value signals the attribute should be deleted (undefined)
|
1417
|
+
value = nil
|
1418
|
+
name = name.chop
|
1419
|
+
end
|
1420
|
+
|
1421
|
+
name = sanitize_attribute_name(name)
|
1422
|
+
accessible = true
|
1423
|
+
if !parent.nil?
|
1424
|
+
accessible = value.nil? ?
|
1425
|
+
parent.document.delete_attribute(name) :
|
1426
|
+
parent.document.set_attribute(name, value)
|
1427
|
+
end
|
1428
|
+
|
1429
|
+
if !attributes.nil?
|
1430
|
+
Document::AttributeEntry.new(name, value).save_to(attributes) if accessible
|
1431
|
+
end
|
1432
|
+
true
|
1433
|
+
else
|
1434
|
+
false
|
1435
|
+
end
|
1436
|
+
end
|
1437
|
+
|
1304
1438
|
# Internal: Resolve the 0-index marker for this list item
|
1305
1439
|
#
|
1306
1440
|
# For ordered lists, match the marker used for this list item against the
|
@@ -1387,6 +1521,7 @@ class Asciidoctor::Lexer
|
|
1387
1521
|
end
|
1388
1522
|
|
1389
1523
|
if validate && expected != actual
|
1524
|
+
# FIXME I need a reader reference or line number to report line number
|
1390
1525
|
puts "asciidoctor: WARNING: list item index: expected #{expected}, got #{actual}"
|
1391
1526
|
end
|
1392
1527
|
|
@@ -1441,8 +1576,8 @@ class Asciidoctor::Lexer
|
|
1441
1576
|
|
1442
1577
|
table_reader.skip_blank_lines
|
1443
1578
|
|
1444
|
-
parser_ctx =
|
1445
|
-
while table_reader.
|
1579
|
+
parser_ctx = Table::ParserContext.new(table, attributes)
|
1580
|
+
while table_reader.has_more_lines?
|
1446
1581
|
line = table_reader.get_line
|
1447
1582
|
|
1448
1583
|
if parser_ctx.format == 'psv'
|
@@ -1507,7 +1642,7 @@ class Asciidoctor::Lexer
|
|
1507
1642
|
|
1508
1643
|
table_reader.skip_blank_lines unless parser_ctx.cell_open?
|
1509
1644
|
|
1510
|
-
if !table_reader.
|
1645
|
+
if !table_reader.has_more_lines?
|
1511
1646
|
parser_ctx.close_cell true
|
1512
1647
|
end
|
1513
1648
|
end
|
@@ -1597,7 +1732,7 @@ class Asciidoctor::Lexer
|
|
1597
1732
|
|
1598
1733
|
if m = line.match(REGEXP[:table_cellspec][pos])
|
1599
1734
|
spec = {}
|
1600
|
-
return [spec, line] if m[0].
|
1735
|
+
return [spec, line] if m[0].chomp.empty?
|
1601
1736
|
rest = (pos == :start ? m.post_match : m.pre_match)
|
1602
1737
|
if m[1]
|
1603
1738
|
colspec, rowspec = m[1].split '.'
|
@@ -1629,6 +1764,26 @@ class Asciidoctor::Lexer
|
|
1629
1764
|
[spec, rest]
|
1630
1765
|
end
|
1631
1766
|
|
1767
|
+
# Public: Convert a string to a legal attribute name.
|
1768
|
+
#
|
1769
|
+
# name - the String name of the attribute
|
1770
|
+
#
|
1771
|
+
# Returns a String with the legal AsciiDoc attribute name.
|
1772
|
+
#
|
1773
|
+
# Examples
|
1774
|
+
#
|
1775
|
+
# sanitize_attribute_name('Foo Bar')
|
1776
|
+
# => 'foobar'
|
1777
|
+
#
|
1778
|
+
# sanitize_attribute_name('foo')
|
1779
|
+
# => 'foo'
|
1780
|
+
#
|
1781
|
+
# sanitize_attribute_name('Foo 3 #-Billy')
|
1782
|
+
# => 'foo3-billy'
|
1783
|
+
def self.sanitize_attribute_name(name)
|
1784
|
+
name.gsub(REGEXP[:illegal_attr_name_chars], '').downcase
|
1785
|
+
end
|
1786
|
+
|
1632
1787
|
# Internal: Converts a Roman numeral to an integer value.
|
1633
1788
|
#
|
1634
1789
|
# value - The String Roman numeral to convert
|
@@ -1651,3 +1806,4 @@ class Asciidoctor::Lexer
|
|
1651
1806
|
result
|
1652
1807
|
end
|
1653
1808
|
end
|
1809
|
+
end
|