gollum-lib 0.0.1

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.

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