gollum 1.0.1 → 1.1.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 might be problematic. Click here for more details.
- data/HISTORY.md +31 -1
- data/README.md +2 -2
- data/Rakefile +2 -2
- data/bin/gollum +69 -7
- data/gollum.gemspec +14 -6
- data/lib/gollum.rb +25 -1
- data/lib/gollum/blob_entry.rb +71 -0
- data/lib/gollum/file.rb +11 -6
- data/lib/gollum/frontend/app.rb +34 -24
- data/lib/gollum/frontend/templates/compare.mustache +1 -1
- data/lib/gollum/frontend/templates/create.mustache +1 -1
- data/lib/gollum/frontend/templates/edit.mustache +2 -2
- data/lib/gollum/frontend/templates/error.mustache +11 -0
- data/lib/gollum/frontend/templates/history.mustache +3 -3
- data/lib/gollum/frontend/templates/page.mustache +9 -3
- data/lib/gollum/frontend/templates/search.mustache +19 -0
- data/lib/gollum/frontend/views/error.rb +7 -0
- data/lib/gollum/frontend/views/layout.rb +6 -0
- data/lib/gollum/frontend/views/search.rb +12 -0
- data/lib/gollum/markup.rb +73 -29
- data/lib/gollum/page.rb +49 -56
- data/lib/gollum/ruby1.8.rb +3 -0
- data/lib/gollum/wiki.rb +305 -92
- data/test/helper.rb +7 -1
- data/test/test_file.rb +9 -2
- data/test/test_markup.rb +107 -23
- data/test/test_page.rb +7 -1
- data/test/test_wiki.rb +131 -48
- metadata +45 -11
@@ -1,8 +1,8 @@
|
|
1
1
|
<div class="write">
|
2
|
-
<a href="/{{
|
2
|
+
<a href="/{{escaped_name}}">« Back</a>
|
3
3
|
<h1>{{title}}</h1>
|
4
4
|
|
5
|
-
<form class="edit_wiki" method="post" action="/edit/{{
|
5
|
+
<form class="edit_wiki" method="post" action="/edit/{{escaped_name}}">
|
6
6
|
<div>
|
7
7
|
<label>
|
8
8
|
Title<br />
|
@@ -1,10 +1,10 @@
|
|
1
1
|
<div class="guide">
|
2
2
|
<div class="main">
|
3
3
|
<div class="actions">
|
4
|
-
<a href="/{{
|
4
|
+
<a href="/{{escaped_name}}">« Back</a>
|
5
5
|
</div>
|
6
6
|
<h1>{{title}}</h1>
|
7
|
-
<form id="history" method="post" action="/compare/{{
|
7
|
+
<form id="history" method="post" action="/compare/{{escaped_name}}">
|
8
8
|
<table class="commits" cellpadding="0" cellspacing="0">
|
9
9
|
<tr>
|
10
10
|
<th colspan="5">
|
@@ -17,7 +17,7 @@
|
|
17
17
|
<input name="versions[]" type="checkbox" value="{{id}}" />
|
18
18
|
</td>
|
19
19
|
<td class="sha">
|
20
|
-
<a href="/{{
|
20
|
+
<a href="/{{escaped_name}}/{{id}}">{{id7}}</a>
|
21
21
|
</td>
|
22
22
|
<td nowrap class="author">
|
23
23
|
<img src="http://www.gravatar.com/avatar/{{gravatar}}?s=16" alt="Gravatar" />
|
@@ -1,7 +1,13 @@
|
|
1
1
|
<div class="guide">
|
2
2
|
<div class="main">
|
3
3
|
<div class="actions">
|
4
|
-
<
|
4
|
+
<form action="/search" method="get">
|
5
|
+
<div>
|
6
|
+
<a href="/">Home</a> |
|
7
|
+
<a href="/edit/{{escaped_name}}">Edit</a> |
|
8
|
+
<input type="search" name="q" size="10" /> <input type="submit" value="search" />
|
9
|
+
</div>
|
10
|
+
</form>
|
5
11
|
</div>
|
6
12
|
<h1>{{title}}</h1>
|
7
13
|
<div class="content wikistyle gollum {{format}}">
|
@@ -18,10 +24,10 @@
|
|
18
24
|
<div style="float: left;">
|
19
25
|
<small>Last edited by <b>{{author}}</b>, {{date}}</small>
|
20
26
|
<div class="actions">
|
21
|
-
<a href="/">Home</a> | <a href="/edit/{{
|
27
|
+
<a href="/">Home</a> | <a href="/edit/{{escaped_name}}">Edit</a>
|
22
28
|
</div>
|
23
29
|
</div>
|
24
30
|
<div style="float: right;">
|
25
|
-
<a href="/history/{{
|
31
|
+
<a href="/history/{{escaped_name}}">View Revision History</a>
|
26
32
|
</div>
|
27
33
|
</div>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<div class="guide">
|
2
|
+
<div class="main">
|
3
|
+
<div class="actions">
|
4
|
+
<form action="/search" method="get">
|
5
|
+
<a href="/">Home</a> | <input type="search" name="q" size="10" /> <input type="submit" value="search" />
|
6
|
+
</form>
|
7
|
+
</div>
|
8
|
+
<h1>Search for “{{query}}”</h1>
|
9
|
+
<div class="content wikistyle gollum">
|
10
|
+
{{#has_results}}
|
11
|
+
<ul>
|
12
|
+
{{#results}}
|
13
|
+
<li><a href="/{{name}}">{{name}} ({{count}})</a></li>
|
14
|
+
{{/results}}
|
15
|
+
</ul>
|
16
|
+
{{/has_results}}
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
</div>
|
data/lib/gollum/markup.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'digest/sha1'
|
2
|
+
require 'cgi'
|
2
3
|
|
3
4
|
module Gollum
|
4
5
|
class Markup
|
@@ -21,8 +22,14 @@ module Gollum
|
|
21
22
|
# Render the content with Gollum wiki syntax on top of the file's own
|
22
23
|
# markup language.
|
23
24
|
#
|
25
|
+
# no_follow - Boolean that determines if rel="nofollow" is added to all
|
26
|
+
# <a> tags.
|
27
|
+
#
|
24
28
|
# Returns the formatted String content.
|
25
|
-
def render
|
29
|
+
def render(no_follow = false)
|
30
|
+
sanitize_options = no_follow ?
|
31
|
+
HISTORY_SANITIZATION_OPTIONS :
|
32
|
+
SANITIZATION_OPTIONS
|
26
33
|
data = extract_tex(@data)
|
27
34
|
data = extract_code(data)
|
28
35
|
data = extract_tags(data)
|
@@ -36,9 +43,9 @@ module Gollum
|
|
36
43
|
end
|
37
44
|
data = process_tags(data)
|
38
45
|
data = process_code(data)
|
39
|
-
data = Sanitize.clean(data,
|
46
|
+
data = Sanitize.clean(data, sanitize_options)
|
40
47
|
data = process_tex(data)
|
41
|
-
data
|
48
|
+
data.gsub!(/<p><\/p>/, '')
|
42
49
|
data
|
43
50
|
end
|
44
51
|
|
@@ -114,28 +121,33 @@ module Gollum
|
|
114
121
|
# Process all tags from the tagmap and replace the placeholders with the
|
115
122
|
# final markup.
|
116
123
|
#
|
117
|
-
# data
|
124
|
+
# data - The String data (with placeholders).
|
125
|
+
# no_follow - Boolean that determines if rel="nofollow" is added to all
|
126
|
+
# <a> tags.
|
118
127
|
#
|
119
128
|
# Returns the marked up String data.
|
120
|
-
def process_tags(data)
|
129
|
+
def process_tags(data, no_follow = false)
|
121
130
|
@tagmap.each do |id, tag|
|
122
|
-
data.gsub!(id, process_tag(tag))
|
131
|
+
data.gsub!(id, process_tag(tag, no_follow))
|
123
132
|
end
|
124
133
|
data
|
125
134
|
end
|
126
135
|
|
127
136
|
# Process a single tag into its final HTML form.
|
128
137
|
#
|
129
|
-
# tag
|
138
|
+
# tag - The String tag contents (the stuff inside the double
|
139
|
+
# brackets).
|
140
|
+
# no_follow - Boolean that determines if rel="nofollow" is added to all
|
141
|
+
# <a> tags.
|
130
142
|
#
|
131
143
|
# Returns the String HTML version of the tag.
|
132
|
-
def process_tag(tag)
|
144
|
+
def process_tag(tag, no_follow = false)
|
133
145
|
if html = process_image_tag(tag)
|
134
|
-
|
135
|
-
elsif html = process_file_link_tag(tag)
|
136
|
-
|
146
|
+
html
|
147
|
+
elsif html = process_file_link_tag(tag, no_follow)
|
148
|
+
html
|
137
149
|
else
|
138
|
-
|
150
|
+
process_page_link_tag(tag, no_follow)
|
139
151
|
end
|
140
152
|
end
|
141
153
|
|
@@ -150,7 +162,7 @@ module Gollum
|
|
150
162
|
name = parts[0].strip
|
151
163
|
path = if file = find_file(name)
|
152
164
|
::File.join @wiki.base_path, file.path
|
153
|
-
elsif name =~ /^https?:\/\/.+(jpg|png|gif|svg|bmp)$/
|
165
|
+
elsif name =~ /^https?:\/\/.+(jpg|png|gif|svg|bmp)$/i
|
154
166
|
name
|
155
167
|
end
|
156
168
|
|
@@ -200,7 +212,7 @@ module Gollum
|
|
200
212
|
classes << 'frame' if opts['frame']
|
201
213
|
%{<span class="#{classes.join(' ')}">} +
|
202
214
|
%{<span>} +
|
203
|
-
%{<img src="
|
215
|
+
%{<img src="#{path}" #{attr_string}/>} +
|
204
216
|
(alt ? %{<span>#{alt}</span>} : '') +
|
205
217
|
%{</span>} +
|
206
218
|
%{</span>}
|
@@ -228,11 +240,14 @@ module Gollum
|
|
228
240
|
|
229
241
|
# Attempt to process the tag as a file link tag.
|
230
242
|
#
|
231
|
-
# tag
|
243
|
+
# tag - The String tag contents (the stuff inside the double
|
244
|
+
# brackets).
|
245
|
+
# no_follow - Boolean that determines if rel="nofollow" is added to all
|
246
|
+
# <a> tags.
|
232
247
|
#
|
233
248
|
# Returns the String HTML if the tag is a valid file link tag or nil
|
234
249
|
# if it is not.
|
235
|
-
def process_file_link_tag(tag)
|
250
|
+
def process_file_link_tag(tag, no_follow = false)
|
236
251
|
parts = tag.split('|')
|
237
252
|
name = parts[0].strip
|
238
253
|
path = parts[1] && parts[1].strip
|
@@ -244,37 +259,49 @@ module Gollum
|
|
244
259
|
nil
|
245
260
|
end
|
246
261
|
|
247
|
-
if name && path && file
|
262
|
+
tag = if name && path && file
|
248
263
|
%{<a href="#{::File.join @wiki.base_path, file.path}">#{name}</a>}
|
249
264
|
elsif name && path
|
250
265
|
%{<a href="#{path}">#{name}</a>}
|
251
266
|
else
|
252
267
|
nil
|
253
268
|
end
|
269
|
+
if tag && no_follow
|
270
|
+
tag.sub! /^<a/, '<a ref="nofollow"'
|
271
|
+
end
|
272
|
+
tag
|
254
273
|
end
|
255
274
|
|
256
275
|
# Attempt to process the tag as a page link tag.
|
257
276
|
#
|
258
|
-
# tag
|
277
|
+
# tag - The String tag contents (the stuff inside the double
|
278
|
+
# brackets).
|
279
|
+
# no_follow - Boolean that determines if rel="nofollow" is added to all
|
280
|
+
# <a> tags.
|
259
281
|
#
|
260
282
|
# Returns the String HTML if the tag is a valid page link tag or nil
|
261
283
|
# if it is not.
|
262
|
-
def process_page_link_tag(tag)
|
284
|
+
def process_page_link_tag(tag, no_follow = false)
|
263
285
|
parts = tag.split('|')
|
264
286
|
name = parts[0].strip
|
265
287
|
cname = Page.cname((parts[1] || parts[0]).strip)
|
266
|
-
if name =~ %r{^https?://} && parts[1].nil?
|
288
|
+
tag = if name =~ %r{^https?://} && parts[1].nil?
|
267
289
|
%{<a href="#{name}">#{name}</a>}
|
268
290
|
else
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
presence
|
291
|
+
presence = "absent"
|
292
|
+
link_name = cname
|
293
|
+
page, extra = find_page_from_name(cname)
|
294
|
+
if page
|
295
|
+
link_name = Page.cname(page.name)
|
296
|
+
presence = "present"
|
275
297
|
end
|
276
|
-
|
298
|
+
link = ::File.join(@wiki.base_path, CGI.escape(link_name))
|
299
|
+
%{<a class="internal #{presence}" href="#{link}#{extra}">#{name}</a>}
|
277
300
|
end
|
301
|
+
if tag && no_follow
|
302
|
+
tag.sub! /^<a/, '<a ref="nofollow"'
|
303
|
+
end
|
304
|
+
tag
|
278
305
|
end
|
279
306
|
|
280
307
|
# Find the given file in the repo.
|
@@ -291,6 +318,23 @@ module Gollum
|
|
291
318
|
end
|
292
319
|
end
|
293
320
|
|
321
|
+
# Find a page from a given cname. If the page has an anchor (#) and has
|
322
|
+
# no match, strip the anchor and try again.
|
323
|
+
#
|
324
|
+
# cname - The String canonical page name.
|
325
|
+
#
|
326
|
+
# Returns a Gollum::Page instance if a page is found, or an Array of
|
327
|
+
# [Gollum::Page, String extra] if a page without the extra anchor data
|
328
|
+
# is found.
|
329
|
+
def find_page_from_name(cname)
|
330
|
+
if page = @wiki.page(cname)
|
331
|
+
return page
|
332
|
+
end
|
333
|
+
if pos = cname.index('#')
|
334
|
+
[@wiki.page(cname[0...pos]), cname[pos..-1]]
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
294
338
|
#########################################################################
|
295
339
|
#
|
296
340
|
# Code
|
@@ -320,7 +364,7 @@ module Gollum
|
|
320
364
|
@codemap.each do |id, spec|
|
321
365
|
lang = spec[:lang]
|
322
366
|
code = spec[:code]
|
323
|
-
if code.all? { |line| line =~ /\A\r?\n\Z/ || line =~ /^( |\t)/ }
|
367
|
+
if code.lines.all? { |line| line =~ /\A\r?\n\Z/ || line =~ /^( |\t)/ }
|
324
368
|
code.gsub!(/^( |\t)/m, '')
|
325
369
|
end
|
326
370
|
data.gsub!(id, Gollum::Albino.new(code, lang).colorize)
|
@@ -328,4 +372,4 @@ module Gollum
|
|
328
372
|
data
|
329
373
|
end
|
330
374
|
end
|
331
|
-
end
|
375
|
+
end
|
data/lib/gollum/page.rb
CHANGED
@@ -14,6 +14,11 @@ module Gollum
|
|
14
14
|
:asciidoc => "AsciiDoc",
|
15
15
|
:pod => "Pod" }
|
16
16
|
|
17
|
+
# Sets a Boolean determing whether this page is a historical version.
|
18
|
+
#
|
19
|
+
# Returns nothing.
|
20
|
+
attr_writer :historical
|
21
|
+
|
17
22
|
# Checks if a filename has a valid extension understood by GitHub::Markup.
|
18
23
|
#
|
19
24
|
# filename - String filename, like "Home.md".
|
@@ -35,6 +40,14 @@ module Gollum
|
|
35
40
|
filename =~ /^_/ ? false : match
|
36
41
|
end
|
37
42
|
|
43
|
+
# Reusable filter to turn a filename (without path) into a canonical name.
|
44
|
+
# Strips extension, converts spaces to dashes.
|
45
|
+
#
|
46
|
+
# Returns the filtered String.
|
47
|
+
def self.canonicalize_filename(filename)
|
48
|
+
filename.split('.')[0..-2].join('.').gsub('-', ' ')
|
49
|
+
end
|
50
|
+
|
38
51
|
# Public: Initialize a page.
|
39
52
|
#
|
40
53
|
# wiki - The Gollum::Wiki in question.
|
@@ -57,7 +70,7 @@ module Gollum
|
|
57
70
|
#
|
58
71
|
# Returns the String name.
|
59
72
|
def name
|
60
|
-
|
73
|
+
self.class.canonicalize_filename(filename)
|
61
74
|
end
|
62
75
|
|
63
76
|
# Public: If the first element of a formatted page is an <h1> tag it can
|
@@ -107,7 +120,7 @@ module Gollum
|
|
107
120
|
#
|
108
121
|
# Returns the String data.
|
109
122
|
def formatted_data
|
110
|
-
@blob && Gollum::Markup.new(self).render
|
123
|
+
@blob && Gollum::Markup.new(self).render(historical?)
|
111
124
|
end
|
112
125
|
|
113
126
|
# Public: The format of the page.
|
@@ -174,20 +187,24 @@ module Gollum
|
|
174
187
|
|
175
188
|
dirs = self.path.split('/')
|
176
189
|
dirs.pop
|
190
|
+
map = @wiki.tree_map_for(self.version.id)
|
177
191
|
while !dirs.empty?
|
178
|
-
|
179
|
-
if page = find_page_in_this_tree(tree, dirs.join('/'), '_Footer')
|
192
|
+
if page = find_page_in_tree(map, '_Footer', dirs.join('/'))
|
180
193
|
return page
|
181
194
|
end
|
182
195
|
dirs.pop
|
183
196
|
end
|
184
197
|
|
185
|
-
|
186
|
-
|
187
|
-
return page
|
188
|
-
end
|
198
|
+
find_page_in_tree(map, '_Footer', '')
|
199
|
+
end
|
189
200
|
|
190
|
-
|
201
|
+
# Gets a Boolean determining whether this page is a historical version.
|
202
|
+
# Historical pages are pulled using exact SHA hashes and format all links
|
203
|
+
# with rel="nofollow"
|
204
|
+
#
|
205
|
+
# Returns true if the page is pulled from a named branch or tag, or false.
|
206
|
+
def historical?
|
207
|
+
!!@historical
|
191
208
|
end
|
192
209
|
|
193
210
|
#########################################################################
|
@@ -207,7 +224,9 @@ module Gollum
|
|
207
224
|
#
|
208
225
|
# Returns the String canonical name.
|
209
226
|
def self.cname(name)
|
210
|
-
name.gsub
|
227
|
+
name.respond_to?(:gsub) ?
|
228
|
+
name.gsub(%r{[ /<>]}, '-') :
|
229
|
+
''
|
211
230
|
end
|
212
231
|
|
213
232
|
# Convert a format Symbol into an extension String.
|
@@ -251,61 +270,35 @@ module Gollum
|
|
251
270
|
#
|
252
271
|
# Returns a Gollum::Page or nil if the page could not be found.
|
253
272
|
def find(name, version)
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
273
|
+
map = @wiki.tree_map_for(version)
|
274
|
+
if page = find_page_in_tree(map, name)
|
275
|
+
sha = @wiki.ref_map[version] || version
|
276
|
+
page.version = Grit::Commit.create(@wiki.repo, :id => sha)
|
277
|
+
page.historical = sha == version
|
278
|
+
page
|
259
279
|
end
|
280
|
+
rescue Grit::GitRuby::Repository::NoSuchShaFound
|
260
281
|
end
|
261
282
|
|
262
283
|
# Find a page in a given tree.
|
263
284
|
#
|
264
|
-
#
|
265
|
-
# name
|
285
|
+
# map - The Array tree map from Wiki#tree_map.
|
286
|
+
# name - The canonical String page name.
|
287
|
+
# checked_dir - Optional String of the directory a matching page needs
|
288
|
+
# to be in. The string should
|
266
289
|
#
|
267
290
|
# Returns a Gollum::Page or nil if the page could not be found.
|
268
|
-
def find_page_in_tree(
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
while !trees.empty?
|
273
|
-
ptree = trees.shift
|
274
|
-
ptree.contents.each do |item|
|
275
|
-
case item
|
276
|
-
when Grit::Blob
|
277
|
-
if page_match(name, item.name)
|
278
|
-
return self.class.new(@wiki).populate(item, tree_path(treemap, ptree))
|
279
|
-
end
|
280
|
-
when Grit::Tree
|
281
|
-
treemap[item] = ptree
|
282
|
-
trees << item
|
283
|
-
end
|
284
|
-
end
|
291
|
+
def find_page_in_tree(map, name, checked_dir = nil)
|
292
|
+
return nil if name.to_s.empty?
|
293
|
+
if checked_dir = BlobEntry.normalize_dir(checked_dir)
|
294
|
+
checked_dir.downcase!
|
285
295
|
end
|
286
296
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
# tree - The Grit::Tree in which to look.
|
293
|
-
# dir - The String path of the given Grit::Tree.
|
294
|
-
# name - The canonical String page name.
|
295
|
-
#
|
296
|
-
# Returns a Gollum::Page or nil if the page could not be found.
|
297
|
-
def find_page_in_this_tree(tree, dir, name)
|
298
|
-
treemap = {}
|
299
|
-
tree.contents.each do |item|
|
300
|
-
case item
|
301
|
-
when Grit::Blob
|
302
|
-
if page_match(name, item.name)
|
303
|
-
path = dir == '' ? '' : ::File.join('/', dir)
|
304
|
-
page = self.class.new(@wiki).populate(item, path)
|
305
|
-
page.version = self.version
|
306
|
-
return page
|
307
|
-
end
|
308
|
-
end
|
297
|
+
map.each do |entry|
|
298
|
+
next if entry.name.to_s.empty?
|
299
|
+
next unless checked_dir.nil? || entry.dir.downcase == checked_dir
|
300
|
+
next unless page_match(name, entry.name)
|
301
|
+
return entry.page(@wiki, @version)
|
309
302
|
end
|
310
303
|
|
311
304
|
return nil # nothing was found
|