asciidoctor 0.1.3 → 0.1.4
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 +387 -0
- data/README.adoc +358 -348
- data/asciidoctor.gemspec +30 -9
- data/bin/asciidoctor +3 -0
- data/bin/asciidoctor-safe +3 -0
- data/compat/asciidoc.conf +76 -4
- data/lib/asciidoctor.rb +174 -79
- data/lib/asciidoctor/abstract_block.rb +131 -101
- data/lib/asciidoctor/abstract_node.rb +108 -26
- data/lib/asciidoctor/attribute_list.rb +1 -1
- data/lib/asciidoctor/backends/_stylesheets.rb +204 -62
- data/lib/asciidoctor/backends/base_template.rb +11 -22
- data/lib/asciidoctor/backends/docbook45.rb +158 -163
- data/lib/asciidoctor/backends/docbook5.rb +103 -0
- data/lib/asciidoctor/backends/html5.rb +662 -445
- data/lib/asciidoctor/block.rb +54 -44
- data/lib/asciidoctor/cli/invoker.rb +41 -20
- data/lib/asciidoctor/cli/options.rb +66 -20
- data/lib/asciidoctor/debug.rb +1 -1
- data/lib/asciidoctor/document.rb +265 -100
- data/lib/asciidoctor/extensions.rb +443 -0
- data/lib/asciidoctor/helpers.rb +38 -6
- data/lib/asciidoctor/inline.rb +5 -5
- data/lib/asciidoctor/lexer.rb +532 -250
- data/lib/asciidoctor/{list_item.rb → list.rb} +33 -13
- data/lib/asciidoctor/path_resolver.rb +28 -2
- data/lib/asciidoctor/reader.rb +814 -455
- data/lib/asciidoctor/renderer.rb +128 -42
- data/lib/asciidoctor/section.rb +55 -41
- data/lib/asciidoctor/substituters.rb +380 -107
- data/lib/asciidoctor/table.rb +40 -30
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +32 -96
- data/man/{asciidoctor.ad → asciidoctor.adoc} +57 -48
- data/test/attributes_test.rb +200 -27
- data/test/blocks_test.rb +361 -22
- data/test/document_test.rb +496 -81
- data/test/extensions_test.rb +448 -0
- data/test/fixtures/basic-docinfo-footer.html +6 -0
- data/test/fixtures/basic-docinfo-footer.xml +8 -0
- data/test/fixtures/basic-docinfo.xml +3 -3
- data/test/fixtures/basic.asciidoc +1 -0
- data/test/fixtures/child-include.adoc +5 -0
- data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +6 -0
- data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +1 -0
- data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +3 -0
- data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +5 -0
- data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +6 -0
- data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +3 -0
- data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +5 -0
- data/test/fixtures/docinfo-footer.html +1 -0
- data/test/fixtures/docinfo-footer.xml +9 -0
- data/test/fixtures/docinfo.xml +1 -0
- data/test/fixtures/grandchild-include.adoc +3 -0
- data/test/fixtures/parent-include-restricted.adoc +5 -0
- data/test/fixtures/parent-include.adoc +5 -0
- data/test/invoker_test.rb +82 -8
- data/test/lexer_test.rb +21 -3
- data/test/links_test.rb +34 -2
- data/test/lists_test.rb +304 -7
- data/test/options_test.rb +19 -3
- data/test/paragraphs_test.rb +13 -0
- data/test/paths_test.rb +22 -0
- data/test/preamble_test.rb +20 -0
- data/test/reader_test.rb +1096 -644
- data/test/renderer_test.rb +152 -12
- data/test/sections_test.rb +417 -76
- data/test/substitutions_test.rb +339 -138
- data/test/tables_test.rb +109 -4
- data/test/test_helper.rb +79 -13
- data/test/text_test.rb +111 -11
- metadata +54 -18
@@ -1,4 +1,28 @@
|
|
1
1
|
module Asciidoctor
|
2
|
+
# Public: Methods for managing AsciiDoc lists (ordered, unordered and labeled lists)
|
3
|
+
class List < AbstractBlock
|
4
|
+
|
5
|
+
# Public: Create alias for blocks
|
6
|
+
alias :items :blocks
|
7
|
+
alias :items? :blocks?
|
8
|
+
|
9
|
+
def initialize(parent, context)
|
10
|
+
super(parent, context)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Public: Get the items in this list as an Array
|
14
|
+
def content
|
15
|
+
@blocks
|
16
|
+
end
|
17
|
+
|
18
|
+
def render
|
19
|
+
result = super
|
20
|
+
@document.callouts.next_list if @context == :colist
|
21
|
+
result
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
2
26
|
# Public: Methods for managing items for AsciiDoc olists, ulist, and dlists.
|
3
27
|
class ListItem < AbstractBlock
|
4
28
|
|
@@ -20,12 +44,7 @@ class ListItem < AbstractBlock
|
|
20
44
|
end
|
21
45
|
|
22
46
|
def text
|
23
|
-
|
24
|
-
Block.new(self, nil, [@text]).content
|
25
|
-
end
|
26
|
-
|
27
|
-
def content
|
28
|
-
blocks? ? blocks.map {|b| b.render }.join : nil
|
47
|
+
apply_subs @text
|
29
48
|
end
|
30
49
|
|
31
50
|
# Public: Fold the first paragraph block into the text
|
@@ -40,23 +59,24 @@ class ListItem < AbstractBlock
|
|
40
59
|
#
|
41
60
|
# Returns nothing
|
42
61
|
def fold_first(continuation_connects_first_block = false, content_adjacent = false)
|
43
|
-
if !blocks.
|
44
|
-
((
|
45
|
-
((content_adjacent || !continuation_connects_first_block) &&
|
46
|
-
|
62
|
+
if !(first_block = @blocks.first).nil? && first_block.is_a?(Block) &&
|
63
|
+
((first_block.context == :paragraph && !continuation_connects_first_block) ||
|
64
|
+
((content_adjacent || !continuation_connects_first_block) && first_block.context == :literal &&
|
65
|
+
first_block.option?('listparagraph')))
|
47
66
|
|
48
67
|
block = blocks.shift
|
49
68
|
unless @text.to_s.empty?
|
50
|
-
block.
|
69
|
+
block.lines.unshift("#@text\n")
|
51
70
|
end
|
52
71
|
|
53
|
-
@text = block.
|
72
|
+
@text = block.source
|
54
73
|
end
|
55
74
|
nil
|
56
75
|
end
|
57
76
|
|
58
77
|
def to_s
|
59
|
-
"
|
78
|
+
"#@context [text:#@text, blocks:#{(@blocks || []).size}]"
|
60
79
|
end
|
80
|
+
|
61
81
|
end
|
62
82
|
end
|
@@ -312,7 +312,7 @@ class PathResolver
|
|
312
312
|
elsif !recover
|
313
313
|
raise SecurityError, "#{opts[:target_name] || 'path'} #{target} refers to location outside jail: #{jail} (disallowed in safe mode)"
|
314
314
|
elsif !warned
|
315
|
-
|
315
|
+
warn "asciidoctor: WARNING: #{opts[:target_name] || 'path'} has illegal reference to ancestor of jail, auto-recovering"
|
316
316
|
warned = true
|
317
317
|
end
|
318
318
|
else
|
@@ -339,9 +339,14 @@ class PathResolver
|
|
339
339
|
def web_path(target, start = nil)
|
340
340
|
target = posixfy(target)
|
341
341
|
start = posixfy(start)
|
342
|
+
uri_prefix = nil
|
342
343
|
|
343
344
|
unless is_web_root?(target) || start.empty?
|
344
345
|
target = "#{start}#{SLASH}#{target}"
|
346
|
+
if target.include?(':') && target.match(Asciidoctor::REGEXP[:uri_sniff])
|
347
|
+
uri_prefix = $~[0]
|
348
|
+
target = target[uri_prefix.length..-1]
|
349
|
+
end
|
345
350
|
end
|
346
351
|
|
347
352
|
target_segments, target_root, _ = partition_path(target, true)
|
@@ -360,7 +365,28 @@ class PathResolver
|
|
360
365
|
accum
|
361
366
|
end
|
362
367
|
|
363
|
-
|
368
|
+
if uri_prefix.nil?
|
369
|
+
join_path resolved_segments, target_root
|
370
|
+
else
|
371
|
+
"#{uri_prefix}#{join_path resolved_segments, target_root}"
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
# Public: Calculate the relative path to this absolute filename from the specified base directory
|
376
|
+
#
|
377
|
+
# If either the filename or the base_directory are not absolute paths, no work is done.
|
378
|
+
#
|
379
|
+
# filename - An absolute file name as a String
|
380
|
+
# base_directory - An absolute base directory as a String
|
381
|
+
#
|
382
|
+
# Return the relative path String of the filename calculated from the base directory
|
383
|
+
def relative_path(filename, base_directory)
|
384
|
+
if (is_root? filename) && (is_root? base_directory)
|
385
|
+
offset = base_directory.chomp(@file_separator).length + 1
|
386
|
+
filename[offset..-1]
|
387
|
+
else
|
388
|
+
filename
|
389
|
+
end
|
364
390
|
end
|
365
391
|
end
|
366
392
|
end
|
data/lib/asciidoctor/reader.rb
CHANGED
@@ -1,90 +1,284 @@
|
|
1
1
|
module Asciidoctor
|
2
2
|
# Public: Methods for retrieving lines from AsciiDoc source files
|
3
3
|
class Reader
|
4
|
+
class Cursor
|
5
|
+
attr_accessor :file
|
6
|
+
attr_accessor :dir
|
7
|
+
attr_accessor :path
|
8
|
+
attr_accessor :lineno
|
9
|
+
|
10
|
+
def initialize file, dir = nil, path = nil, lineno = nil
|
11
|
+
@file = file
|
12
|
+
@dir = dir
|
13
|
+
@path = path
|
14
|
+
@lineno = lineno
|
15
|
+
end
|
4
16
|
|
5
|
-
|
6
|
-
|
17
|
+
def line_info
|
18
|
+
%(#{path}: line #{lineno})
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :file
|
23
|
+
attr_reader :dir
|
24
|
+
attr_reader :path
|
7
25
|
|
8
26
|
# Public: Get the 1-based offset of the current line.
|
9
27
|
attr_reader :lineno
|
10
28
|
|
11
|
-
# Public:
|
29
|
+
# Public: Get the document source as a String Array of lines.
|
30
|
+
attr_reader :source_lines
|
31
|
+
|
32
|
+
# Public: Control whether lines are processed using Reader#process_line on first visit (default: true)
|
33
|
+
attr_accessor :process_lines
|
34
|
+
|
35
|
+
# Public: Initialize the Reader object
|
36
|
+
def initialize data = nil, cursor = nil
|
37
|
+
if cursor.nil?
|
38
|
+
@file = @dir = nil
|
39
|
+
@path = '<stdin>'
|
40
|
+
@lineno = 1 # IMPORTANT lineno assignment must proceed prepare_lines call!
|
41
|
+
elsif cursor.is_a? String
|
42
|
+
@file = cursor
|
43
|
+
@dir = File.dirname @file
|
44
|
+
@path = File.basename @file
|
45
|
+
@lineno = 1 # IMPORTANT lineno assignment must proceed prepare_lines call!
|
46
|
+
else
|
47
|
+
@file = cursor.file
|
48
|
+
@dir = cursor.dir
|
49
|
+
@path = cursor.path || '<stdin>'
|
50
|
+
unless @file.nil?
|
51
|
+
if @dir.nil?
|
52
|
+
# REVIEW might to look at this assignment closer
|
53
|
+
@dir = File.dirname @file
|
54
|
+
@dir = nil if @dir == '.' # right?
|
55
|
+
end
|
56
|
+
|
57
|
+
if cursor.path.nil?
|
58
|
+
@path = File.basename @file
|
59
|
+
end
|
60
|
+
end
|
61
|
+
@lineno = cursor.lineno || 1 # IMPORTANT lineno assignment must proceed prepare_lines call!
|
62
|
+
end
|
63
|
+
@lines = data.nil? ? [] : (prepare_lines data)
|
64
|
+
@source_lines = @lines.dup
|
65
|
+
@eof = @lines.empty?
|
66
|
+
@look_ahead = 0
|
67
|
+
@process_lines = true
|
68
|
+
@unescape_next_line = false
|
69
|
+
end
|
70
|
+
|
71
|
+
# Internal: Prepare the lines from the provided data
|
72
|
+
#
|
73
|
+
# This method strips whitespace from the end of every line of
|
74
|
+
# the source data and appends a LF (i.e., Unix endline). This
|
75
|
+
# whitespace substitution is very important to how Asciidoctor
|
76
|
+
# works.
|
12
77
|
#
|
13
|
-
#
|
14
|
-
# original instance of this Array is not modified (default: nil)
|
15
|
-
# document - The document with which this reader is associated. Used to access
|
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
|
-
# block - A block that can be used to retrieve external Asciidoc
|
22
|
-
# data to include in this document.
|
78
|
+
# Any leading or trailing blank lines are also removed.
|
23
79
|
#
|
24
|
-
#
|
80
|
+
# The normalized lines are assigned to the @lines instance variable.
|
25
81
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
82
|
+
# data - A String Array of input data to be normalized
|
83
|
+
# opts - A Hash of options to control what cleansing is done
|
84
|
+
#
|
85
|
+
# Returns The String lines extracted from the data
|
86
|
+
def prepare_lines data, opts = {}
|
87
|
+
data.is_a?(String) ? data.each_line.to_a : data.dup
|
88
|
+
end
|
89
|
+
|
90
|
+
# Internal: Processes a previously unvisited line
|
91
|
+
#
|
92
|
+
# By default, this method marks the line as processed
|
93
|
+
# by incrementing the look_ahead counter and returns
|
94
|
+
# the line unmodified.
|
95
|
+
#
|
96
|
+
# Returns The String line the Reader should make available to the next
|
97
|
+
# invocation of Reader#read_line or nil if the Reader should drop the line,
|
98
|
+
# advance to the next line and process it.
|
99
|
+
def process_line line
|
100
|
+
@look_ahead += 1 if @process_lines
|
101
|
+
line
|
102
|
+
end
|
103
|
+
|
104
|
+
# Public: Check whether there are any lines left to read.
|
105
|
+
#
|
106
|
+
# If a previous call to this method resulted in a value of false,
|
107
|
+
# immediately returned the cached value. Otherwise, delegate to
|
108
|
+
# peek_line to determine if there is a next line available.
|
109
|
+
#
|
110
|
+
# Returns True if there are more lines, False if there are not.
|
111
|
+
def has_more_lines?
|
112
|
+
!(@eof || (@eof = peek_line.nil?))
|
113
|
+
end
|
114
|
+
|
115
|
+
# Public: Peek at the next line and check if it's empty (i.e., whitespace only)
|
116
|
+
#
|
117
|
+
# This method Does not consume the line from the stack.
|
118
|
+
#
|
119
|
+
# Returns True if the there are no more lines or if the next line is empty
|
120
|
+
def next_line_empty?
|
121
|
+
(line = peek_line).nil? || line.chomp.empty?
|
122
|
+
end
|
123
|
+
|
124
|
+
# Public: Peek at the next line of source data. Processes the line, if not
|
125
|
+
# already marked as processed, but does not consume it.
|
126
|
+
#
|
127
|
+
# This method will probe the reader for more lines. If there is a next line
|
128
|
+
# that has not previously been visited, the line is passed to the
|
129
|
+
# Reader#preprocess_line method to be initialized. This call gives
|
130
|
+
# sub-classess the opportunity to do preprocessing. If the return value of
|
131
|
+
# the Reader#process_line is nil, the data is assumed to be changed and
|
132
|
+
# Reader#peek_line is invoked again to perform further processing.
|
133
|
+
#
|
134
|
+
# direct - A Boolean flag to bypasses the check for more lines and immediately
|
135
|
+
# returns the first element of the internal @lines Array. (default: false)
|
136
|
+
#
|
137
|
+
# Returns the next line of the source data as a String if there are lines remaining.
|
138
|
+
# Returns nil if there is no more data.
|
139
|
+
def peek_line direct = false
|
140
|
+
if direct || @look_ahead > 0
|
141
|
+
@unescape_next_line ? @lines.first[1..-1] : @lines.first
|
142
|
+
elsif @eof || @lines.empty?
|
143
|
+
@eof = true
|
144
|
+
@look_ahead = 0
|
145
|
+
nil
|
41
146
|
else
|
42
|
-
|
43
|
-
@
|
147
|
+
# FIXME the problem with this approach is that we aren't
|
148
|
+
# retaining the modified line (hence the @unescape_next_line tweak)
|
149
|
+
# perhaps we need a stack of proxy lines
|
150
|
+
if (line = process_line @lines.first).nil?
|
151
|
+
peek_line
|
152
|
+
else
|
153
|
+
line
|
154
|
+
end
|
44
155
|
end
|
156
|
+
end
|
45
157
|
|
46
|
-
|
158
|
+
# Public: Peek at the next multiple lines of source data. Processes the lines, if not
|
159
|
+
# already marked as processed, but does not consume them.
|
160
|
+
#
|
161
|
+
# This method delegates to Reader#read_line to process and collect the line, then
|
162
|
+
# restores the lines to the stack before returning them. This allows the lines to
|
163
|
+
# be processed and marked as such so that subsequent reads will not need to process
|
164
|
+
# the lines again.
|
165
|
+
#
|
166
|
+
# num - The Integer number of lines to peek.
|
167
|
+
# direct - A Boolean indicating whether processing should be disabled when reading lines
|
168
|
+
#
|
169
|
+
# Returns A String Array of the next multiple lines of source data, or an empty Array
|
170
|
+
# if there are no more lines in this Reader.
|
171
|
+
def peek_lines num = 1, direct = true
|
172
|
+
old_look_ahead = @look_ahead
|
173
|
+
result = []
|
174
|
+
(1..num).each do
|
175
|
+
if (line = read_line direct)
|
176
|
+
result << line
|
177
|
+
else
|
178
|
+
break
|
179
|
+
end
|
180
|
+
end
|
47
181
|
|
48
|
-
|
49
|
-
|
182
|
+
unless result.empty?
|
183
|
+
result.reverse_each {|line| unshift line }
|
184
|
+
@look_ahead = old_look_ahead if direct
|
185
|
+
end
|
50
186
|
|
51
|
-
|
52
|
-
@skipping = false
|
53
|
-
@eof = false
|
187
|
+
result
|
54
188
|
end
|
55
189
|
|
56
|
-
# Public: Get
|
57
|
-
|
58
|
-
|
190
|
+
# Public: Get the next line of source data. Consumes the line returned.
|
191
|
+
#
|
192
|
+
# direct - A Boolean flag to bypasses the check for more lines and immediately
|
193
|
+
# returns the first element of the internal @lines Array. (default: false)
|
194
|
+
#
|
195
|
+
# Returns the String of the next line of the source data if data is present.
|
196
|
+
# Returns nil if there is no more data.
|
197
|
+
def read_line direct = false
|
198
|
+
if direct || @look_ahead > 0 || has_more_lines?
|
199
|
+
shift
|
200
|
+
else
|
201
|
+
nil
|
202
|
+
end
|
59
203
|
end
|
60
204
|
|
61
|
-
# Public:
|
205
|
+
# Public: Get the remaining lines of source data.
|
62
206
|
#
|
63
|
-
#
|
64
|
-
#
|
207
|
+
# This method calls Reader#read_line repeatedly until all lines are consumed
|
208
|
+
# and returns the lines as a String Array. This method differs from
|
209
|
+
# Reader#lines in that it processes each line in turn, hence triggering
|
210
|
+
# any preprocessors implemented in sub-classes.
|
65
211
|
#
|
66
|
-
# Returns
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
preprocess_next_line.nil? ? false : !@lines.empty?
|
72
|
-
else
|
73
|
-
true
|
212
|
+
# Returns the lines read as a String Array
|
213
|
+
def read_lines
|
214
|
+
lines = []
|
215
|
+
while has_more_lines?
|
216
|
+
lines << read_line
|
74
217
|
end
|
218
|
+
lines
|
75
219
|
end
|
220
|
+
alias :readlines :read_lines
|
76
221
|
|
77
|
-
# Public:
|
222
|
+
# Public: Get the remaining lines of source data joined as a String.
|
78
223
|
#
|
79
|
-
#
|
80
|
-
# the next line is preprocessed before checking whether there are more lines.
|
224
|
+
# Delegates to Reader#read_lines, then joins the result.
|
81
225
|
#
|
82
|
-
# Returns
|
83
|
-
def
|
84
|
-
|
226
|
+
# Returns the lines read joined as a String
|
227
|
+
def read
|
228
|
+
read_lines.join
|
229
|
+
end
|
230
|
+
|
231
|
+
# Public: Advance to the next line by discarding the line at the front of the stack
|
232
|
+
#
|
233
|
+
# direct - A Boolean flag to bypasses the check for more lines and immediately
|
234
|
+
# returns the first element of the internal @lines Array. (default: true)
|
235
|
+
#
|
236
|
+
# returns a Boolean indicating whether there was a line to discard.
|
237
|
+
def advance direct = true
|
238
|
+
!(read_line direct).nil?
|
239
|
+
end
|
240
|
+
|
241
|
+
# Public: Push the String line onto the beginning of the Array of source data.
|
242
|
+
#
|
243
|
+
# Since this line was (assumed to be) previously retrieved through the
|
244
|
+
# reader, it is marked as seen.
|
245
|
+
#
|
246
|
+
# returns nil
|
247
|
+
def unshift_line line_to_restore
|
248
|
+
unshift line_to_restore
|
249
|
+
nil
|
250
|
+
end
|
251
|
+
alias :restore_line :unshift_line
|
252
|
+
|
253
|
+
# Public: Push an Array of lines onto the front of the Array of source data.
|
254
|
+
#
|
255
|
+
# Since these lines were (assumed to be) previously retrieved through the
|
256
|
+
# reader, they are marked as seen.
|
257
|
+
#
|
258
|
+
# Returns nil
|
259
|
+
def unshift_lines lines_to_restore
|
260
|
+
# QUESTION is it faster to use unshift(*lines_to_restore)?
|
261
|
+
lines_to_restore.reverse_each {|line| unshift line }
|
262
|
+
nil
|
85
263
|
end
|
264
|
+
alias :restore_lines :unshift_lines
|
86
265
|
|
87
|
-
#
|
266
|
+
# Public: Replace the current line with the specified line.
|
267
|
+
#
|
268
|
+
# Calls Reader#advance to consume the current line, then calls
|
269
|
+
# Reader#unshift to push the replacement onto the top of the
|
270
|
+
# line stack.
|
271
|
+
#
|
272
|
+
# replacement - The String line to put in place of the line at the cursor.
|
273
|
+
#
|
274
|
+
# Returns nothing.
|
275
|
+
def replace_line replacement
|
276
|
+
advance
|
277
|
+
unshift replacement
|
278
|
+
nil
|
279
|
+
end
|
280
|
+
|
281
|
+
# Public: Strip off leading blank lines in the Array of lines.
|
88
282
|
#
|
89
283
|
# Examples
|
90
284
|
#
|
@@ -99,76 +293,66 @@ class Reader
|
|
99
293
|
#
|
100
294
|
# Returns an Integer of the number of lines skipped
|
101
295
|
def skip_blank_lines
|
102
|
-
|
296
|
+
return 0 if eof?
|
297
|
+
|
298
|
+
num_skipped = 0
|
103
299
|
# optimized code for shortest execution path
|
104
|
-
while
|
300
|
+
while (next_line = peek_line)
|
105
301
|
if next_line.chomp.empty?
|
106
|
-
|
302
|
+
advance
|
303
|
+
num_skipped += 1
|
107
304
|
else
|
108
|
-
|
109
|
-
break
|
305
|
+
return num_skipped
|
110
306
|
end
|
111
|
-
end
|
307
|
+
end
|
112
308
|
|
113
|
-
|
309
|
+
num_skipped
|
114
310
|
end
|
115
311
|
|
116
|
-
# Public:
|
117
|
-
#
|
118
|
-
# Returns the Array of lines that were consumed
|
312
|
+
# Public: Skip consecutive lines containing line comments and return them.
|
119
313
|
#
|
120
314
|
# Examples
|
121
315
|
# @lines
|
122
|
-
# => ["// foo\n", "
|
316
|
+
# => ["// foo\n", "bar\n"]
|
123
317
|
#
|
124
|
-
# comment_lines =
|
125
|
-
# => ["// foo\n"
|
318
|
+
# comment_lines = skip_comment_lines
|
319
|
+
# => ["// foo\n"]
|
126
320
|
#
|
127
321
|
# @lines
|
128
|
-
# => ["
|
129
|
-
|
322
|
+
# => ["bar\n"]
|
323
|
+
#
|
324
|
+
# Returns the Array of lines that were skipped
|
325
|
+
def skip_comment_lines opts = {}
|
326
|
+
return [] if eof?
|
327
|
+
|
130
328
|
comment_lines = []
|
131
|
-
|
132
|
-
while
|
133
|
-
if
|
134
|
-
comment_lines <<
|
329
|
+
include_blank_lines = opts[:include_blank_lines]
|
330
|
+
while (next_line = peek_line)
|
331
|
+
if include_blank_lines && next_line.chomp.empty?
|
332
|
+
comment_lines << read_line
|
135
333
|
elsif (commentish = next_line.start_with?('//')) && (match = next_line.match(REGEXP[:comment_blk]))
|
136
|
-
comment_lines <<
|
137
|
-
comment_lines.push(*(
|
334
|
+
comment_lines << read_line
|
335
|
+
comment_lines.push(*(read_lines_until(:terminator => match[0], :read_last_line => true, :skip_processing => true)))
|
138
336
|
elsif commentish && next_line.match(REGEXP[:comment])
|
139
|
-
comment_lines <<
|
337
|
+
comment_lines << read_line
|
140
338
|
else
|
141
|
-
# throw it back
|
142
|
-
unshift_line next_line
|
143
339
|
break
|
144
340
|
end
|
145
341
|
end
|
146
342
|
|
147
343
|
comment_lines
|
148
344
|
end
|
149
|
-
alias :skip_comment_lines :consume_comments
|
150
345
|
|
151
|
-
# Public:
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
# Examples
|
156
|
-
# @lines
|
157
|
-
# => ["// foo\n", "bar\n"]
|
158
|
-
#
|
159
|
-
# comment_lines = consume_comments
|
160
|
-
# => ["// foo\n"]
|
161
|
-
#
|
162
|
-
# @lines
|
163
|
-
# => ["bar\n"]
|
164
|
-
def consume_line_comments
|
346
|
+
# Public: Skip consecutive lines that are line comments and return them.
|
347
|
+
def skip_line_comments
|
348
|
+
return [] if eof?
|
349
|
+
|
165
350
|
comment_lines = []
|
166
351
|
# optimized code for shortest execution path
|
167
|
-
while
|
352
|
+
while (next_line = peek_line)
|
168
353
|
if next_line.match(REGEXP[:comment])
|
169
|
-
comment_lines <<
|
354
|
+
comment_lines << read_line
|
170
355
|
else
|
171
|
-
unshift_line next_line
|
172
356
|
break
|
173
357
|
end
|
174
358
|
end
|
@@ -176,122 +360,302 @@ class Reader
|
|
176
360
|
comment_lines
|
177
361
|
end
|
178
362
|
|
179
|
-
# Public:
|
363
|
+
# Public: Advance to the end of the reader, consuming all remaining lines
|
180
364
|
#
|
181
|
-
#
|
182
|
-
|
365
|
+
# Returns nothing.
|
366
|
+
def terminate
|
367
|
+
@lineno += @lines.size
|
368
|
+
@lines.clear
|
369
|
+
@eof = true
|
370
|
+
@look_ahead = 0
|
371
|
+
nil
|
372
|
+
end
|
373
|
+
|
374
|
+
# Public: Check whether this reader is empty (contains no lines)
|
183
375
|
#
|
184
|
-
# Returns
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
376
|
+
# Returns true if there are no more lines to peek, otherwise false.
|
377
|
+
def eof?
|
378
|
+
!has_more_lines?
|
379
|
+
end
|
380
|
+
alias :empty? :eof?
|
381
|
+
|
382
|
+
# Public: Return all the lines from `@lines` until we (1) run out them,
|
383
|
+
# (2) find a blank line with :break_on_blank_lines => true, or (3) find
|
384
|
+
# a line for which the given block evals to true.
|
385
|
+
#
|
386
|
+
# options - an optional Hash of processing options:
|
387
|
+
# * :break_on_blank_lines may be used to specify to break on
|
388
|
+
# blank lines
|
389
|
+
# * :skip_first_line may be used to tell the reader to advance
|
390
|
+
# beyond the first line before beginning the scan
|
391
|
+
# * :preserve_last_line may be used to specify that the String
|
392
|
+
# causing the method to stop processing lines should be
|
393
|
+
# pushed back onto the `lines` Array.
|
394
|
+
# * :read_last_line may be used to specify that the String
|
395
|
+
# causing the method to stop processing lines should be
|
396
|
+
# included in the lines being returned
|
397
|
+
#
|
398
|
+
# Returns the Array of lines forming the next segment.
|
399
|
+
#
|
400
|
+
# Examples
|
401
|
+
#
|
402
|
+
# reader = Reader.new ["First paragraph\n", "Second paragraph\n",
|
403
|
+
# "Open block\n", "\n", "Can have blank lines\n",
|
404
|
+
# "--\n", "\n", "In a different segment\n"]
|
405
|
+
#
|
406
|
+
# reader.read_lines_until
|
407
|
+
# => ["First paragraph\n", "Second paragraph\n", "Open block\n"]
|
408
|
+
def read_lines_until options = {}
|
409
|
+
result = []
|
410
|
+
advance if options[:skip_first_line]
|
411
|
+
if @process_lines && options[:skip_processing]
|
412
|
+
@process_lines = false
|
413
|
+
restore_process_lines = true
|
194
414
|
else
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
415
|
+
restore_process_lines = false
|
416
|
+
end
|
417
|
+
|
418
|
+
has_block = block_given?
|
419
|
+
if (terminator = options[:terminator])
|
420
|
+
break_on_blank_lines = false
|
421
|
+
break_on_list_continuation = false
|
422
|
+
chomp_last_line = options.fetch :chomp_last_line, false
|
423
|
+
else
|
424
|
+
break_on_blank_lines = options[:break_on_blank_lines]
|
425
|
+
break_on_list_continuation = options[:break_on_list_continuation]
|
426
|
+
chomp_last_line = break_on_blank_lines
|
427
|
+
end
|
428
|
+
skip_line_comments = options[:skip_line_comments]
|
429
|
+
line_read = false
|
430
|
+
line_restored = false
|
431
|
+
|
432
|
+
while (line = read_line)
|
433
|
+
finish = while true
|
434
|
+
break true if terminator && line.chomp == terminator
|
435
|
+
# QUESTION: can we get away with line.chomp.empty? here?
|
436
|
+
break true if break_on_blank_lines && line.chomp.empty?
|
437
|
+
if break_on_list_continuation && line_read && line.chomp == LIST_CONTINUATION
|
438
|
+
options[:preserve_last_line] = true
|
439
|
+
break true
|
440
|
+
end
|
441
|
+
break true if has_block && (yield line)
|
442
|
+
break false
|
443
|
+
end
|
444
|
+
|
445
|
+
if finish
|
446
|
+
if options[:read_last_line]
|
447
|
+
result << line
|
448
|
+
line_read = true
|
449
|
+
end
|
450
|
+
if options[:preserve_last_line]
|
451
|
+
restore_line line
|
452
|
+
line_restored = true
|
453
|
+
end
|
454
|
+
break
|
455
|
+
end
|
456
|
+
|
457
|
+
unless skip_line_comments && line.start_with?('//') && line.match(REGEXP[:comment])
|
458
|
+
result << line
|
459
|
+
line_read = true
|
202
460
|
end
|
203
461
|
end
|
462
|
+
|
463
|
+
if chomp_last_line && line_read
|
464
|
+
result << result.pop.chomp
|
465
|
+
end
|
466
|
+
|
467
|
+
if restore_process_lines
|
468
|
+
@process_lines = true
|
469
|
+
@look_ahead -= 1 if line_restored && terminator.nil?
|
470
|
+
end
|
471
|
+
result
|
204
472
|
end
|
205
473
|
|
206
|
-
#
|
474
|
+
# Internal: Shift the line off the stack and increment the lineno
|
475
|
+
def shift
|
476
|
+
@lineno += 1
|
477
|
+
@look_ahead -= 1 unless @look_ahead == 0
|
478
|
+
@lines.shift
|
479
|
+
end
|
480
|
+
|
481
|
+
# Internal: Restore the line to the stack and decrement the lineno
|
482
|
+
def unshift line
|
483
|
+
@lineno -= 1
|
484
|
+
@look_ahead += 1
|
485
|
+
@eof = false
|
486
|
+
@lines.unshift line
|
487
|
+
end
|
488
|
+
|
489
|
+
def cursor
|
490
|
+
Cursor.new @file, @dir, @path, @lineno
|
491
|
+
end
|
492
|
+
|
493
|
+
# Public: Get information about the last line read, including file name and line number.
|
207
494
|
#
|
208
|
-
#
|
495
|
+
# Returns A String summary of the last line read
|
496
|
+
def line_info
|
497
|
+
%(#{@path}: line #{@lineno})
|
498
|
+
end
|
499
|
+
alias :next_line_info :line_info
|
500
|
+
|
501
|
+
def prev_line_info
|
502
|
+
%(#{@path}: line #{@lineno - 1})
|
503
|
+
end
|
504
|
+
|
505
|
+
# Public: Get a copy of the remaining Array of String lines managed by this Reader
|
209
506
|
#
|
210
|
-
#
|
211
|
-
def
|
212
|
-
@
|
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
|
507
|
+
# Returns A copy of the String Array of lines remaining in this Reader
|
508
|
+
def lines
|
509
|
+
@lines.dup
|
221
510
|
end
|
222
511
|
|
223
|
-
# Public: Get the
|
512
|
+
# Public: Get a copy of the remaining lines managed by this Reader joined as a String
|
513
|
+
def string
|
514
|
+
@lines.join
|
515
|
+
end
|
516
|
+
|
517
|
+
# Public: Get the source lines for this Reader joined as a String
|
518
|
+
def source
|
519
|
+
@source_lines.join
|
520
|
+
end
|
521
|
+
|
522
|
+
# Public: Get a summary of this Reader.
|
224
523
|
#
|
225
|
-
# preprocess - A Boolean flag indicating whether to evaluate preprocessing
|
226
|
-
# directives (macros) before reading line (default: true)
|
227
524
|
#
|
228
|
-
# Returns
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
525
|
+
# Returns A string summary of this reader, which contains the path and line information
|
526
|
+
def to_s
|
527
|
+
line_info
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
# Public: Methods for retrieving lines from AsciiDoc source files, evaluating preprocessor
|
532
|
+
# directives as each line is read off the Array of lines.
|
533
|
+
class PreprocessorReader < Reader
|
534
|
+
attr_reader :include_stack
|
535
|
+
attr_reader :includes
|
536
|
+
|
537
|
+
# Public: Initialize the PreprocessorReader object
|
538
|
+
def initialize document, data = nil, cursor = nil
|
539
|
+
@document = document
|
540
|
+
super data, cursor
|
541
|
+
include_depth_default = document.attributes.fetch('max-include-depth', 64).to_i
|
542
|
+
include_depth_default = 0 if include_depth_default < 0
|
543
|
+
# track both absolute depth for comparing to size of include stack and relative depth for reporting
|
544
|
+
@maxdepth = {:abs => include_depth_default, :rel => include_depth_default}
|
545
|
+
@include_stack = []
|
546
|
+
@includes = (document.references[:includes] ||= [])
|
547
|
+
@skipping = false
|
548
|
+
@conditional_stack = []
|
549
|
+
@include_processors = nil
|
550
|
+
end
|
551
|
+
|
552
|
+
def prepare_lines data, opts = {}
|
553
|
+
if data.is_a?(String)
|
554
|
+
if ::Asciidoctor::FORCE_ENCODING
|
555
|
+
result = data.each_line.map {|line| "#{line.rstrip.force_encoding ::Encoding::UTF_8}#{::Asciidoctor::EOL}" }
|
556
|
+
else
|
557
|
+
result = data.each_line.map {|line| "#{line.rstrip}#{::Asciidoctor::EOL}" }
|
558
|
+
end
|
237
559
|
else
|
238
|
-
|
560
|
+
if ::Asciidoctor::FORCE_ENCODING
|
561
|
+
result = data.map {|line| "#{line.rstrip.force_encoding ::Encoding::UTF_8}#{::Asciidoctor::EOL}" }
|
562
|
+
else
|
563
|
+
result = data.map {|line| "#{line.rstrip}#{::Asciidoctor::EOL}" }
|
564
|
+
end
|
239
565
|
end
|
240
|
-
end
|
241
566
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
567
|
+
# QUESTION should this work for AsciiDoc table cell content? Currently it does not.
|
568
|
+
unless @document.nil? || !(@document.attributes.has_key? 'skip-front-matter')
|
569
|
+
if (front_matter = skip_front_matter! result)
|
570
|
+
@document.attributes['front-matter'] = front_matter.join.chomp
|
251
571
|
end
|
252
|
-
break if idx >= @lines.size
|
253
|
-
# QUESTION do we need to dup?
|
254
|
-
lines << @lines[idx].dup
|
255
|
-
idx += 1
|
256
572
|
end
|
257
|
-
|
573
|
+
|
574
|
+
# QUESTION should we chomp last line? (with or without the condense flag?)
|
575
|
+
if opts.fetch(:condense, true)
|
576
|
+
result.shift && @lineno += 1 while !(first = result.first).nil? && first == ::Asciidoctor::EOL
|
577
|
+
result.pop while !(last = result.last).nil? && last == ::Asciidoctor::EOL
|
578
|
+
end
|
579
|
+
|
580
|
+
if (indent = opts.fetch(:indent, nil))
|
581
|
+
Lexer.reset_block_indent! result, indent.to_i
|
582
|
+
end
|
583
|
+
|
584
|
+
result
|
258
585
|
end
|
259
586
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
if next_line.start_with? '\\'
|
273
|
-
@next_line_preprocessed = true
|
587
|
+
def process_line line
|
588
|
+
return line unless @process_lines
|
589
|
+
|
590
|
+
if line.chomp.empty?
|
591
|
+
@look_ahead += 1
|
592
|
+
return ''
|
593
|
+
end
|
594
|
+
|
595
|
+
macroish = line.include?('::') && line.include?('[')
|
596
|
+
if macroish && line.include?('if') && (match = line.match(REGEXP[:ifdef_macro]))
|
597
|
+
# if escaped, mark as processed and return line unescaped
|
598
|
+
if line.start_with? '\\'
|
274
599
|
@unescape_next_line = true
|
275
|
-
|
600
|
+
@look_ahead += 1
|
601
|
+
line[1..-1]
|
276
602
|
else
|
277
|
-
preprocess_conditional_inclusion(*match.captures)
|
603
|
+
if preprocess_conditional_inclusion(*match.captures)
|
604
|
+
# move the pointer past the conditional line
|
605
|
+
advance
|
606
|
+
# treat next line as uncharted territory
|
607
|
+
nil
|
608
|
+
else
|
609
|
+
# the line was not a valid conditional line
|
610
|
+
# mark it as visited and return it
|
611
|
+
@look_ahead += 1
|
612
|
+
line
|
613
|
+
end
|
278
614
|
end
|
279
615
|
elsif @skipping
|
280
616
|
advance
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
if next_line.start_with? '\\'
|
286
|
-
@next_line_preprocessed = true
|
617
|
+
nil
|
618
|
+
elsif macroish && line.include?('include::') && (match = line.match(REGEXP[:include_macro]))
|
619
|
+
# if escaped, mark as processed and return line unescaped
|
620
|
+
if line.start_with? '\\'
|
287
621
|
@unescape_next_line = true
|
288
|
-
|
622
|
+
@look_ahead += 1
|
623
|
+
line[1..-1]
|
289
624
|
else
|
290
|
-
|
625
|
+
# QUESTION should we strip whitespace from raw attributes in Substituters#parse_attributes? (check perf)
|
626
|
+
if preprocess_include match[1], match[2].strip
|
627
|
+
# peek again since the content has changed
|
628
|
+
nil
|
629
|
+
else
|
630
|
+
# the line was not a valid include line and is unchanged
|
631
|
+
# mark it as visited and return it
|
632
|
+
@look_ahead += 1
|
633
|
+
line
|
634
|
+
end
|
291
635
|
end
|
292
636
|
else
|
293
|
-
|
294
|
-
|
637
|
+
# optimization to inline super
|
638
|
+
#super
|
639
|
+
@look_ahead += 1
|
640
|
+
line
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
# Public: Override the Reader#peek_line method to pop the include
|
645
|
+
# stack if the last line has been reached and there's at least
|
646
|
+
# one include on the stack.
|
647
|
+
#
|
648
|
+
# Returns the next line of the source data as a String if there are lines remaining
|
649
|
+
# in the current include context or a parent include context.
|
650
|
+
# Returns nil if there are no more lines remaining and the include stack is empty.
|
651
|
+
def peek_line direct = false
|
652
|
+
if (line = super)
|
653
|
+
line
|
654
|
+
elsif @include_stack.empty?
|
655
|
+
nil
|
656
|
+
else
|
657
|
+
pop_include
|
658
|
+
peek_line direct
|
295
659
|
end
|
296
660
|
end
|
297
661
|
|
@@ -314,37 +678,36 @@ class Reader
|
|
314
678
|
# Used for a single-line conditional block in the case of the ifdef or
|
315
679
|
# ifndef directives, and for the conditional expression for the ifeval directive.
|
316
680
|
#
|
317
|
-
# returns a Boolean indicating whether the cursor
|
318
|
-
|
319
|
-
def preprocess_conditional_inclusion(directive, target, delimiter, text)
|
681
|
+
# returns a Boolean indicating whether the cursor should be advanced
|
682
|
+
def preprocess_conditional_inclusion directive, target, delimiter, text
|
320
683
|
# must have a target before brackets if ifdef or ifndef
|
321
684
|
# must not have text between brackets if endif
|
322
685
|
# don't honor match if it doesn't meet this criteria
|
686
|
+
# QUESTION should we warn for these bogus declarations?
|
323
687
|
if ((directive == 'ifdef' || directive == 'ifndef') && target.empty?) ||
|
324
688
|
(directive == 'endif' && !text.nil?)
|
325
|
-
@next_line_preprocessed = true
|
326
689
|
return false
|
327
690
|
end
|
328
691
|
|
329
692
|
if directive == 'endif'
|
330
|
-
stack_size = @
|
693
|
+
stack_size = @conditional_stack.size
|
331
694
|
if stack_size > 0
|
332
|
-
pair = @
|
695
|
+
pair = @conditional_stack.last
|
333
696
|
if target.empty? || target == pair[:target]
|
334
|
-
@
|
335
|
-
@skipping = @
|
697
|
+
@conditional_stack.pop
|
698
|
+
@skipping = @conditional_stack.empty? ? false : @conditional_stack.last[:skipping]
|
336
699
|
else
|
337
|
-
|
700
|
+
warn "asciidoctor: ERROR: #{line_info}: mismatched macro: endif::#{target}[], expected endif::#{pair[:target]}[]"
|
338
701
|
end
|
339
702
|
else
|
340
|
-
|
703
|
+
warn "asciidoctor: ERROR: #{line_info}: unmatched macro: endif::#{target}[]"
|
341
704
|
end
|
342
|
-
|
343
|
-
return preprocess_next_line.nil? ? nil : true
|
705
|
+
return true
|
344
706
|
end
|
345
707
|
|
346
708
|
skip = false
|
347
|
-
|
709
|
+
unless @skipping
|
710
|
+
# QUESTION any way to wrap ifdef & ifndef logic up together?
|
348
711
|
case directive
|
349
712
|
when 'ifdef'
|
350
713
|
case delimiter
|
@@ -374,32 +737,35 @@ class Reader
|
|
374
737
|
# the text in brackets must match an expression
|
375
738
|
# don't honor match if it doesn't meet this criteria
|
376
739
|
if !target.empty? || !(expr_match = text.strip.match(REGEXP[:eval_expr]))
|
377
|
-
@next_line_preprocessed = true
|
378
740
|
return false
|
379
741
|
end
|
380
742
|
|
381
|
-
lhs = resolve_expr_val
|
743
|
+
lhs = resolve_expr_val expr_match[1]
|
744
|
+
# regex enforces a restrict set of math-related operations
|
382
745
|
op = expr_match[2]
|
383
|
-
rhs = resolve_expr_val
|
746
|
+
rhs = resolve_expr_val expr_match[3]
|
384
747
|
|
385
|
-
skip = !lhs.send
|
748
|
+
skip = !(lhs.send op.to_sym, rhs)
|
386
749
|
end
|
387
750
|
end
|
388
|
-
|
389
|
-
# single line conditional inclusion
|
390
|
-
if directive != 'ifeval' && !text.nil?
|
391
|
-
if !@skipping && !skip
|
392
|
-
unshift_line "#{text.rstrip}\n"
|
393
|
-
return true
|
394
|
-
end
|
751
|
+
|
395
752
|
# conditional inclusion block
|
753
|
+
if directive == 'ifeval' || text.nil?
|
754
|
+
@skipping = true if skip
|
755
|
+
@conditional_stack << {:target => target, :skip => skip, :skipping => @skipping}
|
756
|
+
# single line conditional inclusion
|
396
757
|
else
|
397
|
-
|
398
|
-
|
758
|
+
unless @skipping || skip
|
759
|
+
# FIXME slight hack to skip past conditional line
|
760
|
+
# but keep our synthetic line marked as processed
|
761
|
+
conditional_line = peek_line true
|
762
|
+
replace_line "#{text.rstrip}#{::Asciidoctor::EOL}"
|
763
|
+
unshift conditional_line
|
764
|
+
return true
|
399
765
|
end
|
400
|
-
@conditionals_stack << {:target => target, :skip => skip, :skipping => @skipping}
|
401
766
|
end
|
402
|
-
|
767
|
+
|
768
|
+
true
|
403
769
|
end
|
404
770
|
|
405
771
|
# Internal: Preprocess the directive (macro) to include the target document.
|
@@ -410,13 +776,12 @@ class Reader
|
|
410
776
|
# If SafeMode is SECURE or greater, the directive is ignore and the include
|
411
777
|
# directive line is emitted verbatim.
|
412
778
|
#
|
413
|
-
# Otherwise, if an include
|
414
|
-
#
|
415
|
-
# lines in return.
|
779
|
+
# Otherwise, if an include processor is specified pass the target and
|
780
|
+
# attributes to that processor and expect an Array of String lines in return.
|
416
781
|
#
|
417
|
-
# Otherwise, if the
|
418
|
-
# target path and read the lines onto the beginning
|
419
|
-
# data.
|
782
|
+
# Otherwise, if the max depth is greater than 0, and is not exceeded by the
|
783
|
+
# stack size, normalize the target path and read the lines onto the beginning
|
784
|
+
# of the Array of source data.
|
420
785
|
#
|
421
786
|
# If none of the above apply, emit the include directive line verbatim.
|
422
787
|
#
|
@@ -424,39 +789,68 @@ class Reader
|
|
424
789
|
# target slot of the include::[] macro
|
425
790
|
#
|
426
791
|
# returns a Boolean indicating whether the line under the cursor has changed.
|
427
|
-
def preprocess_include
|
428
|
-
target = @document.sub_attributes target
|
792
|
+
def preprocess_include target, raw_attributes
|
793
|
+
target = @document.sub_attributes target, :attribute_missing => 'drop-line'
|
429
794
|
if target.empty?
|
795
|
+
if @document.attributes.fetch('attribute-missing', COMPLIANCE[:attribute_missing]) == 'skip'
|
796
|
+
false
|
797
|
+
else
|
798
|
+
advance
|
799
|
+
true
|
800
|
+
end
|
801
|
+
# assume that if an include processor is given, the developer wants
|
802
|
+
# to handle when and how to process the include
|
803
|
+
elsif include_processors? &&
|
804
|
+
(processor = @include_processors.find {|candidate| candidate.handles? target })
|
430
805
|
advance
|
431
|
-
|
432
|
-
|
806
|
+
# QUESTION should we use @document.parse_attribues?
|
807
|
+
processor.process self, target, AttributeList.new(raw_attributes).parse
|
808
|
+
true
|
433
809
|
# if running in SafeMode::SECURE or greater, don't process this directive
|
434
810
|
# however, be friendly and at least make it a link to the source document
|
435
811
|
elsif @document.safe >= SafeMode::SECURE
|
436
|
-
|
437
|
-
|
812
|
+
replace_line "link:#{target}[]#{::Asciidoctor::EOL}"
|
813
|
+
# TODO make creating the output target a helper method
|
814
|
+
#output_target = %(#{File.join(File.dirname(target), File.basename(target, File.extname(target)))}#{@document.attributes['outfilesuffix']})
|
815
|
+
#unshift "link:#{output_target}[]#{::Asciidoctor::EOL}"
|
816
|
+
true
|
817
|
+
elsif (abs_maxdepth = @maxdepth[:abs]) > 0 && @include_stack.size >= abs_maxdepth
|
818
|
+
warn %(asciidoctor: ERROR: #{line_info}: maximum include depth of #{@maxdepth[:rel]} exceeded)
|
438
819
|
false
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
820
|
+
elsif abs_maxdepth > 0
|
821
|
+
if target.include?(':') && target.match(REGEXP[:uri_sniff])
|
822
|
+
unless @document.attributes.has_key? 'allow-uri-read'
|
823
|
+
replace_line "link:#{target}[]#{::Asciidoctor::EOL}"
|
824
|
+
return true
|
825
|
+
end
|
826
|
+
|
827
|
+
target_type = :uri
|
828
|
+
include_file = path = target
|
829
|
+
if @document.attributes.has_key? 'cache-uri'
|
830
|
+
# caching requires the open-uri-cached gem to be installed
|
831
|
+
# processing will be automatically aborted if these libraries can't be opened
|
832
|
+
Helpers.require_library 'open-uri/cached', 'open-uri-cached'
|
833
|
+
else
|
834
|
+
Helpers.require_library 'open-uri'
|
835
|
+
end
|
836
|
+
else
|
837
|
+
target_type = :file
|
838
|
+
# include file is resolved relative to dir of current include, or base_dir if within original docfile
|
839
|
+
include_file = @document.normalize_system_path(target, @dir, nil, :target_name => 'include file')
|
840
|
+
if !File.file?(include_file)
|
841
|
+
warn "asciidoctor: WARNING: #{line_info}: include file not found: #{include_file}"
|
842
|
+
advance
|
843
|
+
return true
|
844
|
+
end
|
845
|
+
#path = @document.relative_path include_file
|
846
|
+
path = PathResolver.new.relative_path include_file, @document.base_dir
|
454
847
|
end
|
455
848
|
|
456
849
|
inc_lines = nil
|
457
850
|
tags = nil
|
458
851
|
attributes = {}
|
459
852
|
if !raw_attributes.empty?
|
853
|
+
# QUESTION should we use @document.parse_attribues?
|
460
854
|
attributes = AttributeList.new(raw_attributes).parse
|
461
855
|
if attributes.has_key? 'lines'
|
462
856
|
inc_lines = []
|
@@ -474,6 +868,8 @@ class Reader
|
|
474
868
|
end
|
475
869
|
end
|
476
870
|
inc_lines = inc_lines.sort.uniq
|
871
|
+
elsif attributes.has_key? 'tag'
|
872
|
+
tags = [attributes['tag']]
|
477
873
|
elsif attributes.has_key? 'tags'
|
478
874
|
tags = attributes['tags'].split(REGEXP[:ssv_or_csv_delim]).uniq
|
479
875
|
end
|
@@ -481,213 +877,227 @@ class Reader
|
|
481
877
|
if !inc_lines.nil?
|
482
878
|
if !inc_lines.empty?
|
483
879
|
selected = []
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
880
|
+
inc_line_offset = 0
|
881
|
+
inc_lineno = 0
|
882
|
+
begin
|
883
|
+
open(include_file) do |f|
|
884
|
+
f.each_line do |l|
|
885
|
+
inc_lineno += 1
|
886
|
+
take = inc_lines.first
|
887
|
+
if take.is_a?(Float) && take.infinite?
|
888
|
+
selected.push l
|
889
|
+
inc_line_offset = inc_lineno if inc_line_offset == 0
|
890
|
+
else
|
891
|
+
if f.lineno == take
|
892
|
+
selected.push l
|
893
|
+
inc_line_offset = inc_lineno if inc_line_offset == 0
|
894
|
+
inc_lines.shift
|
895
|
+
end
|
896
|
+
break if inc_lines.empty?
|
897
|
+
end
|
493
898
|
end
|
494
|
-
break if inc_lines.empty?
|
495
899
|
end
|
900
|
+
rescue
|
901
|
+
warn "asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{include_file}"
|
902
|
+
advance
|
903
|
+
return true
|
496
904
|
end
|
497
|
-
|
905
|
+
advance
|
906
|
+
# FIXME not accounting for skipped lines in reader line numbering
|
907
|
+
push_include selected, include_file, path, inc_line_offset, attributes
|
498
908
|
end
|
499
909
|
elsif !tags.nil?
|
500
910
|
if !tags.empty?
|
501
911
|
selected = []
|
912
|
+
inc_line_offset = 0
|
913
|
+
inc_lineno = 0
|
502
914
|
active_tag = nil
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
915
|
+
begin
|
916
|
+
open(include_file) do |f|
|
917
|
+
f.each_line do |l|
|
918
|
+
inc_lineno += 1
|
919
|
+
# must force encoding here since we're performing String operations on line
|
920
|
+
l.force_encoding(::Encoding::UTF_8) if ::Asciidoctor::FORCE_ENCODING
|
921
|
+
if !active_tag.nil?
|
922
|
+
if l.include?("end::#{active_tag}[]")
|
923
|
+
active_tag = nil
|
924
|
+
else
|
925
|
+
selected.push l
|
926
|
+
inc_line_offset = inc_lineno if inc_line_offset == 0
|
927
|
+
end
|
928
|
+
else
|
929
|
+
tags.each do |tag|
|
930
|
+
if l.include?("tag::#{tag}[]")
|
931
|
+
active_tag = tag
|
932
|
+
break
|
933
|
+
end
|
934
|
+
end
|
517
935
|
end
|
518
936
|
end
|
519
937
|
end
|
938
|
+
rescue
|
939
|
+
warn "asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{include_file}"
|
940
|
+
advance
|
941
|
+
return true
|
520
942
|
end
|
521
|
-
|
522
|
-
|
943
|
+
advance
|
944
|
+
# FIXME not accounting for skipped lines in reader line numbering
|
945
|
+
push_include selected, include_file, path, inc_line_offset, attributes
|
523
946
|
end
|
524
947
|
else
|
525
|
-
|
948
|
+
begin
|
949
|
+
advance
|
950
|
+
push_include open(include_file) {|f| f.read }, include_file, path, 1, attributes
|
951
|
+
rescue
|
952
|
+
warn "asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{include_file}"
|
953
|
+
advance
|
954
|
+
return true
|
955
|
+
end
|
526
956
|
end
|
527
957
|
true
|
528
958
|
else
|
529
|
-
@next_line_preprocessed = true
|
530
959
|
false
|
531
960
|
end
|
532
961
|
end
|
533
962
|
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
@
|
543
|
-
|
544
|
-
|
545
|
-
|
963
|
+
def push_include data, file = nil, path = nil, lineno = 1, attributes = {}
|
964
|
+
@include_stack << [@lines, @file, @dir, @path, @lineno, @maxdepth, @process_lines]
|
965
|
+
@includes << Helpers.rootname(path)
|
966
|
+
@file = file
|
967
|
+
@dir = File.dirname file
|
968
|
+
@path = path
|
969
|
+
@lineno = lineno
|
970
|
+
# NOTE only process lines in AsciiDoc files
|
971
|
+
@process_lines = ASCIIDOC_EXTENSIONS[File.extname(@file)]
|
972
|
+
if attributes.has_key? 'depth'
|
973
|
+
depth = attributes['depth'].to_i
|
974
|
+
depth = 1 if depth <= 0
|
975
|
+
@maxdepth = {:abs => (@include_stack.size - 1) + depth, :rel => depth}
|
976
|
+
end
|
977
|
+
# effectively fill the buffer
|
978
|
+
@lines = prepare_lines data, :condense => false, :indent => attributes['indent']
|
979
|
+
# FIXME kind of a hack
|
980
|
+
#Document::AttributeEntry.new('infile', @file).save_to_next_block @document
|
981
|
+
#Document::AttributeEntry.new('indir', File.dirname(@file)).save_to_next_block @document
|
982
|
+
if @lines.empty?
|
983
|
+
pop_include
|
984
|
+
else
|
985
|
+
@eof = false
|
986
|
+
@look_ahead = 0
|
987
|
+
end
|
546
988
|
end
|
547
989
|
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
@lines.
|
555
|
-
|
556
|
-
@next_line_preprocessed = true
|
557
|
-
@eof = false
|
558
|
-
@lineno -= size
|
990
|
+
def pop_include
|
991
|
+
if @include_stack.size > 0
|
992
|
+
@lines, @file, @dir, @path, @lineno, @maxdepth, @process_lines = @include_stack.pop
|
993
|
+
# FIXME kind of a hack
|
994
|
+
#Document::AttributeEntry.new('infile', @file).save_to_next_block @document
|
995
|
+
#Document::AttributeEntry.new('indir', File.dirname(@file)).save_to_next_block @document
|
996
|
+
@eof = @lines.empty?
|
997
|
+
@look_ahead = 0
|
559
998
|
end
|
560
|
-
nil
|
561
999
|
end
|
562
1000
|
|
563
|
-
|
564
|
-
|
565
|
-
# Delegates to chomp!
|
566
|
-
#
|
567
|
-
# Returns nil
|
568
|
-
def chomp_last!
|
569
|
-
@lines.last.chomp! unless @eof || (@eof = @lines.empty?)
|
570
|
-
nil
|
1001
|
+
def include_depth
|
1002
|
+
@include_stack.size
|
571
1003
|
end
|
572
1004
|
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
# * :skip_first_line may be used to tell the reader to advance
|
581
|
-
# beyond the first line before beginning the scan
|
582
|
-
# * :preserve_last_line may be used to specify that the String
|
583
|
-
# causing the method to stop processing lines should be
|
584
|
-
# pushed back onto the `lines` Array.
|
585
|
-
# * :grab_last_line may be used to specify that the String
|
586
|
-
# causing the method to stop processing lines should be
|
587
|
-
# included in the lines being returned
|
588
|
-
#
|
589
|
-
# Returns the Array of lines forming the next segment.
|
590
|
-
#
|
591
|
-
# Examples
|
592
|
-
#
|
593
|
-
# reader = Reader.new ["First paragraph\n", "Second paragraph\n",
|
594
|
-
# "Open block\n", "\n", "Can have blank lines\n",
|
595
|
-
# "--\n", "\n", "In a different segment\n"]
|
596
|
-
#
|
597
|
-
# reader.grab_lines_until
|
598
|
-
# => ["First paragraph\n", "Second paragraph\n", "Open block\n"]
|
599
|
-
def grab_lines_until(options = {}, &block)
|
600
|
-
buffer = []
|
1005
|
+
def exceeded_max_depth?
|
1006
|
+
if (abs_maxdepth = @maxdepth[:abs]) > 0 && @include_stack.size >= abs_maxdepth
|
1007
|
+
@maxdepth[:rel]
|
1008
|
+
else
|
1009
|
+
false
|
1010
|
+
end
|
1011
|
+
end
|
601
1012
|
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
chomp_last_line = options[:chomp_last_line] || false
|
1013
|
+
# TODO Document this override
|
1014
|
+
# also, we now have the field in the super class, so perhaps
|
1015
|
+
# just implement the logic there?
|
1016
|
+
def shift
|
1017
|
+
if @unescape_next_line
|
1018
|
+
@unescape_next_line = false
|
1019
|
+
super[1..-1]
|
610
1020
|
else
|
611
|
-
|
612
|
-
break_on_blank_lines = options[:break_on_blank_lines]
|
613
|
-
break_on_list_continuation = options[:break_on_list_continuation]
|
614
|
-
chomp_last_line = break_on_blank_lines
|
1021
|
+
super
|
615
1022
|
end
|
616
|
-
|
617
|
-
preprocess = options.fetch(:preprocess, true)
|
618
|
-
buffer_empty = true
|
619
|
-
while !(this_line = get_line(preprocess)).nil?
|
620
|
-
# effectively a no-args lamba, but much faster
|
621
|
-
finish = while true
|
622
|
-
break true if terminator && this_line.chomp == terminator
|
623
|
-
break true if break_on_blank_lines && this_line.strip.empty?
|
624
|
-
if break_on_list_continuation && !buffer_empty && this_line.chomp == LIST_CONTINUATION
|
625
|
-
options[:preserve_last_line] = true
|
626
|
-
break true
|
627
|
-
end
|
628
|
-
break true if block && yield(this_line)
|
629
|
-
break false
|
630
|
-
end
|
1023
|
+
end
|
631
1024
|
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
1025
|
+
# Private: Ignore front-matter, commonly used in static site generators
|
1026
|
+
def skip_front_matter! data, increment_linenos = true
|
1027
|
+
front_matter = nil
|
1028
|
+
if data.size > 0 && data.first.chomp == '---'
|
1029
|
+
original_data = data.dup
|
1030
|
+
front_matter = []
|
1031
|
+
data.shift
|
1032
|
+
@lineno += 1 if increment_linenos
|
1033
|
+
while !data.empty? && data.first.chomp != '---'
|
1034
|
+
front_matter.push data.shift
|
1035
|
+
@lineno += 1 if increment_linenos
|
640
1036
|
end
|
641
1037
|
|
642
|
-
|
643
|
-
|
644
|
-
|
1038
|
+
if data.empty?
|
1039
|
+
data.unshift(*original_data)
|
1040
|
+
@lineno = 0 if increment_linenos
|
1041
|
+
front_matter = nil
|
1042
|
+
else
|
1043
|
+
data.shift
|
1044
|
+
@lineno += 1 if increment_linenos
|
645
1045
|
end
|
646
1046
|
end
|
647
1047
|
|
648
|
-
|
649
|
-
buffer.last.chomp! if chomp_last_line && !buffer_empty
|
650
|
-
buffer
|
1048
|
+
front_matter
|
651
1049
|
end
|
652
1050
|
|
653
|
-
#
|
1051
|
+
# Private: Resolve the value of one side of the expression
|
654
1052
|
#
|
655
|
-
#
|
1053
|
+
# Examples
|
656
1054
|
#
|
657
|
-
#
|
1055
|
+
# expr = '"value"'
|
1056
|
+
# resolve_expr_val(expr)
|
1057
|
+
# # => "value"
|
658
1058
|
#
|
659
|
-
#
|
1059
|
+
# expr = '"value'
|
1060
|
+
# resolve_expr_val(expr)
|
1061
|
+
# # => "\"value"
|
660
1062
|
#
|
661
|
-
#
|
662
|
-
#
|
1063
|
+
# expr = '"{undefined}"'
|
1064
|
+
# resolve_expr_val(expr)
|
1065
|
+
# # => ""
|
663
1066
|
#
|
664
|
-
#
|
665
|
-
#
|
1067
|
+
# expr = '{undefined}'
|
1068
|
+
# resolve_expr_val(expr)
|
1069
|
+
# # => nil
|
666
1070
|
#
|
667
|
-
#
|
668
|
-
#
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
#
|
1071
|
+
# expr = '2'
|
1072
|
+
# resolve_expr_val(expr)
|
1073
|
+
# # => 2
|
1074
|
+
#
|
1075
|
+
# @document.attributes['name'] = 'value'
|
1076
|
+
# expr = '"{name}"'
|
1077
|
+
# resolve_expr_val(expr)
|
1078
|
+
# # => "value"
|
1079
|
+
#
|
1080
|
+
# Returns The value of the expression, coerced to the appropriate type
|
674
1081
|
def resolve_expr_val(str)
|
675
1082
|
val = str
|
676
1083
|
type = nil
|
677
1084
|
|
678
1085
|
if val.start_with?('"') && val.end_with?('"') ||
|
679
1086
|
val.start_with?('\'') && val.end_with?('\'')
|
680
|
-
type = :
|
681
|
-
val = val[1
|
1087
|
+
type = :string
|
1088
|
+
val = val[1...-1]
|
682
1089
|
end
|
683
1090
|
|
1091
|
+
# QUESTION should we substitute first?
|
684
1092
|
if val.include? '{'
|
685
1093
|
val = @document.sub_attributes val
|
686
1094
|
end
|
687
1095
|
|
688
|
-
|
1096
|
+
unless type == :string
|
689
1097
|
if val.empty?
|
690
1098
|
val = nil
|
1099
|
+
elsif val.strip.empty?
|
1100
|
+
val = ' '
|
691
1101
|
elsif val == 'true'
|
692
1102
|
val = true
|
693
1103
|
elsif val == 'false'
|
@@ -695,6 +1105,8 @@ class Reader
|
|
695
1105
|
elsif val.include?('.')
|
696
1106
|
val = val.to_f
|
697
1107
|
else
|
1108
|
+
# fallback to coercing to integer, since we
|
1109
|
+
# require string values to be explicitly quoted
|
698
1110
|
val = val.to_i
|
699
1111
|
end
|
700
1112
|
end
|
@@ -702,75 +1114,22 @@ class Reader
|
|
702
1114
|
val
|
703
1115
|
end
|
704
1116
|
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
# data - A String Array of input data to be normalized
|
715
|
-
#
|
716
|
-
# returns the processed lines
|
717
|
-
#-
|
718
|
-
# FIXME this shares too much in common w/ normalize_data; combine
|
719
|
-
# in a shared function
|
720
|
-
def normalize_include_data(data, indent = nil)
|
721
|
-
if ::Asciidoctor::FORCE_ENCODING
|
722
|
-
result = data.map {|line| "#{line.rstrip.force_encoding(::Encoding::UTF_8)}\n" }
|
1117
|
+
def include_processors?
|
1118
|
+
if @include_processors.nil?
|
1119
|
+
if @document.extensions? && @document.extensions.include_processors?
|
1120
|
+
@include_processors = @document.extensions.load_include_processors(@document)
|
1121
|
+
true
|
1122
|
+
else
|
1123
|
+
@include_processors = false
|
1124
|
+
false
|
1125
|
+
end
|
723
1126
|
else
|
724
|
-
|
1127
|
+
@include_processors != false
|
725
1128
|
end
|
726
|
-
|
727
|
-
unless indent.nil?
|
728
|
-
Lexer.reset_block_indent! result, indent.to_i
|
729
|
-
end
|
730
|
-
|
731
|
-
result
|
732
|
-
|
733
|
-
#data.shift while !data.first.nil? && data.first.chomp.empty?
|
734
|
-
#data.pop while !data.last.nil? && data.last.chomp.empty?
|
735
|
-
#data
|
736
1129
|
end
|
737
1130
|
|
738
|
-
|
739
|
-
|
740
|
-
# This method strips whitespace from the end of every line of
|
741
|
-
# the source data and appends a LF (i.e., Unix endline). This
|
742
|
-
# whitespace substitution is very important to how Asciidoctor
|
743
|
-
# works.
|
744
|
-
#
|
745
|
-
# Any leading or trailing blank lines are also removed.
|
746
|
-
#
|
747
|
-
# The normalized lines are assigned to the @lines instance variable.
|
748
|
-
#
|
749
|
-
# data - A String Array of input data to be normalized
|
750
|
-
#
|
751
|
-
# returns nothing
|
752
|
-
def normalize_data(data)
|
753
|
-
# normalize line ending to LF (purging occurrences of CRLF)
|
754
|
-
# this rstrip is *very* important to how Asciidoctor works
|
755
|
-
|
756
|
-
if ::Asciidoctor::FORCE_ENCODING
|
757
|
-
@lines = data.map {|line| "#{line.rstrip.force_encoding(::Encoding::UTF_8)}\n" }
|
758
|
-
else
|
759
|
-
@lines = data.map {|line| "#{line.rstrip}\n" }
|
760
|
-
end
|
761
|
-
|
762
|
-
@lines.shift && @lineno += 1 while !@lines.first.nil? && @lines.first.chomp.empty?
|
763
|
-
@lines.pop while !@lines.last.nil? && @lines.last.chomp.empty?
|
764
|
-
|
765
|
-
# Process bibliography references, so they're available when text
|
766
|
-
# before the reference is being rendered.
|
767
|
-
# FIXME reenable whereever it belongs
|
768
|
-
#@lines.each do |line|
|
769
|
-
# if biblio = line.match(REGEXP[:biblio])
|
770
|
-
# @document.register(:ids, biblio[1])
|
771
|
-
# end
|
772
|
-
#end
|
773
|
-
nil
|
1131
|
+
def to_s
|
1132
|
+
%(#{self.class.name} [path: #{@path}, line #: #{@lineno}, include depth: #{@include_stack.size}, include stack: [#{@include_stack.map {|inc| inc.to_s}.join ', '}]])
|
774
1133
|
end
|
775
1134
|
end
|
776
1135
|
end
|