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.

@@ -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