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,38 @@
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: servlet.rb,v 1.7 2004/09/29 05:36:30 austin Exp $
10
+ #++
11
+ require 'webrick'
12
+
13
+ class Ruwiki::Servlet < WEBrick::HTTPServlet::AbstractServlet
14
+ class << self
15
+ attr_accessor :config
16
+ end
17
+
18
+ def initialize(config)
19
+ @config = config
20
+ end
21
+
22
+ # Converts a POST into a GET.
23
+ def do_POST(req, res)
24
+ do_GET(req, res)
25
+ end
26
+
27
+ def do_GET(req, res)
28
+ # Generate the reponse handlers for Ruwiki from the request and response
29
+ # objects provided.
30
+ wiki = Ruwiki.new(Ruwiki::Handler.from_webrick(req, res))
31
+
32
+ # Configuration defaults to certain values. This overrides the defaults.
33
+ wiki.config = Ruwiki::Servlet.config unless Ruwiki::Servlet.config.nil?
34
+ wiki.config!
35
+ wiki.config.logger = @config.logger
36
+ wiki.run
37
+ end
38
+ end
@@ -0,0 +1,553 @@
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
+ # This file is originally from rdoc by Dave Thomas (dave@pragprog.com).
10
+ #
11
+ # $Id: template.rb,v 1.11 2004/11/26 12:18:46 austin Exp $
12
+ #++
13
+ require 'cgi'
14
+
15
+ # Ruwiki templating, based originally on RDoc's "cheap-n-cheerful" HTML
16
+ # page template system, which is a line-oriented, text-based templating
17
+ # system.
18
+ #
19
+ # Templates can contain:
20
+ #
21
+ # * The directive !INCLUDE!, which will include the next template from the
22
+ # provided list. This is processed before any template substitution, so
23
+ # repeating and optional blocks work on the values within the template
24
+ # substitution.
25
+ # * Substitutable variable values between percent signs (<tt>%key%</tt>).
26
+ # Optional variable values can be preceded by a question mark
27
+ # (<tt>%?key%</tt>).
28
+ # * Label values between hash marks (<tt>#key#</tt>). Optional label
29
+ # values can be preceded by a question mark (<tt>#?key#</tt>).
30
+ # * Links (<tt>HREF:ref:name:</tt>).
31
+ # * Repeating substitution values (<tt>[:key| stuff :]</tt>). The value of
32
+ # +key+ may be an integer value or a range (in which case key will be
33
+ # used as an iterator, providing the current value of key on successive
34
+ # values), an array of scalar values (substituting each value), or an
35
+ # array of hashes (in which case it works like repeating blocks, see
36
+ # below). These must NOT be nested. Note that integer value counting is
37
+ # one-based.
38
+ # * Optional substitution values (<tt>[?key| stuff ?]</tt> or <tt>[!key|
39
+ # stuff ?]</tt>. These must NOT be nested.
40
+ # * Repeating blocks:
41
+ # START:key
42
+ # ... stuff
43
+ # END:key
44
+ # * Optional blocks:
45
+ # IF:key
46
+ # ... stuff
47
+ # ENDIF:key
48
+ # or:
49
+ # IFNOT:key
50
+ # ... stuff
51
+ # ENDIF:key
52
+ #
53
+ # When generating the output, a hash of values is provided and an optional
54
+ # hash of labels is provided. Simple variables are resolved directly from
55
+ # the hash; labels are resolved as Symbols from the label hash or are
56
+ # otherwise treated as variables. Labels are always resolved from a single
57
+ # message hash.
58
+ #
59
+ # The +key+ for repeating blocks (one-line or multi-line) must be an array
60
+ # of hashes. The repeating block will be generated once for each entry.
61
+ # Blocks can be nested arbitrarily deeply.
62
+ #
63
+ # Optional blocks will only be generated if the test is true. IF blocks
64
+ # test for the presence of +key+ or that +key+ is non-+nil+; IFNOT blocks
65
+ # look for the absence or +nil+ value of +key+. IFBLANK blocks test for
66
+ # the absence, +nil+ value, or emptiness of +key+; IFNOTBLANK blocks test
67
+ # for the presence of +key+ and that it is neither +nil+ nor empty.
68
+ #
69
+ # Usage: Given a set of templates <tt>T1</tt>, <tt>T2</tt>, etc.
70
+ #
71
+ # values = { "name" => "Dave", "state" => "TX" }
72
+ # fr = { :name => "Nom", :state => "Etat" }
73
+ # en = { :name => "Name", :state => "State" }
74
+ # tt = TemplatePage.new(T1, T2, T3)
75
+ #
76
+ # res = ""
77
+ # tt.process(res, values, fr)
78
+ # tt.process(res, values, en)
79
+ #
80
+ class Ruwiki::TemplatePage
81
+ BLOCK_RE = %r{^\s*(IF|IFNOT|IFBLANK|IFNOTBLANK|ENDIF|START|END):(\w+)?}
82
+ HREF_RE = %r{HREF:(\w+?):(\w+?):}
83
+ LABEL_RE = %r{#(\??)(-?)(\w+?)#}
84
+ VARIABLE_RE = %r{%(\??)(-?)(\w+?)%}
85
+ IFLINE_RE = %r{\[([?!])(\w+?)\|(.*?)\?\]}
86
+ BLOCKLINE_RE = %r{\[:(\w+?)\|(.*?):\]}
87
+ INCLUDE_RE = %r{!INCLUDE!}
88
+
89
+ DDLB_RES = [
90
+ [ :check, %r{%check:(\w+?)%} ],
91
+ [ :date, %r{%date:(\w+?)%} ],
92
+ [ :popup, %r{%popup:(\w+?):(\w+?)%} ],
93
+ [ :ddlb, %r{%ddlb:(\w+?):(\w+?)%} ],
94
+ [ :vsortddlb, %r{%vsortddlb:(\w+?):(\w+?)%} ],
95
+ [ :radio, %r{%radio:(\w+?):(\w+?)%} ],
96
+ [ :radioone, %r{%radioone:(\w+?):(\w+?)%} ],
97
+ [ :input, %r{%input:(\w+?):(\d+?):(\d+?)%} ],
98
+ [ :text, %r{%text:(\w+?):(\d+?):(\d+?)%} ],
99
+ [ :pwinput, %r{%pwinput:(\w+?):(\d+?):(\d+?)%} ],
100
+ [ :pair, %r{%pair(\d)?:([^:]+)(\w+?)%} ]
101
+ ]
102
+
103
+ # Nasty hack to allow folks to insert tags if they really, really want to
104
+ OPEN_TAG = "\001"
105
+ CLOSE_TAG = "\002"
106
+ BR = "#{OPEN_TAG}br#{CLOSE_TAG}"
107
+
108
+ # A Context holds a stack of key/value pairs (like a symbol table). When
109
+ # asked to resolve a key, it first searches the top of the stack, then the
110
+ # next level, and so on until it finds a match (or runs out of entries).
111
+ class Context
112
+ def initialize
113
+ @stack = []
114
+ end
115
+
116
+ def push(hash)
117
+ @stack.push(hash)
118
+ end
119
+
120
+ def pop
121
+ @stack.pop
122
+ end
123
+
124
+ # Find a scalar value, throwing an exception if not found. This method is
125
+ # used when substituting the %xxx% constructs
126
+ def find_scalar_raw(key)
127
+ @stack.reverse_each do |level|
128
+ if level.has_key?(key)
129
+ val = level[key]
130
+ return val unless val.kind_of?(Array)
131
+ end
132
+ end
133
+ raise "Template error: can't find variable '#{key}'."
134
+ end
135
+
136
+ def find_scalar(key)
137
+ find_scalar_raw(key) || ''
138
+ end
139
+
140
+ # Lookup any key in the stack of hashes
141
+ def lookup(key)
142
+ @stack.reverse_each do |level|
143
+ return level[key] if level.has_key?(key)
144
+ end
145
+ nil
146
+ end
147
+ end
148
+
149
+ # Simple class to read lines out of a string
150
+ class LineReader
151
+ attr_reader :lines
152
+ def initialize(lines)
153
+ @lines = lines
154
+ end
155
+
156
+ # read the next line
157
+ def read
158
+ @lines.shift
159
+ end
160
+
161
+ # Return a list of lines up to the line that matches a pattern. That last
162
+ # line is discarded.
163
+ def read_up_to(pattern)
164
+ res = []
165
+ while line = read
166
+ if pattern.match(line)
167
+ return LineReader.new(res)
168
+ else
169
+ res << line
170
+ end
171
+ end
172
+ raise "Missing end tag in template: #{pattern.source}"
173
+ end
174
+
175
+ # Return a copy of ourselves that can be modified without affecting us
176
+ def dup
177
+ LineReader.new(@lines.dup)
178
+ end
179
+ end
180
+
181
+ # +templates+ is an array of strings containing the templates. We start at
182
+ # the first, and substitute in subsequent ones where the string
183
+ # <tt>!INCLUDE!</tt> occurs. For example, we could have the overall page
184
+ # template containing
185
+ #
186
+ # <html><body>
187
+ # <h1>Master</h1>
188
+ # !INCLUDE!
189
+ # </body></html>
190
+ #
191
+ # and substitute subpages in to it by passing [master, sub_page]. This
192
+ # gives us a cheap way of framing pages
193
+ def initialize(*templates)
194
+ result = templates.shift.dup
195
+ templates.each { |content| result.sub!(INCLUDE_RE, content) }
196
+ @lines = LineReader.new(result.split(/\r?\n/))
197
+ end
198
+
199
+ attr_reader :lines
200
+
201
+ def set_options(opts = {})
202
+ @message = opts[:messages] || {}
203
+ @output = opts[:output] || $stdout
204
+ end
205
+
206
+ # Render templates as HTML. Compatibility method for Rublog and
207
+ # Rdoc.
208
+ def write_html_on(op, value_hash, message_hash = {})
209
+ to_html(value_hash, { :output => op, :messages => message_hash })
210
+ end
211
+
212
+ # Render templates as HTML
213
+ def to_html(value_hash, options = {})
214
+ set_options(options)
215
+ esc = proc { |str| CGI.escapeHTML(str) }
216
+ @output << process(value_hash, esc)
217
+ end
218
+
219
+ # Render templates as TeX. Compatibility method for Rublog and
220
+ # Rdoc.
221
+ def write_tex_on(op, value_hash, message_hash = {})
222
+ to_tex(value_hash, { :output => op, :messages => message_hash })
223
+ end
224
+
225
+ # Render templates as TeX
226
+ def to_tex(value_hash, options = {})
227
+ set_options(options)
228
+
229
+ esc = proc do |str|
230
+ str.
231
+ gsub(/&lt;/, '<').
232
+ gsub(/&gt;/, '>').
233
+ gsub(/&amp;/) { '\\&' }.
234
+ gsub(/([$&%\#{}_])/) { "\\#$1" }.
235
+ gsub(/>/, '$>$').
236
+ gsub(/</, '$<$')
237
+ end
238
+ str = ""
239
+
240
+ str << process(value_hash, esc)
241
+ @output << str
242
+ end
243
+
244
+ # Render templates as plain text. Compatibility method for Rublog and
245
+ # Rdoc.
246
+ def write_plain_on(op, value_hash, message_hash = {})
247
+ to_plain(value_hash, { :output => op, :messages => message_hash })
248
+ end
249
+
250
+ # Render templates as plain text.
251
+ def to_plain(value_hash, options = {})
252
+ set_options(options)
253
+ esc = proc {|str| str}
254
+ @output << process(value_hash, esc)
255
+ end
256
+
257
+ # Render the templates. The The <tt>value_hash</tt> contains key/value
258
+ # pairs used to drive the substitution (as described above). The
259
+ # +escaper+ is a proc which will be used to sanitise the contents of the
260
+ # template.
261
+ def process(value_hash, escaper)
262
+ @context = Context.new
263
+ sub(@lines.dup, value_hash, escaper).
264
+ tr("\000", '\\').
265
+ tr(OPEN_TAG, '<').
266
+ tr(CLOSE_TAG, '>')
267
+ end
268
+
269
+ # Substitute a set of key/value pairs into the given template. Keys with
270
+ # scalar values have them substituted directly into the page. Those with
271
+ # array values invoke <tt>substitute_array</tt> (below), which examples a
272
+ # block of the template once for each row in the array.
273
+ #
274
+ # This routine also copes with the <tt>IF:</tt>_key_ directive, removing
275
+ # chunks of the template if the corresponding key does not appear in the
276
+ # hash, and the START: directive, which loops its contents for each value
277
+ # in an array
278
+ def sub(lines, values, escaper)
279
+ @context.push(values)
280
+ skip_to = nil
281
+ result = []
282
+
283
+ while line = lines.read
284
+ mv = line.match(BLOCK_RE)
285
+
286
+ if mv.nil?
287
+ result << expand(line.dup, escaper)
288
+ next
289
+ else
290
+ cmd = mv.captures[0]
291
+ tag = mv.captures[1]
292
+ end
293
+
294
+ case cmd
295
+ when "IF", "IFNOT", "IFNOTBLANK", "IFBLANK"
296
+ raise "#{cmd}: must have a key to test." if tag.nil?
297
+
298
+ val = @context.lookup(tag)
299
+ case cmd # Skip lines if the value is...
300
+ when "IF" # false or +nil+ (not false => true)
301
+ test = (not val)
302
+ when "IFBLANK" # +nil+ or empty
303
+ test = (not (val.nil? or val.empty?))
304
+ when "IFNOT"
305
+ test = val
306
+ when "IFNOTBLANK" #
307
+ test = (val.nil? or val.empty?)
308
+ end
309
+ lines.read_up_to(/^\s*ENDIF:#{tag}/) if test
310
+ when "ENDIF"
311
+ nil
312
+ when "START"
313
+ raise "#{cmd}: must have a key." if tag.nil?
314
+
315
+ body = lines.read_up_to(/^\s*END:#{tag}/)
316
+ inner = @context.lookup(tag)
317
+ raise "unknown tag: #{tag}" unless inner
318
+ raise "not array: #{tag}" unless inner.kind_of?(Array)
319
+ inner.each { |vals| result << sub(body.dup, vals, escaper) }
320
+ result << "" # Append the missing \n
321
+ else
322
+ result << expand(line.dup, escaper)
323
+ end
324
+ end
325
+
326
+ @context.pop
327
+
328
+ result.join("\n")
329
+ end
330
+
331
+ # Given an individual line, we look for %xxx%, %?xxx%, #xxx#, #?xxx#,
332
+ # [:key| xxx :], [?key| stuff ?], [!key| stuff ?] and HREF:ref:name:
333
+ # constructs, substituting as appropriate.
334
+ def expand(line, escaper)
335
+ # Generate a cross-reference if a reference is given. Otherwise, just
336
+ # fill in the name part.
337
+ line = line.gsub(HREF_RE) do
338
+ ref = @context.lookup($1)
339
+ name = @context.find_scalar($2)
340
+
341
+ if ref and not ref.kind_of?(Array)
342
+ %Q(<a href="#{ref}">#{name}</a>)
343
+ else
344
+ name
345
+ end
346
+ end
347
+
348
+ # Look for optional content.
349
+ line = line.gsub(IFLINE_RE) do
350
+ type = $1
351
+ name = $2
352
+ stuff = $3
353
+
354
+ case type
355
+ when '?'
356
+ test = @context.lookup(name)
357
+ when '!'
358
+ test = (not @context.lookup(name))
359
+ end
360
+
361
+ if test
362
+ stuff
363
+ else
364
+ ""
365
+ end
366
+ end
367
+
368
+ # Look for repeating content.
369
+ line = line.gsub(BLOCKLINE_RE) do |match|
370
+ name = $1
371
+ stuff = $2
372
+
373
+ val = @context.lookup(name)
374
+ ss = ""
375
+ case val
376
+ when nil
377
+ nil
378
+ when Fixnum
379
+ val.times { |ii| ss << stuff.sub(/%#{name}%/, "#{ii + 1}") }
380
+ when Range
381
+ val.each { |ii| ss << stuff.sub(/%#{name}%/, "#{ii}") }
382
+ when Array
383
+ if not val.empty? and val[0].kind_of?(Hash)
384
+ val.each do |vv|
385
+ @context.push(vv)
386
+ ss << expand(stuff, escaper)
387
+ @context.pop
388
+ end
389
+ else
390
+ val.each { |ee| ss << stuff.sub(/%#{name}%/, "#{ee}") }
391
+ end
392
+ end
393
+ ss
394
+ end
395
+
396
+ # Substitute in values for #xxx# constructs.
397
+ line = line.gsub(LABEL_RE) do
398
+ mandatory = $1.nil?
399
+ escaped = $2.nil?
400
+ key = $3.intern
401
+ val = @message[key]
402
+
403
+ if val.nil?
404
+ raise "Template error: can't find label '#{key}'." if mandatory
405
+ ""
406
+ else
407
+ val = val.to_s
408
+ val = escaper.call(val) if escaped
409
+ val.tr('\\', "\000")
410
+ end
411
+ end
412
+
413
+ # Substitute in values for %xxx% constructs. This is made complex
414
+ # because the replacement string may contain characters that are
415
+ # meaningful to the regexp (like \1)
416
+ line = line.gsub(VARIABLE_RE) do
417
+ mandatory = $1.nil?
418
+ escaped = $2.nil?
419
+ key = $3
420
+ val = @context.lookup(key)
421
+
422
+ if val.nil?
423
+ raise "Template error: can't find variable '#{key}'." if mandatory
424
+ ""
425
+ else
426
+ val = val.to_s
427
+ val = escaper.call(val) if escaped
428
+ val.tr('\\', "\000")
429
+ end
430
+ end
431
+
432
+ # Substitute DDLB controls:
433
+ DDLB_RES.each do |ddlb|
434
+ line = line.gsub(ddlb[1]) do
435
+ self.send(ddlb[0], Regexp.last_match.captures)
436
+ end
437
+ end
438
+
439
+ line
440
+ rescue Exception => ex
441
+ raise "Error in template: #{ex}\nOriginal line: #{line}\n#{ex.backtrace[0]}"
442
+ end
443
+
444
+ def check(*args)
445
+ value = @context.find_scalar_raw(args[0])
446
+ checked = value ? " checked" : ""
447
+ "<input type=\"checkbox\" name=\"#{name}\"#{checked}>"
448
+ end
449
+
450
+ def vsortddlb(*args)
451
+ ddlb(*(args.dup << true))
452
+ end
453
+
454
+ def ddlb(*args)
455
+ value = @context.find_scalar(args[0]).to_s
456
+ options = @context.lookup(args[1])
457
+ sort_on = args[2] || 0
458
+
459
+ unless options and options.kind_of?(Hash)
460
+ raise "Missing options #{args[1]} for ddlb #{args[0]}."
461
+ end
462
+
463
+ res = %Q(<select name="#{args[0]}">)
464
+
465
+ sorted = options.to_a.sort do |aa, bb|
466
+ if aa[0] == -1
467
+ -1
468
+ elsif bb[0] == -1
469
+ 1
470
+ else
471
+ aa[sort_on] <=> bb[sort_on]
472
+ end
473
+ end
474
+
475
+ sorted.each do |key, val|
476
+ selected = (key.to_s == value) ? " selected" : ""
477
+ res << %Q(<option value="#{key}"#{selected}>#{val}</option>)
478
+ end
479
+ res << "</select>"
480
+ end
481
+
482
+ def date(*args)
483
+ yy = "#{argv[0]}_y"
484
+ mm = "#{argv[0]}_m"
485
+ dd = "#{argv[0]}_d"
486
+ %Q<#{input(yy, 4, 4)}&nbsp;.&nbsp;#{input(mm, 2, 2)}&nbsp;.&nbsp;#{input(dd, 2, 2)}>
487
+ end
488
+
489
+ def radioone(*args)
490
+ radio(*(args.dup << ""))
491
+ end
492
+
493
+ def radio(*args)
494
+ value = @context.find_scalar(argv[0]).to_s
495
+ options = @context.lookup(argv[1])
496
+ br = argv[2] || "<br />"
497
+
498
+ unless options and options.kind_of?(Hash)
499
+ raise "Missing options #{args[1]} for radio #{args[0]}."
500
+ end
501
+
502
+ res = ""
503
+ options.keys.sort.each do |key|
504
+ val = options[key]
505
+ checked = (key.to_s == value) ? " checked" : ""
506
+ res << %Q(<label>
507
+ <input type="radio" name="#{args[0]}"
508
+ value="#{key}"#{checked}">#{val}</label>#{br})
509
+ end
510
+ res
511
+ end
512
+
513
+ def text(*args)
514
+ value = @context.find_scalar(args[0]).to_s
515
+ %Q(<textarea name="#{args[0]}" cols="#{args[1]}" rows="#{args[2]}">
516
+ #{CGI.escapeHTML(value)}
517
+ </textarea>)
518
+ end
519
+
520
+ def pwinput(*args)
521
+ input(*(args.dup << "password"))
522
+ end
523
+
524
+ def input(*args)
525
+ name = args[0]
526
+ value = @context.find_scalar(name).to_s
527
+ width = args[1]
528
+ max = args[2]
529
+ iptype = args[3] || "text"
530
+ %Q(<input type="#{iptype}" name="#{name}" value="#{value}" size="#{width}" maxsize="#{max}">)
531
+ end
532
+
533
+ def popup(*args)
534
+ url = CGI.escapeHTML(@context.find_scalar(args[0]).to_s)
535
+ text = @context.find_scalar(args[1]).to_s
536
+ %Q|<a href="#{url}" target="Popup" class="methodtitle" onClick="popup('#{url}'); return false;">#{text}</a>|
537
+ end
538
+
539
+ def pair(*args)
540
+ label = args[0]
541
+ name = args[1]
542
+ colsp = args[2]
543
+
544
+ value = @context.find_scalar(name).to_s
545
+ value = case value
546
+ when "true" then "Yes"
547
+ when "false" then "No"
548
+ else value
549
+ end
550
+ td = (colsp.nil? or colsp.empty?) ? "<td>" : %Q{<td colspan="#{colsp}">}
551
+ "#{Html.tag(label)}#{td}#{value}</td>"
552
+ end
553
+ end