ruwiki 0.9.0

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