gollum-lib 0.0.1
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.
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
|