diff options
| author | Dan Allen <dan.j.allen@gmail.com> | 2012-12-17 03:20:30 -0700 |
|---|---|---|
| committer | Dan Allen <dan.j.allen@gmail.com> | 2012-12-17 03:20:30 -0700 |
| commit | 34e6ad2a2da41b759ccaa26c4b73d0ffecbdce42 (patch) | |
| tree | e5143f46d76813387403ff0aa0d6614753cb649c | |
| parent | e4102d5b8244e2c2573065701adabc9d196af3ed (diff) | |
support book doctype, streamline doc title accessors
- add doctype accessor to Document
- support the book doctype (multiple level-0 headings)
- polish and document logic for retrieving the document and header titles
- subtitute attributes in section titles
- tests for everything mentioned above
| -rw-r--r-- | lib/asciidoctor/document.rb | 65 | ||||
| -rw-r--r-- | lib/asciidoctor/lexer.rb | 4 | ||||
| -rw-r--r-- | lib/asciidoctor/render_templates.rb | 21 | ||||
| -rw-r--r-- | lib/asciidoctor/section.rb | 4 | ||||
| -rw-r--r-- | test/attributes_test.rb | 7 | ||||
| -rw-r--r-- | test/document_test.rb | 23 | ||||
| -rw-r--r-- | test/headers_test.rb | 107 | ||||
| -rw-r--r-- | test/preamble_test.rb | 24 |
8 files changed, 195 insertions, 60 deletions
diff --git a/lib/asciidoctor/document.rb b/lib/asciidoctor/document.rb index c0aa7ef1..676052bb 100644 --- a/lib/asciidoctor/document.rb +++ b/lib/asciidoctor/document.rb @@ -1,5 +1,20 @@ # Public: Methods for parsing Asciidoc documents and rendering them # using erb templates. +# +# There are several strategies for getting the title of the document: +# +# doctitle - value of title attribute, if assigned and non-empty, +# otherwise title of first section in document, if present +# otherwise nil +# name - an alias of doctitle +# title - value of the title attribute, or nil if not present +# first_section.title - title of first section in document, if present +# header.title - title of section level 0 +# +# Keep in mind that you'll want to honor these document settings: +# +# notitle - The h1 heading should not be shown +# noheader - The header block (h1 heading, author, revision info) should not be shown class Asciidoctor::Document include Asciidoctor @@ -18,7 +33,9 @@ class Asciidoctor::Document # Public: Initialize an Asciidoc object. # - # data - The Array of Strings holding the Asciidoc source document. + # data - The Array of Strings holding the Asciidoc source document. + # options - A Hash of options to control processing, such as disabling + # the header/footer (:header_footer) or attribute overrides (:attributes) # block - A block that can be used to retrieve external Asciidoc # data to include in this document. # @@ -38,7 +55,6 @@ class Asciidoctor::Document @reader = Reader.new(data, @attributes, &block) # pseudo-delegation :) - #@attributes = @reader.attributes @references = @reader.references # dynamic intrinstic attribute values @@ -48,6 +64,9 @@ class Asciidoctor::Document @attributes['localtime'] ||= now.strftime('%H:%m:%S %Z') @attributes['localdatetime'] ||= [@attributes['localdate'], @attributes['localtime']].join(' ') @attributes['asciidoctor-version'] = VERSION + if options.has_key? :attributes + @attributes.update(options[:attributes]) + end # Now parse @lines into elements while @reader.has_lines? @@ -65,7 +84,13 @@ class Asciidoctor::Document root = @elements.first if root.is_a?(Section) && root.level == 0 @header = @elements.shift - @elements = @header.blocks + # a book has multiple level 0 sections + if doctype == 'book' + @elements = @header.blocks + @elements + # an article only has one level 0 section + else + @elements = @header.blocks + end @header.clear_blocks end @@ -86,6 +111,10 @@ class Asciidoctor::Document #@attributes.has_key? name.to_s.tr('_', '-') end + def doctype + @attributes['doctype'] + end + def level 0 end @@ -97,16 +126,13 @@ class Asciidoctor::Document # We need to be able to return some semblance of a title def doctitle - # cached value - return @doctitle if @doctitle - - if @header - @doctitle = @header.title - elsif @elements.first - @doctitle = @elements.first.title + if !(title = @attributes.fetch('title', '')).empty? + title + elsif !(sect = first_section).nil? && !sect.title.empty? + sect.title + else + nil end - - @doctitle end alias :name :doctitle @@ -114,6 +140,18 @@ class Asciidoctor::Document @attributes.has_key? 'notitle' end + def noheader + @attributes.has_key? 'noheader' + end + + def first_section + has_header ? @header : @elements.detect{|e| e.is_a? Section} + end + + def has_header + !@header.nil? + end + def splain if @header Asciidoctor.debug "Header is #{@header}" @@ -155,6 +193,9 @@ class Asciidoctor::Document end def content + # per AsciiDoc-spec, remove the title after rendering the header + @attributes.delete('title') + html_pieces = [] @elements.each do |element| Asciidoctor::debug "Rendering element: #{element}" diff --git a/lib/asciidoctor/lexer.rb b/lib/asciidoctor/lexer.rb index b1ff83a4..8c13b237 100644 --- a/lib/asciidoctor/lexer.rb +++ b/lib/asciidoctor/lexer.rb @@ -754,8 +754,8 @@ class Asciidoctor::Lexer end # detect preamble and push it into a block - # QUESTION make this an operation on Section? - if section.level == 0 + # QUESTION make this an operation on Section named extract_preamble? + if section.level == 0 && parent.is_a?(Document) && parent.elements.empty? blocks = section.blocks.take_while {|b| !b.is_a? Section} if !blocks.empty? # QUESTION Should we propagate the buffer? diff --git a/lib/asciidoctor/render_templates.rb b/lib/asciidoctor/render_templates.rb index fe28dd53..0a263f63 100644 --- a/lib/asciidoctor/render_templates.rb +++ b/lib/asciidoctor/render_templates.rb @@ -57,7 +57,7 @@ class DocumentTemplate < BaseTemplate <meta name='generator' content='Asciidoctor <%= attr 'asciidoctor-version' %>'> <% if attr? :description %><meta name='description' content='<%= attr :description %>'><% end %> <% if attr? :keywords %><meta name='keywords' content='<%= attr :keywords %>'><% end %> - <title><%= title ? title : (doctitle ? doctitle : '') %></title> + <title><%= doctitle %></title> <% if attr?(:stylesheet) %> <% if attr(:stylesheet).empty? %> <style> @@ -68,13 +68,15 @@ class DocumentTemplate < BaseTemplate <% end %> <% end %> </head> - <body class='<%= attr :doctype %>'> + <body class='<%= doctype %>'> + <% unless noheader %> <div id='header'> - <% if !notitle && doctitle %> - <h1><%= doctitle %></h1> - <% if attr? :author %><span id='author'><%= attr :author %></span><br><% end %> + <% unless notitle || !has_header %> + <h1><%= header.title %></h1> + <% if attr? :author %><span id='author'><%= attr :author %></span><br><% end %> <% end %> </div> + <% end %> <div id='content'> <%= content %> </div> @@ -104,8 +106,12 @@ end class SectionTemplate < BaseTemplate def template @template ||= ERB.new <<-EOF -<div class='sect<%= level %>'> - <h<%= level + 1 %> id='<%= id ? id : section_id %>'><%= name %></h<%= level + 1 %>> +<% if level == 0 %> +<h1 id='<%= id ? id : section_id %>'><%= title %></h1> +<%= content %> +<% else %> +<div class='sect<%= level %>#{role}'> + <h<%= level + 1 %> id='<%= id ? id : section_id %>'><%= title %></h<%= level + 1 %>> <% if level == 1 %> <div class='sectionbody'> <%= content %> @@ -114,6 +120,7 @@ class SectionTemplate < BaseTemplate <%= content %> <% end %> </div> +<% end %> EOF end end diff --git a/lib/asciidoctor/section.rb b/lib/asciidoctor/section.rb index 55b9b4b9..baaf4f53 100644 --- a/lib/asciidoctor/section.rb +++ b/lib/asciidoctor/section.rb @@ -57,7 +57,7 @@ class Asciidoctor::Section # Returns the String section name def name @name && - @name.gsub(/(^|[^\\])\{(\w[\w\-]+\w)\}/) { $1 + Asciidoctor::INTRINSICS[$2] }. + @name.gsub(/(^|[^\\])\{(\w[\w\-]+\w)\}/) { $1 + (attr?($2) ? attr($2) : Asciidoctor::INTRINSICS[$2]) }. gsub( /`([^`]+)`/, '<tt>\1</tt>' ) end @@ -132,7 +132,7 @@ class Asciidoctor::Section # Public: The title of this section, an alias of the section name def title - @name + name end # Public: Get the Integer number of blocks in the section. diff --git a/test/attributes_test.rb b/test/attributes_test.rb index 3fd2ba3b..ac2d6a5b 100644 --- a/test/attributes_test.rb +++ b/test/attributes_test.rb @@ -59,6 +59,13 @@ context "Attributes" do assert_match /snort at the bar/, result.css("li").first.content.strip end + test "substitutes inside heading" do + output = render_string(":prefix: Cool\n\n== {prefix} Title\n\ncontent") + result = Nokogiri::HTML(output) + assert_match /Cool Title/, result.css('h2').first.content + assert_match /_cool_title/, result.css('h2').first.attr('id') + end + test "renders attribute until it's deleted" do pending "Not working yet (will require adding element-specific attributes or early attr substitution during parsing)" # html = render_string(":foo: bar\nCrossing the {foo}\n\n:foo!:\nBelly up to the {foo}") diff --git a/test/document_test.rb b/test/document_test.rb index 1c5d5623..ea7eaafe 100644 --- a/test/document_test.rb +++ b/test/document_test.rb @@ -8,14 +8,35 @@ class DocumentTest < Test::Unit::TestCase def test_title assert_equal "AsciiDoc Home Page", @doc.doctitle + assert_equal "AsciiDoc Home Page", @doc.name assert_equal 14, @doc.elements.size assert_equal :preamble, @doc.elements[0].context assert @doc.elements[1].is_a? ::Asciidoctor::Section end def test_with_no_title - d = Asciidoctor::Document.new(["Snorf"]) + d = document_from_string("Snorf") assert_nil d.doctitle + assert_nil d.name + assert !d.has_header + assert_nil d.header + end + + def test_with_explicit_title + d = document_from_string("= Title\n:title: Document Title\n\npreamble\n\n== Section") + assert_equal 'Document Title', d.doctitle + assert_equal 'Document Title', d.title + assert d.has_header + assert_equal 'Title', d.header.title + assert_equal 'Title', d.first_section.title + end + + def test_empty_document + d = document_from_string('') + assert d.elements.empty? + assert_nil d.doctitle + assert !d.has_header + assert_nil d.header end def test_with_header_footer diff --git a/test/headers_test.rb b/test/headers_test.rb index fcf98ab4..a0c8d2c2 100644 --- a/test/headers_test.rb +++ b/test/headers_test.rb @@ -1,47 +1,49 @@ require 'test_helper' context "Headers" do - test "document title with multiline syntax" do - title = "My Title" - chars = "=" * title.length - assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string(title + "\n" + chars) - assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string(title + "\n" + chars + "\n") - end + context "document title" do + test "document title with multiline syntax" do + title = "My Title" + chars = "=" * title.length + assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string(title + "\n" + chars) + assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string(title + "\n" + chars + "\n") + end - test "document title with multiline syntax, give a char" do - title = "My Title" - chars = "=" * (title.length + 1) - assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string(title + "\n" + chars) - assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string(title + "\n" + chars + "\n") - end + test "document title with multiline syntax, give a char" do + title = "My Title" + chars = "=" * (title.length + 1) + assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string(title + "\n" + chars) + assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string(title + "\n" + chars + "\n") + end - test "document title with multiline syntax, take a char" do - title = "My Title" - chars = "=" * (title.length - 1) - assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string(title + "\n" + chars) - assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string(title + "\n" + chars + "\n") - end + test "document title with multiline syntax, take a char" do + title = "My Title" + chars = "=" * (title.length - 1) + assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string(title + "\n" + chars) + assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string(title + "\n" + chars + "\n") + end - test "not enough chars for a multiline document title" do - title = "My Title" - chars = "=" * (title.length - 2) - assert_xpath '//h1', render_string(title + "\n" + chars), 0 - assert_xpath '//h1', render_string(title + "\n" + chars + "\n"), 0 - end + test "not enough chars for a multiline document title" do + title = "My Title" + chars = "=" * (title.length - 2) + assert_xpath '//h1', render_string(title + "\n" + chars), 0 + assert_xpath '//h1', render_string(title + "\n" + chars + "\n"), 0 + end - test "too many chars for a multiline document title" do - title = "My Title" - chars = "=" * (title.length + 2) - assert_xpath '//h1', render_string(title + "\n" + chars), 0 - assert_xpath '//h1', render_string(title + "\n" + chars + "\n"), 0 - end + test "too many chars for a multiline document title" do + title = "My Title" + chars = "=" * (title.length + 2) + assert_xpath '//h1', render_string(title + "\n" + chars), 0 + assert_xpath '//h1', render_string(title + "\n" + chars + "\n"), 0 + end - test "document title with single-line syntax" do - assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string("= My Title") - end + test "document title with single-line syntax" do + assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string("= My Title") + end - test "document title with symmetric syntax" do - assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string("= My Title =") + test "document title with symmetric syntax" do + assert_xpath "//h1[not(@id)][text() = 'My Title']", render_string("= My Title =") + end end context "level 1" do @@ -110,5 +112,38 @@ context "Headers" do test "with single line syntax" do assert_xpath "//h5[@id='_my_title'][text() = 'My Title']", render_string("===== My Title") end - end + end + + context "book doctype" do + test "document title with level 0 headings" do + input = <<-EOS +Book +==== +:doctype: book + += Chapter One + +It was a dark and stormy night... + += Chapter Two + +They couldn't believe their eyes when... + +== Interlude + +While they were waiting... + += Chapter Three + +That's all she wrote! + EOS + + output = render_string(input) + assert_xpath '//h1', output, 4 + assert_xpath '//h2', output, 1 + assert_xpath '//h1[@id="_chapter_one"][text() = "Chapter One"]', output, 1 + assert_xpath '//h1[@id="_chapter_two"][text() = "Chapter Two"]', output, 1 + assert_xpath '//h1[@id="_chapter_three"][text() = "Chapter Three"]', output, 1 + end + end end diff --git a/test/preamble_test.rb b/test/preamble_test.rb index 1b3b4fc6..5ae997c8 100644 --- a/test/preamble_test.rb +++ b/test/preamble_test.rb @@ -85,4 +85,28 @@ Section paragraph 1. assert_xpath '//h2[@id="_first_section"]/preceding::p', result, 1 end + test 'preamble in book doctype' do + input = <<-EOS +Book +==== +:doctype: book + +Back then... + += Chapter One + +It was a dark and stormy night... + += Chapter Two + +They couldn't believe their eyes when... + EOS + + d = document_from_string(input) + assert_equal 'book', d.doctype + output = d.render + assert_xpath '//h1', output, 3 + assert_xpath '//*[@id="preamble"]//p[text() = "Back then..."]', output, 1 + end + end |
