wee 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +75 -0
- data/README +17 -9
- data/Rakefile +2 -2
- data/TODO +20 -0
- data/benchmark/Makefile +14 -9
- data/benchmark/counter.rb +11 -30
- data/benchmark/report_req.rb +12 -0
- data/doc/rdoc/classes/Array.html +12 -12
- data/doc/rdoc/classes/Cache/StorageCache.html +38 -38
- data/doc/rdoc/classes/Cache/Strategy/CapacityBounded.html +30 -30
- data/doc/rdoc/classes/Cache/Strategy/LFU.html +24 -24
- data/doc/rdoc/classes/Cache/Strategy/LRU.html +24 -24
- data/doc/rdoc/classes/Cache/Strategy/Unbounded.html +24 -24
- data/doc/rdoc/classes/Enumerable.html +6 -6
- data/doc/rdoc/classes/Object.html +12 -12
- data/doc/rdoc/classes/OgApplication.html +126 -0
- data/doc/rdoc/classes/OgScaffolder.html +401 -0
- data/doc/rdoc/classes/OgSession.html +172 -0
- data/doc/rdoc/classes/String.html +12 -12
- data/doc/rdoc/classes/Struct.html +12 -12
- data/doc/rdoc/classes/Wee.html +5 -62
- data/doc/rdoc/classes/Wee/AnswerDecoration.html +9 -9
- data/doc/rdoc/classes/Wee/Application.html +107 -39
- data/doc/rdoc/classes/Wee/Brush.html +22 -18
- data/doc/rdoc/classes/Wee/Brush/ActionCallbackMixin.html +17 -11
- data/doc/rdoc/classes/Wee/Brush/ActionURLCallbackMixin.html +18 -10
- data/doc/rdoc/classes/Wee/Brush/AnchorTag.html +30 -64
- data/doc/rdoc/classes/Wee/Brush/FileUploadTag.html +8 -10
- data/doc/rdoc/classes/Wee/Brush/FormTag.html +27 -79
- data/doc/rdoc/classes/Wee/Brush/GenericEncodedTextBrush.html +12 -12
- data/doc/rdoc/classes/Wee/Brush/GenericSingleTagBrush.html +146 -0
- data/doc/rdoc/classes/Wee/Brush/GenericTagBrush.html +179 -65
- data/doc/rdoc/classes/Wee/Brush/GenericTextBrush.html +12 -12
- data/doc/rdoc/classes/Wee/Brush/ImageButtonTag.html +16 -18
- data/doc/rdoc/classes/Wee/Brush/ImageTag.html +203 -0
- data/doc/rdoc/classes/Wee/Brush/InputCallbackMixin.html +17 -11
- data/doc/rdoc/classes/Wee/Brush/InputTag.html +15 -15
- data/doc/rdoc/classes/Wee/Brush/JavascriptTag.html +147 -0
- data/doc/rdoc/classes/Wee/Brush/Page.html +17 -17
- data/doc/rdoc/classes/Wee/Brush/SelectListTag.html +25 -50
- data/doc/rdoc/classes/Wee/Brush/SelectOptionTag.html +7 -38
- data/doc/rdoc/classes/Wee/Brush/SubmitButtonTag.html +7 -7
- data/doc/rdoc/classes/Wee/Brush/TableDataTag.html +15 -16
- data/doc/rdoc/classes/Wee/Brush/TableHeaderTag.html +7 -7
- data/doc/rdoc/classes/Wee/Brush/TableRowTag.html +65 -50
- data/doc/rdoc/classes/Wee/Brush/TableTag.html +7 -7
- data/doc/rdoc/classes/Wee/Brush/TextAreaTag.html +14 -64
- data/doc/rdoc/classes/Wee/Brush/TextInputTag.html +7 -7
- data/doc/rdoc/classes/Wee/Brush/ToCallback.html +146 -0
- data/doc/rdoc/classes/Wee/CallbackRegistry.html +40 -40
- data/doc/rdoc/classes/Wee/CallbackStream.html +18 -18
- data/doc/rdoc/classes/Wee/Canvas.html +24 -24
- data/doc/rdoc/classes/Wee/Component.html +232 -149
- data/doc/rdoc/classes/Wee/Component/OnAnswer.html +153 -0
- data/doc/rdoc/classes/Wee/Decoration.html +42 -42
- data/doc/rdoc/classes/Wee/Delegate.html +27 -27
- data/doc/rdoc/classes/Wee/ErrorResponse.html +12 -12
- data/doc/rdoc/classes/Wee/FormDecoration.html +148 -0
- data/doc/rdoc/classes/Wee/GenericResponse.html +6 -6
- data/doc/rdoc/classes/Wee/HtmlCanvas.html +296 -215
- data/doc/rdoc/classes/Wee/HtmlWriter.html +83 -81
- data/doc/rdoc/classes/Wee/LiteralMethodCallback.html +21 -16
- data/doc/rdoc/classes/Wee/MessageBox.html +180 -0
- data/doc/rdoc/classes/Wee/PageDecoration.html +30 -30
- data/doc/rdoc/classes/Wee/Presenter.html +237 -69
- data/doc/rdoc/classes/Wee/RedirectResponse.html +6 -6
- data/doc/rdoc/classes/Wee/RefreshResponse.html +6 -6
- data/doc/rdoc/classes/Wee/Request.html +18 -18
- data/doc/rdoc/classes/Wee/RequestHandler.html +43 -39
- data/doc/rdoc/classes/Wee/Response.html +24 -24
- data/doc/rdoc/classes/Wee/Session.html +746 -72
- data/doc/rdoc/classes/Wee/SimpleIdGenerator.html +18 -18
- data/doc/rdoc/classes/Wee/Snapshot.html +19 -19
- data/doc/rdoc/classes/Wee/Utils.html +138 -2
- data/doc/rdoc/classes/Wee/Utils/LRUCache.html +7 -7
- data/doc/rdoc/classes/Wee/ValueHolder.html +18 -18
- data/doc/rdoc/classes/Wee/WEBrickAdaptor.html +43 -68
- data/doc/rdoc/classes/Wee/WrapperDecoration.html +150 -0
- data/doc/rdoc/created.rid +1 -1
- data/doc/rdoc/files/README.html +29 -15
- data/doc/rdoc/files/lib/wee/adaptors/webrick_rb.html +1 -1
- data/doc/rdoc/files/lib/wee/application_rb.html +1 -1
- data/doc/rdoc/files/lib/wee/components/form_decoration_rb.html +101 -0
- data/doc/rdoc/files/lib/wee/components/messagebox_rb.html +101 -0
- data/doc/rdoc/files/lib/wee/components/page_decoration_rb.html +1 -1
- data/doc/rdoc/files/lib/wee/components/wrapper_decoration_rb.html +101 -0
- data/doc/rdoc/files/lib/wee/components_rb.html +4 -1
- data/doc/rdoc/files/lib/wee/continuation/core/component_rb.html +101 -0
- data/doc/rdoc/files/lib/wee/continuation/session_rb.html +110 -0
- data/doc/rdoc/files/lib/wee/continuation_rb.html +116 -0
- data/doc/rdoc/files/lib/wee/core/callback_rb.html +1 -1
- data/doc/rdoc/files/lib/wee/core/component_rb.html +1 -1
- data/doc/rdoc/files/lib/wee/core/presenter_rb.html +1 -1
- data/doc/rdoc/files/lib/wee/core_rb.html +3 -3
- data/doc/rdoc/files/lib/wee/databases/og_rb.html +108 -0
- data/doc/rdoc/files/lib/wee/renderer/html/brushes_rb.html +1 -1
- data/doc/rdoc/files/lib/wee/renderer/html/canvas_rb.html +1 -1
- data/doc/rdoc/files/lib/wee/renderer/html/writer_rb.html +1 -1
- data/doc/rdoc/files/lib/wee/requesthandler_rb.html +1 -1
- data/doc/rdoc/files/lib/wee/session_rb.html +1 -2
- data/doc/rdoc/files/lib/wee/utils/autoreload_rb.html +1 -1
- data/doc/rdoc/files/lib/wee/utils/cache_rb.html +1 -1
- data/doc/rdoc/files/lib/wee/utils/helper_rb.html +1 -8
- data/doc/rdoc/files/lib/wee/utils_rb.html +110 -0
- data/doc/rdoc/files/lib/wee_rb.html +1 -1
- data/doc/rdoc/fr_class_index.html +11 -1
- data/doc/rdoc/fr_file_index.html +8 -0
- data/doc/rdoc/fr_method_index.html +269 -228
- data/examples/calculator.rb +69 -0
- data/examples/calendar.rb +5 -17
- data/examples/example.rb +2 -2
- data/examples/hw.rb +17 -0
- data/examples/live-update.rb +45 -0
- data/examples/og-test.rb +51 -0
- data/lib/wee.rb +1 -1
- data/lib/wee/adaptors/webrick.rb +2 -0
- data/lib/wee/application.rb +16 -0
- data/lib/wee/components.rb +3 -0
- data/lib/wee/components/form_decoration.rb +7 -0
- data/{test → lib/wee}/components/messagebox.rb +1 -1
- data/lib/wee/components/page_decoration.rb +5 -5
- data/lib/wee/components/wrapper_decoration.rb +7 -0
- data/lib/wee/continuation.rb +5 -0
- data/lib/wee/continuation/core/component.rb +55 -0
- data/lib/wee/continuation/session.rb +217 -0
- data/lib/wee/core/callback.rb +11 -6
- data/lib/wee/core/component.rb +45 -33
- data/lib/wee/core/presenter.rb +68 -0
- data/lib/wee/databases/og.rb +114 -0
- data/lib/wee/renderer/html/brushes.rb +179 -98
- data/lib/wee/renderer/html/canvas.rb +37 -13
- data/lib/wee/renderer/html/writer.rb +34 -32
- data/lib/wee/requesthandler.rb +6 -3
- data/lib/wee/session.rb +73 -54
- data/lib/wee/utils.rb +5 -0
- data/lib/wee/utils/autoreload.rb +1 -1
- data/lib/wee/utils/cache.rb +0 -2
- data/lib/wee/utils/helper.rb +40 -8
- data/test/components/calltest-cont.rb +16 -0
- data/test/components/calltest.rb +15 -10
- data/test/stress.rb +31 -28
- data/test/stress_and_measure.rb +53 -0
- data/test/stressed_application.rb +15 -0
- data/test/test_html_writer.rb +9 -4
- metadata +236 -195
- data/benchmark/bench.sh +0 -24
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'wee'
|
2
|
+
|
3
|
+
class RpnCalculator < Wee::Component
|
4
|
+
def initialize
|
5
|
+
super()
|
6
|
+
add_decoration(Wee::FormDecoration.new)
|
7
|
+
|
8
|
+
@number_stack = []
|
9
|
+
@input = ""
|
10
|
+
end
|
11
|
+
|
12
|
+
def render
|
13
|
+
# the number stack
|
14
|
+
|
15
|
+
r.ul { @number_stack.each {|num| r.li(num)} }
|
16
|
+
|
17
|
+
# the display
|
18
|
+
|
19
|
+
r.text_input.value(@input).readonly
|
20
|
+
|
21
|
+
r.space
|
22
|
+
|
23
|
+
r.submit_button.value("Enter").callback {
|
24
|
+
@number_stack << @input.to_f
|
25
|
+
@input = ""
|
26
|
+
}
|
27
|
+
|
28
|
+
r.break
|
29
|
+
|
30
|
+
# the number buttons
|
31
|
+
|
32
|
+
(0..9).each {|num|
|
33
|
+
r.submit_button.value(num).callback { @input << num.to_s }
|
34
|
+
}
|
35
|
+
|
36
|
+
# the decimal point
|
37
|
+
|
38
|
+
r.submit_button.value(".").disabled(@input.include?(".")).callback {
|
39
|
+
@input << "."
|
40
|
+
}
|
41
|
+
|
42
|
+
# binary operators
|
43
|
+
|
44
|
+
['+', '-', '*', '/'].each { |op|
|
45
|
+
r.submit_button.value(op).callback {
|
46
|
+
unless @input.empty?
|
47
|
+
@number_stack << @input.to_f
|
48
|
+
@input = ""
|
49
|
+
end
|
50
|
+
r2, r1 = @number_stack.pop, @number_stack.pop
|
51
|
+
@number_stack.push(r1.send(op, r2))
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if __FILE__ == $0
|
59
|
+
require 'wee/adaptors/webrick'
|
60
|
+
require 'wee/utils'
|
61
|
+
|
62
|
+
app = Wee::Utils.app_for {
|
63
|
+
comp = RpnCalculator.new
|
64
|
+
comp.add_decoration(Wee::PageDecoration.new('RPN Calculator'))
|
65
|
+
comp
|
66
|
+
}
|
67
|
+
|
68
|
+
Wee::WEBrickAdaptor.register('/calc' => app).start
|
69
|
+
end
|
data/examples/calendar.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
$LOAD_PATH.unshift << "../lib"
|
4
4
|
require 'wee'
|
5
5
|
require 'wee/adaptors/webrick'
|
6
|
-
require 'wee/utils
|
6
|
+
require 'wee/utils'
|
7
7
|
require 'date'
|
8
8
|
|
9
9
|
class Date
|
@@ -184,7 +184,7 @@ class MiniCalendar < Wee::Component
|
|
184
184
|
#
|
185
185
|
def render_header
|
186
186
|
r.table_row do
|
187
|
-
r.table_header.colspan(4).with { r.
|
187
|
+
r.table_header.colspan(4).with { r.encode_text(month_heading) }
|
188
188
|
r.table_header { r.anchor.callback { go_prev }.with(prev_month_abbr) }
|
189
189
|
r.table_header { r.anchor.callback { go_next }.with(next_month_abbr) }
|
190
190
|
r.table_header { browse? ? r.space : r.anchor.callback { back }.style('color: black').with('X') }
|
@@ -194,7 +194,7 @@ class MiniCalendar < Wee::Component
|
|
194
194
|
# Render Calendar footer
|
195
195
|
#
|
196
196
|
def render_footer
|
197
|
-
r.table_row { r.table_header.colspan(7).with { r.
|
197
|
+
r.table_row { r.table_header.colspan(7).with { r.encode_text(today_string) } }
|
198
198
|
end
|
199
199
|
|
200
200
|
# Render Calendar
|
@@ -316,7 +316,7 @@ if __FILE__ == $0
|
|
316
316
|
#
|
317
317
|
def render_icon
|
318
318
|
icon = 'http://www.softcomplex.com/products/tigra_calendar/img/cal.gif'
|
319
|
-
r.
|
319
|
+
r.image.src(icon).width(16).height(16).border(0).alt('Calendar')
|
320
320
|
end
|
321
321
|
|
322
322
|
# Render Calendar demo
|
@@ -349,18 +349,6 @@ if __FILE__ == $0
|
|
349
349
|
end
|
350
350
|
end
|
351
351
|
|
352
|
-
|
353
|
-
def initialize
|
354
|
-
super do
|
355
|
-
self.root_component = CustomCalendarDemo.new
|
356
|
-
self.page_store = Wee::Utils::LRUCache.new(10) # backtrack up to 10 pages
|
357
|
-
end
|
358
|
-
end
|
359
|
-
end
|
360
|
-
|
361
|
-
app = Wee::Application.new {|app|
|
362
|
-
app.default_request_handler { MySession.new }
|
363
|
-
app.id_generator = Wee::SimpleIdGenerator.new(rand(1_000_000))
|
364
|
-
}
|
352
|
+
app = Wee::Utils.app_for(CustomCalendarDemo)
|
365
353
|
Wee::WEBrickAdaptor.register('/app' => app).start
|
366
354
|
end
|
data/examples/example.rb
CHANGED
@@ -204,6 +204,6 @@ end
|
|
204
204
|
|
205
205
|
if __FILE__ == $0
|
206
206
|
require 'wee/adaptors/webrick'
|
207
|
-
require 'wee/utils
|
208
|
-
Wee::WEBrickAdaptor.register('/app' => Wee::
|
207
|
+
require 'wee/utils'
|
208
|
+
Wee::WEBrickAdaptor.register('/app' => Wee::Utils.app_for(MainPage)).start
|
209
209
|
end
|
data/examples/hw.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'wee'
|
3
|
+
require 'wee/adaptors/webrick'
|
4
|
+
require 'wee/utils'
|
5
|
+
|
6
|
+
class HelloWorld < Wee::Component
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
add_decoration(Wee::PageDecoration.new("Hello World"))
|
10
|
+
end
|
11
|
+
|
12
|
+
def render
|
13
|
+
r.h1 "Hello World from Wee!"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Wee::WEBrickAdaptor.register('/app' => Wee::Utils.app_for(HelloWorld)).start
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'wee'
|
3
|
+
require 'wee/adaptors/webrick'
|
4
|
+
require 'wee/utils'
|
5
|
+
|
6
|
+
class LiveUpdateTest < Wee::Component
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
add_decoration(Wee::PageDecoration.new("Hello World"))
|
10
|
+
@live_updates = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def render
|
14
|
+
r.h1 "Hello World from Wee!"
|
15
|
+
r.anchor.callback { @live_updates += 1; do_live_update }.with('live update')
|
16
|
+
end
|
17
|
+
|
18
|
+
def render_live_update
|
19
|
+
r.page { r.text "Live-updates works! This is no. #{ @live_updates }" }
|
20
|
+
end
|
21
|
+
|
22
|
+
def do_live_update
|
23
|
+
# generate a response
|
24
|
+
response = Wee::GenericResponse.new('text/html', '')
|
25
|
+
|
26
|
+
# get the current context we are in
|
27
|
+
context = session.current_context
|
28
|
+
|
29
|
+
# a rendering context is needed to use 'r' (if you want, you can
|
30
|
+
# simply omit this and just return the response with some html/xml filled
|
31
|
+
# in.
|
32
|
+
rendering_context = Wee::RenderingContext.new(context.request,
|
33
|
+
context.response, session.current_page.callbacks,
|
34
|
+
Wee::HtmlWriter.new(response.content))
|
35
|
+
|
36
|
+
with_renderer_for(rendering_context) do
|
37
|
+
# call your own render method for the live-update
|
38
|
+
render_live_update
|
39
|
+
end
|
40
|
+
|
41
|
+
throw :wee_live_update, response
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Wee::WEBrickAdaptor.register('/app' => Wee::Utils.app_for(LiveUpdateTest)).start
|
data/examples/og-test.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# -----------------------------------------
|
2
|
+
# The datamodel
|
3
|
+
# -----------------------------------------
|
4
|
+
|
5
|
+
require 'og'
|
6
|
+
|
7
|
+
class Customer
|
8
|
+
prop_accessor :address, String, :sql => 'VARCHAR(100) NOT NULL'
|
9
|
+
prop_accessor :email, String, :sql => 'VARCHAR(50) NOT NULL'
|
10
|
+
prop_accessor :password, String, :sql => 'VARCHAR(10) NOT NULL'
|
11
|
+
end
|
12
|
+
|
13
|
+
# -----------------------------------------
|
14
|
+
# The Wee part
|
15
|
+
# -----------------------------------------
|
16
|
+
|
17
|
+
require 'wee'
|
18
|
+
|
19
|
+
class CustomerList < Wee::Component
|
20
|
+
def initialize
|
21
|
+
super()
|
22
|
+
add_decoration(Wee::PageDecoration.new("Hello World"))
|
23
|
+
|
24
|
+
add_child(@customer_list = OgScaffolder.new(Customer))
|
25
|
+
add_child(@customer_list2 = OgScaffolder.new(Customer))
|
26
|
+
end
|
27
|
+
|
28
|
+
def render
|
29
|
+
r.render @customer_list
|
30
|
+
r.render @customer_list2
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if __FILE__ == $0
|
35
|
+
require 'wee/adaptors/webrick'
|
36
|
+
require 'wee/utils'
|
37
|
+
require 'wee/databases/og'
|
38
|
+
|
39
|
+
DB_CONFIG = {
|
40
|
+
:address => "localhost",
|
41
|
+
:database => "mneumann",
|
42
|
+
:backend => "psql",
|
43
|
+
:user => "mneumann",
|
44
|
+
:password => "",
|
45
|
+
:connection_count => 10
|
46
|
+
}
|
47
|
+
|
48
|
+
app = Wee::Utils.app_for(CustomerList, :application => OgApplication, :session => OgSession)
|
49
|
+
app.db = Og::Database.new(DB_CONFIG)
|
50
|
+
Wee::WEBrickAdaptor.register('/app' => app).start
|
51
|
+
end
|
data/lib/wee.rb
CHANGED
data/lib/wee/adaptors/webrick.rb
CHANGED
data/lib/wee/application.rb
CHANGED
@@ -102,6 +102,22 @@ class Wee::Application
|
|
102
102
|
context.response = Wee::ErrorResponse.new(exn)
|
103
103
|
end
|
104
104
|
|
105
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
106
|
+
# :section: Properties
|
107
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
108
|
+
|
109
|
+
attr_accessor :properties
|
110
|
+
|
111
|
+
# Returns an "owned" property for the given +klass+.
|
112
|
+
|
113
|
+
def get_property(prop, klass)
|
114
|
+
if @properties
|
115
|
+
@properties.fetch(klass, {})[prop]
|
116
|
+
else
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
105
121
|
private
|
106
122
|
|
107
123
|
# MUST be called while holding @mutex
|
data/lib/wee/components.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
class Wee::PageDecoration < Wee::
|
1
|
+
class Wee::PageDecoration < Wee::WrapperDecoration
|
2
2
|
def initialize(title='')
|
3
3
|
@title = title
|
4
4
|
super()
|
@@ -6,9 +6,9 @@ class Wee::PageDecoration < Wee::Decoration
|
|
6
6
|
|
7
7
|
def global?() true end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
private
|
10
|
+
|
11
|
+
def render_wrapper
|
12
|
+
r.page.title(@title).with { yield }
|
13
13
|
end
|
14
14
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class Wee::Component
|
2
|
+
|
3
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
4
|
+
# :section: Call/Answer
|
5
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
# Call another component. The calling component is neither rendered nor are
|
10
|
+
# it's callbacks processed until the called component answers using method
|
11
|
+
# #answer.
|
12
|
+
#
|
13
|
+
# [+component+]
|
14
|
+
# The component to be called.
|
15
|
+
#
|
16
|
+
# <b>How it works</b>
|
17
|
+
#
|
18
|
+
# At first a continuation is created. The component to be called is then
|
19
|
+
# wrapped with an AnswerDecoration and the continuation is assigned to it's
|
20
|
+
# +on_answer+ attribute. Then a Delegate decoration is added to the calling
|
21
|
+
# component (self), which delegates to the component to be called
|
22
|
+
# (+component+). Then we unwind the calling stack back to the Session by
|
23
|
+
# throwing <i>:wee_back_to_session</i>. This means, that there is only ever
|
24
|
+
# one action callback invoked per request. When at a later point in time the
|
25
|
+
# called component invokes #answer, this will throw a <i>:wee_answer</i>
|
26
|
+
# exception which is catched in the AnswerDecoration. The AnswerDecoration
|
27
|
+
# then jumps back to the continuation we created at the beginning, and
|
28
|
+
# finally method #call returns.
|
29
|
+
#
|
30
|
+
# Note that #call returns to an "old" stack-frame from a previous request.
|
31
|
+
# That is why we throw <i>:wee_back_to_session</i> after invoking an action
|
32
|
+
# callback, and that's why only ever one is invoked. We could remove this
|
33
|
+
# limitation without problems, but then there would be a difference between
|
34
|
+
# those action callbacks that call other components and those that do not.
|
35
|
+
|
36
|
+
def call(component, return_callback=:use_continuation, *additional_args)
|
37
|
+
add_decoration(delegate = Wee::Delegate.new(component))
|
38
|
+
component.add_decoration(answer = Wee::AnswerDecoration.new)
|
39
|
+
|
40
|
+
if return_callback == :use_continuation
|
41
|
+
result = callcc {|cc|
|
42
|
+
answer.on_answer = cc
|
43
|
+
throw :wee_back_to_session
|
44
|
+
}
|
45
|
+
remove_decoration(delegate)
|
46
|
+
component.remove_decoration(answer)
|
47
|
+
return result
|
48
|
+
else
|
49
|
+
answer.on_answer = OnAnswer.new(self, component, delegate, answer,
|
50
|
+
return_callback, additional_args)
|
51
|
+
throw :wee_back_to_session
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
require 'wee/page'
|
2
|
+
require 'thread'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
class Wee::Session < Wee::RequestHandler
|
6
|
+
attr_accessor :root_component, :page_store
|
7
|
+
|
8
|
+
def self.current
|
9
|
+
sess = Thread.current[:wee_session]
|
10
|
+
raise "not in session" if sess.nil?
|
11
|
+
return sess
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(&block)
|
15
|
+
Thread.current[:wee_session] = self
|
16
|
+
|
17
|
+
@idgen = Wee::SimpleIdGenerator.new
|
18
|
+
@in_queue, @out_queue = SizedQueue.new(1), SizedQueue.new(1)
|
19
|
+
|
20
|
+
block.call(self)
|
21
|
+
|
22
|
+
raise ArgumentError, "No root component specified" if @root_component.nil?
|
23
|
+
raise ArgumentError, "No page_store specified" if @page_store.nil?
|
24
|
+
|
25
|
+
@initial_snapshot = snapshot()
|
26
|
+
|
27
|
+
start_request_response_loop
|
28
|
+
super()
|
29
|
+
ensure
|
30
|
+
Thread.current[:wee_session] = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def snapshot
|
34
|
+
@root_component.backtrack_state_chain(snap = Wee::Snapshot.new)
|
35
|
+
return snap.freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
# called by application to send the session a request
|
39
|
+
def handle_request(context)
|
40
|
+
super
|
41
|
+
|
42
|
+
# Send a request to the session. If the session is currently busy
|
43
|
+
# processing another request, this will block.
|
44
|
+
@in_queue.push(context)
|
45
|
+
|
46
|
+
# Wait for the response.
|
47
|
+
return @out_queue.pop
|
48
|
+
end
|
49
|
+
|
50
|
+
def start_request_response_loop
|
51
|
+
Thread.abort_on_exception = true
|
52
|
+
Thread.new {
|
53
|
+
Thread.current[:wee_session] = self
|
54
|
+
loop {
|
55
|
+
@context = nil
|
56
|
+
|
57
|
+
# get a request, check whether this session is alive after every 5
|
58
|
+
# seconds.
|
59
|
+
while @context.nil?
|
60
|
+
begin
|
61
|
+
Timeout.timeout(5) {
|
62
|
+
@context = @in_queue.pop
|
63
|
+
}
|
64
|
+
rescue Timeout::Error
|
65
|
+
break unless alive?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# abort thread if no longer alive
|
70
|
+
break if not alive?
|
71
|
+
|
72
|
+
raise "invalid request" if @context.nil?
|
73
|
+
|
74
|
+
begin
|
75
|
+
awake
|
76
|
+
process_request
|
77
|
+
sleep
|
78
|
+
rescue Exception => exn
|
79
|
+
@context.response = Wee::ErrorResponse.new(exn)
|
80
|
+
end
|
81
|
+
@out_queue.push(@context)
|
82
|
+
}
|
83
|
+
p "session loop terminated" if $DEBUG
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_page(snapshot)
|
88
|
+
idgen = Wee::SimpleIdGenerator.new
|
89
|
+
page = Wee::Page.new(snapshot, Wee::CallbackRegistry.new(idgen))
|
90
|
+
end
|
91
|
+
|
92
|
+
# Is called before process_request is invoked
|
93
|
+
# Can be used to setup e.g. a database connection.
|
94
|
+
def awake
|
95
|
+
end
|
96
|
+
|
97
|
+
# Is called after process_request is run
|
98
|
+
# Can be used to release e.g. a database connection.
|
99
|
+
def sleep
|
100
|
+
end
|
101
|
+
|
102
|
+
def process_request
|
103
|
+
if @context.request.page_id.nil?
|
104
|
+
|
105
|
+
# No page_id was specified in the URL. This means that we start with a
|
106
|
+
# fresh component and a fresh page_id, then redirect to render itself.
|
107
|
+
|
108
|
+
handle_new_page_view(@context, @initial_snapshot)
|
109
|
+
|
110
|
+
elsif @page = @page_store.fetch(@context.request.page_id, false)
|
111
|
+
|
112
|
+
# A valid page_id was specified and the corresponding page exists.
|
113
|
+
|
114
|
+
@page.snapshot.restore
|
115
|
+
|
116
|
+
p @context.request.fields if $DEBUG
|
117
|
+
|
118
|
+
if @context.request.fields.empty?
|
119
|
+
|
120
|
+
# No action/inputs were specified -> render page
|
121
|
+
#
|
122
|
+
# 1. Reset the action/input fields (as they are regenerated in the
|
123
|
+
# rendering process).
|
124
|
+
# 2. Render the page (respond).
|
125
|
+
# 3. Store the page back into the store
|
126
|
+
|
127
|
+
@page = create_page(@page.snapshot) # remove all action/input handlers
|
128
|
+
respond(@context, @page.callbacks) # render
|
129
|
+
@page_store[@context.request.page_id] = @page # store
|
130
|
+
|
131
|
+
else
|
132
|
+
|
133
|
+
# Actions/inputs were specified.
|
134
|
+
#
|
135
|
+
# We process the request and invoke actions/inputs. Then we generate a
|
136
|
+
# new page view.
|
137
|
+
|
138
|
+
callback_stream = Wee::CallbackStream.new(@page.callbacks, @context.request.fields)
|
139
|
+
|
140
|
+
if callback_stream.all_of_type(:action).size > 1
|
141
|
+
raise "Not allowed to specify more than one action callback"
|
142
|
+
end
|
143
|
+
|
144
|
+
live_update_response = catch(:wee_live_update) {
|
145
|
+
catch(:wee_back_to_session) {
|
146
|
+
@root_component.process_callbacks_chain(callback_stream)
|
147
|
+
}
|
148
|
+
nil
|
149
|
+
}
|
150
|
+
|
151
|
+
if live_update_response
|
152
|
+
# replace existing page with new snapshot
|
153
|
+
@page.snapshot = self.snapshot
|
154
|
+
@page_store[@context.request.page_id] = @page
|
155
|
+
@context.response = live_update_response
|
156
|
+
else
|
157
|
+
handle_new_page_view(@context)
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
else
|
163
|
+
|
164
|
+
# A page_id was specified in the URL, but there's no page for it in the
|
165
|
+
# page store. Either the page has timed out, or an invalid page_id was
|
166
|
+
# specified.
|
167
|
+
#
|
168
|
+
# TODO:: Display an "invalid page or page timed out" message, which
|
169
|
+
# forwards to /app/session-id
|
170
|
+
|
171
|
+
raise "Not yet implemented"
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def current_context
|
177
|
+
@context
|
178
|
+
end
|
179
|
+
|
180
|
+
def current_page
|
181
|
+
@page
|
182
|
+
end
|
183
|
+
|
184
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
185
|
+
# :section: Properties
|
186
|
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
187
|
+
|
188
|
+
attr_accessor :properties
|
189
|
+
|
190
|
+
# Returns an "owned" property for the given +klass+.
|
191
|
+
|
192
|
+
def get_property(prop, klass)
|
193
|
+
if @properties
|
194
|
+
@properties.fetch(klass, {})[prop]
|
195
|
+
else
|
196
|
+
nil
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def handle_new_page_view(context, snapshot=nil)
|
203
|
+
new_page_id = @idgen.next.to_s
|
204
|
+
new_page = create_page(snapshot || self.snapshot())
|
205
|
+
@page_store[new_page_id] = new_page
|
206
|
+
redirect_url = context.request.build_url(context.request.request_handler_id, new_page_id)
|
207
|
+
context.response = Wee::RedirectResponse.new(redirect_url)
|
208
|
+
end
|
209
|
+
|
210
|
+
def respond(context, callbacks)
|
211
|
+
context.response = Wee::GenericResponse.new('text/html', '')
|
212
|
+
|
213
|
+
rctx = Wee::RenderingContext.new(context.request, context.response, callbacks, Wee::HtmlWriter.new(context.response.content))
|
214
|
+
@root_component.do_render_chain(rctx)
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|