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.
- checksums.yaml +15 -0
- data/Gemfile +4 -0
- data/HISTORY.md +106 -0
- data/LICENSE +21 -0
- data/README.md +617 -0
- data/Rakefile +171 -0
- data/config.rb +28 -0
- data/docs/sanitization.md +32 -0
- data/gollum-lib.gemspec +74 -0
- data/lib/gollum-lib.rb +53 -0
- data/lib/gollum-lib/blob_entry.rb +95 -0
- data/lib/gollum-lib/committer.rb +236 -0
- data/lib/gollum-lib/file.rb +101 -0
- data/lib/gollum-lib/file_view.rb +155 -0
- data/lib/gollum-lib/git_access.rb +249 -0
- data/lib/gollum-lib/gitcode.rb +48 -0
- data/lib/gollum-lib/grit_ext.rb +20 -0
- data/lib/gollum-lib/helpers.rb +13 -0
- data/lib/gollum-lib/markup.rb +688 -0
- data/lib/gollum-lib/markups.rb +13 -0
- data/lib/gollum-lib/page.rb +485 -0
- data/lib/gollum-lib/pagination.rb +62 -0
- data/lib/gollum-lib/sanitization.rb +176 -0
- data/lib/gollum-lib/web_sequence_diagram.rb +44 -0
- data/lib/gollum-lib/wiki.rb +833 -0
- data/licenses/licenses.txt +6 -0
- metadata +301 -0
@@ -0,0 +1,236 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
module Gollum
|
3
|
+
# Responsible for handling the commit process for a Wiki. It sets up the
|
4
|
+
# Git index, provides methods for modifying the tree, and stores callbacks
|
5
|
+
# to be fired after the commit has been made. This is specifically
|
6
|
+
# designed to handle multiple updated pages in a single commit.
|
7
|
+
class Committer
|
8
|
+
# Gets the instance of the Gollum::Wiki that is being updated.
|
9
|
+
attr_reader :wiki
|
10
|
+
|
11
|
+
# Gets a Hash of commit options.
|
12
|
+
attr_reader :options
|
13
|
+
|
14
|
+
# Initializes the Committer.
|
15
|
+
#
|
16
|
+
# wiki - The Gollum::Wiki instance that is being updated.
|
17
|
+
# options - The commit Hash details:
|
18
|
+
# :message - The String commit message.
|
19
|
+
# :name - The String author full name.
|
20
|
+
# :email - The String email address.
|
21
|
+
# :parent - Optional Grit::Commit parent to this update.
|
22
|
+
# :tree - Optional String SHA of the tree to create the
|
23
|
+
# index from.
|
24
|
+
# :committer - Optional Gollum::Committer instance. If provided,
|
25
|
+
# assume that this operation is part of batch of
|
26
|
+
# updates and the commit happens later.
|
27
|
+
#
|
28
|
+
# Returns the Committer instance.
|
29
|
+
def initialize(wiki, options = {})
|
30
|
+
@wiki = wiki
|
31
|
+
@options = options
|
32
|
+
@callbacks = []
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: References the Git index for this commit.
|
36
|
+
#
|
37
|
+
# Returns a Grit::Index.
|
38
|
+
def index
|
39
|
+
@index ||= begin
|
40
|
+
idx = @wiki.repo.index
|
41
|
+
if tree = options[:tree]
|
42
|
+
idx.read_tree(tree)
|
43
|
+
elsif parent = parents.first
|
44
|
+
idx.read_tree(parent.tree.id)
|
45
|
+
end
|
46
|
+
idx
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Public: The committer for this commit.
|
51
|
+
#
|
52
|
+
# Returns a Grit::Actor.
|
53
|
+
def actor
|
54
|
+
@actor ||= begin
|
55
|
+
@options[:name] = @wiki.default_committer_name if @options[:name].to_s.empty?
|
56
|
+
@options[:email] = @wiki.default_committer_email if @options[:email].to_s.empty?
|
57
|
+
Grit::Actor.new(@options[:name], @options[:email])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Public: The parent commits to this pending commit.
|
62
|
+
#
|
63
|
+
# Returns an array of Grit::Commit instances.
|
64
|
+
def parents
|
65
|
+
@parents ||= begin
|
66
|
+
arr = [@options[:parent] || @wiki.repo.commit(@wiki.ref)]
|
67
|
+
arr.flatten!
|
68
|
+
arr.compact!
|
69
|
+
arr
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Adds a page to the given Index.
|
74
|
+
#
|
75
|
+
# dir - The String subdirectory of the Gollum::Page without any
|
76
|
+
# prefix or suffix slashes (e.g. "foo/bar").
|
77
|
+
# name - The String Gollum::Page filename_stripped.
|
78
|
+
# format - The Symbol Gollum::Page format.
|
79
|
+
# data - The String wiki data to store in the tree map.
|
80
|
+
# allow_same_ext - A Boolean determining if the tree map allows the same
|
81
|
+
# filename with the same extension.
|
82
|
+
#
|
83
|
+
# Raises Gollum::DuplicatePageError if a matching filename already exists.
|
84
|
+
# This way, pages are not inadvertently overwritten.
|
85
|
+
#
|
86
|
+
# Returns nothing (modifies the Index in place).
|
87
|
+
def add_to_index(dir, name, format, data, allow_same_ext = false)
|
88
|
+
# spaces must be dashes
|
89
|
+
dir.gsub!(' ', '-')
|
90
|
+
name.gsub!(' ', '-')
|
91
|
+
|
92
|
+
path = @wiki.page_file_name(name, format)
|
93
|
+
|
94
|
+
dir = '/' if dir.strip.empty?
|
95
|
+
|
96
|
+
fullpath = ::File.join(*[@wiki.page_file_dir, dir, path].compact)
|
97
|
+
fullpath = fullpath[1..-1] if fullpath =~ /^\//
|
98
|
+
|
99
|
+
if index.current_tree && tree = index.current_tree / (@wiki.page_file_dir || '/')
|
100
|
+
tree = tree / dir unless tree.nil?
|
101
|
+
end
|
102
|
+
|
103
|
+
if tree
|
104
|
+
downpath = path.downcase.sub(/\.\w+$/, '')
|
105
|
+
|
106
|
+
tree.blobs.each do |blob|
|
107
|
+
next if page_path_scheduled_for_deletion?(index.tree, fullpath)
|
108
|
+
|
109
|
+
existing_file = blob.name.downcase.sub(/\.\w+$/, '')
|
110
|
+
existing_file_ext = ::File.extname(blob.name).sub(/^\./, '')
|
111
|
+
|
112
|
+
new_file_ext = ::File.extname(path).sub(/^\./, '')
|
113
|
+
|
114
|
+
if downpath == existing_file && !(allow_same_ext && new_file_ext == existing_file_ext)
|
115
|
+
raise DuplicatePageError.new(dir, blob.name, path)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
fullpath = fullpath.force_encoding('ascii-8bit') if fullpath.respond_to?(:force_encoding)
|
121
|
+
|
122
|
+
index.add(fullpath, @wiki.normalize(data))
|
123
|
+
end
|
124
|
+
|
125
|
+
# Update the given file in the repository's working directory if there
|
126
|
+
# is a working directory present.
|
127
|
+
#
|
128
|
+
# dir - The String directory in which the file lives.
|
129
|
+
# name - The String name of the page or the stripped filename
|
130
|
+
# (should be pre-canonicalized if required).
|
131
|
+
# format - The Symbol format of the page.
|
132
|
+
#
|
133
|
+
# Returns nothing.
|
134
|
+
def update_working_dir(dir, name, format)
|
135
|
+
unless @wiki.repo.bare
|
136
|
+
if @wiki.page_file_dir
|
137
|
+
dir = dir.size.zero? ? @wiki.page_file_dir : ::File.join(dir, @wiki.page_file_dir)
|
138
|
+
end
|
139
|
+
|
140
|
+
path =
|
141
|
+
if dir == ''
|
142
|
+
@wiki.page_file_name(name, format)
|
143
|
+
else
|
144
|
+
::File.join(dir, @wiki.page_file_name(name, format))
|
145
|
+
end
|
146
|
+
|
147
|
+
path = path.force_encoding('ascii-8bit') if path.respond_to?(:force_encoding)
|
148
|
+
|
149
|
+
Dir.chdir(::File.join(@wiki.repo.path, '..')) do
|
150
|
+
if file_path_scheduled_for_deletion?(index.tree, path)
|
151
|
+
@wiki.repo.git.rm({'f' => true}, '--', path)
|
152
|
+
else
|
153
|
+
@wiki.repo.git.checkout({}, 'HEAD', '--', path)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Writes the commit to Git and runs the after_commit callbacks.
|
160
|
+
#
|
161
|
+
# Returns the String SHA1 of the new commit.
|
162
|
+
def commit
|
163
|
+
sha1 = index.commit(@options[:message], parents, actor, nil, @wiki.ref)
|
164
|
+
@callbacks.each do |cb|
|
165
|
+
cb.call(self, sha1)
|
166
|
+
end
|
167
|
+
sha1
|
168
|
+
end
|
169
|
+
|
170
|
+
# Adds a callback to be fired after a commit.
|
171
|
+
#
|
172
|
+
# block - A block that expects this Committer instance and the created
|
173
|
+
# commit's SHA1 as the arguments.
|
174
|
+
#
|
175
|
+
# Returns nothing.
|
176
|
+
def after_commit(&block)
|
177
|
+
@callbacks << block
|
178
|
+
end
|
179
|
+
|
180
|
+
# Determine if a given page (regardless of format) is scheduled to be
|
181
|
+
# deleted in the next commit for the given Index.
|
182
|
+
#
|
183
|
+
# map - The Hash map:
|
184
|
+
# key - The String directory or filename.
|
185
|
+
# val - The Hash submap or the String contents of the file.
|
186
|
+
# path - The String path of the page file. This may include the format
|
187
|
+
# extension in which case it will be ignored.
|
188
|
+
#
|
189
|
+
# Returns the Boolean response.
|
190
|
+
def page_path_scheduled_for_deletion?(map, path)
|
191
|
+
parts = path.split('/')
|
192
|
+
if parts.size == 1
|
193
|
+
deletions = map.keys.select { |k| !map[k] }
|
194
|
+
downfile = parts.first.downcase.sub(/\.\w+$/, '')
|
195
|
+
deletions.any? { |d| d.downcase.sub(/\.\w+$/, '') == downfile }
|
196
|
+
else
|
197
|
+
part = parts.shift
|
198
|
+
if rest = map[part]
|
199
|
+
page_path_scheduled_for_deletion?(rest, parts.join('/'))
|
200
|
+
else
|
201
|
+
false
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Determine if a given file is scheduled to be deleted in the next commit
|
207
|
+
# for the given Index.
|
208
|
+
#
|
209
|
+
# map - The Hash map:
|
210
|
+
# key - The String directory or filename.
|
211
|
+
# val - The Hash submap or the String contents of the file.
|
212
|
+
# path - The String path of the file including extension.
|
213
|
+
#
|
214
|
+
# Returns the Boolean response.
|
215
|
+
def file_path_scheduled_for_deletion?(map, path)
|
216
|
+
parts = path.split('/')
|
217
|
+
if parts.size == 1
|
218
|
+
deletions = map.keys.select { |k| !map[k] }
|
219
|
+
deletions.any? { |d| d == parts.first }
|
220
|
+
else
|
221
|
+
part = parts.shift
|
222
|
+
if rest = map[part]
|
223
|
+
file_path_scheduled_for_deletion?(rest, parts.join('/'))
|
224
|
+
else
|
225
|
+
false
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Proxies methods t
|
231
|
+
def method_missing(name, *args)
|
232
|
+
args.map! { |item| item.respond_to?(:force_encoding) ? item.force_encoding('ascii-8bit') : item }
|
233
|
+
index.send(name, *args)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
module Gollum
|
3
|
+
class File
|
4
|
+
Wiki.file_class = self
|
5
|
+
|
6
|
+
# Public: Initialize a file.
|
7
|
+
#
|
8
|
+
# wiki - The Gollum::Wiki in question.
|
9
|
+
#
|
10
|
+
# Returns a newly initialized Gollum::File.
|
11
|
+
def initialize(wiki)
|
12
|
+
@wiki = wiki
|
13
|
+
@blob = nil
|
14
|
+
@path = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Public: The url path required to reach this page within the repo.
|
18
|
+
#
|
19
|
+
# Returns the String url_path
|
20
|
+
def url_path
|
21
|
+
path = self.path
|
22
|
+
path = path.sub(/\/[^\/]+$/, '/') if path.include?('/')
|
23
|
+
path
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: The url_path, but CGI escaped.
|
27
|
+
#
|
28
|
+
# Returns the String url_path
|
29
|
+
def escaped_url_path
|
30
|
+
CGI.escape(self.url_path).gsub('%2F','/')
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: The on-disk filename of the file.
|
34
|
+
#
|
35
|
+
# Returns the String name.
|
36
|
+
def name
|
37
|
+
@blob && @blob.name
|
38
|
+
end
|
39
|
+
alias filename name
|
40
|
+
|
41
|
+
# Public: The raw contents of the page.
|
42
|
+
#
|
43
|
+
# Returns the String data.
|
44
|
+
def raw_data
|
45
|
+
return nil unless @blob
|
46
|
+
|
47
|
+
if !@wiki.repo.bare && @blob.is_symlink
|
48
|
+
new_path = @blob.symlink_target(::File.join(@wiki.repo.path, '..', self.path))
|
49
|
+
return IO.read(new_path) if new_path
|
50
|
+
end
|
51
|
+
|
52
|
+
@blob.data
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: The Grit::Commit version of the file.
|
56
|
+
attr_accessor :version
|
57
|
+
|
58
|
+
# Public: The String path of the file.
|
59
|
+
attr_reader :path
|
60
|
+
|
61
|
+
# Public: The String mime type of the file.
|
62
|
+
def mime_type
|
63
|
+
@blob.mime_type
|
64
|
+
end
|
65
|
+
|
66
|
+
# Populate the File with information from the Blob.
|
67
|
+
#
|
68
|
+
# blob - The Grit::Blob that contains the info.
|
69
|
+
# path - The String directory path of the file.
|
70
|
+
#
|
71
|
+
# Returns the populated Gollum::File.
|
72
|
+
def populate(blob, path=nil)
|
73
|
+
@blob = blob
|
74
|
+
@path = "#{path}/#{blob.name}"[1..-1]
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
#########################################################################
|
79
|
+
#
|
80
|
+
# Internal Methods
|
81
|
+
#
|
82
|
+
#########################################################################
|
83
|
+
|
84
|
+
# Find a file in the given Gollum repo.
|
85
|
+
#
|
86
|
+
# name - The full String path.
|
87
|
+
# version - The String version ID to find.
|
88
|
+
#
|
89
|
+
# Returns a Gollum::File or nil if the file could not be found.
|
90
|
+
def find(name, version)
|
91
|
+
checked = name.downcase
|
92
|
+
map = @wiki.tree_map_for(version)
|
93
|
+
if entry = map.detect { |entry| entry.path.downcase == checked }
|
94
|
+
@path = name
|
95
|
+
@blob = entry.blob(@wiki.repo)
|
96
|
+
@version = version.is_a?(Grit::Commit) ? version : @wiki.commit_for(version)
|
97
|
+
self
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
module Gollum
|
3
|
+
=begin
|
4
|
+
FileView requires that:
|
5
|
+
- All files in root dir are processed first
|
6
|
+
- Then all the folders are sorted and processed
|
7
|
+
=end
|
8
|
+
class FileView
|
9
|
+
# common use cases:
|
10
|
+
# set pages to wiki.pages and show_all to false
|
11
|
+
# set pages to wiki.pages + wiki.files and show_all to true
|
12
|
+
def initialize pages, options = {}
|
13
|
+
@pages = pages
|
14
|
+
@show_all = options[:show_all] || false
|
15
|
+
@checked = options[:collapse_tree] ? '' : "checked"
|
16
|
+
end
|
17
|
+
|
18
|
+
def enclose_tree string
|
19
|
+
%Q(<ol class="tree">\n) + string + %Q(</ol>)
|
20
|
+
end
|
21
|
+
|
22
|
+
def new_page page
|
23
|
+
name = page.name
|
24
|
+
url = url_for_page page
|
25
|
+
%Q( <li class="file"><a href="#{url}"><span class="icon"></span>#{name}</a></li>)
|
26
|
+
end
|
27
|
+
|
28
|
+
def new_folder folder_path
|
29
|
+
new_sub_folder folder_path
|
30
|
+
end
|
31
|
+
|
32
|
+
def new_sub_folder path
|
33
|
+
<<-HTML
|
34
|
+
<li>
|
35
|
+
<label>#{path}</label> <input type="checkbox" #{@checked} />
|
36
|
+
<ol>
|
37
|
+
HTML
|
38
|
+
end
|
39
|
+
|
40
|
+
def end_folder
|
41
|
+
"</ol></li>\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
def url_for_page page
|
45
|
+
url = ''
|
46
|
+
if @show_all
|
47
|
+
# Remove ext for valid pages.
|
48
|
+
filename = page.filename
|
49
|
+
filename = Page::valid_page_name?(filename) ? filename.chomp(::File.extname(filename)) : filename
|
50
|
+
|
51
|
+
url = ::File.join(::File.dirname(page.path), filename)
|
52
|
+
else
|
53
|
+
url = ::File.join(::File.dirname(page.path), page.filename_stripped)
|
54
|
+
end
|
55
|
+
url = url[2..-1] if url[0,2] == './'
|
56
|
+
url
|
57
|
+
end
|
58
|
+
|
59
|
+
def render_files
|
60
|
+
html = ''
|
61
|
+
count = @pages.size
|
62
|
+
folder_start = -1
|
63
|
+
|
64
|
+
# Process all pages until folders start
|
65
|
+
count.times do | index |
|
66
|
+
page = @pages[ index ]
|
67
|
+
path = page.path
|
68
|
+
|
69
|
+
unless path.include? '/'
|
70
|
+
# Page processed (not contained in a folder)
|
71
|
+
html += new_page page
|
72
|
+
else
|
73
|
+
# Folders start at the next index
|
74
|
+
folder_start = index
|
75
|
+
break # Pages finished, move on to folders
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# If there are no folders, then we're done.
|
80
|
+
return enclose_tree(html) if folder_start <= -1
|
81
|
+
|
82
|
+
# Handle special case of only one folder.
|
83
|
+
if (count - folder_start == 1)
|
84
|
+
page = @pages[ folder_start ]
|
85
|
+
html += <<-HTML
|
86
|
+
<li>
|
87
|
+
<label>#{::File.dirname(page.path)}</label> <input type="checkbox" #{@checked} />
|
88
|
+
<ol>
|
89
|
+
#{new_page page}
|
90
|
+
</ol>
|
91
|
+
</li>
|
92
|
+
HTML
|
93
|
+
|
94
|
+
return enclose_tree html
|
95
|
+
end
|
96
|
+
|
97
|
+
sorted_folders = []
|
98
|
+
(folder_start).upto count - 1 do | index |
|
99
|
+
sorted_folders += [[ @pages[ index ].path, index ]]
|
100
|
+
end
|
101
|
+
|
102
|
+
# http://stackoverflow.com/questions/3482814/sorting-list-of-string-paths-in-vb-net
|
103
|
+
sorted_folders.sort! do |first,second|
|
104
|
+
a = first[0]
|
105
|
+
b = second[0]
|
106
|
+
|
107
|
+
# use :: operator because gollum defines its own conflicting File class
|
108
|
+
dir_compare = ::File.dirname(a) <=> ::File.dirname(b)
|
109
|
+
|
110
|
+
# Sort based on directory name unless they're equal (0) in
|
111
|
+
# which case sort based on file name.
|
112
|
+
if dir_compare == 0
|
113
|
+
::File.basename(a) <=> ::File.basename(b)
|
114
|
+
else
|
115
|
+
dir_compare
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# keep track of folder depth, 0 = at root.
|
120
|
+
cwd_array = []
|
121
|
+
changed = false
|
122
|
+
|
123
|
+
# process rest of folders
|
124
|
+
(0...sorted_folders.size).each do | index |
|
125
|
+
page = @pages[ sorted_folders[ index ][ 1 ] ]
|
126
|
+
path = page.path
|
127
|
+
folder = ::File.dirname path
|
128
|
+
|
129
|
+
tmp_array = folder.split '/'
|
130
|
+
|
131
|
+
(0...tmp_array.size).each do | index |
|
132
|
+
if cwd_array[ index ].nil? || changed
|
133
|
+
html += new_sub_folder tmp_array[ index ]
|
134
|
+
next
|
135
|
+
end
|
136
|
+
|
137
|
+
if cwd_array[ index ] != tmp_array[ index ]
|
138
|
+
changed = true
|
139
|
+
(cwd_array.size - index).times do
|
140
|
+
html += end_folder
|
141
|
+
end
|
142
|
+
html += new_sub_folder tmp_array[ index ]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
html += new_page page
|
147
|
+
cwd_array = tmp_array
|
148
|
+
changed = false
|
149
|
+
end
|
150
|
+
|
151
|
+
# return the completed html
|
152
|
+
enclose_tree html
|
153
|
+
end # end render_files
|
154
|
+
end # end FileView class
|
155
|
+
end # end Gollum module
|