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
@@ -1,5 +1,6 @@
|
|
1
|
+
module Asciidoctor
|
1
2
|
# Public: Methods for managing items for AsciiDoc olists, ulist, and dlists.
|
2
|
-
class
|
3
|
+
class ListItem < AbstractBlock
|
3
4
|
|
4
5
|
# Public: Get/Set the String used to mark this list item
|
5
6
|
attr_accessor :marker
|
@@ -20,7 +21,7 @@ class Asciidoctor::ListItem < Asciidoctor::AbstractBlock
|
|
20
21
|
|
21
22
|
def text
|
22
23
|
# this will allow the text to be processed
|
23
|
-
|
24
|
+
Block.new(self, nil, [@text]).content
|
24
25
|
end
|
25
26
|
|
26
27
|
def content
|
@@ -39,7 +40,7 @@ class Asciidoctor::ListItem < Asciidoctor::AbstractBlock
|
|
39
40
|
#
|
40
41
|
# Returns nothing
|
41
42
|
def fold_first(continuation_connects_first_block = false, content_adjacent = false)
|
42
|
-
if !blocks.empty? && blocks.first.is_a?(
|
43
|
+
if !blocks.empty? && blocks.first.is_a?(Block) &&
|
43
44
|
((blocks.first.context == :paragraph && !continuation_connects_first_block) ||
|
44
45
|
((content_adjacent || !continuation_connects_first_block) && blocks.first.context == :literal &&
|
45
46
|
blocks.first.attr('options', []).include?('listparagraph')))
|
@@ -56,20 +57,20 @@ class Asciidoctor::ListItem < Asciidoctor::AbstractBlock
|
|
56
57
|
|
57
58
|
def splain(parent_level = 0)
|
58
59
|
parent_level += 1
|
59
|
-
|
60
|
-
|
60
|
+
Debug.puts_indented(parent_level, "List Item anchor: #{@anchor}") unless @anchor.nil?
|
61
|
+
Debug.puts_indented(parent_level, "Text: #{@text}") unless @text.nil?
|
61
62
|
|
62
|
-
|
63
|
+
Debug.puts_indented(parent_level, "Blocks: #{@blocks.count}")
|
63
64
|
|
64
65
|
if @blocks.any?
|
65
|
-
|
66
|
+
Debug.puts_indented(parent_level, "Blocks content (#{@blocks.count}):")
|
66
67
|
@blocks.each_with_index do |block, i|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
68
|
+
Debug.puts_indented(parent_level, "v" * (60 - parent_level*2))
|
69
|
+
Debug.puts_indented(parent_level, "Block ##{i} is a #{block.class}")
|
70
|
+
Debug.puts_indented(parent_level, "Name is #{block.title rescue 'n/a'}")
|
71
|
+
Debug.puts_indented(parent_level, "=" * 40)
|
71
72
|
block.splain(parent_level) if block.respond_to? :splain
|
72
|
-
|
73
|
+
Debug.puts_indented(parent_level, "^" * (60 - parent_level*2))
|
73
74
|
end
|
74
75
|
end
|
75
76
|
nil
|
@@ -79,3 +80,4 @@ class Asciidoctor::ListItem < Asciidoctor::AbstractBlock
|
|
79
80
|
"#{super.to_s} - #@context [text:#@text, blocks:#{(@blocks || []).size}]"
|
80
81
|
end
|
81
82
|
end
|
83
|
+
end
|
data/lib/asciidoctor/reader.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Asciidoctor
|
2
|
+
# Public: Methods for retrieving lines from AsciiDoc source files
|
3
|
+
class Reader
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
# Public: Get the String document source.
|
5
|
+
# Public: Get the document source as a String Array of lines.
|
7
6
|
attr_reader :source
|
8
7
|
|
9
|
-
# Public: Get the
|
10
|
-
attr_reader :
|
8
|
+
# Public: Get the 1-based offset of the current line.
|
9
|
+
attr_reader :lineno
|
11
10
|
|
12
11
|
# Public: Initialize the Reader object.
|
13
12
|
#
|
14
13
|
# data - The Array of Strings holding the Asciidoc source document. The
|
15
|
-
# original instance of this Array is not modified
|
14
|
+
# original instance of this Array is not modified (default: nil)
|
16
15
|
# document - The document with which this reader is associated. Used to access
|
17
|
-
# document attributes
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
16
|
+
# document attributes (default: nil)
|
17
|
+
# preprocess - A flag indicating whether to run the preprocessor on these lines.
|
18
|
+
# Only enable for the outer-most Reader. If this argument is true,
|
19
|
+
# a Document object must also be supplied.
|
20
|
+
# (default: false)
|
21
21
|
# block - A block that can be used to retrieve external Asciidoc
|
22
22
|
# data to include in this document.
|
23
23
|
#
|
@@ -25,33 +25,63 @@ class Asciidoctor::Reader
|
|
25
25
|
#
|
26
26
|
# data = File.readlines(filename)
|
27
27
|
# reader = Asciidoctor::Reader.new data
|
28
|
-
def initialize(data =
|
29
|
-
|
30
|
-
|
28
|
+
def initialize(data = nil, document = nil, preprocess = false, &block)
|
29
|
+
data = [] if data.nil?
|
30
|
+
# TODO use Struct to track file/lineno info; track as file changes; offset for sub-readers
|
31
|
+
@lineno = 0
|
32
|
+
if !preprocess
|
31
33
|
@lines = data.is_a?(String) ? data.lines.entries : data.dup
|
34
|
+
@preprocess_source = false
|
32
35
|
elsif !data.empty?
|
33
|
-
|
36
|
+
# NOTE we assume document is not nil!
|
34
37
|
@document = document
|
35
|
-
|
38
|
+
@preprocess_source = true
|
39
|
+
@include_block = block_given? ? block : nil
|
40
|
+
normalize_data(data.is_a?(String) ? data.lines.entries : data)
|
36
41
|
else
|
37
42
|
@lines = []
|
43
|
+
@preprocess_source = false
|
38
44
|
end
|
39
45
|
|
40
|
-
@source = @lines.
|
46
|
+
@source = @lines.dup
|
47
|
+
|
48
|
+
@next_line_preprocessed = false
|
49
|
+
@unescape_next_line = false
|
50
|
+
|
51
|
+
@conditionals_stack = []
|
52
|
+
@skipping = false
|
53
|
+
@eof = false
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: Get a copy of the remaining Array of String lines parsed from the source
|
57
|
+
def lines
|
58
|
+
@lines.nil? ? nil : @lines.dup
|
41
59
|
end
|
42
60
|
|
43
61
|
# Public: Check whether there are any lines left to read.
|
44
62
|
#
|
45
|
-
#
|
46
|
-
|
47
|
-
|
63
|
+
# If preprocessing is enabled for this Reader, and there are lines remaining,
|
64
|
+
# the next line is preprocessed before checking whether there are more lines.
|
65
|
+
#
|
66
|
+
# Returns true if @lines is empty, or false otherwise.
|
67
|
+
def has_more_lines?
|
68
|
+
if @eof || (@eof = @lines.empty?)
|
69
|
+
false
|
70
|
+
elsif @preprocess_source && !@next_line_preprocessed
|
71
|
+
preprocess_next_line.nil? ? false : !@lines.empty?
|
72
|
+
else
|
73
|
+
true
|
74
|
+
end
|
48
75
|
end
|
49
76
|
|
50
77
|
# Public: Check whether this reader is empty (contains no lines)
|
51
78
|
#
|
52
|
-
#
|
79
|
+
# If preprocessing is enabled for this Reader, and there are lines remaining,
|
80
|
+
# the next line is preprocessed before checking whether there are more lines.
|
81
|
+
#
|
82
|
+
# Returns true if @lines is empty, otherwise false.
|
53
83
|
def empty?
|
54
|
-
|
84
|
+
!has_more_lines?
|
55
85
|
end
|
56
86
|
|
57
87
|
# Private: Strip off leading blank lines in the Array of lines.
|
@@ -61,25 +91,27 @@ class Asciidoctor::Reader
|
|
61
91
|
# @lines
|
62
92
|
# => ["\n", "\t\n", "Foo\n", "Bar\n", "\n"]
|
63
93
|
#
|
64
|
-
#
|
94
|
+
# skip_blank_lines
|
65
95
|
# => 2
|
66
96
|
#
|
67
97
|
# @lines
|
68
98
|
# => ["Foo\n", "Bar\n"]
|
69
99
|
#
|
70
100
|
# Returns an Integer of the number of lines skipped
|
71
|
-
def
|
101
|
+
def skip_blank_lines
|
72
102
|
skipped = 0
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
103
|
+
# optimized code for shortest execution path
|
104
|
+
while !(next_line = get_line).nil?
|
105
|
+
if next_line.chomp.empty?
|
106
|
+
skipped += 1
|
107
|
+
else
|
108
|
+
unshift_line next_line
|
109
|
+
break
|
110
|
+
end
|
111
|
+
end
|
77
112
|
|
78
113
|
skipped
|
79
114
|
end
|
80
|
-
# Create alias of skip_blank named skip_blank_lines for readability
|
81
|
-
# TODO likely want to drop the original method name
|
82
|
-
alias :skip_blank_lines :skip_blank
|
83
115
|
|
84
116
|
# Public: Consume consecutive lines containing line- or block-level comments.
|
85
117
|
#
|
@@ -94,25 +126,27 @@ class Asciidoctor::Reader
|
|
94
126
|
#
|
95
127
|
# @lines
|
96
128
|
# => ["actual text\n"]
|
97
|
-
def consume_comments(
|
129
|
+
def consume_comments(options = {})
|
98
130
|
comment_lines = []
|
99
|
-
|
100
|
-
|
101
|
-
if
|
102
|
-
comment_lines <<
|
103
|
-
elsif match = next_line.match(REGEXP[:comment_blk])
|
104
|
-
comment_lines <<
|
105
|
-
comment_lines.push(*(grab_lines_until(:terminator => match[0], :
|
106
|
-
|
107
|
-
|
108
|
-
comment_lines << get_line
|
131
|
+
preprocess = options.fetch(:preprocess, true)
|
132
|
+
while !(next_line = get_line(preprocess)).nil?
|
133
|
+
if options[:include_blank_lines] && next_line.chomp.empty?
|
134
|
+
comment_lines << next_line
|
135
|
+
elsif (commentish = next_line.start_with?('//')) && (match = next_line.match(REGEXP[:comment_blk]))
|
136
|
+
comment_lines << next_line
|
137
|
+
comment_lines.push(*(grab_lines_until(:terminator => match[0], :grab_last_line => true, :preprocess => false)))
|
138
|
+
elsif commentish && next_line.match(REGEXP[:comment])
|
139
|
+
comment_lines << next_line
|
109
140
|
else
|
141
|
+
# throw it back
|
142
|
+
unshift_line next_line
|
110
143
|
break
|
111
144
|
end
|
112
145
|
end
|
113
146
|
|
114
147
|
comment_lines
|
115
148
|
end
|
149
|
+
alias :skip_comment_lines :consume_comments
|
116
150
|
|
117
151
|
# Public: Consume consecutive lines containing line comments.
|
118
152
|
#
|
@@ -129,10 +163,12 @@ class Asciidoctor::Reader
|
|
129
163
|
# => ["bar\n"]
|
130
164
|
def consume_line_comments
|
131
165
|
comment_lines = []
|
132
|
-
|
133
|
-
|
134
|
-
|
166
|
+
# optimized code for shortest execution path
|
167
|
+
while !(next_line = get_line).nil?
|
168
|
+
if next_line.match(REGEXP[:comment])
|
169
|
+
comment_lines << next_line
|
135
170
|
else
|
171
|
+
unshift_line next_line
|
136
172
|
break
|
137
173
|
end
|
138
174
|
end
|
@@ -140,41 +176,302 @@ class Asciidoctor::Reader
|
|
140
176
|
comment_lines
|
141
177
|
end
|
142
178
|
|
143
|
-
# Skip the next line if it's a list continuation character
|
144
|
-
#
|
145
|
-
# Returns nil
|
146
|
-
def skip_list_continuation
|
147
|
-
if has_lines? && @lines.first.chomp == '+'
|
148
|
-
@lines.shift
|
149
|
-
end
|
150
|
-
|
151
|
-
nil
|
152
|
-
end
|
153
|
-
|
154
179
|
# Public: Get the next line of source data. Consumes the line returned.
|
155
180
|
#
|
181
|
+
# preprocess - A Boolean flag indicating whether to evaluate preprocessing
|
182
|
+
# directives (macros) before reading line (default: true)
|
183
|
+
#
|
156
184
|
# Returns the String of the next line of the source data if data is present.
|
157
185
|
# Returns nil if there is no more data.
|
158
|
-
def get_line
|
159
|
-
@lines.
|
186
|
+
def get_line(preprocess = true)
|
187
|
+
if @eof || (@eof = @lines.empty?)
|
188
|
+
@next_line_preprocessed = true
|
189
|
+
nil
|
190
|
+
elsif preprocess && @preprocess_source &&
|
191
|
+
!@next_line_preprocessed && preprocess_next_line.nil?
|
192
|
+
@next_line_preprocessed = true
|
193
|
+
nil
|
194
|
+
else
|
195
|
+
@lineno += 1
|
196
|
+
@next_line_preprocessed = false
|
197
|
+
if @unescape_next_line
|
198
|
+
@unescape_next_line = false
|
199
|
+
@lines.shift[1..-1]
|
200
|
+
else
|
201
|
+
@lines.shift
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Public: Advance to the next line by discarding the line at the front of the stack
|
207
|
+
#
|
208
|
+
# Removes the line at the front of the stack without any processing.
|
209
|
+
#
|
210
|
+
# returns a boolean indicating whether there was a line to discard
|
211
|
+
def advance
|
212
|
+
@next_line_preprocessed = false
|
213
|
+
# we assume that we're advancing over a line of known content
|
214
|
+
if @eof || (@eof = @lines.empty?)
|
215
|
+
false
|
216
|
+
else
|
217
|
+
@lineno += 1
|
218
|
+
@lines.shift
|
219
|
+
true
|
220
|
+
end
|
160
221
|
end
|
161
|
-
# QUESTION what about advance?
|
162
|
-
# Create an alias of get_line named next_line for readability
|
163
|
-
alias :next_line :get_line
|
164
222
|
|
165
223
|
# Public: Get the next line of source data. Does not consume the line returned.
|
166
224
|
#
|
225
|
+
# preprocess - A Boolean flag indicating whether to evaluate preprocessing
|
226
|
+
# directives (macros) before reading line (default: true)
|
227
|
+
#
|
167
228
|
# Returns a String dup of the next line of the source data if data is present.
|
168
229
|
# Returns nil if there is no more data.
|
169
|
-
def peek_line
|
170
|
-
|
230
|
+
def peek_line(preprocess = true)
|
231
|
+
if !preprocess
|
232
|
+
# QUESTION do we need to dup?
|
233
|
+
@eof || (@eof = @lines.empty?) ? nil : @lines.first.dup
|
234
|
+
elsif has_more_lines?
|
235
|
+
# QUESTION do we need to dup?
|
236
|
+
@lines.first.dup
|
237
|
+
else
|
238
|
+
nil
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# TODO document & test me!
|
243
|
+
def peek_lines(number = 1)
|
244
|
+
lines = []
|
245
|
+
idx = 0
|
246
|
+
(1..number).each do
|
247
|
+
if @preprocess_source && !@next_line_preprocessed
|
248
|
+
advanced = preprocess_next_line
|
249
|
+
break if advanced.nil? || @eof || (@eof = @lines.empty?)
|
250
|
+
idx = 0 if advanced
|
251
|
+
end
|
252
|
+
break if idx >= @lines.size
|
253
|
+
# QUESTION do we need to dup?
|
254
|
+
lines << @lines[idx].dup
|
255
|
+
idx += 1
|
256
|
+
end
|
257
|
+
lines
|
258
|
+
end
|
259
|
+
|
260
|
+
# Internal: Preprocess the next line until the cursor is at a line of content
|
261
|
+
#
|
262
|
+
# Evaluate preprocessor macros on the next line, continuing to do so until
|
263
|
+
# the cursor arrives at a line of included content. That line is marked as
|
264
|
+
# preprocessed so that preprocessing is not performed multiple times.
|
265
|
+
#
|
266
|
+
# returns a Boolean indicating whether the cursor advanced, or nil if there
|
267
|
+
# are no more lines available.
|
268
|
+
def preprocess_next_line
|
269
|
+
# this return could be happening from a recursive call
|
270
|
+
return nil if @eof || (next_line = @lines.first).nil?
|
271
|
+
if next_line.include?('if') && (match = next_line.match(REGEXP[:ifdef_macro]))
|
272
|
+
if next_line.start_with? '\\'
|
273
|
+
@next_line_preprocessed = true
|
274
|
+
@unescape_next_line = true
|
275
|
+
false
|
276
|
+
else
|
277
|
+
preprocess_conditional_inclusion(*match.captures)
|
278
|
+
end
|
279
|
+
elsif @skipping
|
280
|
+
advance
|
281
|
+
# skip over comment blocks, we don't want to process directives in them
|
282
|
+
skip_comment_lines :include_blank_lines => true, :preprocess => false
|
283
|
+
preprocess_next_line.nil? ? nil : true
|
284
|
+
elsif next_line.include?('include::') && match = next_line.match(REGEXP[:include_macro])
|
285
|
+
if next_line.start_with? '\\'
|
286
|
+
@next_line_preprocessed = true
|
287
|
+
@unescape_next_line = true
|
288
|
+
false
|
289
|
+
else
|
290
|
+
preprocess_include(match[1])
|
291
|
+
end
|
292
|
+
else
|
293
|
+
@next_line_preprocessed = true
|
294
|
+
false
|
295
|
+
end
|
171
296
|
end
|
172
297
|
|
173
|
-
#
|
298
|
+
# Internal: Preprocess the directive (macro) to conditionally include content.
|
299
|
+
#
|
300
|
+
# Preprocess the conditional inclusion directive (ifdef, ifndef, ifeval,
|
301
|
+
# endif) under the cursor. If the Reader is currently skipping content, then
|
302
|
+
# simply track the open and close delimiters of any nested conditional
|
303
|
+
# blocks. If the Reader is not skipping, mark whether the condition is
|
304
|
+
# satisfied and continue preprocessing recursively until the next line of
|
305
|
+
# available content is found.
|
306
|
+
#
|
307
|
+
# directive - The conditional inclusion directive (ifdef, ifndef, ifeval, endif)
|
308
|
+
# target - The target, which is the name of one or more attributes that are
|
309
|
+
# used in the condition (blank in the case of the ifeval directive)
|
310
|
+
# delimiter - The conditional delimiter for multiple attributes ('+' means all
|
311
|
+
# attributes must be defined or undefined, ',' means any of the attributes
|
312
|
+
# can be defined or undefined.
|
313
|
+
# text - The text associated with this directive (occurring between the square brackets)
|
314
|
+
# Used for a single-line conditional block in the case of the ifdef or
|
315
|
+
# ifndef directives, and for the conditional expression for the ifeval directive.
|
316
|
+
#
|
317
|
+
# returns a Boolean indicating whether the cursor advanced, or nil if there
|
318
|
+
# are no more lines available.
|
319
|
+
def preprocess_conditional_inclusion(directive, target, delimiter, text)
|
320
|
+
# must have a target before brackets if ifdef or ifndef
|
321
|
+
# must not have text between brackets if endif
|
322
|
+
# don't honor match if it doesn't meet this criteria
|
323
|
+
if ((directive == 'ifdef' || directive == 'ifndef') && target.empty?) ||
|
324
|
+
(directive == 'endif' && !text.nil?)
|
325
|
+
@next_line_preprocessed = true
|
326
|
+
return false
|
327
|
+
end
|
328
|
+
|
329
|
+
if directive == 'endif'
|
330
|
+
stack_size = @conditionals_stack.size
|
331
|
+
if stack_size > 0
|
332
|
+
pair = @conditionals_stack.last
|
333
|
+
if target.empty? || target == pair[:target]
|
334
|
+
@conditionals_stack.pop
|
335
|
+
@skipping = @conditionals_stack.empty? ? false : @conditionals_stack.last[:skipping]
|
336
|
+
else
|
337
|
+
puts "asciidoctor: ERROR: line #{@lineno + 1}: mismatched macro: endif::#{target}[], expected endif::#{pair[:target]}[]"
|
338
|
+
end
|
339
|
+
else
|
340
|
+
puts "asciidoctor: ERROR: line #{@lineno + 1}: unmatched macro: endif::#{target}[]"
|
341
|
+
end
|
342
|
+
advance
|
343
|
+
return preprocess_next_line.nil? ? nil : true
|
344
|
+
end
|
345
|
+
|
346
|
+
skip = nil
|
347
|
+
if !@skipping
|
348
|
+
case directive
|
349
|
+
when 'ifdef'
|
350
|
+
case delimiter
|
351
|
+
when nil
|
352
|
+
# if the attribute is undefined, then skip
|
353
|
+
skip = !@document.attributes.has_key?(target)
|
354
|
+
when ','
|
355
|
+
# if any attribute is defined, then don't skip
|
356
|
+
skip = !target.split(',').detect {|name| @document.attributes.has_key? name }
|
357
|
+
when '+'
|
358
|
+
# if any attribute is undefined, then skip
|
359
|
+
skip = target.split('+').detect {|name| !@document.attributes.has_key? name }
|
360
|
+
end
|
361
|
+
when 'ifndef'
|
362
|
+
case delimiter
|
363
|
+
when nil
|
364
|
+
# if the attribute is defined, then skip
|
365
|
+
skip = @document.attributes.has_key?(target)
|
366
|
+
when ','
|
367
|
+
# if any attribute is undefined, then don't skip
|
368
|
+
skip = !target.split(',').detect {|name| !@document.attributes.has_key? name }
|
369
|
+
when '+'
|
370
|
+
# if any attribute is defined, then skip
|
371
|
+
skip = target.split('+').detect {|name| @document.attributes.has_key? name }
|
372
|
+
end
|
373
|
+
when 'ifeval'
|
374
|
+
# the text in brackets must match an expression
|
375
|
+
# don't honor match if it doesn't meet this criteria
|
376
|
+
if !target.empty? || !(expr_match = text.strip.match(REGEXP[:eval_expr]))
|
377
|
+
@next_line_preprocessed = true
|
378
|
+
return false
|
379
|
+
end
|
380
|
+
|
381
|
+
lhs = resolve_expr_val(expr_match[1])
|
382
|
+
op = expr_match[2]
|
383
|
+
rhs = resolve_expr_val(expr_match[3])
|
384
|
+
|
385
|
+
skip = !lhs.send(op.to_sym, rhs)
|
386
|
+
end
|
387
|
+
@skipping = skip
|
388
|
+
end
|
389
|
+
advance
|
390
|
+
# single line conditional inclusion
|
391
|
+
if directive != 'ifeval' && !text.nil?
|
392
|
+
if !@skipping
|
393
|
+
unshift_line "#{text.rstrip}\n"
|
394
|
+
return true
|
395
|
+
end
|
396
|
+
# conditional inclusion block
|
397
|
+
else
|
398
|
+
@conditionals_stack << {:target => target, :skip => skip, :skipping => @skipping}
|
399
|
+
end
|
400
|
+
return preprocess_next_line.nil? ? nil : true
|
401
|
+
end
|
402
|
+
|
403
|
+
# Internal: Preprocess the directive (macro) to include the target document.
|
404
|
+
#
|
405
|
+
# Preprocess the directive to include the target document. The scenarios
|
406
|
+
# are as follows:
|
407
|
+
#
|
408
|
+
# If SafeMode is SECURE or greater, the directive is ignore and the include
|
409
|
+
# directive line is emitted verbatim.
|
410
|
+
#
|
411
|
+
# Otherwise, if an include handler is specified (currently controlled by a
|
412
|
+
# closure block), pass the target to that block and expect an Array of String
|
413
|
+
# lines in return.
|
414
|
+
#
|
415
|
+
# Otherwise, if the include-depth attribute is greater than 0, normalize the
|
416
|
+
# target path and read the lines onto the beginning of the Array of source
|
417
|
+
# data.
|
418
|
+
#
|
419
|
+
# If none of the above apply, emit the include directive line verbatim.
|
420
|
+
#
|
421
|
+
# target - The name of the source document to include as specified in the
|
422
|
+
# target slot of the include::[] macro
|
423
|
+
#
|
424
|
+
# returns a Boolean indicating whether the line under the cursor has changed.
|
425
|
+
def preprocess_include(target)
|
426
|
+
# if running in SafeMode::SECURE or greater, don't process this directive
|
427
|
+
if @document.safe >= SafeMode::SECURE
|
428
|
+
@next_line_preprocessed = true
|
429
|
+
false
|
430
|
+
# assume that if a block is given, the developer wants
|
431
|
+
# to handle when and how to process the include, even
|
432
|
+
# if the include-depth attribute is 0
|
433
|
+
elsif @include_block
|
434
|
+
advance
|
435
|
+
# FIXME this borks line numbers
|
436
|
+
@lines.unshift(*@include_block.call(target).map {|l| "#{l.rstrip}\n"})
|
437
|
+
# FIXME currently we're not checking the upper bound of the include depth
|
438
|
+
elsif @document.attributes.fetch('include-depth', 0).to_i > 0
|
439
|
+
advance
|
440
|
+
# FIXME this borks line numbers
|
441
|
+
@lines.unshift(*File.readlines(@document.normalize_asset_path(target, 'include file')).map {|l| "#{l.rstrip}\n"})
|
442
|
+
true
|
443
|
+
else
|
444
|
+
@next_line_preprocessed = true
|
445
|
+
false
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
# Public: Push the String line onto the beginning of the Array of source data.
|
450
|
+
#
|
451
|
+
# Since this line was (assumed to be) previously retrieved through the
|
452
|
+
# reader, it is marked as preprocessed.
|
453
|
+
#
|
454
|
+
# returns nil
|
455
|
+
def unshift_line(line)
|
456
|
+
@lines.unshift line
|
457
|
+
@next_line_preprocessed = true
|
458
|
+
@eof = false
|
459
|
+
@lineno -= 1
|
460
|
+
nil
|
461
|
+
end
|
462
|
+
|
463
|
+
# Public: Push Array of lines onto the front of the Array of source data, unless `lines` has no non-nil values.
|
174
464
|
#
|
175
465
|
# Returns nil
|
176
466
|
def unshift(*new_lines)
|
177
|
-
|
467
|
+
size = new_lines.size
|
468
|
+
if size > 0
|
469
|
+
@lines.unshift(*new_lines)
|
470
|
+
# assume that what we are putting back on is already processed for directives
|
471
|
+
@next_line_preprocessed = true
|
472
|
+
@eof = false
|
473
|
+
@lineno -= size
|
474
|
+
end
|
178
475
|
nil
|
179
476
|
end
|
180
477
|
|
@@ -184,7 +481,7 @@ class Asciidoctor::Reader
|
|
184
481
|
#
|
185
482
|
# Returns nil
|
186
483
|
def chomp_last!
|
187
|
-
@lines.last.chomp! unless @lines.empty?
|
484
|
+
@lines.last.chomp! unless @eof || (@eof = @lines.empty?)
|
188
485
|
nil
|
189
486
|
end
|
190
487
|
|
@@ -218,27 +515,27 @@ class Asciidoctor::Reader
|
|
218
515
|
buffer = []
|
219
516
|
|
220
517
|
finis = false
|
221
|
-
|
518
|
+
advance if options[:skip_first_line]
|
222
519
|
# save options to locals for minor optimization
|
223
520
|
terminator = options[:terminator]
|
224
521
|
terminator.chomp! if terminator
|
225
522
|
break_on_blank_lines = options[:break_on_blank_lines]
|
226
523
|
break_on_list_continuation = options[:break_on_list_continuation]
|
227
|
-
|
228
|
-
|
524
|
+
skip_line_comments = options[:skip_line_comments]
|
525
|
+
preprocess = options.fetch(:preprocess, true)
|
526
|
+
while !(this_line = get_line(preprocess)).nil?
|
527
|
+
Debug.debug { "Reader processing line: '#{this_line}'" }
|
229
528
|
finis = true if terminator && this_line.chomp == terminator
|
230
529
|
finis = true if !finis && break_on_blank_lines && this_line.strip.empty?
|
231
530
|
finis = true if !finis && break_on_list_continuation && this_line.chomp == LIST_CONTINUATION
|
232
531
|
finis = true if !finis && block && yield(this_line)
|
233
532
|
if finis
|
234
|
-
self.unshift(this_line) if options[:preserve_last_line]
|
235
533
|
buffer << this_line if options[:grab_last_line]
|
534
|
+
unshift_line(this_line) if options[:preserve_last_line]
|
236
535
|
break
|
237
536
|
end
|
238
537
|
|
239
|
-
|
240
|
-
# skip it
|
241
|
-
else
|
538
|
+
unless skip_line_comments && this_line.match(REGEXP[:comment])
|
242
539
|
buffer << this_line
|
243
540
|
end
|
244
541
|
end
|
@@ -263,109 +560,63 @@ class Asciidoctor::Reader
|
|
263
560
|
# sanitize_attribute_name('Foo 3 #-Billy')
|
264
561
|
# => 'foo3-billy'
|
265
562
|
def sanitize_attribute_name(name)
|
266
|
-
|
563
|
+
Lexer.sanitize_attribute_name(name)
|
267
564
|
end
|
268
565
|
|
269
|
-
# Private:
|
270
|
-
def
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
# if running in SafeMode::SECURE or greater, don't process
|
279
|
-
# this directive (or should we swallow it?)
|
280
|
-
elsif @document.safe >= SafeMode::SECURE
|
281
|
-
raw_source << line
|
282
|
-
# assume that if a block is given, the developer wants
|
283
|
-
# to handle when and how to process the include, even
|
284
|
-
# if the include-depth attribute is 0
|
285
|
-
elsif block_given?
|
286
|
-
raw_source.concat yield(inc[1])
|
287
|
-
elsif include_depth > 0
|
288
|
-
raw_source.concat File.readlines(@document.normalize_asset_path(inc[1], 'include file'))
|
289
|
-
else
|
290
|
-
raw_source << line
|
291
|
-
end
|
292
|
-
else
|
293
|
-
raw_source << line
|
294
|
-
end
|
566
|
+
# Private: Resolve the value of one side of the expression
|
567
|
+
def resolve_expr_val(str)
|
568
|
+
val = str
|
569
|
+
type = nil
|
570
|
+
|
571
|
+
if val.start_with?('"') && val.end_with?('"') ||
|
572
|
+
val.start_with?('\'') && val.end_with?('\'')
|
573
|
+
type = :s
|
574
|
+
val = val[1..-2]
|
295
575
|
end
|
296
576
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
continuing_value += ' ' + $1.rstrip
|
313
|
-
# An empty line ends a continuation
|
314
|
-
elsif line.strip.empty?
|
315
|
-
raw_source.unshift(line)
|
316
|
-
close_continue = true
|
317
|
-
else
|
318
|
-
# If this continued line isn't empty and doesn't end with a +, then
|
319
|
-
# this is the end of the continuation, no matter what the next line
|
320
|
-
# does.
|
321
|
-
continuing_value += ' ' + line.strip
|
322
|
-
close_continue = true
|
323
|
-
end
|
324
|
-
if close_continue
|
325
|
-
unless attribute_overridden? continuing_key
|
326
|
-
@document.attributes[continuing_key] = apply_attribute_value_subs(continuing_value)
|
327
|
-
end
|
328
|
-
continuing_key = nil
|
329
|
-
continuing_value = nil
|
330
|
-
end
|
331
|
-
elsif line.match(REGEXP[:ifdef_macro])
|
332
|
-
attr = $2
|
333
|
-
skip = case $1
|
334
|
-
when 'ifdef'; !@document.attributes.has_key?(attr)
|
335
|
-
when 'ifndef'; @document.attributes.has_key?(attr)
|
336
|
-
end
|
337
|
-
skip_to = /^endif::#{attr}\[\]\s*\n/ if skip
|
338
|
-
elsif line.match(REGEXP[:attr_assign])
|
339
|
-
key = sanitize_attribute_name($1)
|
340
|
-
value = $2
|
341
|
-
if value.match(REGEXP[:attr_continue])
|
342
|
-
# attribute value continuation line; grab lines until we run out
|
343
|
-
# of continuation lines
|
344
|
-
continuing_key = key
|
345
|
-
continuing_value = $1.rstrip # strip off the spaces and +
|
346
|
-
else
|
347
|
-
unless attribute_overridden? key
|
348
|
-
@document.attributes[key] = apply_attribute_value_subs(value)
|
349
|
-
if key == 'backend'
|
350
|
-
@document.update_backend_attributes()
|
351
|
-
end
|
352
|
-
end
|
353
|
-
end
|
354
|
-
elsif line.match(REGEXP[:attr_delete])
|
355
|
-
key = sanitize_attribute_name($1)
|
356
|
-
unless attribute_overridden? key
|
357
|
-
@document.attributes.delete(key)
|
358
|
-
end
|
359
|
-
elsif !line.match(REGEXP[:endif_macro])
|
360
|
-
while line.match(REGEXP[:attr_conditional])
|
361
|
-
value = @document.attributes.has_key?($1) ? $2 : ''
|
362
|
-
line.sub!(REGEXP[:attr_conditional], value)
|
363
|
-
end
|
364
|
-
# NOTE leave line comments in as they play a role in flow (such as a list divider)
|
365
|
-
@lines << line
|
577
|
+
if val.include? '{'
|
578
|
+
val = @document.sub_attributes(val)
|
579
|
+
end
|
580
|
+
|
581
|
+
if type != :s
|
582
|
+
if val.empty?
|
583
|
+
val = nil
|
584
|
+
elsif val == 'true'
|
585
|
+
val = true
|
586
|
+
elsif val == 'false'
|
587
|
+
val = false
|
588
|
+
elsif val.include?('.')
|
589
|
+
val = val.to_f
|
590
|
+
else
|
591
|
+
val = val.to_i
|
366
592
|
end
|
367
593
|
end
|
368
594
|
|
595
|
+
val
|
596
|
+
end
|
597
|
+
|
598
|
+
# Private: Normalize raw input, used for the outermost Reader.
|
599
|
+
#
|
600
|
+
# This method strips whitespace from the end of every line of
|
601
|
+
# the source data and appends a LF (i.e., Unix endline). This
|
602
|
+
# whitespace substitution is very important to how Asciidoctor
|
603
|
+
# works.
|
604
|
+
#
|
605
|
+
# Any leading or trailing blank lines are also removed.
|
606
|
+
#
|
607
|
+
# The normalized lines are assigned to the @lines instance variable.
|
608
|
+
#
|
609
|
+
# data - A String Array of input data to be normalized
|
610
|
+
#
|
611
|
+
# returns nothing
|
612
|
+
def normalize_data(data)
|
613
|
+
# normalize line ending to LF (purging occurrences of CRLF)
|
614
|
+
# this rstrip is *very* important to how Asciidoctor works
|
615
|
+
@lines = data.map {|line| "#{line.rstrip}\n" }
|
616
|
+
|
617
|
+
@lines.shift && @lineno += 1 while !@lines.first.nil? && @lines.first.chomp.empty?
|
618
|
+
@lines.pop while !@lines.last.nil? && @lines.last.chomp.empty?
|
619
|
+
|
369
620
|
# Process bibliography references, so they're available when text
|
370
621
|
# before the reference is being rendered.
|
371
622
|
# FIXME we don't have support for bibliography lists yet, so disable for now
|
@@ -375,41 +626,7 @@ class Asciidoctor::Reader
|
|
375
626
|
# @document.register(:ids, biblio[1])
|
376
627
|
# end
|
377
628
|
#end
|
629
|
+
nil
|
378
630
|
end
|
379
|
-
|
380
|
-
# Internal: Determine if the attribute has been overridden in the document options
|
381
|
-
#
|
382
|
-
# key - The attribute key to check
|
383
|
-
#
|
384
|
-
# Returns true if the attribute has been overridden, false otherwise
|
385
|
-
def attribute_overridden?(key)
|
386
|
-
@overrides.has_key?(key) || @overrides.has_key?(key + '!')
|
387
|
-
end
|
388
|
-
|
389
|
-
# Internal: Apply substitutions to the attribute value
|
390
|
-
#
|
391
|
-
# If the value is an inline passthrough macro (e.g., pass:[text]), then
|
392
|
-
# apply the substitutions defined on the macro to the text. Otherwise,
|
393
|
-
# apply the verbatim substitutions to the value.
|
394
|
-
#
|
395
|
-
# value - The String attribute value on which to perform substitutions
|
396
|
-
#
|
397
|
-
# Returns The String value with substitutions performed.
|
398
|
-
def apply_attribute_value_subs(value)
|
399
|
-
if value.match(REGEXP[:pass_macro_basic])
|
400
|
-
# copy match for Ruby 1.8.7 compat
|
401
|
-
m = $~
|
402
|
-
subs = []
|
403
|
-
if !m[1].empty?
|
404
|
-
subs = @document.resolve_subs(m[1])
|
405
|
-
end
|
406
|
-
if !subs.empty?
|
407
|
-
@document.apply_subs(m[2], subs)
|
408
|
-
else
|
409
|
-
m[2]
|
410
|
-
end
|
411
|
-
else
|
412
|
-
@document.apply_header_subs(value)
|
413
|
-
end
|
414
|
-
end
|
631
|
+
end
|
415
632
|
end
|