diff options
author | Guillaume Grossetie <g.grossetie@gmail.com> | 2019-06-14 07:53:08 +0000 |
---|---|---|
committer | James Lopez <james@gitlab.com> | 2019-06-14 07:53:08 +0000 |
commit | 3f5d7c7e1c9a8b5ba53996e8a8f2f4881929b2ea (patch) | |
tree | 6b8c33c73a5391953e9da8c9d7b7f2c72295cf07 /lib | |
parent | cd300323c8cc6744c46ef3f732c412226615abbf (diff) | |
download | gitlab-ce-3f5d7c7e1c9a8b5ba53996e8a8f2f4881929b2ea.tar.gz |
Add basic support for AsciiDoc include directive
See http://asciidoctor.org/docs/user-manual/#include-directive
Diffstat (limited to 'lib')
-rw-r--r-- | lib/gitlab/asciidoc.rb | 54 | ||||
-rw-r--r-- | lib/gitlab/asciidoc/html5_converter.rb | 32 | ||||
-rw-r--r-- | lib/gitlab/asciidoc/include_processor.rb | 126 |
3 files changed, 180 insertions, 32 deletions
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb index df8f0470063..7f8300a0c2f 100644 --- a/lib/gitlab/asciidoc.rb +++ b/lib/gitlab/asciidoc.rb @@ -1,27 +1,41 @@ # frozen_string_literal: true require 'asciidoctor' -require 'asciidoctor/converter/html5' -require "asciidoctor-plantuml" +require 'asciidoctor-plantuml' +require 'asciidoctor/extensions' +require 'gitlab/asciidoc/html5_converter' module Gitlab # Parser/renderer for the AsciiDoc format that uses Asciidoctor and filters # the resulting HTML through HTML pipeline filters. module Asciidoc - DEFAULT_ADOC_ATTRS = [ - 'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab', - 'env-gitlab', 'source-highlighter=html-pipeline', 'icons=font', - 'outfilesuffix=.adoc' - ].freeze + MAX_INCLUDE_DEPTH = 5 + DEFAULT_ADOC_ATTRS = { + 'showtitle' => true, + 'idprefix' => 'user-content-', + 'idseparator' => '-', + 'env' => 'gitlab', + 'env-gitlab' => '', + 'source-highlighter' => 'html-pipeline', + 'icons' => 'font', + 'outfilesuffix' => '.adoc', + 'max-include-depth' => MAX_INCLUDE_DEPTH + }.freeze # Public: Converts the provided Asciidoc markup into HTML. # # input - the source text in Asciidoc format + # context - :commit, :project, :ref, :requested_path # def self.render(input, context) + extensions = proc do + include_processor ::Gitlab::Asciidoc::IncludeProcessor.new(context) + end + asciidoc_opts = { safe: :secure, backend: :gitlab_html5, - attributes: DEFAULT_ADOC_ATTRS } + attributes: DEFAULT_ADOC_ATTRS, + extensions: extensions } context[:pipeline] = :ascii_doc @@ -40,29 +54,5 @@ module Gitlab conf.txt_enable = false end end - - class Html5Converter < Asciidoctor::Converter::Html5Converter - extend Asciidoctor::Converter::Config - - register_for 'gitlab_html5' - - def stem(node) - return super unless node.style.to_sym == :latexmath - - %(<pre#{id_attribute(node)} data-math-style="display"><code>#{node.content}</code></pre>) - end - - def inline_quoted(node) - return super unless node.type.to_sym == :latexmath - - %(<code#{id_attribute(node)} data-math-style="inline">#{node.text}</code>) - end - - private - - def id_attribute(node) - node.id ? %( id="#{node.id}") : nil - end - end end end diff --git a/lib/gitlab/asciidoc/html5_converter.rb b/lib/gitlab/asciidoc/html5_converter.rb new file mode 100644 index 00000000000..2c5c74e4789 --- /dev/null +++ b/lib/gitlab/asciidoc/html5_converter.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'asciidoctor' +require 'asciidoctor/converter/html5' + +module Gitlab + module Asciidoc + class Html5Converter < Asciidoctor::Converter::Html5Converter + extend Asciidoctor::Converter::Config + + register_for 'gitlab_html5' + + def stem(node) + return super unless node.style.to_sym == :latexmath + + %(<pre#{id_attribute(node)} data-math-style="display"><code>#{node.content}</code></pre>) + end + + def inline_quoted(node) + return super unless node.type.to_sym == :latexmath + + %(<code#{id_attribute(node)} data-math-style="inline">#{node.text}</code>) + end + + private + + def id_attribute(node) + node.id ? %( id="#{node.id}") : nil + end + end + end +end diff --git a/lib/gitlab/asciidoc/include_processor.rb b/lib/gitlab/asciidoc/include_processor.rb new file mode 100644 index 00000000000..c6fbf540e9c --- /dev/null +++ b/lib/gitlab/asciidoc/include_processor.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require 'asciidoctor/include_ext/include_processor' + +module Gitlab + module Asciidoc + # Asciidoctor extension for processing includes (macro include::[]) within + # documents inside the same repository. + class IncludeProcessor < Asciidoctor::IncludeExt::IncludeProcessor + extend ::Gitlab::Utils::Override + + def initialize(context) + super(logger: Gitlab::AppLogger) + + @context = context + @repository = context[:project].try(:repository) + + # Note: Asciidoctor calls #freeze on extensions, so we can't set new + # instance variables after initialization. + @cache = { + uri_types: {} + } + end + + protected + + override :include_allowed? + def include_allowed?(target, reader) + doc = reader.document + + return false if doc.attributes.fetch('max-include-depth').to_i < 1 + return false if target_uri?(target) + + true + end + + override :resolve_target_path + def resolve_target_path(target, reader) + return unless repository.try(:exists?) + + base_path = reader.include_stack.empty? ? requested_path : reader.file + path = resolve_relative_path(target, base_path) + + path if Gitlab::Git::Blob.find(repository, ref, path) + end + + override :read_lines + def read_lines(filename, selector) + blob = read_blob(ref, filename) + + if selector + blob.data.each_line.select.with_index(1, &selector) + else + blob.data + end + end + + override :unresolved_include! + def unresolved_include!(target, reader) + reader.unshift_line("*[ERROR: include::#{target}[] - unresolved directive]*") + end + + private + + attr_accessor :context, :repository, :cache + + # Gets a Blob at a path for a specific revision. + # This method will check that the Blob exists and contains readable text. + # + # revision - The String SHA1. + # path - The String file path. + # + # Returns a Blob + def read_blob(ref, filename) + blob = repository&.blob_at(ref, filename) + + raise 'Blob not found' unless blob + raise 'File is not readable' unless blob.readable_text? + + blob + end + + # Resolves the given relative path of file in repository into canonical + # path based on the specified base_path. + # + # Examples: + # + # # File in the same directory as the current path + # resolve_relative_path("users.adoc", "doc/api/README.adoc") + # # => "doc/api/users.adoc" + # + # # File in the same directory, which is also the current path + # resolve_relative_path("users.adoc", "doc/api") + # # => "doc/api/users.adoc" + # + # # Going up one level to a different directory + # resolve_relative_path("../update/7.14-to-8.0.adoc", "doc/api/README.adoc") + # # => "doc/update/7.14-to-8.0.adoc" + # + # Returns a String + def resolve_relative_path(path, base_path) + p = Pathname(base_path) + p = p.dirname unless p.extname.empty? + p += path + + p.cleanpath.to_s + end + + def current_commit + cache[:current_commit] ||= context[:commit] || repository&.commit(ref) + end + + def ref + context[:ref] || context[:project].default_branch + end + + def requested_path + cache[:requested_path] ||= Addressable::URI.unescape(context[:requested_path]) + end + + def uri_type(path) + cache[:uri_types][path] ||= current_commit&.uri_type(path) + end + end + end +end |