gollum-lib 1.0.9 → 2.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.
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.
|