summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.adoc5
-rw-r--r--compat/asciidoc.conf22
-rwxr-xr-xlib/asciidoctor.rb17
-rw-r--r--lib/asciidoctor/lexer.rb60
-rw-r--r--test/blocks_test.rb110
5 files changed, 199 insertions, 15 deletions
diff --git a/README.adoc b/README.adoc
index c3404aed..409bcd3c 100644
--- a/README.adoc
+++ b/README.adoc
@@ -321,7 +321,7 @@ NOTE: In general, Asciidoctor handles whitespace much more intelligently
* Use can set the extension for icons using the +icontype+ attribute
(AsciiDoc defaults to .png)
-* AsciiDoc uses the +<blockquote>+ for the content and +<cite>+ tag for
+* Asciidoctor uses the +<blockquote>+ for the content and +<cite>+ tag for
attribution title in the HTML output for quote blocks, requiring some
additional styling to match AsciiDoc
+
@@ -329,6 +329,9 @@ NOTE: In general, Asciidoctor handles whitespace much more intelligently
cite { color: navy; }
+
+* Asciidoctor supports markdown-style blockquotes as well as a shorthand
+ for a blockquote paragraph.
+
* Asciidoctor does not support the deprecated index term syntax (`++`
and `+++`)
diff --git a/compat/asciidoc.conf b/compat/asciidoc.conf
index 68b524c5..bb5fc416 100644
--- a/compat/asciidoc.conf
+++ b/compat/asciidoc.conf
@@ -21,11 +21,31 @@ space=" "
tilde=~
# enables fenced code blocks
-# I haven't sorted out yet how to do syntax highlighting
+# FIXME I haven't sorted out yet how to do syntax highlighting
[blockdef-fenced-code]
delimiter=^```\w*$
template::[blockdef-listing]
+# enables blockquotes to be defined using two double quotes
+[blockdef-air-quote]
+delimiter=^""$
+template::[blockdef-quote]
+
+# markdown-style blockquote (paragraph only)
+# FIXME does not strip leading > on subsequent lines
+[paradef-markdown-quote]
+delimiter=(?s)>\s*(?P<text>\S.*)
+style=quote
+quote-style=template="quoteparagraph",posattrs=("style","attribution","citetitle")
+
+# fix regex for callout list to require number; also makes markdown-style blockquote work
+[listdef-callout]
+posattrs=style
+delimiter=^<?(?P<index>\d+>) +(?P<text>.+)$
+type=callout
+tags=callout
+style=arabic
+
# enables literal block to be used as code block
[blockdef-literal]
template::[source-filter-style]
diff --git a/lib/asciidoctor.rb b/lib/asciidoctor.rb
index ced5391d..c305dad8 100755
--- a/lib/asciidoctor.rb
+++ b/lib/asciidoctor.rb
@@ -152,6 +152,7 @@ module Asciidoctor
'====' => [:example, ['admonition'].to_set],
'****' => [:sidebar, Set.new],
'____' => [:quote, ['verse'].to_set],
+ '""' => [:quote, ['verse'].to_set],
'++++' => [:pass, Set.new],
'|===' => [:table, Set.new],
',===' => [:table, Set.new],
@@ -208,7 +209,12 @@ module Asciidoctor
# a block to be different lengths
# this option requires that they be the same
# Compliance value: false
- :congruent_block_delimiters => true
+ :congruent_block_delimiters => true,
+
+ # AsciiDoc will recognize commonly-used Markdown syntax
+ # to the degree it does not interfere with existing
+ # AsciiDoc behavior.
+ :markdown_syntax => true
}
# The following pattern, which appears frequently, captures the contents between square brackets,
@@ -231,10 +237,11 @@ module Asciidoctor
# [[ref]] (anywhere inline)
:anchor_macro => /\\?\[\[([\w":].*?)\]\]/,
- # matches any block delimiter:
- # open, listing, example, literal, comment, quote, sidebar, passthrough, table
- # NOTE position the most common blocks towards the front of the pattern
- :any_blk => %r{^(?:--|(?:-|\.|=|\*|_|\+|/){4,}|[\|,;!]={3,}|(?:`|~){3,}.*)$},
+ # matches any unbounded block delimiter:
+ # listing, literal, example, sidebar, quote, passthrough, table, fenced code
+ # does not include open block or air quotes
+ # TIP position the most common blocks towards the front of the pattern
+ :any_blk => %r{^(?:(?:-|\.|=|\*|_|\+|/){4,}|[\|,;!]={3,}|(?:`|~){3,}.*)$},
# detect a list item of any sort
# [[:graph:]] is a non-blank character
diff --git a/lib/asciidoctor/lexer.rb b/lib/asciidoctor/lexer.rb
index bb892dec..efa9bb55 100644
--- a/lib/asciidoctor/lexer.rb
+++ b/lib/asciidoctor/lexer.rb
@@ -292,10 +292,11 @@ class Lexer
# bail if we've reached the end of the parent block or document
return nil unless reader.has_more_lines?
+ text_only = options[:text]
# check for option to find list item text only
# if skipped a line, assume a list continuation was
# used and block content is acceptable
- if options[:text] && skipped > 0
+ if text_only && skipped > 0
options.delete(:text)
end
@@ -363,7 +364,7 @@ class Lexer
end
# process lines normally
- if !options[:text]
+ if !text_only
# NOTE we're letting break lines (ruler, page_break, etc) have attributes
if (match = this_line.match(REGEXP[:break_line]))
block = Block.new(parent, BREAK_LINES[match[0][0..2]])
@@ -565,12 +566,49 @@ class Lexer
catalog_inline_anchors(buffer.join, document)
- if !options[:text] && (admonition_match = buffer.first.match(REGEXP[:admonition_inline]))
+ first_line = buffer.first
+ if !text_only && (admonition_match = first_line.match(REGEXP[:admonition_inline]))
buffer[0] = admonition_match.post_match.lstrip
block = Block.new(parent, :admonition, buffer)
attributes['style'] = admonition_match[1]
attributes['name'] = admonition_name = admonition_match[1].downcase
attributes['caption'] ||= document.attributes["#{admonition_name}-caption"]
+ elsif !text_only && COMPLIANCE[:markdown_syntax] && first_line.start_with?('> ')
+ buffer.map! {|line|
+ if line.start_with?('> ')
+ line[2..-1]
+ elsif line.chomp == '>'
+ line[1..-1]
+ else
+ line
+ end
+ }
+
+ if buffer.last.start_with?('-- ')
+ attribution, citetitle = buffer.pop[3..-1].split(', ')
+ buffer.pop while buffer.last.chomp.empty?
+ buffer[-1] = buffer.last.chomp
+ else
+ attribution, citetitle = nil
+ end
+ attributes['style'] = 'quote'
+ attributes['attribution'] = attribution unless attribution.nil?
+ attributes['citetitle'] = citetitle unless citetitle.nil?
+ # NOTE will only detect headings that are floating titles (not section titles)
+ # TODO could assume a floating title when inside a block context
+ block = build_block(:quote, :complex, false, parent, Reader.new(buffer), attributes)
+ elsif !text_only && buffer.size > 1 && first_line.start_with?('"') &&
+ buffer.last.start_with?('-- ') && buffer[-2].chomp.end_with?('"')
+ buffer[0] = first_line[1..-1]
+ attribution, citetitle = buffer.pop[3..-1].split(', ')
+ buffer.pop while buffer.last.chomp.empty?
+ buffer[-1] = buffer.last.chomp.chop
+ attributes['style'] = 'quote'
+ attributes['attribution'] = attribution unless attribution.nil?
+ attributes['citetitle'] = citetitle unless citetitle.nil?
+ block = Block.new(parent, :quote, buffer)
+ #block = Block.new(parent, :quote)
+ #block << Block.new(block, :paragraph, buffer)
else
# QUESTION is this necessary?
#if style == 'normal' && [' ', "\t"].include?(buffer.first[0..0])
@@ -685,11 +723,13 @@ class Lexer
tip = line[0..3]
tl = 4
- # special case for fenced code blocks
- tip_alt = tip.chop
- if tip_alt == '```' || tip_alt == '~~~'
- tip = tip_alt
- tl = 3
+ if COMPLIANCE[:markdown_syntax]
+ # special case for fenced code blocks
+ tip_alt = tip.chop
+ if tip_alt == '```' || tip_alt == '~~~'
+ tip = tip_alt
+ tl = 3
+ end
end
end
@@ -723,6 +763,7 @@ class Lexer
end
# whether a block supports complex content should be a config setting
+ # if terminator is false, that means the all the lines in the reader should be parsed
# NOTE could invoke filter in here, before and after parsing
def self.build_block(block_context, content_type, terminator, parent, reader, attributes, options = {})
if terminator.nil?
@@ -740,6 +781,9 @@ class Lexer
end
elsif content_type != :complex
buffer = reader.grab_lines_until(:terminator => terminator, :chomp_last_line => true)
+ elsif terminator == false
+ buffer = nil
+ block_reader = reader
else
buffer = nil
block_reader = Reader.new reader.grab_lines_until(:terminator => terminator)
diff --git a/test/blocks_test.rb b/test/blocks_test.rb
index c948b01e..a98a9fe1 100644
--- a/test/blocks_test.rb
+++ b/test/blocks_test.rb
@@ -181,6 +181,116 @@ ____
assert_css '.quoteblock > blockquote > .paragraph + .admonitionblock', output, 1
end
+ test 'quote block using air quotes with no attribution' do
+ input = <<-EOS
+""
+A famous quote.
+""
+ EOS
+ output = render_string input
+ assert_css '.quoteblock', output, 1
+ assert_css '.quoteblock > blockquote', output, 1
+ assert_css '.quoteblock > blockquote > .paragraph > p', output, 1
+ assert_css '.quoteblock > .attribution', output, 0
+ assert_xpath '//*[@class = "quoteblock"]//p[text() = "A famous quote."]', output, 1
+ end
+
+ test 'markdown-style quote block with single paragraph and no attribution' do
+ input = <<-EOS
+> A famous quote.
+> Some more inspiring words.
+ EOS
+ output = render_string input
+ assert_css '.quoteblock', output, 1
+ assert_css '.quoteblock > blockquote', output, 1
+ assert_css '.quoteblock > blockquote > .paragraph > p', output, 1
+ assert_css '.quoteblock > .attribution', output, 0
+ assert_xpath %(//*[@class = "quoteblock"]//p[text() = "A famous quote.\nSome more inspiring words."]), output, 1
+ end
+
+ test 'lazy markdown-style quote block with single paragraph and no attribution' do
+ input = <<-EOS
+> A famous quote.
+Some more inspiring words.
+ EOS
+ output = render_string input
+ assert_css '.quoteblock', output, 1
+ assert_css '.quoteblock > blockquote', output, 1
+ assert_css '.quoteblock > blockquote > .paragraph > p', output, 1
+ assert_css '.quoteblock > .attribution', output, 0
+ assert_xpath %(//*[@class = "quoteblock"]//p[text() = "A famous quote.\nSome more inspiring words."]), output, 1
+ end
+
+ test 'markdown-style quote block with multiple paragraphs and no attribution' do
+ input = <<-EOS
+> A famous quote.
+>
+> Some more inspiring words.
+ EOS
+ output = render_string input
+ assert_css '.quoteblock', output, 1
+ assert_css '.quoteblock > blockquote', output, 1
+ assert_css '.quoteblock > blockquote > .paragraph > p', output, 2
+ assert_css '.quoteblock > .attribution', output, 0
+ assert_xpath %((//*[@class = "quoteblock"]//p)[1][text() = "A famous quote."]), output, 1
+ assert_xpath %((//*[@class = "quoteblock"]//p)[2][text() = "Some more inspiring words."]), output, 1
+ end
+
+ test 'markdown-style quote block with multiple blocks and no attribution' do
+ input = <<-EOS
+> A famous quote.
+>
+> NOTE: Some more inspiring words.
+ EOS
+ output = render_string input
+ assert_css '.quoteblock', output, 1
+ assert_css '.quoteblock > blockquote', output, 1
+ assert_css '.quoteblock > blockquote > .paragraph > p', output, 1
+ assert_css '.quoteblock > blockquote > .admonitionblock', output, 1
+ assert_css '.quoteblock > .attribution', output, 0
+ assert_xpath %((//*[@class = "quoteblock"]//p)[1][text() = "A famous quote."]), output, 1
+ assert_xpath %((//*[@class = "quoteblock"]//*[@class = "admonitionblock note"]//*[@class="content"])[1][normalize-space(text()) = "Some more inspiring words."]), output, 1
+ end
+
+ test 'markdown-style quote block with single paragraph and attribution' do
+ input = <<-EOS
+> A famous quote.
+> Some more inspiring words.
+> -- Famous Person, Famous Source (1999)
+ EOS
+ output = render_string input
+ assert_css '.quoteblock', output, 1
+ assert_css '.quoteblock > blockquote', output, 1
+ assert_css '.quoteblock > blockquote > .paragraph > p', output, 1
+ assert_xpath %(//*[@class = "quoteblock"]//p[text() = "A famous quote.\nSome more inspiring words."]), output, 1
+ assert_css '.quoteblock > .attribution', output, 1
+ assert_css '.quoteblock > .attribution > cite', output, 1
+ assert_css '.quoteblock > .attribution > cite + br', output, 1
+ assert_xpath '//*[@class = "quoteblock"]/*[@class = "attribution"]/cite[text() = "Famous Source (1999)"]', output, 1
+ attribution = xmlnodes_at_xpath '//*[@class = "quoteblock"]/*[@class = "attribution"]', output, 1
+ author = attribution.children.last
+ assert_equal "#{expand_entity 8212} Famous Person", author.text.strip
+ end
+
+ test 'quoted paragraph-style quote block with attribution' do
+ input = <<-EOS
+"A famous quote.
+Some more inspiring words."
+-- Famous Person, Famous Source (1999)
+ EOS
+ output = render_string input
+ assert_css '.quoteblock', output, 1
+ assert_css '.quoteblock > blockquote', output, 1
+ assert_xpath %(//*[@class = "quoteblock"]/blockquote[normalize-space(text()) = "A famous quote. Some more inspiring words."]), output, 1
+ assert_css '.quoteblock > .attribution', output, 1
+ assert_css '.quoteblock > .attribution > cite', output, 1
+ assert_css '.quoteblock > .attribution > cite + br', output, 1
+ assert_xpath '//*[@class = "quoteblock"]/*[@class = "attribution"]/cite[text() = "Famous Source (1999)"]', output, 1
+ attribution = xmlnodes_at_xpath '//*[@class = "quoteblock"]/*[@class = "attribution"]', output, 1
+ author = attribution.children.last
+ assert_equal "#{expand_entity 8212} Famous Person", author.text.strip
+ end
+
test 'single-line verse block without attribution' do
input = <<-EOS
[verse]