1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
# 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[:repository] || context[:project].try(:repository)
@max_includes = context[:max_includes].to_i
@included = []
# 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
max_include_depth = doc.attributes.fetch('max-include-depth').to_i
return false if max_include_depth < 1
return false if target_uri?(target)
return false if included.size >= max_includes
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_reader :context, :repository, :cache, :max_includes, :included
# 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?
included << filename
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] || repository&.root_ref
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
|