ruwiki 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/Readme.rubygems +86 -0
  2. data/Readme.tarfile +65 -0
  3. data/bin/ruwiki +58 -0
  4. data/bin/ruwiki.cgi +87 -0
  5. data/bin/ruwiki_convert +56 -0
  6. data/bin/ruwiki_service.rb +82 -0
  7. data/bin/ruwiki_servlet +53 -0
  8. data/contrib/enscript-token.rb +55 -0
  9. data/contrib/rublog_integrator.rb +68 -0
  10. data/data/Default/ProjectIndex.ruwiki +49 -0
  11. data/data/Ruwiki/Antispam.ruwiki +65 -0
  12. data/data/Ruwiki/BugTracking.ruwiki +33 -0
  13. data/data/Ruwiki/ChangeLog.ruwiki +102 -0
  14. data/data/Ruwiki/Configuring_Ruwiki.ruwiki +151 -0
  15. data/data/Ruwiki/Extending_Ruwiki.ruwiki +317 -0
  16. data/data/Ruwiki/LicenseAndAuthorInfo.ruwiki +30 -0
  17. data/data/Ruwiki/ProjectIndex.ruwiki +84 -0
  18. data/data/Ruwiki/Roadmap.ruwiki +225 -0
  19. data/data/Ruwiki/RuwikiTemplatingLibrary.ruwiki +156 -0
  20. data/data/Ruwiki/RuwikiUtilities.ruwiki +157 -0
  21. data/data/Ruwiki/SandBox.ruwiki +9 -0
  22. data/data/Ruwiki/To_Do.ruwiki +51 -0
  23. data/data/Ruwiki/TroubleShooting.ruwiki +33 -0
  24. data/data/Ruwiki/WikiFeatures.ruwiki +17 -0
  25. data/data/Ruwiki/WikiMarkup.ruwiki +261 -0
  26. data/data/Tutorial/AddingPages.ruwiki +16 -0
  27. data/data/Tutorial/AddingProjects.ruwiki +16 -0
  28. data/data/Tutorial/ProjectIndex.ruwiki +11 -0
  29. data/data/Tutorial/SandBox.ruwiki +9 -0
  30. data/data/agents.banned +60 -0
  31. data/data/agents.readonly +321 -0
  32. data/data/hostip.banned +30 -0
  33. data/data/hostip.readonly +28 -0
  34. data/lib/ruwiki.rb +622 -0
  35. data/lib/ruwiki/auth.rb +56 -0
  36. data/lib/ruwiki/auth/gforge.rb +73 -0
  37. data/lib/ruwiki/backend.rb +318 -0
  38. data/lib/ruwiki/backend/flatfiles.rb +217 -0
  39. data/lib/ruwiki/config.rb +244 -0
  40. data/lib/ruwiki/exportable.rb +192 -0
  41. data/lib/ruwiki/handler.rb +342 -0
  42. data/lib/ruwiki/lang/de.rb +339 -0
  43. data/lib/ruwiki/lang/en.rb +334 -0
  44. data/lib/ruwiki/lang/es.rb +339 -0
  45. data/lib/ruwiki/page.rb +262 -0
  46. data/lib/ruwiki/servlet.rb +38 -0
  47. data/lib/ruwiki/template.rb +553 -0
  48. data/lib/ruwiki/utils.rb +24 -0
  49. data/lib/ruwiki/utils/command.rb +102 -0
  50. data/lib/ruwiki/utils/converter.rb +297 -0
  51. data/lib/ruwiki/utils/manager.rb +639 -0
  52. data/lib/ruwiki/utils/servletrunner.rb +295 -0
  53. data/lib/ruwiki/wiki.rb +147 -0
  54. data/lib/ruwiki/wiki/tokens.rb +136 -0
  55. data/lib/ruwiki/wiki/tokens/00default.rb +211 -0
  56. data/lib/ruwiki/wiki/tokens/01wikilinks.rb +166 -0
  57. data/lib/ruwiki/wiki/tokens/02actions.rb +63 -0
  58. data/lib/ruwiki/wiki/tokens/abbreviations.rb +40 -0
  59. data/lib/ruwiki/wiki/tokens/calendar.rb +147 -0
  60. data/lib/ruwiki/wiki/tokens/headings.rb +43 -0
  61. data/lib/ruwiki/wiki/tokens/lists.rb +112 -0
  62. data/lib/ruwiki/wiki/tokens/rubylists.rb +48 -0
  63. data/ruwiki.conf +22 -0
  64. data/ruwiki.pkg +0 -0
  65. data/templates/default/body.tmpl +19 -0
  66. data/templates/default/content.tmpl +7 -0
  67. data/templates/default/controls.tmpl +23 -0
  68. data/templates/default/edit.tmpl +27 -0
  69. data/templates/default/error.tmpl +14 -0
  70. data/templates/default/footer.tmpl +23 -0
  71. data/templates/default/ruwiki.css +297 -0
  72. data/templates/default/save.tmpl +8 -0
  73. data/templates/sidebar/body.tmpl +19 -0
  74. data/templates/sidebar/content.tmpl +8 -0
  75. data/templates/sidebar/controls.tmpl +8 -0
  76. data/templates/sidebar/edit.tmpl +27 -0
  77. data/templates/sidebar/error.tmpl +13 -0
  78. data/templates/sidebar/footer.tmpl +22 -0
  79. data/templates/sidebar/ruwiki.css +347 -0
  80. data/templates/sidebar/save.tmpl +10 -0
  81. data/templates/simple/body.tmpl +13 -0
  82. data/templates/simple/content.tmpl +7 -0
  83. data/templates/simple/controls.tmpl +8 -0
  84. data/templates/simple/edit.tmpl +25 -0
  85. data/templates/simple/error.tmpl +10 -0
  86. data/templates/simple/footer.tmpl +10 -0
  87. data/templates/simple/ruwiki.css +192 -0
  88. data/templates/simple/save.tmpl +8 -0
  89. data/tests/harness.rb +52 -0
  90. data/tests/tc_backend_flatfile.rb +103 -0
  91. data/tests/tc_bugs.rb +74 -0
  92. data/tests/tc_exportable.rb +64 -0
  93. data/tests/tc_template.rb +145 -0
  94. data/tests/tc_tokens.rb +335 -0
  95. data/tests/testall.rb +20 -0
  96. metadata +182 -0
@@ -0,0 +1,56 @@
1
+ #--
2
+ # Ruwiki
3
+ # Copyright � 2002 - 2004, Digikata and HaloStatue
4
+ # Alan Chen (alan@digikata.com)
5
+ # Austin Ziegler (ruwiki@halostatue.ca)
6
+ #
7
+ # Licensed under the same terms as Ruby.
8
+ #
9
+ # $Id: auth.rb,v 1.2 2004/11/22 04:53:40 austin Exp $
10
+ #++
11
+ class Ruwiki::Auth
12
+ class << self
13
+ def [](name)
14
+ @delegate ||= {}
15
+
16
+ if @delegate.has_key?(name)
17
+ @delegate[name]
18
+ else
19
+ require "ruwiki/auth/#{name}"
20
+ @delegate[name] = Ruwiki::Auth.const_get(name.capitalize)
21
+ end
22
+ end
23
+ end
24
+
25
+ class Token
26
+ def initialize(name = nil, groups = [], permissions = {})
27
+ @user_name = name
28
+ @groups = groups
29
+ @permissions = permissions
30
+ end
31
+
32
+ def found?
33
+ not @user_name.nil?
34
+ end
35
+
36
+ def name
37
+ @user_name
38
+ end
39
+
40
+ def member?(unix_group_name)
41
+ @groups.include?(unix_group_name)
42
+ end
43
+
44
+ def groups
45
+ @groups
46
+ end
47
+
48
+ def allowed?(action)
49
+ @permission[action]
50
+ end
51
+
52
+ def permissions
53
+ @permissions
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,73 @@
1
+ #--
2
+ # Ruwiki
3
+ # Copyright � 2002 - 2004, Digikata and HaloStatue
4
+ # Alan Chen (alan@digikata.com)
5
+ # Austin Ziegler (ruwiki@halostatue.ca)
6
+ #
7
+ # Licensed under the same terms as Ruby.
8
+ #
9
+ # $Id: gforge.rb,v 1.3 2004/11/28 23:28:15 austin Exp $
10
+ #++
11
+
12
+ begin
13
+ require 'gforge_auth'
14
+ rescue LoadError
15
+ class GForgeAuthenticator
16
+ class AuthenticationResult
17
+ def initialize(name = nil, groups = [])
18
+ @user_name = name
19
+ @groups = groups
20
+ end
21
+
22
+ def found?
23
+ not @user_name.nil?
24
+ end
25
+
26
+ def user_name
27
+ raise "No session associated with the given key was found" unless found?
28
+ @user_name
29
+ end
30
+
31
+ def member?(unix_group_name)
32
+ raise "No session associated with the given key was found" unless found?
33
+ @groups.include?(unix_group_name)
34
+ end
35
+
36
+ def groups
37
+ raise "No session associated with the given key was found" unless found?
38
+ @groups
39
+ end
40
+ end
41
+
42
+ def self.authenticate(sessionkey, options = {})
43
+ sql = %Q(SELECT user_name FROM users u, user_session us WHERE us.session_hash = '#{sessionkey}' AND us.user_id = u.user_id;)
44
+ res = %x{psql -q -t -U #{options['user']} #{options['pass']} -c \"#{sql}\"}
45
+ rows = res.split(/\n/)
46
+ return AuthenticationResult.new if rows.size != 1
47
+
48
+ user_name = rows[0].strip
49
+ sql = %Q(SELECT unix_group_name FROM groups g, users u, user_group ug WHERE u.user_name = '#{user_name}' AND ug.user_id = u.user_id AND g.group_id = ug.group_id)
50
+
51
+ res = %x(psql -q -t -U #{options['user']} #{options['pass']} -c \"#{sql}\")
52
+ groups = []
53
+ res.split(/\n/).each {|row| groups << row.strip }
54
+ AuthenticationResult.new(user_name, groups)
55
+ end
56
+ end
57
+ end
58
+
59
+ class Ruwiki::Auth::Gforge < Ruwiki::Auth
60
+ def self.authenticate(request, response, options = {})
61
+ options['user'] = options['user'].gsub!(%r{^(\w+)}, '\1')
62
+ options['pass'] = options['pass'].gsub!(%r{^(\w+)}, '\1')
63
+ session_key = request.cookies['session_ser'].value[0].split(%r{-\*-})[-1]
64
+ token = GForgeAuthenticator.authenticate(session_key, options)
65
+ token = Ruwiki::Auth::Token.new(token.user_name, token.groups)
66
+ token.permissions.default = true if token.found?
67
+ rescue
68
+ token = Ruwiki::Auth::Token.new
69
+ token.permissions.default = false
70
+ ensure
71
+ return token
72
+ end
73
+ end
@@ -0,0 +1,318 @@
1
+ #--
2
+ # Ruwiki
3
+ # Copyright � 2002 - 2004, Digikata and HaloStatue
4
+ # Alan Chen (alan@digikata.com)
5
+ # Austin Ziegler (ruwiki@halostatue.ca)
6
+ #
7
+ # Licensed under the same terms as Ruby.
8
+ #
9
+ # $Id: backend.rb,v 1.24 2004/11/28 02:26:57 austin Exp $
10
+ #++
11
+
12
+ begin
13
+ if defined?(Gem::Cache)
14
+ require_gem 'diff-lcs', '~> 1.1.2'
15
+ else
16
+ require 'diff/lcs'
17
+ end
18
+ rescue LoadError => ex
19
+ $stderr.puts ex.message
20
+ raise
21
+ end
22
+
23
+ class Ruwiki
24
+ # The list of known backends.
25
+ KNOWN_BACKENDS = %w(flatfiles)
26
+
27
+ # The Ruwiki backend delegator. Ruwiki will always instantiate a version
28
+ # of this class which delegates the actual method execution to the Backend
29
+ # class. Error handling is handled by capturing (and possibly forwarding)
30
+ # exceptions raised by the delegate class.
31
+ class BackendDelegator
32
+ def initialize(ruwiki, backend)
33
+ @message = ruwiki.config.message
34
+ @time_format = ruwiki.config.time_format || "%H:%M:%S"
35
+ @date_format = ruwiki.config.date_format || "%Y.%m.%d"
36
+ @datetime_format = ruwiki.config.datetime_format || "#{@date_format} #{@time_format}"
37
+ options = ruwiki.config.storage_options
38
+ options['default-page'] = ruwiki.config.default_page
39
+
40
+ unless Ruwiki::KNOWN_BACKENDS.include?(backend)
41
+ raise RuntimeError, @message[:backend_unknown] % [backend]
42
+ end
43
+
44
+ beconst = backend.capitalize
45
+
46
+ require "ruwiki/backend/#{backend}"
47
+
48
+ beoptions = options[backend]
49
+ @delegate = Ruwiki::Backend.const_get(beconst).new(beoptions)
50
+ rescue Ruwiki::Backend::BackendError => ex
51
+ if ex.kind_of?(Array)
52
+ raise Ruwiki::Backend::BackendError.new(nil), @message[ex.reason[0]] % ex.reason[1]
53
+ else
54
+ raise
55
+ end
56
+ end
57
+
58
+ # Retrieve the specified topic and project page. Calls Backend#load
59
+ # after verifying that the project exists.
60
+ def retrieve(topic, project = 'Default')
61
+ unless page_exists?(topic, project)
62
+ exported = Ruwiki::Page::NULL_PAGE.dup
63
+ exported['properties'] = {
64
+ 'title' => topic,
65
+ 'topic' => topic,
66
+ 'project' => project,
67
+ 'create-date' => Time.now,
68
+ 'edit-date' => Time.now,
69
+ 'editable' => true,
70
+ 'indexable' => true,
71
+ 'entropy' => 0.0,
72
+ 'html-headers' => [],
73
+ 'version' => 0
74
+ }
75
+ exported['page'] = {
76
+ 'header' => nil,
77
+ 'footer' => nil
78
+ }
79
+
80
+ if project_exists?(project)
81
+ exported['page']['content'] = ""
82
+ else
83
+ exported['page']['content'] = @message[:project_does_not_exist] % [project]
84
+ end
85
+ return exported
86
+ end
87
+
88
+ return @delegate.load(topic, project)
89
+ rescue Ruwiki::Backend::InvalidFormatError => ex
90
+ raise Ruwiki::Backend::BackendError.new(nil), @message[:page_not_in_backend_format] % [project, topic, @delegate.class]
91
+ rescue Errno::EACCES => ex
92
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:no_access_to_read_topic] % [project, topic]
93
+ rescue Exception => ex
94
+ mm = [project, topic, %Q~#{ex}<br />\n#{ex.backtrace.join('<br />\n')}~]
95
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:cannot_retrieve_topic] % mm
96
+ end
97
+
98
+ # Stores the specified topic and project page.
99
+ def store(page)
100
+ @delegate.store(page)
101
+
102
+ # update change page
103
+ begin
104
+ recent_changes = nil
105
+ if (page.topic == 'RecentChanges')
106
+ recent_changes = page.dup
107
+ else
108
+ recent_changes = Page.new(retrieve('RecentChanges', page.project))
109
+ end
110
+
111
+ changeline = "\n; #{page.editor_ip} (#{Time.now.strftime(@datetime_format)}), #{page.topic} : #{page.edit_comment}"
112
+
113
+ # add changeline to top of page
114
+ recent_changes.content = changeline + (recent_changes.content || "")
115
+ @delegate.store(recent_changes)
116
+ rescue Exception => ex
117
+ raise "Couldn't save RecentChanges\n#{ex.backtrace}"
118
+ end
119
+ rescue Errno::EACCES => ex
120
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:no_access_to_store_topic] % [page.project, page.topic]
121
+ rescue Exception => ex
122
+ mm = [page.project, page.topic, %Q~#{ex}<br />\n#{ex.backtrace.join('<br />\n')}~]
123
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:cannot_store_topic] % mm
124
+ end
125
+
126
+ # Destroys the specified topic and project page.
127
+ def destroy(page)
128
+ @delegate.destroy(page)
129
+ rescue Errno::EACCES => ex
130
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:no_access_to_destroy_topic] % [page.project, page.topic]
131
+ rescue Exception => ex
132
+ mm = [page.project, page.topic, %Q~#{ex}<br />\n#{ex.backtrace.join('<br />\n')}~]
133
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:cannot_destroy_topic] % mm
134
+ end
135
+
136
+ # Releases the lock on the page.
137
+ def release_lock(page, address = 'UNKNOWN')
138
+ time = Time.now.to_i
139
+ @delegate.release_lock(page, time, address)
140
+ rescue Ruwiki::Backend::BackendError
141
+ raise Ruwiki::Backend::BackendError.new(nil), @message[:cannot_release_lock] % [page.project, page.topic]
142
+ rescue Errno::EACCES, Exception => ex
143
+ mm = [page.project, page.topic, %Q~#{ex}<br />\n#{ex.backtrace.join('<br />\n')}~]
144
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:error_releasing_lock] % mm
145
+ end
146
+
147
+ # Attempts to obtain a lock on the page. The lock
148
+ def obtain_lock(page, address = 'UNKNOWN', timeout = 600)
149
+ time = Time.now.to_i
150
+ expire = time + timeout
151
+ @delegate.obtain_lock(page, time, expire, address)
152
+ rescue Ruwiki::Backend::BackendError
153
+ raise Ruwiki::Backend::BackendError.new(nil), @message[:cannot_obtain_lock] % [page.project, page.topic]
154
+ rescue Errno::EACCES, Exception => ex
155
+ mm = [page.project, page.topic, %Q~#{ex}<br />\n#{ex.backtrace.join('<br />\n')}~]
156
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:error_creating_lock] % mm
157
+ end
158
+
159
+ # Checks to see if the project exists.
160
+ def project_exists?(project)
161
+ @delegate.project_exists?(project)
162
+ end
163
+
164
+ # Checks to see if the page exists.
165
+ def page_exists?(topic, project = 'Default')
166
+ @delegate.page_exists?(topic, project)
167
+ end
168
+
169
+ # Attempts to create the project.
170
+ def create_project(project)
171
+ @delegate.create_project(project)
172
+ rescue Ruwiki::Backend::ProjectExists => ex
173
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:project_already_exists] % [project]
174
+ rescue Errno::EACCES => ex
175
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:no_access_to_create_project] % [project]
176
+ rescue Exception => ex
177
+ mm = [project, %Q~#{ex}<br />\n#{ex.backtrace.join('<br />\n')}~]
178
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:cannot_create_project] % mm
179
+ end
180
+
181
+ # Attempts to destroy the project.
182
+ def destroy_project(project)
183
+ @delegate.destroy_project(project)
184
+ rescue Errno::EACCES => ex
185
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:no_access_to_destroy_project] % [project]
186
+ rescue Exception => ex
187
+ mm = [project, %Q~#{ex}<br />\n#{ex.backtrace.join('<br />\n')}~]
188
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:cannot_destroy_project] % mm
189
+ end
190
+
191
+ def search_all_projects(searchstr)
192
+ if @delegate.respond_to?(:search_all_projects)
193
+ @delegate.search_all_projects(searchstr)
194
+ else
195
+ search_all_projects_default(searchstr)
196
+ end
197
+ end
198
+
199
+ # Attempts to search all projects. This is the default
200
+ # search_all_projects used unless the delegate implements
201
+ # a specialized search_all_projects.
202
+ def search_all_projects_default(searchstr)
203
+ hits = {}
204
+ list_projects.each do |project|
205
+ lhits = search_project(project, searchstr)
206
+ # Transform the keys from project local to global links.
207
+ lhits.each { |key, val| hits["#{project}::#{key}"] = val }
208
+ end
209
+ hits
210
+ end
211
+
212
+ # Attempts to search a project
213
+ def search_project(project, searchstr)
214
+ #TODO: Validate searchstr is a safe regexp?
215
+ @delegate.search_project(project, searchstr)
216
+ rescue Exception => ex
217
+ mm = [project, searchstr, ex.class, %Q~#{ex}<br />\n#{ex.backtrace.join('<br />\n')}~]
218
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:search_project_fail] % mm
219
+ end
220
+
221
+ # Return an array of projects
222
+ def list_projects
223
+ @delegate.list_projects
224
+ rescue Errno::EACCES => ex
225
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:no_access_list_projects]
226
+ rescue Exception => ex
227
+ mm = ['', %Q~#{ex}<br />\n#{ex.backtrace.join('<br />\n')}~]
228
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:cannot_list_projects] % mm
229
+ end
230
+
231
+ # Return an array of projects
232
+ def list_topics(projname)
233
+ @delegate.list_topics(projname)
234
+ rescue Errno::EACCES => ex
235
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:no_access_list_topics] % [projname]
236
+ rescue Exception => ex
237
+ mm = [projname, ex.message]
238
+ raise Ruwiki::Backend::BackendError.new(ex), @message[:cannot_list_topics] % mm
239
+ end
240
+ end
241
+
242
+ # The Ruwiki backend abstract class and factory.
243
+ class Backend
244
+ class ProjectExists < RuntimeError #:nodoc:
245
+ end
246
+ class InvalidFormatError < RuntimeError #:nodoc:
247
+ end
248
+ class BackendError < RuntimeError #:nodoc:
249
+ attr_reader :reason
250
+
251
+ def initialize(reason, *args)
252
+ if @reason.respond_to?(:message)
253
+ @reason = reason.message
254
+ else
255
+ @reason = reason
256
+ end
257
+ end
258
+ end
259
+ def initialize(storage_options)
260
+ end
261
+
262
+ private
263
+ NL_RE = %r{\n} #:nodoc:
264
+
265
+ def map_diffset(diffset)
266
+ diffset.map do |hunk|
267
+ if hunk.kind_of?(Array)
268
+ hunk.map { |change| change.to_a }
269
+ else
270
+ hunk.to_a
271
+ end
272
+ end
273
+ end
274
+
275
+ # Creates the current diff object. This is made from two
276
+ # Ruwiki::Page#export hashes.
277
+ def make_diff(oldpage, newpage)
278
+ oldpage = oldpage.export if oldpage.kind_of?(Ruwiki::Page)
279
+ newpage = newpage.export if newpage.kind_of?(Ruwiki::Page)
280
+
281
+ diff = Hash.new
282
+
283
+ newpage.keys.sort.each do |sect|
284
+ newpage[sect].keys.sort.each do |item|
285
+ oldval = oldpage[sect][item]
286
+ newval = newpage[sect][item]
287
+
288
+ case [sect, item]
289
+ when ['properties', 'html-headers']
290
+ # Protect against NoMethodError.
291
+ oldval ||= []
292
+ newval ||= []
293
+ val = Diff::LCS.sdiff(oldval, newval, Diff::LCS::ContextDiffCallbacks)
294
+ when ['ruwiki', 'content-version'], ['properties', 'version'],
295
+ ['properties', 'entropy']
296
+ val = Diff::LCS.sdiff([oldval], [newval], Diff::LCS::ContextDiffCallbacks)
297
+ when ['properties', 'create-date'], ['properties', 'edit-date']
298
+ val = Diff::LCS.sdiff([oldval.to_i], [newval.to_i], Diff::LCS::ContextDiffCallbacks)
299
+ else
300
+ # Protect against NoMethodError.
301
+ val = Diff::LCS.sdiff(oldval.to_s.split(NL_RE), newval.to_s.split(NL_RE), Diff::LCS::ContextDiffCallbacks)
302
+ end
303
+
304
+ (diff[sect] ||= {})[item] = map_diffset(val) unless val.nil? or val.empty?
305
+ end
306
+ end
307
+
308
+ diff_hash = {
309
+ 'old_version' => oldpage['properties']['version'],
310
+ 'new_version' => newpage['properties']['version'],
311
+ 'edit-date' => newpage['properties']['edit-date'].to_i,
312
+ 'editor-ip' => newpage['properties']['editor-ip'],
313
+ 'editor' => newpage['properties']['editor'],
314
+ 'diff' => diff
315
+ }
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,217 @@
1
+ #--
2
+ # Ruwiki
3
+ # Copyright � 2002 - 2004, Digikata and HaloStatue
4
+ # Alan Chen (alan@digikata.com)
5
+ # Austin Ziegler (ruwiki@halostatue.ca)
6
+ #
7
+ # Licensed under the same terms as Ruby.
8
+ #
9
+ # $Id: flatfiles.rb,v 1.25 2004/11/26 12:18:47 austin Exp $
10
+ #++
11
+ require 'ruwiki/exportable'
12
+
13
+ # Stores Ruwiki pages as flatfiles.
14
+ class Ruwiki::Backend::Flatfiles < Ruwiki::Backend
15
+ # Initializes the Flatfiles backend. The known options for the Flatfiles
16
+ # backend are documented below.
17
+ #
18
+ # data-path:: The directory in which the wiki files will be found. By
19
+ # default, this is "./data/"
20
+ # extension:: The extension of the wiki files. By default, this is
21
+ # +nil+ in the backend.
22
+ # format:: The format of the files in the backend. By default,
23
+ # this is 'exportable', a tagged data format produced by
24
+ # Ruwiki::Exportable; alternative formats are 'yaml'
25
+ # (::YAML.dump) and 'marshal' (::Marshal.dump).
26
+ # default-page:: The default page for a project. By default, this is
27
+ # ProjectIndex. This is provided only so that the backend
28
+ # can make reasonable guesses.
29
+ def initialize(options)
30
+ @data_path = options['data-path'] || File.join(".", "data")
31
+ @extension = options['extension']
32
+ @format = case options['format']
33
+ when 'exportable', nil
34
+ Ruwiki::Exportable
35
+ when 'yaml'
36
+ ::YAML
37
+ when 'marshal'
38
+ ::Marshal
39
+ end
40
+
41
+ if @extension.nil?
42
+ @extension_re = /$/
43
+ else
44
+ @extension_re = /\.#{@extension}$/
45
+ end
46
+
47
+ @default_page = options['default-page'] || "ProjectIndex"
48
+ if not (File.exists?(@data_path) and File.directory?(@data_path))
49
+ raise Ruwiki::Backend::BackendError.new([:flatfiles_no_data_directory, [@data_path]])
50
+ end
51
+
52
+ super
53
+ end
54
+
55
+ # Destroys the topic page.
56
+ def destroy(page)
57
+ pf = page_file(page.topic, page.project)
58
+ File.unlink(pf) if File.exists?(pf)
59
+ end
60
+
61
+ # Checks to see if the project exists.
62
+ def project_exists?(project)
63
+ pd = project_directory(project)
64
+ File.exists?(pd) and File.directory?(pd)
65
+ end
66
+
67
+ # Checks to see if the page exists.
68
+ def page_exists?(topic, project = 'Default')
69
+ pf = page_file(topic, project)
70
+ project_exists?(project) and File.exists?(pf)
71
+ end
72
+
73
+ # Tries to create the project.
74
+ def create_project(project)
75
+ pd = project_directory(project)
76
+ raise Ruwiki::Backend::ProjectExists if File.exists?(pd)
77
+ Dir.mkdir(pd)
78
+ end
79
+
80
+ # Tries to destroy the project.
81
+ def destroy_project(project)
82
+ pd = project_directory(project)
83
+ Dir.rmdir(pd) if File.exists?(pd) and File.directory?(pd)
84
+ end
85
+
86
+ # String search all topic names and content in a project and
87
+ # return a hash of topic hits.
88
+ def search_project(project, searchstr)
89
+ re_search = Regexp.new(searchstr, Regexp::IGNORECASE)
90
+
91
+ hits = Hash.new { |hh, kk| hh[kk] = 0 }
92
+ topic_list = list_topics(project)
93
+
94
+ return hits if topic_list.empty?
95
+
96
+ # search topic content
97
+ topic_list.each do |topic|
98
+ # search name
99
+ hits[topic] += topic.scan(re_search).size
100
+
101
+ # check content
102
+ page = load(topic, project) rescue Ruwiki::Page::NULL_PAGE
103
+ page['page'].each_value do |item|
104
+ item = item.join("") if item.kind_of?(Array)
105
+ item ||= ""
106
+ hits[topic] += item.scan(re_search).size
107
+ end
108
+ end
109
+
110
+ hits
111
+ end
112
+
113
+ def lock_okay?(page, time, address = 'UNKNOWN')
114
+ lockokay = false
115
+ lockfile = "#{page_file(page.topic, page.project)}.lock"
116
+
117
+ if File.exists?(lockfile)
118
+ data = File.read(lockfile).split(%r{!})
119
+ # If the lock belongs to this address, we don't care how old it is.
120
+ # Thus, release it.
121
+ lock_okay ||= (data[0].chomp == address)
122
+ # If the lock is older than 10 minutes, release it.
123
+ lock_okay ||= (data[1].to_i < time)
124
+ else
125
+ lockokay = true
126
+ end
127
+ end
128
+
129
+ # Attempts to obtain a lock on the topic page. This must return the lock
130
+ def obtain_lock(page, time, expire, address = 'UNKNOWN')
131
+ lock = "#{address}!#{expire}"
132
+
133
+ if lock_okay?(page, time, address)
134
+ File.open("#{page_file(page.topic, page.project)}.lock", 'wb') { |lfh| lfh.puts lock }
135
+ else
136
+ raise Ruwiki::Backend::BackendError.new(nil)
137
+ end
138
+ lock
139
+ end
140
+
141
+ # Releases the lock on the topic page.
142
+ def release_lock(page, time, address = 'UNKNOWN')
143
+ time = Time.now.to_i
144
+ lockfile = "#{page_file(page.topic, page.project)}.lock"
145
+
146
+ if lock_okay?(page, time, address)
147
+ File.unlink(lockfile) if File.exists?(lockfile)
148
+ else
149
+ raise Ruwiki::Backend::BackendError.new(nil)
150
+ end
151
+ true
152
+ end
153
+
154
+ # list projects found in data path
155
+ def list_projects
156
+ Dir[File.join(@data_path, "*")].select do |dd|
157
+ File.directory?(dd) and File.exist?(page_file(@default_page, File.basename(dd)))
158
+ end.map { |dd| File.basename(dd) }
159
+ end
160
+
161
+ # list topics found in data path
162
+ def list_topics(project)
163
+ pd = project_directory(project)
164
+ raise Ruwiki::Backend::BackendError.new(:no_project) unless File.exist?(pd)
165
+
166
+ Dir[File.join(pd, "*")].select do |ff|
167
+ ff !~ /\.rdiff$/ and ff !~ /\.lock$/ and File.file?(ff) and ff =~ @extension_re
168
+ end.map { |ff| File.basename(ff).sub(@extension_re, "") }
169
+ end
170
+
171
+ def project_directory(project) # :nodoc:
172
+ File.join(@data_path, project)
173
+ end
174
+
175
+ def page_file(topic, project = 'Default') # :nodoc:
176
+ if @extension.nil?
177
+ File.join(project_directory(project), topic)
178
+ else
179
+ File.join(project_directory(project), "#{topic}.#{@extension}")
180
+ end
181
+ end
182
+
183
+ def make_rdiff(page_file, new_page)
184
+ diff_file = "#{page_file}.rdiff"
185
+
186
+ old_page = self.class.load(pf) rescue Ruwiki::Page::NULL_PAGE
187
+
188
+ diffs = []
189
+ File.open(diff_file, 'rb') { |ff| diffs = Marshal.load(ff) } if File.exists?(diff_file)
190
+ diffs << make_diff(old_page, new_page)
191
+ changes = Marshal.dump(diffs)
192
+
193
+ File.open(diff_file, 'wb') { |ff| ff << changes }
194
+ end
195
+
196
+ # Provides a HEADER marker.
197
+ # Loads the topic page from disk.
198
+ def load(topic, project)
199
+ data = nil
200
+ File.open(page_file(topic, project), 'rb') { |ff| data = ff.read }
201
+
202
+ Ruwiki::Page::NULL_PAGE.merge(@format.load(data))
203
+ rescue Ruwiki::Exportable::InvalidFormatError, TypeError, ArgumentError
204
+ raise Ruwiki::Backend::InvalidFormatError
205
+ end
206
+
207
+ # Saves the topic page -- and its difference with the previous version
208
+ # -- to disk.
209
+ def store(page)
210
+ pagefile = page_file(page.topic, page.project)
211
+ export = page.export
212
+ newpage = @format.dump(export)
213
+ make_rdiff(pagefile, export)
214
+
215
+ File.open(pagefile, 'wb') { |ff| ff.puts newpage }
216
+ end
217
+ end