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/cache/cache.rb +158 -0
- data/lib/wee.rb +11 -0
- data/lib/wee/application.rb +91 -0
- data/lib/wee/component.rb +126 -0
- data/lib/wee/context.rb +12 -0
- data/lib/wee/delegate_decoration.rb +22 -0
- data/lib/wee/handler_registry.rb +89 -0
- data/lib/wee/holder.rb +14 -0
- data/lib/wee/html_canvas.rb +379 -0
- data/lib/wee/html_writer.rb +63 -0
- data/lib/wee/page.rb +1 -0
- data/lib/wee/session.rb +144 -0
- data/lib/wee/snapshot.rb +47 -0
- data/lib/wee/state_registry.rb +173 -0
- data/lib/wee/stuff.rb +29 -0
- data/lib/wee/utils/cache.rb +9 -0
- data/lib/wee/webrick.rb +15 -0
- metadata +55 -0
data/lib/wee/page.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
class Wee::Page < Struct.new(:snapshot, :handler_registry); end
|
data/lib/wee/session.rb
ADDED
@@ -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
|
data/lib/wee/snapshot.rb
ADDED
@@ -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
|
data/lib/wee/webrick.rb
ADDED
@@ -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: []
|