asciidoctor 1.5.8 → 2.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (197) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +11 -0
  3. data/CHANGELOG.adoc +628 -45
  4. data/LICENSE +2 -1
  5. data/README-de.adoc +28 -38
  6. data/README-fr.adoc +30 -43
  7. data/README-jp.adoc +255 -201
  8. data/README-zh_CN.adoc +40 -44
  9. data/README.adoc +170 -143
  10. data/asciidoctor.gemspec +22 -34
  11. data/bin/asciidoctor +5 -4
  12. data/data/locale/attributes-ar.adoc +4 -3
  13. data/data/locale/attributes-be.adoc +23 -0
  14. data/data/locale/attributes-bg.adoc +4 -3
  15. data/data/locale/attributes-ca.adoc +6 -5
  16. data/data/locale/attributes-cs.adoc +4 -3
  17. data/data/locale/attributes-da.adoc +6 -5
  18. data/data/locale/attributes-de.adoc +6 -5
  19. data/data/locale/attributes-en.adoc +4 -4
  20. data/data/locale/attributes-es.adoc +6 -5
  21. data/data/locale/attributes-fa.adoc +4 -3
  22. data/data/locale/attributes-fi.adoc +4 -3
  23. data/data/locale/attributes-fr.adoc +8 -7
  24. data/data/locale/attributes-hu.adoc +4 -3
  25. data/data/locale/attributes-id.adoc +4 -3
  26. data/data/locale/attributes-it.adoc +6 -5
  27. data/data/locale/attributes-ja.adoc +4 -3
  28. data/data/locale/{attributes-kr.adoc → attributes-ko.adoc} +4 -3
  29. data/data/locale/attributes-nb.adoc +4 -3
  30. data/data/locale/attributes-nl.adoc +6 -5
  31. data/data/locale/attributes-nn.adoc +4 -3
  32. data/data/locale/attributes-pl.adoc +8 -7
  33. data/data/locale/attributes-pt.adoc +6 -5
  34. data/data/locale/attributes-pt_BR.adoc +6 -5
  35. data/data/locale/attributes-ro.adoc +4 -3
  36. data/data/locale/attributes-ru.adoc +6 -5
  37. data/data/locale/attributes-sr.adoc +4 -4
  38. data/data/locale/attributes-sr_Latn.adoc +4 -4
  39. data/data/locale/attributes-sv.adoc +4 -4
  40. data/data/locale/attributes-th.adoc +23 -0
  41. data/data/locale/attributes-tr.adoc +4 -3
  42. data/data/locale/attributes-uk.adoc +6 -5
  43. data/data/locale/attributes-vi.adoc +23 -0
  44. data/data/locale/attributes-zh_CN.adoc +4 -3
  45. data/data/locale/attributes-zh_TW.adoc +4 -3
  46. data/data/reference/syntax.adoc +296 -0
  47. data/data/stylesheets/asciidoctor-default.css +120 -114
  48. data/data/stylesheets/coderay-asciidoctor.css +15 -17
  49. data/lib/asciidoctor/abstract_block.rb +146 -140
  50. data/lib/asciidoctor/abstract_node.rb +152 -170
  51. data/lib/asciidoctor/attribute_list.rb +77 -89
  52. data/lib/asciidoctor/block.rb +29 -28
  53. data/lib/asciidoctor/callouts.rb +4 -2
  54. data/lib/asciidoctor/cli/invoker.rb +20 -24
  55. data/lib/asciidoctor/cli/options.rb +107 -96
  56. data/lib/asciidoctor/cli.rb +3 -2
  57. data/lib/asciidoctor/convert.rb +199 -0
  58. data/lib/asciidoctor/converter/composite.rb +40 -48
  59. data/lib/asciidoctor/converter/docbook5.rb +627 -644
  60. data/lib/asciidoctor/converter/html5.rb +1053 -951
  61. data/lib/asciidoctor/converter/manpage.rb +581 -532
  62. data/lib/asciidoctor/converter/template.rb +232 -271
  63. data/lib/asciidoctor/converter.rb +370 -185
  64. data/lib/asciidoctor/core_ext/float/truncate.rb +20 -0
  65. data/lib/asciidoctor/core_ext/hash/merge.rb +8 -0
  66. data/lib/asciidoctor/core_ext/match_data/names.rb +7 -0
  67. data/lib/asciidoctor/core_ext/nil_or_empty.rb +1 -0
  68. data/lib/asciidoctor/core_ext/regexp/is_match.rb +4 -2
  69. data/lib/asciidoctor/core_ext.rb +8 -17
  70. data/lib/asciidoctor/document.rb +503 -461
  71. data/lib/asciidoctor/extensions.rb +127 -174
  72. data/lib/asciidoctor/helpers.rb +184 -107
  73. data/lib/asciidoctor/inline.rb +9 -12
  74. data/lib/asciidoctor/list.rb +11 -29
  75. data/lib/asciidoctor/load.rb +119 -0
  76. data/lib/asciidoctor/logging.rb +22 -17
  77. data/lib/asciidoctor/parser.rb +673 -719
  78. data/lib/asciidoctor/path_resolver.rb +48 -33
  79. data/lib/asciidoctor/reader.rb +383 -338
  80. data/lib/asciidoctor/rouge_ext.rb +39 -0
  81. data/lib/asciidoctor/rx.rb +723 -0
  82. data/lib/asciidoctor/section.rb +17 -16
  83. data/lib/asciidoctor/stylesheets.rb +19 -37
  84. data/lib/asciidoctor/substitutors.rb +926 -1022
  85. data/lib/asciidoctor/syntax_highlighter/coderay.rb +88 -0
  86. data/lib/asciidoctor/syntax_highlighter/highlightjs.rb +34 -0
  87. data/lib/asciidoctor/syntax_highlighter/html_pipeline.rb +10 -0
  88. data/lib/asciidoctor/syntax_highlighter/prettify.rb +30 -0
  89. data/lib/asciidoctor/syntax_highlighter/pygments.rb +157 -0
  90. data/lib/asciidoctor/syntax_highlighter/rouge.rb +143 -0
  91. data/lib/asciidoctor/syntax_highlighter.rb +253 -0
  92. data/lib/asciidoctor/table.rb +152 -114
  93. data/lib/asciidoctor/timings.rb +7 -5
  94. data/lib/asciidoctor/version.rb +2 -1
  95. data/lib/asciidoctor/writer.rb +30 -0
  96. data/lib/asciidoctor.rb +266 -1340
  97. data/man/asciidoctor.1 +49 -47
  98. data/man/asciidoctor.adoc +54 -45
  99. metadata +50 -245
  100. data/CONTRIBUTING.adoc +0 -185
  101. data/Gemfile +0 -60
  102. data/Rakefile +0 -129
  103. data/bin/asciidoctor-safe +0 -15
  104. data/features/open_block.feature +0 -92
  105. data/features/pass_block.feature +0 -66
  106. data/features/step_definitions.rb +0 -49
  107. data/features/text_formatting.feature +0 -57
  108. data/features/xref.feature +0 -1039
  109. data/lib/asciidoctor/converter/base.rb +0 -59
  110. data/lib/asciidoctor/converter/docbook45.rb +0 -93
  111. data/lib/asciidoctor/converter/factory.rb +0 -226
  112. data/lib/asciidoctor/core_ext/1.8.7/base64/strict_encode64.rb +0 -6
  113. data/lib/asciidoctor/core_ext/1.8.7/concurrent/hash.rb +0 -5
  114. data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +0 -4
  115. data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +0 -6
  116. data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +0 -5
  117. data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +0 -6
  118. data/lib/asciidoctor/core_ext/1.8.7/string/limit_bytesize.rb +0 -29
  119. data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +0 -6
  120. data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +0 -6
  121. data/lib/asciidoctor/core_ext/string/limit_bytesize.rb +0 -10
  122. data/test/api_test.rb +0 -1240
  123. data/test/attribute_list_test.rb +0 -242
  124. data/test/attributes_test.rb +0 -1623
  125. data/test/blocks_test.rb +0 -3870
  126. data/test/converter_test.rb +0 -470
  127. data/test/document_test.rb +0 -1853
  128. data/test/extensions_test.rb +0 -1560
  129. data/test/fixtures/asciidoc_index.txt +0 -521
  130. data/test/fixtures/basic-docinfo-footer.html +0 -6
  131. data/test/fixtures/basic-docinfo-footer.xml +0 -8
  132. data/test/fixtures/basic-docinfo.html +0 -1
  133. data/test/fixtures/basic-docinfo.xml +0 -4
  134. data/test/fixtures/basic.asciidoc +0 -5
  135. data/test/fixtures/chapter-a.adoc +0 -3
  136. data/test/fixtures/child-include.adoc +0 -5
  137. data/test/fixtures/circle.svg +0 -9
  138. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +0 -6
  139. data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +0 -6
  140. data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +0 -3
  141. data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +0 -5
  142. data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +0 -1
  143. data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +0 -6
  144. data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +0 -3
  145. data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +0 -5
  146. data/test/fixtures/custom-docinfodir/basic-docinfo.html +0 -1
  147. data/test/fixtures/custom-docinfodir/docinfo.html +0 -1
  148. data/test/fixtures/docinfo-footer.html +0 -1
  149. data/test/fixtures/docinfo-footer.xml +0 -9
  150. data/test/fixtures/docinfo.html +0 -1
  151. data/test/fixtures/docinfo.xml +0 -3
  152. data/test/fixtures/doctime-localtime.adoc +0 -2
  153. data/test/fixtures/dot.gif +0 -0
  154. data/test/fixtures/encoding.asciidoc +0 -13
  155. data/test/fixtures/file-with-missing-include.adoc +0 -1
  156. data/test/fixtures/grandchild-include.adoc +0 -3
  157. data/test/fixtures/hello-asciidoctor.pdf +0 -69
  158. data/test/fixtures/include-file.asciidoc +0 -24
  159. data/test/fixtures/include-file.jsx +0 -8
  160. data/test/fixtures/include-file.ml +0 -3
  161. data/test/fixtures/include-file.xml +0 -5
  162. data/test/fixtures/lists.adoc +0 -96
  163. data/test/fixtures/master.adoc +0 -5
  164. data/test/fixtures/mismatched-end-tag.adoc +0 -7
  165. data/test/fixtures/other-chapters.adoc +0 -11
  166. data/test/fixtures/outer-include.adoc +0 -5
  167. data/test/fixtures/parent-include-restricted.adoc +0 -5
  168. data/test/fixtures/parent-include.adoc +0 -5
  169. data/test/fixtures/sample.asciidoc +0 -30
  170. data/test/fixtures/section-a.adoc +0 -4
  171. data/test/fixtures/stylesheets/custom.css +0 -3
  172. data/test/fixtures/subdir/index.adoc +0 -3
  173. data/test/fixtures/subdir/inner-include.adoc +0 -3
  174. data/test/fixtures/subdir/middle-include.adoc +0 -5
  175. data/test/fixtures/subs-docinfo.html +0 -2
  176. data/test/fixtures/subs.adoc +0 -6
  177. data/test/fixtures/tagged-class-enclosed.rb +0 -25
  178. data/test/fixtures/tagged-class.rb +0 -23
  179. data/test/fixtures/tip.gif +0 -0
  180. data/test/fixtures/unclosed-tag.adoc +0 -3
  181. data/test/fixtures/unexpected-end-tag.adoc +0 -4
  182. data/test/invoker_test.rb +0 -745
  183. data/test/links_test.rb +0 -855
  184. data/test/lists_test.rb +0 -5151
  185. data/test/logger_test.rb +0 -211
  186. data/test/manpage_test.rb +0 -660
  187. data/test/options_test.rb +0 -262
  188. data/test/paragraphs_test.rb +0 -562
  189. data/test/parser_test.rb +0 -742
  190. data/test/paths_test.rb +0 -395
  191. data/test/preamble_test.rb +0 -173
  192. data/test/reader_test.rb +0 -2161
  193. data/test/sections_test.rb +0 -3575
  194. data/test/substitutions_test.rb +0 -2066
  195. data/test/tables_test.rb +0 -2036
  196. data/test/test_helper.rb +0 -447
  197. data/test/text_test.rb +0 -309
@@ -1,4 +1,4 @@
1
- # encoding: UTF-8
1
+ # frozen_string_literal: true
2
2
  module Asciidoctor
3
3
  # Public: Methods for retrieving lines from AsciiDoc source files
4
4
  class Reader
@@ -44,11 +44,11 @@ class Reader
44
44
  @file = nil
45
45
  @dir = '.'
46
46
  @path = '<stdin>'
47
- @lineno = 1 # IMPORTANT lineno assignment must proceed prepare_lines call!
47
+ @lineno = 1
48
48
  elsif ::String === cursor
49
49
  @file = cursor
50
50
  @dir, @path = ::File.split @file
51
- @lineno = 1 # IMPORTANT lineno assignment must proceed prepare_lines call!
51
+ @lineno = 1
52
52
  else
53
53
  if (@file = cursor.file)
54
54
  @dir = cursor.dir || (::File.dirname @file)
@@ -57,10 +57,9 @@ class Reader
57
57
  @dir = cursor.dir || '.'
58
58
  @path = cursor.path || '<stdin>'
59
59
  end
60
- @lineno = cursor.lineno || 1 # IMPORTANT lineno assignment must proceed prepare_lines call!
60
+ @lineno = cursor.lineno || 1
61
61
  end
62
- @lines = data ? (prepare_lines data, opts) : []
63
- @source_lines = @lines.drop 0
62
+ @lines = (@source_lines = prepare_lines data, opts).reverse
64
63
  @mark = nil
65
64
  @look_ahead = 0
66
65
  @process_lines = true
@@ -69,47 +68,6 @@ class Reader
69
68
  @saved = nil
70
69
  end
71
70
 
72
- # Internal: Prepare the lines from the provided data
73
- #
74
- # This method strips whitespace from the end of every line of
75
- # the source data and appends a LF (i.e., Unix endline). This
76
- # whitespace substitution is very important to how Asciidoctor
77
- # works.
78
- #
79
- # Any leading or trailing blank lines are also removed.
80
- #
81
- # data - A String Array of input data to be normalized
82
- # opts - A Hash of options to control what cleansing is done
83
- #
84
- # Returns The String lines extracted from the data
85
- def prepare_lines data, opts = {}
86
- if ::String === data
87
- if opts[:normalize]
88
- Helpers.normalize_lines_from_string data
89
- else
90
- data.split LF, -1
91
- end
92
- elsif opts[:normalize]
93
- Helpers.normalize_lines_array data
94
- else
95
- data.drop 0
96
- end
97
- end
98
-
99
- # Internal: Processes a previously unvisited line
100
- #
101
- # By default, this method marks the line as processed
102
- # by incrementing the look_ahead counter and returns
103
- # the line unmodified.
104
- #
105
- # Returns The String line the Reader should make available to the next
106
- # invocation of Reader#read_line or nil if the Reader should drop the line,
107
- # advance to the next line and process it.
108
- def process_line line
109
- @look_ahead += 1 if @process_lines
110
- line
111
- end
112
-
113
71
  # Public: Check whether there are any lines left to read.
114
72
  #
115
73
  # If a previous call to this method resulted in a value of false,
@@ -168,7 +126,7 @@ class Reader
168
126
  # Returns nothing if there is no more data.
169
127
  def peek_line direct = false
170
128
  if direct || @look_ahead > 0
171
- @unescape_next_line ? ((line = @lines[0]).slice 1, line.length) : @lines[0]
129
+ @unescape_next_line ? ((line = @lines[-1]).slice 1, line.length) : @lines[-1]
172
130
  elsif @lines.empty?
173
131
  @look_ahead = 0
174
132
  nil
@@ -176,7 +134,7 @@ class Reader
176
134
  # FIXME the problem with this approach is that we aren't
177
135
  # retaining the modified line (hence the @unescape_next_line tweak)
178
136
  # perhaps we need a stack of proxied lines
179
- (line = process_line @lines[0]) ? line : peek_line
137
+ (process_line @lines[-1]) || peek_line
180
138
  end
181
139
  end
182
140
 
@@ -233,9 +191,7 @@ class Reader
233
191
  def read_lines
234
192
  lines = []
235
193
  # has_more_lines? triggers preprocessor
236
- while has_more_lines?
237
- lines << shift
238
- end
194
+ lines << shift while has_more_lines?
239
195
  lines
240
196
  end
241
197
  alias readlines read_lines
@@ -282,7 +238,6 @@ class Reader
282
238
  # Returns nothing.
283
239
  def unshift_lines lines_to_restore
284
240
  unshift_all lines_to_restore
285
- nil
286
241
  end
287
242
  alias restore_lines unshift_lines
288
243
 
@@ -351,7 +306,7 @@ class Reader
351
306
  if next_line.start_with? '//'
352
307
  if next_line.start_with? '///'
353
308
  if (ll = next_line.length) > 3 && next_line == '/' * ll
354
- read_lines_until :terminator => next_line, :skip_first_line => true, :read_last_line => true, :skip_processing => true, :context => :comment
309
+ read_lines_until terminator: next_line, skip_first_line: true, read_last_line: true, skip_processing: true, context: :comment
355
310
  else
356
311
  break
357
312
  end
@@ -375,7 +330,7 @@ class Reader
375
330
  comment_lines = []
376
331
  # optimized code for shortest execution path
377
332
  while (next_line = peek_line) && !next_line.empty?
378
- if (next_line.start_with? '//')
333
+ if next_line.start_with? '//'
379
334
  comment_lines << shift
380
335
  else
381
336
  break
@@ -396,7 +351,7 @@ class Reader
396
351
  end
397
352
 
398
353
  # Public: Return all the lines from `@lines` until we (1) run out them,
399
- # (2) find a blank line with :break_on_blank_lines => true, or (3) find
354
+ # (2) find a blank line with `break_on_blank_lines: true`, or (3) find
400
355
  # a line for which the given block evals to true.
401
356
  #
402
357
  # options - an optional Hash of processing options:
@@ -429,7 +384,7 @@ class Reader
429
384
  # "\n",
430
385
  # "Third line\n",
431
386
  # ]
432
- # reader = Reader.new data, nil, :normalize => true
387
+ # reader = Reader.new data, nil, normalize: true
433
388
  #
434
389
  # reader.read_lines_until
435
390
  # => ["First line", "Second line"]
@@ -448,34 +403,22 @@ class Reader
448
403
  break_on_list_continuation = options[:break_on_list_continuation]
449
404
  end
450
405
  skip_comments = options[:skip_line_comments]
451
- complete = line_read = line_restored = nil
406
+ line_read = line_restored = nil
452
407
  shift if options[:skip_first_line]
453
- while !complete && (line = read_line)
454
- complete = while true
455
- break true if terminator && line == terminator
456
- # QUESTION: can we get away with line.empty? here?
457
- break true if break_on_blank_lines && line.empty?
458
- if break_on_list_continuation && line_read && line == LIST_CONTINUATION
459
- options[:preserve_last_line] = true
460
- break true
461
- end
462
- break true if block_given? && (yield line)
463
- break false
464
- end
465
- if complete
466
- if options[:read_last_line]
467
- result << line
468
- line_read = true
469
- end
408
+ while (line = read_line)
409
+ if terminator ? line == terminator : ((break_on_blank_lines && line.empty?) ||
410
+ (break_on_list_continuation && line_read && line == LIST_CONTINUATION && (options[:preserve_last_line] = true)) ||
411
+ (block_given? && (yield line)))
412
+ result << line if options[:read_last_line]
470
413
  if options[:preserve_last_line]
471
414
  unshift line
472
415
  line_restored = true
473
416
  end
474
- else
475
- unless skip_comments && (line.start_with? '//') && !(line.start_with? '///')
476
- result << line
477
- line_read = true
478
- end
417
+ break
418
+ end
419
+ unless skip_comments && (line.start_with? '//') && !(line.start_with? '///')
420
+ result << line
421
+ line_read = true
479
422
  end
480
423
  end
481
424
  if restore_process_lines
@@ -484,7 +427,7 @@ class Reader
484
427
  end
485
428
  if terminator && terminator != line && (context = options.fetch :context, terminator)
486
429
  start_cursor = cursor_at_mark if start_cursor == :at_mark
487
- logger.warn message_with_context %(unterminated #{context} block), :source_location => start_cursor
430
+ logger.warn message_with_context %(unterminated #{context} block), source_location: start_cursor
488
431
  @unterminated = true
489
432
  end
490
433
  result
@@ -500,21 +443,37 @@ class Reader
500
443
  def shift
501
444
  @lineno += 1
502
445
  @look_ahead -= 1 unless @look_ahead == 0
503
- @lines.shift
446
+ @lines.pop
504
447
  end
505
448
 
506
449
  # Internal: Restore the line to the stack and decrement the lineno
507
450
  def unshift line
508
451
  @lineno -= 1
509
452
  @look_ahead += 1
510
- @lines.unshift line
453
+ @lines.push line
454
+ nil
511
455
  end
512
456
 
513
- # Internal: Restore the lines to the stack and decrement the lineno
514
- def unshift_all lines
515
- @lineno -= lines.size
516
- @look_ahead += lines.size
517
- @lines.unshift(*lines)
457
+ if ::RUBY_ENGINE == 'jruby'
458
+ # Internal: Restore the lines to the stack and decrement the lineno
459
+ def unshift_all lines_to_restore
460
+ @lineno -= lines_to_restore.size
461
+ @look_ahead += lines_to_restore.size
462
+ if lines_to_restore.respond_to? :reverse
463
+ @lines.push(*lines_to_restore.reverse)
464
+ else
465
+ lines_to_restore.reverse_each {|it| @lines.push it }
466
+ end
467
+ nil
468
+ end
469
+ else
470
+ # Internal: Restore the lines to the stack and decrement the lineno
471
+ def unshift_all lines_to_restore
472
+ @lineno -= lines_to_restore.size
473
+ @look_ahead += lines_to_restore.size
474
+ @lines.push(*lines_to_restore.reverse)
475
+ nil
476
+ end
518
477
  end
519
478
 
520
479
  def cursor
@@ -557,12 +516,12 @@ class Reader
557
516
  #
558
517
  # Returns A copy of the String Array of lines remaining in this Reader
559
518
  def lines
560
- @lines.drop 0
519
+ @lines.reverse
561
520
  end
562
521
 
563
522
  # Public: Get a copy of the remaining lines managed by this Reader joined as a String
564
523
  def string
565
- @lines.join LF
524
+ @lines.reverse.join LF
566
525
  end
567
526
 
568
527
  # Public: Get the source lines for this Reader joined as a String
@@ -570,15 +529,19 @@ class Reader
570
529
  @source_lines.join LF
571
530
  end
572
531
 
532
+ # Internal: Save the state of the reader at cursor
573
533
  def save
574
- accum = {}
575
- instance_variables.each do |name|
576
- accum[name] = ::Array === (val = instance_variable_get name) ? val.dup : val unless name == :@saved || name == :@source_lines
534
+ @saved = {}.tap do |accum|
535
+ instance_variables.each do |name|
536
+ unless name == :@saved || name == :@source_lines
537
+ accum[name] = ::Array === (val = instance_variable_get name) ? (val.drop 0) : val
538
+ end
539
+ end
577
540
  end
578
- @saved = accum
579
541
  nil
580
542
  end
581
543
 
544
+ # Internal: Restore the state of the reader at cursor
582
545
  def restore_save
583
546
  if @saved
584
547
  @saved.each do |name, val|
@@ -588,15 +551,60 @@ class Reader
588
551
  end
589
552
  end
590
553
 
554
+ # Internal: Discard a previous saved state
591
555
  def discard_save
592
556
  @saved = nil
593
557
  end
594
558
 
595
- # Public: Get a summary of this Reader.
559
+ def to_s
560
+ %(#<#{self.class}@#{object_id} {path: #{@path.inspect}, line: #{@lineno}}>)
561
+ end
562
+
563
+ private
564
+
565
+ # Internal: Prepare the source data for parsing.
566
+ #
567
+ # Converts the source data into an Array of lines ready for parsing. If the +:normalize+ option is set, this method
568
+ # coerces the encoding of each line to UTF-8 and strips trailing whitespace, including the newline. (This whitespace
569
+ # cleaning is very important to how Asciidoctor works). Subclasses may choose to perform additional preparation.
596
570
  #
571
+ # data - A String Array or String of source data to be normalized.
572
+ # opts - A Hash of options to control how lines are prepared.
573
+ # :normalize - Enables line normalization, which coerces the encoding to UTF-8 and removes trailing whitespace;
574
+ # :rstrip removes all trailing whitespace; :chomp removes trailing newline only (optional, not set).
597
575
  #
598
- # Returns A string summary of this reader, which contains the path and line information
599
- alias to_s line_info
576
+ # Returns A String Array of source lines. If the source data is an Array, this method returns a copy.
577
+ def prepare_lines data, opts = {}
578
+ if (normalize = opts[:normalize])
579
+ ::Array === data ? (Helpers.prepare_source_array data, normalize != :chomp) : (Helpers.prepare_source_string data, normalize != :chomp)
580
+ elsif ::Array === data
581
+ data.drop 0
582
+ elsif data
583
+ data.chomp.split LF, -1
584
+ else
585
+ []
586
+ end
587
+ rescue
588
+ if (::Array === data ? data.join : data.to_s).valid_encoding?
589
+ raise
590
+ else
591
+ raise ::ArgumentError, 'source is either binary or contains invalid Unicode data'
592
+ end
593
+ end
594
+
595
+ # Internal: Processes a previously unvisited line
596
+ #
597
+ # By default, this method marks the line as processed
598
+ # by incrementing the look_ahead counter and returns
599
+ # the line unmodified.
600
+ #
601
+ # Returns The String line the Reader should make available to the next
602
+ # invocation of Reader#read_line or nil if the Reader should drop the line,
603
+ # advance to the next line and process it.
604
+ def process_line line
605
+ @look_ahead += 1 if @process_lines
606
+ line
607
+ end
600
608
  end
601
609
 
602
610
  # Public: Methods for retrieving lines from AsciiDoc source files, evaluating preprocessor
@@ -608,10 +616,13 @@ class PreprocessorReader < Reader
608
616
  def initialize document, data = nil, cursor = nil, opts = {}
609
617
  @document = document
610
618
  super data, cursor, opts
611
- include_depth_default = document.attributes.fetch('max-include-depth', 64).to_i
612
- include_depth_default = 0 if include_depth_default < 0
613
- # track both absolute depth for comparing to size of include stack and relative depth for reporting
614
- @maxdepth = {:abs => include_depth_default, :rel => include_depth_default}
619
+ if (default_include_depth = (document.attributes['max-include-depth'] || 64).to_i) > 0
620
+ # track absolute max depth, current max depth for comparing to include stack size, and relative max depth for reporting
621
+ @maxdepth = { abs: default_include_depth, curr: default_include_depth, rel: default_include_depth }
622
+ else
623
+ # if @maxdepth is not set, built-in include functionality is disabled
624
+ @maxdepth = nil
625
+ end
615
626
  @include_stack = []
616
627
  @includes = document.catalog[:includes]
617
628
  @skipping = false
@@ -619,24 +630,183 @@ class PreprocessorReader < Reader
619
630
  @include_processor_extensions = nil
620
631
  end
621
632
 
633
+ # (see Reader#has_more_lines?)
634
+ def has_more_lines?
635
+ peek_line ? true : false
636
+ end
637
+
638
+ # (see Reader#empty?)
639
+ def empty?
640
+ peek_line ? false : true
641
+ end
642
+ alias eof? empty?
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 nothing 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
659
+ end
660
+ end
661
+
662
+ # Public: Push source onto the front of the reader and switch the context
663
+ # based on the file, document-relative path and line information given.
664
+ #
665
+ # This method is typically used in an IncludeProcessor to add source
666
+ # read from the target specified.
667
+ #
668
+ # Examples
669
+ #
670
+ # path = 'partial.adoc'
671
+ # file = File.expand_path path
672
+ # data = File.read file
673
+ # reader.push_include data, file, path
674
+ #
675
+ # Returns this Reader object.
676
+ def push_include data, file = nil, path = nil, lineno = 1, attributes = {}
677
+ @include_stack << [@lines, @file, @dir, @path, @lineno, @maxdepth, @process_lines]
678
+ if (@file = file)
679
+ # NOTE if file is not a string, assume it's a URI
680
+ if ::String === file
681
+ @dir = ::File.dirname file
682
+ elsif RUBY_ENGINE_OPAL
683
+ @dir = ::URI.parse ::File.dirname(file = file.to_s)
684
+ else
685
+ # NOTE this intentionally throws an error if URI has no path
686
+ (@dir = file.dup).path = (dir = ::File.dirname file.path) == '/' ? '' : dir
687
+ file = file.to_s
688
+ end
689
+ @path = (path ||= ::File.basename file)
690
+ # only process lines in AsciiDoc files
691
+ if (@process_lines = file.end_with?(*ASCIIDOC_EXTENSIONS.keys))
692
+ # NOTE registering the include with a nil value tracks it while not making it visible to interdocument xrefs
693
+ @includes[path.slice 0, (path.rindex '.')] = attributes['partial-option'] ? nil : true
694
+ end
695
+ else
696
+ @dir = '.'
697
+ # we don't know what file type we have, so assume AsciiDoc
698
+ @process_lines = true
699
+ if (@path = path)
700
+ # NOTE registering the include with a nil value tracks it while not making it visible to interdocument xrefs
701
+ @includes[Helpers.rootname path] = attributes['partial-option'] ? nil : true
702
+ else
703
+ @path = '<stdin>'
704
+ end
705
+ end
706
+
707
+ @lineno = lineno
708
+
709
+ if @maxdepth && (attributes.key? 'depth')
710
+ if (rel_maxdepth = attributes['depth'].to_i) > 0
711
+ if (curr_maxdepth = @include_stack.size + rel_maxdepth) > (abs_maxdepth = @maxdepth[:abs])
712
+ # if relative depth exceeds absolute max depth, effectively ignore relative depth request
713
+ curr_maxdepth = rel_maxdepth = abs_maxdepth
714
+ end
715
+ @maxdepth = { abs: abs_maxdepth, curr: curr_maxdepth, rel: rel_maxdepth }
716
+ else
717
+ @maxdepth = { abs: @maxdepth[:abs], curr: @include_stack.size, rel: 0 }
718
+ end
719
+ end
720
+
721
+ # effectively fill the buffer
722
+ if (@lines = prepare_lines data, normalize: @process_lines || :chomp, condense: false, indent: attributes['indent']).empty?
723
+ pop_include
724
+ else
725
+ # FIXME we eventually want to handle leveloffset without affecting the lines
726
+ if attributes.key? 'leveloffset'
727
+ @lines = [((leveloffset = @document.attr 'leveloffset') ? %(:leveloffset: #{leveloffset}) : ':leveloffset!:'), ''] + @lines.reverse + ['', %(:leveloffset: #{attributes['leveloffset']})]
728
+ # compensate for these extra lines at the top
729
+ @lineno -= 2
730
+ else
731
+ @lines.reverse!
732
+ end
733
+
734
+ # FIXME kind of a hack
735
+ #Document::AttributeEntry.new('infile', @file).save_to_next_block @document
736
+ #Document::AttributeEntry.new('indir', @dir).save_to_next_block @document
737
+ @look_ahead = 0
738
+ end
739
+ self
740
+ end
741
+
742
+ def include_depth
743
+ @include_stack.size
744
+ end
745
+
746
+ # Public: Reports whether pushing an include on the include stack exceeds the max include depth.
747
+ #
748
+ # Returns nil if no max depth is set and includes are disabled (max-include-depth=0), false if the current max depth
749
+ # will not be exceeded, and the relative max include depth if the current max depth will be exceed.
750
+ def exceeds_max_depth?
751
+ @maxdepth && @include_stack.size >= @maxdepth[:curr] && @maxdepth[:rel]
752
+ end
753
+ alias exceeded_max_depth? exceeds_max_depth?
754
+
755
+ # TODO Document this override
756
+ # also, we now have the field in the super class, so perhaps
757
+ # just implement the logic there?
758
+ def shift
759
+ if @unescape_next_line
760
+ @unescape_next_line = false
761
+ (line = super).slice 1, line.length
762
+ else
763
+ super
764
+ end
765
+ end
766
+
767
+ def include_processors?
768
+ if @include_processor_extensions.nil?
769
+ if @document.extensions? && @document.extensions.include_processors?
770
+ !!(@include_processor_extensions = @document.extensions.include_processors)
771
+ else
772
+ @include_processor_extensions = false
773
+ end
774
+ else
775
+ @include_processor_extensions != false
776
+ end
777
+ end
778
+
779
+ def create_include_cursor file, path, lineno
780
+ if ::String === file
781
+ dir = ::File.dirname file
782
+ elsif RUBY_ENGINE_OPAL
783
+ dir = ::File.dirname(file = file.to_s)
784
+ else
785
+ dir = (dir = ::File.dirname file.path) == '' ? '/' : dir
786
+ file = file.to_s
787
+ end
788
+ Cursor.new file, dir, path, lineno
789
+ end
790
+
791
+ def to_s
792
+ %(#<#{self.class}@#{object_id} {path: #{@path.inspect}, line: #{@lineno}, include depth: #{@include_stack.size}, include stack: [#{@include_stack.map {|inc| inc.to_s }.join ', '}]}>)
793
+ end
794
+
795
+ private
796
+
622
797
  def prepare_lines data, opts = {}
623
798
  result = super
624
799
 
625
800
  # QUESTION should this work for AsciiDoc table cell content? Currently it does not.
626
- if @document && @document.attributes['skip-front-matter']
627
- if (front_matter = skip_front_matter! result)
628
- @document.attributes['front-matter'] = front_matter.join LF
629
- end
801
+ if @document && @document.attributes['skip-front-matter'] && (front_matter = skip_front_matter! result)
802
+ @document.attributes['front-matter'] = front_matter.join LF
630
803
  end
631
804
 
632
805
  if opts.fetch :condense, true
633
- result.shift && @lineno += 1 while (first = result[0]) && first.empty?
634
806
  result.pop while (last = result[-1]) && last.empty?
635
807
  end
636
808
 
637
- if opts[:indent]
638
- Parser.adjust_indentation! result, opts[:indent], (@document.attr 'tabsize')
639
- end
809
+ Parser.adjust_indentation! result, opts[:indent].to_i, (@document.attr 'tabsize').to_i if opts[:indent]
640
810
 
641
811
  result
642
812
  end
@@ -702,35 +872,6 @@ class PreprocessorReader < Reader
702
872
  end
703
873
  end
704
874
 
705
- # (see Reader#has_more_lines?)
706
- def has_more_lines?
707
- peek_line ? true : false
708
- end
709
-
710
- # (see Reader#empty?)
711
- def empty?
712
- peek_line ? false : true
713
- end
714
- alias eof? empty?
715
-
716
- # Public: Override the Reader#peek_line method to pop the include
717
- # stack if the last line has been reached and there's at least
718
- # one include on the stack.
719
- #
720
- # Returns the next line of the source data as a String if there are lines remaining
721
- # in the current include context or a parent include context.
722
- # Returns nothing if there are no more lines remaining and the include stack is empty.
723
- def peek_line direct = false
724
- if (line = super)
725
- line
726
- elsif @include_stack.empty?
727
- nil
728
- else
729
- pop_include
730
- peek_line direct
731
- end
732
- end
733
-
734
875
  # Internal: Preprocess the directive to conditionally include or exclude content.
735
876
  #
736
877
  # Preprocess the conditional directive (ifdef, ifndef, ifeval, endif) under
@@ -755,30 +896,28 @@ class PreprocessorReader < Reader
755
896
  # attributes are case insensitive
756
897
  target = target.downcase unless (no_target = target.empty?)
757
898
 
758
- # must have a target before brackets if ifdef or ifndef
759
- # must not have text between brackets if endif
760
- # skip line if it doesn't meet this criteria
761
- # QUESTION should we warn for these bogus declarations?
762
- return false if (no_target && (keyword == 'ifdef' || keyword == 'ifndef')) || (text && keyword == 'endif')
763
-
764
899
  if keyword == 'endif'
765
- if @conditional_stack.empty?
766
- logger.error message_with_context %(unmatched macro: endif::#{target}[]), :source_location => cursor
900
+ if text
901
+ logger.error message_with_context %(malformed preprocessor directive - text not permitted: endif::#{target}[#{text}]), source_location: cursor
902
+ elsif @conditional_stack.empty?
903
+ logger.error message_with_context %(unmatched preprocessor directive: endif::#{target}[]), source_location: cursor
767
904
  elsif no_target || target == (pair = @conditional_stack[-1])[:target]
768
905
  @conditional_stack.pop
769
906
  @skipping = @conditional_stack.empty? ? false : @conditional_stack[-1][:skipping]
770
907
  else
771
- logger.error message_with_context %(mismatched macro: endif::#{target}[], expected endif::#{pair[:target]}[]), :source_location => cursor
908
+ logger.error message_with_context %(mismatched preprocessor directive: endif::#{target}[], expected endif::#{pair[:target]}[]), source_location: cursor
772
909
  end
773
910
  return true
774
- end
775
-
776
- if @skipping
911
+ elsif @skipping
777
912
  skip = false
778
913
  else
779
914
  # QUESTION any way to wrap ifdef & ifndef logic up together?
780
915
  case keyword
781
916
  when 'ifdef'
917
+ if no_target
918
+ logger.error message_with_context %(malformed preprocessor directive - missing target: ifdef::[#{text}]), source_location: cursor
919
+ return true
920
+ end
782
921
  case delimiter
783
922
  when ','
784
923
  # skip if no attribute is defined
@@ -791,6 +930,10 @@ class PreprocessorReader < Reader
791
930
  skip = !@document.attributes.key?(target)
792
931
  end
793
932
  when 'ifndef'
933
+ if no_target
934
+ logger.error message_with_context %(malformed preprocessor directive - missing target: ifndef::[#{text}]), source_location: cursor
935
+ return true
936
+ end
794
937
  case delimiter
795
938
  when ','
796
939
  # skip if any attribute is defined
@@ -803,20 +946,22 @@ class PreprocessorReader < Reader
803
946
  skip = @document.attributes.key?(target)
804
947
  end
805
948
  when 'ifeval'
806
- # the text in brackets must match an expression
807
- # don't honor match if it doesn't meet this criteria
808
- return false unless no_target && EvalExpressionRx =~ text.strip
809
-
810
- # NOTE save values eagerly for Ruby 1.8.7 compat
811
- lhs, op, rhs = $1, $2, $3
812
- lhs = resolve_expr_val lhs
813
- rhs = resolve_expr_val rhs
814
-
815
- # regex enforces a restricted set of math-related operations
816
- if op == '!='
817
- skip = lhs.send :==, rhs
949
+ if no_target
950
+ # the text in brackets must match a conditional expression
951
+ if text && EvalExpressionRx =~ text.strip
952
+ # NOTE assignments must happen before call to resolve_expr_val for compatibility with Opal
953
+ lhs = $1
954
+ # regex enforces a restricted set of math-related operations (==, !=, <=, >=, <, >)
955
+ op = $2
956
+ rhs = $3
957
+ skip = ((resolve_expr_val lhs).send op, (resolve_expr_val rhs)) ? false : true rescue true
958
+ else
959
+ logger.error message_with_context %(malformed preprocessor directive - #{text ? 'invalid expression' : 'missing expression'}: ifeval::[#{text}]), source_location: cursor
960
+ return true
961
+ end
818
962
  else
819
- skip = !(lhs.send op.to_sym, rhs)
963
+ logger.error message_with_context %(malformed preprocessor directive - target not permitted: ifeval::#{target}[#{text}]), source_location: cursor
964
+ return true
820
965
  end
821
966
  end
822
967
  end
@@ -824,7 +969,7 @@ class PreprocessorReader < Reader
824
969
  # conditional inclusion block
825
970
  if keyword == 'ifeval' || !text
826
971
  @skipping = true if skip
827
- @conditional_stack << {:target => target, :skip => skip, :skipping => @skipping}
972
+ @conditional_stack << { target: target, skip: skip, skipping: @skipping }
828
973
  # single line conditional inclusion
829
974
  else
830
975
  unless @skipping || skip
@@ -867,40 +1012,64 @@ class PreprocessorReader < Reader
867
1012
  def preprocess_include_directive target, attrlist
868
1013
  doc = @document
869
1014
  if ((expanded_target = target).include? ATTR_REF_HEAD) &&
870
- (expanded_target = doc.sub_attributes target, :attribute_missing => 'drop-line').empty?
871
- shift
872
- if (doc.attributes['attribute-missing'] || Compliance.attribute_missing) == 'skip'
873
- unshift %(Unresolved directive in #{@path} - include::#{target}[#{attrlist}])
1015
+ (expanded_target = doc.sub_attributes target, attribute_missing: ((attr_missing = doc.attributes['attribute-missing'] || Compliance.attribute_missing) == 'warn' ? 'drop-line' : attr_missing)).empty?
1016
+ if attr_missing == 'drop-line' && (doc.sub_attributes target + ' ', attribute_missing: 'drop-line', drop_line_severity: :ignore).empty?
1017
+ logger.info { message_with_context %(include dropped due to missing attribute: include::#{target}[#{attrlist}]), source_location: cursor }
1018
+ shift
1019
+ true
1020
+ elsif (doc.parse_attributes attrlist, [], sub_input: true)['optional-option']
1021
+ logger.info { message_with_context %(optional include dropped #{attr_missing == 'warn' && (doc.sub_attributes target + ' ', attribute_missing: 'drop-line', drop_line_severity: :ignore).empty? ? 'due to missing attribute' : 'because resolved target is blank'}: include::#{target}[#{attrlist}]), source_location: cursor }
1022
+ shift
1023
+ true
1024
+ else
1025
+ logger.warn message_with_context %(include dropped #{attr_missing == 'warn' && (doc.sub_attributes target + ' ', attribute_missing: 'drop-line', drop_line_severity: :ignore).empty? ? 'due to missing attribute' : 'because resolved target is blank'}: include::#{target}[#{attrlist}]), source_location: cursor
1026
+ # QUESTION should this line include target or expanded_target (or escaped target?)
1027
+ replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{attrlist}])
874
1028
  end
875
- true
876
1029
  elsif include_processors? && (ext = @include_processor_extensions.find {|candidate| candidate.instance.handles? expanded_target })
877
1030
  shift
878
1031
  # FIXME parse attributes only if requested by extension
879
- ext.process_method[doc, self, expanded_target, (doc.parse_attributes attrlist, [], :sub_input => true)]
1032
+ ext.process_method[doc, self, expanded_target, (doc.parse_attributes attrlist, [], sub_input: true)]
880
1033
  true
881
1034
  # if running in SafeMode::SECURE or greater, don't process this directive
882
1035
  # however, be friendly and at least make it a link to the source document
883
1036
  elsif doc.safe >= SafeMode::SECURE
884
1037
  # FIXME we don't want to use a link macro if we are in a verbatim context
885
1038
  replace_next_line %(link:#{expanded_target}[])
886
- elsif (abs_maxdepth = @maxdepth[:abs]) > 0
887
- if @include_stack.size >= abs_maxdepth
888
- logger.error message_with_context %(maximum include depth of #{@maxdepth[:rel]} exceeded), :source_location => cursor
1039
+ elsif @maxdepth
1040
+ if @include_stack.size >= @maxdepth[:curr]
1041
+ logger.error message_with_context %(maximum include depth of #{@maxdepth[:rel]} exceeded), source_location: cursor
889
1042
  return
890
1043
  end
891
1044
 
892
- parsed_attrs = doc.parse_attributes attrlist, [], :sub_input => true
1045
+ parsed_attrs = doc.parse_attributes attrlist, [], sub_input: true
893
1046
  inc_path, target_type, relpath = resolve_include_path expanded_target, attrlist, parsed_attrs
894
- return inc_path unless target_type
1047
+ case target_type
1048
+ when :file
1049
+ reader = ::File.method :open
1050
+ read_mode = FILE_READ_MODE
1051
+ when :uri
1052
+ reader = ::OpenURI.method :open_uri
1053
+ read_mode = URI_READ_MODE
1054
+ else
1055
+ # NOTE if target_type is not set, inc_path is a boolean to skip over (false) or reevaluate (true) the current line
1056
+ return inc_path
1057
+ end
1058
+
1059
+ if (enc = parsed_attrs['encoding']) && (::Encoding.find enc rescue nil)
1060
+ (read_mode_params = read_mode.split ':')[1] = enc
1061
+ read_mode = read_mode_params.join ':'
1062
+ end unless RUBY_ENGINE_OPAL
895
1063
 
896
1064
  inc_linenos = inc_tags = nil
1065
+ # NOTE attrlist is nil if missing from include directive
897
1066
  if attrlist
898
1067
  if parsed_attrs.key? 'lines'
899
1068
  inc_linenos = []
900
1069
  (split_delimited_value parsed_attrs['lines']).each do |linedef|
901
1070
  if linedef.include? '..'
902
- from, to = linedef.split '..', 2
903
- inc_linenos += (to.empty? || (to = to.to_i) < 0) ? [from.to_i, 1.0/0.0] : ::Range.new(from.to_i, to).to_a
1071
+ from, _, to = linedef.partition '..'
1072
+ inc_linenos += (to.empty? || (to = to.to_i) < 0) ? [from.to_i, ::Float::INFINITY] : (from.to_i..to).to_a
904
1073
  else
905
1074
  inc_linenos << linedef.to_i
906
1075
  end
@@ -926,7 +1095,7 @@ class PreprocessorReader < Reader
926
1095
  if inc_linenos
927
1096
  inc_lines, inc_offset, inc_lineno = [], nil, 0
928
1097
  begin
929
- open(inc_path, 'rb') do |f|
1098
+ reader.call inc_path, read_mode do |f|
930
1099
  select_remaining = nil
931
1100
  f.each_line do |l|
932
1101
  inc_lineno += 1
@@ -946,54 +1115,58 @@ class PreprocessorReader < Reader
946
1115
  end
947
1116
  end
948
1117
  rescue
949
- logger.error message_with_context %(include #{target_type} not readable: #{inc_path}), :source_location => cursor
1118
+ logger.error message_with_context %(include #{target_type} not readable: #{inc_path}), source_location: cursor
950
1119
  return replace_next_line %(Unresolved directive in #{@path} - include::#{expanded_target}[#{attrlist}])
951
1120
  end
952
1121
  shift
953
1122
  # FIXME not accounting for skipped lines in reader line numbering
954
1123
  if inc_offset
955
- parsed_attrs['partial-option'] = true
1124
+ parsed_attrs['partial-option'] = ''
956
1125
  push_include inc_lines, inc_path, relpath, inc_offset, parsed_attrs
957
1126
  end
958
1127
  elsif inc_tags
959
- inc_lines, inc_offset, inc_lineno, tag_stack, tags_used, active_tag = [], nil, 0, [], ::Set.new, nil
1128
+ inc_lines, inc_offset, inc_lineno, tag_stack, tags_selected, active_tag = [], nil, 0, [], ::Set.new, nil
960
1129
  if inc_tags.key? '**'
1130
+ select = base_select = inc_tags.delete '**'
961
1131
  if inc_tags.key? '*'
962
- select = base_select = (inc_tags.delete '**')
963
1132
  wildcard = inc_tags.delete '*'
1133
+ elsif !select && inc_tags.values.first == false
1134
+ wildcard = true
1135
+ end
1136
+ elsif inc_tags.key? '*'
1137
+ if inc_tags.keys.first == '*'
1138
+ select = base_select = !(wildcard = inc_tags.delete '*')
964
1139
  else
965
- select = base_select = wildcard = (inc_tags.delete '**')
1140
+ select = base_select = false
1141
+ wildcard = inc_tags.delete '*'
966
1142
  end
967
1143
  else
968
1144
  select = base_select = !(inc_tags.value? true)
969
- wildcard = inc_tags.delete '*'
970
1145
  end
971
1146
  begin
972
- open(inc_path, 'rb') do |f|
1147
+ reader.call inc_path, read_mode do |f|
973
1148
  dbl_co, dbl_sb = '::', '[]'
974
- encoding = ::Encoding::UTF_8 if COERCE_ENCODING
975
1149
  f.each_line do |l|
976
1150
  inc_lineno += 1
977
- # must force encoding since we're performing String operations on line
978
- l.force_encoding encoding if encoding
979
1151
  if (l.include? dbl_co) && (l.include? dbl_sb) && TagDirectiveRx =~ l
1152
+ this_tag = $2
980
1153
  if $1 # end tag
981
- if (this_tag = $2) == active_tag
1154
+ if this_tag == active_tag
982
1155
  tag_stack.pop
983
1156
  active_tag, select = tag_stack.empty? ? [nil, base_select] : tag_stack[-1]
984
1157
  elsif inc_tags.key? this_tag
985
1158
  include_cursor = create_include_cursor inc_path, expanded_target, inc_lineno
986
- if (idx = tag_stack.rindex {|key, _| key == this_tag })
1159
+ if (idx = tag_stack.rindex {|key,| key == this_tag })
987
1160
  idx == 0 ? tag_stack.shift : (tag_stack.delete_at idx)
988
- logger.warn message_with_context %(mismatched end tag (expected '#{active_tag}' but found '#{this_tag}') at line #{inc_lineno} of include #{target_type}: #{inc_path}), :source_location => cursor, :include_location => include_cursor
1161
+ logger.warn message_with_context %(mismatched end tag (expected '#{active_tag}' but found '#{this_tag}') at line #{inc_lineno} of include #{target_type}: #{inc_path}), source_location: cursor, include_location: include_cursor
989
1162
  else
990
- logger.warn message_with_context %(unexpected end tag '#{this_tag}' at line #{inc_lineno} of include #{target_type}: #{inc_path}), :source_location => cursor, :include_location => include_cursor
1163
+ logger.warn message_with_context %(unexpected end tag '#{this_tag}' at line #{inc_lineno} of include #{target_type}: #{inc_path}), source_location: cursor, include_location: include_cursor
991
1164
  end
992
1165
  end
993
- elsif inc_tags.key?(this_tag = $2)
994
- tags_used << this_tag
1166
+ elsif inc_tags.key? this_tag
1167
+ tags_selected << this_tag if (select = inc_tags[this_tag])
995
1168
  # QUESTION should we prevent tag from being selected when enclosing tag is excluded?
996
- tag_stack << [(active_tag = this_tag), (select = inc_tags[this_tag]), inc_lineno]
1169
+ tag_stack << [(active_tag = this_tag), select, inc_lineno]
997
1170
  elsif !wildcard.nil?
998
1171
  select = active_tag && !select ? false : wildcard
999
1172
  tag_stack << [(active_tag = this_tag), select, inc_lineno]
@@ -1006,31 +1179,31 @@ class PreprocessorReader < Reader
1006
1179
  end
1007
1180
  end
1008
1181
  rescue
1009
- logger.error message_with_context %(include #{target_type} not readable: #{inc_path}), :source_location => cursor
1182
+ logger.error message_with_context %(include #{target_type} not readable: #{inc_path}), source_location: cursor
1010
1183
  return replace_next_line %(Unresolved directive in #{@path} - include::#{expanded_target}[#{attrlist}])
1011
1184
  end
1012
1185
  unless tag_stack.empty?
1013
1186
  tag_stack.each do |tag_name, _, tag_lineno|
1014
- logger.warn message_with_context %(detected unclosed tag '#{tag_name}' starting at line #{tag_lineno} of include #{target_type}: #{inc_path}), :source_location => cursor, :include_location => (create_include_cursor inc_path, expanded_target, tag_lineno)
1187
+ logger.warn message_with_context %(detected unclosed tag '#{tag_name}' starting at line #{tag_lineno} of include #{target_type}: #{inc_path}), source_location: cursor, include_location: (create_include_cursor inc_path, expanded_target, tag_lineno)
1015
1188
  end
1016
1189
  end
1017
- unless (missing_tags = inc_tags.keys.to_a - tags_used.to_a).empty?
1018
- logger.warn message_with_context %(tag#{missing_tags.size > 1 ? 's' : ''} '#{missing_tags.join ', '}' not found in include #{target_type}: #{inc_path}), :source_location => cursor
1190
+ unless (missing_tags = inc_tags.keep_if {|_, v| v }.keys - tags_selected.to_a).empty?
1191
+ logger.warn message_with_context %(tag#{missing_tags.size > 1 ? 's' : ''} '#{missing_tags.join ', '}' not found in include #{target_type}: #{inc_path}), source_location: cursor
1019
1192
  end
1020
1193
  shift
1021
1194
  if inc_offset
1022
- parsed_attrs['partial-option'] = true unless base_select && wildcard && inc_tags.empty?
1195
+ parsed_attrs['partial-option'] = '' unless base_select && wildcard != false && inc_tags.empty?
1023
1196
  # FIXME not accounting for skipped lines in reader line numbering
1024
1197
  push_include inc_lines, inc_path, relpath, inc_offset, parsed_attrs
1025
1198
  end
1026
1199
  else
1027
1200
  begin
1028
- # NOTE read content first so that we only advance cursor if IO operation succeeds
1029
- inc_content = target_type == :file ? ::File.open(inc_path, 'rb') {|f| f.read } : open(inc_path, 'rb') {|f| f.read }
1201
+ # NOTE read content before shift so cursor is only advanced if IO operation succeeds
1202
+ inc_content = reader.call(inc_path, read_mode) {|f| f.read }
1030
1203
  shift
1031
1204
  push_include inc_content, inc_path, relpath, 1, parsed_attrs
1032
1205
  rescue
1033
- logger.error message_with_context %(include #{target_type} not readable: #{inc_path}), :source_location => cursor
1206
+ logger.error message_with_context %(include #{target_type} not readable: #{inc_path}), source_location: cursor
1034
1207
  return replace_next_line %(Unresolved directive in #{@path} - include::#{expanded_target}[#{attrlist}])
1035
1208
  end
1036
1209
  end
@@ -1064,20 +1237,21 @@ class PreprocessorReader < Reader
1064
1237
  # caching requires the open-uri-cached gem to be installed
1065
1238
  # processing will be automatically aborted if these libraries can't be opened
1066
1239
  Helpers.require_library 'open-uri/cached', 'open-uri-cached' unless defined? ::OpenURI::Cache
1067
- elsif !::RUBY_ENGINE_OPAL
1240
+ elsif !RUBY_ENGINE_OPAL
1068
1241
  # autoload open-uri
1069
1242
  ::OpenURI
1070
1243
  end
1071
1244
  [(::URI.parse target), :uri, target]
1072
1245
  else
1073
1246
  # include file is resolved relative to dir of current include, or base_dir if within original docfile
1074
- inc_path = doc.normalize_system_path target, @dir, nil, :target_name => 'include file'
1247
+ inc_path = doc.normalize_system_path target, @dir, nil, target_name: 'include file'
1075
1248
  unless ::File.file? inc_path
1076
- if attributes.key? 'optional-option'
1249
+ if attributes['optional-option']
1250
+ logger.info { message_with_context %(optional include dropped because include file not found: #{inc_path}), source_location: cursor }
1077
1251
  shift
1078
1252
  return true
1079
1253
  else
1080
- logger.error message_with_context %(include file not found: #{inc_path}), :source_location => cursor
1254
+ logger.error message_with_context %(include file not found: #{inc_path}), source_location: cursor
1081
1255
  return replace_next_line %(Unresolved directive in #{@path} - include::#{target}[#{attrlist}])
1082
1256
  end
1083
1257
  end
@@ -1088,97 +1262,8 @@ class PreprocessorReader < Reader
1088
1262
  end
1089
1263
  end
1090
1264
 
1091
- # Public: Push source onto the front of the reader and switch the context
1092
- # based on the file, document-relative path and line information given.
1093
- #
1094
- # This method is typically used in an IncludeProcessor to add source
1095
- # read from the target specified.
1096
- #
1097
- # Examples
1098
- #
1099
- # path = 'partial.adoc'
1100
- # file = File.expand_path path
1101
- # data = IO.read file
1102
- # reader.push_include data, file, path
1103
- #
1104
- # Returns this Reader object.
1105
- def push_include data, file = nil, path = nil, lineno = 1, attributes = {}
1106
- @include_stack << [@lines, @file, @dir, @path, @lineno, @maxdepth, @process_lines]
1107
- if (@file = file)
1108
- # NOTE if file is not a string, assume it's a URI
1109
- if ::String === file
1110
- @dir = ::File.dirname file
1111
- elsif ::RUBY_ENGINE_OPAL
1112
- @dir = ::URI.parse ::File.dirname(file = file.to_s)
1113
- else
1114
- # NOTE this intentionally throws an error if URI has no path
1115
- (@dir = file.dup).path = (dir = ::File.dirname file.path) == '/' ? '' : dir
1116
- file = file.to_s
1117
- end
1118
- path ||= ::File.basename file
1119
- # only process lines in AsciiDoc files
1120
- @process_lines = ASCIIDOC_EXTENSIONS[::File.extname file]
1121
- else
1122
- @dir = '.'
1123
- # we don't know what file type we have, so assume AsciiDoc
1124
- @process_lines = true
1125
- end
1126
-
1127
- if path
1128
- @path = path
1129
- @includes[Helpers.rootname path] = attributes['partial-option'] ? nil : true if @process_lines
1130
- else
1131
- @path = '<stdin>'
1132
- end
1133
-
1134
- @lineno = lineno
1135
-
1136
- if attributes.key? 'depth'
1137
- depth = attributes['depth'].to_i
1138
- depth = 1 if depth <= 0
1139
- @maxdepth = {:abs => (@include_stack.size - 1) + depth, :rel => depth}
1140
- end
1141
-
1142
- # effectively fill the buffer
1143
- if (@lines = prepare_lines data, :normalize => true, :condense => false, :indent => attributes['indent']).empty?
1144
- pop_include
1145
- else
1146
- # FIXME we eventually want to handle leveloffset without affecting the lines
1147
- if attributes.key? 'leveloffset'
1148
- @lines.unshift ''
1149
- @lines.unshift %(:leveloffset: #{attributes['leveloffset']})
1150
- @lines << ''
1151
- if (old_leveloffset = @document.attr 'leveloffset')
1152
- @lines << %(:leveloffset: #{old_leveloffset})
1153
- else
1154
- @lines << ':leveloffset!:'
1155
- end
1156
- # compensate for these extra lines
1157
- @lineno -= 2
1158
- end
1159
-
1160
- # FIXME kind of a hack
1161
- #Document::AttributeEntry.new('infile', @file).save_to_next_block @document
1162
- #Document::AttributeEntry.new('indir', @dir).save_to_next_block @document
1163
- @look_ahead = 0
1164
- end
1165
- self
1166
- end
1167
-
1168
- def create_include_cursor file, path, lineno
1169
- if ::String === file
1170
- dir = ::File.dirname file
1171
- elsif ::RUBY_ENGINE_OPAL
1172
- dir = ::File.dirname(file = file.to_s)
1173
- else
1174
- dir = (dir = ::File.dirname file.path) == '' ? '/' : dir
1175
- file = file.to_s
1176
- end
1177
- Cursor.new file, dir, path, lineno
1178
- end
1179
-
1180
1265
  def pop_include
1181
- if @include_stack.size > 0
1266
+ unless @include_stack.empty?
1182
1267
  @lines, @file, @dir, @path, @lineno, @maxdepth, @process_lines = @include_stack.pop
1183
1268
  # FIXME kind of a hack
1184
1269
  #Document::AttributeEntry.new('infile', @file).save_to_next_block @document
@@ -1188,30 +1273,6 @@ class PreprocessorReader < Reader
1188
1273
  end
1189
1274
  end
1190
1275
 
1191
- def include_depth
1192
- @include_stack.size
1193
- end
1194
-
1195
- def exceeded_max_depth?
1196
- if (abs_maxdepth = @maxdepth[:abs]) > 0 && @include_stack.size >= abs_maxdepth
1197
- @maxdepth[:rel]
1198
- else
1199
- false
1200
- end
1201
- end
1202
-
1203
- # TODO Document this override
1204
- # also, we now have the field in the super class, so perhaps
1205
- # just implement the logic there?
1206
- def shift
1207
- if @unescape_next_line
1208
- @unescape_next_line = false
1209
- (line = super).slice 1, line.length
1210
- else
1211
- super
1212
- end
1213
- end
1214
-
1215
1276
  # Private: Split delimited value on comma (if found), otherwise semi-colon
1216
1277
  def split_delimited_value val
1217
1278
  (val.include? ',') ? (val.split ',') : (val.split ';')
@@ -1284,7 +1345,7 @@ class PreprocessorReader < Reader
1284
1345
 
1285
1346
  # QUESTION should we substitute first?
1286
1347
  # QUESTION should we also require string to be single quoted (like block attribute values?)
1287
- val = @document.sub_attributes val, :attribute_missing => 'drop' if val.include? ATTR_REF_HEAD
1348
+ val = @document.sub_attributes val, attribute_missing: 'drop' if val.include? ATTR_REF_HEAD
1288
1349
 
1289
1350
  if quoted
1290
1351
  val
@@ -1304,21 +1365,5 @@ class PreprocessorReader < Reader
1304
1365
  val.to_i
1305
1366
  end
1306
1367
  end
1307
-
1308
- def include_processors?
1309
- if @include_processor_extensions.nil?
1310
- if @document.extensions? && @document.extensions.include_processors?
1311
- !!(@include_processor_extensions = @document.extensions.include_processors)
1312
- else
1313
- @include_processor_extensions = false
1314
- end
1315
- else
1316
- @include_processor_extensions != false
1317
- end
1318
- end
1319
-
1320
- def to_s
1321
- %(#<#{self.class}@#{object_id} {path: #{@path.inspect}, line #: #{@lineno}, include depth: #{@include_stack.size}, include stack: [#{@include_stack.map {|inc| inc.to_s }.join ', '}]}>)
1322
- end
1323
1368
  end
1324
1369
  end