wee 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.
data/lib/wee/page.rb ADDED
@@ -0,0 +1 @@
1
+ class Wee::Page < Struct.new(:snapshot, :handler_registry); end
@@ -0,0 +1,144 @@
1
+ require 'wee/page'
2
+ require 'wee/state_registry'
3
+ require 'wee/handler_registry'
4
+ require 'thread'
5
+
6
+ class Wee::Session
7
+ attr_accessor :root_component, :page_store
8
+ attr_reader :state_registry
9
+
10
+ def self.current
11
+ sess = Thread.current['Wee::Session']
12
+ raise "not in session" if sess.nil?
13
+ return sess
14
+ end
15
+
16
+ def register_object_for_backtracking(obj)
17
+ @state_registry << obj
18
+ end
19
+
20
+ def initialize(&block)
21
+ Thread.current['Wee::Session'] = self
22
+
23
+ @next_page_id = 0
24
+ @mutex = Mutex.new
25
+ @state_registry = Wee::StateRegistry.new
26
+
27
+ block.call(self)
28
+
29
+ raise ArgumentError, "No root component specified" if @root_component.nil?
30
+ raise ArgumentError, "No page_store specified" if @page_store.nil?
31
+
32
+ @initial_snapshot = @state_registry.snapshot
33
+
34
+ ensure
35
+ Thread.current['Wee::Session'] = nil
36
+ end
37
+
38
+ def setup(context)
39
+ end
40
+
41
+ def handle_request(context)
42
+ Thread.current['Wee::Session'] = self
43
+
44
+ @mutex.synchronize do
45
+
46
+ setup(context)
47
+
48
+ if context.page_id.nil?
49
+
50
+ # No page_id was specified in the URL. This means that we start with a
51
+ # fresh component and a fresh page_id, then redirect to render itself.
52
+
53
+ handle_new_page_view(context, @initial_snapshot)
54
+
55
+ elsif page = @page_store.fetch(context.page_id, false)
56
+
57
+ # A valid page_id was specified and the corresponding page exists.
58
+
59
+ page.snapshot.apply unless context.resource_id
60
+
61
+ raise "invalid request URL! both request_id and handler_id given!" if context.resource_id and context.handler_id
62
+
63
+ if context.resource_id
64
+ # This is a resource request
65
+ res = page.handler_registry.get_resource(context.resource_id)
66
+
67
+ context.response.status = 200
68
+ context.response['Content-Type'] = res.content_type
69
+ context.response.body = res.content
70
+
71
+ elsif context.handler_id.nil?
72
+
73
+ # No action/inputs were specified -> render page
74
+ #
75
+ # 1. Reset the action/input fields (as they are regenerated in the
76
+ # rendering process).
77
+ # 2. Render the page (respond).
78
+ # 3. Store the page back into the store (only neccessary if page is not
79
+ # stored in memory).
80
+
81
+ page = Wee::Page.new(page.snapshot, Wee::HandlerRegistry.new) # remove all action/input handlers
82
+ context.handler_registry = page.handler_registry
83
+ respond(context.freeze) # render
84
+ @page_store[context.page_id] = page # store
85
+
86
+ else
87
+
88
+ # Actions/inputs were specified.
89
+ #
90
+ # We process the request and invoke actions/inputs. Then we generate a
91
+ # new page view.
92
+
93
+ context.handler_registry = page.handler_registry
94
+ @root_component.decoration.process_request(context.freeze)
95
+ handle_new_page_view(context)
96
+
97
+ end
98
+
99
+ else
100
+
101
+ # A page_id was specified in the URL, but there's no page for it in the
102
+ # page store. Either the page has timed out, or an invalid page_id was
103
+ # specified.
104
+ #
105
+ # TODO:: Display an "invalid page or page timed out" message, which
106
+ # forwards to /app/session-id
107
+
108
+ raise "Not yet implemented"
109
+
110
+ end
111
+
112
+ end # mutex
113
+
114
+ ensure
115
+ Thread.current['Wee::Session'] = nil
116
+ end
117
+
118
+ private
119
+
120
+ def handle_new_page_view(context, snapshot=nil)
121
+ new_page_id = create_new_page_id()
122
+ new_page = Wee::Page.new(snapshot || @state_registry.snapshot, Wee::HandlerRegistry.new)
123
+ @page_store[new_page_id] = new_page
124
+
125
+ redirect_url = "#{ context.application.path }/s:#{ context.session_id }/p:#{ new_page_id }"
126
+ context.response.set_redirect(WEBrick::HTTPStatus::MovedPermanently, redirect_url)
127
+ end
128
+
129
+ def respond(context)
130
+ context.response.status = 200
131
+ context.response['Content-Type'] = 'text/html'
132
+
133
+ rctx = Wee::RenderingContext.new(context, Wee::HtmlWriter.new(context.response.body))
134
+ renderer = Wee::HtmlCanvas.new(rctx)
135
+ @root_component.render_on(renderer)
136
+ end
137
+
138
+ def create_new_page_id
139
+ @next_page_id.to_s
140
+ ensure
141
+ @next_page_id += 1
142
+ end
143
+
144
+ end
@@ -0,0 +1,47 @@
1
+ class Object
2
+ def take_snapshot
3
+ snap = Hash.new
4
+ instance_variables.each do |iv|
5
+ snap[iv] = instance_variable_get(iv)
6
+ end
7
+ snap
8
+ end
9
+
10
+ def apply_snapshot(snap)
11
+ instance_variables.each do |iv|
12
+ instance_variable_set(iv, snap[iv])
13
+ end
14
+ end
15
+ end
16
+
17
+ class Array
18
+ def take_snapshot
19
+ dup
20
+ end
21
+
22
+ def apply_snapshot(snap)
23
+ replace(snap)
24
+ end
25
+ end
26
+
27
+ class String
28
+ def take_snapshot
29
+ dup
30
+ end
31
+
32
+ def apply_snapshot(snap)
33
+ replace(snap)
34
+ end
35
+ end
36
+
37
+ class Struct
38
+ def take_snapshot
39
+ snap = Hash.new
40
+ each_pair {|k,v| snap[k] = v}
41
+ snap
42
+ end
43
+
44
+ def apply_snapshot(snap)
45
+ snap.each_pair {|k,v| send(k.to_s + "=", v)}
46
+ end
47
+ end
@@ -0,0 +1,173 @@
1
+ require 'thread'
2
+ require 'set'
3
+ require 'wee/snapshot'
4
+
5
+ class Wee::StateRegistry
6
+ def initialize
7
+ @registered_objects = Hash.new # { oid => Set:{snap_oid1, snap_oid2}
8
+ @snap_to_oid_map = Hash.new # { snap_oid => Set:{oid1, oid2} }
9
+
10
+ @finalizer_snap = proc {|snap_oid|
11
+ Thread.exclusive do
12
+ @snap_to_oid_map[snap_oid].each do |oid|
13
+ if r = @registered_objects[oid]
14
+ r.delete(snap_oid)
15
+ end
16
+ end
17
+ @snap_to_oid_map.delete(snap_oid)
18
+ end
19
+ }
20
+
21
+ @finalizer_obj = proc {|oid|
22
+ Thread.exclusive do
23
+ (@registered_objects.delete(oid) || []).each do |snap_oid|
24
+ with_object(snap_oid) { |snap|
25
+ snap.delete(oid)
26
+ @snap_to_oid_map[snap_oid].delete(oid)
27
+ }
28
+ end
29
+ end
30
+ }
31
+ end
32
+
33
+ def marshal_load(dump)
34
+ initialize
35
+ objs, snaps = dump
36
+
37
+ objs.each do |obj|
38
+ register(obj)
39
+ end
40
+
41
+ snaps.each do |snap|
42
+ set = (@snap_to_oid_map[snap.object_id] ||= Set.new)
43
+
44
+ snap.each do |oid, hash|
45
+ set.add(oid)
46
+ @registered_objects[oid].add(snap.object_id)
47
+ end
48
+
49
+ ObjectSpace.define_finalizer(snap, @finalizer_snap)
50
+ end
51
+ end
52
+
53
+ # TODO: should do a GC before marshalling?!
54
+ # NOTE: we have to marshal the @registered_objects too, as we might have not
55
+ # yet taken any snapshot
56
+ def marshal_dump
57
+ objs = []
58
+ snaps = []
59
+
60
+ each_object {|obj| objs << obj}
61
+ each_snapshot { |snap| snaps << snap }
62
+
63
+ [objs, snaps]
64
+ end
65
+
66
+ def snapshot
67
+ snap = Snapshot.new
68
+ set = (@snap_to_oid_map[snap.object_id] ||= Set.new)
69
+
70
+ each_object do |obj|
71
+ snap.add_object(obj)
72
+ set.add(obj.object_id)
73
+ @registered_objects[obj.object_id].add(snap.object_id)
74
+ end
75
+
76
+ ObjectSpace.define_finalizer(snap, @finalizer_snap)
77
+
78
+ return snap
79
+ end
80
+
81
+ def register(obj)
82
+ @registered_objects[obj.object_id] ||= Set.new
83
+ ObjectSpace.define_finalizer(obj, @finalizer_obj)
84
+ end
85
+
86
+ alias << register
87
+
88
+ def each_object(&block)
89
+ Thread.exclusive do
90
+ @registered_objects.each_key do |oid|
91
+ with_object(oid, &block)
92
+ end
93
+ end
94
+ end
95
+
96
+ def each_snapshot(&block)
97
+ Thread.exclusive do
98
+ @snap_to_oid_map.each_key do |oid|
99
+ with_object(oid, &block)
100
+ end
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def with_object(oid, &block)
107
+ begin
108
+ obj = ObjectSpace._id2ref(oid)
109
+ rescue RangeError
110
+ return
111
+ end
112
+ block.call(obj) if block
113
+ end
114
+
115
+ class Snapshot
116
+ def initialize
117
+ @data = Hash.new
118
+ end
119
+
120
+ def delete(key)
121
+ @data.delete(key)
122
+ end
123
+
124
+ def each(&block)
125
+ Thread.exclusive do
126
+ @data.each(&block)
127
+ end
128
+ end
129
+
130
+ def add_object(obj)
131
+ @data[obj.object_id] = obj.take_snapshot
132
+ end
133
+
134
+ def apply
135
+ each do |oid, snap|
136
+ with_object(oid) {|obj|
137
+ obj.apply_snapshot(snap)
138
+ }
139
+ end
140
+ end
141
+
142
+ def marshal_dump
143
+ # generates a { obj => {instance variables} } hash
144
+ dump = Hash.new
145
+
146
+ each do |oid, hash|
147
+ with_object(oid) {|obj| dump[obj] = hash }
148
+ end
149
+
150
+ dump
151
+ end
152
+
153
+ def marshal_load(dump)
154
+ initialize
155
+ dump.each do |obj, hash|
156
+ @data[obj.object_id] = hash
157
+ end
158
+ end
159
+
160
+ private
161
+
162
+ def with_object(oid, &block)
163
+ begin
164
+ obj = ObjectSpace._id2ref(oid)
165
+ rescue RangeError
166
+ return
167
+ end
168
+ block.call(obj) if block
169
+ end
170
+
171
+ end # class Snapshot
172
+
173
+ end
data/lib/wee/stuff.rb ADDED
@@ -0,0 +1,29 @@
1
+ class Wee::ErrorPage < Wee::Component
2
+ def initialize(msg)
3
+ @msg = msg
4
+ super()
5
+ end
6
+
7
+ def render_content_on(r)
8
+ r << "<html><head><title>Error: #{@msg}</title><head><body>Error: #{@msg}</body></html>"
9
+ end
10
+ end
11
+
12
+ def parse_url(request)
13
+ hash = {}
14
+ request.path_info.split('/').each do |part|
15
+ # we are only interested in "k:v" parts
16
+ next unless part.include?(':')
17
+
18
+ k, v = part.split(/:/, 2)
19
+ hash[k] = v
20
+ end
21
+ hash
22
+ end
23
+
24
+ # for Ruby 1.8
25
+ module Enumerable
26
+ def min_by(&block)
27
+ min {|i,j| block.call(i) <=> block.call(j) }
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ require 'cache/cache'
2
+
3
+ module Wee::Utils; end
4
+
5
+ class Wee::Utils::LRUCache < Cache::StorageCache
6
+ def initialize(capacity=20)
7
+ super(Cache::Strategy::LRU.new(capacity))
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ require 'webrick'
2
+ class Wee::Application
3
+ def start(hash=nil)
4
+ hash = {:Port => 2000}.update(hash||{})
5
+ server = WEBrick::HTTPServer.new(hash)
6
+ server.mount_proc(hash[:mount_path] || self.path) {|req, res| self.handle_request(req, res)}
7
+ trap("INT") {
8
+ trap("INT", "IGNORE")
9
+ self.shutdown
10
+ server.shutdown
11
+ exit
12
+ }
13
+ server.start
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.1
3
+ specification_version: 1
4
+ name: wee
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2004-11-02
8
+ summary: Wee is a framework for building dynamic dynamic web applications.
9
+ require_paths:
10
+ - lib
11
+ author: Michael Neumann
12
+ email: mneumann@ntecs.de
13
+ homepage: http://wee.rubyforge.org
14
+ rubyforge_project: wee
15
+ description:
16
+ autorequire: wee
17
+ default_executable:
18
+ bindir: bin
19
+ has_rdoc: false
20
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
21
+ requirements:
22
+ -
23
+ - ">"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.0.0
26
+ version:
27
+ platform: ruby
28
+ files:
29
+ - lib/wee
30
+ - lib/cache
31
+ - lib/wee.rb
32
+ - lib/wee/holder.rb
33
+ - lib/wee/utils
34
+ - lib/wee/snapshot.rb
35
+ - lib/wee/delegate_decoration.rb
36
+ - lib/wee/session.rb
37
+ - lib/wee/context.rb
38
+ - lib/wee/webrick.rb
39
+ - lib/wee/stuff.rb
40
+ - lib/wee/handler_registry.rb
41
+ - lib/wee/html_canvas.rb
42
+ - lib/wee/html_writer.rb
43
+ - lib/wee/component.rb
44
+ - lib/wee/application.rb
45
+ - lib/wee/state_registry.rb
46
+ - lib/wee/page.rb
47
+ - lib/wee/utils/cache.rb
48
+ - lib/cache/cache.rb
49
+ test_files: []
50
+ rdoc_options: []
51
+ extra_rdoc_files: []
52
+ executables: []
53
+ extensions: []
54
+ requirements: []
55
+ dependencies: []