"Fossies" - the Fresh Open Source Software Archive

Member "asciidoctor-2.0.10/test/reader_test.rb" (1 Jun 2019, 81248 Bytes) of package /linux/www/asciidoctor-2.0.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Ruby source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "reader_test.rb": 2.0.5_vs_2.0.6.

    1 # frozen_string_literal: true
    2 require_relative 'test_helper'
    3 
    4 class ReaderTest < Minitest::Test
    5   DIRNAME = ASCIIDOCTOR_TEST_DIR
    6 
    7   SAMPLE_DATA = ['first line', 'second line', 'third line']
    8 
    9   context 'Reader' do
   10     context 'Prepare lines' do
   11       test 'should prepare lines from Array data' do
   12         reader = Asciidoctor::Reader.new SAMPLE_DATA
   13         assert_equal SAMPLE_DATA, reader.lines
   14       end
   15 
   16       test 'should prepare lines from String data' do
   17         reader = Asciidoctor::Reader.new SAMPLE_DATA.join(Asciidoctor::LF)
   18         assert_equal SAMPLE_DATA, reader.lines
   19       end
   20 
   21       test 'should remove UTF-8 BOM from first line of String data' do
   22         ['UTF-8', 'ASCII-8BIT'].each do |start_encoding|
   23           data = String.new %(\xef\xbb\xbf#{SAMPLE_DATA.join ::Asciidoctor::LF}), encoding: start_encoding
   24           reader = Asciidoctor::Reader.new data, nil, normalize: true
   25           assert_equal Encoding::UTF_8, reader.lines[0].encoding
   26           assert_equal 'f', reader.lines[0].chr
   27           assert_equal SAMPLE_DATA, reader.lines
   28         end
   29       end
   30 
   31       test 'should remove UTF-8 BOM from first line of Array data' do
   32         ['UTF-8', 'ASCII-8BIT'].each do |start_encoding|
   33           data = SAMPLE_DATA.drop 0
   34           data[0] = String.new %(\xef\xbb\xbf#{data.first}), encoding: start_encoding
   35           reader = Asciidoctor::Reader.new data, nil, normalize: true
   36           assert_equal Encoding::UTF_8, reader.lines[0].encoding
   37           assert_equal 'f', reader.lines[0].chr
   38           assert_equal SAMPLE_DATA, reader.lines
   39         end
   40       end
   41 
   42       test 'should encode UTF-16LE string to UTF-8 when BOM is found' do
   43         ['UTF-8', 'ASCII-8BIT'].each do |start_encoding|
   44           data = "\ufeff#{SAMPLE_DATA.join ::Asciidoctor::LF}".encode('UTF-16LE').force_encoding(start_encoding)
   45           reader = Asciidoctor::Reader.new data, nil, normalize: true
   46           assert_equal Encoding::UTF_8, reader.lines[0].encoding
   47           assert_equal 'f', reader.lines[0].chr
   48           assert_equal SAMPLE_DATA, reader.lines
   49         end
   50       end
   51 
   52       test 'should encode UTF-16LE string array to UTF-8 when BOM is found' do
   53         ['UTF-8', 'ASCII-8BIT'].each do |start_encoding|
   54           # NOTE can't split a UTF-16LE string using .lines when encoding is set to UTF-8
   55           data = SAMPLE_DATA.drop 0
   56           data.unshift %(\ufeff#{data.shift})
   57           data.each {|line| (line.encode 'UTF-16LE').force_encoding start_encoding }
   58           reader = Asciidoctor::Reader.new data, nil, normalize: true
   59           assert_equal Encoding::UTF_8, reader.lines[0].encoding
   60           assert_equal 'f', reader.lines[0].chr
   61           assert_equal SAMPLE_DATA, reader.lines
   62         end
   63       end
   64 
   65       test 'should encode UTF-16BE string to UTF-8 when BOM is found' do
   66         ['UTF-8', 'ASCII-8BIT'].each do |start_encoding|
   67           data = "\ufeff#{SAMPLE_DATA.join ::Asciidoctor::LF}".encode('UTF-16BE').force_encoding(start_encoding)
   68           reader = Asciidoctor::Reader.new data, nil, normalize: true
   69           assert_equal Encoding::UTF_8, reader.lines[0].encoding
   70           assert_equal 'f', reader.lines[0].chr
   71           assert_equal SAMPLE_DATA, reader.lines
   72         end
   73       end
   74 
   75       test 'should encode UTF-16BE string array to UTF-8 when BOM is found' do
   76         ['UTF-8', 'ASCII-8BIT'].each do |start_encoding|
   77           data = SAMPLE_DATA.drop 0
   78           data.unshift %(\ufeff#{data.shift})
   79           data = data.map {|line| (line.encode 'UTF-16BE').force_encoding start_encoding }
   80           reader = Asciidoctor::Reader.new data, nil, normalize: true
   81           assert_equal Encoding::UTF_8, reader.lines[0].encoding
   82           assert_equal 'f', reader.lines[0].chr
   83           assert_equal SAMPLE_DATA, reader.lines
   84         end
   85       end
   86     end
   87 
   88     context 'With empty data' do
   89       test 'has_more_lines? should return false with empty data' do
   90         refute Asciidoctor::Reader.new.has_more_lines?
   91       end
   92 
   93       test 'empty? should return true with empty data' do
   94         assert Asciidoctor::Reader.new.empty?
   95         assert Asciidoctor::Reader.new.eof?
   96       end
   97 
   98       test 'next_line_empty? should return true with empty data' do
   99         assert Asciidoctor::Reader.new.next_line_empty?
  100       end
  101 
  102       test 'peek_line should return nil with empty data' do
  103         assert_nil Asciidoctor::Reader.new.peek_line
  104       end
  105 
  106       test 'peek_lines should return empty Array with empty data' do
  107         assert_equal [], Asciidoctor::Reader.new.peek_lines(1)
  108       end
  109 
  110       test 'read_line should return nil with empty data' do
  111         assert_nil Asciidoctor::Reader.new.read_line
  112         #assert_nil Asciidoctor::Reader.new.get_line
  113       end
  114 
  115       test 'read_lines should return empty Array with empty data' do
  116         assert_equal [], Asciidoctor::Reader.new.read_lines
  117         #assert_equal [], Asciidoctor::Reader.new.get_lines
  118       end
  119     end
  120 
  121     context 'With data' do
  122       test 'has_more_lines? should return true if there are lines remaining' do
  123         reader = Asciidoctor::Reader.new SAMPLE_DATA
  124         assert reader.has_more_lines?
  125       end
  126 
  127       test 'empty? should return false if there are lines remaining' do
  128         reader = Asciidoctor::Reader.new SAMPLE_DATA
  129         refute reader.empty?
  130         refute reader.eof?
  131       end
  132 
  133       test 'next_line_empty? should return false if next line is not blank' do
  134         reader = Asciidoctor::Reader.new SAMPLE_DATA
  135         refute reader.next_line_empty?
  136       end
  137 
  138       test 'next_line_empty? should return true if next line is blank' do
  139         reader = Asciidoctor::Reader.new ['', 'second line']
  140         assert reader.next_line_empty?
  141       end
  142 
  143       test 'peek_line should return next line if there are lines remaining' do
  144         reader = Asciidoctor::Reader.new SAMPLE_DATA
  145         assert_equal SAMPLE_DATA.first, reader.peek_line
  146       end
  147 
  148       test 'peek_line should not consume line or increment line number' do
  149         reader = Asciidoctor::Reader.new SAMPLE_DATA
  150         assert_equal SAMPLE_DATA.first, reader.peek_line
  151         assert_equal SAMPLE_DATA.first, reader.peek_line
  152         assert_equal 1, reader.lineno
  153       end
  154 
  155       test 'peek_line should return next lines if there are lines remaining' do
  156         reader = Asciidoctor::Reader.new SAMPLE_DATA
  157         assert_equal SAMPLE_DATA[0..1], reader.peek_lines(2)
  158       end
  159 
  160       test 'peek_lines should not consume lines or increment line number' do
  161         reader = Asciidoctor::Reader.new SAMPLE_DATA
  162         assert_equal SAMPLE_DATA[0..1], reader.peek_lines(2)
  163         assert_equal SAMPLE_DATA[0..1], reader.peek_lines(2)
  164         assert_equal 1, reader.lineno
  165       end
  166 
  167       test 'peek_lines should not increment line number if reader overruns buffer' do
  168         reader = Asciidoctor::Reader.new SAMPLE_DATA
  169         assert_equal SAMPLE_DATA, (reader.peek_lines SAMPLE_DATA.size * 2)
  170         assert_equal 1, reader.lineno
  171       end
  172 
  173       test 'peek_lines should peek all lines if no arguments are given' do
  174         reader = Asciidoctor::Reader.new SAMPLE_DATA
  175         assert_equal SAMPLE_DATA, reader.peek_lines
  176         assert_equal 1, reader.lineno
  177       end
  178 
  179       test 'peek_lines should not invert order of lines' do
  180         reader = Asciidoctor::Reader.new SAMPLE_DATA
  181         assert_equal SAMPLE_DATA, reader.lines
  182         reader.peek_lines 3
  183         assert_equal SAMPLE_DATA, reader.lines
  184       end
  185 
  186       test 'read_line should return next line if there are lines remaining' do
  187         reader = Asciidoctor::Reader.new SAMPLE_DATA
  188         assert_equal SAMPLE_DATA.first, reader.read_line
  189       end
  190 
  191       test 'read_line should consume next line and increment line number' do
  192         reader = Asciidoctor::Reader.new SAMPLE_DATA
  193         assert_equal SAMPLE_DATA[0], reader.read_line
  194         assert_equal SAMPLE_DATA[1], reader.read_line
  195         assert_equal 3, reader.lineno
  196       end
  197 
  198       test 'advance should consume next line and return a Boolean indicating if a line was consumed' do
  199         reader = Asciidoctor::Reader.new SAMPLE_DATA
  200         assert reader.advance
  201         assert reader.advance
  202         assert reader.advance
  203         refute reader.advance
  204       end
  205 
  206       test 'read_lines should return all lines' do
  207         reader = Asciidoctor::Reader.new SAMPLE_DATA
  208         assert_equal SAMPLE_DATA, reader.read_lines
  209       end
  210 
  211       test 'read should return all lines joined as String' do
  212         reader = Asciidoctor::Reader.new SAMPLE_DATA
  213         assert_equal SAMPLE_DATA.join(::Asciidoctor::LF), reader.read
  214       end
  215 
  216       test 'has_more_lines? should return false after read_lines is invoked' do
  217         reader = Asciidoctor::Reader.new SAMPLE_DATA
  218         reader.read_lines
  219         refute reader.has_more_lines?
  220       end
  221 
  222       test 'unshift puts line onto Reader as next line to read' do
  223         reader = Asciidoctor::Reader.new SAMPLE_DATA, nil, normalize: true
  224         reader.unshift 'line zero'
  225         assert_equal 'line zero', reader.peek_line
  226         assert_equal 'line zero', reader.read_line
  227         assert_equal 1, reader.lineno
  228       end
  229 
  230       test 'terminate should consume all lines and update line number' do
  231         reader = Asciidoctor::Reader.new SAMPLE_DATA
  232         reader.terminate
  233         assert reader.eof?
  234         assert_equal 4, reader.lineno
  235       end
  236 
  237       test 'skip_blank_lines should skip blank lines' do
  238         reader = Asciidoctor::Reader.new ['', ''].concat(SAMPLE_DATA)
  239         reader.skip_blank_lines
  240         assert_equal SAMPLE_DATA.first, reader.peek_line
  241       end
  242 
  243       test 'lines should return remaining lines' do
  244         reader = Asciidoctor::Reader.new SAMPLE_DATA
  245         reader.read_line
  246         assert_equal SAMPLE_DATA[1..-1], reader.lines
  247       end
  248 
  249       test 'source_lines should return copy of original data Array' do
  250         reader = Asciidoctor::Reader.new SAMPLE_DATA
  251         reader.read_lines
  252         assert_equal SAMPLE_DATA, reader.source_lines
  253       end
  254 
  255       test 'source should return original data Array joined as String' do
  256         reader = Asciidoctor::Reader.new SAMPLE_DATA
  257         reader.read_lines
  258         assert_equal SAMPLE_DATA.join(::Asciidoctor::LF), reader.source
  259       end
  260 
  261     end
  262 
  263     context 'Line context' do
  264       test 'cursor.to_s should return file name and line number of current line' do
  265         reader = Asciidoctor::Reader.new SAMPLE_DATA, 'sample.adoc'
  266         reader.read_line
  267         assert_equal 'sample.adoc: line 2', reader.cursor.to_s
  268       end
  269 
  270       test 'line_info should return file name and line number of current line' do
  271         reader = Asciidoctor::Reader.new SAMPLE_DATA, 'sample.adoc'
  272         reader.read_line
  273         assert_equal 'sample.adoc: line 2', reader.line_info
  274       end
  275 
  276       test 'cursor_at_prev_line should return file name and line number of previous line read' do
  277         reader = Asciidoctor::Reader.new SAMPLE_DATA, 'sample.adoc'
  278         reader.read_line
  279         assert_equal 'sample.adoc: line 1', reader.cursor_at_prev_line.to_s
  280       end
  281     end
  282 
  283     context 'Read lines until' do
  284       test 'Read lines until until end' do
  285         lines = <<~'EOS'.lines
  286         This is one paragraph.
  287 
  288         This is another paragraph.
  289         EOS
  290 
  291         reader = Asciidoctor::Reader.new lines, nil, normalize: true
  292         result = reader.read_lines_until
  293         assert_equal 3, result.size
  294         assert_equal lines.map(&:chomp), result
  295         refute reader.has_more_lines?
  296         assert reader.eof?
  297       end
  298 
  299       test 'Read lines until until blank line' do
  300         lines = <<~'EOS'.lines
  301         This is one paragraph.
  302 
  303         This is another paragraph.
  304         EOS
  305 
  306         reader = Asciidoctor::Reader.new lines, nil, normalize: true
  307         result = reader.read_lines_until break_on_blank_lines: true
  308         assert_equal 1, result.size
  309         assert_equal lines.first.chomp, result.first
  310         assert_equal lines.last.chomp, reader.peek_line
  311       end
  312 
  313       test 'Read lines until until blank line preserving last line' do
  314         lines = <<~'EOS'.split ::Asciidoctor::LF
  315         This is one paragraph.
  316 
  317         This is another paragraph.
  318         EOS
  319 
  320         reader = Asciidoctor::Reader.new lines
  321         result = reader.read_lines_until break_on_blank_lines: true, preserve_last_line: true
  322         assert_equal 1, result.size
  323         assert_equal lines.first.chomp, result.first
  324         assert reader.next_line_empty?
  325       end
  326 
  327       test 'Read lines until until condition is true' do
  328         lines = <<~'EOS'.split ::Asciidoctor::LF
  329         --
  330         This is one paragraph inside the block.
  331 
  332         This is another paragraph inside the block.
  333         --
  334 
  335         This is a paragraph outside the block.
  336         EOS
  337 
  338         reader = Asciidoctor::Reader.new lines
  339         reader.read_line
  340         result = reader.read_lines_until {|line| line == '--' }
  341         assert_equal 3, result.size
  342         assert_equal lines[1, 3], result
  343         assert reader.next_line_empty?
  344       end
  345 
  346       test 'Read lines until until condition is true, taking last line' do
  347         lines = <<~'EOS'.split ::Asciidoctor::LF
  348         --
  349         This is one paragraph inside the block.
  350 
  351         This is another paragraph inside the block.
  352         --
  353 
  354         This is a paragraph outside the block.
  355         EOS
  356 
  357         reader = Asciidoctor::Reader.new lines
  358         reader.read_line
  359         result = reader.read_lines_until(read_last_line: true) {|line| line == '--' }
  360         assert_equal 4, result.size
  361         assert_equal lines[1, 4], result
  362         assert reader.next_line_empty?
  363       end
  364 
  365       test 'Read lines until until condition is true, taking and preserving last line' do
  366         lines = <<~'EOS'.split ::Asciidoctor::LF
  367         --
  368         This is one paragraph inside the block.
  369 
  370         This is another paragraph inside the block.
  371         --
  372 
  373         This is a paragraph outside the block.
  374         EOS
  375 
  376         reader = Asciidoctor::Reader.new lines
  377         reader.read_line
  378         result = reader.read_lines_until(read_last_line: true, preserve_last_line: true) {|line| line == '--' }
  379         assert_equal 4, result.size
  380         assert_equal lines[1, 4], result
  381         assert_equal '--', reader.peek_line
  382       end
  383 
  384       test 'read lines until terminator' do
  385         lines = <<~'EOS'.lines
  386         ****
  387         captured
  388 
  389         also captured
  390         ****
  391 
  392         not captured
  393         EOS
  394 
  395         expected = ['captured', '', 'also captured']
  396 
  397         doc = empty_safe_document base_dir: DIRNAME
  398         reader = Asciidoctor::PreprocessorReader.new doc, lines, nil, normalize: true
  399         terminator = reader.read_line
  400         result = reader.read_lines_until terminator: terminator, skip_processing: true
  401         assert_equal expected, result
  402         refute reader.unterminated
  403       end
  404 
  405       test 'should flag reader as unterminated if reader reaches end of source without finding terminator' do
  406         lines = <<~'EOS'.lines
  407         ****
  408         captured
  409 
  410         also captured
  411 
  412         captured yet again
  413         EOS
  414 
  415         expected = lines[1..-1].map(&:chomp)
  416 
  417         using_memory_logger do |logger|
  418           doc = empty_safe_document base_dir: DIRNAME
  419           reader = Asciidoctor::PreprocessorReader.new doc, lines, nil, normalize: true
  420           terminator = reader.peek_line
  421           result = reader.read_lines_until terminator: terminator, skip_first_line: true, skip_processing: true
  422           assert_equal expected, result
  423           assert reader.unterminated
  424           assert_message logger, :WARN, '<stdin>: line 1: unterminated **** block', Hash
  425         end
  426       end
  427     end
  428   end
  429 
  430   context 'PreprocessorReader' do
  431     context 'Type hierarchy' do
  432       test 'PreprocessorReader should extend from Reader' do
  433         reader = empty_document.reader
  434         assert_kind_of Asciidoctor::PreprocessorReader, reader
  435       end
  436 
  437       test 'PreprocessorReader should invoke or emulate Reader initializer' do
  438         doc = Asciidoctor::Document.new SAMPLE_DATA
  439         reader = doc.reader
  440         assert_equal SAMPLE_DATA, reader.lines
  441         assert_equal 1, reader.lineno
  442       end
  443     end
  444 
  445     context 'Prepare lines' do
  446       test 'should prepare and normalize lines from Array data' do
  447         data = SAMPLE_DATA.drop 0
  448         data.unshift ''
  449         data.push ''
  450         doc = Asciidoctor::Document.new data
  451         reader = doc.reader
  452         assert_equal SAMPLE_DATA, reader.lines
  453       end
  454 
  455       test 'should prepare and normalize lines from String data' do
  456         data = SAMPLE_DATA.drop 0
  457         data.unshift ' '
  458         data.push ' '
  459         data_as_string = data * ::Asciidoctor::LF
  460         doc = Asciidoctor::Document.new data_as_string
  461         reader = doc.reader
  462         assert_equal SAMPLE_DATA, reader.lines
  463       end
  464 
  465       test 'should clean CRLF from end of lines' do
  466         input = <<~EOS
  467         source\r
  468         with\r
  469         CRLF\r
  470         line endings\r
  471         EOS
  472 
  473         [input, input.lines, input.split(::Asciidoctor::LF), input.split(::Asciidoctor::LF).join(::Asciidoctor::LF)].each do |lines|
  474           doc = Asciidoctor::Document.new lines
  475           reader = doc.reader
  476           reader.lines.each do |line|
  477             refute line.end_with?("\r"), "CRLF not properly cleaned for source lines: #{lines.inspect}"
  478             refute line.end_with?("\r\n"), "CRLF not properly cleaned for source lines: #{lines.inspect}"
  479             refute line.end_with?("\n"), "CRLF not properly cleaned for source lines: #{lines.inspect}"
  480           end
  481         end
  482       end
  483 
  484       test 'should not skip front matter by default' do
  485         input = <<~'EOS'
  486         ---
  487         layout: post
  488         title: Document Title
  489         author: username
  490         tags: [ first, second ]
  491         ---
  492         = Document Title
  493         Author Name
  494 
  495         preamble
  496         EOS
  497 
  498         doc = Asciidoctor::Document.new input
  499         reader = doc.reader
  500         refute doc.attributes.key?('front-matter')
  501         assert_equal '---', reader.peek_line
  502       end
  503 
  504       test 'should skip front matter if specified by skip-front-matter attribute' do
  505         front_matter = <<~'EOS'.chop
  506         layout: post
  507         title: Document Title
  508         author: username
  509         tags: [ first, second ]
  510         EOS
  511 
  512         input = <<~EOS
  513         ---
  514         #{front_matter}
  515         ---
  516         = Document Title
  517         Author Name
  518 
  519         preamble
  520         EOS
  521 
  522         doc = Asciidoctor::Document.new input, attributes: { 'skip-front-matter' => '' }
  523         reader = doc.reader
  524         assert_equal '= Document Title', reader.peek_line
  525         assert_equal front_matter, doc.attributes['front-matter']
  526       end
  527     end
  528 
  529     context 'Include Stack' do
  530       test 'PreprocessorReader#push_include method should return reader' do
  531         reader = empty_document.reader
  532         append_lines = %w(one two three)
  533         result = reader.push_include append_lines, '<stdin>', '<stdin>'
  534         assert_equal reader, result
  535       end
  536 
  537       test 'PreprocessorReader#push_include method should put lines on top of stack' do
  538         lines = %w(a b c)
  539         doc = Asciidoctor::Document.new lines
  540         reader = doc.reader
  541         append_lines = %w(one two three)
  542         reader.push_include append_lines, '', '<stdin>'
  543         assert_equal 1, reader.include_stack.size
  544         assert_equal 'one', reader.read_line.rstrip
  545       end
  546 
  547       test 'PreprocessorReader#push_include method should gracefully handle file and path' do
  548         lines = %w(a b c)
  549         doc = Asciidoctor::Document.new lines
  550         reader = doc.reader
  551         append_lines = %w(one two three)
  552         reader.push_include append_lines
  553         assert_equal 1, reader.include_stack.size
  554         assert_equal 'one', reader.read_line.rstrip
  555         assert_nil reader.file
  556         assert_equal '<stdin>', reader.path
  557       end
  558 
  559       test 'PreprocessorReader#push_include method should set path from file automatically if not specified' do
  560         lines = %w(a b c)
  561         doc = Asciidoctor::Document.new lines
  562         reader = doc.reader
  563         append_lines = %w(one two three)
  564         reader.push_include append_lines, '/tmp/lines.adoc'
  565         assert_equal '/tmp/lines.adoc', reader.file
  566         assert_equal 'lines.adoc', reader.path
  567         assert doc.catalog[:includes]['lines']
  568       end
  569 
  570       test 'PreprocessorReader#push_include method should accept file as a URI and compute dir and path' do
  571         file_uri = ::URI.parse 'http://example.com/docs/file.adoc'
  572         dir_uri = ::URI.parse 'http://example.com/docs'
  573         reader = empty_document.reader
  574         reader.push_include %w(one two three), file_uri
  575         assert_same file_uri, reader.file
  576         assert_equal dir_uri, reader.dir
  577         assert_equal 'file.adoc', reader.path
  578       end
  579 
  580       test 'PreprocessorReader#push_include method should accept file as a top-level URI and compute dir and path' do
  581         file_uri = ::URI.parse 'http://example.com/index.adoc'
  582         dir_uri = ::URI.parse 'http://example.com'
  583         reader = empty_document.reader
  584         reader.push_include %w(one two three), file_uri
  585         assert_same file_uri, reader.file
  586         assert_equal dir_uri, reader.dir
  587         assert_equal 'index.adoc', reader.path
  588       end
  589 
  590       test 'PreprocessorReader#push_include method should not fail if data is nil' do
  591         lines = %w(a b c)
  592         doc = Asciidoctor::Document.new lines
  593         reader = doc.reader
  594         reader.push_include nil, '', '<stdin>'
  595         assert_equal 0, reader.include_stack.size
  596         assert_equal 'a', reader.read_line.rstrip
  597       end
  598 
  599       test 'PreprocessorReader#push_include method should ignore dot in directory name when computing include path' do
  600         lines = %w(a b c)
  601         doc = Asciidoctor::Document.new lines
  602         reader = doc.reader
  603         append_lines = %w(one two three)
  604         reader.push_include append_lines, nil, 'include.d/data'
  605         assert_nil reader.file
  606         assert_equal 'include.d/data', reader.path
  607         assert doc.catalog[:includes]['include.d/data']
  608       end
  609     end
  610 
  611     context 'Include Directive' do
  612       test 'include directive is disabled by default and becomes a link' do
  613         input = 'include::include-file.adoc[]'
  614         doc = Asciidoctor::Document.new input
  615         reader = doc.reader
  616         assert_equal 'link:include-file.adoc[]', reader.read_line
  617       end
  618 
  619       test 'include directive is enabled when safe mode is less than SECURE' do
  620         input = 'include::fixtures/include-file.adoc[]'
  621         doc = document_from_string input, safe: :safe, standalone: false, base_dir: DIRNAME
  622         output = doc.convert
  623         assert_match(/included content/, output)
  624         assert doc.catalog[:includes]['fixtures/include-file']
  625       end
  626 
  627       test 'should not track include in catalog for non-AsciiDoc include files' do
  628         input = <<~'EOS'
  629         ----
  630         include::fixtures/circle.svg[]
  631         ----
  632         EOS
  633 
  634         doc = document_from_string input, safe: :safe, standalone: false, base_dir: DIRNAME
  635         assert doc.catalog[:includes].empty?
  636       end
  637 
  638       test 'include directive should resolve file with spaces in name' do
  639         input = 'include::fixtures/include file.adoc[]'
  640         include_file = File.join DIRNAME, 'fixtures', 'include-file.adoc'
  641         include_file_with_sp = File.join DIRNAME, 'fixtures', 'include file.adoc'
  642         begin
  643           FileUtils.cp include_file, include_file_with_sp
  644           doc = document_from_string input, safe: :safe, standalone: false, base_dir: DIRNAME
  645           output = doc.convert
  646           assert_match(/included content/, output)
  647         ensure
  648           FileUtils.rm include_file_with_sp
  649         end
  650       end
  651 
  652       test 'include directive should resolve file with {sp} in name' do
  653         input = 'include::fixtures/include{sp}file.adoc[]'
  654         include_file = File.join DIRNAME, 'fixtures', 'include-file.adoc'
  655         include_file_with_sp = File.join DIRNAME, 'fixtures', 'include file.adoc'
  656         begin
  657           FileUtils.cp include_file, include_file_with_sp
  658           doc = document_from_string input, safe: :safe, standalone: false, base_dir: DIRNAME
  659           output = doc.convert
  660           assert_match(/included content/, output)
  661         ensure
  662           FileUtils.rm include_file_with_sp
  663         end
  664       end
  665 
  666       test 'include directive should resolve file relative to current include' do
  667         input = 'include::fixtures/parent-include.adoc[]'
  668         pseudo_docfile = File.join DIRNAME, 'include-master.adoc'
  669         fixtures_dir = File.join DIRNAME, 'fixtures'
  670         parent_include_docfile = File.join fixtures_dir, 'parent-include.adoc'
  671         child_include_docfile = File.join fixtures_dir, 'child-include.adoc'
  672         grandchild_include_docfile = File.join fixtures_dir, 'grandchild-include.adoc'
  673 
  674         doc = empty_safe_document base_dir: DIRNAME
  675         reader = Asciidoctor::PreprocessorReader.new doc, input, pseudo_docfile, normalize: true
  676 
  677         assert_equal pseudo_docfile, reader.file
  678         assert_equal DIRNAME, reader.dir
  679         assert_equal 'include-master.adoc', reader.path
  680 
  681         assert_equal 'first line of parent', reader.read_line
  682 
  683         assert_equal 'fixtures/parent-include.adoc: line 1', reader.cursor_at_prev_line.to_s
  684         assert_equal parent_include_docfile, reader.file
  685         assert_equal fixtures_dir, reader.dir
  686         assert_equal 'fixtures/parent-include.adoc', reader.path
  687 
  688         reader.skip_blank_lines
  689 
  690         assert_equal 'first line of child', reader.read_line
  691 
  692         assert_equal 'fixtures/child-include.adoc: line 1', reader.cursor_at_prev_line.to_s
  693         assert_equal child_include_docfile, reader.file
  694         assert_equal fixtures_dir, reader.dir
  695         assert_equal 'fixtures/child-include.adoc', reader.path
  696 
  697         reader.skip_blank_lines
  698 
  699         assert_equal 'first line of grandchild', reader.read_line
  700 
  701         assert_equal 'fixtures/grandchild-include.adoc: line 1', reader.cursor_at_prev_line.to_s
  702         assert_equal grandchild_include_docfile, reader.file
  703         assert_equal fixtures_dir, reader.dir
  704         assert_equal 'fixtures/grandchild-include.adoc', reader.path
  705 
  706         reader.skip_blank_lines
  707 
  708         assert_equal 'last line of grandchild', reader.read_line
  709 
  710         reader.skip_blank_lines
  711 
  712         assert_equal 'last line of child', reader.read_line
  713 
  714         reader.skip_blank_lines
  715 
  716         assert_equal 'last line of parent', reader.read_line
  717 
  718         assert_equal 'fixtures/parent-include.adoc: line 5', reader.cursor_at_prev_line.to_s
  719         assert_equal parent_include_docfile, reader.file
  720         assert_equal fixtures_dir, reader.dir
  721         assert_equal 'fixtures/parent-include.adoc', reader.path
  722       end
  723 
  724       test 'include directive should process lines when file extension of target is .asciidoc' do
  725         input = 'include::fixtures/include-alt-extension.asciidoc[]'
  726         doc = document_from_string input, safe: :safe, base_dir: DIRNAME
  727         assert_equal 3, doc.blocks.size
  728         assert_equal ['first line'], doc.blocks[0].lines
  729         assert_equal ['Asciidoctor!'], doc.blocks[1].lines
  730         assert_equal ['last line'], doc.blocks[2].lines
  731       end
  732 
  733       test 'unresolved target referenced by include directive is skipped when optional option is set' do
  734         input = <<~'EOS'
  735         include::fixtures/{no-such-file}[opts=optional]
  736 
  737         trailing content
  738         EOS
  739 
  740         begin
  741           using_memory_logger do |logger|
  742             doc = document_from_string input, safe: :safe, base_dir: DIRNAME
  743             assert_equal 1, doc.blocks.size
  744             assert_equal ['trailing content'], doc.blocks[0].lines
  745             assert_message logger, :INFO, '~<stdin>: line 1: optional include dropped because include file not found', Hash
  746           end
  747         rescue
  748           flunk 'include directive should not raise exception on unresolved target'
  749         end
  750       end
  751 
  752       test 'missing file referenced by include directive is skipped when optional option is set' do
  753         input = <<~'EOS'
  754         include::fixtures/no-such-file.adoc[opts=optional]
  755 
  756         trailing content
  757         EOS
  758 
  759         begin
  760           using_memory_logger do |logger|
  761             doc = document_from_string input, safe: :safe, base_dir: DIRNAME
  762             assert_equal 1, doc.blocks.size
  763             assert_equal ['trailing content'], doc.blocks[0].lines
  764             assert_message logger, :INFO, '~<stdin>: line 1: optional include dropped because include file not found', Hash
  765           end
  766         rescue
  767           flunk 'include directive should not raise exception on missing file'
  768         end
  769       end
  770 
  771       test 'missing file referenced by include directive is replaced by warning' do
  772         input = <<~'EOS'
  773         include::fixtures/no-such-file.adoc[]
  774 
  775         trailing content
  776         EOS
  777 
  778         begin
  779           using_memory_logger do |logger|
  780             doc = document_from_string input, safe: :safe, base_dir: DIRNAME
  781             assert_equal 2, doc.blocks.size
  782             assert_equal ['Unresolved directive in <stdin> - include::fixtures/no-such-file.adoc[]'], doc.blocks[0].lines
  783             assert_equal ['trailing content'], doc.blocks[1].lines
  784             assert_message logger, :ERROR, '~<stdin>: line 1: include file not found', Hash
  785           end
  786         rescue
  787           flunk 'include directive should not raise exception on missing file'
  788         end
  789       end
  790 
  791       test 'unreadable file referenced by include directive is replaced by warning' do
  792         include_file = File.join DIRNAME, 'fixtures', 'chapter-a.adoc'
  793         FileUtils.chmod 0000, include_file
  794         input = <<~'EOS'
  795         include::fixtures/chapter-a.adoc[]
  796 
  797         trailing content
  798         EOS
  799 
  800         begin
  801           using_memory_logger do |logger|
  802             doc = document_from_string input, safe: :safe, base_dir: DIRNAME
  803             assert_equal 2, doc.blocks.size
  804             assert_equal ['Unresolved directive in <stdin> - include::fixtures/chapter-a.adoc[]'], doc.blocks[0].lines
  805             assert_equal ['trailing content'], doc.blocks[1].lines
  806             assert_message logger, :ERROR, '~<stdin>: line 1: include file not readable', Hash
  807           end
  808         rescue
  809           flunk 'include directive should not raise exception on missing file'
  810         ensure
  811           FileUtils.chmod 0644, include_file
  812         end
  813       end unless windows?
  814 
  815       # IMPORTANT this test needs to be run on Windows to verify proper behavior in Windows
  816       test 'can resolve include directive with absolute path' do
  817         include_path = ::File.join DIRNAME, 'fixtures', 'chapter-a.adoc'
  818         input = %(include::#{include_path}[])
  819         result = document_from_string input, safe: :safe
  820         assert_equal 'Chapter A', result.doctitle
  821 
  822         result = document_from_string input, safe: :unsafe, base_dir: ::Dir.tmpdir
  823         assert_equal 'Chapter A', result.doctitle
  824       end
  825 
  826       test 'include directive can retrieve data from uri' do
  827         url = %(http://#{resolve_localhost}:9876/name/asciidoctor)
  828         input = <<~EOS
  829         ....
  830         include::#{url}[]
  831         ....
  832         EOS
  833         expect = /\{"name": "asciidoctor"\}/
  834         output = using_test_webserver do
  835           convert_string_to_embedded input, safe: :safe, attributes: { 'allow-uri-read' => '' }
  836         end
  837 
  838         refute_nil output
  839         assert_match(expect, output)
  840       end
  841 
  842       test 'nested include directives are resolved relative to current file' do
  843         input = <<~'EOS'
  844         ....
  845         include::fixtures/outer-include.adoc[]
  846         ....
  847         EOS
  848 
  849         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
  850         expected = <<~'EOS'.chop
  851         first line of outer
  852 
  853         first line of middle
  854 
  855         first line of inner
  856 
  857         last line of inner
  858 
  859         last line of middle
  860 
  861         last line of outer
  862         EOS
  863         assert_includes output, expected
  864       end
  865 
  866       test 'nested remote include directive is resolved relative to uri of current file' do
  867         url = %(http://#{resolve_localhost}:9876/fixtures/outer-include.adoc)
  868         input = <<~EOS
  869         ....
  870         include::#{url}[]
  871         ....
  872         EOS
  873         output = using_test_webserver do
  874           convert_string_to_embedded input, safe: :safe, attributes: { 'allow-uri-read' => '' }
  875         end
  876 
  877         expected = <<~'EOS'.chop
  878         first line of outer
  879 
  880         first line of middle
  881 
  882         first line of inner
  883 
  884         last line of inner
  885 
  886         last line of middle
  887 
  888         last line of outer
  889         EOS
  890         assert_includes output, expected
  891       end
  892 
  893       test 'nested remote include directive that cannot be resolved does not crash processor' do
  894         include_url = %(http://#{resolve_localhost}:9876/fixtures/file-with-missing-include.adoc)
  895         nested_include_url = 'no-such-file.adoc'
  896         input = <<~EOS
  897         ....
  898         include::#{include_url}[]
  899         ....
  900         EOS
  901         begin
  902           using_memory_logger do |logger|
  903             result = using_test_webserver do
  904               convert_string_to_embedded input, safe: :safe, attributes: { 'allow-uri-read' => '' }
  905             end
  906             assert_includes result, %(Unresolved directive in #{include_url} - include::#{nested_include_url}[])
  907             assert_message logger, :ERROR, %(#{include_url}: line 1: include uri not readable: http://#{resolve_localhost}:9876/fixtures/#{nested_include_url}), Hash
  908           end
  909         rescue
  910           flunk 'include directive should not raise exception on missing file'
  911         end
  912       end
  913 
  914       test 'tag filtering is supported for remote includes' do
  915         url = %(http://#{resolve_localhost}:9876/fixtures/tagged-class.rb)
  916         input = <<~EOS
  917         [source,ruby]
  918         ----
  919         include::#{url}[tag=init,indent=0]
  920         ----
  921         EOS
  922         output = using_test_webserver do
  923           convert_string_to_embedded input, safe: :safe, attributes: { 'allow-uri-read' => '' }
  924         end
  925 
  926         # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
  927         expected = <<~EOS.chop
  928         <code class="language-ruby" data-lang="ruby">def initialize breed
  929           @breed = breed
  930         end</code>
  931         EOS
  932         assert_includes output, expected
  933       end
  934 
  935       test 'inaccessible uri referenced by include directive does not crash processor' do
  936         url = %(http://#{resolve_localhost}:9876/no_such_file)
  937         input = <<~EOS
  938         ....
  939         include::#{url}[]
  940         ....
  941         EOS
  942 
  943         begin
  944           using_memory_logger do |logger|
  945             output = using_test_webserver do
  946               convert_string_to_embedded input, safe: :safe, attributes: { 'allow-uri-read' => '' }
  947             end
  948             refute_nil output
  949             assert_match(/Unresolved directive/, output)
  950             assert_message logger, :ERROR, %(<stdin>: line 2: include uri not readable: #{url}), Hash
  951           end
  952         rescue
  953           flunk 'include directive should not raise exception on inaccessible uri'
  954         end
  955       end
  956 
  957       test 'include directive supports selecting lines by line number' do
  958         input = 'include::fixtures/include-file.adoc[lines=1;3..4;6..-1]'
  959         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
  960         assert_match(/first line/, output)
  961         refute_match(/second line/, output)
  962         assert_match(/third line/, output)
  963         assert_match(/fourth line/, output)
  964         refute_match(/fifth line/, output)
  965         assert_match(/sixth line/, output)
  966         assert_match(/seventh line/, output)
  967         assert_match(/eighth line/, output)
  968         assert_match(/last line of included content/, output)
  969       end
  970 
  971       test 'include directive supports line ranges specified in quoted attribute value' do
  972         input = 'include::fixtures/include-file.adoc[lines="1, 3..4 , 6 .. -1"]'
  973         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
  974         assert_match(/first line/, output)
  975         refute_match(/second line/, output)
  976         assert_match(/third line/, output)
  977         assert_match(/fourth line/, output)
  978         refute_match(/fifth line/, output)
  979         assert_match(/sixth line/, output)
  980         assert_match(/seventh line/, output)
  981         assert_match(/eighth line/, output)
  982         assert_match(/last line of included content/, output)
  983       end
  984 
  985       test 'include directive supports implicit endless range' do
  986         input = 'include::fixtures/include-file.adoc[lines=6..]'
  987         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
  988         refute_match(/first line/, output)
  989         refute_match(/second line/, output)
  990         refute_match(/third line/, output)
  991         refute_match(/fourth line/, output)
  992         refute_match(/fifth line/, output)
  993         assert_match(/sixth line/, output)
  994         assert_match(/seventh line/, output)
  995         assert_match(/eighth line/, output)
  996         assert_match(/last line of included content/, output)
  997       end
  998 
  999       test 'include directive ignores empty lines attribute' do
 1000         input = <<~'EOS'
 1001         ++++
 1002         include::fixtures/include-file.adoc[lines=]
 1003         ++++
 1004         EOS
 1005 
 1006         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1007         assert_includes output, 'first line of included content'
 1008         assert_includes output, 'last line of included content'
 1009       end
 1010 
 1011       test 'include directive supports selecting lines by tag' do
 1012         input = 'include::fixtures/include-file.adoc[tag=snippetA]'
 1013         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1014         assert_match(/snippetA content/, output)
 1015         refute_match(/snippetB content/, output)
 1016         refute_match(/non-tagged content/, output)
 1017         refute_match(/included content/, output)
 1018       end
 1019 
 1020       test 'include directive supports selecting lines by tags' do
 1021         input = 'include::fixtures/include-file.adoc[tags=snippetA;snippetB]'
 1022         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1023         assert_match(/snippetA content/, output)
 1024         assert_match(/snippetB content/, output)
 1025         refute_match(/non-tagged content/, output)
 1026         refute_match(/included content/, output)
 1027       end
 1028 
 1029       test 'include directive supports selecting lines by tag in language that uses circumfix comments' do
 1030         {
 1031           'include-file.xml' => '<snippet>content</snippet>',
 1032           'include-file.ml' => 'let s = SS.empty;;',
 1033           'include-file.jsx' => '<p>Welcome to the club.</p>',
 1034         }.each do |filename, expect|
 1035           input = <<~EOS
 1036           [source,xml]
 1037           ----
 1038           include::fixtures/#{filename}[tag=snippet,indent=0]
 1039           ----
 1040           EOS
 1041 
 1042           doc = document_from_string input, safe: :safe, base_dir: DIRNAME
 1043           assert_equal expect, doc.blocks[0].source
 1044         end
 1045       end
 1046 
 1047       test 'include directive supports selecting tagged lines in file that has CRLF line endings' do
 1048         begin
 1049           tmp_include = Tempfile.new %w(include- .adoc)
 1050           tmp_include_dir, tmp_include_path = File.split tmp_include.path
 1051           tmp_include.write %(do not include\r\ntag::include-me[]\r\nincluded line\r\nend::include-me[]\r\ndo not include\r\n)
 1052           tmp_include.close
 1053           input = %(include::#{tmp_include_path}[tag=include-me])
 1054           output = convert_string_to_embedded input, safe: :safe, base_dir: tmp_include_dir
 1055           assert_includes output, 'included line'
 1056           refute_includes output, 'do not include'
 1057         ensure
 1058           tmp_include.close!
 1059         end
 1060       end
 1061 
 1062       test 'include directive finds closing tag on last line of file without a trailing newline' do
 1063         begin
 1064           tmp_include = Tempfile.new %w(include- .adoc)
 1065           tmp_include_dir, tmp_include_path = File.split tmp_include.path
 1066           tmp_include.write %(line not included\ntag::include-me[]\nline included\nend::include-me[])
 1067           tmp_include.close
 1068           input = %(include::#{tmp_include_path}[tag=include-me])
 1069           using_memory_logger do |logger|
 1070             output = convert_string_to_embedded input, safe: :safe, base_dir: tmp_include_dir
 1071             assert_empty logger.messages
 1072             assert_includes output, 'line included'
 1073             refute_includes output, 'line not included'
 1074           end
 1075         ensure
 1076           tmp_include.close!
 1077         end
 1078       end
 1079 
 1080       test 'include directive does not select lines with tag directives within selected tag region' do
 1081         input = <<~'EOS'
 1082         ++++
 1083         include::fixtures/include-file.adoc[tags=snippet]
 1084         ++++
 1085         EOS
 1086 
 1087         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1088         expected = <<~'EOS'.chop
 1089         snippetA content
 1090 
 1091         non-tagged content
 1092 
 1093         snippetB content
 1094         EOS
 1095         assert_equal expected, output
 1096       end
 1097 
 1098       test 'include directive skips lines marked with negated tags' do
 1099         input = <<~'EOS'
 1100         ----
 1101         include::fixtures/tagged-class-enclosed.rb[tags=all;!bark]
 1102         ----
 1103         EOS
 1104 
 1105         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1106         # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1107         expected = <<~EOS.chop
 1108         class Dog
 1109           def initialize breed
 1110             @breed = breed
 1111           end
 1112         end
 1113         EOS
 1114         assert_includes output, expected
 1115       end
 1116 
 1117       test 'include directive takes all lines without tag directives when value is double asterisk' do
 1118         input = <<~'EOS'
 1119         ----
 1120         include::fixtures/tagged-class.rb[tags=**]
 1121         ----
 1122         EOS
 1123 
 1124         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1125         # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1126         expected = <<~EOS.chop
 1127         class Dog
 1128           def initialize breed
 1129             @breed = breed
 1130           end
 1131 
 1132           def bark
 1133             if @breed == 'beagle'
 1134               'woof woof woof woof woof'
 1135             else
 1136               'woof woof'
 1137             end
 1138           end
 1139         end
 1140         EOS
 1141         assert_includes output, expected
 1142       end
 1143 
 1144       test 'include directive takes all lines except negated tags when value contains double asterisk' do
 1145         input = <<~'EOS'
 1146         ----
 1147         include::fixtures/tagged-class.rb[tags=**;!bark]
 1148         ----
 1149         EOS
 1150 
 1151         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1152         # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1153         expected = <<~EOS.chop
 1154         class Dog
 1155           def initialize breed
 1156             @breed = breed
 1157           end
 1158         end
 1159         EOS
 1160         assert_includes output, expected
 1161       end
 1162 
 1163       test 'include directive selects lines for all tags when value of tags attribute is wildcard' do
 1164         input = <<~'EOS'
 1165         ----
 1166         include::fixtures/tagged-class-enclosed.rb[tags=*]
 1167         ----
 1168         EOS
 1169 
 1170         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1171         # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1172         expected = <<~EOS.chop
 1173         class Dog
 1174           def initialize breed
 1175             @breed = breed
 1176           end
 1177 
 1178           def bark
 1179             if @breed == 'beagle'
 1180               'woof woof woof woof woof'
 1181             else
 1182               'woof woof'
 1183             end
 1184           end
 1185         end
 1186         EOS
 1187         assert_includes output, expected
 1188       end
 1189 
 1190       test 'include directive selects lines for all tags except exclusions when value of tags attribute is wildcard' do
 1191         input = <<~'EOS'
 1192         ----
 1193         include::fixtures/tagged-class-enclosed.rb[tags=*;!init]
 1194         ----
 1195         EOS
 1196 
 1197         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1198         # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1199         expected = <<~EOS.chop
 1200         class Dog
 1201 
 1202           def bark
 1203             if @breed == 'beagle'
 1204               'woof woof woof woof woof'
 1205             else
 1206               'woof woof'
 1207             end
 1208           end
 1209         end
 1210         EOS
 1211         assert_includes output, expected
 1212       end
 1213 
 1214       test 'include directive skips lines all tagged lines when value of tags attribute is negated wildcard' do
 1215         input = <<~'EOS'
 1216         ----
 1217         include::fixtures/tagged-class.rb[tags=!*]
 1218         ----
 1219         EOS
 1220 
 1221         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1222         expected = %(class Dog\nend)
 1223         assert_includes output, expected
 1224       end
 1225 
 1226       test 'include directive selects specified tagged lines and ignores the other tag directives' do
 1227         input = <<~'EOS'
 1228         [indent=0]
 1229         ----
 1230         include::fixtures/tagged-class.rb[tags=bark;!bark-other]
 1231         ----
 1232         EOS
 1233 
 1234         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1235         # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1236         expected = <<~EOS.chop
 1237         def bark
 1238           if @breed == 'beagle'
 1239             'woof woof woof woof woof'
 1240           end
 1241         end
 1242         EOS
 1243         assert_includes output, expected
 1244       end
 1245 
 1246       test 'should warn if specified tag is not found in include file' do
 1247         input = 'include::fixtures/include-file.adoc[tag=no-such-tag]'
 1248         using_memory_logger do |logger|
 1249           convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1250           assert_message logger, :WARN, %(~<stdin>: line 1: tag 'no-such-tag' not found in include file), Hash
 1251         end
 1252       end
 1253 
 1254       test 'should warn if specified tags are not found in include file' do
 1255         input = <<~'EOS'
 1256         ++++
 1257         include::fixtures/include-file.adoc[tags=no-such-tag-b;no-such-tag-a]
 1258         ++++
 1259         EOS
 1260 
 1261         using_memory_logger do |logger|
 1262           convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1263           expected_tags = 'no-such-tag-b, no-such-tag-a'
 1264           assert_message logger, :WARN, %(~<stdin>: line 2: tags '#{expected_tags}' not found in include file), Hash
 1265         end
 1266       end
 1267 
 1268       test 'should warn if specified tag in include file is not closed' do
 1269         input = <<~'EOS'
 1270         ++++
 1271         include::fixtures/unclosed-tag.adoc[tag=a]
 1272         ++++
 1273         EOS
 1274 
 1275         using_memory_logger do |logger|
 1276           result = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1277           assert_equal 'a', result
 1278           assert_message logger, :WARN, %(~<stdin>: line 2: detected unclosed tag 'a' starting at line 2 of include file), Hash
 1279           refute_nil logger.messages[0][:message][:include_location]
 1280         end
 1281       end
 1282 
 1283       test 'should warn if end tag in included file is mismatched' do
 1284         input = <<~'EOS'
 1285         ++++
 1286         include::fixtures/mismatched-end-tag.adoc[tags=a;b]
 1287         ++++
 1288         EOS
 1289 
 1290         inc_path = File.join DIRNAME, 'fixtures/mismatched-end-tag.adoc'
 1291         using_memory_logger do |logger|
 1292           result = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1293           assert_equal %(a\nb), result
 1294           assert_message logger, :WARN, %(<stdin>: line 2: mismatched end tag (expected 'b' but found 'a') at line 5 of include file: #{inc_path}), Hash
 1295           refute_nil logger.messages[0][:message][:include_location]
 1296         end
 1297       end
 1298 
 1299       test 'should warn if unexpected end tag is found in included file' do
 1300         input = <<~'EOS'
 1301         ++++
 1302         include::fixtures/unexpected-end-tag.adoc[tags=a]
 1303         ++++
 1304         EOS
 1305 
 1306         inc_path = File.join DIRNAME, 'fixtures/unexpected-end-tag.adoc'
 1307         using_memory_logger do |logger|
 1308           result = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1309           assert_equal 'a', result
 1310           assert_message logger, :WARN, %(<stdin>: line 2: unexpected end tag 'a' at line 4 of include file: #{inc_path}), Hash
 1311           refute_nil logger.messages[0][:message][:include_location]
 1312         end
 1313       end
 1314 
 1315       test 'include directive ignores tags attribute when empty' do
 1316         ['tag', 'tags'].each do |attr_name|
 1317           input = <<~EOS
 1318           ++++
 1319           include::fixtures/include-file.xml[#{attr_name}=]
 1320           ++++
 1321           EOS
 1322 
 1323           output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1324           assert_match(/(?:tag|end)::/, output, 2)
 1325         end
 1326       end
 1327 
 1328       test 'lines attribute takes precedence over tags attribute in include directive' do
 1329         input = 'include::fixtures/include-file.adoc[lines=1, tags=snippetA;snippetB]'
 1330         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1331         assert_match(/first line of included content/, output)
 1332         refute_match(/snippetA content/, output)
 1333         refute_match(/snippetB content/, output)
 1334       end
 1335 
 1336       test 'indent of included file can be reset to size of indent attribute' do
 1337         input = <<~'EOS'
 1338         [source, xml]
 1339         ----
 1340         include::fixtures/basic-docinfo.xml[lines=2..3, indent=0]
 1341         ----
 1342         EOS
 1343 
 1344         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1345         result = xmlnodes_at_xpath('//pre', output, 1).text
 1346         assert_equal "<year>2013</year>\n<holder>Acme™, Inc.</holder>", result
 1347       end
 1348 
 1349       test 'should substitute attribute references in attrlist' do
 1350         input = <<~'EOS'
 1351         :name-of-tag: snippetA
 1352         include::fixtures/include-file.adoc[tag={name-of-tag}]
 1353         EOS
 1354 
 1355         output = convert_string_to_embedded input, safe: :safe, base_dir: DIRNAME
 1356         assert_match(/snippetA content/, output)
 1357         refute_match(/snippetB content/, output)
 1358         refute_match(/non-tagged content/, output)
 1359         refute_match(/included content/, output)
 1360       end
 1361 
 1362       test 'should fall back to built-in include directive behavior when not handled by include processor' do
 1363         input = 'include::fixtures/include-file.adoc[]'
 1364         include_processor = Class.new do
 1365           def initialize document
 1366           end
 1367 
 1368           def handles? target
 1369             false
 1370           end
 1371 
 1372           def process reader, target, attributes
 1373             raise 'TestIncludeHandler should not have been invoked'
 1374           end
 1375         end
 1376 
 1377         document = empty_safe_document base_dir: DIRNAME
 1378         reader = Asciidoctor::PreprocessorReader.new document, input, nil, normalize: true
 1379         reader.instance_variable_set '@include_processors', [include_processor.new(document)]
 1380         lines = reader.read_lines
 1381         source = lines * ::Asciidoctor::LF
 1382         assert_match(/included content/, source)
 1383       end
 1384 
 1385       test 'leveloffset attribute entries should be added to content if leveloffset attribute is specified' do
 1386         input = 'include::fixtures/master.adoc[]'
 1387         expected = <<~'EOS'.split ::Asciidoctor::LF
 1388         = Master Document
 1389 
 1390         preamble
 1391 
 1392         :leveloffset: +1
 1393 
 1394         = Chapter A
 1395 
 1396         content
 1397 
 1398         :leveloffset!:
 1399         EOS
 1400 
 1401         document = Asciidoctor.load input, safe: :safe, base_dir: DIRNAME, parse: false
 1402         assert_equal expected, document.reader.read_lines
 1403       end
 1404 
 1405       test 'attributes are substituted in target of include directive' do
 1406         input = <<~'EOS'
 1407         :fixturesdir: fixtures
 1408         :ext: adoc
 1409 
 1410         include::{fixturesdir}/include-file.{ext}[]
 1411         EOS
 1412 
 1413         doc = document_from_string input, safe: :safe, base_dir: DIRNAME
 1414         output = doc.convert
 1415         assert_match(/included content/, output)
 1416       end
 1417 
 1418       test 'line is skipped by default if target of include directive resolves to empty' do
 1419         input = 'include::{blank}[]'
 1420         using_memory_logger do |logger|
 1421           doc = empty_safe_document base_dir: DIRNAME
 1422           reader = Asciidoctor::PreprocessorReader.new doc, input, nil, normalize: true
 1423           line = reader.read_line
 1424           assert_equal 'Unresolved directive in <stdin> - include::{blank}[]', line
 1425           assert_message logger, :WARN, '<stdin>: line 1: include dropped because resolved target is blank: include::{blank}[]', Hash
 1426         end
 1427       end
 1428 
 1429       test 'include is dropped if target contains missing attribute and attribute-missing is drop-line' do
 1430         input = 'include::{foodir}/include-file.adoc[]'
 1431         using_memory_logger Logger::INFO do |logger|
 1432           doc = empty_safe_document base_dir: DIRNAME, attributes: { 'attribute-missing' => 'drop-line' }
 1433           reader = Asciidoctor::PreprocessorReader.new doc, input, nil, normalize: true
 1434           line = reader.read_line
 1435           assert_nil line
 1436           assert_messages logger, [
 1437             [:INFO, 'dropping line containing reference to missing attribute: foodir'],
 1438             [:INFO, '<stdin>: line 1: include dropped due to missing attribute: include::{foodir}/include-file.adoc[]', Hash],
 1439           ]
 1440         end
 1441       end
 1442 
 1443       test 'line following dropped include is not dropped' do
 1444         input = <<~'EOS'
 1445         include::{foodir}/include-file.adoc[]
 1446         yo
 1447         EOS
 1448 
 1449         using_memory_logger do |logger|
 1450           doc = empty_safe_document base_dir: DIRNAME, attributes: { 'attribute-missing' => 'warn' }
 1451           reader = Asciidoctor::PreprocessorReader.new doc, input, nil, normalize: true
 1452           line = reader.read_line
 1453           assert_equal 'Unresolved directive in <stdin> - include::{foodir}/include-file.adoc[]', line
 1454           line = reader.read_line
 1455           assert_equal 'yo', line
 1456           assert_messages logger, [
 1457             [:INFO, 'dropping line containing reference to missing attribute: foodir'],
 1458             [:WARN, '<stdin>: line 1: include dropped due to missing attribute: include::{foodir}/include-file.adoc[]', Hash],
 1459           ]
 1460         end
 1461       end
 1462 
 1463       test 'escaped include directive is left unprocessed' do
 1464         input = <<~'EOS'
 1465         \include::fixtures/include-file.adoc[]
 1466         \escape preserved here
 1467         EOS
 1468         doc = empty_safe_document base_dir: DIRNAME
 1469         reader = Asciidoctor::PreprocessorReader.new doc, input, nil, normalize: true
 1470         # we should be able to peek it multiple times and still have the backslash preserved
 1471         # this is the test for @unescape_next_line
 1472         assert_equal 'include::fixtures/include-file.adoc[]', reader.peek_line
 1473         assert_equal 'include::fixtures/include-file.adoc[]', reader.peek_line
 1474         assert_equal 'include::fixtures/include-file.adoc[]', reader.read_line
 1475         assert_equal '\\escape preserved here', reader.read_line
 1476       end
 1477 
 1478       test 'include directive not at start of line is ignored' do
 1479         input = ' include::include-file.adoc[]'
 1480         para = block_from_string input
 1481         assert_equal 1, para.lines.size
 1482         # NOTE the space gets stripped because the line is treated as an inline literal
 1483         assert_equal :literal, para.context
 1484         assert_equal 'include::include-file.adoc[]', para.source
 1485       end
 1486 
 1487       test 'include directive is disabled when max-include-depth attribute is 0' do
 1488         input = 'include::include-file.adoc[]'
 1489         para = block_from_string input, safe: :safe, attributes: { 'max-include-depth' => 0 }
 1490         assert_equal 1, para.lines.size
 1491         assert_equal 'include::include-file.adoc[]', para.source
 1492       end
 1493 
 1494       test 'max-include-depth cannot be set by document' do
 1495         input = <<~'EOS'
 1496         :max-include-depth: 1
 1497 
 1498         include::include-file.adoc[]
 1499         EOS
 1500         para = block_from_string input, safe: :safe, attributes: { 'max-include-depth' => 0 }
 1501         assert_equal 1, para.lines.size
 1502         assert_equal 'include::include-file.adoc[]', para.source
 1503       end
 1504 
 1505       test 'include directive should be disabled if max include depth has been exceeded' do
 1506         input = 'include::fixtures/parent-include.adoc[depth=1]'
 1507         using_memory_logger do |logger|
 1508           pseudo_docfile = File.join DIRNAME, 'include-master.adoc'
 1509           doc = empty_safe_document base_dir: DIRNAME
 1510           reader = Asciidoctor::PreprocessorReader.new doc, input, Asciidoctor::Reader::Cursor.new(pseudo_docfile), normalize: true
 1511           lines = reader.readlines
 1512           assert_includes lines, 'include::grandchild-include.adoc[]'
 1513           assert_message logger, :ERROR, 'fixtures/child-include.adoc: line 3: maximum include depth of 1 exceeded', Hash
 1514         end
 1515       end
 1516 
 1517       test 'include directive should be disabled if max include depth set in nested context has been exceeded' do
 1518         input = 'include::fixtures/parent-include-restricted.adoc[depth=3]'
 1519         using_memory_logger do |logger|
 1520           pseudo_docfile = File.join DIRNAME, 'include-master.adoc'
 1521           doc = empty_safe_document base_dir: DIRNAME
 1522           reader = Asciidoctor::PreprocessorReader.new doc, input, Asciidoctor::Reader::Cursor.new(pseudo_docfile), normalize: true
 1523           lines = reader.readlines
 1524           assert_includes lines, 'first line of child'
 1525           assert_includes lines, 'include::grandchild-include.adoc[]'
 1526           assert_message logger, :ERROR, 'fixtures/child-include.adoc: line 3: maximum include depth of 0 exceeded', Hash
 1527         end
 1528       end
 1529 
 1530       test 'read_lines_until should not process lines if process option is false' do
 1531         lines = <<~'EOS'.lines
 1532         ////
 1533         include::fixtures/no-such-file.adoc[]
 1534         ////
 1535         EOS
 1536 
 1537         doc = empty_safe_document base_dir: DIRNAME
 1538         reader = Asciidoctor::PreprocessorReader.new doc, lines, nil, normalize: true
 1539         reader.read_line
 1540         result = reader.read_lines_until(terminator: '////', skip_processing: true)
 1541         assert_equal lines.map(&:chomp)[1..1], result
 1542       end
 1543 
 1544       test 'skip_comment_lines should not process lines read' do
 1545         lines = <<~'EOS'.lines
 1546         ////
 1547         include::fixtures/no-such-file.adoc[]
 1548         ////
 1549         EOS
 1550 
 1551         using_memory_logger do |logger|
 1552           doc = empty_safe_document base_dir: DIRNAME
 1553           reader = Asciidoctor::PreprocessorReader.new doc, lines, nil, normalize: true
 1554           reader.skip_comment_lines
 1555           assert reader.empty?
 1556           assert logger.empty?
 1557         end
 1558       end
 1559     end
 1560 
 1561     context 'Conditional Inclusions' do
 1562       test 'process_line returns nil if cursor advanced' do
 1563         input = <<~'EOS'
 1564         ifdef::asciidoctor[]
 1565         Asciidoctor!
 1566         endif::asciidoctor[]
 1567         EOS
 1568 
 1569         doc = Asciidoctor::Document.new input
 1570         reader = doc.reader
 1571         assert_nil reader.send :process_line, reader.lines.first
 1572       end
 1573 
 1574       test 'peek_line advances cursor to next conditional line of content' do
 1575         input = <<~'EOS'
 1576         ifdef::asciidoctor[]
 1577         Asciidoctor!
 1578         endif::asciidoctor[]
 1579         EOS
 1580 
 1581         doc = Asciidoctor::Document.new input
 1582         reader = doc.reader
 1583         assert_equal 1, reader.lineno
 1584         assert_equal 'Asciidoctor!', reader.peek_line
 1585         assert_equal 2, reader.lineno
 1586       end
 1587 
 1588       test 'peek_lines should preprocess lines if direct is false' do
 1589         input = <<~'EOS'
 1590         The Asciidoctor
 1591         ifdef::asciidoctor[is in.]
 1592         EOS
 1593         doc = Asciidoctor::Document.new input
 1594         reader = doc.reader
 1595         result = reader.peek_lines 2, false
 1596         assert_equal ['The Asciidoctor', 'is in.'], result
 1597       end
 1598 
 1599       test 'peek_lines should not preprocess lines if direct is true' do
 1600         input = <<~'EOS'
 1601         The Asciidoctor
 1602         ifdef::asciidoctor[is in.]
 1603         EOS
 1604         doc = Asciidoctor::Document.new input
 1605         reader = doc.reader
 1606         result = reader.peek_lines 2, true
 1607         assert_equal ['The Asciidoctor', 'ifdef::asciidoctor[is in.]'], result
 1608       end
 1609 
 1610       test 'peek_lines should not prevent subsequent preprocessing of peeked lines' do
 1611         input = <<~'EOS'
 1612         The Asciidoctor
 1613         ifdef::asciidoctor[is in.]
 1614         EOS
 1615         doc = Asciidoctor::Document.new input
 1616         reader = doc.reader
 1617         result = reader.peek_lines 2, true
 1618         result = reader.peek_lines 2, false
 1619         assert_equal ['The Asciidoctor', 'is in.'], result
 1620       end
 1621 
 1622       test 'process_line returns line if cursor not advanced' do
 1623         input = <<~'EOS'
 1624         content
 1625         ifdef::asciidoctor[]
 1626         Asciidoctor!
 1627         endif::asciidoctor[]
 1628         EOS
 1629 
 1630         doc = Asciidoctor::Document.new input
 1631         reader = doc.reader
 1632         refute_nil reader.send :process_line, reader.lines.first
 1633       end
 1634 
 1635       test 'peek_line does not advance cursor when on a regular content line' do
 1636         input = <<~'EOS'
 1637         content
 1638         ifdef::asciidoctor[]
 1639         Asciidoctor!
 1640         endif::asciidoctor[]
 1641         EOS
 1642 
 1643         doc = Asciidoctor::Document.new input
 1644         reader = doc.reader
 1645         assert_equal 1, reader.lineno
 1646         assert_equal 'content', reader.peek_line
 1647         assert_equal 1, reader.lineno
 1648       end
 1649 
 1650       test 'peek_line returns nil if cursor advances past end of source' do
 1651         input = <<~'EOS'
 1652         ifdef::foobar[]
 1653         swallowed content
 1654         endif::foobar[]
 1655         EOS
 1656 
 1657         doc = Asciidoctor::Document.new input
 1658         reader = doc.reader
 1659         assert_equal 1, reader.lineno
 1660         assert_nil reader.peek_line
 1661         assert_equal 4, reader.lineno
 1662       end
 1663 
 1664       test 'ifdef with defined attribute includes content' do
 1665         input = <<~'EOS'
 1666         ifdef::holygrail[]
 1667         There is a holy grail!
 1668         endif::holygrail[]
 1669         EOS
 1670 
 1671         doc = Asciidoctor::Document.new input, attributes: { 'holygrail' => '' }
 1672         reader = doc.reader
 1673         lines = []
 1674         while reader.has_more_lines?
 1675           lines << reader.read_line
 1676         end
 1677         assert_equal 'There is a holy grail!', (lines * ::Asciidoctor::LF)
 1678       end
 1679 
 1680       test 'ifdef with defined attribute includes text in brackets' do
 1681         input = <<~'EOS'
 1682         On our quest we go...
 1683         ifdef::holygrail[There is a holy grail!]
 1684         There was much rejoicing.
 1685         EOS
 1686 
 1687         doc = Asciidoctor::Document.new input, attributes: { 'holygrail' => '' }
 1688         reader = doc.reader
 1689         lines = []
 1690         while reader.has_more_lines?
 1691           lines << reader.read_line
 1692         end
 1693         assert_equal "On our quest we go...\nThere is a holy grail!\nThere was much rejoicing.", (lines * ::Asciidoctor::LF)
 1694       end
 1695 
 1696       test 'ifdef with defined attribute processes include directive in brackets' do
 1697         input = 'ifdef::asciidoctor-version[include::fixtures/include-file.adoc[tag=snippetA]]'
 1698         doc = Asciidoctor::Document.new input, safe: :safe, base_dir: DIRNAME
 1699         reader = doc.reader
 1700         lines = []
 1701         while reader.has_more_lines?
 1702           lines << reader.read_line
 1703         end
 1704         assert_equal 'snippetA content', lines[0]
 1705       end
 1706 
 1707       test 'ifdef attribute name is not case sensitive' do
 1708         input = <<~'EOS'
 1709         ifdef::showScript[]
 1710         The script is shown!
 1711         endif::showScript[]
 1712         EOS
 1713 
 1714         doc = Asciidoctor::Document.new input, attributes: { 'showscript' => '' }
 1715         result = doc.reader.read
 1716         assert_equal 'The script is shown!', result
 1717       end
 1718 
 1719       test 'ifndef with defined attribute does not include text in brackets' do
 1720         input = <<~'EOS'
 1721         On our quest we go...
 1722         ifndef::hardships[There is a holy grail!]
 1723         There was no rejoicing.
 1724         EOS
 1725 
 1726         doc = Asciidoctor::Document.new input, attributes: { 'hardships' => '' }
 1727         reader = doc.reader
 1728         lines = []
 1729         while reader.has_more_lines?
 1730           lines << reader.read_line
 1731         end
 1732         assert_equal "On our quest we go...\nThere was no rejoicing.", (lines * ::Asciidoctor::LF)
 1733       end
 1734 
 1735       test 'include with non-matching nested exclude' do
 1736         input = <<~'EOS'
 1737         ifdef::grail[]
 1738         holy
 1739         ifdef::swallow[]
 1740         swallow
 1741         endif::swallow[]
 1742         grail
 1743         endif::grail[]
 1744         EOS
 1745 
 1746         doc = Asciidoctor::Document.new input, attributes: { 'grail' => '' }
 1747         reader = doc.reader
 1748         lines = []
 1749         while reader.has_more_lines?
 1750           lines << reader.read_line
 1751         end
 1752         assert_equal "holy\ngrail", (lines * ::Asciidoctor::LF)
 1753       end
 1754 
 1755       test 'nested excludes with same condition' do
 1756         input = <<~'EOS'
 1757         ifndef::grail[]
 1758         ifndef::grail[]
 1759         not here
 1760         endif::grail[]
 1761         endif::grail[]
 1762         EOS
 1763 
 1764         doc = Asciidoctor::Document.new input, attributes: { 'grail' => '' }
 1765         reader = doc.reader
 1766         lines = []
 1767         while reader.has_more_lines?
 1768           lines << reader.read_line
 1769         end
 1770         assert_equal '', (lines * ::Asciidoctor::LF)
 1771       end
 1772 
 1773       test 'include with nested exclude of inverted condition' do
 1774         input = <<~'EOS'
 1775         ifdef::grail[]
 1776         holy
 1777         ifndef::grail[]
 1778         not here
 1779         endif::grail[]
 1780         grail
 1781         endif::grail[]
 1782         EOS
 1783 
 1784         doc = Asciidoctor::Document.new input, attributes: { 'grail' => '' }
 1785         reader = doc.reader
 1786         lines = []
 1787         while reader.has_more_lines?
 1788           lines << reader.read_line
 1789         end
 1790         assert_equal "holy\ngrail", (lines * ::Asciidoctor::LF)
 1791       end
 1792 
 1793       test 'exclude with matching nested exclude' do
 1794         input = <<~'EOS'
 1795         poof
 1796         ifdef::swallow[]
 1797         no
 1798         ifdef::swallow[]
 1799         swallow
 1800         endif::swallow[]
 1801         here
 1802         endif::swallow[]
 1803         gone
 1804         EOS
 1805 
 1806         doc = Asciidoctor::Document.new input, attributes: { 'grail' => '' }
 1807         reader = doc.reader
 1808         lines = []
 1809         while reader.has_more_lines?
 1810           lines << reader.read_line
 1811         end
 1812         assert_equal "poof\ngone", (lines * ::Asciidoctor::LF)
 1813       end
 1814 
 1815       test 'exclude with nested include using shorthand end' do
 1816         input = <<~'EOS'
 1817         poof
 1818         ifndef::grail[]
 1819         no grail
 1820         ifndef::swallow[]
 1821         or swallow
 1822         endif::[]
 1823         in here
 1824         endif::[]
 1825         gone
 1826         EOS
 1827 
 1828         doc = Asciidoctor::Document.new input, attributes: { 'grail' => '' }
 1829         reader = doc.reader
 1830         lines = []
 1831         while reader.has_more_lines?
 1832           lines << reader.read_line
 1833         end
 1834         assert_equal "poof\ngone", (lines * ::Asciidoctor::LF)
 1835       end
 1836 
 1837       test 'ifdef with one alternative attribute set includes content' do
 1838         input = <<~'EOS'
 1839         ifdef::holygrail,swallow[]
 1840         Our quest is complete!
 1841         endif::holygrail,swallow[]
 1842         EOS
 1843 
 1844         doc = Asciidoctor::Document.new input, attributes: { 'swallow' => '' }
 1845         reader = doc.reader
 1846         lines = []
 1847         while reader.has_more_lines?
 1848           lines << reader.read_line
 1849         end
 1850         assert_equal 'Our quest is complete!', (lines * ::Asciidoctor::LF)
 1851       end
 1852 
 1853       test 'ifdef with no alternative attributes set does not include content' do
 1854         input = <<~'EOS'
 1855         ifdef::holygrail,swallow[]
 1856         Our quest is complete!
 1857         endif::holygrail,swallow[]
 1858         EOS
 1859 
 1860         doc = Asciidoctor::Document.new input
 1861         reader = doc.reader
 1862         lines = []
 1863         while reader.has_more_lines?
 1864           lines << reader.read_line
 1865         end
 1866         assert_equal '', (lines * ::Asciidoctor::LF)
 1867       end
 1868 
 1869       test 'ifdef with all required attributes set includes content' do
 1870         input = <<~'EOS'
 1871         ifdef::holygrail+swallow[]
 1872         Our quest is complete!
 1873         endif::holygrail+swallow[]
 1874         EOS
 1875 
 1876         doc = Asciidoctor::Document.new input, attributes: { 'holygrail' => '', 'swallow' => '' }
 1877         reader = doc.reader
 1878         lines = []
 1879         while reader.has_more_lines?
 1880           lines << reader.read_line
 1881         end
 1882         assert_equal 'Our quest is complete!', (lines * ::Asciidoctor::LF)
 1883       end
 1884 
 1885       test 'ifdef with missing required attributes does not include content' do
 1886         input = <<~'EOS'
 1887         ifdef::holygrail+swallow[]
 1888         Our quest is complete!
 1889         endif::holygrail+swallow[]
 1890         EOS
 1891 
 1892         doc = Asciidoctor::Document.new input, attributes: { 'holygrail' => '' }
 1893         reader = doc.reader
 1894         lines = []
 1895         while reader.has_more_lines?
 1896           lines << reader.read_line
 1897         end
 1898         assert_equal '', (lines * ::Asciidoctor::LF)
 1899       end
 1900 
 1901       test 'ifdef should permit leading, trailing, and repeat operators' do
 1902         {
 1903           'asciidoctor,' => 'content',
 1904           ',asciidoctor' => 'content',
 1905           'asciidoctor+' => '',
 1906           '+asciidoctor' => '',
 1907           'asciidoctor,,asciidoctor-version' => 'content',
 1908           'asciidoctor++asciidoctor-version' => '',
 1909         }.each do |condition, expected|
 1910           input = <<~EOS
 1911           ifdef::#{condition}[]
 1912           content
 1913           endif::[]
 1914           EOS
 1915           assert_equal expected, (document_from_string input, parse: false).reader.read
 1916         end
 1917       end
 1918 
 1919       test 'ifndef with undefined attribute includes block' do
 1920         input = <<~'EOS'
 1921         ifndef::holygrail[]
 1922         Our quest continues to find the holy grail!
 1923         endif::holygrail[]
 1924         EOS
 1925 
 1926         doc = Asciidoctor::Document.new input
 1927         reader = doc.reader
 1928         lines = []
 1929         while reader.has_more_lines?
 1930           lines << reader.read_line
 1931         end
 1932         assert_equal 'Our quest continues to find the holy grail!', (lines * ::Asciidoctor::LF)
 1933       end
 1934 
 1935       test 'ifndef with one alternative attribute set does not include content' do
 1936         input = <<~'EOS'
 1937         ifndef::holygrail,swallow[]
 1938         Our quest is complete!
 1939         endif::holygrail,swallow[]
 1940         EOS
 1941 
 1942         result = (Asciidoctor::Document.new input, attributes: { 'swallow' => '' }).reader.read
 1943         assert_empty result
 1944       end
 1945 
 1946       test 'ifndef with both alternative attributes set does not include content' do
 1947         input = <<~'EOS'
 1948         ifndef::holygrail,swallow[]
 1949         Our quest is complete!
 1950         endif::holygrail,swallow[]
 1951         EOS
 1952 
 1953         result = (Asciidoctor::Document.new input, attributes: { 'swallow' => '', 'holygrail' => '' }).reader.read
 1954         assert_empty result
 1955       end
 1956 
 1957       test 'ifndef with no alternative attributes set includes content' do
 1958         input = <<~'EOS'
 1959         ifndef::holygrail,swallow[]
 1960         Our quest is complete!
 1961         endif::holygrail,swallow[]
 1962         EOS
 1963 
 1964         result = (Asciidoctor::Document.new input).reader.read
 1965         assert_equal 'Our quest is complete!', result
 1966       end
 1967 
 1968       test 'ifndef with no required attributes set includes content' do
 1969         input = <<~'EOS'
 1970         ifndef::holygrail+swallow[]
 1971         Our quest is complete!
 1972         endif::holygrail+swallow[]
 1973         EOS
 1974 
 1975         result = (Asciidoctor::Document.new input).reader.read
 1976         assert_equal 'Our quest is complete!', result
 1977       end
 1978 
 1979       test 'ifndef with all required attributes set does not include content' do
 1980         input = <<~'EOS'
 1981         ifndef::holygrail+swallow[]
 1982         Our quest is complete!
 1983         endif::holygrail+swallow[]
 1984         EOS
 1985 
 1986         result = (Asciidoctor::Document.new input, attributes: { 'swallow' => '', 'holygrail' => '' }).reader.read
 1987         assert_empty result
 1988       end
 1989 
 1990       test 'ifndef with at least one required attributes set does not include content' do
 1991         input = <<~'EOS'
 1992         ifndef::holygrail+swallow[]
 1993         Our quest is complete!
 1994         endif::holygrail+swallow[]
 1995         EOS
 1996 
 1997         result = (Asciidoctor::Document.new input, attributes: { 'swallow' => '' }).reader.read
 1998         assert_equal 'Our quest is complete!', result
 1999       end
 2000 
 2001       test 'should log warning if endif is unmatched' do
 2002         input = <<~'EOS'
 2003         Our quest is complete!
 2004         endif::on-quest[]
 2005         EOS
 2006 
 2007         using_memory_logger do |logger|
 2008           result = (Asciidoctor::Document.new input, attributes: { 'on-quest' => '' }).reader.read
 2009           assert_equal 'Our quest is complete!', result
 2010           assert_message logger, :ERROR, '~<stdin>: line 2: unmatched preprocessor directive: endif::on-quest[]', Hash
 2011         end
 2012       end
 2013 
 2014       test 'should log warning if endif is mismatched' do
 2015         input = <<~'EOS'
 2016         ifdef::on-quest[]
 2017         Our quest is complete!
 2018         endif::on-journey[]
 2019         EOS
 2020 
 2021         using_memory_logger do |logger|
 2022           result = (Asciidoctor::Document.new input, attributes: { 'on-quest' => '' }).reader.read
 2023           assert_equal 'Our quest is complete!', result
 2024           assert_message logger, :ERROR, '~<stdin>: line 3: mismatched preprocessor directive: endif::on-journey[]', Hash
 2025         end
 2026       end
 2027 
 2028       test 'should log warning if endif contains text' do
 2029         input = <<~'EOS'
 2030         ifdef::on-quest[]
 2031         Our quest is complete!
 2032         endif::on-quest[complete!]
 2033         EOS
 2034 
 2035         using_memory_logger do |logger|
 2036           result = (Asciidoctor::Document.new input, attributes: { 'on-quest' => '' }).reader.read
 2037           assert_equal 'Our quest is complete!', result
 2038           assert_message logger, :ERROR, '~<stdin>: line 3: malformed preprocessor directive - text not permitted: endif::on-quest[complete!]', Hash
 2039         end
 2040       end
 2041 
 2042       test 'escaped ifdef is unescaped and ignored' do
 2043         input = <<~'EOS'
 2044         \ifdef::holygrail[]
 2045         content
 2046         \endif::holygrail[]
 2047         EOS
 2048 
 2049         doc = Asciidoctor::Document.new input
 2050         reader = doc.reader
 2051         lines = []
 2052         while reader.has_more_lines?
 2053           lines << reader.read_line
 2054         end
 2055         assert_equal "ifdef::holygrail[]\ncontent\nendif::holygrail[]", (lines * ::Asciidoctor::LF)
 2056       end
 2057 
 2058       test 'ifeval comparing missing attribute to nil includes content' do
 2059         input = <<~'EOS'
 2060         ifeval::['{foo}' == '']
 2061         No foo for you!
 2062         endif::[]
 2063         EOS
 2064 
 2065         doc = Asciidoctor::Document.new input
 2066         reader = doc.reader
 2067         lines = []
 2068         while reader.has_more_lines?
 2069           lines << reader.read_line
 2070         end
 2071         assert_equal 'No foo for you!', (lines * ::Asciidoctor::LF)
 2072       end
 2073 
 2074       test 'ifeval comparing missing attribute to 0 drops content' do
 2075         input = <<~'EOS'
 2076         ifeval::[{leveloffset} == 0]
 2077         I didn't make the cut!
 2078         endif::[]
 2079         EOS
 2080 
 2081         doc = Asciidoctor::Document.new input
 2082         reader = doc.reader
 2083         lines = []
 2084         while reader.has_more_lines?
 2085           lines << reader.read_line
 2086         end
 2087         assert_equal '', (lines * ::Asciidoctor::LF)
 2088       end
 2089 
 2090       test 'ifeval comparing double-quoted attribute to matching string includes content' do
 2091         input = <<~'EOS'
 2092         ifeval::["{gem}" == "asciidoctor"]
 2093         Asciidoctor it is!
 2094         endif::[]
 2095         EOS
 2096 
 2097         doc = Asciidoctor::Document.new input, attributes: { 'gem' => 'asciidoctor' }
 2098         reader = doc.reader
 2099         lines = []
 2100         while reader.has_more_lines?
 2101           lines << reader.read_line
 2102         end
 2103         assert_equal 'Asciidoctor it is!', (lines * ::Asciidoctor::LF)
 2104       end
 2105 
 2106       test 'ifeval comparing single-quoted attribute to matching string includes content' do
 2107         input = <<~'EOS'
 2108         ifeval::['{gem}' == 'asciidoctor']
 2109         Asciidoctor it is!
 2110         endif::[]
 2111         EOS
 2112 
 2113         doc = Asciidoctor::Document.new input, attributes: { 'gem' => 'asciidoctor' }
 2114         reader = doc.reader
 2115         lines = []
 2116         while reader.has_more_lines?
 2117           lines << reader.read_line
 2118         end
 2119         assert_equal 'Asciidoctor it is!', (lines * ::Asciidoctor::LF)
 2120       end
 2121 
 2122       test 'ifeval comparing quoted attribute to non-matching string drops content' do
 2123         input = <<~'EOS'
 2124         ifeval::['{gem}' == 'asciidoctor']
 2125         Asciidoctor it is!
 2126         endif::[]
 2127         EOS
 2128 
 2129         doc = Asciidoctor::Document.new input, attributes: { 'gem' => 'tilt' }
 2130         reader = doc.reader
 2131         lines = []
 2132         while reader.has_more_lines?
 2133           lines << reader.read_line
 2134         end
 2135         assert_equal '', (lines * ::Asciidoctor::LF)
 2136       end
 2137 
 2138       test 'ifeval comparing attribute to lower version number includes content' do
 2139         input = <<~'EOS'
 2140         ifeval::['{asciidoctor-version}' >= '0.1.0']
 2141         That version will do!
 2142         endif::[]
 2143         EOS
 2144 
 2145         doc = Asciidoctor::Document.new input
 2146         reader = doc.reader
 2147         lines = []
 2148         while reader.has_more_lines?
 2149           lines << reader.read_line
 2150         end
 2151         assert_equal 'That version will do!', (lines * ::Asciidoctor::LF)
 2152       end
 2153 
 2154       test 'ifeval comparing attribute to self includes content' do
 2155         input = <<~'EOS'
 2156         ifeval::['{asciidoctor-version}' == '{asciidoctor-version}']
 2157         Of course it's the same!
 2158         endif::[]
 2159         EOS
 2160 
 2161         doc = Asciidoctor::Document.new input
 2162         reader = doc.reader
 2163         lines = []
 2164         while reader.has_more_lines?
 2165           lines << reader.read_line
 2166         end
 2167         assert_equal 'Of course it\'s the same!', (lines * ::Asciidoctor::LF)
 2168       end
 2169 
 2170       test 'ifeval arguments can be transposed' do
 2171         input = <<~'EOS'
 2172         ifeval::['0.1.0' <= '{asciidoctor-version}']
 2173         That version will do!
 2174         endif::[]
 2175         EOS
 2176 
 2177         doc = Asciidoctor::Document.new input
 2178         reader = doc.reader
 2179         lines = []
 2180         while reader.has_more_lines?
 2181           lines << reader.read_line
 2182         end
 2183         assert_equal 'That version will do!', (lines * ::Asciidoctor::LF)
 2184       end
 2185 
 2186       test 'ifeval matching numeric equality includes content' do
 2187         input = <<~'EOS'
 2188         ifeval::[{rings} == 1]
 2189         One ring to rule them all!
 2190         endif::[]
 2191         EOS
 2192 
 2193         doc = Asciidoctor::Document.new input, attributes: { 'rings' => '1' }
 2194         reader = doc.reader
 2195         lines = []
 2196         while reader.has_more_lines?
 2197           lines << reader.read_line
 2198         end
 2199         assert_equal 'One ring to rule them all!', (lines * ::Asciidoctor::LF)
 2200       end
 2201 
 2202       test 'ifeval matching numeric inequality includes content' do
 2203         input = <<~'EOS'
 2204         ifeval::[{rings} != 0]
 2205         One ring to rule them all!
 2206         endif::[]
 2207         EOS
 2208 
 2209         doc = Asciidoctor::Document.new input, attributes: { 'rings' => '1' }
 2210         reader = doc.reader
 2211         lines = []
 2212         while reader.has_more_lines?
 2213           lines << reader.read_line
 2214         end
 2215         assert_equal 'One ring to rule them all!', (lines * ::Asciidoctor::LF)
 2216       end
 2217 
 2218       test 'should warn if ifeval has target' do
 2219         input = <<~'EOS'
 2220         ifeval::target[1 == 1]
 2221         content
 2222         EOS
 2223 
 2224         using_memory_logger do |logger|
 2225           doc = Asciidoctor::Document.new input
 2226           reader = doc.reader
 2227           lines = []
 2228           lines << reader.read_line while reader.has_more_lines?
 2229           assert_equal 'content', (lines * ::Asciidoctor::LF)
 2230           assert_message logger, :ERROR, '~<stdin>: line 1: malformed preprocessor directive - target not permitted: ifeval::target[1 == 1]', Hash
 2231         end
 2232       end
 2233 
 2234       test 'should warn if ifeval has invalid expression' do
 2235         input = <<~'EOS'
 2236         ifeval::[1 | 2]
 2237         content
 2238         EOS
 2239 
 2240         using_memory_logger do |logger|
 2241           doc = Asciidoctor::Document.new input
 2242           reader = doc.reader
 2243           lines = []
 2244           lines << reader.read_line while reader.has_more_lines?
 2245           assert_equal 'content', (lines * ::Asciidoctor::LF)
 2246           assert_message logger, :ERROR, '~<stdin>: line 1: malformed preprocessor directive - invalid expression: ifeval::[1 | 2]', Hash
 2247         end
 2248       end
 2249 
 2250       test 'should warn if ifeval is missing expression' do
 2251         input = <<~'EOS'
 2252         ifeval::[]
 2253         content
 2254         EOS
 2255 
 2256         using_memory_logger do |logger|
 2257           doc = Asciidoctor::Document.new input
 2258           reader = doc.reader
 2259           lines = []
 2260           lines << reader.read_line while reader.has_more_lines?
 2261           assert_equal 'content', (lines * ::Asciidoctor::LF)
 2262           assert_message logger, :ERROR, '~<stdin>: line 1: malformed preprocessor directive - missing expression: ifeval::[]', Hash
 2263         end
 2264       end
 2265 
 2266       test 'ifdef with no target is ignored' do
 2267         input = <<~'EOS'
 2268         ifdef::[]
 2269         content
 2270         EOS
 2271 
 2272         using_memory_logger do |logger|
 2273           doc = Asciidoctor::Document.new input
 2274           reader = doc.reader
 2275           lines = []
 2276           lines << reader.read_line while reader.has_more_lines?
 2277           assert_equal 'content', (lines * ::Asciidoctor::LF)
 2278           assert_message logger, :ERROR, '~<stdin>: line 1: malformed preprocessor directive - missing target: ifdef::[]', Hash
 2279         end
 2280       end
 2281 
 2282       test 'should not warn if preprocessor directive is invalid if already skipping' do
 2283         input = <<~'EOS'
 2284         ifdef::attribute-not-set[]
 2285         foo
 2286         ifdef::[]
 2287         bar
 2288         endif::[]
 2289         EOS
 2290 
 2291         using_memory_logger do |logger|
 2292           result = (Asciidoctor::Document.new input).reader.read
 2293           assert_empty result
 2294           assert_empty logger
 2295         end
 2296       end
 2297     end
 2298   end
 2299 end