ruwiki 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Readme.rubygems +86 -0
- data/Readme.tarfile +65 -0
- data/bin/ruwiki +58 -0
- data/bin/ruwiki.cgi +87 -0
- data/bin/ruwiki_convert +56 -0
- data/bin/ruwiki_service.rb +82 -0
- data/bin/ruwiki_servlet +53 -0
- data/contrib/enscript-token.rb +55 -0
- data/contrib/rublog_integrator.rb +68 -0
- data/data/Default/ProjectIndex.ruwiki +49 -0
- data/data/Ruwiki/Antispam.ruwiki +65 -0
- data/data/Ruwiki/BugTracking.ruwiki +33 -0
- data/data/Ruwiki/ChangeLog.ruwiki +102 -0
- data/data/Ruwiki/Configuring_Ruwiki.ruwiki +151 -0
- data/data/Ruwiki/Extending_Ruwiki.ruwiki +317 -0
- data/data/Ruwiki/LicenseAndAuthorInfo.ruwiki +30 -0
- data/data/Ruwiki/ProjectIndex.ruwiki +84 -0
- data/data/Ruwiki/Roadmap.ruwiki +225 -0
- data/data/Ruwiki/RuwikiTemplatingLibrary.ruwiki +156 -0
- data/data/Ruwiki/RuwikiUtilities.ruwiki +157 -0
- data/data/Ruwiki/SandBox.ruwiki +9 -0
- data/data/Ruwiki/To_Do.ruwiki +51 -0
- data/data/Ruwiki/TroubleShooting.ruwiki +33 -0
- data/data/Ruwiki/WikiFeatures.ruwiki +17 -0
- data/data/Ruwiki/WikiMarkup.ruwiki +261 -0
- data/data/Tutorial/AddingPages.ruwiki +16 -0
- data/data/Tutorial/AddingProjects.ruwiki +16 -0
- data/data/Tutorial/ProjectIndex.ruwiki +11 -0
- data/data/Tutorial/SandBox.ruwiki +9 -0
- data/data/agents.banned +60 -0
- data/data/agents.readonly +321 -0
- data/data/hostip.banned +30 -0
- data/data/hostip.readonly +28 -0
- data/lib/ruwiki.rb +622 -0
- data/lib/ruwiki/auth.rb +56 -0
- data/lib/ruwiki/auth/gforge.rb +73 -0
- data/lib/ruwiki/backend.rb +318 -0
- data/lib/ruwiki/backend/flatfiles.rb +217 -0
- data/lib/ruwiki/config.rb +244 -0
- data/lib/ruwiki/exportable.rb +192 -0
- data/lib/ruwiki/handler.rb +342 -0
- data/lib/ruwiki/lang/de.rb +339 -0
- data/lib/ruwiki/lang/en.rb +334 -0
- data/lib/ruwiki/lang/es.rb +339 -0
- data/lib/ruwiki/page.rb +262 -0
- data/lib/ruwiki/servlet.rb +38 -0
- data/lib/ruwiki/template.rb +553 -0
- data/lib/ruwiki/utils.rb +24 -0
- data/lib/ruwiki/utils/command.rb +102 -0
- data/lib/ruwiki/utils/converter.rb +297 -0
- data/lib/ruwiki/utils/manager.rb +639 -0
- data/lib/ruwiki/utils/servletrunner.rb +295 -0
- data/lib/ruwiki/wiki.rb +147 -0
- data/lib/ruwiki/wiki/tokens.rb +136 -0
- data/lib/ruwiki/wiki/tokens/00default.rb +211 -0
- data/lib/ruwiki/wiki/tokens/01wikilinks.rb +166 -0
- data/lib/ruwiki/wiki/tokens/02actions.rb +63 -0
- data/lib/ruwiki/wiki/tokens/abbreviations.rb +40 -0
- data/lib/ruwiki/wiki/tokens/calendar.rb +147 -0
- data/lib/ruwiki/wiki/tokens/headings.rb +43 -0
- data/lib/ruwiki/wiki/tokens/lists.rb +112 -0
- data/lib/ruwiki/wiki/tokens/rubylists.rb +48 -0
- data/ruwiki.conf +22 -0
- data/ruwiki.pkg +0 -0
- data/templates/default/body.tmpl +19 -0
- data/templates/default/content.tmpl +7 -0
- data/templates/default/controls.tmpl +23 -0
- data/templates/default/edit.tmpl +27 -0
- data/templates/default/error.tmpl +14 -0
- data/templates/default/footer.tmpl +23 -0
- data/templates/default/ruwiki.css +297 -0
- data/templates/default/save.tmpl +8 -0
- data/templates/sidebar/body.tmpl +19 -0
- data/templates/sidebar/content.tmpl +8 -0
- data/templates/sidebar/controls.tmpl +8 -0
- data/templates/sidebar/edit.tmpl +27 -0
- data/templates/sidebar/error.tmpl +13 -0
- data/templates/sidebar/footer.tmpl +22 -0
- data/templates/sidebar/ruwiki.css +347 -0
- data/templates/sidebar/save.tmpl +10 -0
- data/templates/simple/body.tmpl +13 -0
- data/templates/simple/content.tmpl +7 -0
- data/templates/simple/controls.tmpl +8 -0
- data/templates/simple/edit.tmpl +25 -0
- data/templates/simple/error.tmpl +10 -0
- data/templates/simple/footer.tmpl +10 -0
- data/templates/simple/ruwiki.css +192 -0
- data/templates/simple/save.tmpl +8 -0
- data/tests/harness.rb +52 -0
- data/tests/tc_backend_flatfile.rb +103 -0
- data/tests/tc_bugs.rb +74 -0
- data/tests/tc_exportable.rb +64 -0
- data/tests/tc_template.rb +145 -0
- data/tests/tc_tokens.rb +335 -0
- data/tests/testall.rb +20 -0
- metadata +182 -0
data/lib/ruwiki/auth.rb
ADDED
@@ -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
|