ruwiki 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. data/Readme.rubygems +86 -0
  2. data/Readme.tarfile +65 -0
  3. data/bin/ruwiki +58 -0
  4. data/bin/ruwiki.cgi +87 -0
  5. data/bin/ruwiki_convert +56 -0
  6. data/bin/ruwiki_service.rb +82 -0
  7. data/bin/ruwiki_servlet +53 -0
  8. data/contrib/enscript-token.rb +55 -0
  9. data/contrib/rublog_integrator.rb +68 -0
  10. data/data/Default/ProjectIndex.ruwiki +49 -0
  11. data/data/Ruwiki/Antispam.ruwiki +65 -0
  12. data/data/Ruwiki/BugTracking.ruwiki +33 -0
  13. data/data/Ruwiki/ChangeLog.ruwiki +102 -0
  14. data/data/Ruwiki/Configuring_Ruwiki.ruwiki +151 -0
  15. data/data/Ruwiki/Extending_Ruwiki.ruwiki +317 -0
  16. data/data/Ruwiki/LicenseAndAuthorInfo.ruwiki +30 -0
  17. data/data/Ruwiki/ProjectIndex.ruwiki +84 -0
  18. data/data/Ruwiki/Roadmap.ruwiki +225 -0
  19. data/data/Ruwiki/RuwikiTemplatingLibrary.ruwiki +156 -0
  20. data/data/Ruwiki/RuwikiUtilities.ruwiki +157 -0
  21. data/data/Ruwiki/SandBox.ruwiki +9 -0
  22. data/data/Ruwiki/To_Do.ruwiki +51 -0
  23. data/data/Ruwiki/TroubleShooting.ruwiki +33 -0
  24. data/data/Ruwiki/WikiFeatures.ruwiki +17 -0
  25. data/data/Ruwiki/WikiMarkup.ruwiki +261 -0
  26. data/data/Tutorial/AddingPages.ruwiki +16 -0
  27. data/data/Tutorial/AddingProjects.ruwiki +16 -0
  28. data/data/Tutorial/ProjectIndex.ruwiki +11 -0
  29. data/data/Tutorial/SandBox.ruwiki +9 -0
  30. data/data/agents.banned +60 -0
  31. data/data/agents.readonly +321 -0
  32. data/data/hostip.banned +30 -0
  33. data/data/hostip.readonly +28 -0
  34. data/lib/ruwiki.rb +622 -0
  35. data/lib/ruwiki/auth.rb +56 -0
  36. data/lib/ruwiki/auth/gforge.rb +73 -0
  37. data/lib/ruwiki/backend.rb +318 -0
  38. data/lib/ruwiki/backend/flatfiles.rb +217 -0
  39. data/lib/ruwiki/config.rb +244 -0
  40. data/lib/ruwiki/exportable.rb +192 -0
  41. data/lib/ruwiki/handler.rb +342 -0
  42. data/lib/ruwiki/lang/de.rb +339 -0
  43. data/lib/ruwiki/lang/en.rb +334 -0
  44. data/lib/ruwiki/lang/es.rb +339 -0
  45. data/lib/ruwiki/page.rb +262 -0
  46. data/lib/ruwiki/servlet.rb +38 -0
  47. data/lib/ruwiki/template.rb +553 -0
  48. data/lib/ruwiki/utils.rb +24 -0
  49. data/lib/ruwiki/utils/command.rb +102 -0
  50. data/lib/ruwiki/utils/converter.rb +297 -0
  51. data/lib/ruwiki/utils/manager.rb +639 -0
  52. data/lib/ruwiki/utils/servletrunner.rb +295 -0
  53. data/lib/ruwiki/wiki.rb +147 -0
  54. data/lib/ruwiki/wiki/tokens.rb +136 -0
  55. data/lib/ruwiki/wiki/tokens/00default.rb +211 -0
  56. data/lib/ruwiki/wiki/tokens/01wikilinks.rb +166 -0
  57. data/lib/ruwiki/wiki/tokens/02actions.rb +63 -0
  58. data/lib/ruwiki/wiki/tokens/abbreviations.rb +40 -0
  59. data/lib/ruwiki/wiki/tokens/calendar.rb +147 -0
  60. data/lib/ruwiki/wiki/tokens/headings.rb +43 -0
  61. data/lib/ruwiki/wiki/tokens/lists.rb +112 -0
  62. data/lib/ruwiki/wiki/tokens/rubylists.rb +48 -0
  63. data/ruwiki.conf +22 -0
  64. data/ruwiki.pkg +0 -0
  65. data/templates/default/body.tmpl +19 -0
  66. data/templates/default/content.tmpl +7 -0
  67. data/templates/default/controls.tmpl +23 -0
  68. data/templates/default/edit.tmpl +27 -0
  69. data/templates/default/error.tmpl +14 -0
  70. data/templates/default/footer.tmpl +23 -0
  71. data/templates/default/ruwiki.css +297 -0
  72. data/templates/default/save.tmpl +8 -0
  73. data/templates/sidebar/body.tmpl +19 -0
  74. data/templates/sidebar/content.tmpl +8 -0
  75. data/templates/sidebar/controls.tmpl +8 -0
  76. data/templates/sidebar/edit.tmpl +27 -0
  77. data/templates/sidebar/error.tmpl +13 -0
  78. data/templates/sidebar/footer.tmpl +22 -0
  79. data/templates/sidebar/ruwiki.css +347 -0
  80. data/templates/sidebar/save.tmpl +10 -0
  81. data/templates/simple/body.tmpl +13 -0
  82. data/templates/simple/content.tmpl +7 -0
  83. data/templates/simple/controls.tmpl +8 -0
  84. data/templates/simple/edit.tmpl +25 -0
  85. data/templates/simple/error.tmpl +10 -0
  86. data/templates/simple/footer.tmpl +10 -0
  87. data/templates/simple/ruwiki.css +192 -0
  88. data/templates/simple/save.tmpl +8 -0
  89. data/tests/harness.rb +52 -0
  90. data/tests/tc_backend_flatfile.rb +103 -0
  91. data/tests/tc_bugs.rb +74 -0
  92. data/tests/tc_exportable.rb +64 -0
  93. data/tests/tc_template.rb +145 -0
  94. data/tests/tc_tokens.rb +335 -0
  95. data/tests/testall.rb +20 -0
  96. metadata +182 -0
@@ -0,0 +1,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