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