2019-06-14 03:53:08 -04:00
|
|
|
# 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
|
2020-01-14 10:07:55 -05:00
|
|
|
@repository = context[:repository] || context[:project].try(:repository)
|
2020-01-30 16:08:47 -05:00
|
|
|
@max_includes = context[:max_includes].to_i
|
|
|
|
@included = []
|
2019-06-14 03:53:08 -04:00
|
|
|
|
|
|
|
# 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
|
|
|
|
|
2020-01-30 16:08:47 -05:00
|
|
|
max_include_depth = doc.attributes.fetch('max-include-depth').to_i
|
|
|
|
|
|
|
|
return false if max_include_depth < 1
|
2022-03-31 20:08:25 -04:00
|
|
|
return false if target_http?(target)
|
2020-01-30 16:08:47 -05:00
|
|
|
return false if included.size >= max_includes
|
2019-06-14 03:53:08 -04:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2020-01-30 16:08:47 -05:00
|
|
|
attr_reader :context, :repository, :cache, :max_includes, :included
|
2019-06-14 03:53:08 -04:00
|
|
|
|
|
|
|
# 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?
|
|
|
|
|
2020-01-30 16:08:47 -05:00
|
|
|
included << filename
|
|
|
|
|
2019-06-14 03:53:08 -04:00
|
|
|
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
|
2020-01-14 10:07:55 -05:00
|
|
|
context[:ref] || repository&.root_ref
|
2019-06-14 03:53:08 -04:00
|
|
|
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
|