summaryrefslogtreecommitdiff
path: root/lib/gitlab/asciidoc/include_processor.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/asciidoc/include_processor.rb')
-rw-r--r--lib/gitlab/asciidoc/include_processor.rb126
1 files changed, 126 insertions, 0 deletions
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