gitlab-gollum-lib 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/HISTORY.md +3 -0
- data/LICENSE +21 -0
- data/README.md +601 -0
- data/Rakefile +171 -0
- data/docs/sanitization.md +33 -0
- data/gollum-lib.gemspec +75 -0
- data/lib/gollum-lib.rb +53 -0
- data/lib/gollum-lib/blob_entry.rb +95 -0
- data/lib/gollum-lib/committer.rb +236 -0
- data/lib/gollum-lib/file.rb +101 -0
- data/lib/gollum-lib/file_view.rb +155 -0
- data/lib/gollum-lib/git_access.rb +249 -0
- data/lib/gollum-lib/gitcode.rb +48 -0
- data/lib/gollum-lib/grit_ext.rb +20 -0
- data/lib/gollum-lib/helpers.rb +13 -0
- data/lib/gollum-lib/markup.rb +689 -0
- data/lib/gollum-lib/markups.rb +14 -0
- data/lib/gollum-lib/page.rb +485 -0
- data/lib/gollum-lib/pagination.rb +62 -0
- data/lib/gollum-lib/remote_code.rb +39 -0
- data/lib/gollum-lib/sanitization.rb +176 -0
- data/lib/gollum-lib/web_sequence_diagram.rb +44 -0
- data/lib/gollum-lib/wiki.rb +833 -0
- data/licenses/licenses.txt +6 -0
- metadata +315 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https' # ruby 1.8.7 fix, remove at upgrade
|
4
|
+
require 'uri'
|
5
|
+
require 'open-uri'
|
6
|
+
|
7
|
+
module Gollum
|
8
|
+
class Gitcode
|
9
|
+
def initialize path
|
10
|
+
raise(ArgumentError, 'path is nil or empty') if path.nil? or path.empty?
|
11
|
+
|
12
|
+
@uri = URI::HTTP.build({
|
13
|
+
:path => self.unchomp(path),
|
14
|
+
:host => 'raw.github.com',
|
15
|
+
:scheme => 'https',
|
16
|
+
:port => 443 })
|
17
|
+
end
|
18
|
+
|
19
|
+
def contents
|
20
|
+
@contents ||= self.req @uri
|
21
|
+
end
|
22
|
+
|
23
|
+
def unchomp p
|
24
|
+
return p if p.nil?
|
25
|
+
p[0] == '/' ? p : ('/' + p)
|
26
|
+
end
|
27
|
+
|
28
|
+
def req uri, cut = 1
|
29
|
+
return "Too many redirects or retries" if cut >= 10
|
30
|
+
http = Net::HTTP.new uri.host, uri.port
|
31
|
+
http.use_ssl = true
|
32
|
+
resp = http.get uri.path, {
|
33
|
+
'Accept' => 'text/plain',
|
34
|
+
'Cache-Control' => 'no-cache',
|
35
|
+
'Connection' => 'keep-alive',
|
36
|
+
'Host' => uri.host,
|
37
|
+
'User-Agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0'
|
38
|
+
}
|
39
|
+
code = resp.code.to_i
|
40
|
+
return resp.body if code == 200
|
41
|
+
return "Not Found" if code == 404
|
42
|
+
return "Unhandled Response Code #{code}" unless code == 304 or not resp.header['location'].nil?
|
43
|
+
loc = URI.parse resp.header['location']
|
44
|
+
uri2 = loc.relative?() ? (uri + loc) : loc # overloads (+)
|
45
|
+
return req uri2, (cut + 1)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
|
3
|
+
module Grit
|
4
|
+
class Blob
|
5
|
+
def is_symlink
|
6
|
+
self.mode == 0120000
|
7
|
+
end
|
8
|
+
|
9
|
+
def symlink_target(base_path = nil)
|
10
|
+
target = self.data
|
11
|
+
new_path = File.expand_path(File.join('..', target), base_path)
|
12
|
+
|
13
|
+
if File.file? new_path
|
14
|
+
return new_path
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,689 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'cgi'
|
4
|
+
require 'pygments'
|
5
|
+
require 'base64'
|
6
|
+
|
7
|
+
require File.expand_path '../helpers', __FILE__
|
8
|
+
require File.expand_path '../remote_code', __FILE__
|
9
|
+
|
10
|
+
# initialize Pygments
|
11
|
+
Pygments.start
|
12
|
+
|
13
|
+
module Gollum
|
14
|
+
|
15
|
+
class Markup
|
16
|
+
include Helpers
|
17
|
+
|
18
|
+
@formats = {}
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_reader :formats
|
22
|
+
|
23
|
+
# Register a file extension and associated markup type
|
24
|
+
#
|
25
|
+
# ext - The file extension
|
26
|
+
# name - The name of the markup type
|
27
|
+
# options - Hash of options:
|
28
|
+
# regexp - Regexp to match against.
|
29
|
+
# Defaults to exact match of ext.
|
30
|
+
#
|
31
|
+
# If given a block, that block will be registered with GitHub::Markup to
|
32
|
+
# render any matching pages
|
33
|
+
def register(ext, name, options = {}, &block)
|
34
|
+
regexp = options[:regexp] || Regexp.new(ext.to_s)
|
35
|
+
@formats[ext] = { :name => name, :regexp => regexp }
|
36
|
+
GitHub::Markup.add_markup(regexp, &block) if block_given?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_accessor :toc
|
41
|
+
attr_reader :metadata
|
42
|
+
|
43
|
+
# Initialize a new Markup object.
|
44
|
+
#
|
45
|
+
# page - The Gollum::Page.
|
46
|
+
#
|
47
|
+
# Returns a new Gollum::Markup object, ready for rendering.
|
48
|
+
def initialize(page)
|
49
|
+
@wiki = page.wiki
|
50
|
+
@name = page.filename
|
51
|
+
@data = page.text_data
|
52
|
+
@version = page.version.id if page.version
|
53
|
+
@format = page.format
|
54
|
+
@sub_page = page.sub_page
|
55
|
+
@parent_page = page.parent_page
|
56
|
+
@dir = ::File.dirname(page.path)
|
57
|
+
@tagmap = {}
|
58
|
+
@codemap = {}
|
59
|
+
@wsdmap = {}
|
60
|
+
@premap = {}
|
61
|
+
@toc = nil
|
62
|
+
@metadata = nil
|
63
|
+
@to_xml = { :save_with => Nokogiri::XML::Node::SaveOptions::DEFAULT_XHTML ^ 1, :indent => 0, :encoding => 'UTF-8' }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Render the content with Gollum wiki syntax on top of the file's own
|
67
|
+
# markup language.
|
68
|
+
#
|
69
|
+
# no_follow - Boolean that determines if rel="nofollow" is added to all
|
70
|
+
# <a> tags.
|
71
|
+
# encoding - Encoding Constant or String.
|
72
|
+
#
|
73
|
+
# Returns the formatted String content.
|
74
|
+
def render(no_follow = false, encoding = nil)
|
75
|
+
sanitize = no_follow ?
|
76
|
+
@wiki.history_sanitizer :
|
77
|
+
@wiki.sanitizer
|
78
|
+
|
79
|
+
data = @data.dup
|
80
|
+
data = extract_metadata(data)
|
81
|
+
data = extract_remote_code(data)
|
82
|
+
data = extract_code(data)
|
83
|
+
data = extract_wsd(data)
|
84
|
+
data = extract_tags(data)
|
85
|
+
begin
|
86
|
+
data = GitHub::Markup.render(@name, data)
|
87
|
+
if data.nil?
|
88
|
+
raise "There was an error converting #{@name} to HTML."
|
89
|
+
end
|
90
|
+
rescue Object => e
|
91
|
+
data = %{<p class="gollum-error">#{e.message}</p>}
|
92
|
+
end
|
93
|
+
data = process_tags(data)
|
94
|
+
data = process_code(data, encoding)
|
95
|
+
|
96
|
+
doc = Nokogiri::HTML::DocumentFragment.parse(data)
|
97
|
+
doc = sanitize.clean_node!(doc) if sanitize
|
98
|
+
doc,toc = process_headers(doc)
|
99
|
+
@toc = @sub_page ? ( @parent_page ? @parent_page.toc_data : "[[_TOC_]]" ) : toc
|
100
|
+
yield doc if block_given?
|
101
|
+
# nokogiri's save options are ored together. FORMAT has a value of 1 so ^ 1 removes it.
|
102
|
+
# formatting will create extra spaces in pre tags.
|
103
|
+
# https://github.com/sparklemotion/nokogiri/issues/782
|
104
|
+
# DEFAULT_HTML encodes unicode so XHTML is used for proper unicode support in href.
|
105
|
+
data = doc.to_xml( @to_xml )
|
106
|
+
|
107
|
+
data = process_toc_tags(data)
|
108
|
+
data = process_wsd(data)
|
109
|
+
data.gsub!(/<p><\/p>/) do
|
110
|
+
''
|
111
|
+
end
|
112
|
+
|
113
|
+
data
|
114
|
+
end
|
115
|
+
|
116
|
+
# Inserts header anchors and creates TOC
|
117
|
+
#
|
118
|
+
# doc - Nokogiri parsed document
|
119
|
+
#
|
120
|
+
# Returns doc Document and toc String
|
121
|
+
def process_headers(doc)
|
122
|
+
toc = nil
|
123
|
+
doc.css('h1,h2,h3,h4,h5,h6').each do |h|
|
124
|
+
# must escape "
|
125
|
+
h_name = h.content.gsub(' ','-').gsub('"','%22')
|
126
|
+
|
127
|
+
level = h.name.gsub(/[hH]/,'').to_i
|
128
|
+
|
129
|
+
# Add anchors
|
130
|
+
h.add_child(%Q{<a class="anchor" id="#{h_name}" href="##{h_name}"></a>})
|
131
|
+
|
132
|
+
# Build TOC
|
133
|
+
toc ||= Nokogiri::XML::DocumentFragment.parse('<div class="toc"><div class="toc-title">Table of Contents</div></div>')
|
134
|
+
tail ||= toc.child
|
135
|
+
tail_level ||= 0
|
136
|
+
|
137
|
+
while tail_level < level
|
138
|
+
node = Nokogiri::XML::Node.new('ul', doc)
|
139
|
+
tail = tail.add_child(node)
|
140
|
+
tail_level += 1
|
141
|
+
end
|
142
|
+
while tail_level > level
|
143
|
+
tail = tail.parent
|
144
|
+
tail_level -= 1
|
145
|
+
end
|
146
|
+
node = Nokogiri::XML::Node.new('li', doc)
|
147
|
+
# % -> %25 so anchors work on Firefox. See issue #475
|
148
|
+
node.add_child(%Q{<a href="##{h_name}">#{h.content}</a>})
|
149
|
+
tail.add_child(node)
|
150
|
+
end
|
151
|
+
toc = toc.to_xml(@to_xml) if toc != nil
|
152
|
+
[doc, toc]
|
153
|
+
end
|
154
|
+
|
155
|
+
#########################################################################
|
156
|
+
#
|
157
|
+
# Tags
|
158
|
+
#
|
159
|
+
#########################################################################
|
160
|
+
|
161
|
+
# Extract all tags into the tagmap and replace with placeholders.
|
162
|
+
#
|
163
|
+
# data - The raw String data.
|
164
|
+
#
|
165
|
+
# Returns the placeholder'd String data.
|
166
|
+
def extract_tags(data)
|
167
|
+
if @format == :asciidoc
|
168
|
+
return data
|
169
|
+
end
|
170
|
+
data.gsub!(/(.?)\[\[(.+?)\]\]([^\[]?)/m) do
|
171
|
+
if $1 == "'" && $3 != "'"
|
172
|
+
"[[#{$2}]]#{$3}"
|
173
|
+
elsif $2.include?('][')
|
174
|
+
if $2[0..4] == 'file:'
|
175
|
+
pre = $1
|
176
|
+
post = $3
|
177
|
+
parts = $2.split('][')
|
178
|
+
parts[0][0..4] = ""
|
179
|
+
link = "#{parts[1]}|#{parts[0].sub(/\.org/,'')}"
|
180
|
+
id = Digest::SHA1.hexdigest(link)
|
181
|
+
@tagmap[id] = link
|
182
|
+
"#{pre}#{id}#{post}"
|
183
|
+
else
|
184
|
+
$&
|
185
|
+
end
|
186
|
+
else
|
187
|
+
id = Digest::SHA1.hexdigest($2)
|
188
|
+
@tagmap[id] = $2
|
189
|
+
"#{$1}#{id}#{$3}"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
data
|
193
|
+
end
|
194
|
+
|
195
|
+
# Process all tags from the tagmap and replace the placeholders with the
|
196
|
+
# final markup.
|
197
|
+
#
|
198
|
+
# data - The String data (with placeholders).
|
199
|
+
#
|
200
|
+
# Returns the marked up String data.
|
201
|
+
def process_tags(data)
|
202
|
+
@tagmap.each do |id, tag|
|
203
|
+
# If it's preformatted, just put the tag back
|
204
|
+
if is_preformatted?(data, id)
|
205
|
+
data.gsub!(id) do
|
206
|
+
"[[#{tag}]]"
|
207
|
+
end
|
208
|
+
else
|
209
|
+
data.gsub!(id) do
|
210
|
+
process_tag(tag).gsub('%2F', '/')
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
data
|
215
|
+
end
|
216
|
+
|
217
|
+
# Find `id` within `data` and determine if it's within
|
218
|
+
# preformatted tags.
|
219
|
+
#
|
220
|
+
# data - The String data (with placeholders).
|
221
|
+
# id - The String SHA1 hash.
|
222
|
+
PREFORMATTED_TAGS = %w(code tt)
|
223
|
+
def is_preformatted?(data, id)
|
224
|
+
doc = Nokogiri::HTML::DocumentFragment.parse(data)
|
225
|
+
node = doc.search("[text()*='#{id}']").first
|
226
|
+
node && (PREFORMATTED_TAGS.include?(node.name) ||
|
227
|
+
node.ancestors.any? { |a| PREFORMATTED_TAGS.include?(a.name) })
|
228
|
+
end
|
229
|
+
|
230
|
+
# Process a single tag into its final HTML form.
|
231
|
+
#
|
232
|
+
# tag - The String tag contents (the stuff inside the double
|
233
|
+
# brackets).
|
234
|
+
#
|
235
|
+
# Returns the String HTML version of the tag.
|
236
|
+
def process_tag(tag)
|
237
|
+
if tag =~ /^_TOC_$/
|
238
|
+
%{[[#{tag}]]}
|
239
|
+
elsif tag =~ /^_$/
|
240
|
+
%{<div class="clearfloats"></div>}
|
241
|
+
elsif html = process_image_tag(tag)
|
242
|
+
html
|
243
|
+
elsif html = process_file_link_tag(tag)
|
244
|
+
html
|
245
|
+
else
|
246
|
+
process_page_link_tag(tag)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Attempt to process the tag as an image tag.
|
251
|
+
#
|
252
|
+
# tag - The String tag contents (the stuff inside the double brackets).
|
253
|
+
#
|
254
|
+
# Returns the String HTML if the tag is a valid image tag or nil
|
255
|
+
# if it is not.
|
256
|
+
def process_image_tag(tag)
|
257
|
+
parts = tag.split('|')
|
258
|
+
return if parts.size.zero?
|
259
|
+
|
260
|
+
name = parts[0].strip
|
261
|
+
path = if file = find_file(name)
|
262
|
+
::File.join @wiki.base_path, file.path
|
263
|
+
elsif name =~ /^https?:\/\/.+(jpg|png|gif|svg|bmp)$/i
|
264
|
+
name
|
265
|
+
end
|
266
|
+
|
267
|
+
if path
|
268
|
+
opts = parse_image_tag_options(tag)
|
269
|
+
|
270
|
+
containered = false
|
271
|
+
|
272
|
+
classes = [] # applied to whatever the outermost container is
|
273
|
+
attrs = [] # applied to the image
|
274
|
+
|
275
|
+
align = opts['align']
|
276
|
+
if opts['float']
|
277
|
+
containered = true
|
278
|
+
align ||= 'left'
|
279
|
+
if %w{left right}.include?(align)
|
280
|
+
classes << "float-#{align}"
|
281
|
+
end
|
282
|
+
elsif %w{top texttop middle absmiddle bottom absbottom baseline}.include?(align)
|
283
|
+
attrs << %{align="#{align}"}
|
284
|
+
elsif align
|
285
|
+
if %w{left center right}.include?(align)
|
286
|
+
containered = true
|
287
|
+
classes << "align-#{align}"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
if width = opts['width']
|
292
|
+
if width =~ /^\d+(\.\d+)?(em|px)$/
|
293
|
+
attrs << %{width="#{width}"}
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
if height = opts['height']
|
298
|
+
if height =~ /^\d+(\.\d+)?(em|px)$/
|
299
|
+
attrs << %{height="#{height}"}
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
if alt = opts['alt']
|
304
|
+
attrs << %{alt="#{alt}"}
|
305
|
+
end
|
306
|
+
|
307
|
+
attr_string = attrs.size > 0 ? attrs.join(' ') + ' ' : ''
|
308
|
+
|
309
|
+
if opts['frame'] || containered
|
310
|
+
classes << 'frame' if opts['frame']
|
311
|
+
%{<span class="#{classes.join(' ')}">} +
|
312
|
+
%{<span>} +
|
313
|
+
%{<img src="#{path}" #{attr_string}/>} +
|
314
|
+
(alt ? %{<span>#{alt}</span>} : '') +
|
315
|
+
%{</span>} +
|
316
|
+
%{</span>}
|
317
|
+
else
|
318
|
+
%{<img src="#{path}" #{attr_string}/>}
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Parse any options present on the image tag and extract them into a
|
324
|
+
# Hash of option names and values.
|
325
|
+
#
|
326
|
+
# tag - The String tag contents (the stuff inside the double brackets).
|
327
|
+
#
|
328
|
+
# Returns the options Hash:
|
329
|
+
# key - The String option name.
|
330
|
+
# val - The String option value or true if it is a binary option.
|
331
|
+
def parse_image_tag_options(tag)
|
332
|
+
tag.split('|')[1..-1].inject({}) do |memo, attr|
|
333
|
+
parts = attr.split('=').map { |x| x.strip }
|
334
|
+
memo[parts[0]] = (parts.size == 1 ? true : parts[1])
|
335
|
+
memo
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Attempt to process the tag as a file link tag.
|
340
|
+
#
|
341
|
+
# tag - The String tag contents (the stuff inside the double
|
342
|
+
# brackets).
|
343
|
+
#
|
344
|
+
# Returns the String HTML if the tag is a valid file link tag or nil
|
345
|
+
# if it is not.
|
346
|
+
def process_file_link_tag(tag)
|
347
|
+
parts = tag.split('|')
|
348
|
+
return if parts.size.zero?
|
349
|
+
|
350
|
+
name = parts[0].strip
|
351
|
+
path = parts[1] && parts[1].strip
|
352
|
+
path = if path && file = find_file(path)
|
353
|
+
::File.join @wiki.base_path, file.path
|
354
|
+
elsif path =~ %r{^https?://}
|
355
|
+
path
|
356
|
+
else
|
357
|
+
nil
|
358
|
+
end
|
359
|
+
|
360
|
+
if name && path && file
|
361
|
+
%{<a href="#{::File.join @wiki.base_path, file.path}">#{name}</a>}
|
362
|
+
elsif name && path
|
363
|
+
%{<a href="#{path}">#{name}</a>}
|
364
|
+
else
|
365
|
+
nil
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# Attempt to process the tag as a page link tag.
|
370
|
+
#
|
371
|
+
# tag - The String tag contents (the stuff inside the double
|
372
|
+
# brackets).
|
373
|
+
#
|
374
|
+
# Returns the String HTML if the tag is a valid page link tag or nil
|
375
|
+
# if it is not.
|
376
|
+
def process_page_link_tag(tag)
|
377
|
+
parts = tag.split('|')
|
378
|
+
parts.reverse! if @format == :mediawiki
|
379
|
+
|
380
|
+
name, page_name = *parts.compact.map(&:strip)
|
381
|
+
cname = @wiki.page_class.cname(page_name || name)
|
382
|
+
|
383
|
+
if name =~ %r{^https?://} && page_name.nil?
|
384
|
+
%{<a href="#{name}">#{name}</a>}
|
385
|
+
else
|
386
|
+
presence = "absent"
|
387
|
+
link_name = cname
|
388
|
+
page, extra = find_page_from_name(cname)
|
389
|
+
if page
|
390
|
+
link_name = @wiki.page_class.cname(page.name)
|
391
|
+
presence = "present"
|
392
|
+
end
|
393
|
+
link = ::File.join(@wiki.base_path, page ? page.escaped_url_path : CGI.escape(link_name))
|
394
|
+
|
395
|
+
# //page is invalid
|
396
|
+
# strip all duplicate forward slashes using helpers.rb trim_leading_slash
|
397
|
+
# //page => /page
|
398
|
+
link = trim_leading_slash link
|
399
|
+
|
400
|
+
%{<a class="internal #{presence}" href="#{link}#{extra}">#{name}</a>}
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
|
405
|
+
# Process the special table of contents tag [[_TOC_]]
|
406
|
+
#
|
407
|
+
# data - The String data (with placeholders).
|
408
|
+
#
|
409
|
+
# Returns the marked up String data.
|
410
|
+
def process_toc_tags(data)
|
411
|
+
data.gsub!("[[_TOC_]]") do
|
412
|
+
@toc.nil? ? '' : @toc
|
413
|
+
end
|
414
|
+
data
|
415
|
+
end
|
416
|
+
|
417
|
+
# Find the given file in the repo.
|
418
|
+
#
|
419
|
+
# name - The String absolute or relative path of the file.
|
420
|
+
#
|
421
|
+
# Returns the Gollum::File or nil if none was found.
|
422
|
+
def find_file(name, version=@version)
|
423
|
+
if name =~ /^\//
|
424
|
+
@wiki.file(name[1..-1], version)
|
425
|
+
else
|
426
|
+
path = @dir == '.' ? name : ::File.join(@dir, name)
|
427
|
+
@wiki.file(path, version)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
# Find a page from a given cname. If the page has an anchor (#) and has
|
432
|
+
# no match, strip the anchor and try again.
|
433
|
+
#
|
434
|
+
# cname - The String canonical page name including path.
|
435
|
+
#
|
436
|
+
# Returns a Gollum::Page instance if a page is found, or an Array of
|
437
|
+
# [Gollum::Page, String extra] if a page without the extra anchor data
|
438
|
+
# is found.
|
439
|
+
def find_page_from_name(cname)
|
440
|
+
slash = cname.rindex('/')
|
441
|
+
|
442
|
+
unless slash.nil?
|
443
|
+
name = cname[slash+1..-1]
|
444
|
+
path = cname[0..slash]
|
445
|
+
page = @wiki.paged(name, path)
|
446
|
+
else
|
447
|
+
page = @wiki.paged(cname, '/') || @wiki.page(cname)
|
448
|
+
end
|
449
|
+
|
450
|
+
if page
|
451
|
+
return page
|
452
|
+
end
|
453
|
+
if pos = cname.index('#')
|
454
|
+
[@wiki.page(cname[0...pos]), cname[pos..-1]]
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
#########################################################################
|
459
|
+
#
|
460
|
+
# Remote code - fetch code from url and replace the contents to a
|
461
|
+
# code-block that gets run the next parse.
|
462
|
+
# Acceptable formats:
|
463
|
+
# ```language:local-file.ext```
|
464
|
+
# ```language:/abs/other-file.ext```
|
465
|
+
# ```language:https://example.com/somefile.txt```
|
466
|
+
#
|
467
|
+
#########################################################################
|
468
|
+
|
469
|
+
def extract_remote_code data
|
470
|
+
data.gsub /^[ \t]*``` ?([^:\n\r]+):((http)?[^`\n\r]+)```/ do
|
471
|
+
language = $1
|
472
|
+
uri = $2
|
473
|
+
protocol = $3
|
474
|
+
|
475
|
+
# Detect local file
|
476
|
+
if protocol.nil?
|
477
|
+
if file = self.find_file(uri, @wiki.ref)
|
478
|
+
contents = file.raw_data
|
479
|
+
else
|
480
|
+
# How do we communicate a render error?
|
481
|
+
next "File not found: #{CGI::escapeHTML(uri)}"
|
482
|
+
end
|
483
|
+
else
|
484
|
+
contents = Gollum::RemoteCode.new(uri).contents
|
485
|
+
end
|
486
|
+
|
487
|
+
"```#{language}\n#{contents}\n```\n"
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
#########################################################################
|
492
|
+
#
|
493
|
+
# Code
|
494
|
+
#
|
495
|
+
#########################################################################
|
496
|
+
|
497
|
+
# Extract all code blocks into the codemap and replace with placeholders.
|
498
|
+
#
|
499
|
+
# data - The raw String data.
|
500
|
+
#
|
501
|
+
# Returns the placeholder'd String data.
|
502
|
+
def extract_code(data)
|
503
|
+
data.gsub!(/^([ \t]*)(~~~+) ?([^\r\n]+)?\r?\n(.+?)\r?\n\1(~~~+)[ \t\r]*$/m) do
|
504
|
+
m_indent = $1
|
505
|
+
m_start = $2 # ~~~
|
506
|
+
m_lang = $3
|
507
|
+
m_code = $4
|
508
|
+
m_end = $5 # ~~~
|
509
|
+
|
510
|
+
# start and finish tilde fence must be the same length
|
511
|
+
return '' if m_start.length != m_end.length
|
512
|
+
|
513
|
+
lang = m_lang ? m_lang.strip : nil
|
514
|
+
id = Digest::SHA1.hexdigest("#{lang}.#{m_code}")
|
515
|
+
cached = check_cache(:code, id)
|
516
|
+
|
517
|
+
# extract lang from { .ruby } or { #stuff .ruby .indent }
|
518
|
+
# see http://johnmacfarlane.net/pandoc/README.html#delimited-code-blocks
|
519
|
+
|
520
|
+
if lang
|
521
|
+
lang = lang.match(/\.([^}\s]+)/)
|
522
|
+
lang = lang[1] unless lang.nil?
|
523
|
+
end
|
524
|
+
|
525
|
+
@codemap[id] = cached ?
|
526
|
+
{ :output => cached } :
|
527
|
+
{ :lang => lang, :code => m_code, :indent => m_indent }
|
528
|
+
|
529
|
+
"#{m_indent}#{id}" # print the SHA1 ID with the proper indentation
|
530
|
+
end
|
531
|
+
|
532
|
+
data.gsub!(/^([ \t]*)``` ?([^\r\n]+)?\r?\n(.+?)\r?\n\1```[ \t]*\r?$/m) do
|
533
|
+
lang = $2 ? $2.strip : nil
|
534
|
+
id = Digest::SHA1.hexdigest("#{lang}.#{$3}")
|
535
|
+
cached = check_cache(:code, id)
|
536
|
+
@codemap[id] = cached ?
|
537
|
+
{ :output => cached } :
|
538
|
+
{ :lang => lang, :code => $3, :indent => $1 }
|
539
|
+
"#{$1}#{id}" # print the SHA1 ID with the proper indentation
|
540
|
+
end
|
541
|
+
data
|
542
|
+
end
|
543
|
+
|
544
|
+
# Remove the leading space from a code block. Leading space
|
545
|
+
# is only removed if every single line in the block has leading
|
546
|
+
# whitespace.
|
547
|
+
#
|
548
|
+
# code - The code block to remove spaces from
|
549
|
+
# regex - A regex to match whitespace
|
550
|
+
def remove_leading_space(code, regex)
|
551
|
+
if code.lines.all? { |line| line =~ /\A\r?\n\Z/ || line =~ regex }
|
552
|
+
code.gsub!(regex) do
|
553
|
+
''
|
554
|
+
end
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
# Process all code from the codemap and replace the placeholders with the
|
559
|
+
# final HTML.
|
560
|
+
#
|
561
|
+
# data - The String data (with placeholders).
|
562
|
+
# encoding - Encoding Constant or String.
|
563
|
+
#
|
564
|
+
# Returns the marked up String data.
|
565
|
+
def process_code(data, encoding = nil)
|
566
|
+
return data if data.nil? || data.size.zero? || @codemap.size.zero?
|
567
|
+
|
568
|
+
blocks = []
|
569
|
+
@codemap.each do |id, spec|
|
570
|
+
next if spec[:output] # cached
|
571
|
+
|
572
|
+
code = spec[:code]
|
573
|
+
|
574
|
+
remove_leading_space(code, /^#{spec[:indent]}/m)
|
575
|
+
remove_leading_space(code, /^( |\t)/m)
|
576
|
+
|
577
|
+
blocks << [spec[:lang], code]
|
578
|
+
end
|
579
|
+
|
580
|
+
highlighted = []
|
581
|
+
blocks.each do |lang, code|
|
582
|
+
encoding ||= 'utf-8'
|
583
|
+
begin
|
584
|
+
# must set startinline to true for php to be highlighted without <?
|
585
|
+
# http://pygments.org/docs/lexers/
|
586
|
+
hl_code = Pygments.highlight(code, :lexer => lang, :options => {:encoding => encoding.to_s, :startinline => true})
|
587
|
+
rescue
|
588
|
+
hl_code = code
|
589
|
+
end
|
590
|
+
highlighted << hl_code
|
591
|
+
end
|
592
|
+
|
593
|
+
@codemap.each do |id, spec|
|
594
|
+
body = spec[:output] || begin
|
595
|
+
if (body = highlighted.shift.to_s).size > 0
|
596
|
+
update_cache(:code, id, body)
|
597
|
+
body
|
598
|
+
else
|
599
|
+
"<pre><code>#{CGI.escapeHTML(spec[:code])}</code></pre>"
|
600
|
+
end
|
601
|
+
end
|
602
|
+
data.gsub!(id) do
|
603
|
+
body
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
data
|
608
|
+
end
|
609
|
+
|
610
|
+
#########################################################################
|
611
|
+
#
|
612
|
+
# Sequence Diagrams
|
613
|
+
#
|
614
|
+
#########################################################################
|
615
|
+
|
616
|
+
# Extract all sequence diagram blocks into the wsdmap and replace with
|
617
|
+
# placeholders.
|
618
|
+
#
|
619
|
+
# data - The raw String data.
|
620
|
+
#
|
621
|
+
# Returns the placeholder'd String data.
|
622
|
+
def extract_wsd(data)
|
623
|
+
data.gsub(/^\{\{\{\{\{\{ ?(.+?)\r?\n(.+?)\r?\n\}\}\}\}\}\}\r?$/m) do
|
624
|
+
id = Digest::SHA1.hexdigest($2)
|
625
|
+
@wsdmap[id] = { :style => $1, :code => $2 }
|
626
|
+
id
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
# Process all diagrams from the wsdmap and replace the placeholders with
|
631
|
+
# the final HTML.
|
632
|
+
#
|
633
|
+
# data - The String data (with placeholders).
|
634
|
+
#
|
635
|
+
# Returns the marked up String data.
|
636
|
+
def process_wsd(data)
|
637
|
+
@wsdmap.each do |id, spec|
|
638
|
+
style = spec[:style]
|
639
|
+
code = spec[:code]
|
640
|
+
data.gsub!(id) do
|
641
|
+
Gollum::WebSequenceDiagram.new(code, style).to_tag
|
642
|
+
end
|
643
|
+
end
|
644
|
+
data
|
645
|
+
end
|
646
|
+
|
647
|
+
#########################################################################
|
648
|
+
#
|
649
|
+
# Metadata
|
650
|
+
#
|
651
|
+
#########################################################################
|
652
|
+
|
653
|
+
# Extract metadata for data and build metadata table. Metadata
|
654
|
+
# is content found between markers, and must
|
655
|
+
# be a valid YAML mapping.
|
656
|
+
#
|
657
|
+
# Because ri and ruby 1.8.7 are awesome, the markers can't
|
658
|
+
# be included in this documentation without triggering
|
659
|
+
# `Unhandled special: Special: type=17`
|
660
|
+
# Please read the source code for the exact markers
|
661
|
+
#
|
662
|
+
# Returns the String of formatted data with metadata removed.
|
663
|
+
def extract_metadata(data)
|
664
|
+
@metadata = {}
|
665
|
+
data
|
666
|
+
end
|
667
|
+
|
668
|
+
# Hook for getting the formatted value of extracted tag data.
|
669
|
+
#
|
670
|
+
# type - Symbol value identifying what type of data is being extracted.
|
671
|
+
# id - String SHA1 hash of original extracted tag data.
|
672
|
+
#
|
673
|
+
# Returns the String cached formatted data, or nil.
|
674
|
+
def check_cache(type, id)
|
675
|
+
end
|
676
|
+
|
677
|
+
# Hook for caching the formatted value of extracted tag data.
|
678
|
+
#
|
679
|
+
# type - Symbol value identifying what type of data is being extracted.
|
680
|
+
# id - String SHA1 hash of original extracted tag data.
|
681
|
+
# data - The String formatted value to be cached.
|
682
|
+
#
|
683
|
+
# Returns nothing.
|
684
|
+
def update_cache(type, id, data)
|
685
|
+
end
|
686
|
+
end
|
687
|
+
|
688
|
+
MarkupGFM = Markup
|
689
|
+
end
|