wee 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|