summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Allen <dan.j.allen@gmail.com>2012-12-17 03:20:30 -0700
committerDan Allen <dan.j.allen@gmail.com>2012-12-17 03:20:30 -0700
commit34e6ad2a2da41b759ccaa26c4b73d0ffecbdce42 (patch)
treee5143f46d76813387403ff0aa0d6614753cb649c
parente4102d5b8244e2c2573065701adabc9d196af3ed (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.rb65
-rw-r--r--lib/asciidoctor/lexer.rb4
-rw-r--r--lib/asciidoctor/render_templates.rb21
-rw-r--r--lib/asciidoctor/section.rb4
-rw-r--r--test/attributes_test.rb7
-rw-r--r--test/document_test.rb23
-rw-r--r--test/headers_test.rb107
-rw-r--r--test/preamble_test.rb24
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