tofu 0.1.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.
- checksums.yaml +7 -0
- data/doc/Div.rd +16 -0
- data/doc/request.gif +0 -0
- data/doc/request.obj +809 -0
- data/doc/response.gif +0 -0
- data/doc/response.obj +765 -0
- data/doc/yfd01.gif +0 -0
- data/doc/yfd01.obj +200 -0
- data/doc/yfd02.gif +0 -0
- data/doc/yfd02.obj +146 -0
- data/doc/yfd03.gif +0 -0
- data/doc/yfd03.obj +243 -0
- data/lib/tofu/version.rb +3 -0
- data/lib/tofu.rb +636 -0
- data/sample/koto/base.erb +53 -0
- data/sample/koto/enter.erb +3 -0
- data/sample/koto/koto.rb +206 -0
- data/sample/koto/list.erb +20 -0
- metadata +89 -0
data/lib/tofu.rb
ADDED
@@ -0,0 +1,636 @@
|
|
1
|
+
require "tofu/version"
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require 'drb/drb'
|
5
|
+
require 'monitor'
|
6
|
+
require 'digest/md5'
|
7
|
+
require 'webrick'
|
8
|
+
require 'webrick/cgi'
|
9
|
+
require 'uri'
|
10
|
+
|
11
|
+
module Tofu
|
12
|
+
class Session
|
13
|
+
include MonitorMixin
|
14
|
+
|
15
|
+
def initialize(bartender, hint=nil)
|
16
|
+
super()
|
17
|
+
@session_id = Digest::MD5.hexdigest(Time.now.to_s + __id__.to_s)
|
18
|
+
@contents = {}
|
19
|
+
@hint = hint
|
20
|
+
renew
|
21
|
+
end
|
22
|
+
attr_reader :session_id
|
23
|
+
attr_accessor :hint
|
24
|
+
|
25
|
+
def service(context)
|
26
|
+
case context.req_method
|
27
|
+
when 'GET', 'POST', 'HEAD'
|
28
|
+
do_GET(context)
|
29
|
+
else
|
30
|
+
context.res_method_not_allowed
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def expires
|
35
|
+
Time.now + 24 * 60 * 60
|
36
|
+
end
|
37
|
+
|
38
|
+
def hint_expires
|
39
|
+
Time.now + 60 * 24 * 60 * 60
|
40
|
+
end
|
41
|
+
|
42
|
+
def renew
|
43
|
+
@expires = expires
|
44
|
+
end
|
45
|
+
|
46
|
+
def expired?
|
47
|
+
@expires && Time.now > @expires
|
48
|
+
end
|
49
|
+
|
50
|
+
def do_GET(context)
|
51
|
+
dispatch_event(context)
|
52
|
+
tofu = lookup_view(context)
|
53
|
+
body = tofu ? tofu.to_html(context) : ''
|
54
|
+
context.res_header('content-type', 'text/html; charset=utf-8')
|
55
|
+
context.res_body(body)
|
56
|
+
end
|
57
|
+
|
58
|
+
def dispatch_event(context)
|
59
|
+
params = context.req_params
|
60
|
+
tofu_id ,= params['tofu_id']
|
61
|
+
tofu = fetch(tofu_id)
|
62
|
+
return unless tofu
|
63
|
+
tofu.send_request(context, context.req_params)
|
64
|
+
end
|
65
|
+
|
66
|
+
def lookup_view(context)
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def entry(tofu)
|
71
|
+
synchronize do
|
72
|
+
@contents[tofu.tofu_id] = tofu
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def fetch(ref)
|
77
|
+
@contents[ref]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class SessionBar
|
82
|
+
include MonitorMixin
|
83
|
+
|
84
|
+
def initialize
|
85
|
+
super()
|
86
|
+
@pool = {}
|
87
|
+
@keeper = keeper
|
88
|
+
@interval = 60
|
89
|
+
end
|
90
|
+
|
91
|
+
def store(session)
|
92
|
+
key = session.session_id
|
93
|
+
synchronize do
|
94
|
+
@pool[key] = session
|
95
|
+
end
|
96
|
+
@keeper.wakeup
|
97
|
+
return key
|
98
|
+
end
|
99
|
+
|
100
|
+
def fetch(key)
|
101
|
+
return nil if key.nil?
|
102
|
+
synchronize do
|
103
|
+
session = @pool[key]
|
104
|
+
return nil unless session
|
105
|
+
if session.expired?
|
106
|
+
@pool.delete(key)
|
107
|
+
return nil
|
108
|
+
end
|
109
|
+
return session
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
def keeper
|
115
|
+
Thread.new do
|
116
|
+
loop do
|
117
|
+
synchronize do
|
118
|
+
@pool.delete_if do |k, v|
|
119
|
+
v.nil? || v.expired?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
Thread.stop if @pool.size == 0
|
123
|
+
sleep @interval
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class Bartender
|
130
|
+
def initialize(factory, name=nil)
|
131
|
+
@factory = factory
|
132
|
+
@prefix = name ? name : factory.to_s.split(':')[-1]
|
133
|
+
@bar = SessionBar.new
|
134
|
+
end
|
135
|
+
attr_reader :prefix
|
136
|
+
|
137
|
+
def service(context)
|
138
|
+
begin
|
139
|
+
session = retrieve_session(context)
|
140
|
+
catch(:tofu_done) { session.service(context) }
|
141
|
+
store_session(context, session)
|
142
|
+
ensure
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
def retrieve_session(context)
|
148
|
+
sid = context.req_cookie(@prefix + '_id')
|
149
|
+
session = @bar.fetch(sid) || make_session(context)
|
150
|
+
return session
|
151
|
+
end
|
152
|
+
|
153
|
+
def store_session(context, session)
|
154
|
+
sid = @bar.store(session)
|
155
|
+
context.res_add_cookie(@prefix + '_id', sid, session.expires)
|
156
|
+
hint = session.hint
|
157
|
+
if hint
|
158
|
+
context.res_add_cookie(@prefix +'_hint', hint, session.hint_expires)
|
159
|
+
end
|
160
|
+
session.renew
|
161
|
+
return sid
|
162
|
+
end
|
163
|
+
|
164
|
+
def make_session(context)
|
165
|
+
hint = context.req_cookie(@prefix + '_hint')
|
166
|
+
@factory.new(self, hint)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class ERBMethod
|
171
|
+
def initialize(method_name, fname, dir=nil)
|
172
|
+
@fname = build_fname(fname, dir)
|
173
|
+
@method_name = method_name
|
174
|
+
end
|
175
|
+
|
176
|
+
def reload(mod)
|
177
|
+
erb = File.open(@fname) {|f| ERB.new(f.read)}
|
178
|
+
erb.def_method(mod, @method_name, @fname)
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
def build_fname(fname, dir)
|
183
|
+
case dir
|
184
|
+
when String
|
185
|
+
ary = [dir]
|
186
|
+
when Array
|
187
|
+
ary = dir
|
188
|
+
else
|
189
|
+
ary = $:
|
190
|
+
end
|
191
|
+
|
192
|
+
found = fname # default
|
193
|
+
ary.each do |dir|
|
194
|
+
path = File::join(dir, fname)
|
195
|
+
if File::readable?(path)
|
196
|
+
found = path
|
197
|
+
break
|
198
|
+
end
|
199
|
+
end
|
200
|
+
found
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class Tofu
|
205
|
+
include DRbUndumped
|
206
|
+
include ERB::Util
|
207
|
+
|
208
|
+
@erb_method = []
|
209
|
+
def self.add_erb(method_name, fname, dir=nil)
|
210
|
+
erb = ERBMethod.new(method_name, fname, dir)
|
211
|
+
@erb_method.push(erb)
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.set_erb(fname, dir=nil)
|
215
|
+
@erb_method = [ERBMethod.new('to_html(context=nil)', fname, dir)]
|
216
|
+
reload_erb
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.reload_erb1(erb)
|
220
|
+
erb.reload(self)
|
221
|
+
rescue SyntaxError
|
222
|
+
end
|
223
|
+
|
224
|
+
def self.reload_erb
|
225
|
+
@erb_method.each do |erb|
|
226
|
+
reload_erb1(erb)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def initialize(session)
|
231
|
+
@session = session
|
232
|
+
@session.entry(self)
|
233
|
+
@tofu_seq = nil
|
234
|
+
end
|
235
|
+
attr_reader :session
|
236
|
+
|
237
|
+
def tofu_class
|
238
|
+
self.class.to_s
|
239
|
+
end
|
240
|
+
|
241
|
+
def tofu_id
|
242
|
+
self.__id__.to_s
|
243
|
+
end
|
244
|
+
|
245
|
+
def to_div(context)
|
246
|
+
to_elem('div', context)
|
247
|
+
end
|
248
|
+
|
249
|
+
def to_span(context)
|
250
|
+
to_elem('span', context)
|
251
|
+
end
|
252
|
+
|
253
|
+
def to_elem(elem, context)
|
254
|
+
elem('elem', {'class'=>tofu_class, 'id'=>tofu_id}) {
|
255
|
+
begin
|
256
|
+
to_html(context)
|
257
|
+
rescue
|
258
|
+
"<p>error! #{h($!)}</p>"
|
259
|
+
end
|
260
|
+
}
|
261
|
+
end
|
262
|
+
|
263
|
+
def to_html(context); ''; end
|
264
|
+
|
265
|
+
def to_inner_html(context)
|
266
|
+
to_html(context)
|
267
|
+
end
|
268
|
+
|
269
|
+
def send_request(context, params)
|
270
|
+
cmd, = params['tofu_cmd']
|
271
|
+
msg = 'do_' + cmd.to_s
|
272
|
+
|
273
|
+
if @tofu_seq
|
274
|
+
seq, = params['tofu_seq']
|
275
|
+
unless @tofu_seq.to_s == seq
|
276
|
+
p [seq, @tofu_seq.to_s] if $DEBUG
|
277
|
+
return
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
if respond_to?(msg)
|
282
|
+
send(msg, context, params)
|
283
|
+
else
|
284
|
+
do_else(context, params)
|
285
|
+
end
|
286
|
+
ensure
|
287
|
+
@tofu_seq = @tofu_seq.succ if @tofu_seq
|
288
|
+
end
|
289
|
+
|
290
|
+
def do_else(context, params)
|
291
|
+
end
|
292
|
+
|
293
|
+
def action(context)
|
294
|
+
context.req_script_name.to_s + context.req_path_info.to_s
|
295
|
+
end
|
296
|
+
|
297
|
+
private
|
298
|
+
def attr(opt)
|
299
|
+
ary = opt.collect do |k, v|
|
300
|
+
if v
|
301
|
+
%Q!#{k}="#{h(v)}"!
|
302
|
+
else
|
303
|
+
nil
|
304
|
+
end
|
305
|
+
end.compact
|
306
|
+
return nil if ary.size == 0
|
307
|
+
ary.join(' ')
|
308
|
+
end
|
309
|
+
|
310
|
+
def elem(name, opt={})
|
311
|
+
head = ["#{name}", attr(opt)].compact.join(" ")
|
312
|
+
if block_given?
|
313
|
+
%Q!<#{head}>\n#{yield}\n</#{name}>!
|
314
|
+
else
|
315
|
+
%Q!<#{head} />!
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def make_param(method_name, add_param={})
|
320
|
+
param = {
|
321
|
+
'tofu_id' => tofu_id,
|
322
|
+
'tofu_cmd' => method_name
|
323
|
+
}
|
324
|
+
param['tofu_seq'] = @tofu_seq if @tofu_seq
|
325
|
+
param.update(add_param)
|
326
|
+
return param
|
327
|
+
end
|
328
|
+
|
329
|
+
def form(method_name, context_or_param, context_or_empty=nil)
|
330
|
+
if context_or_empty.nil?
|
331
|
+
context = context_or_param
|
332
|
+
add_param = {}
|
333
|
+
else
|
334
|
+
context = context_or_empty
|
335
|
+
add_param = context_or_param
|
336
|
+
end
|
337
|
+
param = make_param(method_name, add_param)
|
338
|
+
hidden = input_hidden(param)
|
339
|
+
%Q!<form action="#{action(context)}" method="post">\n! + hidden
|
340
|
+
end
|
341
|
+
|
342
|
+
def href(method_name, add_param, context)
|
343
|
+
param = make_param(method_name, add_param)
|
344
|
+
ary = param.collect do |k, v|
|
345
|
+
"#{u(k)}=#{u(v)}"
|
346
|
+
end
|
347
|
+
%Q!href="#{action(context)}?#{ary.join(';')}"!
|
348
|
+
end
|
349
|
+
|
350
|
+
def input_hidden(param)
|
351
|
+
ary = param.collect do |k, v|
|
352
|
+
%Q!<input type="hidden" name="#{h(k)}" value="#{h(v)}" />\n!
|
353
|
+
end
|
354
|
+
ary.join('')
|
355
|
+
end
|
356
|
+
|
357
|
+
def make_anchor(method, param, context)
|
358
|
+
"<a #{href(method, param, context)}>"
|
359
|
+
end
|
360
|
+
|
361
|
+
def a(method, param, context)
|
362
|
+
make_anchor(method, param, context)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def reload_erb
|
367
|
+
ObjectSpace.each_object(Class) do |o|
|
368
|
+
if o.ancestors.include?(Tofu::Tofu)
|
369
|
+
o.reload_erb
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
module_function :reload_erb
|
374
|
+
|
375
|
+
class Context
|
376
|
+
def initialize(req, res)
|
377
|
+
@req = req
|
378
|
+
@res = res
|
379
|
+
end
|
380
|
+
attr_reader :req, :res
|
381
|
+
|
382
|
+
def done
|
383
|
+
throw(:tofu_done)
|
384
|
+
rescue NameError
|
385
|
+
nil
|
386
|
+
end
|
387
|
+
|
388
|
+
def service(bartender)
|
389
|
+
bartender.service(self)
|
390
|
+
nil
|
391
|
+
end
|
392
|
+
|
393
|
+
def req_params
|
394
|
+
hash = {}
|
395
|
+
@req.query.each do |k,v|
|
396
|
+
hash[k] = v.list
|
397
|
+
end
|
398
|
+
hash
|
399
|
+
end
|
400
|
+
|
401
|
+
def req_cookie(name)
|
402
|
+
found = @req.cookies.find {|c| c.name == name}
|
403
|
+
found ? found.value : nil
|
404
|
+
end
|
405
|
+
|
406
|
+
def res_add_cookie(name, value, expires=nil)
|
407
|
+
c = WEBrick::Cookie.new(name, value)
|
408
|
+
c.expires = expires if expires
|
409
|
+
@res.cookies.push(c)
|
410
|
+
end
|
411
|
+
|
412
|
+
def req_method
|
413
|
+
@req.request_method
|
414
|
+
end
|
415
|
+
|
416
|
+
def res_method_not_allowed
|
417
|
+
raise HTTPStatus::MethodNotAllowed, "unsupported method `#{req_method}'."
|
418
|
+
end
|
419
|
+
|
420
|
+
def req_path_info
|
421
|
+
@req.path_info
|
422
|
+
end
|
423
|
+
|
424
|
+
def req_script_name
|
425
|
+
@req.script_name
|
426
|
+
end
|
427
|
+
|
428
|
+
def req_absolute_path
|
429
|
+
(@req.request_uri + '/').to_s.chomp('/')
|
430
|
+
end
|
431
|
+
|
432
|
+
def res_body(v)
|
433
|
+
@res.body = v
|
434
|
+
end
|
435
|
+
|
436
|
+
def res_header(k, v)
|
437
|
+
if k.downcase == 'status'
|
438
|
+
@res.status = v.to_i
|
439
|
+
return
|
440
|
+
end
|
441
|
+
@res[k] = v
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
class Tofulet < WEBrick::HTTPServlet::AbstractServlet
|
446
|
+
def initialize(config, bartender, *options)
|
447
|
+
@bartender = bartender
|
448
|
+
super(config, *options)
|
449
|
+
@logger.debug("#{self.class}(initialize)")
|
450
|
+
end
|
451
|
+
attr_reader :logger, :config, :options, :bartender
|
452
|
+
|
453
|
+
def service(req, res)
|
454
|
+
Context.new(req, res).service(@bartender)
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
class CGITofulet < WEBrick::CGI
|
459
|
+
def initialize(bartender, *args)
|
460
|
+
@bartender = bartender
|
461
|
+
super(*args)
|
462
|
+
end
|
463
|
+
|
464
|
+
def service(req, res)
|
465
|
+
Context.new(req, res).service(@bartender)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
module Tofu
|
471
|
+
class Tofu
|
472
|
+
def update_js
|
473
|
+
<<-"EOS"
|
474
|
+
function tofu_x_eval(tofu_id) {
|
475
|
+
var ary = document.getElementsByName(tofu_id + "tofu_x_eval");
|
476
|
+
for (var j = 0; j < ary.length; j++) {
|
477
|
+
var tofu_arg = ary[j];
|
478
|
+
for (var i = 0; i < tofu_arg.childNodes.length; i++) {
|
479
|
+
var node = tofu_arg.childNodes[i];
|
480
|
+
if (node.attributes.getNamedItem('name').nodeValue == 'tofu_x_eval') {
|
481
|
+
var script = node.attributes.getNamedItem('value').nodeValue;
|
482
|
+
try {
|
483
|
+
eval(script);
|
484
|
+
} catch(e) {
|
485
|
+
}
|
486
|
+
}
|
487
|
+
}
|
488
|
+
}
|
489
|
+
}
|
490
|
+
|
491
|
+
function tofu_x_update(tofu_id, url) {
|
492
|
+
var x;
|
493
|
+
try {
|
494
|
+
x = new ActiveXObject("Msxml2.XMLHTTP");
|
495
|
+
} catch (e) {
|
496
|
+
try {
|
497
|
+
x = new ActiveXObject("Microsoft.XMLHTTP");
|
498
|
+
} catch (e) {
|
499
|
+
x = null;
|
500
|
+
}
|
501
|
+
}
|
502
|
+
if (!x && typeof XMLHttpRequest != "undefined") {
|
503
|
+
x = new XMLHttpRequest();
|
504
|
+
}
|
505
|
+
if (x) {
|
506
|
+
x.onreadystatechange = function() {
|
507
|
+
if (x.readyState == 4 && x.status == 200) {
|
508
|
+
var tofu = document.getElementById(tofu_id);
|
509
|
+
tofu.innerHTML = x.responseText;
|
510
|
+
tofu_x_eval(tofu_id);
|
511
|
+
}
|
512
|
+
}
|
513
|
+
x.open("GET", url);
|
514
|
+
x.send(null);
|
515
|
+
}
|
516
|
+
}
|
517
|
+
EOS
|
518
|
+
end
|
519
|
+
|
520
|
+
def a_and_update(method_name, add_param, context, target=nil)
|
521
|
+
target ||= self
|
522
|
+
param = {
|
523
|
+
'tofu_inner_id' => target.tofu_id
|
524
|
+
}
|
525
|
+
param.update(add_param)
|
526
|
+
|
527
|
+
param = make_param(method_name, param)
|
528
|
+
ary = param.collect do |k, v|
|
529
|
+
"#{u(k)}=#{u(v)}"
|
530
|
+
end
|
531
|
+
path = URI.parse(context.req_absolute_path)
|
532
|
+
url = path + %Q!#{action(context)}?#{ary.join(';')}!
|
533
|
+
%Q!tofu_x_update("#{target.tofu_id}", #{url.to_s.dump});!
|
534
|
+
end
|
535
|
+
|
536
|
+
def on_update_script(ary_or_script)
|
537
|
+
ary = if String === ary_or_script
|
538
|
+
[ary_or_script]
|
539
|
+
else
|
540
|
+
ary_or_script
|
541
|
+
end
|
542
|
+
str = %Q!<form name="#{tofu_id}tofu_x_eval">!
|
543
|
+
ary.each do |script|
|
544
|
+
str << %Q!<input type='hidden' name='tofu_x_eval' value="#{script.gsub('"', '"')}" />!
|
545
|
+
end
|
546
|
+
str << '</form>'
|
547
|
+
str
|
548
|
+
end
|
549
|
+
|
550
|
+
def update_me(context)
|
551
|
+
a_and_update('else', {}, context)
|
552
|
+
end
|
553
|
+
|
554
|
+
def update_after(msec, context)
|
555
|
+
callback = update_me(context)
|
556
|
+
script = %Q!setTimeout(#{callback.dump}, #{msec})!
|
557
|
+
on_update_script(script)
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
class Session
|
562
|
+
def do_inner_html(context)
|
563
|
+
params = context.req_params
|
564
|
+
tofu_id ,= params['tofu_inner_id']
|
565
|
+
return false unless tofu_id
|
566
|
+
|
567
|
+
tofu = fetch(tofu_id)
|
568
|
+
body = tofu ? tofu.to_inner_html(context) : ''
|
569
|
+
|
570
|
+
context.res_header('content-type', 'text/html; charset=utf-8')
|
571
|
+
context.res_body(body)
|
572
|
+
|
573
|
+
throw(:tofu_done)
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
if __FILE__ == $0
|
579
|
+
require 'pp'
|
580
|
+
|
581
|
+
class EnterTofu < Tofu::Tofu
|
582
|
+
ERB.new(<<EOS).def_method(self, 'to_html(context)')
|
583
|
+
<%=form('enter', {}, context)%>
|
584
|
+
<dl>
|
585
|
+
<dt>hint</dt><dd><%=h @session.hint %><input class='enter' type='text' size='40' name='hint' value='<%=h @session.hint %>'/></dd>
|
586
|
+
<dt>volatile</dt><dd><%=h @session.text %><input class='enter' type='text' size='40' name='text' value='<%=h @session.text%>'/></dd>
|
587
|
+
</dl>
|
588
|
+
<input type='submit' />
|
589
|
+
</form>
|
590
|
+
EOS
|
591
|
+
def do_enter(context, params)
|
592
|
+
hint ,= params['hint']
|
593
|
+
@session.hint = hint || ''
|
594
|
+
text ,= params['text']
|
595
|
+
@session.text = text || ''
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
class BaseTofu < Tofu::Tofu
|
600
|
+
ERB.new(<<EOS).def_method(self, 'to_html(context)')
|
601
|
+
<html><title>base</title><body>
|
602
|
+
Hello, World.
|
603
|
+
<%= @enter.to_html(context) %>
|
604
|
+
<hr />
|
605
|
+
<pre><%=h context.pretty_inspect%></pre>
|
606
|
+
</body></html>
|
607
|
+
EOS
|
608
|
+
def initialize(session)
|
609
|
+
super(session)
|
610
|
+
@enter = EnterTofu.new(session)
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
class HelloSession < Tofu::Session
|
615
|
+
def initialize(bartender, hint=nil)
|
616
|
+
super
|
617
|
+
@base = BaseTofu.new(self)
|
618
|
+
@text = ''
|
619
|
+
end
|
620
|
+
attr_accessor :text
|
621
|
+
|
622
|
+
def lookup_view(context)
|
623
|
+
@base
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
tofu = Tofu::Bartender.new(HelloSession)
|
628
|
+
if false
|
629
|
+
DRb.start_service('druby://localhost:54322', Tofu::CGITofulet.new(tofu))
|
630
|
+
DRb.thread.join
|
631
|
+
else
|
632
|
+
s = WEBrick::HTTPServer.new(:Port => 8080)
|
633
|
+
s.mount("/", Tofu::Tofulet, tofu)
|
634
|
+
s.start
|
635
|
+
end
|
636
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<script language="javascript">
|
4
|
+
<%= update_js %>
|
5
|
+
</script>
|
6
|
+
<title><%=h @session.headline %></title>
|
7
|
+
<style type="text/css" media="screen">
|
8
|
+
body {
|
9
|
+
font-family: Helvetica;
|
10
|
+
background: #FFFFFF;
|
11
|
+
color: #000000;
|
12
|
+
}
|
13
|
+
|
14
|
+
.EnterTofu * input {
|
15
|
+
box-sizing: border-box;
|
16
|
+
width: 100%;
|
17
|
+
padding: 16px 6px 6px 6px;
|
18
|
+
font-size: 16px;
|
19
|
+
font-weight: normal;
|
20
|
+
background: #6d84a2;
|
21
|
+
}
|
22
|
+
|
23
|
+
.ListTofu > ul {
|
24
|
+
margin: 0;
|
25
|
+
padding: 0;
|
26
|
+
}
|
27
|
+
|
28
|
+
.ListTofu > ul > li {
|
29
|
+
border-bottom: 1px solid #E0E0E0;
|
30
|
+
padding: 8px 0 8px 10px;
|
31
|
+
font-size: 14px;
|
32
|
+
font-weight: bold;
|
33
|
+
list-style: none:
|
34
|
+
}
|
35
|
+
|
36
|
+
.ListTofu > ul > li.group {
|
37
|
+
top: -1px;
|
38
|
+
margin-bottom: -2px;
|
39
|
+
border-top: 1px solid #7d7d7d;
|
40
|
+
border-bottom: 1px solid #999999;
|
41
|
+
padding: 1px 5px;
|
42
|
+
font-size: 13px;
|
43
|
+
font-weight: bold;
|
44
|
+
text-shadow: rgba(0, 0, 0, 0.4) 0 1px 0;
|
45
|
+
color: #FFFFFF;
|
46
|
+
}
|
47
|
+
</style>
|
48
|
+
</head>
|
49
|
+
<body onload='setTimeout(<%= @list.update_me(context).dump%>, <%= @session.interval %>)'>
|
50
|
+
<%= @enter.to_div(context)%>
|
51
|
+
<%= @list.to_div(context)%>
|
52
|
+
</body>
|
53
|
+
</html>
|