gollum-bibanon 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. data/Gemfile +4 -0
  2. data/HISTORY.md +93 -0
  3. data/Home.md +3 -0
  4. data/LICENSE +21 -0
  5. data/README.md +483 -0
  6. data/Rakefile +138 -0
  7. data/bin/gollum +126 -0
  8. data/docs/sanitization.md +32 -0
  9. data/gollum.gemspec +212 -0
  10. data/lib/gollum.rb +40 -0
  11. data/lib/gollum/blob_entry.rb +78 -0
  12. data/lib/gollum/committer.rb +218 -0
  13. data/lib/gollum/file.rb +64 -0
  14. data/lib/gollum/frontend/app.rb +234 -0
  15. data/lib/gollum/frontend/public/css/dialog.css +141 -0
  16. data/lib/gollum/frontend/public/css/editor.css +537 -0
  17. data/lib/gollum/frontend/public/css/gollum.css +660 -0
  18. data/lib/gollum/frontend/public/css/ie7.css +69 -0
  19. data/lib/gollum/frontend/public/css/template.css +381 -0
  20. data/lib/gollum/frontend/public/images/icon-sprite.png +0 -0
  21. data/lib/gollum/frontend/public/javascript/editor/gollum.editor.js +1073 -0
  22. data/lib/gollum/frontend/public/javascript/editor/langs/asciidoc.js +167 -0
  23. data/lib/gollum/frontend/public/javascript/editor/langs/creole.js +104 -0
  24. data/lib/gollum/frontend/public/javascript/editor/langs/markdown.js +211 -0
  25. data/lib/gollum/frontend/public/javascript/editor/langs/org.js +173 -0
  26. data/lib/gollum/frontend/public/javascript/editor/langs/pod.js +111 -0
  27. data/lib/gollum/frontend/public/javascript/editor/langs/rdoc.js +74 -0
  28. data/lib/gollum/frontend/public/javascript/editor/langs/textile.js +175 -0
  29. data/lib/gollum/frontend/public/javascript/gollum.dialog.js +263 -0
  30. data/lib/gollum/frontend/public/javascript/gollum.js +161 -0
  31. data/lib/gollum/frontend/public/javascript/gollum.placeholder.js +54 -0
  32. data/lib/gollum/frontend/public/javascript/jquery.color.js +123 -0
  33. data/lib/gollum/frontend/public/javascript/jquery.js +7179 -0
  34. data/lib/gollum/frontend/templates/compare.mustache +53 -0
  35. data/lib/gollum/frontend/templates/create.mustache +15 -0
  36. data/lib/gollum/frontend/templates/edit.mustache +17 -0
  37. data/lib/gollum/frontend/templates/editor.mustache +116 -0
  38. data/lib/gollum/frontend/templates/error.mustache +8 -0
  39. data/lib/gollum/frontend/templates/history.mustache +60 -0
  40. data/lib/gollum/frontend/templates/layout.mustache +28 -0
  41. data/lib/gollum/frontend/templates/page.mustache +45 -0
  42. data/lib/gollum/frontend/templates/pages.mustache +35 -0
  43. data/lib/gollum/frontend/templates/search.mustache +36 -0
  44. data/lib/gollum/frontend/templates/searchbar.mustache +10 -0
  45. data/lib/gollum/frontend/views/compare.rb +94 -0
  46. data/lib/gollum/frontend/views/create.rb +44 -0
  47. data/lib/gollum/frontend/views/edit.rb +52 -0
  48. data/lib/gollum/frontend/views/editable.rb +13 -0
  49. data/lib/gollum/frontend/views/error.rb +7 -0
  50. data/lib/gollum/frontend/views/history.rb +44 -0
  51. data/lib/gollum/frontend/views/layout.rb +20 -0
  52. data/lib/gollum/frontend/views/page.rb +57 -0
  53. data/lib/gollum/frontend/views/pages.rb +19 -0
  54. data/lib/gollum/frontend/views/search.rb +20 -0
  55. data/lib/gollum/git_access.rb +248 -0
  56. data/lib/gollum/markup.rb +432 -0
  57. data/lib/gollum/page.rb +428 -0
  58. data/lib/gollum/pagination.rb +61 -0
  59. data/lib/gollum/sanitization.rb +161 -0
  60. data/lib/gollum/wiki.rb +636 -0
  61. data/templates/formatting.html +92 -0
  62. data/test/examples/empty.git/HEAD +1 -0
  63. data/test/examples/empty.git/config +5 -0
  64. data/test/examples/empty.git/description +1 -0
  65. data/test/examples/empty.git/hooks/applypatch-msg.sample +15 -0
  66. data/test/examples/empty.git/hooks/commit-msg.sample +24 -0
  67. data/test/examples/empty.git/hooks/post-commit.sample +8 -0
  68. data/test/examples/empty.git/hooks/post-receive.sample +15 -0
  69. data/test/examples/empty.git/hooks/post-update.sample +8 -0
  70. data/test/examples/empty.git/hooks/pre-applypatch.sample +14 -0
  71. data/test/examples/empty.git/hooks/pre-commit.sample +46 -0
  72. data/test/examples/empty.git/hooks/pre-rebase.sample +169 -0
  73. data/test/examples/empty.git/hooks/prepare-commit-msg.sample +36 -0
  74. data/test/examples/empty.git/hooks/update.sample +128 -0
  75. data/test/examples/empty.git/info/exclude +6 -0
  76. data/test/examples/lotr.git/COMMIT_EDITMSG +1 -0
  77. data/test/examples/lotr.git/HEAD +1 -0
  78. data/test/examples/lotr.git/config +12 -0
  79. data/test/examples/lotr.git/description +1 -0
  80. data/test/examples/lotr.git/index +0 -0
  81. data/test/examples/lotr.git/info/exclude +6 -0
  82. data/test/examples/lotr.git/logs/HEAD +3 -0
  83. data/test/examples/lotr.git/logs/refs/heads/master +3 -0
  84. data/test/examples/lotr.git/objects/06/131480411710c92a82fe2d1e76932c70feb2e5 +0 -0
  85. data/test/examples/lotr.git/objects/0a/de1e2916346d4c1f2fb63b863fd3c16808fe44 +0 -0
  86. data/test/examples/lotr.git/objects/0e/d8cbe0a25235bd867e65193c7d837c66b328ef +3 -0
  87. data/test/examples/lotr.git/objects/24/49c2681badfd3c189e8ed658dacffe8ba48fe5 +0 -0
  88. data/test/examples/lotr.git/objects/2c/b9156ad383914561a8502fc70f5a1d887e48ad +4 -0
  89. data/test/examples/lotr.git/objects/5d/cac289a8603188d2c5caf481dcba2985126aaa +0 -0
  90. data/test/examples/lotr.git/objects/60/f12f4254f58801b9ee7db7bca5fa8aeefaa56b +0 -0
  91. data/test/examples/lotr.git/objects/71/4323c104239440a5c66ab12a67ed07a83c404f +0 -0
  92. data/test/examples/lotr.git/objects/84/0ec5b1ba1320e8ec443f28f99566f615d5af10 +0 -0
  93. data/test/examples/lotr.git/objects/93/6b83ee0dd8837adb82511e40d5e4ebe59bb675 +0 -0
  94. data/test/examples/lotr.git/objects/94/523d7ae48aeba575099dd12926420d8fd0425d +2 -0
  95. data/test/examples/lotr.git/objects/96/97dc65e095658bbd1b8e8678e08881e86d32f1 +0 -0
  96. data/test/examples/lotr.git/objects/a3/1ca2a7c352c92531a8b99815d15843b259e814 +0 -0
  97. data/test/examples/lotr.git/objects/a8/ad3c09dd842a3517085bfadd37718856dee813 +0 -0
  98. data/test/examples/lotr.git/objects/aa/b61fe89d56f8614c0a8151da34f939dcedfa68 +0 -0
  99. data/test/examples/lotr.git/objects/c3/b43e9f08966b088e7a0192e436b7a884542e05 +0 -0
  100. data/test/examples/lotr.git/objects/dc/596d6b2dd89ab05c66f4abd7d5eb706bc17f19 +0 -0
  101. data/test/examples/lotr.git/objects/ec/da3205bee14520aab5a7bb307392064b938e83 +0 -0
  102. data/test/examples/lotr.git/objects/fa/e7ef5344202bba4129abdc13060d9297d99465 +3 -0
  103. data/test/examples/lotr.git/objects/info/packs +2 -0
  104. data/test/examples/lotr.git/objects/pack/pack-dcbeaf3f6ff6c5eb08ea2b0a2d83626e8763546b.idx +0 -0
  105. data/test/examples/lotr.git/objects/pack/pack-dcbeaf3f6ff6c5eb08ea2b0a2d83626e8763546b.pack +0 -0
  106. data/test/examples/lotr.git/packed-refs +2 -0
  107. data/test/examples/lotr.git/refs/heads/master +1 -0
  108. data/test/examples/lotr.git/refs/remotes/origin/HEAD +1 -0
  109. data/test/examples/page_file_dir.git/COMMIT_EDITMSG +1 -0
  110. data/test/examples/page_file_dir.git/HEAD +1 -0
  111. data/test/examples/page_file_dir.git/config +6 -0
  112. data/test/examples/page_file_dir.git/description +1 -0
  113. data/test/examples/page_file_dir.git/index +0 -0
  114. data/test/examples/page_file_dir.git/info/exclude +6 -0
  115. data/test/examples/page_file_dir.git/logs/HEAD +1 -0
  116. data/test/examples/page_file_dir.git/logs/refs/heads/master +1 -0
  117. data/test/examples/page_file_dir.git/objects/0c/7d27db1f575263efdcab3dc650f4502a2dbcbf +0 -0
  118. data/test/examples/page_file_dir.git/objects/22/b404803c966dd92865614d86ff22ca12e50c1e +0 -0
  119. data/test/examples/page_file_dir.git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 +0 -0
  120. data/test/examples/page_file_dir.git/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 +0 -0
  121. data/test/examples/page_file_dir.git/objects/5b/43e14e0a15fb6f08feab1773d1c0991e9f71e2 +0 -0
  122. data/test/examples/page_file_dir.git/refs/heads/master +1 -0
  123. data/test/examples/revert.git/COMMIT_EDITMSG +1 -0
  124. data/test/examples/revert.git/HEAD +1 -0
  125. data/test/examples/revert.git/config +12 -0
  126. data/test/examples/revert.git/description +1 -0
  127. data/test/examples/revert.git/index +0 -0
  128. data/test/examples/revert.git/info/exclude +6 -0
  129. data/test/examples/revert.git/logs/HEAD +2 -0
  130. data/test/examples/revert.git/logs/refs/heads/master +2 -0
  131. data/test/examples/revert.git/objects/20/2ced67cea93c7b6bd2928aa1daef8d1d55a20d +0 -0
  132. data/test/examples/revert.git/objects/41/76394bfa11222363c66ce7e84b5f154095b6d9 +0 -0
  133. data/test/examples/revert.git/objects/6a/69f92020f5df77af6e8813ff1232493383b708 +0 -0
  134. data/test/examples/revert.git/objects/b4/785957bc986dc39c629de9fac9df46972c00fc +0 -0
  135. data/test/examples/revert.git/objects/f4/03b791119f8232b7cb0ba455c624ac6435f433 +0 -0
  136. data/test/examples/revert.git/objects/info/packs +2 -0
  137. data/test/examples/revert.git/objects/pack/pack-a561f8437234f74d0bacb9e0eebe52d207f5770d.idx +0 -0
  138. data/test/examples/revert.git/objects/pack/pack-a561f8437234f74d0bacb9e0eebe52d207f5770d.pack +0 -0
  139. data/test/examples/revert.git/packed-refs +2 -0
  140. data/test/examples/revert.git/refs/heads/master +1 -0
  141. data/test/examples/revert.git/refs/remotes/origin/HEAD +1 -0
  142. data/test/examples/yubiwa.git/HEAD +1 -0
  143. data/test/examples/yubiwa.git/config +5 -0
  144. data/test/examples/yubiwa.git/description +1 -0
  145. data/test/examples/yubiwa.git/info/exclude +6 -0
  146. data/test/examples/yubiwa.git/objects/10/fa2ddc4e3b4009d8a453aace10bd6148c1ad00 +0 -0
  147. data/test/examples/yubiwa.git/objects/52/4b82874327ea7cbf730389964ba7cb3de966de +0 -0
  148. data/test/examples/yubiwa.git/objects/58/3fc201cb457fb3f1480f3e1e5999b119633835 +0 -0
  149. data/test/examples/yubiwa.git/objects/87/bc1dd46ab3d3874d4e898d45dd512cc20a7cc8 +1 -0
  150. data/test/examples/yubiwa.git/objects/89/64ed1b4e21aa90e831763bbce9034bfda81b70 +0 -0
  151. data/test/examples/yubiwa.git/objects/9f/f6dd0660da5fba2d3374adb2b84fa653bb538b +0 -0
  152. data/test/examples/yubiwa.git/objects/ac/e97abf2b177815a1972d7db22f229f58c83309 +0 -0
  153. data/test/examples/yubiwa.git/objects/b1/f443863a4816628807fbf86141ebef055dda34 +0 -0
  154. data/test/examples/yubiwa.git/refs/heads/master +1 -0
  155. data/test/helper.rb +66 -0
  156. data/test/test_app.rb +179 -0
  157. data/test/test_committer.rb +64 -0
  158. data/test/test_file.rb +27 -0
  159. data/test/test_git_access.rb +52 -0
  160. data/test/test_markup.rb +580 -0
  161. data/test/test_page.rb +166 -0
  162. data/test/test_page_revert.rb +45 -0
  163. data/test/test_wiki.rb +462 -0
  164. metadata +493 -0
@@ -0,0 +1,432 @@
1
+ require 'digest/sha1'
2
+ require 'cgi'
3
+ require 'pygments'
4
+ require 'base64'
5
+
6
+ module Gollum
7
+
8
+ class Markup
9
+ # Initialize a new Markup object.
10
+ #
11
+ # page - The Gollum::Page.
12
+ #
13
+ # Returns a new Gollum::Markup object, ready for rendering.
14
+ def initialize(page)
15
+ @wiki = page.wiki
16
+ @name = page.filename
17
+ @data = page.text_data
18
+ @version = page.version.id if page.version
19
+ @format = page.format
20
+ @dir = ::File.dirname(page.path)
21
+ @tagmap = {}
22
+ @codemap = {}
23
+ @texmap = {}
24
+ @premap = {}
25
+ end
26
+
27
+ # Render the content with Gollum wiki syntax on top of the file's own
28
+ # markup language.
29
+ #
30
+ # no_follow - Boolean that determines if rel="nofollow" is added to all
31
+ # <a> tags.
32
+ #
33
+ # Returns the formatted String content.
34
+ def render(no_follow = false)
35
+ sanitize = no_follow ?
36
+ @wiki.history_sanitizer :
37
+ @wiki.sanitizer
38
+
39
+ data = extract_tex(@data.dup)
40
+ data = extract_code(data)
41
+ data = extract_tags(data)
42
+ begin
43
+ data = GitHub::Markup.render(@name, data)
44
+ if data.nil?
45
+ raise "There was an error converting #{@name} to HTML."
46
+ end
47
+ rescue Object => e
48
+ data = %{<p class="gollum-error">#{e.message}</p>}
49
+ end
50
+ data = process_tags(data)
51
+ data = process_code(data)
52
+ if sanitize || block_given?
53
+ doc = Nokogiri::HTML::DocumentFragment.parse(data)
54
+ doc = sanitize.clean_node!(doc) if sanitize
55
+ yield doc if block_given?
56
+ data = doc.to_html
57
+ end
58
+ data = process_tex(data)
59
+ data.gsub!(/<p><\/p>/, '')
60
+ data
61
+ end
62
+
63
+ #########################################################################
64
+ #
65
+ # TeX
66
+ #
67
+ #########################################################################
68
+
69
+ # Extract all TeX into the texmap and replace with placeholders.
70
+ #
71
+ # data - The raw String data.
72
+ #
73
+ # Returns the placeholder'd String data.
74
+ def extract_tex(data)
75
+ data.gsub(/\\\[\s*(.*?)\s*\\\]/m) do
76
+ tag = CGI.escapeHTML($1)
77
+ id = Digest::SHA1.hexdigest(tag)
78
+ @texmap[id] = [:block, tag]
79
+ id
80
+ end.gsub(/\\\(\s*(.*?)\s*\\\)/m) do
81
+ tag = CGI.escapeHTML($1)
82
+ id = Digest::SHA1.hexdigest(tag)
83
+ @texmap[id] = [:inline, tag]
84
+ id
85
+ end
86
+ end
87
+
88
+ # Process all TeX from the texmap and replace the placeholders with the
89
+ # final markup.
90
+ #
91
+ # data - The String data (with placeholders).
92
+ #
93
+ # Returns the marked up String data.
94
+ def process_tex(data)
95
+ @texmap.each do |id, spec|
96
+ type, tex = *spec
97
+ out = %{<img src="#{::File.join(@wiki.base_path, '_tex.png')}?type=#{type}&data=#{Base64.encode64(tex).chomp}" alt="#{CGI.escapeHTML(tex)}">}
98
+ data.gsub!(id, out)
99
+ end
100
+ data
101
+ end
102
+
103
+ #########################################################################
104
+ #
105
+ # Tags
106
+ #
107
+ #########################################################################
108
+
109
+ # Extract all tags into the tagmap and replace with placeholders.
110
+ #
111
+ # data - The raw String data.
112
+ #
113
+ # Returns the placeholder'd String data.
114
+ def extract_tags(data)
115
+ data.gsub!(/(.?)\[\[(.+?)\]\]([^\[]?)/m) do
116
+ if $1 == "'" && $3 != "'"
117
+ "[[#{$2}]]#{$3}"
118
+ elsif $2.include?('][')
119
+ if $2[0..4] == 'file:'
120
+ pre = $1
121
+ post = $3
122
+ parts = $2.split('][')
123
+ parts[0][0..4] = ""
124
+ link = "#{parts[1]}|#{parts[0].sub(/\.org/,'')}"
125
+ id = Digest::SHA1.hexdigest(link)
126
+ @tagmap[id] = link
127
+ "#{pre}#{id}#{post}"
128
+ else
129
+ $&
130
+ end
131
+ else
132
+ id = Digest::SHA1.hexdigest($2)
133
+ @tagmap[id] = $2
134
+ "#{$1}#{id}#{$3}"
135
+ end
136
+ end
137
+ data
138
+ end
139
+
140
+ # Process all tags from the tagmap and replace the placeholders with the
141
+ # final markup.
142
+ #
143
+ # data - The String data (with placeholders).
144
+ #
145
+ # Returns the marked up String data.
146
+ def process_tags(data)
147
+ @tagmap.each do |id, tag|
148
+ data.gsub!(id, process_tag(tag))
149
+ end
150
+ data
151
+ end
152
+
153
+ # Process a single tag into its final HTML form.
154
+ #
155
+ # tag - The String tag contents (the stuff inside the double
156
+ # brackets).
157
+ #
158
+ # Returns the String HTML version of the tag.
159
+ def process_tag(tag)
160
+ if html = process_image_tag(tag)
161
+ html
162
+ elsif html = process_file_link_tag(tag)
163
+ html
164
+ else
165
+ process_page_link_tag(tag)
166
+ end
167
+ end
168
+
169
+ # Attempt to process the tag as an image tag.
170
+ #
171
+ # tag - The String tag contents (the stuff inside the double brackets).
172
+ #
173
+ # Returns the String HTML if the tag is a valid image tag or nil
174
+ # if it is not.
175
+ def process_image_tag(tag)
176
+ parts = tag.split('|')
177
+ return if parts.size.zero?
178
+
179
+ name = parts[0].strip
180
+ path = if file = find_file(name)
181
+ ::File.join @wiki.base_path, file.path
182
+ elsif name =~ /^https?:\/\/.+(jpg|png|gif|svg|bmp)$/i
183
+ name
184
+ end
185
+
186
+ if path
187
+ opts = parse_image_tag_options(tag)
188
+
189
+ containered = false
190
+
191
+ classes = [] # applied to whatever the outermost container is
192
+ attrs = [] # applied to the image
193
+
194
+ align = opts['align']
195
+ if opts['float']
196
+ containered = true
197
+ align ||= 'left'
198
+ if %w{left right}.include?(align)
199
+ classes << "float-#{align}"
200
+ end
201
+ elsif %w{top texttop middle absmiddle bottom absbottom baseline}.include?(align)
202
+ attrs << %{align="#{align}"}
203
+ elsif align
204
+ if %w{left center right}.include?(align)
205
+ containered = true
206
+ classes << "align-#{align}"
207
+ end
208
+ end
209
+
210
+ if width = opts['width']
211
+ if width =~ /^\d+(\.\d+)?(em|px)$/
212
+ attrs << %{width="#{width}"}
213
+ end
214
+ end
215
+
216
+ if height = opts['height']
217
+ if height =~ /^\d+(\.\d+)?(em|px)$/
218
+ attrs << %{height="#{height}"}
219
+ end
220
+ end
221
+
222
+ if alt = opts['alt']
223
+ attrs << %{alt="#{alt}"}
224
+ end
225
+
226
+ attr_string = attrs.size > 0 ? attrs.join(' ') + ' ' : ''
227
+
228
+ if opts['frame'] || containered
229
+ classes << 'frame' if opts['frame']
230
+ %{<span class="#{classes.join(' ')}">} +
231
+ %{<span>} +
232
+ %{<img src="#{path}" #{attr_string}/>} +
233
+ (alt ? %{<span>#{alt}</span>} : '') +
234
+ %{</span>} +
235
+ %{</span>}
236
+ else
237
+ %{<img src="#{path}" #{attr_string}/>}
238
+ end
239
+ end
240
+ end
241
+
242
+ # Parse any options present on the image tag and extract them into a
243
+ # Hash of option names and values.
244
+ #
245
+ # tag - The String tag contents (the stuff inside the double brackets).
246
+ #
247
+ # Returns the options Hash:
248
+ # key - The String option name.
249
+ # val - The String option value or true if it is a binary option.
250
+ def parse_image_tag_options(tag)
251
+ tag.split('|')[1..-1].inject({}) do |memo, attr|
252
+ parts = attr.split('=').map { |x| x.strip }
253
+ memo[parts[0]] = (parts.size == 1 ? true : parts[1])
254
+ memo
255
+ end
256
+ end
257
+
258
+ # Attempt to process the tag as a file link tag.
259
+ #
260
+ # tag - The String tag contents (the stuff inside the double
261
+ # brackets).
262
+ #
263
+ # Returns the String HTML if the tag is a valid file link tag or nil
264
+ # if it is not.
265
+ def process_file_link_tag(tag)
266
+ parts = tag.split('|')
267
+ return if parts.size.zero?
268
+
269
+ name = parts[0].strip
270
+ path = parts[1] && parts[1].strip
271
+ path = if path && file = find_file(path)
272
+ ::File.join @wiki.base_path, file.path
273
+ elsif path =~ %r{^https?://}
274
+ path
275
+ else
276
+ nil
277
+ end
278
+
279
+ if name && path && file
280
+ %{<a href="#{::File.join @wiki.base_path, file.path}">#{name}</a>}
281
+ elsif name && path
282
+ %{<a href="#{path}">#{name}</a>}
283
+ else
284
+ nil
285
+ end
286
+ end
287
+
288
+ # Attempt to process the tag as a page link tag.
289
+ #
290
+ # tag - The String tag contents (the stuff inside the double
291
+ # brackets).
292
+ #
293
+ # Returns the String HTML if the tag is a valid page link tag or nil
294
+ # if it is not.
295
+ def process_page_link_tag(tag)
296
+ parts = tag.split('|')
297
+ parts.reverse! if @format == :mediawiki
298
+
299
+ name, page_name = *parts.compact.map(&:strip)
300
+ cname = @wiki.page_class.cname(page_name || name)
301
+
302
+ if name =~ %r{^https?://} && page_name.nil?
303
+ %{<a href="#{name}">#{name}</a>}
304
+ else
305
+ presence = "absent"
306
+ link_name = cname
307
+ page, extra = find_page_from_name(cname)
308
+ if page
309
+ link_name = @wiki.page_class.cname(page.name)
310
+ presence = "present"
311
+ end
312
+ link = ::File.join(@wiki.base_path, CGI.escape(link_name))
313
+ %{<a class="internal #{presence}" href="#{link}#{extra}">#{name}</a>}
314
+ end
315
+ end
316
+
317
+ # Find the given file in the repo.
318
+ #
319
+ # name - The String absolute or relative path of the file.
320
+ #
321
+ # Returns the Gollum::File or nil if none was found.
322
+ def find_file(name)
323
+ if name =~ /^\//
324
+ @wiki.file(name[1..-1], @version)
325
+ else
326
+ path = @dir == '.' ? name : ::File.join(@dir, name)
327
+ @wiki.file(path, @version)
328
+ end
329
+ end
330
+
331
+ # Find a page from a given cname. If the page has an anchor (#) and has
332
+ # no match, strip the anchor and try again.
333
+ #
334
+ # cname - The String canonical page name.
335
+ #
336
+ # Returns a Gollum::Page instance if a page is found, or an Array of
337
+ # [Gollum::Page, String extra] if a page without the extra anchor data
338
+ # is found.
339
+ def find_page_from_name(cname)
340
+ if page = @wiki.page(cname)
341
+ return page
342
+ end
343
+ if pos = cname.index('#')
344
+ [@wiki.page(cname[0...pos]), cname[pos..-1]]
345
+ end
346
+ end
347
+
348
+ #########################################################################
349
+ #
350
+ # Code
351
+ #
352
+ #########################################################################
353
+
354
+ # Extract all code blocks into the codemap and replace with placeholders.
355
+ #
356
+ # data - The raw String data.
357
+ #
358
+ # Returns the placeholder'd String data.
359
+ def extract_code(data)
360
+ data.gsub!(/^``` ?([^\r\n]+)?\r?\n(.+?)\r?\n```\r?$/m) do
361
+ id = Digest::SHA1.hexdigest("#{$1}.#{$2}")
362
+ cached = check_cache(:code, id)
363
+ @codemap[id] = cached ?
364
+ { :output => cached } :
365
+ { :lang => $1, :code => $2 }
366
+ id
367
+ end
368
+ data
369
+ end
370
+
371
+ # Process all code from the codemap and replace the placeholders with the
372
+ # final HTML.
373
+ #
374
+ # data - The String data (with placeholders).
375
+ #
376
+ # Returns the marked up String data.
377
+ def process_code(data)
378
+ return data if data.nil? || data.size.zero? || @codemap.size.zero?
379
+
380
+ blocks = []
381
+ @codemap.each do |id, spec|
382
+ next if spec[:output] # cached
383
+
384
+ code = spec[:code]
385
+ if code.lines.all? { |line| line =~ /\A\r?\n\Z/ || line =~ /^( |\t)/ }
386
+ code.gsub!(/^( |\t)/m, '')
387
+ end
388
+
389
+ blocks << [spec[:lang], code]
390
+ end
391
+
392
+ highlighted = begin
393
+ blocks.map { |lang, code| Pygments.highlight(code, :lexer => lang) }
394
+ rescue ::RubyPython::PythonError
395
+ []
396
+ end
397
+
398
+ @codemap.each do |id, spec|
399
+ body = spec[:output] || begin
400
+ if (body = highlighted.shift.to_s).size > 0
401
+ update_cache(:code, id, body)
402
+ body
403
+ else
404
+ "<pre><code>#{CGI.escapeHTML(spec[:code])}</code></pre>"
405
+ end
406
+ end
407
+ data.gsub!(id, body)
408
+ end
409
+
410
+ data
411
+ end
412
+
413
+ # Hook for getting the formatted value of extracted tag data.
414
+ #
415
+ # type - Symbol value identifying what type of data is being extracted.
416
+ # id - String SHA1 hash of original extracted tag data.
417
+ #
418
+ # Returns the String cached formatted data, or nil.
419
+ def check_cache(type, id)
420
+ end
421
+
422
+ # Hook for caching the formatted value of extracted tag data.
423
+ #
424
+ # type - Symbol value identifying what type of data is being extracted.
425
+ # id - String SHA1 hash of original extracted tag data.
426
+ # data - The String formatted value to be cached.
427
+ #
428
+ # Returns nothing.
429
+ def update_cache(type, id, data)
430
+ end
431
+ end
432
+ end
@@ -0,0 +1,428 @@
1
+ module Gollum
2
+ class Page
3
+ include Pagination
4
+
5
+ Wiki.page_class = self
6
+
7
+ VALID_PAGE_RE = /^(.+)\.(md|mkdn?|mdown|markdown|textile|rdoc|org|creole|re?st(\.txt)?|asciidoc|pod|(media)?wiki)$/i
8
+ FORMAT_NAMES = { :markdown => "Markdown",
9
+ :textile => "Textile",
10
+ :rdoc => "RDoc",
11
+ :org => "Org-mode",
12
+ :creole => "Creole",
13
+ :rest => "reStructuredText",
14
+ :asciidoc => "AsciiDoc",
15
+ :mediawiki => "MediaWiki",
16
+ :pod => "Pod" }
17
+
18
+ # Sets a Boolean determing whether this page is a historical version.
19
+ #
20
+ # Returns nothing.
21
+ attr_writer :historical
22
+
23
+ # Checks if a filename has a valid extension understood by GitHub::Markup.
24
+ #
25
+ # filename - String filename, like "Home.md".
26
+ #
27
+ # Returns the matching String basename of the file without the extension.
28
+ def self.valid_filename?(filename)
29
+ filename && filename.to_s =~ VALID_PAGE_RE && $1
30
+ end
31
+
32
+ # Checks if a filename has a valid extension understood by GitHub::Markup.
33
+ # Also, checks if the filename has no "_" in the front (such as
34
+ # _Footer.md).
35
+ #
36
+ # filename - String filename, like "Home.md".
37
+ #
38
+ # Returns the matching String basename of the file without the extension.
39
+ def self.valid_page_name?(filename)
40
+ match = valid_filename?(filename)
41
+ filename =~ /^_/ ? false : match
42
+ end
43
+
44
+ # Public: The format of a given filename.
45
+ #
46
+ # filename - The String filename.
47
+ #
48
+ # Returns the Symbol format of the page. One of:
49
+ # [ :markdown | :textile | :rdoc | :org | :rest | :asciidoc | :pod |
50
+ # :roff ]
51
+ def self.format_for(filename)
52
+ case filename.to_s
53
+ when /\.(md|mkdn?|mdown|markdown)$/i
54
+ :markdown
55
+ when /\.(textile)$/i
56
+ :textile
57
+ when /\.(rdoc)$/i
58
+ :rdoc
59
+ when /\.(org)$/i
60
+ :org
61
+ when /\.(creole)$/i
62
+ :creole
63
+ when /\.(re?st(\.txt)?)$/i
64
+ :rest
65
+ when /\.(asciidoc)$/i
66
+ :asciidoc
67
+ when /\.(pod)$/i
68
+ :pod
69
+ when /\.(\d)$/i
70
+ :roff
71
+ when /\.(media)?wiki$/i
72
+ :mediawiki
73
+ else
74
+ nil
75
+ end
76
+ end
77
+
78
+ # Reusable filter to turn a filename (without path) into a canonical name.
79
+ # Strips extension, converts dashes to spaces.
80
+ #
81
+ # Returns the filtered String.
82
+ def self.canonicalize_filename(filename)
83
+ strip_filename(filename).gsub('-', ' ')
84
+ end
85
+
86
+ # Reusable filter to strip extension and path from filename
87
+ #
88
+ # filename - The string path or filename to strip
89
+ #
90
+ # Returns the stripped String.
91
+ def self.strip_filename(filename)
92
+ ::File.basename(filename, ::File.extname(filename))
93
+ end
94
+
95
+ # Public: Initialize a page.
96
+ #
97
+ # wiki - The Gollum::Wiki in question.
98
+ #
99
+ # Returns a newly initialized Gollum::Page.
100
+ def initialize(wiki)
101
+ @wiki = wiki
102
+ @blob = @footer = @sidebar = nil
103
+ end
104
+
105
+ # Public: The on-disk filename of the page including extension.
106
+ #
107
+ # Returns the String name.
108
+ def filename
109
+ @blob && @blob.name
110
+ end
111
+
112
+ # Public: The on-disk filename of the page with extension stripped.
113
+ #
114
+ # Returns the String name.
115
+ def filename_stripped
116
+ self.class.strip_filename(filename)
117
+ end
118
+
119
+ # Public: The canonical page name without extension, and dashes converted
120
+ # to spaces.
121
+ #
122
+ # Returns the String name.
123
+ def name
124
+ self.class.canonicalize_filename(filename)
125
+ end
126
+
127
+ # Public: If the first element of a formatted page is an <h1> tag it can
128
+ # be considered the title of the page and used in the display. If the
129
+ # first element is NOT an <h1> tag, the title will be constructed from the
130
+ # filename by stripping the extension and replacing any dashes with
131
+ # spaces.
132
+ #
133
+ # Returns the fully sanitized String title.
134
+ def title
135
+ doc = Nokogiri::HTML(%{<div id="gollum-root">} + self.formatted_data + %{</div>})
136
+
137
+ header =
138
+ case self.format
139
+ when :asciidoc
140
+ doc.css("div#gollum-root > div#header > h1:first-child")
141
+ when :org
142
+ doc.css("div#gollum-root > p.title:first-child")
143
+ when :pod
144
+ doc.css("div#gollum-root > a.dummyTopAnchor:first-child + h1")
145
+ when :rest
146
+ doc.css("div#gollum-root > div > div > h1:first-child")
147
+ else
148
+ doc.css("div#gollum-root > h1:first-child")
149
+ end
150
+
151
+ if !header.empty?
152
+ Sanitize.clean(header.to_html)
153
+ else
154
+ Sanitize.clean(name)
155
+ end.strip
156
+ end
157
+
158
+ # Public: The path of the page within the repo.
159
+ #
160
+ # Returns the String path.
161
+ attr_reader :path
162
+
163
+ # Public: The raw contents of the page.
164
+ #
165
+ # Returns the String data.
166
+ def raw_data
167
+ @blob && @blob.data
168
+ end
169
+
170
+ # Public: A text data encoded in specified encoding.
171
+ #
172
+ # encoding - An Encoding or nil
173
+ #
174
+ # Returns a character encoding aware String.
175
+ def text_data(encoding=nil)
176
+ if raw_data.respond_to?(:encoding)
177
+ raw_data.force_encoding(encoding || Encoding::UTF_8)
178
+ else
179
+ raw_data
180
+ end
181
+ end
182
+
183
+ # Public: The formatted contents of the page.
184
+ #
185
+ # Returns the String data.
186
+ def formatted_data(&block)
187
+ @blob && markup_class.render(historical?, &block)
188
+ end
189
+
190
+ # Public: The format of the page.
191
+ #
192
+ # Returns the Symbol format of the page. One of:
193
+ # [ :markdown | :textile | :rdoc | :org | :rest | :asciidoc | :pod |
194
+ # :roff ]
195
+ def format
196
+ self.class.format_for(@blob.name)
197
+ end
198
+
199
+ # Gets the Gollum::Markup instance that will render this page's content.
200
+ #
201
+ # Returns a Gollum::Markup instance.
202
+ def markup_class
203
+ @markup_class ||= @wiki.markup_classes[format].new(self)
204
+ end
205
+
206
+ # Public: The current version of the page.
207
+ #
208
+ # Returns the Grit::Commit.
209
+ attr_reader :version
210
+
211
+ # Public: All of the versions that have touched the Page.
212
+ #
213
+ # options - The options Hash:
214
+ # :page - The Integer page number (default: 1).
215
+ # :per_page - The Integer max count of items to return.
216
+ # :follow - Follow's a file across renames, but falls back
217
+ # to a slower Grit native call. (default: false)
218
+ #
219
+ # Returns an Array of Grit::Commit.
220
+ def versions(options = {})
221
+ if options[:follow]
222
+ options[:pretty] = 'raw'
223
+ options.delete :max_count
224
+ options.delete :skip
225
+ log = @wiki.repo.git.native "log", options, @wiki.ref, "--", @path
226
+ Grit::Commit.list_from_string(@wiki.repo, log)
227
+ else
228
+ @wiki.repo.log(@wiki.ref, @path, log_pagination_options(options))
229
+ end
230
+ end
231
+
232
+ # Public: The footer Page.
233
+ #
234
+ # Returns the footer Page or nil if none exists.
235
+ def footer
236
+ @footer ||= find_sub_page(:footer)
237
+ end
238
+
239
+ # Public: The sidebar Page.
240
+ #
241
+ # Returns the sidebar Page or nil if none exists.
242
+ def sidebar
243
+ @sidebar ||= find_sub_page(:sidebar)
244
+ end
245
+
246
+ # Gets a Boolean determining whether this page is a historical version.
247
+ # Historical pages are pulled using exact SHA hashes and format all links
248
+ # with rel="nofollow"
249
+ #
250
+ # Returns true if the page is pulled from a named branch or tag, or false.
251
+ def historical?
252
+ !!@historical
253
+ end
254
+
255
+ #########################################################################
256
+ #
257
+ # Class Methods
258
+ #
259
+ #########################################################################
260
+
261
+ # Convert a human page name into a canonical page name.
262
+ #
263
+ # name - The String human page name.
264
+ # char_white_sub - Substitution for whitespace
265
+ # char_other_sub - Substitution for other special chars
266
+ #
267
+ # Examples
268
+ #
269
+ # Page.cname("Bilbo Baggins")
270
+ # # => 'Bilbo-Baggins'
271
+ #
272
+ # Page.cname("Bilbo Baggins",'_')
273
+ # # => 'Bilbo_Baggins'
274
+ #
275
+ # Returns the String canonical name.
276
+ def self.cname(name, char_white_sub = '-', char_other_sub = '-')
277
+ name.respond_to?(:gsub) ?
278
+ name.gsub(%r{\s},char_white_sub).gsub(%r{[/<>+]}, char_other_sub) :
279
+ ''
280
+ end
281
+
282
+ # Convert a format Symbol into an extension String.
283
+ #
284
+ # format - The format Symbol.
285
+ #
286
+ # Returns the String extension (no leading period).
287
+ def self.format_to_ext(format)
288
+ case format
289
+ when :markdown then 'md'
290
+ when :textile then 'textile'
291
+ when :rdoc then 'rdoc'
292
+ when :org then 'org'
293
+ when :creole then 'creole'
294
+ when :rest then 'rest'
295
+ when :asciidoc then 'asciidoc'
296
+ when :pod then 'pod'
297
+ when :mediawiki then 'mediawiki'
298
+ end
299
+ end
300
+
301
+ #########################################################################
302
+ #
303
+ # Internal Methods
304
+ #
305
+ #########################################################################
306
+
307
+ # The underlying wiki repo.
308
+ #
309
+ # Returns the Gollum::Wiki containing the page.
310
+ attr_reader :wiki
311
+
312
+ # Set the Grit::Commit version of the page.
313
+ #
314
+ # Returns nothing.
315
+ attr_writer :version
316
+
317
+ # Find a page in the given Gollum repo.
318
+ #
319
+ # name - The human or canonical String page name to find.
320
+ # version - The String version ID to find.
321
+ #
322
+ # Returns a Gollum::Page or nil if the page could not be found.
323
+ def find(name, version)
324
+ map = @wiki.tree_map_for(version.to_s)
325
+ if page = find_page_in_tree(map, name)
326
+ page.version = version.is_a?(Grit::Commit) ?
327
+ version : @wiki.commit_for(version)
328
+ page.historical = page.version.to_s == version.to_s
329
+ page
330
+ end
331
+ rescue Grit::GitRuby::Repository::NoSuchShaFound
332
+ end
333
+
334
+ # Find a page in a given tree.
335
+ #
336
+ # map - The Array tree map from Wiki#tree_map.
337
+ # name - The canonical String page name.
338
+ # checked_dir - Optional String of the directory a matching page needs
339
+ # to be in. The string should
340
+ #
341
+ # Returns a Gollum::Page or nil if the page could not be found.
342
+ def find_page_in_tree(map, name, checked_dir = nil)
343
+ return nil if !map || name.to_s.empty?
344
+ if checked_dir = BlobEntry.normalize_dir(checked_dir)
345
+ checked_dir.downcase!
346
+ end
347
+
348
+ map.each do |entry|
349
+ next if entry.name.to_s.empty?
350
+ next unless checked_dir.nil? || entry.dir.downcase == checked_dir
351
+ next unless page_match(name, entry.name)
352
+ return entry.page(@wiki, @version)
353
+ end
354
+
355
+ return nil # nothing was found
356
+ end
357
+
358
+ # Populate the Page with information from the Blob.
359
+ #
360
+ # blob - The Grit::Blob that contains the info.
361
+ # path - The String directory path of the page file.
362
+ #
363
+ # Returns the populated Gollum::Page.
364
+ def populate(blob, path=nil)
365
+ @blob = blob
366
+ @path = "#{path}/#{blob.name}"[1..-1]
367
+ self
368
+ end
369
+
370
+ # The full directory path for the given tree.
371
+ #
372
+ # treemap - The Hash treemap containing parentage information.
373
+ # tree - The Grit::Tree for which to compute the path.
374
+ #
375
+ # Returns the String path.
376
+ def tree_path(treemap, tree)
377
+ if ptree = treemap[tree]
378
+ tree_path(treemap, ptree) + '/' + tree.name
379
+ else
380
+ ''
381
+ end
382
+ end
383
+
384
+ # Compare the canonicalized versions of the two names.
385
+ #
386
+ # name - The human or canonical String page name.
387
+ # filename - the String filename on disk (including extension).
388
+ #
389
+ # Returns a Boolean.
390
+ def page_match(name, filename)
391
+ if match = self.class.valid_filename?(filename)
392
+ @wiki.ws_subs.each do |sub|
393
+ return true if Page.cname(name).downcase == Page.cname(match, sub).downcase
394
+ end
395
+ end
396
+ false
397
+ end
398
+
399
+ # Loads a sub page. Sub page nanes (footers) are prefixed with
400
+ # an underscore to distinguish them from other Pages.
401
+ #
402
+ # name - String page name.
403
+ #
404
+ # Returns the Page or nil if none exists.
405
+ def find_sub_page(name)
406
+ return nil unless self.version
407
+ return nil if self.filename =~ /^_/
408
+ name = "_#{name.to_s.capitalize}"
409
+ return nil if page_match(name, self.filename)
410
+
411
+ dirs = self.path.split('/')
412
+ dirs.pop
413
+ map = @wiki.tree_map_for(self.version.id)
414
+ while !dirs.empty?
415
+ if page = find_page_in_tree(map, name, dirs.join('/'))
416
+ return page
417
+ end
418
+ dirs.pop
419
+ end
420
+
421
+ find_page_in_tree(map, name, '')
422
+ end
423
+
424
+ def inspect
425
+ %(#<#{self.class.name}:#{object_id} #{name} (#{format}) @wiki=#{@wiki.repo.path.inspect}>)
426
+ end
427
+ end
428
+ end