asciidoctor 0.0.9 → 0.1.0
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 +163 -41
- data/Rakefile +3 -1
- data/asciidoctor.gemspec +13 -5
- data/bin/asciidoctor +6 -3
- data/bin/asciidoctor-safe +13 -0
- data/lib/asciidoctor.rb +237 -26
- data/lib/asciidoctor/abstract_node.rb +27 -17
- data/lib/asciidoctor/attribute_list.rb +6 -0
- data/lib/asciidoctor/backends/base_template.rb +3 -4
- data/lib/asciidoctor/backends/docbook45.rb +114 -55
- data/lib/asciidoctor/backends/html5.rb +173 -104
- data/lib/asciidoctor/cli/invoker.rb +105 -0
- data/lib/asciidoctor/cli/options.rb +146 -0
- data/lib/asciidoctor/document.rb +135 -35
- data/lib/asciidoctor/lexer.rb +86 -33
- data/lib/asciidoctor/list_item.rb +2 -2
- data/lib/asciidoctor/reader.rb +6 -7
- data/lib/asciidoctor/section.rb +17 -5
- data/lib/asciidoctor/substituters.rb +216 -97
- data/lib/asciidoctor/table.rb +9 -2
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +212 -0
- data/man/asciidoctor.ad +156 -0
- data/test/attributes_test.rb +108 -5
- data/test/blocks_test.rb +102 -15
- data/test/document_test.rb +214 -3
- data/test/fixtures/encoding.asciidoc +4 -0
- data/test/fixtures/sample.asciidoc +26 -0
- data/test/invoker_test.rb +254 -0
- data/test/lexer_test.rb +53 -0
- data/test/links_test.rb +30 -0
- data/test/lists_test.rb +648 -9
- data/test/options_test.rb +68 -0
- data/test/paragraphs_test.rb +65 -1
- data/test/reader_test.rb +18 -4
- data/test/{headers_test.rb → sections_test.rb} +237 -0
- data/test/substitutions_test.rb +247 -5
- data/test/tables_test.rb +22 -4
- data/test/test_helper.rb +47 -3
- data/test/text_test.rb +20 -4
- metadata +34 -6
- data/noof.rb +0 -16
data/lib/asciidoctor/lexer.rb
CHANGED
@@ -45,10 +45,11 @@ class Asciidoctor::Lexer
|
|
45
45
|
def self.parse(reader, document)
|
46
46
|
# process and plow away any attribute lines that proceed the first block so
|
47
47
|
# we can get at the document title, if present, then begin parsing blocks
|
48
|
+
reader.skip_blank_lines
|
48
49
|
attributes = parse_block_metadata_lines(reader, document)
|
49
50
|
|
50
51
|
# by processing the header here, we enforce its position at head of the document
|
51
|
-
next_level = is_next_line_section? reader
|
52
|
+
next_level = is_next_line_section? reader, attributes
|
52
53
|
if next_level == 0
|
53
54
|
title_info = parse_section_title(reader)
|
54
55
|
document.title = title_info[1]
|
@@ -107,7 +108,7 @@ class Asciidoctor::Lexer
|
|
107
108
|
# NOTE we could drop a hint in the attributes to indicate
|
108
109
|
# that we are at a section title (so we don't have to check)
|
109
110
|
if parent.is_a?(Document) && parent.blocks.empty? &&
|
110
|
-
(parent.has_header? || !is_next_line_section?(reader))
|
111
|
+
(parent.has_header? || !is_next_line_section?(reader, attributes))
|
111
112
|
|
112
113
|
if parent.has_header?
|
113
114
|
preamble = Block.new(parent, :preamble)
|
@@ -147,7 +148,7 @@ class Asciidoctor::Lexer
|
|
147
148
|
while reader.has_lines?
|
148
149
|
parse_block_metadata_lines(reader, section, attributes)
|
149
150
|
|
150
|
-
next_level = is_next_line_section?
|
151
|
+
next_level = is_next_line_section? reader, attributes
|
151
152
|
if next_level
|
152
153
|
doctype = parent.document.doctype
|
153
154
|
if next_level == 0 && doctype != 'book'
|
@@ -236,14 +237,12 @@ class Asciidoctor::Lexer
|
|
236
237
|
document = parent.document
|
237
238
|
context = parent.is_a?(Block) ? parent.context : nil
|
238
239
|
block = nil
|
239
|
-
title = nil
|
240
|
-
caption = nil
|
241
240
|
|
242
241
|
while reader.has_lines? && block.nil?
|
243
242
|
if parse_metadata && parse_block_metadata_line(reader, document, attributes, options)
|
244
243
|
reader.next_line
|
245
244
|
next
|
246
|
-
elsif parse_sections && context.nil? && is_next_line_section?(reader)
|
245
|
+
elsif parse_sections && context.nil? && is_next_line_section?(reader, attributes)
|
247
246
|
block, attributes = next_section(reader, parent, attributes)
|
248
247
|
break
|
249
248
|
end
|
@@ -275,6 +274,11 @@ class Asciidoctor::Lexer
|
|
275
274
|
attributes['target'] = target
|
276
275
|
document.register(:images, target)
|
277
276
|
attributes['alt'] ||= File.basename(target, File.extname(target))
|
277
|
+
# hmmm, this assignment seems like a one-off
|
278
|
+
block.title = attributes['title']
|
279
|
+
if block.title? && attributes['caption'].nil?
|
280
|
+
attributes['caption'] = "Figure #{document.counter('figure-number')}. "
|
281
|
+
end
|
278
282
|
else
|
279
283
|
# drop the line if target resolves to nothing
|
280
284
|
block = nil
|
@@ -362,6 +366,7 @@ class Asciidoctor::Lexer
|
|
362
366
|
elsif match = this_line.match(REGEXP[:dlist])
|
363
367
|
reader.unshift this_line
|
364
368
|
block = next_labeled_list(reader, match, parent)
|
369
|
+
AttributeList.rekey(attributes, ['style'])
|
365
370
|
|
366
371
|
elsif delimited_blk && (match = this_line.match(document.nested? ? REGEXP[:table_nested] : REGEXP[:table]))
|
367
372
|
# table is surrounded by lines starting with a | followed by 3 or more '=' chars
|
@@ -369,6 +374,11 @@ class Asciidoctor::Lexer
|
|
369
374
|
AttributeList.rekey(attributes, ['style'])
|
370
375
|
table_reader = Reader.new reader.grab_lines_until(:terminator => terminator, :skip_line_comments => true)
|
371
376
|
block = next_table(table_reader, parent, attributes)
|
377
|
+
# hmmm, this assignment seems like a one-off
|
378
|
+
block.title = attributes['title']
|
379
|
+
if block.title? && attributes['caption'].nil?
|
380
|
+
attributes['caption'] = "Table #{document.counter('table-number')}. "
|
381
|
+
end
|
372
382
|
|
373
383
|
# FIXME violates DRY because it's a duplication of other block parsing
|
374
384
|
elsif delimited_blk && (match = this_line.match(REGEXP[:example]))
|
@@ -381,6 +391,11 @@ class Asciidoctor::Lexer
|
|
381
391
|
attributes['caption'] ||= admonition_style.capitalize
|
382
392
|
else
|
383
393
|
block = Block.new(parent, :example)
|
394
|
+
# hmmm, this assignment seems like a one-off
|
395
|
+
block.title = attributes['title']
|
396
|
+
if block.title? && attributes['caption'].nil?
|
397
|
+
attributes['caption'] = "Example #{document.counter('example-number')}. "
|
398
|
+
end
|
384
399
|
end
|
385
400
|
buffer = Reader.new reader.grab_lines_until(:terminator => terminator)
|
386
401
|
|
@@ -487,7 +502,26 @@ class Asciidoctor::Lexer
|
|
487
502
|
buffer.last.chomp! unless buffer.empty?
|
488
503
|
block = Block.new(parent, quote_context, buffer)
|
489
504
|
|
490
|
-
|
505
|
+
# a floating (i.e., discrete) title
|
506
|
+
elsif ['float', 'discrete'].include?(attributes[1]) && is_section_title?(this_line, reader.peek_line)
|
507
|
+
attributes['style'] = attributes[1]
|
508
|
+
reader.unshift this_line
|
509
|
+
float_id, float_title, float_level, _ = parse_section_title reader
|
510
|
+
block = Block.new(parent, :floating_title)
|
511
|
+
if float_id.nil? || float_id.empty?
|
512
|
+
# FIXME remove hack of creating throwaway Section to get at the generate_id method
|
513
|
+
tmp_sect = Section.new(parent)
|
514
|
+
tmp_sect.title = float_title
|
515
|
+
block.id = tmp_sect.generate_id
|
516
|
+
else
|
517
|
+
block.id = float_id
|
518
|
+
@document.register(:ids, [float_id, float_title])
|
519
|
+
end
|
520
|
+
block.level = float_level
|
521
|
+
block.title = float_title
|
522
|
+
|
523
|
+
# a paragraph - contiguous nonblank/noncontinuation lines
|
524
|
+
else
|
491
525
|
reader.unshift this_line
|
492
526
|
buffer = reader.grab_lines_until(:break_on_blank_lines => true, :preserve_last_line => true, :skip_line_comments => true) {|line|
|
493
527
|
delimited_block?(line) || line.match(REGEXP[:attr_line]) ||
|
@@ -524,8 +558,8 @@ class Asciidoctor::Lexer
|
|
524
558
|
# so handle accordingly
|
525
559
|
if !block.nil?
|
526
560
|
block.id = attributes['id'] if attributes.has_key?('id')
|
527
|
-
block.title
|
528
|
-
block.caption ||= caption unless block.is_a?(Section)
|
561
|
+
block.title = attributes['title'] unless block.title?
|
562
|
+
block.caption ||= attributes['caption'] unless block.is_a?(Section)
|
529
563
|
# AsciiDoc always use [id] as the reftext in HTML output,
|
530
564
|
# but I'd like to do better in Asciidoctor
|
531
565
|
if block.id && block.title? && !attributes.has_key?('reftext')
|
@@ -812,22 +846,23 @@ class Asciidoctor::Lexer
|
|
812
846
|
|
813
847
|
# a delimited block immediately breaks the list unless preceded
|
814
848
|
# by a list continuation (they are harsh like that ;0)
|
815
|
-
if
|
816
|
-
# technically attr_line only breaks if ensuing line is not a list item
|
817
|
-
# which really means attr_line only breaks if it's acting as a block delimiter
|
818
|
-
(list_type == :dlist && match = this_line.match(REGEXP[:attr_line]))
|
819
|
-
terminator = match[0]
|
849
|
+
if match = delimited_block?(this_line)
|
820
850
|
if continuation == :active
|
821
851
|
buffer << this_line
|
822
852
|
# grab all the lines in the block, leaving the delimiters in place
|
823
853
|
# we're being more strict here about the terminator, but I think that's a good thing
|
854
|
+
terminator = match[0]
|
824
855
|
buffer.concat reader.grab_lines_until(:terminator => terminator, :grab_last_line => true)
|
825
856
|
continuation = :inactive
|
826
857
|
else
|
827
858
|
break
|
828
859
|
end
|
860
|
+
# technically attr_line only breaks if ensuing line is not a list item
|
861
|
+
# which really means attr_line only breaks if it's acting as a block delimiter
|
862
|
+
elsif list_type == :dlist && continuation != :active && this_line.match(REGEXP[:attr_line])
|
863
|
+
break
|
829
864
|
else
|
830
|
-
if continuation == :active && !this_line.
|
865
|
+
if continuation == :active && !this_line.chomp.empty?
|
831
866
|
# literal paragraphs have special considerations (and this is one of
|
832
867
|
# two entry points into one)
|
833
868
|
# if we don't process it as a whole, then a line in it that looks like a
|
@@ -837,7 +872,15 @@ class Asciidoctor::Lexer
|
|
837
872
|
buffer.concat reader.grab_lines_until(
|
838
873
|
:preserve_last_line => true,
|
839
874
|
:break_on_blank_lines => true,
|
840
|
-
:break_on_list_continuation => true)
|
875
|
+
:break_on_list_continuation => true) {|line|
|
876
|
+
# we may be in an indented list disguised as a literal paragraph
|
877
|
+
# so we need to make sure we don't slurp up a legitimate sibling
|
878
|
+
list_type == :dlist && is_sibling_list_item?(line, list_type, sibling_trait)
|
879
|
+
}
|
880
|
+
continuation = :inactive
|
881
|
+
# let block metadata play out until we find the block
|
882
|
+
elsif this_line.match(REGEXP[:blk_title]) || this_line.match(REGEXP[:attr_line])
|
883
|
+
buffer << this_line
|
841
884
|
else
|
842
885
|
if nested_list_type = (within_nested_list ? [:dlist] : NESTABLE_LIST_CONTEXTS).detect {|ctx| this_line.match(REGEXP[ctx]) }
|
843
886
|
within_nested_list = true
|
@@ -847,12 +890,12 @@ class Asciidoctor::Lexer
|
|
847
890
|
end
|
848
891
|
end
|
849
892
|
buffer << this_line
|
893
|
+
continuation = :inactive
|
850
894
|
end
|
851
|
-
|
852
|
-
elsif !prev_line.nil? && prev_line.strip.empty?
|
895
|
+
elsif !prev_line.nil? && prev_line.chomp.empty?
|
853
896
|
# advance to the next line of content
|
854
|
-
if this_line.
|
855
|
-
reader.
|
897
|
+
if this_line.chomp.empty?
|
898
|
+
reader.skip_blank_lines
|
856
899
|
this_line = reader.get_line
|
857
900
|
# if we hit eof or a sibling, stop reading
|
858
901
|
break if this_line.nil? || is_sibling_list_item?(this_line, list_type, sibling_trait)
|
@@ -872,13 +915,15 @@ class Asciidoctor::Lexer
|
|
872
915
|
buffer.concat reader.grab_lines_until(
|
873
916
|
:preserve_last_line => true,
|
874
917
|
:break_on_blank_lines => true,
|
875
|
-
:break_on_list_continuation => true)
|
918
|
+
:break_on_list_continuation => true) {|line|
|
919
|
+
# we may be in an indented list disguised as a literal paragraph
|
920
|
+
# so we need to make sure we don't slurp up a legitimate sibling
|
921
|
+
list_type == :dlist && is_sibling_list_item?(line, list_type, sibling_trait)
|
922
|
+
}
|
876
923
|
# TODO any way to combine this with the check after skipping blank lines?
|
877
924
|
elsif is_sibling_list_item?(this_line, list_type, sibling_trait)
|
878
|
-
#buffer.pop unless within_nested_list
|
879
925
|
break
|
880
926
|
elsif nested_list_type = NESTABLE_LIST_CONTEXTS.detect {|ctx| this_line.match(REGEXP[ctx]) }
|
881
|
-
#buffer.pop unless within_nested_list
|
882
927
|
buffer << this_line
|
883
928
|
within_nested_list = true
|
884
929
|
if nested_list_type == :dlist && $~[3].to_s.empty?
|
@@ -896,7 +941,7 @@ class Asciidoctor::Lexer
|
|
896
941
|
end
|
897
942
|
end
|
898
943
|
else
|
899
|
-
has_text = true if !this_line.
|
944
|
+
has_text = true if !this_line.chomp.empty?
|
900
945
|
if nested_list_type = (within_nested_list ? [:dlist] : NESTABLE_LIST_CONTEXTS).detect {|ctx| this_line.match(REGEXP[ctx]) }
|
901
946
|
within_nested_list = true
|
902
947
|
if nested_list_type == :dlist && $~[3].to_s.empty?
|
@@ -916,16 +961,16 @@ class Asciidoctor::Lexer
|
|
916
961
|
buffer.delete_at detached_continuation
|
917
962
|
end
|
918
963
|
|
919
|
-
#
|
920
|
-
|
964
|
+
# strip trailing blank lines to prevent empty blocks
|
965
|
+
buffer.pop while !buffer.empty? && buffer.last.strip.empty?
|
921
966
|
|
922
967
|
# We do need to replace the optional trailing continuation
|
923
968
|
# a blank line would have served the same purpose in the document
|
924
969
|
if !buffer.empty? && buffer.last.chomp == LIST_CONTINUATION
|
925
970
|
buffer.pop
|
926
971
|
end
|
927
|
-
#puts "BUFFER>#{buffer.join}<BUFFER"
|
928
|
-
#puts "BUFFER>#{buffer}<BUFFER"
|
972
|
+
#puts "BUFFER[#{list_type},#{sibling_trait}]>#{buffer.join}<BUFFER"
|
973
|
+
#puts "BUFFER[#{list_type},#{sibling_trait}]>#{buffer}<BUFFER"
|
929
974
|
|
930
975
|
buffer
|
931
976
|
end
|
@@ -949,6 +994,15 @@ class Asciidoctor::Lexer
|
|
949
994
|
section.id ||= section.generate_id
|
950
995
|
end
|
951
996
|
|
997
|
+
if attributes[1]
|
998
|
+
section.sectname = attributes[1]
|
999
|
+
section.special = true
|
1000
|
+
if section.sectname == 'appendix'
|
1001
|
+
attributes['caption'] ||= "Appendix #{parent.document.counter('appendix-number', 'A')}: "
|
1002
|
+
end
|
1003
|
+
else
|
1004
|
+
section.sectname = "sect#{section.level}"
|
1005
|
+
end
|
952
1006
|
section.update_attributes(attributes)
|
953
1007
|
reader.skip_blank
|
954
1008
|
|
@@ -978,14 +1032,12 @@ class Asciidoctor::Lexer
|
|
978
1032
|
|
979
1033
|
# Internal: Checks if the next line on the Reader is a section title
|
980
1034
|
#
|
981
|
-
# This is a more efficient version of #is_section_title? and should
|
982
|
-
# eventually replace its usage.
|
983
|
-
#
|
984
1035
|
# reader - the source Reader
|
985
1036
|
#
|
986
1037
|
# returns the section level if the Reader is positioned at a section title,
|
987
1038
|
# false otherwise
|
988
|
-
def self.is_next_line_section?(reader)
|
1039
|
+
def self.is_next_line_section?(reader, attributes)
|
1040
|
+
return false if !attributes[1].nil? && ['float', 'discrete'].include?(attributes[1])
|
989
1041
|
if reader.has_lines?
|
990
1042
|
line1 = reader.get_line
|
991
1043
|
line2 = reader.peek_line
|
@@ -1079,7 +1131,7 @@ class Asciidoctor::Lexer
|
|
1079
1131
|
line1 = reader.get_line
|
1080
1132
|
sect_id = nil
|
1081
1133
|
sect_title = nil
|
1082
|
-
sect_level =
|
1134
|
+
sect_level = -1
|
1083
1135
|
single_line = true
|
1084
1136
|
|
1085
1137
|
if match = line1.match(REGEXP[:section_title])
|
@@ -1188,6 +1240,7 @@ class Asciidoctor::Lexer
|
|
1188
1240
|
# returns the Hash of attributes including any metadata found
|
1189
1241
|
def self.parse_block_metadata_lines(reader, parent, attributes = {}, options = {})
|
1190
1242
|
while parse_block_metadata_line(reader, parent, attributes, options)
|
1243
|
+
# discard the line just processed
|
1191
1244
|
reader.next_line
|
1192
1245
|
reader.skip_blank_lines
|
1193
1246
|
end
|
@@ -46,10 +46,10 @@ class Asciidoctor::ListItem < Asciidoctor::AbstractBlock
|
|
46
46
|
|
47
47
|
block = blocks.shift
|
48
48
|
unless @text.to_s.empty?
|
49
|
-
block.buffer.unshift(
|
49
|
+
block.buffer.unshift("#@text\n")
|
50
50
|
end
|
51
51
|
|
52
|
-
@text = block.buffer.join
|
52
|
+
@text = block.buffer.join
|
53
53
|
end
|
54
54
|
nil
|
55
55
|
end
|
data/lib/asciidoctor/reader.rb
CHANGED
@@ -37,9 +37,6 @@ class Asciidoctor::Reader
|
|
37
37
|
@lines = []
|
38
38
|
end
|
39
39
|
|
40
|
-
# just in case we got some nils floating at the end of our lines after reading a funky document
|
41
|
-
@lines.pop until @lines.empty? || !@lines.last.nil?
|
42
|
-
|
43
40
|
@source = @lines.join
|
44
41
|
end
|
45
42
|
|
@@ -303,6 +300,8 @@ class Asciidoctor::Reader
|
|
303
300
|
@lines = []
|
304
301
|
|
305
302
|
raw_source.each do |line|
|
303
|
+
# normalize line ending to LF (purging occurrences of CRLF)
|
304
|
+
line = "#{line.rstrip}\n"
|
306
305
|
if skip_to
|
307
306
|
skip_to = nil if line.match(skip_to)
|
308
307
|
elsif continuing_value
|
@@ -310,7 +309,7 @@ class Asciidoctor::Reader
|
|
310
309
|
# Lines that start with whitespace and end with a '+' are
|
311
310
|
# a continuation, so gobble them up into `value`
|
312
311
|
if line.match(REGEXP[:attr_continue])
|
313
|
-
continuing_value += ' ' + $1
|
312
|
+
continuing_value += ' ' + $1.rstrip
|
314
313
|
# An empty line ends a continuation
|
315
314
|
elsif line.strip.empty?
|
316
315
|
raw_source.unshift(line)
|
@@ -343,7 +342,7 @@ class Asciidoctor::Reader
|
|
343
342
|
# attribute value continuation line; grab lines until we run out
|
344
343
|
# of continuation lines
|
345
344
|
continuing_key = key
|
346
|
-
continuing_value = $1 # strip off the spaces and +
|
345
|
+
continuing_value = $1.rstrip # strip off the spaces and +
|
347
346
|
else
|
348
347
|
unless attribute_overridden? key
|
349
348
|
@document.attributes[key] = apply_attribute_value_subs(value)
|
@@ -360,9 +359,9 @@ class Asciidoctor::Reader
|
|
360
359
|
elsif !line.match(REGEXP[:endif_macro])
|
361
360
|
while line.match(REGEXP[:attr_conditional])
|
362
361
|
value = @document.attributes.has_key?($1) ? $2 : ''
|
363
|
-
line.sub!(
|
362
|
+
line.sub!(REGEXP[:attr_conditional], value)
|
364
363
|
end
|
365
|
-
# leave line comments in as they play a role in flow (such as a list divider)
|
364
|
+
# NOTE leave line comments in as they play a role in flow (such as a list divider)
|
366
365
|
@lines << line
|
367
366
|
end
|
368
367
|
end
|
data/lib/asciidoctor/section.rb
CHANGED
@@ -22,6 +22,12 @@ class Asciidoctor::Section < Asciidoctor::AbstractBlock
|
|
22
22
|
# Public: Get/Set the Integer index of this section within the parent block
|
23
23
|
attr_accessor :index
|
24
24
|
|
25
|
+
# Public: Get/Set the section name of this section
|
26
|
+
attr_accessor :sectname
|
27
|
+
|
28
|
+
# Public: Get/Set the flag to indicate whether this is a special section or a child of one
|
29
|
+
attr_accessor :special
|
30
|
+
|
25
31
|
# Public: Initialize an Asciidoctor::Section object.
|
26
32
|
#
|
27
33
|
# parent - The parent Asciidoc Object.
|
@@ -30,6 +36,11 @@ class Asciidoctor::Section < Asciidoctor::AbstractBlock
|
|
30
36
|
if level.nil? && !parent.nil?
|
31
37
|
@level = parent.level + 1
|
32
38
|
end
|
39
|
+
if parent.is_a?(::Asciidoctor::Section) && parent.special
|
40
|
+
@special = true
|
41
|
+
else
|
42
|
+
@special = false
|
43
|
+
end
|
33
44
|
@index = 0
|
34
45
|
end
|
35
46
|
|
@@ -58,16 +69,17 @@ class Asciidoctor::Section < Asciidoctor::AbstractBlock
|
|
58
69
|
# another_section.generate_id
|
59
70
|
# => "_foo_1"
|
60
71
|
def generate_id
|
61
|
-
if @document.attr?
|
62
|
-
|
63
|
-
|
72
|
+
if @document.attr? 'sectids'
|
73
|
+
separator = @document.attr('idseparator', '_')
|
74
|
+
base_id = @document.attr('idprefix', '_') + title.downcase.gsub(/&#[0-9]+;/, separator).
|
75
|
+
gsub(/\W+/, separator).tr_s(separator, separator).chomp(separator)
|
64
76
|
gen_id = base_id
|
65
77
|
cnt = 2
|
66
78
|
while @document.references[:ids].has_key? gen_id
|
67
|
-
gen_id = "#{base_id}
|
79
|
+
gen_id = "#{base_id}#{separator}#{cnt}"
|
68
80
|
cnt += 1
|
69
81
|
end
|
70
|
-
@document.
|
82
|
+
@document.register(:ids, [gen_id, title])
|
71
83
|
gen_id
|
72
84
|
else
|
73
85
|
nil
|
@@ -250,14 +250,25 @@ module Asciidoctor
|
|
250
250
|
reject = false
|
251
251
|
subject = line.dup
|
252
252
|
subject.gsub!(REGEXP[:attr_ref]) {
|
253
|
+
# alias match for Ruby 1.8.7 compat
|
254
|
+
m = $~
|
255
|
+
key = m[2].downcase
|
256
|
+
# escaped attribute
|
253
257
|
if !$1.empty? || !$3.empty?
|
254
258
|
"{#$2}"
|
255
|
-
elsif
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
+
elsif m[2].start_with?('counter:')
|
260
|
+
args = m[2].split(':')
|
261
|
+
@document.counter(args[1], args[2])
|
262
|
+
elsif m[2].start_with?('counter2:')
|
263
|
+
args = m[2].split(':')
|
264
|
+
@document.counter(args[1], args[2])
|
265
|
+
''
|
266
|
+
elsif document.attributes.has_key? key
|
267
|
+
@document.attributes[key]
|
268
|
+
elsif INTRINSICS.has_key? key
|
269
|
+
INTRINSICS[key]
|
259
270
|
else
|
260
|
-
Asciidoctor.debug { "Missing attribute:
|
271
|
+
Asciidoctor.debug { "Missing attribute: #{m[2]}, line marked for removal" }
|
261
272
|
reject = true
|
262
273
|
break '{undefined}'
|
263
274
|
end
|
@@ -281,101 +292,202 @@ module Asciidoctor
|
|
281
292
|
|
282
293
|
result = text.dup
|
283
294
|
|
284
|
-
#
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
295
|
+
# some look ahead assertions to cut unnecessary regex calls
|
296
|
+
found = {}
|
297
|
+
found[:square_bracket] = result.include?('[')
|
298
|
+
found[:round_bracket] = result.include?('(')
|
299
|
+
found[:macroish] = (found[:square_bracket] && result.include?(':'))
|
300
|
+
found[:macroish_short_form] = (found[:square_bracket] && result.include?(':['))
|
301
|
+
found[:uri] = result.include?('://')
|
302
|
+
|
303
|
+
if found[:macroish] && result.include?('image:')
|
304
|
+
# image:filename.png[Alt Text]
|
305
|
+
result.gsub!(REGEXP[:image_macro]) {
|
306
|
+
# alias match for Ruby 1.8.7 compat
|
307
|
+
m = $~
|
308
|
+
# honor the escape
|
309
|
+
if m[0].start_with? '\\'
|
310
|
+
next m[0][1..-1]
|
311
|
+
end
|
312
|
+
target = sub_attributes(m[1])
|
313
|
+
@document.register(:images, target)
|
314
|
+
attrs = parse_attributes(m[2], ['alt', 'width', 'height'])
|
315
|
+
if !attrs.has_key?('alt') || attrs['alt'].empty?
|
316
|
+
attrs['alt'] = File.basename(target, File.extname(target))
|
317
|
+
end
|
318
|
+
Inline.new(self, :image, nil, :target => target, :attributes => attrs).render
|
319
|
+
}
|
320
|
+
end
|
300
321
|
|
301
|
-
|
302
|
-
|
303
|
-
#
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
next m[0]
|
312
|
-
end
|
313
|
-
prefix = (m[1] != 'link:' ? m[1] : '')
|
314
|
-
target = m[2]
|
315
|
-
# strip the <> around the link
|
316
|
-
if prefix.end_with? '<'
|
317
|
-
prefix = prefix[0..-5]
|
318
|
-
end
|
319
|
-
if target.end_with? '>'
|
320
|
-
target = target[0..-5]
|
321
|
-
end
|
322
|
-
@document.register(:links, target)
|
323
|
-
text = !m[3].nil? ? sub_attributes(m[3].gsub('\]', ']')) : ''
|
324
|
-
"#{prefix}#{Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target).render}"
|
325
|
-
} unless !result.include?('http')
|
322
|
+
if found[:macroish_short_form] || found[:round_bracket]
|
323
|
+
# indexterm:[Tigers,Big cats]
|
324
|
+
# (((Tigers,Big cats)))
|
325
|
+
result.gsub!(REGEXP[:indexterm_macro]) {
|
326
|
+
# alias match for Ruby 1.8.7 compat
|
327
|
+
m = $~
|
328
|
+
# honor the escape
|
329
|
+
if m[0].start_with? '\\'
|
330
|
+
next m[0][1..-1]
|
331
|
+
end
|
326
332
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
333
|
+
terms = (m[1] || m[2]).strip.tr("\n", ' ').gsub('\]', ']').split(REGEXP[:csv_delimiter])
|
334
|
+
document.register(:indexterms, [*terms])
|
335
|
+
Inline.new(self, :indexterm, text, :attributes => {'terms' => terms}).render
|
336
|
+
}
|
337
|
+
|
338
|
+
# indexterm2:[Tigers]
|
339
|
+
# ((Tigers))
|
340
|
+
result.gsub!(REGEXP[:indexterm2_macro]) {
|
341
|
+
# alias match for Ruby 1.8.7 compat
|
342
|
+
m = $~
|
343
|
+
# honor the escape
|
344
|
+
if m[0].start_with? '\\'
|
345
|
+
next m[0][1..-1]
|
346
|
+
end
|
340
347
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
next m[0][1..-1]
|
347
|
-
end
|
348
|
-
if !m[1].nil?
|
349
|
-
id, reftext = m[1].split(',', 2)
|
350
|
-
id.sub!(/^("|)(.*)\1$/, '\2')
|
351
|
-
reftext.sub!(/^("|)(.*)\1$/m, '\2') unless reftext.nil?
|
352
|
-
else
|
353
|
-
id = m[2]
|
354
|
-
reftext = !m[3].empty? ? m[3] : nil
|
355
|
-
end
|
356
|
-
Inline.new(self, :anchor, reftext, :type => :xref, :target => id).render
|
357
|
-
}
|
348
|
+
text = (m[1] || m[2]).strip.tr("\n", ' ').gsub('\]', ']')
|
349
|
+
document.register(:indexterms, [text])
|
350
|
+
Inline.new(self, :indexterm, text, :type => :visible).render
|
351
|
+
}
|
352
|
+
end
|
358
353
|
|
359
|
-
|
360
|
-
#
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
354
|
+
if found[:uri]
|
355
|
+
# inline urls, target[text] (optionally prefixed with link: and optionally surrounded by <>)
|
356
|
+
result.gsub!(REGEXP[:link_inline]) {
|
357
|
+
# alias match for Ruby 1.8.7 compat
|
358
|
+
m = $~
|
359
|
+
# honor the escape
|
360
|
+
if m[2].start_with? '\\'
|
361
|
+
next "#{m[1]}#{m[2][1..-1]}#{m[3]}"
|
362
|
+
# not a valid macro syntax w/o trailing square brackets
|
363
|
+
# we probably shouldn't even get here...our regex is doing too much
|
364
|
+
elsif m[1] == 'link:' && m[3].nil?
|
365
|
+
next m[0]
|
366
|
+
end
|
367
|
+
prefix = (m[1] != 'link:' ? m[1] : '')
|
368
|
+
target = m[2]
|
369
|
+
# strip the <> around the link
|
370
|
+
if prefix.end_with? '<'
|
371
|
+
prefix = prefix[0..-5]
|
372
|
+
end
|
373
|
+
if target.end_with? '>'
|
374
|
+
target = target[0..-5]
|
375
|
+
end
|
376
|
+
@document.register(:links, target)
|
377
|
+
text = !m[3].nil? ? sub_attributes(m[3].gsub('\]', ']')) : ''
|
378
|
+
"#{prefix}#{Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target).render}"
|
379
|
+
}
|
380
|
+
end
|
381
|
+
|
382
|
+
if found[:macroish] && result.include?('link:')
|
383
|
+
# inline link macros, link:target[text]
|
384
|
+
result.gsub!(REGEXP[:link_macro]) {
|
385
|
+
# alias match for Ruby 1.8.7 compat
|
386
|
+
m = $~
|
387
|
+
# honor the escape
|
388
|
+
if m[0].start_with? '\\'
|
389
|
+
next m[0][1..-1]
|
390
|
+
end
|
391
|
+
target = m[1]
|
392
|
+
@document.register(:links, target)
|
393
|
+
text = sub_attributes(m[2].gsub('\]', ']'))
|
394
|
+
Inline.new(self, :anchor, (!text.empty? ? text : target), :type => :link, :target => target).render
|
395
|
+
}
|
396
|
+
end
|
397
|
+
|
398
|
+
if found[:macroish_short_form] && result.include?('footnote')
|
399
|
+
result.gsub!(REGEXP[:footnote_macro]) {
|
400
|
+
# alias match for Ruby 1.8.7 compat
|
401
|
+
m = $~
|
402
|
+
# honor the escape
|
403
|
+
if m[0].start_with? '\\'
|
404
|
+
next m[0][1..-1]
|
405
|
+
end
|
406
|
+
if m[1] == 'footnote'
|
407
|
+
# hmmmm
|
408
|
+
text = restore_passthroughs(m[2])
|
409
|
+
id = nil
|
410
|
+
index = @document.counter('footnote-number')
|
411
|
+
@document.register(:footnotes, Document::Footnote.new(index, id, text))
|
412
|
+
type = nil
|
413
|
+
target = nil
|
414
|
+
else
|
415
|
+
id, text = m[2].split(/ *, */, 2)
|
416
|
+
if !text.nil?
|
417
|
+
# hmmmm
|
418
|
+
text = restore_passthroughs(text)
|
419
|
+
index = @document.counter('footnote-number')
|
420
|
+
@document.register(:footnotes, Document::Footnote.new(index, id, text))
|
421
|
+
type = :ref
|
422
|
+
target = nil
|
423
|
+
else
|
424
|
+
fn = @document.references[:footnotes].find {|fn| fn.id == id }
|
425
|
+
target = id
|
426
|
+
id = nil
|
427
|
+
index = fn.index
|
428
|
+
text = fn.text
|
429
|
+
type = :xref
|
430
|
+
end
|
431
|
+
end
|
432
|
+
Inline.new(self, :footnote, text, :attributes => {'index' => index}, :id => id, :target => target, :type => type).render
|
433
|
+
}
|
434
|
+
end
|
435
|
+
|
436
|
+
if found[:macroish] || result.include?('<<')
|
437
|
+
result.gsub!(REGEXP[:xref_macro]) {
|
438
|
+
# alias match for Ruby 1.8.7 compat
|
439
|
+
m = $~
|
440
|
+
# honor the escape
|
441
|
+
if m[0].start_with? '\\'
|
442
|
+
next m[0][1..-1]
|
443
|
+
end
|
444
|
+
if !m[1].nil?
|
445
|
+
id, reftext = m[1].split(REGEXP[:csv_delimiter], 2)
|
446
|
+
id.sub!(/^("|)(.*)\1$/, '\2')
|
447
|
+
reftext.sub!(/^("|)(.*)\1$/m, '\2') unless reftext.nil?
|
448
|
+
else
|
449
|
+
id = m[2]
|
450
|
+
reftext = !m[3].empty? ? m[3] : nil
|
451
|
+
end
|
452
|
+
Inline.new(self, :anchor, reftext, :type => :xref, :target => id).render
|
453
|
+
}
|
454
|
+
end
|
455
|
+
|
456
|
+
if found[:square_bracket] && result.include?('[[[')
|
457
|
+
result.gsub!(REGEXP[:biblio_macro]) {
|
458
|
+
# alias match for Ruby 1.8.7 compat
|
459
|
+
m = $~
|
460
|
+
# honor the escape
|
461
|
+
if m[0].start_with? '\\'
|
462
|
+
next m[0][1..-1]
|
463
|
+
end
|
464
|
+
id = reftext = m[1]
|
465
|
+
Inline.new(self, :anchor, reftext, :type => :bibref, :target => id).render
|
466
|
+
}
|
467
|
+
end
|
468
|
+
|
469
|
+
if found[:square_bracket] && result.include?('[[')
|
470
|
+
result.gsub!(REGEXP[:anchor_macro]) {
|
471
|
+
# alias match for Ruby 1.8.7 compat
|
472
|
+
m = $~
|
473
|
+
# honor the escape
|
474
|
+
if m[0].start_with? '\\'
|
475
|
+
next m[0][1..-1]
|
476
|
+
end
|
477
|
+
id, reftext = m[1].split(REGEXP[:csv_delimiter])
|
478
|
+
id.sub!(/^("|)(.*)\1$/, '\2')
|
479
|
+
if reftext.nil?
|
480
|
+
reftext = "[#{id}]"
|
481
|
+
else
|
482
|
+
reftext.sub!(/^("|)(.*)\1$/m, '\2')
|
483
|
+
end
|
484
|
+
# NOTE the reftext should also match what's in our references dic
|
485
|
+
if !@document.references[:ids].has_key? id
|
486
|
+
Asciidoctor.debug { "Missing reference for anchor #{id}" }
|
487
|
+
end
|
488
|
+
Inline.new(self, :anchor, reftext, :type => :ref, :target => id).render
|
489
|
+
}
|
490
|
+
end
|
379
491
|
|
380
492
|
result
|
381
493
|
end
|
@@ -403,7 +515,14 @@ module Asciidoctor
|
|
403
515
|
#
|
404
516
|
# returns The String with the post replacements rendered using the backend templates
|
405
517
|
def sub_post_replacements(text)
|
406
|
-
|
518
|
+
if @document.attr? 'hardbreaks'
|
519
|
+
lines = text.lines.entries
|
520
|
+
return text if lines.size == 1
|
521
|
+
last = lines.pop
|
522
|
+
"#{lines.map {|line| Inline.new(self, :break, line.rstrip.chomp(' +'), :type => :line).render } * "\n"}\n#{last}"
|
523
|
+
else
|
524
|
+
text.gsub(REGEXP[:line_break]) { Inline.new(self, :break, $1, :type => :line).render }
|
525
|
+
end
|
407
526
|
end
|
408
527
|
|
409
528
|
# Internal: Transform (render) a quoted text region
|