asciidoctor 0.1.1 → 0.1.2
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 +7 -0
- data/Gemfile +1 -1
- data/LICENSE +2 -2
- data/README.adoc +461 -0
- data/asciidoctor.gemspec +27 -16
- data/compat/asciidoc.conf +139 -0
- data/lib/asciidoctor.rb +212 -69
- data/lib/asciidoctor/abstract_block.rb +41 -0
- data/lib/asciidoctor/abstract_node.rb +128 -81
- data/lib/asciidoctor/attribute_list.rb +5 -2
- data/lib/asciidoctor/backends/base_template.rb +16 -4
- data/lib/asciidoctor/backends/docbook45.rb +112 -42
- data/lib/asciidoctor/backends/html5.rb +206 -90
- data/lib/asciidoctor/block.rb +5 -5
- data/lib/asciidoctor/cli/invoker.rb +38 -34
- data/lib/asciidoctor/cli/options.rb +3 -3
- data/lib/asciidoctor/document.rb +115 -13
- data/lib/asciidoctor/helpers.rb +16 -0
- data/lib/asciidoctor/lexer.rb +486 -359
- data/lib/asciidoctor/path_resolver.rb +360 -0
- data/lib/asciidoctor/reader.rb +122 -23
- data/lib/asciidoctor/renderer.rb +1 -33
- data/lib/asciidoctor/section.rb +1 -1
- data/lib/asciidoctor/substituters.rb +103 -19
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +6 -6
- data/man/asciidoctor.ad +5 -3
- data/stylesheets/asciidoctor.css +274 -0
- data/test/attributes_test.rb +133 -10
- data/test/blocks_test.rb +302 -17
- data/test/document_test.rb +269 -6
- data/test/fixtures/basic-docinfo.html +1 -0
- data/test/fixtures/basic-docinfo.xml +4 -0
- data/test/fixtures/basic.asciidoc +4 -0
- data/test/fixtures/docinfo.html +1 -0
- data/test/fixtures/docinfo.xml +2 -0
- data/test/fixtures/include-file.asciidoc +22 -1
- data/test/fixtures/stylesheets/custom.css +3 -0
- data/test/invoker_test.rb +38 -6
- data/test/lexer_test.rb +64 -21
- data/test/links_test.rb +4 -0
- data/test/lists_test.rb +251 -12
- data/test/paragraphs_test.rb +225 -30
- data/test/paths_test.rb +174 -0
- data/test/reader_test.rb +89 -2
- data/test/sections_test.rb +518 -16
- data/test/substitutions_test.rb +121 -10
- data/test/tables_test.rb +53 -13
- data/test/test_helper.rb +2 -2
- data/test/text_test.rb +5 -5
- metadata +46 -50
- data/README.asciidoc +0 -296
- data/lib/asciidoctor/errors.rb +0 -5
@@ -0,0 +1,360 @@
|
|
1
|
+
module Asciidoctor
|
2
|
+
# Public: Handles all operations for resolving, cleaning and joining paths.
|
3
|
+
# This class includes operations for handling both web paths (request URIs) and
|
4
|
+
# system paths.
|
5
|
+
#
|
6
|
+
# The main emphasis of the class is on creating clean and secure paths. Clean
|
7
|
+
# paths are void of duplicate parent and current directory references in the
|
8
|
+
# path name. Secure paths are paths which are restricted from accessing
|
9
|
+
# directories outside of a jail root, if specified.
|
10
|
+
#
|
11
|
+
# Since joining two paths can result in an insecure path, this class also
|
12
|
+
# handles the task of joining a parent (start) and child (target) path.
|
13
|
+
#
|
14
|
+
# This class makes no use of path utilities from the Ruby libraries. Instead,
|
15
|
+
# it handles all aspects of path manipulation. The main benefit of
|
16
|
+
# internalizing these operations is that the class is able to handle both posix
|
17
|
+
# and windows paths independent of the operating system on which it runs. This
|
18
|
+
# makes the class both deterministic and easier to test.
|
19
|
+
#
|
20
|
+
# Examples
|
21
|
+
#
|
22
|
+
# resolver = PathResolver.new
|
23
|
+
#
|
24
|
+
# # Web Paths
|
25
|
+
#
|
26
|
+
# resolver.web_path('images')
|
27
|
+
# => 'images'
|
28
|
+
#
|
29
|
+
# resolver.web_path('./images')
|
30
|
+
# => './images'
|
31
|
+
#
|
32
|
+
# resolver.web_path('/images')
|
33
|
+
# => '/images'
|
34
|
+
#
|
35
|
+
# resolver.web_path('./images/../assets/images')
|
36
|
+
# => './assets/images'
|
37
|
+
#
|
38
|
+
# resolver.web_path('/../images')
|
39
|
+
# => '/images'
|
40
|
+
#
|
41
|
+
# resolver.web_path('images', 'assets')
|
42
|
+
# => 'assets/images'
|
43
|
+
#
|
44
|
+
# resolver.web_path('tiger.png', '../assets/images')
|
45
|
+
# => '../assets/images/tiger.png'
|
46
|
+
#
|
47
|
+
# # System Paths
|
48
|
+
#
|
49
|
+
# resolver.working_dir
|
50
|
+
# => '/path/to/docs'
|
51
|
+
#
|
52
|
+
# resolver.system_path('images')
|
53
|
+
# => '/path/to/docs/images'
|
54
|
+
#
|
55
|
+
# resolver.system_path('../images')
|
56
|
+
# => '/path/to/images'
|
57
|
+
#
|
58
|
+
# resolver.system_path('/etc/images')
|
59
|
+
# => '/etc/images'
|
60
|
+
#
|
61
|
+
# resolver.system_path('images', '/etc')
|
62
|
+
# => '/etc/images'
|
63
|
+
#
|
64
|
+
# resolver.system_path('', '/etc/images')
|
65
|
+
# => '/etc/images'
|
66
|
+
#
|
67
|
+
# resolver.system_path(nil, nil, '/path/to/docs')
|
68
|
+
# => '/path/to/docs'
|
69
|
+
#
|
70
|
+
# resolver.system_path('..', nil, '/path/to/docs')
|
71
|
+
# => '/path/to/docs'
|
72
|
+
#
|
73
|
+
# resolver.system_path('../../../css', nil, '/path/to/docs')
|
74
|
+
# => '/path/to/docs/css'
|
75
|
+
#
|
76
|
+
# resolver.system_path('../../../css', '../../..', '/path/to/docs')
|
77
|
+
# => '/path/to/docs/css'
|
78
|
+
#
|
79
|
+
# begin
|
80
|
+
# resolver.system_path('../../../css', '../../..', '/path/to/docs', :recover => false)
|
81
|
+
# rescue SecurityError => e
|
82
|
+
# puts e.message
|
83
|
+
# end
|
84
|
+
# => 'path ../../../../../../css refers to location outside jail: /path/to/docs (disallowed in safe mode)'
|
85
|
+
#
|
86
|
+
# resolver.system_path('/path/to/docs/images', nil, '/path/to/docs')
|
87
|
+
# => '/path/to/docs/images'
|
88
|
+
#
|
89
|
+
# begin
|
90
|
+
# resolver.system_path('images', '/etc', '/path/to/docs')
|
91
|
+
# rescue SecurityError => e
|
92
|
+
# puts e.message
|
93
|
+
# end
|
94
|
+
# => Start path /etc is outside of jail: /path/to/docs'
|
95
|
+
#
|
96
|
+
class PathResolver
|
97
|
+
DOT = '.'
|
98
|
+
DOT_DOT = '..'
|
99
|
+
SLASH = '/'
|
100
|
+
BACKSLASH = '\\'
|
101
|
+
PARTITION_RE = /\/+/
|
102
|
+
WIN_ROOT_RE = /^[[:alpha:]]:(?:\\|\/)/
|
103
|
+
|
104
|
+
attr_accessor :file_separator
|
105
|
+
attr_accessor :working_dir
|
106
|
+
|
107
|
+
# Public: Construct a new instance of PathResolver, optionally specifying the
|
108
|
+
# path separator (to override the system default) and the working directory
|
109
|
+
# (to override the present working directory). The working directory will be
|
110
|
+
# expanded to an absolute path inside the constructor.
|
111
|
+
#
|
112
|
+
# file_separator - the String file separator to use for path operations
|
113
|
+
# (optional, default: File::FILE_SEPARATOR)
|
114
|
+
# working_dir - the String working directory (optional, default: Dir.pwd)
|
115
|
+
#
|
116
|
+
def initialize(file_separator = nil, working_dir = nil)
|
117
|
+
@file_separator = file_separator.nil? ? File::SEPARATOR : file_separator
|
118
|
+
if working_dir.nil?
|
119
|
+
@working_dir = File.expand_path(Dir.pwd)
|
120
|
+
else
|
121
|
+
@working_dir = is_root?(working_dir) ? working_dir : File.expand_path(working_dir)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Public: Check if the specified path is an absolute root path
|
126
|
+
# This operation correctly handles both posix and windows paths.
|
127
|
+
#
|
128
|
+
# path - the String path to check
|
129
|
+
#
|
130
|
+
# returns a Boolean indicating whether the path is an absolute root path
|
131
|
+
def is_root?(path)
|
132
|
+
if @file_separator == BACKSLASH && path.match(WIN_ROOT_RE)
|
133
|
+
true
|
134
|
+
elsif path.start_with? SLASH
|
135
|
+
true
|
136
|
+
else
|
137
|
+
false
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Public: Determine if the path is an absolute (root) web path
|
142
|
+
#
|
143
|
+
# path - the String path to check
|
144
|
+
#
|
145
|
+
# returns a Boolean indicating whether the path is an absolute (root) web path
|
146
|
+
def is_web_root?(path)
|
147
|
+
path.start_with? SLASH
|
148
|
+
end
|
149
|
+
|
150
|
+
# Public: Normalize path by converting any backslashes to forward slashes
|
151
|
+
#
|
152
|
+
# path - the String path to normalize
|
153
|
+
#
|
154
|
+
# returns a String path with any backslashes replaced with forward slashes
|
155
|
+
def posixfy(path)
|
156
|
+
return '' if path.to_s.empty?
|
157
|
+
path.include?(BACKSLASH) ? path.tr(BACKSLASH, SLASH) : path
|
158
|
+
end
|
159
|
+
|
160
|
+
# Public: Expand the path by resolving any parent references (..)
|
161
|
+
# and cleaning self references (.).
|
162
|
+
#
|
163
|
+
# The result will be relative if the path is relative and
|
164
|
+
# absolute if the path is absolute. The file separator used
|
165
|
+
# in the expanded path is the one specified when the class
|
166
|
+
# was constructed.
|
167
|
+
#
|
168
|
+
# path - the String path to expand
|
169
|
+
#
|
170
|
+
# returns a String path with any parent or self references resolved.
|
171
|
+
def expand_path(path)
|
172
|
+
path_segments, path_root, _ = partition_path(path)
|
173
|
+
join_path(path_segments, path_root)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Public: Partition the path into path segments and remove any empty segments
|
177
|
+
# or segments that are self references (.). The path is split on either posix
|
178
|
+
# or windows file separators.
|
179
|
+
#
|
180
|
+
# path - the String path to partition
|
181
|
+
# web_path - a Boolean indicating whether the path should be handled
|
182
|
+
# as a web path (optional, default: false)
|
183
|
+
#
|
184
|
+
# returns a 3-item Array containing the Array of String path segments, the
|
185
|
+
# path root, if the path is absolute, and the posix version of the path.
|
186
|
+
def partition_path(path, web_path = false)
|
187
|
+
posix_path = posixfy path
|
188
|
+
is_root = web_path ? is_web_root?(posix_path) : is_root?(posix_path)
|
189
|
+
path_segments = posix_path.split(PARTITION_RE)
|
190
|
+
# capture relative root
|
191
|
+
root = path_segments.first == DOT ? DOT : nil
|
192
|
+
path_segments.delete(DOT)
|
193
|
+
# capture absolute root, preserving relative root if set
|
194
|
+
root = is_root ? path_segments.shift : root
|
195
|
+
|
196
|
+
[path_segments, root, posix_path]
|
197
|
+
end
|
198
|
+
|
199
|
+
# Public: Join the segments using the file separator specified in the
|
200
|
+
# constructor. Use the root, if specified, to construct an absolute path.
|
201
|
+
# Otherwise join the segments as a relative path.
|
202
|
+
#
|
203
|
+
# segments - a String Array of path segments
|
204
|
+
# root - a String path root (optional, default: nil)
|
205
|
+
#
|
206
|
+
# returns a String path formed by joining the segments and prepending
|
207
|
+
# the root, if specified
|
208
|
+
def join_path(segments, root = nil)
|
209
|
+
if root
|
210
|
+
"#{root}#{@file_separator}#{segments * @file_separator}"
|
211
|
+
else
|
212
|
+
segments * @file_separator
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Public: Resolve a system path from the target and start paths. If a jail
|
217
|
+
# path is specified, enforce that the resolved directory is contained within
|
218
|
+
# the jail path. If a jail path is not provided, the resolved path may be
|
219
|
+
# any location on the system. If the resolved path is absolute, use it as is.
|
220
|
+
# If the resolved path is relative, resolve it relative to the working_dir
|
221
|
+
# specified in the constructor.
|
222
|
+
#
|
223
|
+
# target - the String target path
|
224
|
+
# start - the String start (i.e., parent) path
|
225
|
+
# jail - the String jail path to confine the resolved path
|
226
|
+
# opts - an optional Hash of options to control processing (default: {}):
|
227
|
+
# * :recover is used to control whether the processor should auto-recover
|
228
|
+
# when an illegal path is encountered
|
229
|
+
# * :target_name is used in messages to refer to the path being resolved
|
230
|
+
#
|
231
|
+
# returns a String path that joins the target path with the start path with
|
232
|
+
# any parent references resolved and self references removed and enforces
|
233
|
+
# that the resolved path be contained within the jail, if provided
|
234
|
+
def system_path(target, start, jail = nil, opts = {})
|
235
|
+
recover = opts.fetch(:recover, true)
|
236
|
+
unless jail.nil?
|
237
|
+
unless is_root? jail
|
238
|
+
raise SecurityError, "Jail is not an absolute path: #{jail}"
|
239
|
+
end
|
240
|
+
jail = posixfy jail
|
241
|
+
end
|
242
|
+
|
243
|
+
if target.to_s.empty?
|
244
|
+
target_segments = []
|
245
|
+
else
|
246
|
+
target_segments, target_root, _ = partition_path(target)
|
247
|
+
end
|
248
|
+
|
249
|
+
if target_segments.empty?
|
250
|
+
if start.to_s.empty?
|
251
|
+
return jail.nil? ? @working_dir : jail
|
252
|
+
elsif is_root? start
|
253
|
+
if jail.nil?
|
254
|
+
return expand_path start
|
255
|
+
end
|
256
|
+
else
|
257
|
+
return system_path(start, jail, jail)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
if target_root && target_root != DOT
|
262
|
+
resolved_target = join_path target_segments, target_root
|
263
|
+
# if target is absolute and a sub-directory of jail, or
|
264
|
+
# a jail is not in place, let it slide
|
265
|
+
if jail.nil? || resolved_target.start_with?(jail)
|
266
|
+
return resolved_target
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
if start.to_s.empty?
|
271
|
+
start = jail.nil? ? @working_dir : jail
|
272
|
+
elsif is_root? start
|
273
|
+
start = posixfy start
|
274
|
+
else
|
275
|
+
start = system_path(start, jail, jail)
|
276
|
+
end
|
277
|
+
|
278
|
+
# both jail and start have been posixfied at this point
|
279
|
+
if jail == start
|
280
|
+
jail_segments, jail_root, _ = partition_path(jail)
|
281
|
+
start_segments = jail_segments.dup
|
282
|
+
elsif !jail.nil?
|
283
|
+
if !start.start_with?(jail)
|
284
|
+
raise SecurityError, "#{opts[:target_name] || 'Start path'} #{start} is outside of jail: #{jail} (disallowed in safe mode)"
|
285
|
+
end
|
286
|
+
|
287
|
+
start_segments, start_root, _ = partition_path(start)
|
288
|
+
jail_segments, jail_root, _ = partition_path(jail)
|
289
|
+
|
290
|
+
# Already checked for this condition
|
291
|
+
#if start_root != jail_root
|
292
|
+
# raise SecurityError, "Jail root #{jail_root} does not match root of #{opts[:target_name] || 'start path'}: #{start_root}"
|
293
|
+
#end
|
294
|
+
else
|
295
|
+
start_segments, start_root, _ = partition_path(start)
|
296
|
+
jail_root = start_root
|
297
|
+
end
|
298
|
+
|
299
|
+
resolved_segments = start_segments.dup
|
300
|
+
warned = false
|
301
|
+
target_segments.each do |segment|
|
302
|
+
if segment == DOT_DOT
|
303
|
+
if !jail.nil?
|
304
|
+
if resolved_segments.length > jail_segments.length
|
305
|
+
resolved_segments.pop
|
306
|
+
elsif !recover
|
307
|
+
raise SecurityError, "#{opts[:target_name] || 'path'} #{target} refers to location outside jail: #{jail} (disallowed in safe mode)"
|
308
|
+
elsif !warned
|
309
|
+
puts "asciidoctor: WARNING: #{opts[:target_name] || 'path'} has illegal reference to ancestor of jail, auto-recovering"
|
310
|
+
warned = true
|
311
|
+
end
|
312
|
+
else
|
313
|
+
resolved_segments.pop
|
314
|
+
end
|
315
|
+
else
|
316
|
+
resolved_segments.push segment
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
join_path resolved_segments, jail_root
|
321
|
+
end
|
322
|
+
|
323
|
+
# Public: Resolve a web path from the target and start paths.
|
324
|
+
# The main function of this operation is to resolve any parent
|
325
|
+
# references and remove any self references.
|
326
|
+
#
|
327
|
+
# target - the String target path
|
328
|
+
# start - the String start (i.e., parent) path
|
329
|
+
#
|
330
|
+
# returns a String path that joins the target path with the
|
331
|
+
# start path with any parent references resolved and self
|
332
|
+
# references removed
|
333
|
+
def web_path(target, start = nil)
|
334
|
+
target = posixfy(target)
|
335
|
+
start = posixfy(start)
|
336
|
+
|
337
|
+
unless is_web_root?(target) || start.empty?
|
338
|
+
target = "#{start}#{SLASH}#{target}"
|
339
|
+
end
|
340
|
+
|
341
|
+
target_segments, target_root, _ = partition_path(target, true)
|
342
|
+
resolved_segments = target_segments.inject([]) do |accum, segment|
|
343
|
+
if segment == DOT_DOT
|
344
|
+
if accum.empty?
|
345
|
+
accum.push segment unless target_root && target_root != DOT
|
346
|
+
elsif accum[-1] == DOT_DOT
|
347
|
+
accum.push segment
|
348
|
+
else
|
349
|
+
accum.pop
|
350
|
+
end
|
351
|
+
else
|
352
|
+
accum.push segment
|
353
|
+
end
|
354
|
+
accum
|
355
|
+
end
|
356
|
+
|
357
|
+
join_path resolved_segments, target_root
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
data/lib/asciidoctor/reader.rb
CHANGED
@@ -268,7 +268,7 @@ class Reader
|
|
268
268
|
def preprocess_next_line
|
269
269
|
# this return could be happening from a recursive call
|
270
270
|
return nil if @eof || (next_line = @lines.first).nil?
|
271
|
-
if next_line.include?('if') && (match = next_line.match(REGEXP[:ifdef_macro]))
|
271
|
+
if next_line.include?('::') && (next_line.include?('if') || next_line.include?('endif')) && (match = next_line.match(REGEXP[:ifdef_macro]))
|
272
272
|
if next_line.start_with? '\\'
|
273
273
|
@next_line_preprocessed = true
|
274
274
|
@unescape_next_line = true
|
@@ -287,7 +287,7 @@ class Reader
|
|
287
287
|
@unescape_next_line = true
|
288
288
|
false
|
289
289
|
else
|
290
|
-
preprocess_include(match[1])
|
290
|
+
preprocess_include(match[1], match[2].strip)
|
291
291
|
end
|
292
292
|
else
|
293
293
|
@next_line_preprocessed = true
|
@@ -343,7 +343,7 @@ class Reader
|
|
343
343
|
return preprocess_next_line.nil? ? nil : true
|
344
344
|
end
|
345
345
|
|
346
|
-
skip =
|
346
|
+
skip = false
|
347
347
|
if !@skipping
|
348
348
|
case directive
|
349
349
|
when 'ifdef'
|
@@ -384,17 +384,19 @@ class Reader
|
|
384
384
|
|
385
385
|
skip = !lhs.send(op.to_sym, rhs)
|
386
386
|
end
|
387
|
-
@skipping = skip
|
388
387
|
end
|
389
388
|
advance
|
390
389
|
# single line conditional inclusion
|
391
390
|
if directive != 'ifeval' && !text.nil?
|
392
|
-
if !@skipping
|
391
|
+
if !@skipping && !skip
|
393
392
|
unshift_line "#{text.rstrip}\n"
|
394
393
|
return true
|
395
394
|
end
|
396
395
|
# conditional inclusion block
|
397
396
|
else
|
397
|
+
if !@skipping && skip
|
398
|
+
@skipping = true
|
399
|
+
end
|
398
400
|
@conditionals_stack << {:target => target, :skip => skip, :skipping => @skipping}
|
399
401
|
end
|
400
402
|
return preprocess_next_line.nil? ? nil : true
|
@@ -422,9 +424,11 @@ class Reader
|
|
422
424
|
# target slot of the include::[] macro
|
423
425
|
#
|
424
426
|
# returns a Boolean indicating whether the line under the cursor has changed.
|
425
|
-
def preprocess_include(target)
|
427
|
+
def preprocess_include(target, raw_attributes)
|
426
428
|
# if running in SafeMode::SECURE or greater, don't process this directive
|
429
|
+
# however, be friendly and at least make it a link to the source document
|
427
430
|
if @document.safe >= SafeMode::SECURE
|
431
|
+
@lines[0] = "link:#{target}[#{target}]"
|
428
432
|
@next_line_preprocessed = true
|
429
433
|
false
|
430
434
|
# assume that if a block is given, the developer wants
|
@@ -438,7 +442,81 @@ class Reader
|
|
438
442
|
elsif @document.attributes.fetch('include-depth', 0).to_i > 0
|
439
443
|
advance
|
440
444
|
# FIXME this borks line numbers
|
441
|
-
@
|
445
|
+
include_file = @document.normalize_system_path(target, nil, nil, :target_name => 'include file')
|
446
|
+
if !File.file?(include_file)
|
447
|
+
puts "asciidoctor: WARNING: line #{@lineno}: include file not found: #{include_file}"
|
448
|
+
return true
|
449
|
+
end
|
450
|
+
|
451
|
+
lines = nil
|
452
|
+
tags = nil
|
453
|
+
if !raw_attributes.empty?
|
454
|
+
attributes = AttributeList.new(raw_attributes).parse
|
455
|
+
if attributes.has_key? 'lines'
|
456
|
+
lines = []
|
457
|
+
attributes['lines'].split(REGEXP[:scsv_csv_delim]).each do |linedef|
|
458
|
+
if linedef.include?('..')
|
459
|
+
from, to = linedef.split('..').map(&:to_i)
|
460
|
+
if to == -1
|
461
|
+
lines << from
|
462
|
+
lines << 1.0/0.0
|
463
|
+
else
|
464
|
+
lines.concat Range.new(from, to).to_a
|
465
|
+
end
|
466
|
+
else
|
467
|
+
lines << linedef.to_i
|
468
|
+
end
|
469
|
+
end
|
470
|
+
lines = lines.sort.uniq
|
471
|
+
#lines.push lines.shift if lines.first == -1
|
472
|
+
elsif attributes.has_key? 'tags'
|
473
|
+
tags = attributes['tags'].split(REGEXP[:scsv_csv_delim]).uniq
|
474
|
+
end
|
475
|
+
end
|
476
|
+
if !lines.nil?
|
477
|
+
if !lines.empty?
|
478
|
+
selected = []
|
479
|
+
f = File.new(include_file)
|
480
|
+
f.each_line do |l|
|
481
|
+
take = lines.first
|
482
|
+
if take.is_a?(Float) && take.infinite?
|
483
|
+
selected.push("#{l.rstrip}\n")
|
484
|
+
else
|
485
|
+
if f.lineno == take
|
486
|
+
selected.push("#{l.rstrip}\n")
|
487
|
+
lines.shift
|
488
|
+
end
|
489
|
+
break if lines.empty?
|
490
|
+
end
|
491
|
+
end
|
492
|
+
@lines.unshift(*selected) unless selected.empty?
|
493
|
+
end
|
494
|
+
elsif !tags.nil?
|
495
|
+
if !tags.empty?
|
496
|
+
selected = []
|
497
|
+
active_tag = nil
|
498
|
+
f = File.new(include_file)
|
499
|
+
f.each_line do |l|
|
500
|
+
if !active_tag.nil?
|
501
|
+
if l.include?("end::#{active_tag}[]")
|
502
|
+
active_tag = nil
|
503
|
+
else
|
504
|
+
selected.push("#{l.rstrip}\n")
|
505
|
+
end
|
506
|
+
else
|
507
|
+
tags.each do |tag|
|
508
|
+
if l.include?("tag::#{tag}[]")
|
509
|
+
active_tag = tag
|
510
|
+
break
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
@lines.unshift(*selected) unless selected.empty?
|
516
|
+
end
|
517
|
+
else
|
518
|
+
@lines.unshift(*File.readlines(include_file).map {|l| "#{l.rstrip}\n"})
|
519
|
+
end
|
442
520
|
true
|
443
521
|
else
|
444
522
|
@next_line_preprocessed = true
|
@@ -514,32 +592,54 @@ class Reader
|
|
514
592
|
def grab_lines_until(options = {}, &block)
|
515
593
|
buffer = []
|
516
594
|
|
517
|
-
finis = false
|
518
595
|
advance if options[:skip_first_line]
|
519
|
-
#
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
596
|
+
# very hot code
|
597
|
+
# save options to locals for minor optimizations
|
598
|
+
if options.has_key? :terminator
|
599
|
+
terminator = options[:terminator]
|
600
|
+
break_on_blank_lines = false
|
601
|
+
break_on_list_continuation = false
|
602
|
+
chomp_last_line = options[:chomp_last_line] || false
|
603
|
+
else
|
604
|
+
terminator = nil
|
605
|
+
break_on_blank_lines = options[:break_on_blank_lines]
|
606
|
+
break_on_list_continuation = options[:break_on_list_continuation]
|
607
|
+
chomp_last_line = break_on_blank_lines
|
608
|
+
end
|
524
609
|
skip_line_comments = options[:skip_line_comments]
|
525
610
|
preprocess = options.fetch(:preprocess, true)
|
611
|
+
buffer_empty = true
|
526
612
|
while !(this_line = get_line(preprocess)).nil?
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
613
|
+
# effectively a no-args lamba, but much faster
|
614
|
+
finish = while true
|
615
|
+
break true if terminator && this_line.chomp == terminator
|
616
|
+
break true if break_on_blank_lines && this_line.strip.empty?
|
617
|
+
if break_on_list_continuation && !buffer_empty && this_line.chomp == LIST_CONTINUATION
|
618
|
+
options[:preserve_last_line] = true
|
619
|
+
break true
|
620
|
+
end
|
621
|
+
break true if block && yield(this_line)
|
622
|
+
break false
|
623
|
+
end
|
624
|
+
|
625
|
+
if finish
|
626
|
+
if options[:grab_last_line]
|
627
|
+
buffer << this_line
|
628
|
+
buffer_empty = false
|
629
|
+
end
|
630
|
+
# QUESTION should we dup this_line when restoring??
|
631
|
+
unshift_line this_line if options[:preserve_last_line]
|
535
632
|
break
|
536
633
|
end
|
537
634
|
|
538
635
|
unless skip_line_comments && this_line.match(REGEXP[:comment])
|
539
636
|
buffer << this_line
|
637
|
+
buffer_empty = false
|
540
638
|
end
|
541
639
|
end
|
542
640
|
|
641
|
+
# should we dup the line before chopping?
|
642
|
+
buffer.last.chomp! if chomp_last_line && !buffer_empty
|
543
643
|
buffer
|
544
644
|
end
|
545
645
|
|
@@ -619,8 +719,7 @@ class Reader
|
|
619
719
|
|
620
720
|
# Process bibliography references, so they're available when text
|
621
721
|
# before the reference is being rendered.
|
622
|
-
# FIXME
|
623
|
-
# plus, this should be done while we are walking lines above
|
722
|
+
# FIXME reenable whereever it belongs
|
624
723
|
#@lines.each do |line|
|
625
724
|
# if biblio = line.match(REGEXP[:biblio])
|
626
725
|
# @document.register(:ids, biblio[1])
|