wee 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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