wee 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. data/ChangeLog +75 -0
  2. data/README +17 -9
  3. data/Rakefile +2 -2
  4. data/TODO +20 -0
  5. data/benchmark/Makefile +14 -9
  6. data/benchmark/counter.rb +11 -30
  7. data/benchmark/report_req.rb +12 -0
  8. data/doc/rdoc/classes/Array.html +12 -12
  9. data/doc/rdoc/classes/Cache/StorageCache.html +38 -38
  10. data/doc/rdoc/classes/Cache/Strategy/CapacityBounded.html +30 -30
  11. data/doc/rdoc/classes/Cache/Strategy/LFU.html +24 -24
  12. data/doc/rdoc/classes/Cache/Strategy/LRU.html +24 -24
  13. data/doc/rdoc/classes/Cache/Strategy/Unbounded.html +24 -24
  14. data/doc/rdoc/classes/Enumerable.html +6 -6
  15. data/doc/rdoc/classes/Object.html +12 -12
  16. data/doc/rdoc/classes/OgApplication.html +126 -0
  17. data/doc/rdoc/classes/OgScaffolder.html +401 -0
  18. data/doc/rdoc/classes/OgSession.html +172 -0
  19. data/doc/rdoc/classes/String.html +12 -12
  20. data/doc/rdoc/classes/Struct.html +12 -12
  21. data/doc/rdoc/classes/Wee.html +5 -62
  22. data/doc/rdoc/classes/Wee/AnswerDecoration.html +9 -9
  23. data/doc/rdoc/classes/Wee/Application.html +107 -39
  24. data/doc/rdoc/classes/Wee/Brush.html +22 -18
  25. data/doc/rdoc/classes/Wee/Brush/ActionCallbackMixin.html +17 -11
  26. data/doc/rdoc/classes/Wee/Brush/ActionURLCallbackMixin.html +18 -10
  27. data/doc/rdoc/classes/Wee/Brush/AnchorTag.html +30 -64
  28. data/doc/rdoc/classes/Wee/Brush/FileUploadTag.html +8 -10
  29. data/doc/rdoc/classes/Wee/Brush/FormTag.html +27 -79
  30. data/doc/rdoc/classes/Wee/Brush/GenericEncodedTextBrush.html +12 -12
  31. data/doc/rdoc/classes/Wee/Brush/GenericSingleTagBrush.html +146 -0
  32. data/doc/rdoc/classes/Wee/Brush/GenericTagBrush.html +179 -65
  33. data/doc/rdoc/classes/Wee/Brush/GenericTextBrush.html +12 -12
  34. data/doc/rdoc/classes/Wee/Brush/ImageButtonTag.html +16 -18
  35. data/doc/rdoc/classes/Wee/Brush/ImageTag.html +203 -0
  36. data/doc/rdoc/classes/Wee/Brush/InputCallbackMixin.html +17 -11
  37. data/doc/rdoc/classes/Wee/Brush/InputTag.html +15 -15
  38. data/doc/rdoc/classes/Wee/Brush/JavascriptTag.html +147 -0
  39. data/doc/rdoc/classes/Wee/Brush/Page.html +17 -17
  40. data/doc/rdoc/classes/Wee/Brush/SelectListTag.html +25 -50
  41. data/doc/rdoc/classes/Wee/Brush/SelectOptionTag.html +7 -38
  42. data/doc/rdoc/classes/Wee/Brush/SubmitButtonTag.html +7 -7
  43. data/doc/rdoc/classes/Wee/Brush/TableDataTag.html +15 -16
  44. data/doc/rdoc/classes/Wee/Brush/TableHeaderTag.html +7 -7
  45. data/doc/rdoc/classes/Wee/Brush/TableRowTag.html +65 -50
  46. data/doc/rdoc/classes/Wee/Brush/TableTag.html +7 -7
  47. data/doc/rdoc/classes/Wee/Brush/TextAreaTag.html +14 -64
  48. data/doc/rdoc/classes/Wee/Brush/TextInputTag.html +7 -7
  49. data/doc/rdoc/classes/Wee/Brush/ToCallback.html +146 -0
  50. data/doc/rdoc/classes/Wee/CallbackRegistry.html +40 -40
  51. data/doc/rdoc/classes/Wee/CallbackStream.html +18 -18
  52. data/doc/rdoc/classes/Wee/Canvas.html +24 -24
  53. data/doc/rdoc/classes/Wee/Component.html +232 -149
  54. data/doc/rdoc/classes/Wee/Component/OnAnswer.html +153 -0
  55. data/doc/rdoc/classes/Wee/Decoration.html +42 -42
  56. data/doc/rdoc/classes/Wee/Delegate.html +27 -27
  57. data/doc/rdoc/classes/Wee/ErrorResponse.html +12 -12
  58. data/doc/rdoc/classes/Wee/FormDecoration.html +148 -0
  59. data/doc/rdoc/classes/Wee/GenericResponse.html +6 -6
  60. data/doc/rdoc/classes/Wee/HtmlCanvas.html +296 -215
  61. data/doc/rdoc/classes/Wee/HtmlWriter.html +83 -81
  62. data/doc/rdoc/classes/Wee/LiteralMethodCallback.html +21 -16
  63. data/doc/rdoc/classes/Wee/MessageBox.html +180 -0
  64. data/doc/rdoc/classes/Wee/PageDecoration.html +30 -30
  65. data/doc/rdoc/classes/Wee/Presenter.html +237 -69
  66. data/doc/rdoc/classes/Wee/RedirectResponse.html +6 -6
  67. data/doc/rdoc/classes/Wee/RefreshResponse.html +6 -6
  68. data/doc/rdoc/classes/Wee/Request.html +18 -18
  69. data/doc/rdoc/classes/Wee/RequestHandler.html +43 -39
  70. data/doc/rdoc/classes/Wee/Response.html +24 -24
  71. data/doc/rdoc/classes/Wee/Session.html +746 -72
  72. data/doc/rdoc/classes/Wee/SimpleIdGenerator.html +18 -18
  73. data/doc/rdoc/classes/Wee/Snapshot.html +19 -19
  74. data/doc/rdoc/classes/Wee/Utils.html +138 -2
  75. data/doc/rdoc/classes/Wee/Utils/LRUCache.html +7 -7
  76. data/doc/rdoc/classes/Wee/ValueHolder.html +18 -18
  77. data/doc/rdoc/classes/Wee/WEBrickAdaptor.html +43 -68
  78. data/doc/rdoc/classes/Wee/WrapperDecoration.html +150 -0
  79. data/doc/rdoc/created.rid +1 -1
  80. data/doc/rdoc/files/README.html +29 -15
  81. data/doc/rdoc/files/lib/wee/adaptors/webrick_rb.html +1 -1
  82. data/doc/rdoc/files/lib/wee/application_rb.html +1 -1
  83. data/doc/rdoc/files/lib/wee/components/form_decoration_rb.html +101 -0
  84. data/doc/rdoc/files/lib/wee/components/messagebox_rb.html +101 -0
  85. data/doc/rdoc/files/lib/wee/components/page_decoration_rb.html +1 -1
  86. data/doc/rdoc/files/lib/wee/components/wrapper_decoration_rb.html +101 -0
  87. data/doc/rdoc/files/lib/wee/components_rb.html +4 -1
  88. data/doc/rdoc/files/lib/wee/continuation/core/component_rb.html +101 -0
  89. data/doc/rdoc/files/lib/wee/continuation/session_rb.html +110 -0
  90. data/doc/rdoc/files/lib/wee/continuation_rb.html +116 -0
  91. data/doc/rdoc/files/lib/wee/core/callback_rb.html +1 -1
  92. data/doc/rdoc/files/lib/wee/core/component_rb.html +1 -1
  93. data/doc/rdoc/files/lib/wee/core/presenter_rb.html +1 -1
  94. data/doc/rdoc/files/lib/wee/core_rb.html +3 -3
  95. data/doc/rdoc/files/lib/wee/databases/og_rb.html +108 -0
  96. data/doc/rdoc/files/lib/wee/renderer/html/brushes_rb.html +1 -1
  97. data/doc/rdoc/files/lib/wee/renderer/html/canvas_rb.html +1 -1
  98. data/doc/rdoc/files/lib/wee/renderer/html/writer_rb.html +1 -1
  99. data/doc/rdoc/files/lib/wee/requesthandler_rb.html +1 -1
  100. data/doc/rdoc/files/lib/wee/session_rb.html +1 -2
  101. data/doc/rdoc/files/lib/wee/utils/autoreload_rb.html +1 -1
  102. data/doc/rdoc/files/lib/wee/utils/cache_rb.html +1 -1
  103. data/doc/rdoc/files/lib/wee/utils/helper_rb.html +1 -8
  104. data/doc/rdoc/files/lib/wee/utils_rb.html +110 -0
  105. data/doc/rdoc/files/lib/wee_rb.html +1 -1
  106. data/doc/rdoc/fr_class_index.html +11 -1
  107. data/doc/rdoc/fr_file_index.html +8 -0
  108. data/doc/rdoc/fr_method_index.html +269 -228
  109. data/examples/calculator.rb +69 -0
  110. data/examples/calendar.rb +5 -17
  111. data/examples/example.rb +2 -2
  112. data/examples/hw.rb +17 -0
  113. data/examples/live-update.rb +45 -0
  114. data/examples/og-test.rb +51 -0
  115. data/lib/wee.rb +1 -1
  116. data/lib/wee/adaptors/webrick.rb +2 -0
  117. data/lib/wee/application.rb +16 -0
  118. data/lib/wee/components.rb +3 -0
  119. data/lib/wee/components/form_decoration.rb +7 -0
  120. data/{test → lib/wee}/components/messagebox.rb +1 -1
  121. data/lib/wee/components/page_decoration.rb +5 -5
  122. data/lib/wee/components/wrapper_decoration.rb +7 -0
  123. data/lib/wee/continuation.rb +5 -0
  124. data/lib/wee/continuation/core/component.rb +55 -0
  125. data/lib/wee/continuation/session.rb +217 -0
  126. data/lib/wee/core/callback.rb +11 -6
  127. data/lib/wee/core/component.rb +45 -33
  128. data/lib/wee/core/presenter.rb +68 -0
  129. data/lib/wee/databases/og.rb +114 -0
  130. data/lib/wee/renderer/html/brushes.rb +179 -98
  131. data/lib/wee/renderer/html/canvas.rb +37 -13
  132. data/lib/wee/renderer/html/writer.rb +34 -32
  133. data/lib/wee/requesthandler.rb +6 -3
  134. data/lib/wee/session.rb +73 -54
  135. data/lib/wee/utils.rb +5 -0
  136. data/lib/wee/utils/autoreload.rb +1 -1
  137. data/lib/wee/utils/cache.rb +0 -2
  138. data/lib/wee/utils/helper.rb +40 -8
  139. data/test/components/calltest-cont.rb +16 -0
  140. data/test/components/calltest.rb +15 -10
  141. data/test/stress.rb +31 -28
  142. data/test/stress_and_measure.rb +53 -0
  143. data/test/stressed_application.rb +15 -0
  144. data/test/test_html_writer.rb +9 -4
  145. metadata +236 -195
  146. 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/cache'
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.encoded_text(month_heading) }
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.encoded_text(today_string) } }
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.img.src(icon).width(16).height(16).border(0).alt('Calendar')
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
- class MySession < Wee::Session
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/helper'
208
- Wee::WEBrickAdaptor.register('/app' => Wee::Helper.app_for(MainPage)).start
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Wee
2
- Version = "0.4.0"
2
+ Version = "0.5.0"
3
3
  end
4
4
 
5
5
  require 'wee/core'
@@ -19,6 +19,8 @@ require 'webrick'
19
19
  # start(:Port => 2000)
20
20
  #
21
21
 
22
+ Socket.do_not_reverse_lookup = true
23
+
22
24
  class Wee::WEBrickAdaptor < WEBrick::HTTPServlet::AbstractServlet
23
25
 
24
26
  # Convenience method
@@ -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
@@ -1 +1,4 @@
1
+ require 'wee/components/wrapper_decoration'
2
+ require 'wee/components/form_decoration'
1
3
  require 'wee/components/page_decoration'
4
+ require 'wee/components/messagebox'
@@ -0,0 +1,7 @@
1
+ class Wee::FormDecoration < Wee::WrapperDecoration
2
+ private
3
+
4
+ def render_wrapper
5
+ r.form { yield }
6
+ end
7
+ end
@@ -1,4 +1,4 @@
1
- class MessageBox < Wee::Component
1
+ class Wee::MessageBox < Wee::Component
2
2
  def initialize(text)
3
3
  super()
4
4
  @text = text
@@ -1,4 +1,4 @@
1
- class Wee::PageDecoration < Wee::Decoration
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
- def do_render(rendering_context)
10
- with_renderer_for(rendering_context) do
11
- r.page.title(@title).with { super(rendering_context) }
12
- end
9
+ private
10
+
11
+ def render_wrapper
12
+ r.page.title(@title).with { yield }
13
13
  end
14
14
  end
@@ -0,0 +1,7 @@
1
+ class Wee::WrapperDecoration < Wee::Decoration
2
+ def do_render(rendering_context)
3
+ with_renderer_for(rendering_context) do
4
+ render_wrapper { super(rendering_context) }
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ # Replace the non-continuation based implementation (which is loaded by
2
+ # default) by a continuation enabled implementation.
3
+
4
+ require 'wee/continuation/session'
5
+ require 'wee/continuation/core/component'
@@ -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