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.
- checksums.yaml +4 -4
- data/Gemfile +1 -3
- data/HISTORY.md +25 -0
- data/LICENSE +1 -1
- data/README.md +24 -312
- data/Rakefile +32 -16
- data/gemspec.rb +110 -0
- data/gollum-lib.gemspec +8 -75
- data/gollum-lib_java.gemspec +4 -0
- data/lib/gollum-lib.rb +18 -6
- data/lib/gollum-lib/blob_entry.rb +10 -9
- data/lib/gollum-lib/committer.rb +37 -30
- data/lib/gollum-lib/file.rb +71 -15
- data/lib/gollum-lib/file_view.rb +53 -48
- data/lib/gollum-lib/filter.rb +78 -0
- data/lib/gollum-lib/filter/code.rb +145 -0
- data/lib/gollum-lib/filter/emoji.rb +39 -0
- data/lib/gollum-lib/filter/macro.rb +57 -0
- data/lib/gollum-lib/filter/metadata.rb +29 -0
- data/lib/gollum-lib/filter/plain_text.rb +16 -0
- data/lib/gollum-lib/filter/plantuml.rb +176 -0
- data/lib/gollum-lib/filter/remote_code.rb +63 -0
- data/lib/gollum-lib/filter/render.rb +20 -0
- data/lib/gollum-lib/filter/sanitize.rb +18 -0
- data/lib/gollum-lib/filter/tags.rb +327 -0
- data/lib/gollum-lib/filter/toc.rb +134 -0
- data/lib/gollum-lib/filter/wsd.rb +54 -0
- data/lib/gollum-lib/git_access.rb +30 -32
- data/lib/gollum-lib/gitcode.rb +16 -16
- data/lib/gollum-lib/helpers.rb +3 -3
- data/lib/gollum-lib/hook.rb +35 -0
- data/lib/gollum-lib/macro.rb +43 -0
- data/lib/gollum-lib/macro/all_pages.rb +11 -0
- data/lib/gollum-lib/macro/global_toc.rb +12 -0
- data/lib/gollum-lib/macro/navigation.rb +20 -0
- data/lib/gollum-lib/macro/series.rb +48 -0
- data/lib/gollum-lib/markup.rb +95 -572
- data/lib/gollum-lib/markups.rb +9 -3
- data/lib/gollum-lib/page.rb +109 -80
- data/lib/gollum-lib/pagination.rb +1 -1
- data/lib/gollum-lib/sanitization.rb +75 -75
- data/lib/gollum-lib/version.rb +5 -0
- data/lib/gollum-lib/wiki.rb +287 -129
- metadata +237 -92
- data/CHANGELOG +0 -2
- data/VERSION +0 -1
- data/lib/gollum-lib/grit_ext.rb +0 -20
- data/lib/gollum-lib/remote_code.rb +0 -39
- 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,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
|
data/lib/gollum-lib/markup.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
|
192
|
-
|
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
|
-
|
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
|
111
|
+
# Process the filter chain
|
227
112
|
#
|
228
|
-
#
|
229
|
-
#
|
113
|
+
# data - the data to send through the chain
|
114
|
+
# filter_chain - the chain to process
|
230
115
|
#
|
231
|
-
# Returns the
|
232
|
-
def
|
233
|
-
|
234
|
-
|
235
|
-
|
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
|
-
|
247
|
-
|
248
|
-
|
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
|
-
|
264
|
-
|
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
|
-
|
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
|
-
#
|
136
|
+
# Render the content with Gollum wiki syntax on top of the file's own
|
137
|
+
# markup language.
|
336
138
|
#
|
337
|
-
#
|
338
|
-
#
|
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
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
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
|
-
|
347
|
-
|
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
|
-
|
357
|
-
|
358
|
-
|
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
|
-
|
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.
|