# encoding: UTF-8 unless defined? ASCIIDOCTOR_PROJECT_DIR $: << File.dirname(__FILE__); $:.uniq! require 'test_helper' end class ReaderTest < Minitest::Test DIRNAME = File.expand_path(File.dirname(__FILE__)) SAMPLE_DATA = <<-EOS.chomp.split(::Asciidoctor::EOL) first line second line third line EOS context 'Reader' do context 'Prepare lines' do test 'should prepare lines from Array data' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert_equal SAMPLE_DATA, reader.lines end test 'should prepare lines from String data' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert_equal SAMPLE_DATA, reader.lines end test 'should remove UTF-8 BOM from first line of String data' do data = "\xef\xbb\xbf#{SAMPLE_DATA.join ::Asciidoctor::EOL}" reader = Asciidoctor::Reader.new data, nil, :normalize => true assert_equal 'f', reader.lines.first[0..0] assert_equal SAMPLE_DATA, reader.lines end test 'should remove UTF-8 BOM from first line of Array data' do data = SAMPLE_DATA.dup data[0] = "\xef\xbb\xbf#{data.first}" reader = Asciidoctor::Reader.new data, nil, :normalize => true assert_equal 'f', reader.lines.first[0..0] assert_equal SAMPLE_DATA, reader.lines end if Asciidoctor::COERCE_ENCODING test 'should encode UTF-16LE string to UTF-8 when BOM is found' do data = "\uFEFF#{SAMPLE_DATA.join ::Asciidoctor::EOL}".encode('UTF-16LE').force_encoding('UTF-8') reader = Asciidoctor::Reader.new data, nil, :normalize => true assert_equal 'f', reader.lines.first[0..0] assert_equal SAMPLE_DATA, reader.lines end test 'should encode UTF-16LE string array to UTF-8 when BOM is found' do data = "\uFEFF#{SAMPLE_DATA.join ::Asciidoctor::EOL}".encode('UTF-16LE').force_encoding('UTF-8').lines.to_a reader = Asciidoctor::Reader.new data, nil, :normalize => true assert_equal 'f', reader.lines.first[0..0] assert_equal SAMPLE_DATA, reader.lines end test 'should encode UTF-16BE string to UTF-8 when BOM is found' do data = "\uFEFF#{SAMPLE_DATA.join ::Asciidoctor::EOL}".encode('UTF-16BE').force_encoding('UTF-8') reader = Asciidoctor::Reader.new data, nil, :normalize => true assert_equal 'f', reader.lines.first[0..0] assert_equal SAMPLE_DATA, reader.lines end test 'should encode UTF-16BE string array to UTF-8 when BOM is found' do data = "\uFEFF#{SAMPLE_DATA.join ::Asciidoctor::EOL}".encode('UTF-16BE').force_encoding('UTF-8').lines.to_a reader = Asciidoctor::Reader.new data, nil, :normalize => true assert_equal 'f', reader.lines.first[0..0] assert_equal SAMPLE_DATA, reader.lines end end end context 'With empty data' do test 'has_more_lines? should return false with empty data' do assert !Asciidoctor::Reader.new.has_more_lines? end test 'empty? should return true with empty data' do assert Asciidoctor::Reader.new.empty? assert Asciidoctor::Reader.new.eof? end test 'next_line_empty? should return true with empty data' do assert Asciidoctor::Reader.new.next_line_empty? end test 'peek_line should return nil with empty data' do assert_nil Asciidoctor::Reader.new.peek_line end test 'peek_lines should return empty Array with empty data' do assert_equal [], Asciidoctor::Reader.new.peek_lines end test 'read_line should return nil with empty data' do assert_nil Asciidoctor::Reader.new.read_line #assert_nil Asciidoctor::Reader.new.get_line end test 'read_lines should return empty Array with empty data' do assert_equal [], Asciidoctor::Reader.new.read_lines #assert_equal [], Asciidoctor::Reader.new.get_lines end end context 'With data' do test 'has_more_lines? should return true if there are lines remaining' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert reader.has_more_lines? end test 'empty? should return false if there are lines remaining' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert !reader.empty? assert !reader.eof? end test 'next_line_empty? should return false if next line is not blank' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert !reader.next_line_empty? end test 'next_line_empty? should return true if next line is blank' do reader = Asciidoctor::Reader.new ['', 'second line'] assert reader.next_line_empty? end test 'peek_line should return next line if there are lines remaining' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert_equal SAMPLE_DATA.first, reader.peek_line end test 'peek_line should not consume line or increment line number' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert_equal SAMPLE_DATA.first, reader.peek_line assert_equal SAMPLE_DATA.first, reader.peek_line assert_equal 1, reader.lineno end test 'peek_line should return next lines if there are lines remaining' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert_equal SAMPLE_DATA[0..1], reader.peek_lines(2) end test 'peek_lines should not consume lines or increment line number' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert_equal SAMPLE_DATA[0..1], reader.peek_lines(2) assert_equal SAMPLE_DATA[0..1], reader.peek_lines(2) assert_equal 1, reader.lineno end test 'peek_lines should not invert order of lines' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert_equal SAMPLE_DATA, reader.lines reader.peek_lines 3 assert_equal SAMPLE_DATA, reader.lines end test 'read_line should return next line if there are lines remaining' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert_equal SAMPLE_DATA.first, reader.read_line end test 'read_line should consume next line and increment line number' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert_equal SAMPLE_DATA[0], reader.read_line assert_equal SAMPLE_DATA[1], reader.read_line assert_equal 3, reader.lineno end test 'advance should consume next line and return a Boolean indicating if a line was consumed' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert reader.advance assert reader.advance assert reader.advance assert !reader.advance end test 'read_lines should return all lines' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert_equal SAMPLE_DATA, reader.read_lines end test 'read should return all lines joined as String' do reader = Asciidoctor::Reader.new SAMPLE_DATA assert_equal SAMPLE_DATA.join(::Asciidoctor::EOL), reader.read end test 'has_more_lines? should return false after read_lines is invoked' do reader = Asciidoctor::Reader.new SAMPLE_DATA reader.read_lines assert !reader.has_more_lines? end test 'unshift puts line onto Reader as next line to read' do reader = Asciidoctor::Reader.new SAMPLE_DATA, nil, :normalize => true reader.unshift 'line zero' assert_equal 'line zero', reader.peek_line assert_equal 'line zero', reader.read_line assert_equal 1, reader.lineno end test 'terminate should consume all lines and update line number' do reader = Asciidoctor::Reader.new SAMPLE_DATA reader.terminate assert reader.eof? assert_equal 4, reader.lineno end test 'skip_blank_lines should skip blank lines' do reader = Asciidoctor::Reader.new ['', ''].concat(SAMPLE_DATA) reader.skip_blank_lines assert_equal SAMPLE_DATA.first, reader.peek_line end test 'lines should return remaining lines' do reader = Asciidoctor::Reader.new SAMPLE_DATA reader.read_line assert_equal SAMPLE_DATA[1..-1], reader.lines end test 'source_lines should return copy of original data Array' do reader = Asciidoctor::Reader.new SAMPLE_DATA reader.read_lines assert_equal SAMPLE_DATA, reader.source_lines end test 'source should return original data Array joined as String' do reader = Asciidoctor::Reader.new SAMPLE_DATA reader.read_lines assert_equal SAMPLE_DATA.join(::Asciidoctor::EOL), reader.source end end context 'Line context' do test 'to_s should return file name and line number of current line' do reader = Asciidoctor::Reader.new SAMPLE_DATA, 'sample.adoc' reader.read_line assert_equal 'sample.adoc: line 2', reader.to_s end test 'line_info should return file name and line number of current line' do reader = Asciidoctor::Reader.new SAMPLE_DATA, 'sample.adoc' reader.read_line assert_equal 'sample.adoc: line 2', reader.line_info assert_equal 'sample.adoc: line 2', reader.next_line_info end test 'prev_line_info should return file name and line number of previous line read' do reader = Asciidoctor::Reader.new SAMPLE_DATA, 'sample.adoc' reader.read_line assert_equal 'sample.adoc: line 1', reader.prev_line_info end end context 'Read lines until' do test 'Read lines until until end' do lines = <<-EOS.lines.entries This is one paragraph. This is another paragraph. EOS reader = Asciidoctor::Reader.new lines, nil, :normalize => true result = reader.read_lines_until assert_equal 3, result.size assert_equal lines.map {|l| l.chomp }, result assert !reader.has_more_lines? assert reader.eof? end test 'Read lines until until blank line' do lines = <<-EOS.lines.entries This is one paragraph. This is another paragraph. EOS reader = Asciidoctor::Reader.new lines, nil, :normalize => true result = reader.read_lines_until :break_on_blank_lines => true assert_equal 1, result.size assert_equal lines.first.chomp, result.first assert_equal lines.last.chomp, reader.peek_line end test 'Read lines until until blank line preserving last line' do lines = <<-EOS.chomp.split(::Asciidoctor::EOL) This is one paragraph. This is another paragraph. EOS reader = Asciidoctor::Reader.new lines result = reader.read_lines_until :break_on_blank_lines => true, :preserve_last_line => true assert_equal 1, result.size assert_equal lines.first.chomp, result.first assert reader.next_line_empty? end test 'Read lines until until condition is true' do lines = <<-EOS.chomp.split(::Asciidoctor::EOL) -- This is one paragraph inside the block. This is another paragraph inside the block. -- This is a paragraph outside the block. EOS reader = Asciidoctor::Reader.new lines reader.read_line result = reader.read_lines_until {|line| line == '--' } assert_equal 3, result.size assert_equal lines[1, 3], result assert reader.next_line_empty? end test 'Read lines until until condition is true, taking last line' do lines = <<-EOS.chomp.split(::Asciidoctor::EOL) -- This is one paragraph inside the block. This is another paragraph inside the block. -- This is a paragraph outside the block. EOS reader = Asciidoctor::Reader.new lines reader.read_line result = reader.read_lines_until(:read_last_line => true) {|line| line == '--' } assert_equal 4, result.size assert_equal lines[1, 4], result assert reader.next_line_empty? end test 'Read lines until until condition is true, taking and preserving last line' do lines = <<-EOS.chomp.split(::Asciidoctor::EOL) -- This is one paragraph inside the block. This is another paragraph inside the block. -- This is a paragraph outside the block. EOS reader = Asciidoctor::Reader.new lines reader.read_line result = reader.read_lines_until(:read_last_line => true, :preserve_last_line => true) {|line| line == '--' } assert_equal 4, result.size assert_equal lines[1, 4], result assert_equal '--', reader.peek_line end end end context 'PreprocessorReader' do context 'Type hierarchy' do test 'PreprocessorReader should extend from Reader' do reader = empty_document.reader assert reader.is_a?(Asciidoctor::Reader) end test 'PreprocessorReader should invoke or emulate Reader initializer' do doc = Asciidoctor::Document.new SAMPLE_DATA reader = doc.reader assert_equal SAMPLE_DATA, reader.lines assert_equal 1, reader.lineno end end context 'Prepare lines' do test 'should prepare and normalize lines from Array data' do data = SAMPLE_DATA.map {|line| line.chomp} data.unshift '' data.push '' doc = Asciidoctor::Document.new data reader = doc.reader assert_equal SAMPLE_DATA, reader.lines end test 'should prepare and normalize lines from String data' do data = SAMPLE_DATA.map {|line| line.chomp} data.unshift ' ' data.push ' ' data_as_string = data * ::Asciidoctor::EOL doc = Asciidoctor::Document.new data_as_string reader = doc.reader assert_equal SAMPLE_DATA, reader.lines end test 'should clean CRLF from end of lines' do input = <<-EOS source\r with\r CRLF\r endlines\r EOS [input, input.lines.to_a, input.split(::Asciidoctor::EOL), input.split(::Asciidoctor::EOL).join(::Asciidoctor::EOL)].each do |lines| doc = Asciidoctor::Document.new lines reader = doc.reader reader.lines.each do |line| assert !line.end_with?("\r"), "CRLF not properly cleaned for source lines: #{lines.inspect}" assert !line.end_with?("\r\n"), "CRLF not properly cleaned for source lines: #{lines.inspect}" assert !line.end_with?("\n"), "CRLF not properly cleaned for source lines: #{lines.inspect}" end end end test 'should not skip front matter by default' do input = <<-EOS --- layout: post title: Document Title author: username tags: [ first, second ] --- = Document Title Author Name preamble EOS doc = Asciidoctor::Document.new input reader = doc.reader assert !doc.attributes.key?('front-matter') assert_equal '---', reader.peek_line end test 'should skip front matter if specified by skip-front-matter attribute' do front_matter = %(layout: post title: Document Title author: username tags: [ first, second ]) input = <<-EOS --- #{front_matter} --- = Document Title Author Name preamble EOS doc = Asciidoctor::Document.new input, :attributes => {'skip-front-matter' => ''} reader = doc.reader assert_equal '= Document Title', reader.peek_line assert_equal front_matter, doc.attributes['front-matter'] end end context 'Include Stack' do test 'PreprocessorReader#push_include method should return reader' do reader = empty_document.reader append_lines = %w(one two three) result = reader.push_include append_lines, '', '' assert_equal reader, result end test 'PreprocessorReader#push_include method should put lines on top of stack' do lines = %w(a b c) doc = Asciidoctor::Document.new lines reader = doc.reader append_lines = %w(one two three) reader.push_include append_lines, '', '' assert_equal 1, reader.include_stack.size assert_equal 'one', reader.read_line.rstrip end test 'PreprocessorReader#push_include method should gracefully handle file and path' do lines = %w(a b c) doc = Asciidoctor::Document.new lines reader = doc.reader append_lines = %w(one two three) reader.push_include append_lines assert_equal 1, reader.include_stack.size assert_equal 'one', reader.read_line.rstrip assert_nil reader.file assert_equal '', reader.path end end context 'Include Directive' do test 'include directive is disabled by default and becomes a link' do input = <<-EOS include::include-file.asciidoc[] EOS doc = Asciidoctor::Document.new input reader = doc.reader assert_equal 'link:include-file.asciidoc[]', reader.read_line end test 'include directive is enabled when safe mode is less than SECURE' do input = <<-EOS include::fixtures/include-file.asciidoc[] EOS doc = document_from_string input, :safe => :safe, :header_footer => false, :base_dir => DIRNAME output = doc.render assert_match(/included content/, output) end test 'include directive should resolve file with spaces in name' do input = <<-EOS include::fixtures/include file.asciidoc[] EOS include_file = File.join DIRNAME, 'fixtures', 'include-file.asciidoc' include_file_with_sp = File.join DIRNAME, 'fixtures', 'include file.asciidoc' begin FileUtils.cp include_file, include_file_with_sp doc = document_from_string input, :safe => :safe, :header_footer => false, :base_dir => DIRNAME output = doc.render assert_match(/included content/, output) ensure FileUtils.rm include_file_with_sp end end test 'include directive should resolve file with {sp} in name' do input = <<-EOS include::fixtures/include{sp}file.asciidoc[] EOS include_file = File.join DIRNAME, 'fixtures', 'include-file.asciidoc' include_file_with_sp = File.join DIRNAME, 'fixtures', 'include file.asciidoc' begin FileUtils.cp include_file, include_file_with_sp doc = document_from_string input, :safe => :safe, :header_footer => false, :base_dir => DIRNAME output = doc.render assert_match(/included content/, output) ensure FileUtils.rm include_file_with_sp end end test 'include directive should resolve file relative to current include' do input = <<-EOS include::fixtures/parent-include.adoc[] EOS pseudo_docfile = File.join DIRNAME, 'include-master.adoc' fixtures_dir = File.join DIRNAME, 'fixtures' parent_include_docfile = File.join fixtures_dir, 'parent-include.adoc' child_include_docfile = File.join fixtures_dir, 'child-include.adoc' grandchild_include_docfile = File.join fixtures_dir, 'grandchild-include.adoc' doc = empty_safe_document :base_dir => DIRNAME reader = Asciidoctor::PreprocessorReader.new doc, input, pseudo_docfile assert_equal pseudo_docfile, reader.file assert_equal DIRNAME, reader.dir assert_equal 'include-master.adoc', reader.path assert_equal 'first line of parent', reader.read_line assert_equal 'fixtures/parent-include.adoc: line 1', reader.prev_line_info assert_equal parent_include_docfile, reader.file assert_equal fixtures_dir, reader.dir assert_equal 'fixtures/parent-include.adoc', reader.path reader.skip_blank_lines assert_equal 'first line of child', reader.read_line assert_equal 'fixtures/child-include.adoc: line 1', reader.prev_line_info assert_equal child_include_docfile, reader.file assert_equal fixtures_dir, reader.dir assert_equal 'fixtures/child-include.adoc', reader.path reader.skip_blank_lines assert_equal 'first line of grandchild', reader.read_line assert_equal 'fixtures/grandchild-include.adoc: line 1', reader.prev_line_info assert_equal grandchild_include_docfile, reader.file assert_equal fixtures_dir, reader.dir assert_equal 'fixtures/grandchild-include.adoc', reader.path reader.skip_blank_lines assert_equal 'last line of grandchild', reader.read_line reader.skip_blank_lines assert_equal 'last line of child', reader.read_line reader.skip_blank_lines assert_equal 'last line of parent', reader.read_line assert_equal 'fixtures/parent-include.adoc: line 5', reader.prev_line_info assert_equal parent_include_docfile, reader.file assert_equal fixtures_dir, reader.dir assert_equal 'fixtures/parent-include.adoc', reader.path end test 'missing file referenced by include directive is replaced by warning' do input = <<-EOS include::fixtures/no-such-file.adoc[] trailing content EOS begin doc = document_from_string input, :safe => :safe, :base_dir => DIRNAME assert_equal 2, doc.blocks.size assert_equal ['Unresolved directive in - include::fixtures/no-such-file.adoc[]'], doc.blocks[0].lines assert_equal ['trailing content'], doc.blocks[1].lines rescue flunk 'include directive should not raise exception on missing file' end end test 'unreadable file referenced by include directive is replaced by warning' do include_file = File.join DIRNAME, 'fixtures', 'chapter-a.adoc' FileUtils.chmod 0000, include_file input = <<-EOS include::fixtures/chapter-a.adoc[] trailing content EOS begin doc = document_from_string input, :safe => :safe, :base_dir => DIRNAME assert_equal 2, doc.blocks.size assert_equal ['Unresolved directive in - include::fixtures/chapter-a.adoc[]'], doc.blocks[0].lines assert_equal ['trailing content'], doc.blocks[1].lines rescue flunk 'include directive should not raise exception on missing file' ensure FileUtils.chmod 0644, include_file end end unless windows? # IMPORTANT this test needs to be run on Windows to verify proper behavior in Windows test 'can resolve include directive with absolute path' do include_path = ::File.join DIRNAME, 'fixtures', 'chapter-a.adoc' input = <<-EOS include::#{include_path}[] EOS result = document_from_string input, :safe => :safe assert_equal 'Chapter A', result.doctitle result = document_from_string input, :safe => :unsafe, :base_dir => ::Dir.tmpdir assert_equal 'Chapter A', result.doctitle end test 'include directive can retrieve data from uri' do #url = 'http://echo.jsontest.com/name/asciidoctor' url = %(http://#{resolve_localhost}:9876/name/asciidoctor) input = <<-EOS .... include::#{url}[] .... EOS expect = /\{"name": "asciidoctor"\}/ output = using_test_webserver do render_embedded_string input, :safe => :safe, :attributes => {'allow-uri-read' => ''} end refute_nil output assert_match(expect, output) end test 'inaccessible uri referenced by include directive does not crash processor' do url = %(http://#{resolve_localhost}:9876/no_such_file) input = <<-EOS .... include::#{url}[] .... EOS output = begin using_test_webserver do render_embedded_string input, :safe => :safe, :attributes => {'allow-uri-read' => ''} end rescue flunk 'include directive should not raise exception on inaccessible uri' end refute_nil output assert_match(/Unresolved directive/, output) end test 'include directive supports line selection' do input = <<-EOS include::fixtures/include-file.asciidoc[lines=1;3..4;6..-1] EOS output = render_string input, :safe => :safe, :header_footer => false, :base_dir => DIRNAME assert_match(/first line/, output) refute_match(/second line/, output) assert_match(/third line/, output) assert_match(/fourth line/, output) refute_match(/fifth line/, output) assert_match(/sixth line/, output) assert_match(/seventh line/, output) assert_match(/eighth line/, output) assert_match(/last line of included content/, output) end test 'include directive supports line selection using quoted attribute value' do input = <<-EOS include::fixtures/include-file.asciidoc[lines="1, 3..4 , 6 .. -1"] EOS output = render_string input, :safe => :safe, :header_footer => false, :base_dir => DIRNAME assert_match(/first line/, output) refute_match(/second line/, output) assert_match(/third line/, output) assert_match(/fourth line/, output) refute_match(/fifth line/, output) assert_match(/sixth line/, output) assert_match(/seventh line/, output) assert_match(/eighth line/, output) assert_match(/last line of included content/, output) end test 'include directive supports tagged selection' do input = <<-EOS include::fixtures/include-file.asciidoc[tag=snippetA] EOS output = render_string input, :safe => :safe, :header_footer => false, :base_dir => DIRNAME assert_match(/snippetA content/, output) refute_match(/snippetB content/, output) refute_match(/non-tagged content/, output) refute_match(/included content/, output) end test 'include directive supports multiple tagged selection' do input = <<-EOS include::fixtures/include-file.asciidoc[tags=snippetA;snippetB] EOS output = render_string input, :safe => :safe, :header_footer => false, :base_dir => DIRNAME assert_match(/snippetA content/, output) assert_match(/snippetB content/, output) refute_match(/non-tagged content/, output) refute_match(/included content/, output) end test 'include directive supports tagged selection in XML file' do input = <<-EOS [source,xml,indent=0] ---- include::fixtures/include-file.xml[tag=snippet] ---- EOS output = render_string input, :safe => :safe, :header_footer => false, :base_dir => DIRNAME assert_match('<snippet>content</snippet>', output) refute_match('root', output) end test 'include directive does not select tagged lines inside tagged selection' do input = <<-EOS ++++ include::fixtures/include-file.asciidoc[tags=snippet] ++++ EOS output = render_embedded_string input, :safe => :safe, :base_dir => DIRNAME expect = %(snippetA content non-tagged content snippetB content) assert_equal expect, output end test 'should warn if tag is not found in include file' do input = <<-EOS include::fixtures/include-file.asciidoc[tag=snippetZ] EOS old_stderr = $stderr $stderr = StringIO.new begin render_string input, :safe => :safe, :header_footer => false, :base_dir => DIRNAME warning = $stderr.tap(&:rewind).read refute_nil warning assert_match(/WARNING.*snippetZ/, warning) ensure $stderr = old_stderr end end test 'lines attribute takes precedence over tags attribute in include directive' do input = <<-EOS include::fixtures/include-file.asciidoc[lines=1, tags=snippetA;snippetB] EOS output = render_string input, :safe => :safe, :header_footer => false, :base_dir => DIRNAME assert_match(/first line of included content/, output) refute_match(/snippetA content/, output) refute_match(/snippetB content/, output) end test 'indent of included file can be reset to size of indent attribute' do input = <<-EOS [source, xml] ---- include::fixtures/basic-docinfo.xml[lines=2..3, indent=0] ---- EOS output = render_string input, :safe => :safe, :header_footer => false, :base_dir => DIRNAME result = xmlnodes_at_xpath('//pre', output, 1).text assert_equal "2013\nAcme™, Inc.", result end test 'should fall back to built-in include directive behavior when not handled by include processor' do input = <<-EOS include::fixtures/include-file.asciidoc[] EOS include_processor = Class.new { def initialize document end def handles? target false end def process reader, target, attributes raise 'TestIncludeHandler should not have been invoked' end } document = empty_safe_document :base_dir => DIRNAME reader = Asciidoctor::PreprocessorReader.new document, input reader.instance_variable_set '@include_processors', [include_processor.new(document)] lines = reader.read_lines source = lines * ::Asciidoctor::EOL assert_match(/included content/, source) end test 'leveloffset attribute entries should be added to content if leveloffset attribute is specified' do input = <<-EOS include::fixtures/master.adoc[] EOS expected = <<-EOS.chomp.split(::Asciidoctor::EOL) = Master Document preamble :leveloffset: +1 = Chapter A content :leveloffset!: EOS document = Asciidoctor.load input, :safe => :safe, :base_dir => DIRNAME, :parse => false assert_equal expected, document.reader.read_lines end test 'attributes are substituted in target of include directive' do input = <<-EOS :fixturesdir: fixtures :ext: asciidoc include::{fixturesdir}/include-file.{ext}[] EOS doc = document_from_string input, :safe => :safe, :base_dir => DIRNAME output = doc.render assert_match(/included content/, output) end test 'line is skipped by default if target of include directive resolves to empty' do input = <<-EOS include::{foodir}/include-file.asciidoc[] EOS doc = empty_safe_document :base_dir => DIRNAME reader = Asciidoctor::PreprocessorReader.new doc, input assert_equal 'Unresolved directive in - include::{foodir}/include-file.asciidoc[]', reader.read_line end test 'line is dropped if target of include directive resolves to empty and attribute-missing attribute is not skip' do input = <<-EOS include::{foodir}/include-file.asciidoc[] EOS doc = empty_safe_document :base_dir => DIRNAME, :attributes => {'attribute-missing' => 'drop'} reader = Asciidoctor::PreprocessorReader.new doc, input assert_nil reader.read_line end test 'line following dropped include is not dropped' do input = <<-EOS include::{foodir}/include-file.asciidoc[] yo EOS doc = empty_safe_document :base_dir => DIRNAME, :attributes => {'attribute-missing' => 'drop'} reader = Asciidoctor::PreprocessorReader.new doc, input assert_equal 'yo', reader.read_line end test 'escaped include directive is left unprocessed' do input = <<-EOS \\include::fixtures/include-file.asciidoc[] \\escape preserved here EOS doc = empty_safe_document :base_dir => DIRNAME reader = Asciidoctor::PreprocessorReader.new doc, input # we should be able to peek it multiple times and still have the backslash preserved # this is the test for @unescape_next_line assert_equal 'include::fixtures/include-file.asciidoc[]', reader.peek_line assert_equal 'include::fixtures/include-file.asciidoc[]', reader.peek_line assert_equal 'include::fixtures/include-file.asciidoc[]', reader.read_line assert_equal '\\escape preserved here', reader.read_line end test 'include directive not at start of line is ignored' do input = <<-EOS include::include-file.asciidoc[] EOS para = block_from_string input assert_equal 1, para.lines.size # NOTE the space gets stripped because the line is treated as an inline literal assert_equal :literal, para.context assert_equal 'include::include-file.asciidoc[]', para.source end test 'include directive is disabled when max-include-depth attribute is 0' do input = <<-EOS include::include-file.asciidoc[] EOS para = block_from_string input, :safe => :safe, :attributes => { 'max-include-depth' => 0 } assert_equal 1, para.lines.size assert_equal 'include::include-file.asciidoc[]', para.source end test 'max-include-depth cannot be set by document' do input = <<-EOS :max-include-depth: 1 include::include-file.asciidoc[] EOS para = block_from_string input, :safe => :safe, :attributes => { 'max-include-depth' => 0 } assert_equal 1, para.lines.size assert_equal 'include::include-file.asciidoc[]', para.source end test 'include directive should be disabled if max include depth has been exceeded' do input = <<-EOS include::fixtures/parent-include.adoc[depth=1] EOS pseudo_docfile = File.join DIRNAME, 'include-master.adoc' doc = empty_safe_document :base_dir => DIRNAME reader = Asciidoctor::PreprocessorReader.new doc, input, Asciidoctor::Reader::Cursor.new(pseudo_docfile) lines = reader.readlines assert lines.include?('include::child-include.adoc[]') end test 'include directive should be disabled if max include depth set in nested context has been exceeded' do input = <<-EOS include::fixtures/parent-include-restricted.adoc[depth=3] EOS pseudo_docfile = File.join DIRNAME, 'include-master.adoc' doc = empty_safe_document :base_dir => DIRNAME reader = Asciidoctor::PreprocessorReader.new doc, input, Asciidoctor::Reader::Cursor.new(pseudo_docfile) lines = reader.readlines assert lines.include?('first line of child') assert lines.include?('include::grandchild-include.adoc[]') end test 'read_lines_until should not process lines if process option is false' do lines = <<-EOS.each_line.to_a //// include::fixtures/no-such-file.adoc[] //// EOS doc = empty_safe_document :base_dir => DIRNAME reader = Asciidoctor::PreprocessorReader.new doc, lines reader.read_line result = reader.read_lines_until(:terminator => '////', :skip_processing => true) assert_equal lines.map {|l| l.chomp}[1..1], result end test 'skip_comment_lines should not process lines read' do lines = <<-EOS.each_line.to_a //// include::fixtures/no-such-file.adoc[] //// EOS doc = empty_safe_document :base_dir => DIRNAME reader = Asciidoctor::PreprocessorReader.new doc, lines result = reader.skip_comment_lines assert_equal lines.map {|l| l.chomp}, result end end context 'Conditional Inclusions' do test 'process_line returns nil if cursor advanced' do input = <<-EOS ifdef::asciidoctor[] Asciidoctor! endif::asciidoctor[] EOS doc = Asciidoctor::Document.new input reader = doc.reader assert_nil reader.process_line(reader.lines.first) end test 'peek_line advances cursor to next conditional line of content' do input = <<-EOS ifdef::asciidoctor[] Asciidoctor! endif::asciidoctor[] EOS doc = Asciidoctor::Document.new input reader = doc.reader assert_equal 1, reader.lineno assert_equal 'Asciidoctor!', reader.peek_line assert_equal 2, reader.lineno end test 'process_line returns line if cursor not advanced' do input = <<-EOS content ifdef::asciidoctor[] Asciidoctor! endif::asciidoctor[] EOS doc = Asciidoctor::Document.new input reader = doc.reader refute_nil reader.process_line(reader.lines.first) end test 'peek_line does not advance cursor when on a regular content line' do input = <<-EOS content ifdef::asciidoctor[] Asciidoctor! endif::asciidoctor[] EOS doc = Asciidoctor::Document.new input reader = doc.reader assert_equal 1, reader.lineno assert_equal 'content', reader.peek_line assert_equal 1, reader.lineno end test 'peek_line returns nil if cursor advances past end of source' do input = <<-EOS ifdef::foobar[] swallowed content endif::foobar[] EOS doc = Asciidoctor::Document.new input reader = doc.reader assert_equal 1, reader.lineno assert_nil reader.peek_line assert_equal 4, reader.lineno end test 'ifdef with defined attribute includes content' do input = <<-EOS ifdef::holygrail[] There is a holy grail! endif::holygrail[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'holygrail' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'There is a holy grail!', (lines * ::Asciidoctor::EOL) end test 'ifdef with defined attribute includes text in brackets' do input = <<-EOS On our quest we go... ifdef::holygrail[There is a holy grail!] There was much rejoicing. EOS doc = Asciidoctor::Document.new input, :attributes => { 'holygrail' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal "On our quest we go...\nThere is a holy grail!\nThere was much rejoicing.", (lines * ::Asciidoctor::EOL) end test 'ifdef attribute name is not case sensitive' do input = <<-EOS ifdef::showScript[] The script is shown! endif::showScript[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'showscript' => '' } result = doc.reader.read assert_equal 'The script is shown!', result end test 'ifndef with defined attribute does not include text in brackets' do input = <<-EOS On our quest we go... ifndef::hardships[There is a holy grail!] There was no rejoicing. EOS doc = Asciidoctor::Document.new input, :attributes => { 'hardships' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal "On our quest we go...\nThere was no rejoicing.", (lines * ::Asciidoctor::EOL) end test 'include with non-matching nested exclude' do input = <<-EOS ifdef::grail[] holy ifdef::swallow[] swallow endif::swallow[] grail endif::grail[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'grail' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal "holy\ngrail", (lines * ::Asciidoctor::EOL) end test 'nested excludes with same condition' do input = <<-EOS ifndef::grail[] ifndef::grail[] not here endif::grail[] endif::grail[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'grail' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal '', (lines * ::Asciidoctor::EOL) end test 'include with nested exclude of inverted condition' do input = <<-EOS ifdef::grail[] holy ifndef::grail[] not here endif::grail[] grail endif::grail[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'grail' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal "holy\ngrail", (lines * ::Asciidoctor::EOL) end test 'exclude with matching nested exclude' do input = <<-EOS poof ifdef::swallow[] no ifdef::swallow[] swallow endif::swallow[] here endif::swallow[] gone EOS doc = Asciidoctor::Document.new input, :attributes => { 'grail' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal "poof\ngone", (lines * ::Asciidoctor::EOL) end test 'exclude with nested include using shorthand end' do input = <<-EOS poof ifndef::grail[] no grail ifndef::swallow[] or swallow endif::[] in here endif::[] gone EOS doc = Asciidoctor::Document.new input, :attributes => { 'grail' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal "poof\ngone", (lines * ::Asciidoctor::EOL) end test 'ifdef with one alternative attribute set includes content' do input = <<-EOS ifdef::holygrail,swallow[] Our quest is complete! endif::holygrail,swallow[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'swallow' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'Our quest is complete!', (lines * ::Asciidoctor::EOL) end test 'ifdef with no alternative attributes set does not include content' do input = <<-EOS ifdef::holygrail,swallow[] Our quest is complete! endif::holygrail,swallow[] EOS doc = Asciidoctor::Document.new input reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal '', (lines * ::Asciidoctor::EOL) end test 'ifdef with all required attributes set includes content' do input = <<-EOS ifdef::holygrail+swallow[] Our quest is complete! endif::holygrail+swallow[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'holygrail' => '', 'swallow' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'Our quest is complete!', (lines * ::Asciidoctor::EOL) end test 'ifdef with missing required attributes does not include content' do input = <<-EOS ifdef::holygrail+swallow[] Our quest is complete! endif::holygrail+swallow[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'holygrail' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal '', (lines * ::Asciidoctor::EOL) end test 'ifndef with undefined attribute includes block' do input = <<-EOS ifndef::holygrail[] Our quest continues to find the holy grail! endif::holygrail[] EOS doc = Asciidoctor::Document.new input reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'Our quest continues to find the holy grail!', (lines * ::Asciidoctor::EOL) end test 'ifndef with one alternative attribute set includes content' do input = <<-EOS ifndef::holygrail,swallow[] Our quest is complete! endif::holygrail,swallow[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'swallow' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'Our quest is complete!', (lines * ::Asciidoctor::EOL) end test 'ifndef with no alternative attributes set includes content' do input = <<-EOS ifndef::holygrail,swallow[] Our quest is complete! endif::holygrail,swallow[] EOS doc = Asciidoctor::Document.new input reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'Our quest is complete!', (lines * ::Asciidoctor::EOL) end test 'ifndef with any required attributes set does not include content' do input = <<-EOS ifndef::holygrail+swallow[] Our quest is complete! endif::holygrail+swallow[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'swallow' => '' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal '', (lines * ::Asciidoctor::EOL) end test 'ifndef with no required attributes set includes content' do input = <<-EOS ifndef::holygrail+swallow[] Our quest is complete! endif::holygrail+swallow[] EOS doc = Asciidoctor::Document.new input reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'Our quest is complete!', (lines * ::Asciidoctor::EOL) end test 'escaped ifdef is unescaped and ignored' do input = <<-EOS \\ifdef::holygrail[] content \\endif::holygrail[] EOS doc = Asciidoctor::Document.new input reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal "ifdef::holygrail[]\ncontent\nendif::holygrail[]", (lines * ::Asciidoctor::EOL) end test 'ifeval comparing missing attribute to nil includes content' do input = <<-EOS ifeval::['{foo}' == ''] No foo for you! endif::[] EOS doc = Asciidoctor::Document.new input reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'No foo for you!', (lines * ::Asciidoctor::EOL) end test 'ifeval comparing missing attribute to 0 drops content' do input = <<-EOS ifeval::[{leveloffset} == 0] I didn't make the cut! endif::[] EOS doc = Asciidoctor::Document.new input reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal '', (lines * ::Asciidoctor::EOL) end test 'ifeval comparing double-quoted attribute to matching string includes content' do input = <<-EOS ifeval::["{gem}" == "asciidoctor"] Asciidoctor it is! endif::[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'gem' => 'asciidoctor' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'Asciidoctor it is!', (lines * ::Asciidoctor::EOL) end test 'ifeval comparing single-quoted attribute to matching string includes content' do input = <<-EOS ifeval::['{gem}' == 'asciidoctor'] Asciidoctor it is! endif::[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'gem' => 'asciidoctor' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'Asciidoctor it is!', (lines * ::Asciidoctor::EOL) end test 'ifeval comparing quoted attribute to non-matching string drops content' do input = <<-EOS ifeval::['{gem}' == 'asciidoctor'] Asciidoctor it is! endif::[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'gem' => 'tilt' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal '', (lines * ::Asciidoctor::EOL) end test 'ifeval comparing attribute to lower version number includes content' do input = <<-EOS ifeval::['{asciidoctor-version}' >= '0.1.0'] That version will do! endif::[] EOS doc = Asciidoctor::Document.new input reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'That version will do!', (lines * ::Asciidoctor::EOL) end test 'ifeval comparing attribute to self includes content' do input = <<-EOS ifeval::['{asciidoctor-version}' == '{asciidoctor-version}'] Of course it's the same! endif::[] EOS doc = Asciidoctor::Document.new input reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'Of course it\'s the same!', (lines * ::Asciidoctor::EOL) end test 'ifeval arguments can be transposed' do input = <<-EOS ifeval::['0.1.0' <= '{asciidoctor-version}'] That version will do! endif::[] EOS doc = Asciidoctor::Document.new input reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'That version will do!', (lines * ::Asciidoctor::EOL) end test 'ifeval matching numeric equality includes content' do input = <<-EOS ifeval::[{rings} == 1] One ring to rule them all! endif::[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'rings' => '1' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'One ring to rule them all!', (lines * ::Asciidoctor::EOL) end test 'ifeval matching numeric inequality includes content' do input = <<-EOS ifeval::[{rings} != 0] One ring to rule them all! endif::[] EOS doc = Asciidoctor::Document.new input, :attributes => { 'rings' => '1' } reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal 'One ring to rule them all!', (lines * ::Asciidoctor::EOL) end test 'ifdef with no target is ignored' do input = <<-EOS ifdef::[] content EOS doc = Asciidoctor::Document.new input reader = doc.reader lines = [] while reader.has_more_lines? lines << reader.read_line end assert_equal "ifdef::[]\ncontent", (lines * ::Asciidoctor::EOL) end end end end