From 8dbc4746fe7c723b67f3c90cbf40fd7bf6c29cb7 Mon Sep 17 00:00:00 2001
From: Jakub Jirutka <jakub@jirutka.cz>
Date: Wed, 13 May 2015 01:07:48 +0200
Subject: [PATCH] Handle AsciiDoc better, reuse HTML pipeline filters (fixes
 #9263)

---
 app/helpers/application_helper.rb             | 12 +++-
 app/helpers/gitlab_markdown_helper.rb         | 15 ++++-
 app/helpers/tree_helper.rb                    |  2 +
 lib/gitlab/asciidoc.rb                        | 60 +++++++++++++++++++
 lib/gitlab/markdown_helper.rb                 | 11 +++-
 spec/helpers/application_helper_spec.rb       |  9 ++-
 spec/helpers/gitlab_markdown_helper_spec.rb   |  8 +++
 spec/lib/gitlab/asciidoc_spec.rb              | 59 ++++++++++++++++++
 .../lib/gitlab/gitlab_markdown_helper_spec.rb | 14 ++++-
 9 files changed, 184 insertions(+), 6 deletions(-)
 create mode 100644 lib/gitlab/asciidoc.rb
 create mode 100644 spec/lib/gitlab/asciidoc_spec.rb

diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index ea9722b9bef..bc07c09cd4a 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -222,8 +222,12 @@ module ApplicationHelper
   end
 
   def render_markup(file_name, file_content)
-    GitHub::Markup.render(file_name, file_content).
-      force_encoding(file_content.encoding).html_safe
+    if asciidoc?(file_name)
+      asciidoc(file_content)
+    else
+      GitHub::Markup.render(file_name, file_content).
+        force_encoding(file_content.encoding).html_safe
+    end
   rescue RuntimeError
     simple_format(file_content)
   end
@@ -236,6 +240,10 @@ module ApplicationHelper
     Gitlab::MarkdownHelper.gitlab_markdown?(filename)
   end
 
+  def asciidoc?(filename)
+    Gitlab::MarkdownHelper.asciidoc?(filename)
+  end
+
   # Overrides ActionView::Helpers::UrlHelper#link_to to add `rel="nofollow"` to
   # external links
   def link_to(name = nil, options = nil, html_options = {})
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 846aded4bda..7bcc011fd5f 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -56,6 +56,16 @@ module GitlabMarkdownHelper
     @markdown.render(text).html_safe
   end
 
+  def asciidoc(text)
+    Gitlab::Asciidoc.render(text, {
+      commit: @commit,
+      project: @project,
+      project_wiki: @project_wiki,
+      requested_path: @path,
+      ref: @ref
+    })
+  end
+
   # Return the first line of +text+, up to +max_chars+, after parsing the line
   # as Markdown.  HTML tags in the parsed output are not counted toward the
   # +max_chars+ limit.  If the length limit falls within a tag's contents, then
@@ -67,8 +77,11 @@ module GitlabMarkdownHelper
   end
 
   def render_wiki_content(wiki_page)
-    if wiki_page.format == :markdown
+    case wiki_page.format
+    when :markdown
       markdown(wiki_page.content)
+    when :asciidoc
+      asciidoc(wiki_page.content)
     else
       wiki_page.formatted_content.html_safe
     end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 6dd9b6f017c..c03564a71ab 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -27,6 +27,8 @@ module TreeHelper
   def render_readme(readme)
     if gitlab_markdown?(readme.name)
       preserve(markdown(readme.data))
+    elsif asciidoc?(readme.name)
+      asciidoc(readme.data)
     elsif markup?(readme.name)
       render_markup(readme.name, readme.data)
     else
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
new file mode 100644
index 00000000000..bf33e5b1b1e
--- /dev/null
+++ b/lib/gitlab/asciidoc.rb
@@ -0,0 +1,60 @@
+require 'asciidoctor'
+require 'html/pipeline'
+
+module Gitlab
+  # Parser/renderer for the AsciiDoc format that uses Asciidoctor and filters
+  # the resulting HTML through HTML pipeline filters.
+  module Asciidoc
+
+    # Provide autoload paths for filters to prevent a circular dependency error
+    autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter'
+
+    DEFAULT_ADOC_ATTRS = [
+      'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab',
+      'env-gitlab', 'source-highlighter=html-pipeline'
+    ].freeze
+
+    # Public: Converts the provided Asciidoc markup into HTML.
+    #
+    # input         - the source text in Asciidoc format
+    # context       - a Hash with the template context:
+    #                 :commit
+    #                 :project
+    #                 :project_wiki
+    #                 :requested_path
+    #                 :ref
+    # asciidoc_opts - a Hash of options to pass to the Asciidoctor converter
+    # html_opts     - a Hash of options for HTML output:
+    #                 :xhtml - output XHTML instead of HTML
+    #
+    def self.render(input, context, asciidoc_opts = {}, html_opts = {})
+      asciidoc_opts = asciidoc_opts.reverse_merge(
+        safe: :secure,
+        backend: html_opts[:xhtml] ? :xhtml5 : :html5,
+        attributes: []
+      )
+      asciidoc_opts[:attributes].unshift(*DEFAULT_ADOC_ATTRS)
+
+      html = ::Asciidoctor.convert(input, asciidoc_opts)
+
+      if context[:project]
+        result = HTML::Pipeline.new(filters).call(html, context)
+
+        save_opts = html_opts[:xhtml] ?
+          Nokogiri::XML::Node::SaveOptions::AS_XHTML : 0
+
+        html = result[:output].to_html(save_with: save_opts)
+      end
+
+      html.html_safe
+    end
+
+    private
+
+    def self.filters
+      [
+        Gitlab::Markdown::RelativeLinkFilter
+      ]
+    end
+  end
+end
diff --git a/lib/gitlab/markdown_helper.rb b/lib/gitlab/markdown_helper.rb
index 5e3cfc0585b..70384b1db2c 100644
--- a/lib/gitlab/markdown_helper.rb
+++ b/lib/gitlab/markdown_helper.rb
@@ -9,7 +9,7 @@ module Gitlab
     # Returns boolean
     def markup?(filename)
       filename.downcase.end_with?(*%w(.textile .rdoc .org .creole .wiki
-                                      .mediawiki .rst .adoc .asciidoc .asc))
+                                      .mediawiki .rst .adoc .ad .asciidoc))
     end
 
     # Public: Determines if a given filename is compatible with
@@ -22,6 +22,15 @@ module Gitlab
       filename.downcase.end_with?(*%w(.mdown .md .markdown))
     end
 
+    # Public: Determines if the given filename has AsciiDoc extension.
+    #
+    # filename - Filename string to check
+    #
+    # Returns boolean
+    def asciidoc?(filename)
+      filename.downcase.end_with?(*%w(.adoc .ad .asciidoc))
+    end
+
     def previewable?(filename)
       gitlab_markdown?(filename) || markup?(filename)
     end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index d4cf6540080..59870dfb192 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -261,12 +261,19 @@ describe ApplicationHelper do
     end
   end
 
-  describe 'markup_render' do
+  describe 'render_markup' do
     let(:content) { 'Noël' }
 
     it 'should preserve encoding' do
       expect(content.encoding.name).to eq('UTF-8')
       expect(render_markup('foo.rst', content).encoding.name).to eq('UTF-8')
     end
+
+    it "should delegate to #asciidoc when file name corresponds to AsciiDoc" do
+      expect(self).to receive(:asciidoc?).with('foo.adoc').and_return(true)
+      expect(self).to receive(:asciidoc).and_return('NOEL')
+
+      expect(render_markup('foo.adoc', content)).to eq('NOEL')
+    end
   end
 end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 9f3e8cf585e..0d0418f84a7 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -110,6 +110,14 @@ describe GitlabMarkdownHelper do
       helper.render_wiki_content(@wiki)
     end
 
+    it "should use Asciidoctor for asciidoc files" do
+      allow(@wiki).to receive(:format).and_return(:asciidoc)
+
+      expect(helper).to receive(:asciidoc).with('wiki content')
+
+      helper.render_wiki_content(@wiki)
+    end
+
     it "should use the Gollum renderer for all other file types" do
       allow(@wiki).to receive(:format).and_return(:rdoc)
       formatted_content_stub = double('formatted_content')
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
new file mode 100644
index 00000000000..23f83339ec5
--- /dev/null
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -0,0 +1,59 @@
+require 'spec_helper'
+require 'nokogiri'
+
+module Gitlab
+  describe Asciidoc do
+
+    let(:input) { '<b>ascii</b>' }
+    let(:context) { {} }
+    let(:html) { 'H<sub>2</sub>O' }
+
+    context "without project" do
+
+      it "should convert the input using Asciidoctor and default options" do
+        expected_asciidoc_opts = { safe: :secure, backend: :html5,
+          attributes: described_class::DEFAULT_ADOC_ATTRS }
+
+        expect(Asciidoctor).to receive(:convert)
+          .with(input, expected_asciidoc_opts).and_return(html)
+
+        expect( render(input, context) ).to eql html
+      end
+
+      context "with asciidoc_opts" do
+
+        let(:asciidoc_opts) { {safe: :safe, attributes: ['foo']} }
+
+        it "should merge the options with default ones" do
+          expected_asciidoc_opts = { safe: :safe, backend: :html5,
+            attributes: described_class::DEFAULT_ADOC_ATTRS + ['foo'] }
+
+          expect(Asciidoctor).to receive(:convert)
+            .with(input, expected_asciidoc_opts).and_return(html)
+
+          render(input, context, asciidoc_opts)
+        end
+      end
+    end
+
+    context "with project in context" do
+
+      let(:context) { {project: create(:project)} }
+
+      it "should filter converted input via HTML pipeline and return result" do
+        filtered_html = '<b>ASCII</b>'
+
+        allow(Asciidoctor).to receive(:convert).and_return(html)
+        expect_any_instance_of(HTML::Pipeline).to receive(:call)
+          .with(html, context)
+          .and_return(output: Nokogiri::HTML.fragment(filtered_html))
+
+        expect( render('foo', context) ).to eql filtered_html
+      end
+    end
+
+    def render(*args)
+      described_class.render(*args)
+    end
+  end
+end
diff --git a/spec/lib/gitlab/gitlab_markdown_helper_spec.rb b/spec/lib/gitlab/gitlab_markdown_helper_spec.rb
index ab613193f41..beaafd56352 100644
--- a/spec/lib/gitlab/gitlab_markdown_helper_spec.rb
+++ b/spec/lib/gitlab/gitlab_markdown_helper_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::MarkdownHelper do
   describe '#markup?' do
     %w(textile rdoc org creole wiki
-       mediawiki rst adoc asciidoc asc).each do |type|
+       mediawiki rst adoc ad asciidoc).each do |type|
       it "returns true for #{type} files" do
         expect(Gitlab::MarkdownHelper.markup?("README.#{type}")).to be_truthy
       end
@@ -25,4 +25,16 @@ describe Gitlab::MarkdownHelper do
       expect(Gitlab::MarkdownHelper.gitlab_markdown?('README.rb')).not_to be_truthy
     end
   end
+
+  describe '#asciidoc?' do
+    %w(adoc ad asciidoc ADOC).each do |type|
+      it "returns true for #{type} files" do
+        expect(Gitlab::MarkdownHelper.asciidoc?("README.#{type}")).to be_truthy
+      end
+    end
+
+    it 'returns false when given a non-asciidoc filename' do
+      expect(Gitlab::MarkdownHelper.asciidoc?('README.rb')).not_to be_truthy
+    end
+  end
 end
-- 
GitLab