asciidoctor 1.5.6 → 1.5.6.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.

@@ -458,16 +458,16 @@ class PathResolver
458
458
 
459
459
  # Public: Calculate the relative path to this absolute filename from the specified base directory
460
460
  #
461
- # If either the filename or the base_directory are not absolute paths, no work is done.
461
+ # If either the filename or the base_directory are not absolute paths, or the
462
+ # filename is not contained within the base directory, no work is done.
462
463
  #
463
- # filename - An absolute file name as a String
464
- # base_directory - An absolute base directory as a String
464
+ # filename - [String] an absolute filename.
465
+ # base_directory - [String] an absolute base directory.
465
466
  #
466
- # Return the relative path String of the filename calculated from the base directory
467
+ # Return the [String] relative path of the filename calculated from the base directory.
467
468
  def relative_path filename, base_directory
468
- if (is_root? filename) && (is_root? base_directory)
469
- offset = (base_directory.end_with? @file_separator) ? base_directory.length : base_directory.length + 1
470
- filename[offset..-1]
469
+ if (is_root? filename) && (filename.start_with? base_directory)
470
+ filename.slice base_directory.length + 1, filename.length
471
471
  else
472
472
  filename
473
473
  end
@@ -64,7 +64,6 @@ class Reader
64
64
  end
65
65
  @lines = data ? (prepare_lines data, opts) : []
66
66
  @source_lines = @lines.dup
67
- @eof = @lines.empty?
68
67
  @look_ahead = 0
69
68
  @process_lines = true
70
69
  @unescape_next_line = false
@@ -121,8 +120,26 @@ class Reader
121
120
  #
122
121
  # Returns True if there are more lines, False if there are not.
123
122
  def has_more_lines?
124
- !(@eof || (@eof = peek_line.nil?))
123
+ if @lines.empty?
124
+ @look_ahead = 0
125
+ false
126
+ else
127
+ true
128
+ end
129
+ end
130
+
131
+ # Public: Check whether this reader is empty (contains no lines)
132
+ #
133
+ # Returns true if there are no more lines to peek, otherwise false.
134
+ def empty?
135
+ if @lines.empty?
136
+ @look_ahead = 0
137
+ true
138
+ else
139
+ false
140
+ end
125
141
  end
142
+ alias eof? empty?
126
143
 
127
144
  # Public: Peek at the next line and check if it's empty (i.e., whitespace only)
128
145
  #
@@ -154,19 +171,14 @@ class Reader
154
171
  def peek_line direct = false
155
172
  if direct || @look_ahead > 0
156
173
  @unescape_next_line ? @lines[0][1..-1] : @lines[0]
157
- elsif @eof || @lines.empty?
158
- @eof = true
174
+ elsif @lines.empty?
159
175
  @look_ahead = 0
160
176
  nil
161
177
  else
162
178
  # FIXME the problem with this approach is that we aren't
163
179
  # retaining the modified line (hence the @unescape_next_line tweak)
164
- # perhaps we need a stack of proxy lines
165
- if !(line = process_line @lines[0])
166
- peek_line
167
- else
168
- line
169
- end
180
+ # perhaps we need a stack of proxied lines
181
+ (line = process_line @lines[0]) ? line : peek_line
170
182
  end
171
183
  end
172
184
 
@@ -183,11 +195,11 @@ class Reader
183
195
  #
184
196
  # Returns A String Array of the next multiple lines of source data, or an empty Array
185
197
  # if there are no more lines in this Reader.
186
- def peek_lines num = 1, direct = true
198
+ def peek_lines num, direct = false
187
199
  old_look_ahead = @look_ahead
188
200
  result = []
189
201
  num.times do
190
- if (line = read_line direct)
202
+ if (line = direct ? shift : read_line)
191
203
  result << line
192
204
  else
193
205
  @lineno -= 1 if direct
@@ -205,17 +217,11 @@ class Reader
205
217
 
206
218
  # Public: Get the next line of source data. Consumes the line returned.
207
219
  #
208
- # direct - A Boolean flag to bypasses the check for more lines and immediately
209
- # returns the first element of the internal @lines Array. (default: false)
210
- #
211
220
  # Returns the String of the next line of the source data if data is present.
212
221
  # Returns nothing if there is no more data.
213
- def read_line direct = false
214
- if direct || @look_ahead > 0 || has_more_lines?
215
- shift
216
- else
217
- nil
218
- end
222
+ def read_line
223
+ # has_more_lines? triggers preprocessor
224
+ shift if @look_ahead > 0 || has_more_lines?
219
225
  end
220
226
 
221
227
  # Public: Get the remaining lines of source data.
@@ -228,6 +234,7 @@ class Reader
228
234
  # Returns the lines read as a String Array
229
235
  def read_lines
230
236
  lines = []
237
+ # has_more_lines? triggers preprocessor
231
238
  while has_more_lines?
232
239
  lines << shift
233
240
  end
@@ -246,12 +253,9 @@ class Reader
246
253
 
247
254
  # Public: Advance to the next line by discarding the line at the front of the stack
248
255
  #
249
- # direct - A Boolean flag to bypasses the check for more lines and immediately
250
- # returns the first element of the internal @lines Array. (default: true)
251
- #
252
256
  # Returns a Boolean indicating whether there was a line to discard.
253
- def advance direct = true
254
- !!read_line(direct)
257
+ def advance
258
+ shift ? true : false
255
259
  end
256
260
 
257
261
  # Public: Push the String line onto the beginning of the Array of source data.
@@ -294,42 +298,39 @@ class Reader
294
298
  #
295
299
  # Returns nothing.
296
300
  def replace_next_line replacement
297
- advance
301
+ shift
298
302
  unshift replacement
299
303
  nil
300
304
  end
301
305
  # deprecated
302
306
  alias replace_line replace_next_line
303
307
 
304
- # Public: Strip off leading blank lines in the Array of lines.
308
+ # Public: Skip blank lines at the cursor.
305
309
  #
306
310
  # Examples
307
311
  #
308
- # @lines
312
+ # reader.lines
309
313
  # => ["", "", "Foo", "Bar", ""]
310
- #
311
- # skip_blank_lines
314
+ # reader.skip_blank_lines
312
315
  # => 2
313
- #
314
- # @lines
316
+ # reader.lines
315
317
  # => ["Foo", "Bar", ""]
316
318
  #
317
- # Returns an Integer of the number of lines skipped
319
+ # Returns the [Integer] number of lines skipped or nothing if all lines have
320
+ # been consumed (even if lines were skipped by this method).
318
321
  def skip_blank_lines
319
- return 0 if eof?
322
+ return if empty?
320
323
 
321
324
  num_skipped = 0
322
325
  # optimized code for shortest execution path
323
326
  while (next_line = peek_line)
324
327
  if next_line.empty?
325
- advance
328
+ shift
326
329
  num_skipped += 1
327
330
  else
328
331
  return num_skipped
329
332
  end
330
333
  end
331
-
332
- num_skipped
333
334
  end
334
335
 
335
336
  # Public: Skip consecutive lines containing line comments and return them.
@@ -345,19 +346,22 @@ class Reader
345
346
  # => ["bar"]
346
347
  #
347
348
  # Returns the Array of lines that were skipped
348
- def skip_comment_lines opts = {}
349
- return [] if eof?
349
+ def skip_comment_lines
350
+ return [] if empty?
350
351
 
351
352
  comment_lines = []
352
- include_blank_lines = opts[:include_blank_lines]
353
- while (next_line = peek_line)
354
- if include_blank_lines && next_line.empty?
355
- comment_lines << shift
356
- elsif (commentish = next_line.start_with?('//')) && (CommentBlockRx.match? next_line)
357
- comment_lines << shift
358
- comment_lines.push(*(read_lines_until(:terminator => next_line, :read_last_line => true, :skip_processing => true)))
359
- elsif commentish && (CommentLineRx.match? next_line)
360
- comment_lines << shift
353
+ while (next_line = peek_line) && !next_line.empty?
354
+ if next_line.start_with? '//'
355
+ if next_line.start_with? '///'
356
+ if (ll = next_line.length) > 3 && next_line == '/' * ll
357
+ comment_lines << shift
358
+ comment_lines.push(*(read_lines_until(:terminator => next_line, :read_last_line => true, :skip_processing => true)))
359
+ else
360
+ break
361
+ end
362
+ else
363
+ comment_lines << shift
364
+ end
361
365
  else
362
366
  break
363
367
  end
@@ -367,13 +371,15 @@ class Reader
367
371
  end
368
372
 
369
373
  # Public: Skip consecutive lines that are line comments and return them.
374
+ #
375
+ # This method assumes the reader only contains simple lines (no blocks).
370
376
  def skip_line_comments
371
- return [] if eof?
377
+ return [] if empty?
372
378
 
373
379
  comment_lines = []
374
380
  # optimized code for shortest execution path
375
- while (next_line = peek_line)
376
- if CommentLineRx.match? next_line
381
+ while (next_line = peek_line) && !next_line.empty?
382
+ if (next_line.start_with? '//')
377
383
  comment_lines << shift
378
384
  else
379
385
  break
@@ -389,19 +395,10 @@ class Reader
389
395
  def terminate
390
396
  @lineno += @lines.size
391
397
  @lines.clear
392
- @eof = true
393
398
  @look_ahead = 0
394
399
  nil
395
400
  end
396
401
 
397
- # Public: Check whether this reader is empty (contains no lines)
398
- #
399
- # Returns true if there are no more lines to peek, otherwise false.
400
- def eof?
401
- !has_more_lines?
402
- end
403
- alias empty? eof?
404
-
405
402
  # Public: Return all the lines from `@lines` until we (1) run out them,
406
403
  # (2) find a blank line with :break_on_blank_lines => true, or (3) find
407
404
  # a line for which the given block evals to true.
@@ -434,7 +431,7 @@ class Reader
434
431
  # => ["First line", "Second line"]
435
432
  def read_lines_until options = {}
436
433
  result = []
437
- advance if options[:skip_first_line]
434
+ shift if options[:skip_first_line]
438
435
  if @process_lines && options[:skip_processing]
439
436
  @process_lines = false
440
437
  restore_process_lines = true
@@ -477,7 +474,7 @@ class Reader
477
474
  line_restored = true
478
475
  end
479
476
  else
480
- unless skip_comments && (line.start_with? '//') && (CommentLineRx.match? line)
477
+ unless skip_comments && (line.start_with? '//') && !(line.start_with? '///')
481
478
  result << line
482
479
  line_read = true
483
480
  end
@@ -495,6 +492,7 @@ class Reader
495
492
  #
496
493
  # This method can be used directly when you've already called peek_line
497
494
  # and determined that you do, in fact, want to pluck that line off the stack.
495
+ # Use read_line if the line hasn't (or many not have been) visited yet.
498
496
  #
499
497
  # Returns The String line at the top of the stack
500
498
  def shift
@@ -507,7 +505,6 @@ class Reader
507
505
  def unshift line
508
506
  @lineno -= 1
509
507
  @look_ahead += 1
510
- @eof = false
511
508
  @lines.unshift line
512
509
  end
513
510
 
@@ -515,7 +512,6 @@ class Reader
515
512
  def unshift_all lines
516
513
  @lineno -= lines.size
517
514
  @look_ahead += lines.size
518
- @eof = false
519
515
  @lines.unshift(*lines)
520
516
  end
521
517
 
@@ -609,7 +605,7 @@ class PreprocessorReader < Reader
609
605
 
610
606
  if line.empty?
611
607
  @look_ahead += 1
612
- return ''
608
+ return line
613
609
  end
614
610
 
615
611
  # NOTE highly optimized
@@ -622,7 +618,7 @@ class PreprocessorReader < Reader
622
618
  line[1..-1]
623
619
  elsif preprocess_conditional_directive $2, $3, $4, $5
624
620
  # move the pointer past the conditional line
625
- advance
621
+ shift
626
622
  # treat next line as uncharted territory
627
623
  nil
628
624
  else
@@ -632,7 +628,7 @@ class PreprocessorReader < Reader
632
628
  line
633
629
  end
634
630
  elsif @skipping
635
- advance
631
+ shift
636
632
  nil
637
633
  elsif (line.start_with? 'inc', '\\inc') && IncludeDirectiveRx =~ line
638
634
  # if escaped, mark as processed and return line unescaped
@@ -656,7 +652,7 @@ class PreprocessorReader < Reader
656
652
  line
657
653
  end
658
654
  elsif @skipping
659
- advance
655
+ shift
660
656
  nil
661
657
  else
662
658
  # NOTE optimization to inline super
@@ -665,6 +661,17 @@ class PreprocessorReader < Reader
665
661
  end
666
662
  end
667
663
 
664
+ # (see Reader#has_more_lines?)
665
+ def has_more_lines?
666
+ peek_line ? true : false
667
+ end
668
+
669
+ # (see Reader#empty?)
670
+ def empty?
671
+ peek_line ? false : true
672
+ end
673
+ alias eof? empty?
674
+
668
675
  # Public: Override the Reader#peek_line method to pop the include
669
676
  # stack if the last line has been reached and there's at least
670
677
  # one include on the stack.
@@ -814,15 +821,15 @@ class PreprocessorReader < Reader
814
821
  #
815
822
  # Returns a Boolean indicating whether the line under the cursor has changed.
816
823
  def preprocess_include_directive raw_target, raw_attributes
817
- if ((target = raw_target).include? '{') &&
824
+ if ((target = raw_target).include? ATTR_REF_HEAD) &&
818
825
  (target = @document.sub_attributes raw_target, :attribute_missing => 'drop-line').empty?
819
- advance
826
+ shift
820
827
  if @document.attributes.fetch('attribute-missing', Compliance.attribute_missing) == 'skip'
821
828
  unshift %(Unresolved directive in #{@path} - include::#{raw_target}[#{raw_attributes}])
822
829
  end
823
830
  true
824
831
  elsif include_processors? && (ext = @include_processor_extensions.find {|candidate| candidate.instance.handles? target })
825
- advance
832
+ shift
826
833
  # FIXME parse attributes only if requested by extension
827
834
  ext.process_method[@document, self, target, AttributeList.new(raw_attributes).parse]
828
835
  true
@@ -867,9 +874,9 @@ class PreprocessorReader < Reader
867
874
  replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
868
875
  return true
869
876
  end
870
- # NOTE relpath is the path relative to the outermost document (or base_dir, if set)
871
- #relpath = @document.relative_path inc_path
872
- relpath = PathResolver.new.relative_path inc_path, @document.base_dir
877
+ # NOTE relpath is the path relative to the root document (or base_dir, if set)
878
+ # QUESTION should we move relative_path method to Document
879
+ relpath = (@path_resolver ||= PathResolver.new).relative_path inc_path, @document.base_dir
873
880
  end
874
881
 
875
882
  inc_linenos, inc_tags, attributes = nil, nil, {}
@@ -940,7 +947,7 @@ class PreprocessorReader < Reader
940
947
  replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{raw_attributes}])
941
948
  return true
942
949
  end
943
- advance
950
+ shift
944
951
  # FIXME not accounting for skipped lines in reader line numbering
945
952
  push_include inc_lines, inc_path, relpath, inc_offset, attributes if inc_offset
946
953
  elsif inc_tags
@@ -1003,14 +1010,14 @@ class PreprocessorReader < Reader
1003
1010
  unless (missing_tags = inc_tags.keys.to_a - tags_used.to_a).empty?
1004
1011
  warn %(asciidoctor: WARNING: #{line_info}: tag#{missing_tags.size > 1 ? 's' : nil} '#{missing_tags * ','}' not found in include #{target_type}: #{inc_path})
1005
1012
  end
1006
- advance
1013
+ shift
1007
1014
  # FIXME not accounting for skipped lines in reader line numbering
1008
1015
  push_include inc_lines, inc_path, relpath, inc_offset, attributes if inc_offset
1009
1016
  else
1010
1017
  begin
1011
1018
  # NOTE read content first so that we only advance cursor if IO operation succeeds
1012
1019
  inc_content = target_type == :file ? (::IO.read inc_path) : open(inc_path, 'r') {|f| f.read }
1013
- advance
1020
+ shift
1014
1021
  push_include inc_content, inc_path, relpath, 1, attributes
1015
1022
  rescue
1016
1023
  warn %(asciidoctor: WARNING: #{line_info}: include #{target_type} not readable: #{inc_path})
@@ -1053,8 +1060,7 @@ class PreprocessorReader < Reader
1053
1060
  end
1054
1061
 
1055
1062
  if path
1056
- @includes << Helpers.rootname(path)
1057
- @path = path
1063
+ @includes << Helpers.rootname(@path = path)
1058
1064
  else
1059
1065
  @path = '<stdin>'
1060
1066
  end
@@ -1088,7 +1094,6 @@ class PreprocessorReader < Reader
1088
1094
  # FIXME kind of a hack
1089
1095
  #Document::AttributeEntry.new('infile', @file).save_to_next_block @document
1090
1096
  #Document::AttributeEntry.new('indir', @dir).save_to_next_block @document
1091
- @eof = false
1092
1097
  @look_ahead = 0
1093
1098
  end
1094
1099
  self
@@ -1100,10 +1105,9 @@ class PreprocessorReader < Reader
1100
1105
  # FIXME kind of a hack
1101
1106
  #Document::AttributeEntry.new('infile', @file).save_to_next_block @document
1102
1107
  #Document::AttributeEntry.new('indir', ::File.dirname(@file)).save_to_next_block @document
1103
- @eof = @lines.empty?
1104
1108
  @look_ahead = 0
1109
+ nil
1105
1110
  end
1106
- nil
1107
1111
  end
1108
1112
 
1109
1113
  def include_depth
@@ -1197,7 +1201,7 @@ class PreprocessorReader < Reader
1197
1201
 
1198
1202
  # QUESTION should we substitute first?
1199
1203
  # QUESTION should we also require string to be single quoted (like block attribute values?)
1200
- val = @document.sub_attributes val, :attribute_missing => 'drop' if val.include? '{'
1204
+ val = @document.sub_attributes val, :attribute_missing => 'drop' if val.include? ATTR_REF_HEAD
1201
1205
 
1202
1206
  if quoted
1203
1207
  val
@@ -77,12 +77,12 @@ module Substitutors
77
77
  # Public: Apply the specified substitutions to the source.
78
78
  #
79
79
  # source - The String or String Array of text to process; must not be nil.
80
- # subs - The substitutions to perform; can be a Symbol or Symbol Array; must not be nil (default: :normal).
81
- # expand - A Boolean to control whether substitution aliases are expanded (default: false).
80
+ # subs - The substitutions to perform; can be a Symbol, Symbol Array or nil (default: NORMAL_SUBS).
81
+ # expand - A Boolean (or nil) to control whether substitution aliases are expanded (default: nil).
82
82
  #
83
83
  # Returns a String or String Array with substitutions applied, matching the type of source argument.
84
- def apply_subs source, subs = NORMAL_SUBS, expand = false
85
- if source.empty? || subs.empty?
84
+ def apply_subs source, subs = NORMAL_SUBS, expand = nil
85
+ if source.empty? || !subs
86
86
  return source
87
87
  elsif expand
88
88
  if ::Symbol === subs
@@ -101,6 +101,8 @@ module Substitutors
101
101
  return source
102
102
  end
103
103
  end
104
+ elsif subs.empty?
105
+ return source
104
106
  end
105
107
 
106
108
  text = (multiline = ::Array === source) ? source * LF : source
@@ -117,7 +119,7 @@ module Substitutors
117
119
  when :quotes
118
120
  text = sub_quotes text
119
121
  when :attributes
120
- text = sub_attributes(text.split LF, -1) * LF if text.include? '{'
122
+ text = sub_attributes(text.split LF, -1) * LF if text.include? ATTR_REF_HEAD
121
123
  when :replacements
122
124
  text = sub_replacements text
123
125
  when :macros
@@ -190,9 +192,9 @@ module Substitutors
190
192
  if (boundary = m[4]) # $$, ++, or +++
191
193
  # skip ++ in compat mode, handled as normal quoted text
192
194
  if compat_mode && boundary == '++'
193
- next m[2].nil_or_empty? ?
194
- %(#{m[1]}#{m[3]}++#{extract_passthroughs m[5]}++) :
195
- %(#{m[1]}[#{m[2]}]#{m[3]}++#{extract_passthroughs m[5]}++)
195
+ next m[2] ?
196
+ %(#{m[1]}[#{m[2]}]#{m[3]}++#{extract_passthroughs m[5]}++) :
197
+ %(#{m[1]}#{m[3]}++#{extract_passthroughs m[5]}++)
196
198
  end
197
199
 
198
200
  attributes = m[2]
@@ -504,7 +506,7 @@ module Substitutors
504
506
  $&
505
507
  end
506
508
  end
507
- } if line.include? '{'
509
+ } if line.include? ATTR_REF_HEAD
508
510
 
509
511
  result << line unless reject || (reject_if_empty && line.empty?)
510
512
  end
@@ -649,7 +651,7 @@ module Substitutors
649
651
  # alias match for Ruby 1.8.7 compat
650
652
  m = $~
651
653
  # honor the escape
652
- if (captured = m[0]).start_with? RS
654
+ if (captured = $&).start_with? RS
653
655
  next captured[1..-1]
654
656
  end
655
657
 
@@ -658,7 +660,10 @@ module Substitutors
658
660
  else
659
661
  type, posattrs = 'image', ['alt', 'width', 'height']
660
662
  end
661
- target = m[1]
663
+ if (target = m[1]).include? ATTR_REF_HEAD
664
+ # TODO remove this special case once titles use normal substitution order
665
+ target = sub_attributes target
666
+ end
662
667
  @document.register(:images, target) unless type == 'icon'
663
668
  attrs = parse_attributes(m[2], posattrs, :unescape_input => true)
664
669
  attrs['alt'] ||= (attrs['default-alt'] = Helpers.basename(target, true).tr('_-', ' '))
@@ -1096,7 +1101,7 @@ module Substitutors
1096
1101
  # Returns The converted String text for the quoted text region
1097
1102
  def convert_quoted_text(match, type, scope)
1098
1103
  if match[0].start_with? RS
1099
- if scope == :constrained && !(attrs = match[2]).nil_or_empty?
1104
+ if scope == :constrained && (attrs = match[2])
1100
1105
  unescaped_attrs = %([#{attrs}])
1101
1106
  else
1102
1107
  return match[0][1..-1]
@@ -1130,7 +1135,7 @@ module Substitutors
1130
1135
  # Returns a Hash of attributes (role and id only)
1131
1136
  def parse_quoted_text_attributes str
1132
1137
  # NOTE attributes are typically resolved after quoted text, so substitute eagerly
1133
- str = sub_attributes str if str.include? '{'
1138
+ str = sub_attributes str if str.include? ATTR_REF_HEAD
1134
1139
  # for compliance, only consider first positional attribute
1135
1140
  str = str.slice 0, (str.index ',') if str.include? ','
1136
1141
 
@@ -1173,7 +1178,7 @@ module Substitutors
1173
1178
  def parse_attributes(attrline, posattrs = ['role'], opts = {})
1174
1179
  return unless attrline
1175
1180
  return {} if attrline.empty?
1176
- attrline = @document.sub_attributes(attrline) if opts[:sub_input] && (attrline.include? '{')
1181
+ attrline = @document.sub_attributes(attrline) if opts[:sub_input] && (attrline.include? ATTR_REF_HEAD)
1177
1182
  attrline = unescape_bracketed_text(attrline) if opts[:unescape_input]
1178
1183
  # substitutions are only performed on attribute values if block is not nil
1179
1184
  block = opts.fetch(:sub_result, true) ? self : nil