gitlab-gollum-lib 1.1.0 → 4.2.7

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