asciidoctor 1.5.2 → 1.5.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +107 -1
- data/LICENSE.adoc +1 -1
- data/README.adoc +155 -230
- data/Rakefile +2 -1
- data/bin/asciidoctor +5 -1
- data/data/stylesheets/asciidoctor-default.css +37 -29
- data/data/stylesheets/coderay-asciidoctor.css +3 -3
- data/features/text_formatting.feature +2 -0
- data/lib/asciidoctor.rb +46 -21
- data/lib/asciidoctor/abstract_block.rb +14 -8
- data/lib/asciidoctor/abstract_node.rb +77 -24
- data/lib/asciidoctor/attribute_list.rb +1 -1
- data/lib/asciidoctor/block.rb +2 -3
- data/lib/asciidoctor/cli/options.rb +14 -15
- data/lib/asciidoctor/converter/docbook45.rb +8 -8
- data/lib/asciidoctor/converter/docbook5.rb +25 -17
- data/lib/asciidoctor/converter/factory.rb +6 -1
- data/lib/asciidoctor/converter/html5.rb +159 -117
- data/lib/asciidoctor/converter/manpage.rb +671 -0
- data/lib/asciidoctor/converter/template.rb +24 -17
- data/lib/asciidoctor/document.rb +89 -47
- data/lib/asciidoctor/extensions.rb +22 -21
- data/lib/asciidoctor/helpers.rb +73 -16
- data/lib/asciidoctor/list.rb +26 -5
- data/lib/asciidoctor/parser.rb +179 -122
- data/lib/asciidoctor/path_resolver.rb +6 -10
- data/lib/asciidoctor/reader.rb +37 -34
- data/lib/asciidoctor/stylesheets.rb +16 -10
- data/lib/asciidoctor/substitutors.rb +98 -21
- data/lib/asciidoctor/table.rb +21 -17
- data/lib/asciidoctor/timings.rb +3 -3
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +155 -89
- data/man/asciidoctor.adoc +19 -11
- data/test/attributes_test.rb +86 -0
- data/test/blocks_test.rb +203 -15
- data/test/converter_test.rb +15 -2
- data/test/document_test.rb +290 -36
- data/test/extensions_test.rb +22 -3
- data/test/fixtures/circle.svg +8 -0
- data/test/fixtures/subs-docinfo.html +2 -0
- data/test/fixtures/subs.adoc +7 -0
- data/test/invoker_test.rb +25 -0
- data/test/links_test.rb +17 -0
- data/test/lists_test.rb +173 -0
- data/test/options_test.rb +2 -2
- data/test/paragraphs_test.rb +2 -2
- data/test/parser_test.rb +56 -13
- data/test/reader_test.rb +35 -3
- data/test/sections_test.rb +59 -0
- data/test/substitutions_test.rb +53 -14
- data/test/tables_test.rb +158 -2
- data/test/test_helper.rb +7 -2
- metadata +22 -11
- data/benchmark/benchmark.rb +0 -129
- data/benchmark/sample-data/mdbasics.adoc +0 -334
- data/lib/asciidoctor/opal_ext.rb +0 -26
- data/lib/asciidoctor/opal_ext/comparable.rb +0 -38
- data/lib/asciidoctor/opal_ext/dir.rb +0 -13
- data/lib/asciidoctor/opal_ext/error.rb +0 -2
- data/lib/asciidoctor/opal_ext/file.rb +0 -145
data/lib/asciidoctor/helpers.rb
CHANGED
@@ -5,24 +5,39 @@ module Helpers
|
|
5
5
|
#
|
6
6
|
# Attempts to load the library specified in the first argument using the
|
7
7
|
# Kernel#require. Rescues the LoadError if the library is not available and
|
8
|
-
# passes a message to Kernel#fail
|
9
|
-
# is
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
|
8
|
+
# passes a message to Kernel#fail if on_failure is :abort or Kernel#warn if
|
9
|
+
# on_failure is :warn to communicate to the user that processing is being
|
10
|
+
# aborted or functionality is disabled, respectively. If a gem_name is
|
11
|
+
# specified, the message communicates that a required gem is not installed.
|
12
|
+
#
|
13
|
+
# name - the String name of the library to require.
|
14
|
+
# gem_name - a Boolean that indicates whether this library is provided by a RubyGem,
|
15
|
+
# or the String name of the RubyGem if it differs from the library name
|
16
|
+
# (default: true)
|
17
|
+
# on_failure - a Symbol that indicates how to handle a load failure (:abort, :warn, :ignore) (default: :abort)
|
18
|
+
#
|
19
|
+
# returns The return value of Kernel#require if the library is available and can be, or was previously, loaded.
|
20
|
+
# Otherwise, Kernel#fail is called with an appropriate message if on_failure is :abort.
|
21
|
+
# Otherwise, Kernel#warn is called with an appropriate message and nil returned if on_failure is :warn.
|
22
|
+
# Otherwise, nil is returned.
|
23
|
+
def self.require_library name, gem_name = true, on_failure = :abort
|
20
24
|
require name
|
21
25
|
rescue ::LoadError => e
|
22
|
-
if
|
23
|
-
|
26
|
+
if gem_name
|
27
|
+
gem_name = name if gem_name == true
|
28
|
+
case on_failure
|
29
|
+
when :abort
|
30
|
+
fail %(asciidoctor: FAILED: required gem '#{gem_name}' is not installed. Processing aborted.)
|
31
|
+
when :warn
|
32
|
+
warn %(asciidoctor: WARNING: optional gem '#{gem_name}' is not installed. Functionality disabled.)
|
33
|
+
end
|
24
34
|
else
|
25
|
-
|
35
|
+
case on_failure
|
36
|
+
when :abort
|
37
|
+
fail %(asciidoctor: FAILED: #{e.message.chomp '.'}. Processing aborted.)
|
38
|
+
when :warn
|
39
|
+
warn %(asciidoctor: WARNING: #{e.message.chomp '.'}. Functionality disabled.)
|
40
|
+
end
|
26
41
|
end
|
27
42
|
end
|
28
43
|
|
@@ -109,6 +124,30 @@ module Helpers
|
|
109
124
|
data.each_line.map {|line| line.rstrip }
|
110
125
|
end
|
111
126
|
|
127
|
+
# Public: Efficiently checks whether the specified String resembles a URI
|
128
|
+
#
|
129
|
+
# Uses the Asciidoctor::UriSniffRx regex to check whether the String begins
|
130
|
+
# with a URI prefix (e.g., http://). No validation of the URI is performed.
|
131
|
+
#
|
132
|
+
# str - the String to check
|
133
|
+
#
|
134
|
+
# returns true if the String is a URI, false if it is not
|
135
|
+
def self.uriish? str
|
136
|
+
(str.include? ':') && str =~ UriSniffRx
|
137
|
+
end
|
138
|
+
|
139
|
+
# Public: Efficiently retrieves the URI prefix of the specified String
|
140
|
+
#
|
141
|
+
# Uses the Asciidoctor::UriSniffRx regex to match the URI prefix in the
|
142
|
+
# specified String (e.g., http://), if present.
|
143
|
+
#
|
144
|
+
# str - the String to check
|
145
|
+
#
|
146
|
+
# returns the string URI prefix if the string is a URI, otherwise nil
|
147
|
+
def self.uri_prefix str
|
148
|
+
(str.include? ':') && str =~ UriSniffRx ? $& : nil
|
149
|
+
end
|
150
|
+
|
112
151
|
# Matches the characters in a URI to encode
|
113
152
|
REGEXP_ENCODE_URI_CHARS = /[^\w\-.!~*';:@=+$,()\[\]]/
|
114
153
|
|
@@ -134,10 +173,28 @@ module Helpers
|
|
134
173
|
#
|
135
174
|
# Returns the String filename with the file extension removed
|
136
175
|
def self.rootname(file_name)
|
137
|
-
# alternatively, this could be written as ::File.basename file_name, ((::File.extname file_name) || '')
|
138
176
|
(ext = ::File.extname(file_name)).empty? ? file_name : file_name[0...-ext.length]
|
139
177
|
end
|
140
178
|
|
179
|
+
# Public: Retrieves the basename of the filename, optionally removing the extension, if present
|
180
|
+
#
|
181
|
+
# file_name - The String file name to process
|
182
|
+
# drop_extname - A Boolean flag indicating whether to drop the extension (default: false)
|
183
|
+
#
|
184
|
+
# Examples
|
185
|
+
#
|
186
|
+
# Helpers.basename('images/tiger.png', true)
|
187
|
+
# # => "tiger"
|
188
|
+
#
|
189
|
+
# Returns the String filename with leading directories removed and, if specified, the extension removed
|
190
|
+
def self.basename(file_name, drop_extname = false)
|
191
|
+
if drop_extname
|
192
|
+
::File.basename file_name, ((::File.extname file_name) || '')
|
193
|
+
else
|
194
|
+
::File.basename file_name
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
141
198
|
def self.mkdir_p(dir)
|
142
199
|
unless ::File.directory? dir
|
143
200
|
parent_dir = ::File.dirname(dir)
|
data/lib/asciidoctor/list.rb
CHANGED
@@ -1,19 +1,24 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
module Asciidoctor
|
3
|
-
# Public: Methods for managing AsciiDoc lists (ordered, unordered and
|
3
|
+
# Public: Methods for managing AsciiDoc lists (ordered, unordered and description lists)
|
4
4
|
class List < AbstractBlock
|
5
5
|
|
6
6
|
# Public: Create alias for blocks
|
7
7
|
alias :items :blocks
|
8
|
+
# Public: Get the items in this list as an Array
|
9
|
+
alias :content :blocks
|
10
|
+
# Public: Create alias to check if this list has blocks
|
8
11
|
alias :items? :blocks?
|
9
12
|
|
10
13
|
def initialize parent, context
|
11
14
|
super
|
12
15
|
end
|
13
16
|
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
+
# Check whether this list is an outline list (unordered or ordered).
|
18
|
+
#
|
19
|
+
# Return true if this list is an outline list. Otherwise, return false.
|
20
|
+
def outline?
|
21
|
+
@context == :ulist || @context == :olist
|
17
22
|
end
|
18
23
|
|
19
24
|
def convert
|
@@ -59,6 +64,22 @@ class ListItem < AbstractBlock
|
|
59
64
|
apply_subs @text
|
60
65
|
end
|
61
66
|
|
67
|
+
# Check whether this list item has simple content (no nested blocks aside from a single outline list).
|
68
|
+
# Primarily relevant for outline lists.
|
69
|
+
#
|
70
|
+
# Return true if the list item contains no blocks or it contains a single outline list. Otherwise, return false.
|
71
|
+
def simple?
|
72
|
+
@blocks.empty? || (@blocks.size == 1 && List === (blk = @blocks[0]) && blk.outline?)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Check whether this list item has compound content (nested blocks aside from a single outline list).
|
76
|
+
# Primarily relevant for outline lists.
|
77
|
+
#
|
78
|
+
# Return true if the list item contains blocks other than a single outline list. Otherwise, return false.
|
79
|
+
def compound?
|
80
|
+
!simple?
|
81
|
+
end
|
82
|
+
|
62
83
|
# Public: Fold the first paragraph block into the text
|
63
84
|
#
|
64
85
|
# Here are the rules for when a folding occurs:
|
@@ -71,7 +92,7 @@ class ListItem < AbstractBlock
|
|
71
92
|
#
|
72
93
|
# Returns nothing
|
73
94
|
def fold_first(continuation_connects_first_block = false, content_adjacent = false)
|
74
|
-
if (first_block = @blocks[0]) &&
|
95
|
+
if (first_block = @blocks[0]) && Block === first_block &&
|
75
96
|
((first_block.context == :paragraph && !continuation_connects_first_block) ||
|
76
97
|
((content_adjacent || !continuation_connects_first_block) && first_block.context == :literal &&
|
77
98
|
first_block.option?('listparagraph')))
|
data/lib/asciidoctor/parser.rb
CHANGED
@@ -26,6 +26,20 @@ class Parser
|
|
26
26
|
|
27
27
|
BlockMatchData = Struct.new :context, :masq, :tip, :terminator
|
28
28
|
|
29
|
+
# Regexp for replacing tab character
|
30
|
+
TabRx = /\t/
|
31
|
+
|
32
|
+
# Regexp for leading tab indentation
|
33
|
+
TabIndentRx = /^\t+/
|
34
|
+
|
35
|
+
StartOfBlockProc = lambda {|l| ((l.start_with? '[') && BlockAttributeLineRx =~ l) || (is_delimited_block? l) }
|
36
|
+
|
37
|
+
StartOfListProc = lambda {|l| AnyListRx =~ l }
|
38
|
+
|
39
|
+
StartOfBlockOrListProc = lambda {|l| (is_delimited_block? l) || ((l.start_with? '[') && BlockAttributeLineRx =~ l) || AnyListRx =~ l }
|
40
|
+
|
41
|
+
NoOp = nil
|
42
|
+
|
29
43
|
# Public: Make sure the Parser object doesn't get initialized.
|
30
44
|
#
|
31
45
|
# Raises RuntimeError if this constructor is invoked.
|
@@ -101,6 +115,9 @@ class Parser
|
|
101
115
|
end
|
102
116
|
# default to compat-mode if document uses atx-style doctitle
|
103
117
|
document.set_attribute 'compat-mode', '' unless single_line
|
118
|
+
if (separator = block_attributes.delete('separator'))
|
119
|
+
document.set_attribute('title-separator', separator)
|
120
|
+
end
|
104
121
|
document.header.source_location = source_location if source_location
|
105
122
|
document.attributes['doctitle'] = section_title = doctitle
|
106
123
|
# QUESTION: should the id assignment on Document be encapsulated in the Document class?
|
@@ -138,6 +155,9 @@ class Parser
|
|
138
155
|
document.attributes['manvolnum'] = m[2].strip
|
139
156
|
else
|
140
157
|
warn %(asciidoctor: ERROR: #{reader.prev_line_info}: malformed manpage title)
|
158
|
+
# provide sensible fallbacks
|
159
|
+
document.attributes['mantitle'] = document.attributes['doctitle']
|
160
|
+
document.attributes['manvolnum'] = '1'
|
141
161
|
end
|
142
162
|
|
143
163
|
reader.skip_blank_lines
|
@@ -417,7 +437,7 @@ class Parser
|
|
417
437
|
block_extensions = block_macro_extensions = false
|
418
438
|
end
|
419
439
|
#parent_context = parent.is_a?(Block) ? parent.context : nil
|
420
|
-
in_list =
|
440
|
+
in_list = ListItem === parent
|
421
441
|
block = nil
|
422
442
|
style = nil
|
423
443
|
explicit_style = nil
|
@@ -536,7 +556,7 @@ class Parser
|
|
536
556
|
# end
|
537
557
|
# end
|
538
558
|
# document.register(:images, target)
|
539
|
-
# attributes['alt'] ||=
|
559
|
+
# attributes['alt'] ||= Helpers.basename(target, true).tr('_-', ' ')
|
540
560
|
# # QUESTION should video or audio have an auto-numbered caption?
|
541
561
|
# block.assign_caption attributes.delete('caption'), 'figure'
|
542
562
|
#end
|
@@ -579,7 +599,8 @@ class Parser
|
|
579
599
|
attributes['style'] = 'arabic'
|
580
600
|
reader.unshift_line this_line
|
581
601
|
expected_index = 1
|
582
|
-
begin
|
602
|
+
# NOTE skip the match on the first time through as we've already done it (emulates begin...while)
|
603
|
+
while match || (reader.has_more_lines? && (match = CalloutListRx.match(reader.peek_line)))
|
583
604
|
# might want to move this check to a validate method
|
584
605
|
if match[1].to_i != expected_index
|
585
606
|
# FIXME this lineno - 2 hack means we need a proper look-behind cursor
|
@@ -597,7 +618,8 @@ class Parser
|
|
597
618
|
warn %(asciidoctor: WARNING: #{reader.path}: line #{reader.lineno - 2}: no callouts refer to list item #{block.items.size})
|
598
619
|
end
|
599
620
|
end
|
600
|
-
|
621
|
+
match = nil
|
622
|
+
end
|
601
623
|
|
602
624
|
document.callouts.next_list
|
603
625
|
break
|
@@ -682,19 +704,9 @@ class Parser
|
|
682
704
|
if style != 'normal' && LiteralParagraphRx =~ this_line
|
683
705
|
# So we need to actually include this one in the read_lines group
|
684
706
|
reader.unshift_line this_line
|
685
|
-
lines = reader
|
686
|
-
:break_on_blank_lines => true,
|
687
|
-
:break_on_list_continuation => true,
|
688
|
-
:preserve_last_line => true) {|line|
|
689
|
-
# a preceding blank line (skipped > 0) indicates we are in a list continuation
|
690
|
-
# and therefore we should not break at a list item
|
691
|
-
# (this won't stop breaking on item of same level since we've already parsed them out)
|
692
|
-
# QUESTION can we turn this block into a lambda or function call?
|
693
|
-
(break_at_list && AnyListRx =~ line) ||
|
694
|
-
(Compliance.block_terminates_paragraph && (is_delimited_block?(line) || BlockAttributeLineRx =~ line))
|
695
|
-
}
|
707
|
+
lines = read_paragraph_lines reader, break_at_list, :skip_line_comments => text_only
|
696
708
|
|
697
|
-
|
709
|
+
adjust_indentation! lines
|
698
710
|
|
699
711
|
block = Block.new(parent, :literal, :content_model => :verbatim, :source => lines, :attributes => attributes)
|
700
712
|
# a literal gets special meaning inside of a definition list
|
@@ -704,18 +716,7 @@ class Parser
|
|
704
716
|
# a paragraph is contiguous nonblank/noncontinuation lines
|
705
717
|
else
|
706
718
|
reader.unshift_line this_line
|
707
|
-
lines = reader
|
708
|
-
:break_on_blank_lines => true,
|
709
|
-
:break_on_list_continuation => true,
|
710
|
-
:preserve_last_line => true,
|
711
|
-
:skip_line_comments => true) {|line|
|
712
|
-
# a preceding blank line (skipped > 0) indicates we are in a list continuation
|
713
|
-
# and therefore we should not break at a list item
|
714
|
-
# (this won't stop breaking on item of same level since we've already parsed them out)
|
715
|
-
# QUESTION can we turn this block into a lambda or function call?
|
716
|
-
(break_at_list && AnyListRx =~ line) ||
|
717
|
-
(Compliance.block_terminates_paragraph && (is_delimited_block?(line) || BlockAttributeLineRx =~ line))
|
718
|
-
}
|
719
|
+
lines = read_paragraph_lines reader, break_at_list, :skip_line_comments => true
|
719
720
|
|
720
721
|
# NOTE we need this logic because we've asked the reader to skip
|
721
722
|
# line comments, which may leave us w/ an empty buffer if those
|
@@ -759,8 +760,7 @@ class Parser
|
|
759
760
|
# TODO could assume a floating title when inside a block context
|
760
761
|
# FIXME Reader needs to be created w/ line info
|
761
762
|
block = build_block(:quote, :compound, false, parent, Reader.new(lines), attributes)
|
762
|
-
elsif !text_only && lines
|
763
|
-
lines[-1].start_with?('-- ') && lines[-2].end_with?('"')
|
763
|
+
elsif !text_only && (blockquote? lines, first_line)
|
764
764
|
lines[0] = first_line[1..-1]
|
765
765
|
attribution, citetitle = lines.pop[3..-1].split(', ', 2)
|
766
766
|
lines.pop while lines[-1].empty?
|
@@ -771,16 +771,10 @@ class Parser
|
|
771
771
|
attributes['citetitle'] = citetitle if citetitle
|
772
772
|
block = Block.new(parent, :quote, :content_model => :simple, :source => lines, :attributes => attributes)
|
773
773
|
else
|
774
|
-
# if [normal] is used over an indented paragraph,
|
775
|
-
if style == 'normal'
|
776
|
-
|
777
|
-
|
778
|
-
indent = line_length(first_line) - line_length(first_line_shifted)
|
779
|
-
lines[0] = first_line_shifted
|
780
|
-
# QUESTION should we fix the rest of the lines, since in XML output it's insignificant?
|
781
|
-
lines.size.times do |i|
|
782
|
-
lines[i] = lines[i][indent..-1] if i > 0
|
783
|
-
end
|
774
|
+
# if [normal] is used over an indented paragraph, shift content to left margin
|
775
|
+
if style == 'normal'
|
776
|
+
# QUESTION do we even need to shift since whitespace is normalized by XML in this case?
|
777
|
+
adjust_indentation! lines
|
784
778
|
end
|
785
779
|
|
786
780
|
block = Block.new(parent, :paragraph, :content_model => :simple, :source => lines, :attributes => attributes)
|
@@ -814,21 +808,27 @@ class Parser
|
|
814
808
|
when :listing, :fenced_code, :source
|
815
809
|
if block_context == :fenced_code
|
816
810
|
style = attributes['style'] = 'source'
|
817
|
-
language, linenums = this_line[3..-1].split(',', 2)
|
818
|
-
if
|
811
|
+
language, linenums = this_line[3..-1].tr(' ', '').split(',', 2)
|
812
|
+
if !language.nil_or_empty?
|
819
813
|
attributes['language'] = language
|
820
|
-
attributes['linenums'] = ''
|
814
|
+
attributes['linenums'] = '' unless linenums.nil_or_empty?
|
821
815
|
elsif (default_language = document.attributes['source-language'])
|
822
816
|
attributes['language'] = default_language
|
823
817
|
end
|
818
|
+
if !attributes.key?('indent') && document.attributes.key?('source-indent')
|
819
|
+
attributes['indent'] = document.attributes['source-indent']
|
820
|
+
end
|
824
821
|
terminator = terminator[0..2]
|
825
822
|
elsif block_context == :source
|
826
823
|
AttributeList.rekey(attributes, [nil, 'language', 'linenums'])
|
827
|
-
unless attributes.
|
824
|
+
unless attributes.key? 'language'
|
828
825
|
if (default_language = document.attributes['source-language'])
|
829
826
|
attributes['language'] = default_language
|
830
827
|
end
|
831
828
|
end
|
829
|
+
if !attributes.key?('indent') && document.attributes.key?('source-indent')
|
830
|
+
attributes['indent'] = document.attributes['source-indent']
|
831
|
+
end
|
832
832
|
end
|
833
833
|
block = build_block(:listing, :verbatim, terminator, parent, reader, attributes)
|
834
834
|
|
@@ -905,8 +905,8 @@ class Parser
|
|
905
905
|
if block.context == :image
|
906
906
|
resolved_target = attributes['target']
|
907
907
|
block.document.register(:images, resolved_target)
|
908
|
-
attributes['alt'] ||=
|
909
|
-
attributes['alt'] = block.
|
908
|
+
attributes['alt'] ||= Helpers.basename(resolved_target, true).tr('_-', ' ')
|
909
|
+
attributes['alt'] = block.sub_specialchars attributes['alt']
|
910
910
|
block.assign_caption attributes.delete('caption'), 'figure'
|
911
911
|
if (scaledwidth = attributes['scaledwidth'])
|
912
912
|
# append % to scaledwidth if ends in number (no units present)
|
@@ -947,6 +947,21 @@ class Parser
|
|
947
947
|
block
|
948
948
|
end
|
949
949
|
|
950
|
+
def self.blockquote? lines, first_line = nil
|
951
|
+
lines.size > 1 && ((first_line || lines[0]).start_with? '"') &&
|
952
|
+
(lines[-1].start_with? '-- ') && (lines[-2].end_with? '"')
|
953
|
+
end
|
954
|
+
|
955
|
+
def self.read_paragraph_lines reader, break_at_list, opts = {}
|
956
|
+
opts[:break_on_blank_lines] = true
|
957
|
+
opts[:break_on_list_continuation] = true
|
958
|
+
opts[:preserve_last_line] = true
|
959
|
+
break_condition = (break_at_list ?
|
960
|
+
(Compliance.block_terminates_paragraph ? StartOfBlockOrListProc : StartOfListProc) :
|
961
|
+
(Compliance.block_terminates_paragraph ? StartOfBlockProc : NoOp))
|
962
|
+
reader.read_lines_until opts, &break_condition
|
963
|
+
end
|
964
|
+
|
950
965
|
# Public: Determines whether this line is the start of any of the delimited blocks
|
951
966
|
#
|
952
967
|
# returns the match data if this line is the first line of a delimited block or nil if not
|
@@ -1035,14 +1050,7 @@ class Parser
|
|
1035
1050
|
lines = reader.read_lines_until(:break_on_blank_lines => true, :break_on_list_continuation => true)
|
1036
1051
|
else
|
1037
1052
|
content_model = :simple if content_model == :compound
|
1038
|
-
lines = reader
|
1039
|
-
:break_on_blank_lines => true,
|
1040
|
-
:break_on_list_continuation => true,
|
1041
|
-
:preserve_last_line => true,
|
1042
|
-
:skip_line_comments => true,
|
1043
|
-
:skip_processing => skip_processing) {|line|
|
1044
|
-
Compliance.block_terminates_paragraph && (is_delimited_block?(line) || BlockAttributeLineRx =~ line)
|
1045
|
-
}
|
1053
|
+
lines = read_paragraph_lines reader, false, :skip_line_comments => true, :skip_processing => true
|
1046
1054
|
# QUESTION check for empty lines after grabbing lines for simple content model?
|
1047
1055
|
end
|
1048
1056
|
block_reader = nil
|
@@ -1065,8 +1073,12 @@ class Parser
|
|
1065
1073
|
return lines
|
1066
1074
|
end
|
1067
1075
|
|
1068
|
-
if content_model == :verbatim
|
1069
|
-
|
1076
|
+
if content_model == :verbatim
|
1077
|
+
if (indent = attributes['indent'])
|
1078
|
+
adjust_indentation! lines, indent, (attributes['tabsize'] || parent.document.attributes['tabsize'])
|
1079
|
+
elsif (tab_size = (attributes['tabsize'] || parent.document.attributes['tabsize']).to_i) > 0
|
1080
|
+
adjust_indentation! lines, nil, tab_size
|
1081
|
+
end
|
1070
1082
|
end
|
1071
1083
|
|
1072
1084
|
if (extension = options[:extension])
|
@@ -1238,7 +1250,8 @@ class Parser
|
|
1238
1250
|
# that uses the same delimiter (::, :::, :::: or ;;)
|
1239
1251
|
sibling_pattern = DefinitionListSiblingRx[match[2]]
|
1240
1252
|
|
1241
|
-
begin
|
1253
|
+
# NOTE skip the match on the first time through as we've already done it (emulates begin...while)
|
1254
|
+
while match || (reader.has_more_lines? && (match = sibling_pattern.match(reader.peek_line)))
|
1242
1255
|
term, item = next_list_item(reader, list_block, match, sibling_pattern)
|
1243
1256
|
if previous_pair && !previous_pair[-1]
|
1244
1257
|
previous_pair.pop
|
@@ -1248,7 +1261,8 @@ class Parser
|
|
1248
1261
|
# FIXME this misses the automatic parent assignment
|
1249
1262
|
list_block.items << (previous_pair = [[term], item])
|
1250
1263
|
end
|
1251
|
-
|
1264
|
+
match = nil
|
1265
|
+
end
|
1252
1266
|
|
1253
1267
|
list_block
|
1254
1268
|
end
|
@@ -1333,8 +1347,9 @@ class Parser
|
|
1333
1347
|
# about sections) since the reader is confined within the boundaries of a
|
1334
1348
|
# list
|
1335
1349
|
while list_item_reader.has_more_lines?
|
1336
|
-
new_block = next_block(list_item_reader,
|
1337
|
-
|
1350
|
+
if (new_block = next_block(list_item_reader, list_item, {}, options))
|
1351
|
+
list_item << new_block
|
1352
|
+
end
|
1338
1353
|
end
|
1339
1354
|
|
1340
1355
|
list_item.fold_first(continuation_connects_first_block, content_adjacent)
|
@@ -1821,9 +1836,16 @@ class Parser
|
|
1821
1836
|
if reader.has_more_lines? && !reader.next_line_empty?
|
1822
1837
|
rev_line = reader.read_line
|
1823
1838
|
if (match = RevisionInfoLineRx.match(rev_line))
|
1824
|
-
rev_metadata['
|
1825
|
-
|
1826
|
-
|
1839
|
+
rev_metadata['revnumber'] = match[1].rstrip if match[1]
|
1840
|
+
unless (component = match[2].strip) == ''
|
1841
|
+
# version must begin with 'v' if date is absent
|
1842
|
+
if !match[1] && (component.start_with? 'v')
|
1843
|
+
rev_metadata['revnumber'] = component[1..-1]
|
1844
|
+
else
|
1845
|
+
rev_metadata['revdate'] = component
|
1846
|
+
end
|
1847
|
+
end
|
1848
|
+
rev_metadata['revremark'] = match[3].rstrip if match[3]
|
1827
1849
|
else
|
1828
1850
|
# throw it back
|
1829
1851
|
reader.unshift_line rev_line
|
@@ -1917,7 +1939,7 @@ class Parser
|
|
1917
1939
|
|
1918
1940
|
segments = nil
|
1919
1941
|
if names_only
|
1920
|
-
# splitting on ' ' will collapse repeating spaces
|
1942
|
+
# splitting on ' ' with limit will collapse repeating spaces
|
1921
1943
|
segments = author_entry.split(' ', 3)
|
1922
1944
|
elsif (match = AuthorInfoLineRx.match(author_entry))
|
1923
1945
|
segments = match.to_a
|
@@ -2254,11 +2276,11 @@ class Parser
|
|
2254
2276
|
table.assign_caption attributes.delete('caption')
|
2255
2277
|
end
|
2256
2278
|
|
2257
|
-
if attributes
|
2279
|
+
if attributes['cols'].nil_or_empty?
|
2280
|
+
explicit_col_specs = false
|
2281
|
+
else
|
2258
2282
|
table.create_columns(parse_col_specs(attributes['cols']))
|
2259
2283
|
explicit_col_specs = true
|
2260
|
-
else
|
2261
|
-
explicit_col_specs = false
|
2262
2284
|
end
|
2263
2285
|
|
2264
2286
|
skipped = table_reader.skip_blank_lines
|
@@ -2303,7 +2325,13 @@ class Parser
|
|
2303
2325
|
end
|
2304
2326
|
else
|
2305
2327
|
if m.pre_match.end_with? '\\'
|
2306
|
-
|
2328
|
+
# skip over escaped delimiter
|
2329
|
+
# handle special case when end of line is reached (see issue #1306)
|
2330
|
+
if (line = parser_ctx.skip_matched_delimiter(m, true)).empty?
|
2331
|
+
parser_ctx.buffer = %(#{parser_ctx.buffer}#{EOL})
|
2332
|
+
parser_ctx.keep_cell_open
|
2333
|
+
break
|
2334
|
+
end
|
2307
2335
|
next
|
2308
2336
|
end
|
2309
2337
|
end
|
@@ -2380,9 +2408,12 @@ class Parser
|
|
2380
2408
|
end
|
2381
2409
|
|
2382
2410
|
specs = []
|
2383
|
-
|
2411
|
+
# NOTE -1 argument ensures we don't drop empty records
|
2412
|
+
records.split(',', -1).each {|record|
|
2413
|
+
if record == ''
|
2414
|
+
specs << { 'width' => 1 }
|
2384
2415
|
# TODO might want to use scan rather than this mega-regexp
|
2385
|
-
|
2416
|
+
elsif (m = ColumnSpecRx.match(record))
|
2386
2417
|
spec = {}
|
2387
2418
|
if m[2]
|
2388
2419
|
# make this an operation
|
@@ -2397,18 +2428,20 @@ class Parser
|
|
2397
2428
|
|
2398
2429
|
# to_i permits us to support percentage width by stripping the %
|
2399
2430
|
# NOTE this is slightly out of compliance w/ AsciiDoc, but makes way more sense
|
2400
|
-
spec['width'] =
|
2431
|
+
spec['width'] = (m[3] ? m[3].to_i : 1)
|
2401
2432
|
|
2402
2433
|
# make this an operation
|
2403
2434
|
if m[4] && Table::TEXT_STYLES.has_key?(m[4])
|
2404
2435
|
spec['style'] = Table::TEXT_STYLES[m[4]]
|
2405
2436
|
end
|
2406
2437
|
|
2407
|
-
|
2408
|
-
|
2409
|
-
|
2410
|
-
|
2411
|
-
|
2438
|
+
if m[1]
|
2439
|
+
1.upto(m[1].to_i) {
|
2440
|
+
specs << spec.dup
|
2441
|
+
}
|
2442
|
+
else
|
2443
|
+
specs << spec
|
2444
|
+
end
|
2412
2445
|
end
|
2413
2446
|
}
|
2414
2447
|
specs
|
@@ -2591,18 +2624,14 @@ class Parser
|
|
2591
2624
|
end
|
2592
2625
|
end
|
2593
2626
|
|
2594
|
-
# Remove the indentation (
|
2595
|
-
#
|
2627
|
+
# Remove the block indentation (the leading whitespace equal to the amount of
|
2628
|
+
# leading whitespace of the least indented line), then replace tabs with
|
2629
|
+
# spaces (using proper tab expansion logic) and, finally, indent the lines by
|
2630
|
+
# the amount specified.
|
2596
2631
|
#
|
2597
|
-
#
|
2598
|
-
# of the indent on the least indented line. If the indent argument
|
2599
|
-
# is specified, indent the lines by this many spaces (columns).
|
2600
|
-
#
|
2601
|
-
# The purpose of this method is to shift a block of text to
|
2602
|
-
# align to the left margin, while still preserving the relative
|
2603
|
-
# indentation between lines
|
2632
|
+
# This method preserves the relative indentation of the lines.
|
2604
2633
|
#
|
2605
|
-
# lines - the Array of String lines to process
|
2634
|
+
# lines - the Array of String lines to process (no trailing endlines)
|
2606
2635
|
# indent - the integer number of spaces to add to the beginning
|
2607
2636
|
# of each line; if this value is nil, the existing
|
2608
2637
|
# space is preserved (optional, default: 0)
|
@@ -2615,55 +2644,83 @@ class Parser
|
|
2615
2644
|
# end
|
2616
2645
|
# EOS
|
2617
2646
|
#
|
2618
|
-
# source.split
|
2647
|
+
# source.split "\n"
|
2619
2648
|
# # => [" def names", " @names.split ' '", " end"]
|
2620
2649
|
#
|
2621
|
-
# Parser.
|
2622
|
-
# # => ["def names", " @names.split ' '", "end"]
|
2623
|
-
#
|
2624
|
-
# puts Parser.reset_block_indent(source.split "\n") * "\n"
|
2650
|
+
# puts Parser.adjust_indentation!(source.split "\n") * "\n"
|
2625
2651
|
# # => def names
|
2626
2652
|
# # => @names.split ' '
|
2627
2653
|
# # => end
|
2628
2654
|
#
|
2629
|
-
# returns
|
2655
|
+
# returns Nothing
|
2630
2656
|
#--
|
2631
|
-
#
|
2632
|
-
def self.
|
2633
|
-
return if
|
2634
|
-
|
2635
|
-
|
2636
|
-
|
2637
|
-
|
2638
|
-
|
2639
|
-
|
2640
|
-
|
2641
|
-
|
2642
|
-
|
2643
|
-
|
2644
|
-
|
2645
|
-
|
2646
|
-
|
2647
|
-
|
2648
|
-
|
2649
|
-
|
2650
|
-
|
2651
|
-
|
2657
|
+
# QUESTION should indent be called margin?
|
2658
|
+
def self.adjust_indentation! lines, indent = 0, tab_size = 0
|
2659
|
+
return if lines.empty?
|
2660
|
+
|
2661
|
+
# expand tabs if a tab is detected unless tab_size is nil
|
2662
|
+
if (tab_size = tab_size.to_i) > 0 && (lines.join.include? TAB)
|
2663
|
+
#if (tab_size = tab_size.to_i) > 0 && (lines.index {|line| line.include? TAB })
|
2664
|
+
full_tab_space = ' ' * tab_size
|
2665
|
+
lines.map! do |line|
|
2666
|
+
next line if line.empty?
|
2667
|
+
|
2668
|
+
if line.start_with? TAB
|
2669
|
+
line.sub!(TabIndentRx) {|tabs| full_tab_space * tabs.length }
|
2670
|
+
end
|
2671
|
+
|
2672
|
+
if line.include? TAB
|
2673
|
+
# keeps track of how many spaces were added to adjust offset in match data
|
2674
|
+
spaces_added = 0
|
2675
|
+
line.gsub!(TabRx) {
|
2676
|
+
# calculate how many spaces this tab represents, then replace tab with spaces
|
2677
|
+
if (offset = ($~.begin 0) + spaces_added) % tab_size == 0
|
2678
|
+
spaces_added += (tab_size - 1)
|
2679
|
+
full_tab_space
|
2680
|
+
else
|
2681
|
+
unless (spaces = tab_size - offset % tab_size) == 1
|
2682
|
+
spaces_added += (spaces - 1)
|
2683
|
+
end
|
2684
|
+
' ' * spaces
|
2685
|
+
end
|
2686
|
+
}
|
2687
|
+
else
|
2688
|
+
line
|
2689
|
+
end
|
2652
2690
|
end
|
2653
2691
|
end
|
2654
|
-
|
2655
|
-
|
2656
|
-
|
2657
|
-
|
2658
|
-
|
2659
|
-
|
2660
|
-
|
2692
|
+
|
2693
|
+
# skip adjustment of gutter if indent is -1
|
2694
|
+
return unless indent && (indent = indent.to_i) > -1
|
2695
|
+
|
2696
|
+
# determine width of gutter
|
2697
|
+
gutter_width = nil
|
2698
|
+
lines.each do |line|
|
2699
|
+
next if line.empty?
|
2700
|
+
# NOTE this logic assumes no whitespace-only lines
|
2701
|
+
if (line_indent = line.length - line.lstrip.length) == 0
|
2702
|
+
gutter_width = nil
|
2703
|
+
break
|
2704
|
+
else
|
2705
|
+
unless gutter_width && line_indent > gutter_width
|
2706
|
+
gutter_width = line_indent
|
2707
|
+
end
|
2661
2708
|
end
|
2662
2709
|
end
|
2663
2710
|
|
2664
|
-
|
2711
|
+
# remove gutter then apply new indent if specified
|
2712
|
+
# NOTE gutter_width is > 0 if not nil
|
2713
|
+
if indent == 0
|
2714
|
+
if gutter_width
|
2715
|
+
lines.map! {|line| line.empty? ? line : line[gutter_width..-1] }
|
2716
|
+
end
|
2717
|
+
else
|
2665
2718
|
padding = ' ' * indent
|
2666
|
-
|
2719
|
+
if gutter_width
|
2720
|
+
lines.map! {|line| line.empty? ? line : padding + line[gutter_width..-1] }
|
2721
|
+
else
|
2722
|
+
lines.map! {|line| line.empty? ? line : padding + line }
|
2723
|
+
end
|
2667
2724
|
end
|
2668
2725
|
|
2669
2726
|
nil
|