diff options
| author | Dan Allen <dan.j.allen@gmail.com> | 2014-07-27 17:33:39 -0600 |
|---|---|---|
| committer | Dan Allen <dan.j.allen@gmail.com> | 2014-07-27 17:33:39 -0600 |
| commit | 7cf68acb9a95ffdc54ec0b6374a009f20e84806b (patch) | |
| tree | 58482bb4e9a55e065d48005c78eae899b8f43da4 | |
| parent | 2dd757f561611383daf6a1006c68b2885448427d (diff) | |
| parent | 7d5e459f001d32d38b62d1ea1a2c196705b6dc11 (diff) | |
Merge pull request #1006 from mojavelinux/issue-718
resolves #718 and #714 swap + and ` formatting chars
| -rw-r--r-- | compat/asciidoc.conf | 20 | ||||
| -rw-r--r-- | features/text_formatting.feature | 30 | ||||
| -rw-r--r-- | lib/asciidoctor.rb | 49 | ||||
| -rw-r--r-- | lib/asciidoctor/abstract_node.rb | 2 | ||||
| -rw-r--r-- | lib/asciidoctor/document.rb | 1 | ||||
| -rw-r--r-- | lib/asciidoctor/substitutors.rb | 168 | ||||
| -rw-r--r-- | test/attributes_test.rb | 9 | ||||
| -rw-r--r-- | test/extensions_test.rb | 2 | ||||
| -rw-r--r-- | test/substitutions_test.rb | 131 | ||||
| -rw-r--r-- | test/text_test.rb | 43 |
10 files changed, 348 insertions, 107 deletions
diff --git a/compat/asciidoc.conf b/compat/asciidoc.conf index 94784bb4..d7a96d10 100644 --- a/compat/asciidoc.conf +++ b/compat/asciidoc.conf @@ -21,8 +21,12 @@ space=" " tilde=~ user-home={eval:os.path.expanduser('~')} vbar=| -ifndef::compat-mode[] +# NOTE use -a no-inline-literal to set compat-mode to default when using AsciiDoc Python +ifdef::no-inline-literal[] compat-mode=default +endif::no-inline-literal[] +ifndef::no-inline-literal[] +compat-mode=legacy endif::[] [replacements] @@ -36,6 +40,13 @@ endif::[] # this change can also be made in the document using the attribute entry :quotes.': ifeval::["{compat-mode}"!="legacy"] '= ++= +++= +`= +``=#monospaced +`|`=monospaced +\##=#mark +\#=mark endif::[] # enables markdown-style headings @@ -118,6 +129,11 @@ delimiter=^:={3,}$ format=dsv [macros] +ifdef::no-inline-literal[] +(?su)\\?\+\+(?P<passtext>.*?)\+\+=pass[specialcharacters] +(?su)(?<![+\w])(\\?\+(?P<passtext>\S|\S.*?\S)\+)(?![+\w])=pass[specialcharacters] +endif::no-inline-literal[] + # additional callout match behind line comments #(?://|#|;;) ?\((?P<index>\d+)\)=callout # additional callout match for XML @@ -170,7 +186,7 @@ strong=<strong{1? class="{1}"}>|</strong> monospaced=<code{1? class="{1}"}>|</code> superscript=<sup{1? class="{1}"}>|</sup> subscript=<sub{1? class="{1}"}>|</sub> -unquoted={1=<mark>}{1?<span class="{1}">}|{1?</span>}{1=</mark>} +mark={1=<mark>}{1?<span class="{1}">}|{1?</span>}{1=</mark>} [monospacedwords] <code>{words}</code> diff --git a/features/text_formatting.feature b/features/text_formatting.feature index 625fe697..4517c6e8 100644 --- a/features/text_formatting.feature +++ b/features/text_formatting.feature @@ -5,7 +5,7 @@ Feature: Text Formatting I want to be able to markup inline text with formatting characters - Scenario: Render text that contains superscript and subscript characters + Scenario: Convert text that contains superscript and subscript characters Given the AsciiDoc source """ _v_~rocket~ is the value @@ -25,3 +25,31 @@ Feature: Text Formatting the 10<sup>th</sup> point has coordinate (x<sub>10</sub>, y<sub>10</sub>)</p> </div> """ + + + Scenario: Convert text that has ex-inline literal formatting + Given the AsciiDoc source + """ + Use [x-]`{asciidoctor-version}` to print the version of Asciidoctor. + """ + When it is converted to html + Then the result should match the HTML source + """ + <div class="paragraph"> + <p>Use <code>{asciidoctor-version}</code> to print the version of Asciidoctor.</p> + </div> + """ + + + Scenario: Convert text that has ex-inline monospaced formatting + Given the AsciiDoc source + """ + The document is assumed to be encoded as [x-]+{encoding}+. + """ + When it is converted to html + Then the result should match the HTML source + """ + <div class="paragraph"> + <p>The document is assumed to be encoded as <code>UTF-8</code>.</p> + </div> + """ diff --git a/lib/asciidoctor.rb b/lib/asciidoctor.rb index d099b395..41648361 100644 --- a/lib/asciidoctor.rb +++ b/lib/asciidoctor.rb @@ -906,13 +906,18 @@ module Asciidoctor # MenuInlineRx = /\\?"(#{CG_WORD}[^"]*?#{CG_BLANK}*>#{CG_BLANK}*[^" \t][^"]*)"/ - # Matches a passthrough literal value, which may span multiple lines. + # Matches an inline passthrough value, which may span multiple lines. # # Examples # - # `text` + # +text+ + # `text` (legacy) # - PassInlineLiteralRx = /(^|[^`#{CC_WORD}])(?:\[([^\]]+?)\])?(\\?`([^`\s]|[^`\s].*?\S)`)(?![`#{CC_WORD}])/m + # NOTE we always capture the attributes so we know when to use legacy behavior + PassInlineRx = { + :default => ['+', '`', /(^|[^#{CC_WORD};:])(?:\[([^\]]+?)\])?(\\?(\+|`)(\S|\S.*?\S)\4)(?!#{CC_WORD})/m], + :legacy => ['`', nil, /(^|[^`#{CC_WORD}])(?:\[([^\]]+?)\])?(\\?(`)([^`\s]|[^`\s].*?\S)\4)(?![`#{CC_WORD}])/m] + } # Matches several variants of the passthrough inline macro, which may span multiple lines. # @@ -922,7 +927,7 @@ module Asciidoctor # $$text$$ # pass:quotes[text] # - PassInlineMacroRx = /\\?(?:(\+{3}|\${2})(.*?)\1|pass:([a-z,]*)\[(.*?[^\\])\])/m + PassInlineMacroRx = /(?:(?:(\\?)\[([^\]]+?)\])?(\\{0,2})(\+{2,3}|\${2})(.*?)\4|(\\?)pass:([a-z,]*)\[(.*?[^\\])\])/m # Matches an xref (i.e., cross-reference) inline macro, which may span multiple lines. # @@ -1131,26 +1136,25 @@ module Asciidoctor # constrained quotes:: must be bordered by non-word characters # NOTE these substitutions are processed in the order they appear here and # the order in which they are replaced is important - QUOTE_SUBS = { :default => - [ + default_quote_subs = [ # **strong** [:strong, :unconstrained, /\\?(?:\[([^\]]+?)\])?\*\*(.+?)\*\*/m], # *strong* [:strong, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?\*(\S|\S.*?\S)\*(?!#{CG_WORD})/m], + # ``monospaced`` + [:monospaced, :unconstrained, /\\?(?:\[([^\]]+?)\])?``(.+?)``/m], + + # `monospaced` + [:monospaced, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?`(\S|\S.*?\S)`(?!#{CG_WORD})/m], + # ``double-quoted'' [:double, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?``(\S|\S.*?\S)''(?!#{CG_WORD})/m], # `single-quoted' [:single, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?`(\S|\S.*?\S)'(?!#{CG_WORD})/m], - # ++monospaced++ - [:monospaced, :unconstrained, /\\?(?:\[([^\]]+?)\])?\+\+(.+?)\+\+/m], - - # +monospaced+ - [:monospaced, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?\+(\S|\S.*?\S)\+(?!#{CG_WORD})/m], - # __emphasis__ [:emphasis, :unconstrained, /\\?(?:\[([^\]]+?)\])?__(.+?)__/m], @@ -1168,10 +1172,25 @@ module Asciidoctor # ~subscript~ [:subscript, :unconstrained, /\\?(?:\[([^\]]+?)\])?~(\S*?)~/m] - ]} + ] - QUOTE_SUBS[:legacy] = QUOTE_SUBS[:default].dup. - insert(3, [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?'(\S|\S.*?\S)'(?!#{CG_WORD})/m]) + legacy_quote_subs = default_quote_subs.dup + # remove +monospaced+ and +monospaced+ to reposition + legacy_quote_subs.delete_at 3 + legacy_quote_subs.delete_at 2 + # 'emphasis' + legacy_quote_subs.insert 3, [:emphasis, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?'(\S|\S.*?\S)'(?!#{CG_WORD})/m] + # ++monospaced++ + legacy_quote_subs.insert 5, [:monospaced, :unconstrained, /\\?(?:\[([^\]]+?)\])?\+\+(.+?)\+\+/m] + # +monospaced+ + legacy_quote_subs.insert 6, [:monospaced, :constrained, /(^|[^#{CC_WORD};:}])(?:\[([^\]]+?)\])?\+(\S|\S.*?\S)\+(?!#{CG_WORD})/m] + + QUOTE_SUBS = { + :default => default_quote_subs, + :legacy => legacy_quote_subs, + } + default_quote_subs = nil + legacy_quote_subs = nil # NOTE in Ruby 1.8.7, [^\\] does not match start of line, # so we need to match it explicitly diff --git a/lib/asciidoctor/abstract_node.rb b/lib/asciidoctor/abstract_node.rb index bee857bc..5dcff9d4 100644 --- a/lib/asciidoctor/abstract_node.rb +++ b/lib/asciidoctor/abstract_node.rb @@ -40,7 +40,7 @@ class AbstractNode @node_name = context.to_s # QUESTION are we correct in duplicating the attributes (seems to be just as fast) @attributes = opts.key?(:attributes) ? (opts[:attributes] || {}).dup : {} - @passthroughs = [] + @passthroughs = {} end # Public: Associate this Block with a new parent Block diff --git a/lib/asciidoctor/document.rb b/lib/asciidoctor/document.rb index 206a6efe..8c45e8f3 100644 --- a/lib/asciidoctor/document.rb +++ b/lib/asciidoctor/document.rb @@ -183,6 +183,7 @@ class Document < AbstractBlock SafeMode.const_get(safe_mode.to_s.upcase).to_i rescue SafeMode::SECURE.to_i end @sourcemap = options.key?(:sourcemap) && options[:sourcemap] == true + @compat_mode = :default @converter = nil initialize_extensions = defined? ::Asciidoctor::Extensions @extensions = nil # initialize furthur down diff --git a/lib/asciidoctor/substitutors.rb b/lib/asciidoctor/substitutors.rb index fd08b90e..8482a236 100644 --- a/lib/asciidoctor/substitutors.rb +++ b/lib/asciidoctor/substitutors.rb @@ -101,7 +101,7 @@ module Substitutors if (has_passthroughs = subs.include? :macros) text = extract_passthroughs text - has_passthroughs = false if @passthroughs.nil_or_empty? + has_passthroughs = false if @passthroughs.empty? end subs.each do |type| @@ -164,57 +164,134 @@ module Substitutors # # returns - The text with the passthrough region substituted with placeholders def extract_passthroughs(text) + compat_mode = @document.compat_mode text = text.gsub(PassInlineMacroRx) { # alias match for Ruby 1.8.7 compat m = $~ - # honor the escape - if m[0].start_with? '\\' - next m[0][1..-1] - end + preceding = nil - if m[4].nil_or_empty? - text = m[2] - subs = (m[1] == '$$' ? [:specialcharacters] : []) - else - text = unescape_brackets m[4] - if m[3].nil_or_empty? - subs = [] + if (boundary = m[4]).nil_or_empty? # pass:[] + if m[6] == '\\' + # NOTE we don't look for nested pass:[] macros + next m[0][1..-1] + end + + @passthroughs[pass_key = @passthroughs.size] = {:text => (unescape_brackets m[8]), :subs => (m[7].nil_or_empty? ? [] : (resolve_pass_subs m[7]))} + else # $$, ++ or +++ + # skip ++ in legacy mode, handled as normal quoted text + if compat_mode == :legacy && boundary == '++' + next m[2].nil_or_empty? ? + %(#{m[1]}#{m[3]}++#{extract_passthroughs m[5]}++) : + %(#{m[1]}[#{m[2]}]#{m[3]}++#{extract_passthroughs m[5]}++) + end + + attributes = m[2] + + # fix non-matching group results in Opal under Firefox + if ::RUBY_ENGINE_OPAL + attributes = nil if attributes == '' + end + + escape_count = m[3].size + content = m[5] + old_behavior = false + + if attributes + if escape_count > 0 + # NOTE we don't look for nested unconstrained pass macros + next %(#{m[1]}[#{attributes}]#{'\\' * (escape_count - 1)}#{boundary}#{m[5]}#{boundary}) + elsif m[1] == '\\' + preceding = %([#{attributes}]) + attributes = nil + else + if boundary == '++' && (attributes.end_with? 'x-') + old_behavior = true + attributes = attributes[0...-2] + end + attributes = parse_attributes attributes + end + elsif escape_count > 0 + # NOTE we don't look for nested unconstrained pass macros + next %(#{m[1]}[#{attributes}]#{'\\' * (escape_count - 1)}#{boundary}#{m[5]}#{boundary}) + end + subs = (boundary == '+++' ? [] : [:specialcharacters]) + + pass_key = @passthroughs.size + if attributes + if old_behavior + @passthroughs[pass_key] = {:text => content, :subs => SUBS[:normal], :type => :monospaced, :attributes => attributes} + else + @passthroughs[pass_key] = {:text => content, :subs => subs, :type => :unquoted, :attributes => attributes} + end else - subs = resolve_pass_subs m[3] + @passthroughs[pass_key] = {:text => content, :subs => subs} end end - @passthroughs << {:text => text, :subs => subs} - index = @passthroughs.size - 1 - %(#{PASS_START}#{index}#{PASS_END}) + %(#{preceding}#{PASS_START}#{pass_key}#{PASS_END}) } if (text.include? '++') || (text.include? '$$') || (text.include? 'ss:') - text = text.gsub(PassInlineLiteralRx) { + pass_inline_char1, pass_inline_char2, pass_inline_rx = PassInlineRx[compat_mode] + text = text.gsub(pass_inline_rx) { # alias match for Ruby 1.8.7 compat m = $~ + preceding = m[1] + attributes = m[2] + escape_mark = (m[3].start_with? '\\') ? '\\' : nil + format_mark = m[4] + content = m[5] + # fix non-matching group results in Opal under Firefox if ::RUBY_ENGINE_OPAL - m[2] = nil if m[2] == '' + attributes = nil if attributes == '' end - unescaped_attrs = nil - # honor the escape - if m[3].start_with? '\\' - next m[2] ? %(#{m[1]}[#{m[2]}]#{m[3][1..-1]}) : %(#{m[1]}#{m[3][1..-1]}) - elsif m[1] == '\\' && m[2] - unescaped_attrs = "[#{m[2]}]" + if compat_mode == :default + if (old_behavior = (attributes && (attributes.end_with? 'x-'))) + attributes = attributes[0...-2] + end + else + old_behavior = true end - if !unescaped_attrs && m[2] - attributes = parse_attributes(m[2]) + if attributes + if format_mark == '`' && !old_behavior + next %(#{preceding}[#{attributes}]#{escape_mark}`#{extract_passthroughs content}`) + end + + if escape_mark + # honor the escape of the formatting mark + next "#{preceding}[#{attributes}]#{m[3][1..-1]}" + elsif preceding == '\\' + # honor the escape of the attributes + preceding = %([#{attributes}]) + attributes = nil + else + attributes = parse_attributes attributes + end + elsif format_mark == '`' && !old_behavior + next %(#{preceding}#{escape_mark}`#{extract_passthroughs content}`) + elsif escape_mark + # honor the escape of the formatting mark + next "#{preceding}#{m[3][1..-1]}" + end + + pass_key = @passthroughs.size + if compat_mode == :legacy + @passthroughs[pass_key] = {:text => content, :subs => [:specialcharacters], :attributes => attributes, :type => :monospaced} + elsif attributes + if old_behavior + subs = (format_mark == '`' ? [:specialcharacters] : SUBS[:normal]) + @passthroughs[pass_key] = {:text => content, :subs => subs, :attributes => attributes, :type => :monospaced} + else + @passthroughs[pass_key] = {:text => content, :subs => [:specialcharacters], :attributes => attributes, :type => :unquoted} + end else - attributes = nil + @passthroughs[pass_key] = {:text => content, :subs => [:specialcharacters]} end - @passthroughs << {:text => m[4], :subs => [:specialcharacters], :attributes => attributes, :type => :monospaced} - index = @passthroughs.size - 1 - %(#{unescaped_attrs || m[1]}#{PASS_START}#{index}#{PASS_END}) - } if (text.include? '`') + %(#{preceding}#{PASS_START}#{pass_key}#{PASS_END}) + } if (text.include? pass_inline_char1) || (pass_inline_char2 && (text.include? pass_inline_char2)) # NOTE we need to do the stem in a subsequent step to allow it to be escaped by the former text = text.gsub(StemInlineMacroRx) { @@ -228,16 +305,15 @@ module Substitutors if (type = m[1].to_sym) == :stem type = ((default_stem_type = document.attributes['stem']).nil_or_empty? ? 'asciimath' : default_stem_type).to_sym end - text = unescape_brackets m[3] + content = unescape_brackets m[3] if m[2].nil_or_empty? subs = (@document.basebackend? 'html') ? [:specialcharacters] : [] else subs = resolve_pass_subs m[2] end - @passthroughs << {:text => text, :subs => subs, :type => type} - index = @passthroughs.size - 1 - "#{PASS_START}#{index}#{PASS_END}" + @passthroughs[pass_key = @passthroughs.size] = {:text => content, :subs => subs, :type => type} + %(#{PASS_START}#{pass_key}#{PASS_END}) } if (text.include? ':') && ((text.include? 'stem:') || (text.include? 'math:')) text @@ -249,22 +325,18 @@ module Substitutors # check - A Boolean indicating whether to check whether substitution is necessary (default: true) # # returns The String text with the passthrough text restored - def restore_passthroughs(text, check = true) - return text if check && @passthroughs.nil_or_empty? || !text.include?(PASS_START) + def restore_passthroughs text, check = true + if check && (@passthroughs.empty? || !text.include?(PASS_START)) + return text + end text.gsub(PASS_MATCH) { - pass = @passthroughs[$~[1].to_i] - subbed_text = (subs = pass[:subs]) ? (apply_subs pass[:text], subs) : pass[:text] + pass = @passthroughs.delete $~[1].to_i + subbed_text = (subs = pass[:subs]) ? apply_subs(pass[:text], subs) : pass[:text] if (type = pass[:type]) subbed_text = Inline.new(self, :quoted, subbed_text, :type => type, :attributes => pass[:attributes]).convert end - subbed_text - # TODO enable the following code to restore passthroughs recursively - #if subbed_text.include? PASS_START - # restore_passthroughs subbed_text, false - #else - # subbed_text - #end + (@passthroughs.empty? || !subbed_text.include?(PASS_START)) ? subbed_text : restore_passthroughs(subbed_text, false) } end @@ -659,7 +731,7 @@ module Substitutors m = $~ # honor the escape if m[2].start_with? '\\' - # NOTE Opal doesn't like %() as an enclosure around this string + # NOTE Opal doesn't like %() as an enclosure around this string because of range next "#{m[1]}#{m[2][1..-1]}#{m[3]}" end # fix non-matching group results in Opal under Firefox @@ -1371,7 +1443,7 @@ module Substitutors # fix passthrough placeholders that got caught up in syntax highlighting unless @passthroughs.empty? - result = result.gsub PASS_MATCH_HI, "#{PASS_START}\\1#{PASS_END}" + result = result.gsub PASS_MATCH_HI, %(#{PASS_START}\\1#{PASS_END}) end if !sub_callouts || callout_marks.empty? diff --git a/test/attributes_test.rb b/test/attributes_test.rb index 52eb0eea..22acd83c 100644 --- a/test/attributes_test.rb +++ b/test/attributes_test.rb @@ -480,6 +480,15 @@ v1.0, 2010-01-01: First release! .Require the +{gem_name}+ gem To use {gem_name}, the first thing to do is to import it in your Ruby source file. EOS + output = render_embedded_string input, :attributes => {'compat-mode' => 'legacy'} + assert_xpath '//*[@class="title"]/code[text()="asciidoctor"]', output, 1 + + input = <<-EOS +:gem_name: asciidoctor + +.Require the `{gem_name}` gem +To use {gem_name}, the first thing to do is to import it in your Ruby source file. + EOS output = render_embedded_string input assert_xpath '//*[@class="title"]/code[text()="asciidoctor"]', output, 1 end diff --git a/test/extensions_test.rb b/test/extensions_test.rb index c27105df..9f3cc862 100644 --- a/test/extensions_test.rb +++ b/test/extensions_test.rb @@ -415,7 +415,7 @@ last line # Safe Mode is not required here document = empty_document :base_dir => File.expand_path(File.dirname(__FILE__)) document.extensions.include_processor do - process do |document, reader, target, attributes| + process do |doc, reader, target, attributes| # demonstrate that push_include normalizes endlines content = ["include target:: #{target}\n", "\n", "middle line\n"] reader.push_include content, target, target, 1, attributes diff --git a/test/substitutions_test.rb b/test/substitutions_test.rb index fb144aab..d707ef74 100644 --- a/test/substitutions_test.rb +++ b/test/substitutions_test.rb @@ -290,47 +290,86 @@ context 'Substitutions' do end test 'single-line constrained monospaced chars' do - para = block_from_string(%q{call +save()+ to persist the changes}) + para = block_from_string(%q{call +save()+ to persist the changes}, :attributes => {'compat-mode' => 'legacy'}) + assert_equal 'call <code>save()</code> to persist the changes', para.sub_quotes(para.source) + + para = block_from_string(%q{call [x-]+save()+ to persist the changes}) + assert_equal 'call <code>save()</code> to persist the changes', para.apply_subs(para.source) + + para = block_from_string(%q{call `save()` to persist the changes}) assert_equal 'call <code>save()</code> to persist the changes', para.sub_quotes(para.source) end test 'single-line constrained monospaced chars with role' do - para = block_from_string(%q{call [method]+save()+ to persist the changes}) + para = block_from_string(%q{call [method]+save()+ to persist the changes}, :attributes => {'compat-mode' => 'legacy'}) + assert_equal 'call <code class="method">save()</code> to persist the changes', para.sub_quotes(para.source) + + para = block_from_string(%q{call [method x-]+save()+ to persist the changes}) + assert_equal 'call <code class="method">save()</code> to persist the changes', para.apply_subs(para.source) + + para = block_from_string(%q{call [method]`save()` to persist the changes}) assert_equal 'call <code class="method">save()</code> to persist the changes', para.sub_quotes(para.source) end test 'escaped single-line constrained monospaced chars' do - para = block_from_string(%(call #{BACKSLASH}+save()+ to persist the changes)) + para = block_from_string(%(call #{BACKSLASH}+save()+ to persist the changes), :attributes => {'compat-mode' => 'legacy'}) assert_equal 'call +save()+ to persist the changes', para.sub_quotes(para.source) + + para = block_from_string(%(call #{BACKSLASH}`save()` to persist the changes)) + assert_equal 'call `save()` to persist the changes', para.sub_quotes(para.source) end test 'escaped single-line constrained monospaced chars with role' do - para = block_from_string(%(call [method]#{BACKSLASH}+save()+ to persist the changes)) + para = block_from_string(%(call [method]#{BACKSLASH}+save()+ to persist the changes), :attributes => {'compat-mode' => 'legacy'}) assert_equal 'call [method]+save()+ to persist the changes', para.sub_quotes(para.source) + + para = block_from_string(%(call [method]#{BACKSLASH}`save()` to persist the changes)) + assert_equal 'call [method]`save()` to persist the changes', para.sub_quotes(para.source) end test 'escaped role on single-line constrained monospaced chars' do - para = block_from_string(%(call #{BACKSLASH}[method]+save()+ to persist the changes)) + para = block_from_string(%(call #{BACKSLASH}[method]+save()+ to persist the changes), :attributes => {'compat-mode' => 'legacy'}) + assert_equal 'call [method]<code>save()</code> to persist the changes', para.sub_quotes(para.source) + + para = block_from_string(%(call #{BACKSLASH}[method]`save()` to persist the changes)) assert_equal 'call [method]<code>save()</code> to persist the changes', para.sub_quotes(para.source) end test 'escaped role on escaped single-line constrained monospaced chars' do - para = block_from_string(%(call #{BACKSLASH}[method]#{BACKSLASH}+save()+ to persist the changes)) + para = block_from_string(%(call #{BACKSLASH}[method]#{BACKSLASH}+save()+ to persist the changes), :attributes => {'compat-mode' => 'legacy'}) assert_equal %(call #{BACKSLASH}[method]+save()+ to persist the changes), para.sub_quotes(para.source) + + para = block_from_string(%(call #{BACKSLASH}[method]#{BACKSLASH}`save()` to persist the changes)) + assert_equal %(call #{BACKSLASH}[method]`save()` to persist the changes), para.sub_quotes(para.source) end test 'single-line unconstrained monospaced chars' do - para = block_from_string(%q{Git++Hub++}) + para = block_from_string(%q{Git++Hub++}, :attributes => {'compat-mode' => 'legacy'}) + assert_equal 'Git<code>Hub</code>', para.sub_quotes(para.source) + + para = block_from_string(%q{Git[x-]++Hub++}) + assert_equal 'Git<code>Hub</code>', para.apply_subs(para.source) + + para = block_from_string(%q{Git``Hub``}) assert_equal 'Git<code>Hub</code>', para.sub_quotes(para.source) end test 'escaped single-line unconstrained monospaced chars' do - para = block_from_string(%(Git#{BACKSLASH}++Hub++)) + para = block_from_string(%(Git#{BACKSLASH}++Hub++), :attributes => {'compat-mode' => 'legacy'}) assert_equal 'Git+<code>Hub</code>+', para.sub_quotes(para.source) + + para = block_from_string(%(Git#{BACKSLASH}``Hub``)) + assert_equal 'Git`<code>Hub</code>`', para.sub_quotes(para.source) end test 'multi-line unconstrained monospaced chars' do - para = block_from_string(%Q{Git++\nH\nu\nb++}) + para = block_from_string(%Q{Git++\nH\nu\nb++}, :attributes => {'compat-mode' => 'legacy'}) + assert_equal "Git<code>\nH\nu\nb</code>", para.sub_quotes(para.source) + + para = block_from_string(%Q{Git[x-]++\nH\nu\nb++}) + assert_equal %(Git<code>\nH\nu\nb</code>), para.apply_subs(para.source) + + para = block_from_string(%Q{Git``\nH\nu\nb``}) assert_equal "Git<code>\nH\nu\nb</code>", para.sub_quotes(para.source) end @@ -1046,8 +1085,8 @@ EOS result = para.extract_passthroughs(para.source) assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, para.passthroughs.size - assert_equal '<code>inline code</code>', para.passthroughs.first[:text] - assert para.passthroughs.first[:subs].empty? + assert_equal '<code>inline code</code>', para.passthroughs[0][:text] + assert para.passthroughs[0][:subs].empty? end test 'collect multi-line inline triple plus passthroughs' do @@ -1055,8 +1094,8 @@ EOS result = para.extract_passthroughs(para.source) assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, para.passthroughs.size - assert_equal "<code>inline\ncode</code>", para.passthroughs.first[:text] - assert para.passthroughs.first[:subs].empty? + assert_equal "<code>inline\ncode</code>", para.passthroughs[0][:text] + assert para.passthroughs[0][:subs].empty? end test 'collect inline double dollar passthroughs' do @@ -1064,8 +1103,17 @@ EOS result = para.extract_passthroughs(para.source) assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, para.passthroughs.size - assert_equal '<code>{code}</code>', para.passthroughs.first[:text] - assert_equal [:specialcharacters], para.passthroughs.first[:subs] + assert_equal '<code>{code}</code>', para.passthroughs[0][:text] + assert_equal [:specialcharacters], para.passthroughs[0][:subs] + end + + test 'collect inline double plus passthroughs' do + para = block_from_string('++<code>{code}</code>++') + result = para.extract_passthroughs(para.source) + assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result + assert_equal 1, para.passthroughs.size + assert_equal '<code>{code}</code>', para.passthroughs[0][:text] + assert_equal [:specialcharacters], para.passthroughs[0][:subs] end test 'collect multi-line inline double dollar passthroughs' do @@ -1073,8 +1121,17 @@ EOS result = para.extract_passthroughs(para.source) assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, para.passthroughs.size - assert_equal "<code>\n{code}\n</code>", para.passthroughs.first[:text] - assert_equal [:specialcharacters], para.passthroughs.first[:subs] + assert_equal "<code>\n{code}\n</code>", para.passthroughs[0][:text] + assert_equal [:specialcharacters], para.passthroughs[0][:subs] + end + + test 'collect multi-line inline double plus passthroughs' do + para = block_from_string("++<code>\n{code}\n</code>++") + result = para.extract_passthroughs(para.source) + assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result + assert_equal 1, para.passthroughs.size + assert_equal "<code>\n{code}\n</code>", para.passthroughs[0][:text] + assert_equal [:specialcharacters], para.passthroughs[0][:subs] end test 'collect passthroughs from inline pass macro' do @@ -1082,8 +1139,8 @@ EOS result = para.extract_passthroughs(para.source) assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, para.passthroughs.size - assert_equal %q{<code>['code']</code>}, para.passthroughs.first[:text] - assert_equal [:specialcharacters, :quotes], para.passthroughs.first[:subs] + assert_equal %q{<code>['code']</code>}, para.passthroughs[0][:text] + assert_equal [:specialcharacters, :quotes], para.passthroughs[0][:subs] end test 'collect multi-line passthroughs from inline pass macro' do @@ -1091,15 +1148,15 @@ EOS result = para.extract_passthroughs(para.source) assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, para.passthroughs.size - assert_equal %Q{<code>['more\ncode']</code>}, para.passthroughs.first[:text] - assert_equal [:specialcharacters, :quotes], para.passthroughs.first[:subs] + assert_equal %Q{<code>['more\ncode']</code>}, para.passthroughs[0][:text] + assert_equal [:specialcharacters, :quotes], para.passthroughs[0][:subs] end test 'resolves sub shorthands on inline pass macro' do para = block_from_string 'pass:q,a[*<{backend}>*]' result = para.extract_passthroughs para.source assert_equal 1, para.passthroughs.size - assert_equal [:quotes, :attributes], para.passthroughs.first[:subs] + assert_equal [:quotes, :attributes], para.passthroughs[0][:subs] result = para.restore_passthroughs result assert_equal '<strong><html5></strong>', result end @@ -1107,7 +1164,7 @@ EOS # NOTE placeholder is surrounded by text to prevent reader from stripping trailing boundary char (unique to test scenario) test 'restore inline passthroughs without subs' do para = block_from_string("some #{Asciidoctor::Substitutors::PASS_START}" + '0' + "#{Asciidoctor::Substitutors::PASS_END} to study") - para.passthroughs << {:text => '<code>inline code</code>', :subs => []} + para.passthroughs[0] = {:text => '<code>inline code</code>', :subs => []} result = para.restore_passthroughs(para.source) assert_equal "some <code>inline code</code> to study", result end @@ -1115,12 +1172,22 @@ EOS # NOTE placeholder is surrounded by text to prevent reader from stripping trailing boundary char (unique to test scenario) test 'restore inline passthroughs with subs' do para = block_from_string("some #{Asciidoctor::Substitutors::PASS_START}" + '0' + "#{Asciidoctor::Substitutors::PASS_END} to study in the #{Asciidoctor::Substitutors::PASS_START}" + '1' + "#{Asciidoctor::Substitutors::PASS_END} programming language") - para.passthroughs << {:text => '<code>{code}</code>', :subs => [:specialcharacters]} - para.passthroughs << {:text => '{language}', :subs => [:specialcharacters]} + para.passthroughs[0] = {:text => '<code>{code}</code>', :subs => [:specialcharacters]} + para.passthroughs[1] = {:text => '{language}', :subs => [:specialcharacters]} result = para.restore_passthroughs(para.source) assert_equal 'some <code>{code}</code> to study in the {language} programming language', result end + test 'restore nested passthroughs' do + result = render_embedded_string %q(+I'm in pass:q[`mono`].+), :doctype => :inline + assert_equal %q(I'm in <code>mono</code>.), result + end + + test 'should honor role on double plus passthrough' do + result = render_embedded_string 'Print the version using [var]++{asciidoctor-version}++.', :doctype => :inline + assert_equal 'Print the version using <span class="var">{asciidoctor-version}</span>.', result + end + test 'complex inline passthrough macro' do text_to_escape = %q{[(] <'basic form'> <'logical operator'> <'basic form'> [)]} para = block_from_string %($$#{text_to_escape}$$) @@ -1201,9 +1268,17 @@ EOS assert_equal '\(\sqrt{4} = 2\)', para.content end - test 'should passthrough asciimath macro inside another passthrough' do - input = 'the text `asciimath:[x = y]` should be passed through as `literal` text' - para = block_from_string input + test 'should passthrough math macro inside another passthrough' do + input = 'the text `asciimath:[x = y]` should be passed through as +literal+ text' + para = block_from_string input, :attributes => {'compat-mode' => 'legacy'} + assert_equal 'the text <code>asciimath:[x = y]</code> should be passed through as <code>literal</code> text', para.content + + input = 'the text [x-]`asciimath:[x = y]` should be passed through as `literal` text' + para = block_from_string input + assert_equal 'the text <code>asciimath:[x = y]</code> should be passed through as <code>literal</code> text', para.content + + input = 'the text `+asciimath:[x = y]+` should be passed through as `literal` text' + para = block_from_string input assert_equal 'the text <code>asciimath:[x = y]</code> should be passed through as <code>literal</code> text', para.content end diff --git a/test/text_test.rb b/test/text_test.rb index cc675845..6e40346f 100644 --- a/test/text_test.rb +++ b/test/text_test.rb @@ -214,48 +214,69 @@ This line is separated something that is not a horizontal rule... refute_match(/#/, render_string("An #unquoted# word")) end - test "backtick-escaped text followed by single-quoted text" do + test "backtick text followed by single-quoted text" do assert_match(/<code>foo<\/code>/, render_string(%Q(run `foo` 'dog'))) end + test 'plus characters inside single plus passthrough' do + assert_xpath '//p[text()="+"]', render_embedded_string('+++') + assert_xpath '//p[text()="+="]', render_embedded_string('++=+') + end + + test 'plus passthrough escapes entity reference' do + assert_match(/&#44;/, render_embedded_string('+,+')) + assert_match(/one&#44;two/, render_embedded_string('one++,++two')) + end + context "basic styling" do setup do - @rendered = render_string("A *BOLD* word. An _italic_ word. A +mono+ word. ^superscript!^ and some ~subscript~.") + @rendered = render_string("A *BOLD* word. An _italic_ word. A `mono` word. ^superscript!^ and some ~subscript~.") end test "strong" do - assert_xpath "//strong", @rendered + assert_xpath "//strong", @rendered, 1 end test "italic" do - assert_xpath "//em", @rendered + assert_xpath "//em", @rendered, 1 end test "monospaced" do - assert_xpath "//code", @rendered + assert_xpath "//code", @rendered, 1 end test "superscript" do - assert_xpath "//sup", @rendered + assert_xpath "//sup", @rendered, 1 end test "subscript" do - assert_xpath "//sub", @rendered + assert_xpath "//sub", @rendered, 1 end - test "backticks" do - assert_xpath "//code", render_string("This is `totally cool`.") + test "passthrough" do + assert_xpath "//code", render_string("This is +passed through+."), 0 + assert_xpath "//code", render_string("This is +passed through and monospaced+.", :attributes => {'compat-mode' => 'legacy'}), 1 end test "nested styles" do - rendered = render_string("Winning *big _time_* in the +city *boyeeee*+.") + rendered = render_string("Winning *big _time_* in the +city *boyeeee*+.", :attributes => {'compat-mode' => 'legacy'}) + + assert_xpath "//strong/em", rendered + assert_xpath "//code/strong", rendered + + rendered = render_string("Winning *big _time_* in the `city *boyeeee*`.") assert_xpath "//strong/em", rendered assert_xpath "//code/strong", rendered end test "unconstrained quotes" do - rendered_chars = render_string("**B**__I__++M++") + rendered_chars = render_string("**B**__I__++M++", :attributes => {'compat-mode' => 'legacy'}) + assert_xpath "//strong", rendered_chars + assert_xpath "//em", rendered_chars + assert_xpath "//code", rendered_chars + + rendered_chars = render_string("**B**__I__``M``") assert_xpath "//strong", rendered_chars assert_xpath "//em", rendered_chars assert_xpath "//code", rendered_chars |
