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.
- 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
|
@@ -0,0 +1,244 @@
|
|
|
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: config.rb,v 1.17 2004/11/28 05:27:32 austin Exp $
|
|
10
|
+
#++
|
|
11
|
+
require 'ruwiki/exportable'
|
|
12
|
+
|
|
13
|
+
# Ruwiki configuration.
|
|
14
|
+
class Ruwiki::Config
|
|
15
|
+
include Ruwiki::Exportable
|
|
16
|
+
|
|
17
|
+
CONFIG_NAME = 'ruwiki.conf'
|
|
18
|
+
|
|
19
|
+
exportable_group 'ruwiki-config'
|
|
20
|
+
# Sets or returns the logger. The logger, if set, must respond to the same
|
|
21
|
+
# methods as WEBrick::Logger.
|
|
22
|
+
attr_accessor :logger
|
|
23
|
+
# Sets or returns the time format whenever time is outputted in Ruwiki.
|
|
24
|
+
# Default is <tt>%H:%M:%S</tt> (23:59:59).
|
|
25
|
+
attr_accessor :time_format
|
|
26
|
+
exportable :time_format
|
|
27
|
+
# Sets or returns the date format whenever time is outputted in Ruwiki.
|
|
28
|
+
# Default is <tt>%Y.%m.%d</tt> (2004.08.04).
|
|
29
|
+
attr_accessor :date_format
|
|
30
|
+
exportable :date_format
|
|
31
|
+
# Sets or returns the date-time format whenever time is outputted in
|
|
32
|
+
# Ruwiki. Default is <tt>%Y.%m.%d %H:%M:%S</tt> (2004.08.04 23:59:59).
|
|
33
|
+
attr_accessor :datetime_format
|
|
34
|
+
exportable :datetime_format
|
|
35
|
+
# Adds additional information to the (rare) error reports. Defaults to
|
|
36
|
+
# +false+.
|
|
37
|
+
attr_accessor :debug
|
|
38
|
+
exportable :debug
|
|
39
|
+
# The default page for display when Ruwiki is called without any arguments.
|
|
40
|
+
# Defaults to +ProjectIndex+
|
|
41
|
+
attr_accessor :default_page
|
|
42
|
+
exportable :default_page
|
|
43
|
+
# The default project for display when Ruwiki is called without any
|
|
44
|
+
# arguments or a project specification. Defaults to +Default+
|
|
45
|
+
attr_accessor :default_project
|
|
46
|
+
exportable :default_project
|
|
47
|
+
# The authentication mechanism name as a String. Corresponds to
|
|
48
|
+
# a filename that will be found in ruwiki/auth. The authenticator must
|
|
49
|
+
# have a single class method, +authenticate+, which accepts the
|
|
50
|
+
# +request+, the +response+, and the +#auth_options+. This API is
|
|
51
|
+
# a draft API and is likely to change in future versions of Ruwiki. In
|
|
52
|
+
# this version of Ruwiki, only one authentication mechanism will be
|
|
53
|
+
# found -- for dealing with authenticating users already logged into
|
|
54
|
+
# RubyForge.
|
|
55
|
+
attr_accessor :auth_mechanism
|
|
56
|
+
exportable :auth_mechanism
|
|
57
|
+
# Options for the authentication mechanism as a Hash. This will be
|
|
58
|
+
# passed to the authenticator defined in +#auth_mechanism+.
|
|
59
|
+
attr_accessor :auth_options
|
|
60
|
+
exportable :auth_options
|
|
61
|
+
# The storage type as a String. Corresponds to a filename that will be
|
|
62
|
+
# found in ruwiki/backend. NOTE: The yaml and marshal storage types have
|
|
63
|
+
# been removed from Ruwiki 0.9.0, to be replaced with a single storage
|
|
64
|
+
# type of Flatfiles. Now, the YAML and Marshal formats can be enabled by
|
|
65
|
+
# setting options in the @storage_options field.
|
|
66
|
+
attr_accessor :storage_type
|
|
67
|
+
exportable :storage_type
|
|
68
|
+
# The options for the specified storage type. This is a hash of hashes with
|
|
69
|
+
# auto-vifification. See the storage type for available options.
|
|
70
|
+
attr_reader :storage_options
|
|
71
|
+
exportable :storage_options
|
|
72
|
+
# The path for templates. Defaults to <tt>./templates/</tt>.
|
|
73
|
+
attr_accessor :template_path
|
|
74
|
+
exportable :template_path
|
|
75
|
+
# The name of the Wiki. Defaults to <tt>ruwiki</tt>
|
|
76
|
+
attr_accessor :title
|
|
77
|
+
exportable :title
|
|
78
|
+
# The email address of the webmaster for the Wiki. Defaults to +nil+.
|
|
79
|
+
attr_accessor :webmaster
|
|
80
|
+
exportable :webmaster
|
|
81
|
+
# The name of the Ruwiki CSS file. Defaults to <tt>ruwiki.css</tt>.
|
|
82
|
+
attr_accessor :css
|
|
83
|
+
exportable :css
|
|
84
|
+
# The template set. Templates are always named as
|
|
85
|
+
# <template_path>/<template_set>/<template_name>. Template filename. Must
|
|
86
|
+
# be reachable by File#read.
|
|
87
|
+
attr_accessor :template_set
|
|
88
|
+
exportable :template_set
|
|
89
|
+
# Ruwiki is internationalized. This method sets the Ruwiki error
|
|
90
|
+
# messages (and a few other messages) to the specified language Module.
|
|
91
|
+
# The language Module must have a constant Hash called +Message+
|
|
92
|
+
# containing a set of symbols and localized versions of the messages
|
|
93
|
+
# associated with them.
|
|
94
|
+
#
|
|
95
|
+
# If the file 'ruwiki/lang/es.rb' contains the module
|
|
96
|
+
# <tt>Ruwiki::Lang::ES</tt>, the error messages for RSS could be
|
|
97
|
+
# localized to Espa�ol thus:
|
|
98
|
+
#
|
|
99
|
+
# require 'ruwiki/lang/es'
|
|
100
|
+
# ...
|
|
101
|
+
# wiki.config.language = Ruwiki::Lang::ES
|
|
102
|
+
#
|
|
103
|
+
# Localization is per wiki instance. In a servlet environment, this may
|
|
104
|
+
# mean that only a single language is recognised.
|
|
105
|
+
#
|
|
106
|
+
# See Ruwiki::Lang::EN for more information.
|
|
107
|
+
attr_accessor :language
|
|
108
|
+
exportable :language
|
|
109
|
+
# The message hash.
|
|
110
|
+
attr_reader :message
|
|
111
|
+
|
|
112
|
+
def language=(ll) #:nodoc:
|
|
113
|
+
if ll.kind_of?(String)
|
|
114
|
+
@language = Ruwiki::Lang::const_get(ll.upcase)
|
|
115
|
+
else
|
|
116
|
+
@language = ll
|
|
117
|
+
end
|
|
118
|
+
@message = @language::Message
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Returns the specified template as a string.
|
|
122
|
+
def template(name)
|
|
123
|
+
File.read(File.join(@template_path, @template_set, "#{name.to_s}.tmpl"))
|
|
124
|
+
rescue Errno::ENOENT
|
|
125
|
+
raise ConfigError, message[:no_template_found] % [name.inspect, @template_set]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Returns the CSS stylesheet content for the Wiki. This previously
|
|
129
|
+
# returned the <link> to the stylesheet, but instead returns a <style>
|
|
130
|
+
# block in the head so that the CSS is kept with the template set, which
|
|
131
|
+
# may be kept outside of the HTML area.
|
|
132
|
+
def css_link
|
|
133
|
+
%Q[<style type="text/css" media="screen,print">#{File.read(File.join(@template_path, @template_set, @css))}</style>]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Creates a new configuration object.
|
|
137
|
+
def initialize(exportable = {})
|
|
138
|
+
rc = exportable['ruwiki-config'] || {}
|
|
139
|
+
@debug = (rc['debug'] == "false") ? false : true
|
|
140
|
+
@default_project = rc['default-project'] || "Default"
|
|
141
|
+
@default_page = rc['default-page'] || "ProjectIndex"
|
|
142
|
+
@auth_mechanism = rc['auth-mechanism'] || nil
|
|
143
|
+
|
|
144
|
+
case rc['auth-options']
|
|
145
|
+
when nil, ""
|
|
146
|
+
@auth_options = {}
|
|
147
|
+
else
|
|
148
|
+
@auth_options = Ruwiki::Exportable.load(rc['auth-options'])['default']
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
case rc['storage-type']
|
|
152
|
+
when nil, ""
|
|
153
|
+
@storage_type = 'flatfiles'
|
|
154
|
+
else
|
|
155
|
+
@storage_type = rc['storage-type']
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# in 'type!name:<Tab>value\n' format.
|
|
159
|
+
if rc['storage-options'].nil? or rc['storage-options'].empty?
|
|
160
|
+
@storage_options = Hash.new { |hh, kk| hh[kk] = {} }
|
|
161
|
+
else
|
|
162
|
+
@storage_options = Ruwiki::Exportable.load(rc['storage-options'])
|
|
163
|
+
@storage_options.keys.each do |key|
|
|
164
|
+
@storage_options[key] = @storage_options.delete(key)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
if @storage_options.empty?
|
|
168
|
+
@storage_options[@storage_type]['extension'] = "ruwiki"
|
|
169
|
+
@storage_options[@storage_type]['data-path'] = "./data"
|
|
170
|
+
@storage_options[@storage_type]['format'] = "exportable"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
@storage_options.each_value do |vv|
|
|
174
|
+
if vv['extension'].nil? or vv['extension'].empty?
|
|
175
|
+
vv['extension'] = "ruwiki"
|
|
176
|
+
end
|
|
177
|
+
if vv['data-path'].nil? or vv['data-path'].empty?
|
|
178
|
+
vv['data-path'] = "./data"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
@template_path = rc['template-path'] || "./templates/"
|
|
183
|
+
@template_set = rc['template-set'] || "default"
|
|
184
|
+
@css = rc['css'] || "ruwiki.css"
|
|
185
|
+
@webmaster = rc['webmaster']
|
|
186
|
+
@title = rc['title'] || "Ruwiki"
|
|
187
|
+
@time_format = rc['time-format'] || "%H:%M:%S"
|
|
188
|
+
@date_format = rc['date-format'] || "%Y.%m.%d"
|
|
189
|
+
@datetime_format = rc['datetime-format'] || "#{@date_format} #{@time_format}"
|
|
190
|
+
case rc['language']
|
|
191
|
+
when nil, ""
|
|
192
|
+
self.language = Ruwiki::Lang::EN
|
|
193
|
+
else
|
|
194
|
+
self.language = Ruwiki::Lang::const_get(rc['language'].upcase)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Verifies that required configuration options are actually set. Right
|
|
199
|
+
# now, it only checks the values that are defaulted to +nil+.
|
|
200
|
+
def verify
|
|
201
|
+
raise ConfigError, message[:no_webmaster_defined] if @webmaster.nil? or @webmaster.empty?
|
|
202
|
+
raise ConfigError, message[:invalid_template_dir] % [@template_path] unless File.exists?(@template_path) and File.directory?(@template_path)
|
|
203
|
+
tt = File.join(@template_path, @template_set)
|
|
204
|
+
raise ConfigError, message[:no_template_set] % [@template_set] unless File.exists?(tt) and File.directory?(tt)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Provides the canonical export hash.
|
|
208
|
+
def export
|
|
209
|
+
exportable = super
|
|
210
|
+
|
|
211
|
+
rc = exportable['ruwiki-config']
|
|
212
|
+
|
|
213
|
+
rc['auth-options'] = Ruwiki::Exportable.dump({ 'default' => rc['auth-options']})
|
|
214
|
+
|
|
215
|
+
rc['storage-options'] = Ruwiki::Exportable.dump(rc['storage-options'])
|
|
216
|
+
rc['storage-type'] = rc['storage-type'].to_s
|
|
217
|
+
rc['language'] = "#{rc['language']}".sub(/^.*?::([A-Z]+)$/, '\1').downcase
|
|
218
|
+
exportable
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
class << self
|
|
222
|
+
def write(file, config)
|
|
223
|
+
if file.respond_to?(:write)
|
|
224
|
+
file.puts(config.dump)
|
|
225
|
+
else
|
|
226
|
+
File.open(file, 'wb') { |ff| ff.puts(config.dump) }
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def read(file)
|
|
231
|
+
data = nil
|
|
232
|
+
if file.respond_to?(:read)
|
|
233
|
+
data = file.read
|
|
234
|
+
else
|
|
235
|
+
File.open(file, 'rb') { |ff| data = ff.read }
|
|
236
|
+
end
|
|
237
|
+
hash = Ruwiki::Exportable.load(data)
|
|
238
|
+
|
|
239
|
+
Ruwiki::Config.new(hash)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
class ConfigError < StandardError; end
|
|
244
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
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: exportable.rb,v 1.3 2004/09/21 02:44:22 austin Exp $
|
|
10
|
+
#++
|
|
11
|
+
|
|
12
|
+
# == Synopsis
|
|
13
|
+
# Generalises a marshaling format that is easily read and edited by humans
|
|
14
|
+
# and is relatively easy to manage by software. When an attribute is marked
|
|
15
|
+
# #exportable, the name of the attribute is transformed and stored in
|
|
16
|
+
# a two-level hash, e.g.:
|
|
17
|
+
#
|
|
18
|
+
# exportable_group 'group1'
|
|
19
|
+
# exportable :var_1
|
|
20
|
+
# exportable :var_2
|
|
21
|
+
# exportable_group 'group2'
|
|
22
|
+
# exportable :var_3
|
|
23
|
+
# exportable :var_4
|
|
24
|
+
#
|
|
25
|
+
# Results in an exportable hash of:
|
|
26
|
+
#
|
|
27
|
+
# { 'group1' =>
|
|
28
|
+
# { 'var-1' => @var1,
|
|
29
|
+
# 'var-2' => @var2, },
|
|
30
|
+
# 'group2' =>
|
|
31
|
+
# { 'var-3' => @var3,
|
|
32
|
+
# 'var-4' => @var4, }, }
|
|
33
|
+
#
|
|
34
|
+
module Ruwiki::Exportable
|
|
35
|
+
class InvalidFormatError < RuntimeError; end
|
|
36
|
+
|
|
37
|
+
class << self
|
|
38
|
+
# Adds two methods and an attribute to the class that is including Exportable
|
|
39
|
+
#
|
|
40
|
+
# <tt>__exportables</tt>:: Contains the list of exportable symbols by group.
|
|
41
|
+
# <tt>exportable_group</tt>:: Defines the current group for exportable
|
|
42
|
+
# symbols. Default is 'default'.
|
|
43
|
+
# <tt>exportable</tt>:: Accepts two arguments, the attribute being
|
|
44
|
+
# exported and an option hash, containing the
|
|
45
|
+
# values :name and :group, where :name
|
|
46
|
+
# indicates the name of the attribute (so
|
|
47
|
+
# that the default name transformation is
|
|
48
|
+
# not applied) and :group overrides the
|
|
49
|
+
# current #exportable_group. By default, the
|
|
50
|
+
# name of the attribute is transformed such
|
|
51
|
+
# that underscores are converted to dashes
|
|
52
|
+
# (<tt>var_1</tt> becomes 'var-1').
|
|
53
|
+
def append_features(mod)
|
|
54
|
+
super
|
|
55
|
+
|
|
56
|
+
class << mod
|
|
57
|
+
attr_reader :__exportables
|
|
58
|
+
|
|
59
|
+
define_method(:exportable_group) do |name|
|
|
60
|
+
@__exportable_group = name || 'default'
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
define_method(:exportable) do |*symset|
|
|
64
|
+
symbol = symset.shift
|
|
65
|
+
options = symset.shift || {}
|
|
66
|
+
|
|
67
|
+
@__exportables ||= {}
|
|
68
|
+
|
|
69
|
+
options[:name] ||= symbol.to_s.gsub(/_/, '-')
|
|
70
|
+
options[:group] ||= @__exportable_group || 'default'
|
|
71
|
+
|
|
72
|
+
@__exportables[options[:group]] ||= {}
|
|
73
|
+
@__exportables[options[:group]][options[:name]] = "@#{symbol.to_s}".intern
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Looks for comments. Comments may ONLY be on single lines.
|
|
79
|
+
COMMENT_RE = %r{^#}
|
|
80
|
+
# Looks for newlines
|
|
81
|
+
NL_RE = %r{\n}
|
|
82
|
+
# Looks for a line that indicates an exportable value. See #dump.
|
|
83
|
+
HEADER_RE = %r{^([a-z][-a-z]+)!([a-z][-a-z]+):[ \t](.*)$}
|
|
84
|
+
# Looks for an indented group indicating that the last group is
|
|
85
|
+
# a multiline value.
|
|
86
|
+
FIRST_TAB = %r{^[ \t]}
|
|
87
|
+
|
|
88
|
+
# Dumps the provided exportable hash in the form:
|
|
89
|
+
#
|
|
90
|
+
# section!name:<Tab>Value
|
|
91
|
+
# section!name:<Space>Value
|
|
92
|
+
#
|
|
93
|
+
# Multiline values are indented either one space or one tab:
|
|
94
|
+
#
|
|
95
|
+
# section!name:<Tab>Value Line 1
|
|
96
|
+
# <Tab>Value Line 2
|
|
97
|
+
# <Tab>Value Line 3
|
|
98
|
+
# <Tab>Value Line 4
|
|
99
|
+
#
|
|
100
|
+
# All values in the exportable hash are converted to string
|
|
101
|
+
# representations, so only values that can meaningfully be reinstantiated
|
|
102
|
+
# from string representations should be stored in the exportable hash. It
|
|
103
|
+
# is the responsibility of the class preparing the exportable hash
|
|
104
|
+
# through Exportable#export to make the necessary transformations.
|
|
105
|
+
def dump(export_hash)
|
|
106
|
+
dumpstr = ""
|
|
107
|
+
|
|
108
|
+
export_hash.keys.sort.each do |sect|
|
|
109
|
+
export_hash[sect].keys.sort.each do |item|
|
|
110
|
+
val = export_hash[sect][item].to_s.split(NL_RE).join("\n\t")
|
|
111
|
+
dumpstr << "#{sect}!#{item}:\t#{val}\n"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
dumpstr
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Loads a buffer in the form provided by #dump into an exportable hash.
|
|
119
|
+
# Skips comment lines.
|
|
120
|
+
def load(buffer)
|
|
121
|
+
hash = {}
|
|
122
|
+
return hash if buffer.nil? or buffer.empty?
|
|
123
|
+
|
|
124
|
+
# Split the buffer and eliminate comments.
|
|
125
|
+
buffer = buffer.split(NL_RE).delete_if { |line| line =~ COMMENT_RE }
|
|
126
|
+
|
|
127
|
+
if HEADER_RE.match(buffer[0]).nil?
|
|
128
|
+
raise Ruwiki::Exportable::InvalidFormatError
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
sect = item = nil
|
|
132
|
+
|
|
133
|
+
buffer.each do |line|
|
|
134
|
+
line.chomp!
|
|
135
|
+
match = HEADER_RE.match(line)
|
|
136
|
+
|
|
137
|
+
# If there is no match, add the current line to the previous match.
|
|
138
|
+
# Remove the leading \t, though.
|
|
139
|
+
if match.nil?
|
|
140
|
+
raise Ruwiki::Exportable::InvalidFormatError if FIRST_TAB.match(line).nil?
|
|
141
|
+
hash[sect][item] << "\n#{line.gsub(FIRST_TAB, '')}"
|
|
142
|
+
else
|
|
143
|
+
sect = match.captures[0]
|
|
144
|
+
item = match.captures[1]
|
|
145
|
+
hash[sect] ||= {}
|
|
146
|
+
hash[sect][item] = match.captures[2]
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
hash
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Converts #exportable attributes to an exportable hash, in the form:
|
|
155
|
+
# { 'group1' =>
|
|
156
|
+
# { 'var-1' => @var1,
|
|
157
|
+
# 'var-2' => @var2, },
|
|
158
|
+
# 'group2' =>
|
|
159
|
+
# { 'var-3' => @var3,
|
|
160
|
+
# 'var-4' => @var4, }, }
|
|
161
|
+
#
|
|
162
|
+
# Classes that #include Exportable are encouraged to override export to
|
|
163
|
+
# ensure safe transformations of values. An example use might be:
|
|
164
|
+
#
|
|
165
|
+
# class TimeClass
|
|
166
|
+
# include Ruwiki::Exportable
|
|
167
|
+
#
|
|
168
|
+
# def export
|
|
169
|
+
# sym = super
|
|
170
|
+
#
|
|
171
|
+
# sym['default']['time'] = sym['default']['time'].to_i
|
|
172
|
+
# sym
|
|
173
|
+
# end
|
|
174
|
+
#
|
|
175
|
+
# In this way, the 'time' value is converted to an integer rather than the
|
|
176
|
+
# default string representation.
|
|
177
|
+
def export
|
|
178
|
+
sym = {}
|
|
179
|
+
|
|
180
|
+
self.class.__exportables.each do |group, gval|
|
|
181
|
+
gname = group || @__exportable_group || 'default'
|
|
182
|
+
gsym = {}
|
|
183
|
+
gval.each do |name, nval|
|
|
184
|
+
val = self.instance_variable_get(nval)
|
|
185
|
+
gsym[name] = val unless val.nil?
|
|
186
|
+
end
|
|
187
|
+
sym[gname] = gsym
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
sym
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,342 @@
|
|
|
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: handler.rb,v 1.8 2004/11/28 05:27:32 austin Exp $
|
|
10
|
+
#++
|
|
11
|
+
class Ruwiki::Handler
|
|
12
|
+
class << self
|
|
13
|
+
# Generate a new Handler pair from a CGI request.
|
|
14
|
+
def from_cgi(cgi, output_stream = $stdout)
|
|
15
|
+
Ruwiki::Handler.new do |o|
|
|
16
|
+
o.request = Ruwiki::Handler::CGIRequest.new(cgi)
|
|
17
|
+
o.response = Ruwiki::Handler::CGIResponse.new(cgi, output_stream)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Generate a new Handler pair from a WEBrick request.
|
|
22
|
+
def from_webrick(req, res)
|
|
23
|
+
Ruwiki::Handler.new do |o|
|
|
24
|
+
o.request = Ruwiki::Handler::WEBrickRequest.new(req)
|
|
25
|
+
o.response = Ruwiki::Handler::WEBrickResponse.new(res)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the handler's request object.
|
|
31
|
+
attr_accessor :request
|
|
32
|
+
# Returns the handler's response object.
|
|
33
|
+
attr_accessor :response
|
|
34
|
+
|
|
35
|
+
# Creates the handler pair.
|
|
36
|
+
def initialize(&block) #:yields: self
|
|
37
|
+
@request = nil
|
|
38
|
+
@response = nil
|
|
39
|
+
yield self if block_given?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Essentially a clone of WEBrick::Cookie for use with Ruwiki.
|
|
43
|
+
class Cookie
|
|
44
|
+
attr_reader :name
|
|
45
|
+
attr_accessor :value
|
|
46
|
+
attr_accessor :version
|
|
47
|
+
|
|
48
|
+
FIELDS = %w(domain path secure comment max_age expires)
|
|
49
|
+
|
|
50
|
+
FIELDS.each { |field| attr_accessor field.intern }
|
|
51
|
+
|
|
52
|
+
def initialize(name, value)
|
|
53
|
+
@name = name
|
|
54
|
+
@value = value
|
|
55
|
+
@version = 0 # Netscape Cookie
|
|
56
|
+
|
|
57
|
+
FIELDS.each { |field| instance_variable_set("@#{field}", nil) }
|
|
58
|
+
|
|
59
|
+
yield self if block_given?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def expires=(t) #:nodoc:
|
|
63
|
+
@expires = if t.nil? or t.kind_of?(Time)
|
|
64
|
+
t
|
|
65
|
+
else
|
|
66
|
+
Time.parse(t.to_s)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def to_s
|
|
71
|
+
ret = "#{@name}=#{@value}"
|
|
72
|
+
ret << "; Version=#{@version.to_s}" if @version > 0
|
|
73
|
+
ret << "; Domain=#{@domain}" if @domain
|
|
74
|
+
ret << "; Expires=#{CGI::rfc1123_date(@expires)}" if @expires
|
|
75
|
+
ret << "; Max-Age=#{CGI::rfc1123_date(@max_age)}" if @max_age
|
|
76
|
+
ret << "; Comment=#{@comment}" if @comment
|
|
77
|
+
ret << "; Path=#{@path}" if @path
|
|
78
|
+
ret << "; Secure" if @secure
|
|
79
|
+
ret
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Represents an abstract incoming request. This insulates the rest of
|
|
84
|
+
# the code from knowing whether parameters are passed as part of the
|
|
85
|
+
# path, as parameters in the URL, or in some other fashion.
|
|
86
|
+
class AbstractRequest
|
|
87
|
+
def initialize(*args)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Handles all requests from web applications.
|
|
92
|
+
#
|
|
93
|
+
# Subclasses should provide:
|
|
94
|
+
# @parameters:: Hash-like object that responds to #[] and #hash_key?]
|
|
95
|
+
# @environment:: Hash-like object that responds to #[]
|
|
96
|
+
class AbstractWebRequest < AbstractRequest
|
|
97
|
+
# The parameters provided via the web request.
|
|
98
|
+
attr_reader :parameters
|
|
99
|
+
# The environment provided to the web request.
|
|
100
|
+
attr_reader :environment
|
|
101
|
+
# The request path.
|
|
102
|
+
attr_reader :path
|
|
103
|
+
|
|
104
|
+
# The list of cookies.
|
|
105
|
+
attr_reader :cookies
|
|
106
|
+
|
|
107
|
+
def each_parameter #:yields parameter, value:
|
|
108
|
+
@parameters.each { |kk, vv| yield kk, vv }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def each_environment #:yields variable, value
|
|
112
|
+
@environment.each { |kk, vv| yield kk, vv }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def each_cookie #:yields name, value:
|
|
116
|
+
@cookies.each { |kk, vv| yield kk, vv }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Return the URL of our server.
|
|
120
|
+
def server_url
|
|
121
|
+
res = "http://" # should detect whether we're in secure server mode.
|
|
122
|
+
if @environment['HTTP_HOST']
|
|
123
|
+
res << @environment['HTTP_HOST']
|
|
124
|
+
else
|
|
125
|
+
res << "#{@environment['SERVER_NAME']}:#{@environment['SERVER_PORT']}"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Return the URL of this script.
|
|
130
|
+
def script_url
|
|
131
|
+
server_url << @environment['SCRIPT_NAME'].to_s
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Return the URL of this request.
|
|
135
|
+
def request_url
|
|
136
|
+
res = script_url
|
|
137
|
+
res << @environment['PATH_INFO'] if @environment['PATH_INFO']
|
|
138
|
+
query = @environment['QUERY_STRING']
|
|
139
|
+
res << "?#{@environment['QUERY_STRING']}" if query && !query.empty?
|
|
140
|
+
res
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Convert a file path into a URL
|
|
144
|
+
def make_url(project, path)
|
|
145
|
+
"#{server_url}/#{project}/#{path}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def determine_request_path
|
|
149
|
+
@path = ""
|
|
150
|
+
return @path if @environment['PATH_INFO'].nil?
|
|
151
|
+
@path = @environment['PATH_INFO'].dup
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Request for CGI-based activity to ruwiki.
|
|
156
|
+
class CGIRequest < AbstractWebRequest
|
|
157
|
+
def initialize(cgi, output_stream = $stdout)
|
|
158
|
+
@environment = ENV
|
|
159
|
+
@cgi = cgi
|
|
160
|
+
@parameters = {}
|
|
161
|
+
cgi.params.each { |kk, vv| @parameters[kk] = vv[0] }
|
|
162
|
+
@cookies = {}
|
|
163
|
+
cgi.cookies.each do |name, cookie|
|
|
164
|
+
@cookies[name] = Ruwiki::Handler::Cookie.new(name, cookie.value) do |oc|
|
|
165
|
+
oc.version = cookie.version if cookie.respond_to?(:version)
|
|
166
|
+
oc.domain = cookie.domain
|
|
167
|
+
oc.path = cookie.path
|
|
168
|
+
oc.secure = cookie.secure
|
|
169
|
+
oc.comment = cookie.comment if cookie.respond_to?(:comment)
|
|
170
|
+
oc.expires = cookie.expires
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
super
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Request for WEBrick based servlet activity to ruwiki.
|
|
178
|
+
class WEBrickRequest < AbstractWebRequest
|
|
179
|
+
def initialize(req)
|
|
180
|
+
@environment = req.meta_vars
|
|
181
|
+
@parameters = req.query
|
|
182
|
+
@cookies = {}
|
|
183
|
+
req.cookies.each do |rqc|
|
|
184
|
+
@cookies[rqc.name] = Ruwiki::Handler::Cookie.new(rqc.name, rqc.value) do |oc|
|
|
185
|
+
oc.version = rqc.version
|
|
186
|
+
oc.domain = rqc.domain
|
|
187
|
+
oc.path = rqc.path
|
|
188
|
+
oc.secure = rqc.secure
|
|
189
|
+
oc.comment = rqc.comment
|
|
190
|
+
oc.expires = rqc.expires
|
|
191
|
+
oc.max_age = rqc.max_age
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
super
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Used to write responses in different execution environments such as
|
|
199
|
+
# CGI and Webrick.
|
|
200
|
+
#
|
|
201
|
+
# If you want to create a new response object, you'll need to implement
|
|
202
|
+
# #add_header, #write_headers, #write_cookies, and #<<.
|
|
203
|
+
#
|
|
204
|
+
# The Response object is instantiated with an output stream which must
|
|
205
|
+
# supply +<<+ and +puts+ methods.
|
|
206
|
+
class AbstractResponse
|
|
207
|
+
# Add to the list of headers to be sent back to the client.
|
|
208
|
+
def add_header(key, value)
|
|
209
|
+
raise "Not implemented"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Write the accumulated headers back to the client.
|
|
213
|
+
def write_headers
|
|
214
|
+
raise "Not implemented"
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Write the string to the client.
|
|
218
|
+
def <<(string)
|
|
219
|
+
raise "Not implemented"
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def add_cookies(*cookies)
|
|
223
|
+
cookies.each do |cookie|
|
|
224
|
+
@cookies << cookie
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def write_cookies
|
|
229
|
+
raise "Not implemented"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# output_stream must respond to #<< and #puts.
|
|
233
|
+
def initialize(output_stream = $stdout)
|
|
234
|
+
@headers = {}
|
|
235
|
+
@cookies = []
|
|
236
|
+
@written = false
|
|
237
|
+
@status = nil
|
|
238
|
+
@output_stream = output_stream
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def written?
|
|
242
|
+
@written
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# CGIResponse is the response object for CGI mode.
|
|
247
|
+
class CGIResponse < AbstractResponse
|
|
248
|
+
# output_stream must respond to #<< and #puts.
|
|
249
|
+
def initialize(cgi, output_stream = $stdout)
|
|
250
|
+
@cgi = cgi
|
|
251
|
+
@done = {
|
|
252
|
+
:headers => false,
|
|
253
|
+
:cookies => false,
|
|
254
|
+
:body => false
|
|
255
|
+
}
|
|
256
|
+
super(output_stream)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Add the header pair for later output as a CGI header.
|
|
260
|
+
def add_header(key, value)
|
|
261
|
+
@headers[key] = value
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Write the headers to the stream. The headers can only be written
|
|
265
|
+
# once.
|
|
266
|
+
def write_headers
|
|
267
|
+
return if @done[:headers]
|
|
268
|
+
@headers.each { |key, value| @output_stream.puts "#{key}: #{value}\r\n" }
|
|
269
|
+
write_cookies
|
|
270
|
+
@output_stream.puts
|
|
271
|
+
@done[:headers] = true
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Write the cookies to the stream. The cookies can only be written
|
|
275
|
+
# once.
|
|
276
|
+
def write_cookies
|
|
277
|
+
return if @done[:cookies]
|
|
278
|
+
@cookies.each do |cookie|
|
|
279
|
+
@output_stream.puts "Set-Cookie: #{cookie.to_s}"
|
|
280
|
+
end
|
|
281
|
+
@done[:cookes] = true
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Output the string to the stream provided.
|
|
285
|
+
def <<(string)
|
|
286
|
+
@output_stream << string
|
|
287
|
+
@written = true
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def write_status(status)
|
|
291
|
+
unless status.nil?
|
|
292
|
+
@output_stream << status
|
|
293
|
+
@written = true
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# WEBrickResponse is the response object for WEBrick servlet mode.
|
|
299
|
+
class WEBrickResponse < AbstractResponse
|
|
300
|
+
def initialize(webrick_response)
|
|
301
|
+
@response = webrick_response
|
|
302
|
+
@cookies = []
|
|
303
|
+
@done = {
|
|
304
|
+
:headers => false,
|
|
305
|
+
:cookies => false,
|
|
306
|
+
:body => false
|
|
307
|
+
}
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def add_header(key, value)
|
|
311
|
+
@response[key] = value
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Copy the cookies into the WEBrick::HTTPResponse cookies array.
|
|
315
|
+
def write_cookies
|
|
316
|
+
return if @done[:cookies]
|
|
317
|
+
@cookies.each do |cookie|
|
|
318
|
+
@response.cookies << cookie.to_s
|
|
319
|
+
end
|
|
320
|
+
@done[:cookes] = true
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def write_headers
|
|
324
|
+
write_cookies
|
|
325
|
+
# Webrick will take care of this on its own.
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def <<(string)
|
|
329
|
+
@response.body << string.to_s
|
|
330
|
+
@written = true
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def write_status(status)
|
|
334
|
+
unless status.nil?
|
|
335
|
+
match = %r{^HTTP/(?:\d|\.)+ (\d+) .*}.match(status)
|
|
336
|
+
@response.status = match.captures[0]
|
|
337
|
+
@response.body << status
|
|
338
|
+
@written = true
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|