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
data/lib/gollum-lib/file.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# ~*~ encoding: utf-8 ~*~
|
2
|
+
|
2
3
|
module Gollum
|
3
4
|
class File
|
4
5
|
Wiki.file_class = self
|
@@ -9,9 +10,11 @@ module Gollum
|
|
9
10
|
#
|
10
11
|
# Returns a newly initialized Gollum::File.
|
11
12
|
def initialize(wiki)
|
12
|
-
@wiki
|
13
|
-
@blob
|
14
|
-
@path
|
13
|
+
@wiki = wiki
|
14
|
+
@blob = nil
|
15
|
+
@path = nil
|
16
|
+
@on_disk = false
|
17
|
+
@on_disk_path = nil
|
15
18
|
end
|
16
19
|
|
17
20
|
# Public: The url path required to reach this page within the repo.
|
@@ -27,21 +30,24 @@ module Gollum
|
|
27
30
|
#
|
28
31
|
# Returns the String url_path
|
29
32
|
def escaped_url_path
|
30
|
-
CGI.escape(self.url_path).gsub('%2F','/')
|
33
|
+
CGI.escape(self.url_path).gsub('%2F', '/')
|
31
34
|
end
|
32
35
|
|
33
36
|
# Public: The on-disk filename of the file.
|
34
37
|
#
|
35
38
|
# Returns the String name.
|
36
39
|
def name
|
40
|
+
return @path if on_disk?
|
37
41
|
@blob && @blob.name
|
38
42
|
end
|
43
|
+
|
39
44
|
alias filename name
|
40
45
|
|
41
46
|
# Public: The raw contents of the page.
|
42
47
|
#
|
43
48
|
# Returns the String data.
|
44
49
|
def raw_data
|
50
|
+
return IO.read(@on_disk_path) if on_disk?
|
45
51
|
return nil unless @blob
|
46
52
|
|
47
53
|
if !@wiki.repo.bare && @blob.is_symlink
|
@@ -52,7 +58,21 @@ module Gollum
|
|
52
58
|
@blob.data
|
53
59
|
end
|
54
60
|
|
55
|
-
# Public:
|
61
|
+
# Public: Is this an on-disk file reference?
|
62
|
+
#
|
63
|
+
# Returns true if this is a pointer to an on-disk file
|
64
|
+
def on_disk?
|
65
|
+
@on_disk
|
66
|
+
end
|
67
|
+
|
68
|
+
# Public: The path to this file on disk
|
69
|
+
#
|
70
|
+
# Returns nil if on_disk? is false.
|
71
|
+
def on_disk_path
|
72
|
+
@on_disk_path
|
73
|
+
end
|
74
|
+
|
75
|
+
# Public: The Gollum::Git::Commit version of the file.
|
56
76
|
attr_accessor :version
|
57
77
|
|
58
78
|
# Public: The String path of the file.
|
@@ -60,18 +80,20 @@ module Gollum
|
|
60
80
|
|
61
81
|
# Public: The String mime type of the file.
|
62
82
|
def mime_type
|
63
|
-
@blob.mime_type
|
83
|
+
@blob && @blob.mime_type
|
64
84
|
end
|
65
85
|
|
66
86
|
# Populate the File with information from the Blob.
|
67
87
|
#
|
68
|
-
# blob - The
|
88
|
+
# blob - The Gollum::Git::Blob that contains the info.
|
69
89
|
# path - The String directory path of the file.
|
70
90
|
#
|
71
91
|
# Returns the populated Gollum::File.
|
72
|
-
def populate(blob, path=nil)
|
73
|
-
@blob
|
74
|
-
@path
|
92
|
+
def populate(blob, path = nil)
|
93
|
+
@blob = blob
|
94
|
+
@path = "#{path}/#{blob.name}"[1..-1]
|
95
|
+
@on_disk = false
|
96
|
+
@on_disk_path = nil
|
75
97
|
self
|
76
98
|
end
|
77
99
|
|
@@ -81,19 +103,53 @@ module Gollum
|
|
81
103
|
#
|
82
104
|
#########################################################################
|
83
105
|
|
106
|
+
# Return the file path to this file on disk, if available.
|
107
|
+
#
|
108
|
+
# Returns nil if the file isn't available on disk. This can occur if the
|
109
|
+
# repo is bare, if the commit isn't the HEAD, or if there are problems
|
110
|
+
# resolving symbolic links.
|
111
|
+
def get_disk_reference(name, commit)
|
112
|
+
return false if @wiki.repo.bare
|
113
|
+
return false if commit.sha != @wiki.repo.head.commit.sha
|
114
|
+
|
115
|
+
# This will try to resolve symbolic links, as well
|
116
|
+
pathname = Pathname.new(::File.expand_path(::File.join(@wiki.repo.path, '..', name)))
|
117
|
+
if pathname.symlink?
|
118
|
+
source = ::File.readlink(pathname.to_path)
|
119
|
+
realpath = ::File.join(::File.dirname(pathname.to_path), source)
|
120
|
+
return false unless realpath && ::File.exist?(realpath)
|
121
|
+
@on_disk_path = realpath.to_s
|
122
|
+
else
|
123
|
+
@on_disk_path = pathname.to_path
|
124
|
+
end
|
125
|
+
true
|
126
|
+
end
|
127
|
+
|
84
128
|
# Find a file in the given Gollum repo.
|
85
129
|
#
|
86
130
|
# name - The full String path.
|
87
131
|
# version - The String version ID to find.
|
132
|
+
# try_on_disk - If true, try to return just a reference to a file
|
133
|
+
# that exists on the disk.
|
88
134
|
#
|
89
|
-
# Returns a Gollum::File or nil if the file could not be found.
|
90
|
-
|
135
|
+
# Returns a Gollum::File or nil if the file could not be found. Note
|
136
|
+
# that if you specify try_on_disk=true, you may or may not get a file
|
137
|
+
# for which on_disk? is actually true.
|
138
|
+
def find(name, version, try_on_disk = false)
|
91
139
|
checked = name.downcase
|
92
140
|
map = @wiki.tree_map_for(version)
|
93
|
-
|
141
|
+
commit = version.is_a?(Gollum::Git::Commit) ? version : @wiki.commit_for(version)
|
142
|
+
|
143
|
+
if (result = map.detect { |entry| entry.path.downcase == checked })
|
94
144
|
@path = name
|
95
|
-
@
|
96
|
-
|
145
|
+
@version = commit
|
146
|
+
|
147
|
+
if try_on_disk && get_disk_reference(name, commit)
|
148
|
+
@on_disk = true
|
149
|
+
else
|
150
|
+
@blob = result.blob(@wiki.repo)
|
151
|
+
end
|
152
|
+
|
97
153
|
self
|
98
154
|
end
|
99
155
|
end
|
data/lib/gollum-lib/file_view.rb
CHANGED
@@ -1,35 +1,38 @@
|
|
1
1
|
# ~*~ encoding: utf-8 ~*~
|
2
2
|
module Gollum
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
=end
|
3
|
+
# FileView requires that:
|
4
|
+
# - All files in root dir are processed first
|
5
|
+
# - Then all the folders are sorted and processed
|
6
|
+
|
8
7
|
class FileView
|
9
8
|
# common use cases:
|
10
9
|
# set pages to wiki.pages and show_all to false
|
11
10
|
# set pages to wiki.pages + wiki.files and show_all to true
|
12
|
-
def initialize
|
13
|
-
@pages
|
11
|
+
def initialize(pages, options = {})
|
12
|
+
@pages = pages
|
14
13
|
@show_all = options[:show_all] || false
|
15
|
-
@checked
|
14
|
+
@checked = options[:collapse_tree] ? '' : "checked"
|
16
15
|
end
|
17
16
|
|
18
|
-
def enclose_tree
|
17
|
+
def enclose_tree(string)
|
19
18
|
%Q(<ol class="tree">\n) + string + %Q(</ol>)
|
20
19
|
end
|
21
20
|
|
22
|
-
def new_page
|
21
|
+
def new_page(page)
|
23
22
|
name = page.name
|
24
|
-
url = url_for_page page
|
25
|
-
%Q( <li class="file"><a href="#{url}"><span class="icon"></span>#{name}</a
|
23
|
+
url, valid_page = url_for_page page
|
24
|
+
%Q( <li class="file"><a href="#{url}"><span class="icon"></span>#{name}</a>#{valid_page ? "" : delete_file(url, valid_page)}</li>)
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete_file(url, valid_page)
|
28
|
+
%Q(<form method="POST" action="/deleteFile/#{url}" onsubmit="return confirm('Do you really want to delete the file #{url}?');"><button type="submit" name="delete" value="true"></button></form>)
|
26
29
|
end
|
27
30
|
|
28
|
-
def new_folder
|
31
|
+
def new_folder(folder_path)
|
29
32
|
new_sub_folder folder_path
|
30
33
|
end
|
31
34
|
|
32
|
-
def new_sub_folder
|
35
|
+
def new_sub_folder(path)
|
33
36
|
<<-HTML
|
34
37
|
<li>
|
35
38
|
<label>#{path}</label> <input type="checkbox" #{@checked} />
|
@@ -41,29 +44,31 @@ module Gollum
|
|
41
44
|
"</ol></li>\n"
|
42
45
|
end
|
43
46
|
|
44
|
-
def url_for_page
|
47
|
+
def url_for_page(page)
|
45
48
|
url = ''
|
49
|
+
valid_page_name = false
|
46
50
|
if @show_all
|
47
51
|
# Remove ext for valid pages.
|
48
52
|
filename = page.filename
|
49
|
-
|
53
|
+
valid_page_name = Page::valid_page_name?(filename)
|
54
|
+
filename = valid_page_name ? filename.chomp(::File.extname(filename)) : filename
|
50
55
|
|
51
56
|
url = ::File.join(::File.dirname(page.path), filename)
|
52
57
|
else
|
53
58
|
url = ::File.join(::File.dirname(page.path), page.filename_stripped)
|
54
59
|
end
|
55
|
-
url = url[2..-1] if url[0,2] == './'
|
56
|
-
url
|
60
|
+
url = url[2..-1] if url[0, 2] == './'
|
61
|
+
return url, valid_page_name
|
57
62
|
end
|
58
63
|
|
59
64
|
def render_files
|
60
|
-
html
|
61
|
-
count
|
65
|
+
html = ''
|
66
|
+
count = @pages.size
|
62
67
|
folder_start = -1
|
63
68
|
|
64
69
|
# Process all pages until folders start
|
65
|
-
count.times do |
|
66
|
-
page = @pages[
|
70
|
+
count.times do |index|
|
71
|
+
page = @pages[index]
|
67
72
|
path = page.path
|
68
73
|
|
69
74
|
unless path.include? '/'
|
@@ -81,7 +86,7 @@ module Gollum
|
|
81
86
|
|
82
87
|
# Handle special case of only one folder.
|
83
88
|
if (count - folder_start == 1)
|
84
|
-
page = @pages[
|
89
|
+
page = @pages[folder_start]
|
85
90
|
html += <<-HTML
|
86
91
|
<li>
|
87
92
|
<label>#{::File.dirname(page.path)}</label> <input type="checkbox" #{@checked} />
|
@@ -95,14 +100,14 @@ module Gollum
|
|
95
100
|
end
|
96
101
|
|
97
102
|
sorted_folders = []
|
98
|
-
(folder_start).upto count - 1 do |
|
99
|
-
sorted_folders += [[
|
103
|
+
(folder_start).upto count - 1 do |index|
|
104
|
+
sorted_folders += [[@pages[index].path, index]]
|
100
105
|
end
|
101
106
|
|
102
107
|
# http://stackoverflow.com/questions/3482814/sorting-list-of-string-paths-in-vb-net
|
103
|
-
sorted_folders.sort! do |first,second|
|
104
|
-
a
|
105
|
-
b
|
108
|
+
sorted_folders.sort! do |first, second|
|
109
|
+
a = first[0]
|
110
|
+
b = second[0]
|
106
111
|
|
107
112
|
# use :: operator because gollum defines its own conflicting File class
|
108
113
|
dir_compare = ::File.dirname(a) <=> ::File.dirname(b)
|
@@ -118,34 +123,34 @@ module Gollum
|
|
118
123
|
|
119
124
|
# keep track of folder depth, 0 = at root.
|
120
125
|
cwd_array = []
|
121
|
-
changed
|
126
|
+
changed = false
|
122
127
|
|
123
128
|
# process rest of folders
|
124
|
-
(0...sorted_folders.size).each do |
|
125
|
-
|
126
|
-
|
127
|
-
|
129
|
+
(0...sorted_folders.size).each do |i|
|
130
|
+
page = @pages[sorted_folders[i][1]]
|
131
|
+
path = page.path
|
132
|
+
folder = ::File.dirname path
|
128
133
|
|
129
|
-
|
134
|
+
tmp_array = folder.split '/'
|
130
135
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
+
(0...tmp_array.size).each do |index|
|
137
|
+
if cwd_array[index].nil? || changed
|
138
|
+
html += new_sub_folder tmp_array[index]
|
139
|
+
next
|
140
|
+
end
|
136
141
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
end
|
142
|
-
html += new_sub_folder tmp_array[ index ]
|
142
|
+
if cwd_array[index] != tmp_array[index]
|
143
|
+
changed = true
|
144
|
+
(cwd_array.size - index).times do
|
145
|
+
html += end_folder
|
143
146
|
end
|
147
|
+
html += new_sub_folder tmp_array[index]
|
144
148
|
end
|
149
|
+
end
|
145
150
|
|
146
|
-
|
147
|
-
|
148
|
-
|
151
|
+
html += new_page page
|
152
|
+
cwd_array = tmp_array
|
153
|
+
changed = false
|
149
154
|
end
|
150
155
|
|
151
156
|
# return the completed html
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# A "filter", in Gollum-speak, is an object which extracts tokens from an
|
2
|
+
# input stream of an arbitrary markup language, then replaces them with a
|
3
|
+
# final form in a rendered form of the same document. Filters are composed
|
4
|
+
# into a "filter chain", which forms the order in which filters are applied
|
5
|
+
# in both the extraction and processing phases (processing happens in the
|
6
|
+
# reverse order to extraction). A single instance of a filter class is used
|
7
|
+
# for both the extraction and processing, so you can store internal state
|
8
|
+
# from the extraction phase for use in the processing phase.
|
9
|
+
#
|
10
|
+
# Any class which is to be used as a filter must have an `initialize` method
|
11
|
+
# which takes a single mandatory argument (the instance of the `Markup`
|
12
|
+
# class we're being called from), and must implement two methods: `extract`
|
13
|
+
# and `process`, both of which must take a string as input and return a
|
14
|
+
# (possibly modified) form of that string as output.
|
15
|
+
#
|
16
|
+
# An example rendering session: consider a filter chain with three filters
|
17
|
+
# in it, :A, :B, and :C (filter chains are specified as symbols, which are
|
18
|
+
# taken to be class names within the Gollum::Filter namespace). An
|
19
|
+
# equivalent of the following will take place:
|
20
|
+
#
|
21
|
+
# a = Gollum::Filter.const_get(:A).new
|
22
|
+
# b = Gollum::Filter.const_get(:B).new
|
23
|
+
# c = Gollum::Filter.const_get(:C).new
|
24
|
+
#
|
25
|
+
# data = "<some markup>"
|
26
|
+
#
|
27
|
+
# data = a.extract(data)
|
28
|
+
# data = b.extract(data)
|
29
|
+
# data = c.extract(data)
|
30
|
+
#
|
31
|
+
# data = c.process(data)
|
32
|
+
# data = b.process(data)
|
33
|
+
# data = a.process(data)
|
34
|
+
#
|
35
|
+
# # `data` now contains the rendered document, ready for processing
|
36
|
+
#
|
37
|
+
# Note how the extraction steps go in the order of the filter chain, while
|
38
|
+
# the processing steps go in the reverse order. There hasn't (yet) been a
|
39
|
+
# case where that is a huge problem.
|
40
|
+
#
|
41
|
+
# If your particular filter doesn't need to do something with either the
|
42
|
+
# original markup or rendered forms, you can simply define the relevant
|
43
|
+
# method to be a pass-through (`def extract(d) d; end`), but you *must*
|
44
|
+
# define both methods yourself.
|
45
|
+
#
|
46
|
+
module Gollum
|
47
|
+
class Filter
|
48
|
+
include Gollum::Helpers
|
49
|
+
|
50
|
+
# Setup the object. Sets `@markup` to be the instance of Gollum::Markup that
|
51
|
+
# is running this filter chain, and sets `@map` to be an empty hash (for use
|
52
|
+
# in your extract/process operations).
|
53
|
+
def initialize(markup)
|
54
|
+
@markup = markup
|
55
|
+
@map = {}
|
56
|
+
end
|
57
|
+
|
58
|
+
def extract(_d)
|
59
|
+
raise RuntimeError,
|
60
|
+
"#{self.class} has not implemented ##extract!"
|
61
|
+
end
|
62
|
+
|
63
|
+
def process(_d)
|
64
|
+
raise RuntimeError,
|
65
|
+
"#{self.class} has not implemented ##process!"
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
# Render a (presumably) non-fatal error as HTML
|
70
|
+
#
|
71
|
+
def html_error(message)
|
72
|
+
"<p class=\"gollum-error\">#{message}</p>"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Load all standard filters
|
78
|
+
Dir[File.expand_path('../filter/*.rb', __FILE__)].each { |f| require f }
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
|
3
|
+
# Code
|
4
|
+
#
|
5
|
+
# Render a block of code using the Rouge syntax-highlighter.
|
6
|
+
class Gollum::Filter::Code < Gollum::Filter
|
7
|
+
def extract(data)
|
8
|
+
case @markup.format
|
9
|
+
when :txt
|
10
|
+
return data
|
11
|
+
when :asciidoc
|
12
|
+
data.gsub!(/^(\[source,([^\r\n]*)\]\n)?----\n(.+?)\n----$/m) do
|
13
|
+
cache_codeblock(Regexp.last_match[2], Regexp.last_match[3])
|
14
|
+
end
|
15
|
+
when :org
|
16
|
+
org_headers = %r{([ \t]*#\+HEADER[S]?:[^\r\n]*\n)*}
|
17
|
+
org_name = %r{([ \t]*#\+NAME:[^\r\n]*\n)?}
|
18
|
+
org_lang = %r{[ ]*([^\n \r]*)[ ]*[^\r\n]*}
|
19
|
+
org_begin = %r{[ \t]*#\+BEGIN_SRC#{org_lang}\n}
|
20
|
+
org_end = %r{\n[ \t]*#\+END_SRC[ \t]*}
|
21
|
+
data.gsub!(/^#{org_headers}#{org_name}#{org_begin}(.+?)#{org_end}$/mi) do
|
22
|
+
cache_codeblock(Regexp.last_match[3], Regexp.last_match[4])
|
23
|
+
end
|
24
|
+
when :markdown
|
25
|
+
data.gsub!(/^([ \t]*)(~~~+) ?([^\r\n]+)?\r?\n(.+?)\r?\n\1(~~~+)[ \t\r]*$/m) do
|
26
|
+
m_indent = Regexp.last_match[1]
|
27
|
+
m_start = Regexp.last_match[2] # ~~~
|
28
|
+
m_lang = Regexp.last_match[3]
|
29
|
+
m_code = Regexp.last_match[4]
|
30
|
+
m_end = Regexp.last_match[5] # ~~~
|
31
|
+
# start and finish tilde fence must be the same length
|
32
|
+
next '' if m_start.length != m_end.length
|
33
|
+
lang = m_lang ? m_lang.strip : nil
|
34
|
+
if lang
|
35
|
+
lang = lang.match(/\.([^}\s]+)/)
|
36
|
+
lang = lang[1] unless lang.nil?
|
37
|
+
end
|
38
|
+
"#{m_indent}#{cache_codeblock(lang, m_code, m_indent)}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
data.gsub!(/^([ \t]*)``` ?([^\r\n]+)?\r?\n(.+?)\r?\n\1```[ \t]*\r?$/m) do
|
44
|
+
"#{Regexp.last_match[1]}#{cache_codeblock(Regexp.last_match[2].to_s.strip, Regexp.last_match[3], Regexp.last_match[1])}" # print the SHA1 ID with the proper indentation
|
45
|
+
end
|
46
|
+
data
|
47
|
+
end
|
48
|
+
|
49
|
+
# Process all code from the codemap and replace the placeholders with the
|
50
|
+
# final HTML.
|
51
|
+
#
|
52
|
+
# data - The String data (with placeholders).
|
53
|
+
# encoding - Encoding Constant or String.
|
54
|
+
#
|
55
|
+
# Returns the marked up String data.
|
56
|
+
def process(data)
|
57
|
+
return data if data.nil? || data.size.zero? || @map.size.zero?
|
58
|
+
|
59
|
+
blocks = []
|
60
|
+
|
61
|
+
@map.each do |_id, spec|
|
62
|
+
next if spec[:output] # cached
|
63
|
+
|
64
|
+
code = spec[:code]
|
65
|
+
|
66
|
+
remove_leading_space(code, /^#{spec[:indent]}/m)
|
67
|
+
remove_leading_space(code, /^( |\t)/m)
|
68
|
+
|
69
|
+
blocks << [spec[:lang], code]
|
70
|
+
end
|
71
|
+
|
72
|
+
highlighted = []
|
73
|
+
blocks.each do |lang, code|
|
74
|
+
encoding = @markup.encoding || 'utf-8'
|
75
|
+
|
76
|
+
if defined? Pygments
|
77
|
+
# Set the default lexer to 'text' to prevent #153 and #154
|
78
|
+
lang = lang || 'text'
|
79
|
+
lexer = Pygments::Lexer[(lang)] || Pygments::Lexer['text']
|
80
|
+
|
81
|
+
# must set startinline to true for php to be highlighted without <?
|
82
|
+
hl_code = lexer.highlight(code, :options => { :encoding => encoding.to_s, :startinline => true })
|
83
|
+
else # Rouge
|
84
|
+
begin
|
85
|
+
# if `lang` was not defined then assume plaintext
|
86
|
+
lexer = Rouge::Lexer.find_fancy(lang || 'plaintext')
|
87
|
+
formatter = Rouge::Formatters::HTML.new
|
88
|
+
wrap_template = '<pre class="highlight"><code>%s</code></pre>'
|
89
|
+
|
90
|
+
# if `lang` is defined but cannot be found then wrap it with an error
|
91
|
+
if lexer.nil?
|
92
|
+
lexer = Rouge::Lexers::PlainText
|
93
|
+
wrap_template = '<pre class="highlight"><span class="err">%s</span></pre>'
|
94
|
+
end
|
95
|
+
|
96
|
+
formatted = formatter.format(lexer.lex(code))
|
97
|
+
|
98
|
+
hl_code = Kernel.sprintf(wrap_template, formatted)
|
99
|
+
rescue
|
100
|
+
hl_code = code
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
highlighted << hl_code
|
105
|
+
end
|
106
|
+
|
107
|
+
@map.each do |id, spec|
|
108
|
+
body = spec[:output] || begin
|
109
|
+
if (body = highlighted.shift.to_s).size > 0
|
110
|
+
@markup.update_cache(:code, id, body)
|
111
|
+
body
|
112
|
+
else
|
113
|
+
"<pre><code>#{CGI.escapeHTML(spec[:code])}</code></pre>"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
# Removes paragraph tags surrounding <pre> blocks, see issue https://github.com/gollum/gollum-lib/issues/97
|
117
|
+
data.gsub!(/(<p>#{id}<\/p>|#{id})/) { body }
|
118
|
+
end
|
119
|
+
|
120
|
+
data
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
# Remove the leading space from a code block. Leading space
|
125
|
+
# is only removed if every single line in the block has leading
|
126
|
+
# whitespace.
|
127
|
+
#
|
128
|
+
# code - The code block to remove spaces from
|
129
|
+
# regex - A regex to match whitespace
|
130
|
+
def remove_leading_space(code, regex)
|
131
|
+
if code.lines.all? { |line| line =~ /\A\r?\n\Z/ || line =~ regex }
|
132
|
+
code.gsub!(regex) { '' }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def cache_codeblock(language, code, indent = "")
|
137
|
+
language = language.to_s.empty? ? nil : language
|
138
|
+
id = Digest::SHA1.hexdigest("#{language}.#{code}")
|
139
|
+
cached = @markup.check_cache(:code, id)
|
140
|
+
@map[id] = cached ?
|
141
|
+
{ :output => cached } :
|
142
|
+
{ :lang => language, :code => code, :indent => indent }
|
143
|
+
id
|
144
|
+
end
|
145
|
+
end
|