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