mullen-wee 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +127 -0
- data/Rakefile +25 -0
- data/aWee.gemspec +26 -0
- data/examples/ObjectSpaceBrowser.rb +191 -0
- data/examples/ajax.rb +73 -0
- data/examples/apotomo-webhunter/main.rb +75 -0
- data/examples/apotomo-webhunter/public/images/bear_trap_charged.png +0 -0
- data/examples/apotomo-webhunter/public/images/bear_trap_snapped.png +0 -0
- data/examples/apotomo-webhunter/public/images/cheese.png +0 -0
- data/examples/apotomo-webhunter/public/images/dark_forest.jpg +0 -0
- data/examples/apotomo-webhunter/public/images/mouse.png +0 -0
- data/examples/apotomo-webhunter/public/javascripts/jquery-1.3.2.min.js +19 -0
- data/examples/apotomo-webhunter/public/javascripts/wee-jquery.js +19 -0
- data/examples/apotomo-webhunter/public/stylesheets/forest.css +33 -0
- data/examples/arc_challenge.rb +42 -0
- data/examples/arc_challenge2.rb +46 -0
- data/examples/cheese_task.rb +27 -0
- data/examples/continuations.rb +28 -0
- data/examples/demo.rb +135 -0
- data/examples/demo/calculator.rb +63 -0
- data/examples/demo/calendar.rb +333 -0
- data/examples/demo/counter.rb +38 -0
- data/examples/demo/editable_counter.rb +36 -0
- data/examples/demo/example.rb +142 -0
- data/examples/demo/file_upload.rb +19 -0
- data/examples/demo/messagebox.rb +15 -0
- data/examples/demo/radio.rb +33 -0
- data/examples/demo/window.rb +71 -0
- data/examples/hw.rb +11 -0
- data/examples/i18n/app.rb +16 -0
- data/examples/i18n/locale/de/app.po +25 -0
- data/examples/i18n/locale/en/app.po +25 -0
- data/examples/pager.rb +102 -0
- data/lib/wee.rb +109 -0
- data/lib/wee/application.rb +89 -0
- data/lib/wee/callback.rb +109 -0
- data/lib/wee/component.rb +363 -0
- data/lib/wee/decoration.rb +251 -0
- data/lib/wee/dialog.rb +171 -0
- data/lib/wee/external_resource.rb +39 -0
- data/lib/wee/html_brushes.rb +795 -0
- data/lib/wee/html_canvas.rb +254 -0
- data/lib/wee/html_document.rb +52 -0
- data/lib/wee/html_writer.rb +71 -0
- data/lib/wee/id_generator.rb +81 -0
- data/lib/wee/jquery.rb +11 -0
- data/lib/wee/jquery/jquery-1.3.2.min.js +19 -0
- data/lib/wee/jquery/wee-jquery.js +19 -0
- data/lib/wee/locale.rb +56 -0
- data/lib/wee/lru_cache.rb +91 -0
- data/lib/wee/presenter.rb +44 -0
- data/lib/wee/renderer.rb +72 -0
- data/lib/wee/request.rb +56 -0
- data/lib/wee/response.rb +68 -0
- data/lib/wee/rightjs.rb +11 -0
- data/lib/wee/rightjs/rightjs-1.5.2.min.js +9 -0
- data/lib/wee/rightjs/wee-rightjs.js +18 -0
- data/lib/wee/root_component.rb +45 -0
- data/lib/wee/session.rb +366 -0
- data/lib/wee/state.rb +102 -0
- data/lib/wee/task.rb +16 -0
- data/test/bm_render.rb +34 -0
- data/test/component_spec.rb +40 -0
- data/test/stress/plotter.rb +84 -0
- data/test/stress/stress_client.rb +51 -0
- data/test/stress/stress_local.rb +86 -0
- data/test/stress/stress_server.rb +83 -0
- data/test/test_component.rb +106 -0
- data/test/test_html_canvas.rb +25 -0
- data/test/test_html_writer.rb +32 -0
- data/test/test_lru_cache.rb +51 -0
- data/test/test_request.rb +42 -0
- metadata +185 -0
data/lib/wee/state.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
module Wee
|
2
|
+
|
3
|
+
#
|
4
|
+
# This class is for backtracking the state of components (or
|
5
|
+
# decorations/presenters). Components that want an undo-facility to be
|
6
|
+
# implemented (triggered for example by a browsers back-button), have to
|
7
|
+
# overwrite the Component#state method. Class Wee::State simply
|
8
|
+
# represents a collection of objects from which snapshots were taken via
|
9
|
+
# methods take_snapshot.
|
10
|
+
#
|
11
|
+
class State
|
12
|
+
class Snapshot
|
13
|
+
def initialize(object)
|
14
|
+
@object = object
|
15
|
+
@snapshot = nil
|
16
|
+
@has_snapshot = false
|
17
|
+
@ivars = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def take
|
21
|
+
@snapshot = @object.take_snapshot unless @has_snapshot
|
22
|
+
@has_snapshot = true
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_ivar(ivar, value)
|
26
|
+
@ivars ||= {}
|
27
|
+
@ivars[ivar] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def restore
|
31
|
+
@object.restore_snapshot(@snapshot) if @has_snapshot
|
32
|
+
@ivars.each_pair {|k,v| @object.instance_variable_set(k, v) } if @ivars
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@snapshots = Hash.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def add(object)
|
41
|
+
(@snapshots[object.object_id] ||= Snapshot.new(object)).take
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_ivar(object, ivar, value=object.instance_variable_get(ivar))
|
45
|
+
(@snapshots[object.object_id] ||= Snapshot.new(object)).add_ivar(ivar, value)
|
46
|
+
end
|
47
|
+
|
48
|
+
alias << add
|
49
|
+
|
50
|
+
def restore
|
51
|
+
@snapshots.each_value {|snapshot| snapshot.restore}
|
52
|
+
end
|
53
|
+
end # class State
|
54
|
+
|
55
|
+
module DupReplaceSnapshotMixin
|
56
|
+
def take_snapshot
|
57
|
+
dup
|
58
|
+
end
|
59
|
+
|
60
|
+
def restore_snapshot(snap)
|
61
|
+
replace(snap)
|
62
|
+
end
|
63
|
+
end # module DupReplaceSnapshotMixin
|
64
|
+
|
65
|
+
module ObjectSnapshotMixin
|
66
|
+
def take_snapshot
|
67
|
+
snap = Hash.new
|
68
|
+
instance_variables.each do |iv|
|
69
|
+
snap[iv] = instance_variable_get(iv)
|
70
|
+
end
|
71
|
+
snap
|
72
|
+
end
|
73
|
+
|
74
|
+
def restore_snapshot(snap)
|
75
|
+
snap.each do |k,v|
|
76
|
+
instance_variable_set(k, v)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end # module ObjectSnapshotMixin
|
80
|
+
|
81
|
+
module StructSnapshotMixin
|
82
|
+
def take_snapshot
|
83
|
+
snap = Hash.new
|
84
|
+
each_pair {|k,v| snap[k] = v}
|
85
|
+
snap
|
86
|
+
end
|
87
|
+
|
88
|
+
def restore_snapshot(snap)
|
89
|
+
snap.each_pair {|k,v| send(k.to_s + "=", v)}
|
90
|
+
end
|
91
|
+
end # module StructSnapshotMixin
|
92
|
+
|
93
|
+
end # module Wee
|
94
|
+
|
95
|
+
#
|
96
|
+
# Extend base classes with snapshot functionality
|
97
|
+
#
|
98
|
+
class Object; include Wee::ObjectSnapshotMixin end
|
99
|
+
class Struct; include Wee::StructSnapshotMixin end
|
100
|
+
class Array; include Wee::DupReplaceSnapshotMixin end
|
101
|
+
class String; include Wee::DupReplaceSnapshotMixin end
|
102
|
+
class Hash; include Wee::DupReplaceSnapshotMixin end
|
data/lib/wee/task.rb
ADDED
data/test/bm_render.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#
|
2
|
+
# Render Wee::HelloWorld n-times
|
3
|
+
#
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift "./lib"
|
6
|
+
require 'rubygems'
|
7
|
+
require 'wee'
|
8
|
+
require 'rack'
|
9
|
+
|
10
|
+
class Rack::Request
|
11
|
+
def put?; get? end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Wee::HtmlWriter
|
15
|
+
def join
|
16
|
+
@port
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
root_component = Wee::HelloWorld.new
|
21
|
+
Integer(ARGV[0] || raise).times do
|
22
|
+
r = Wee::Renderer.new
|
23
|
+
r.request = Wee::Request.new({'REQUEST_METHOD' => 'GET', 'SCRIPT_NAME' => 'blah', 'PATH_INFO' => 'blubb',
|
24
|
+
'QUERY_STRING' => '_p=blah&_s=session'})
|
25
|
+
r.document = Wee::HtmlDocument.new
|
26
|
+
r.callbacks = Wee::Callbacks.new
|
27
|
+
|
28
|
+
begin
|
29
|
+
root_component.decoration.render!(r)
|
30
|
+
ensure
|
31
|
+
r.close
|
32
|
+
end
|
33
|
+
Wee::GenericResponse.new(r.document.join)
|
34
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'wee/component'
|
2
|
+
|
3
|
+
describe Wee::Component, "when first created" do
|
4
|
+
before do
|
5
|
+
@component = Wee::Component.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should have no children" do
|
9
|
+
@component.children.should be_empty
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should have no decoration" do
|
13
|
+
@component.decoration.should == @component
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe Wee::Component, "after adding one decoration" do
|
18
|
+
before do
|
19
|
+
@component = Wee::Component.new
|
20
|
+
@decoration = Wee::Decoration.new
|
21
|
+
@component.add_decoration(@decoration)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should point to the added decoration" do
|
25
|
+
@component.decoration.should == @decoration
|
26
|
+
end
|
27
|
+
|
28
|
+
it "the added decoration should point back to the component" do
|
29
|
+
@component.decoration.next.should == @component
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return decoration after removing it" do
|
33
|
+
@component.remove_decoration(@decoration).should == @decoration
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should have no decoration after removing it" do
|
37
|
+
@component.remove_decoration(@decoration)
|
38
|
+
@component.decoration.should == @component
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
class GnuPlot
|
2
|
+
def self.spawn
|
3
|
+
new(IO.popen("gnuplot", "w+"))
|
4
|
+
end
|
5
|
+
|
6
|
+
def initialize(port)
|
7
|
+
@port = port
|
8
|
+
end
|
9
|
+
|
10
|
+
def plot(datasets)
|
11
|
+
@port << "plot"
|
12
|
+
datasets.each_with_index do |h, i|
|
13
|
+
@port << (i == 0 ? ' ' : ', ')
|
14
|
+
@port << "'-' title '#{ h[:title] }' #{ h[:params] }"
|
15
|
+
end
|
16
|
+
@port << "\n"
|
17
|
+
|
18
|
+
datasets.each do |h|
|
19
|
+
@port << h[:data].map{|v| v.join(" ")}.join("\n")
|
20
|
+
@port << "\ne\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def exit
|
27
|
+
@port << "exit\n"
|
28
|
+
@port.close
|
29
|
+
@port = nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class GenericPlotter
|
34
|
+
def initialize(interval, dataset_configs)
|
35
|
+
@interval = interval
|
36
|
+
@datasets = dataset_configs
|
37
|
+
@datasets.each_with_index {|cfg, i|
|
38
|
+
cfg[:params] ||= 'with lines'
|
39
|
+
cfg[:title] ||= i.to_s
|
40
|
+
cfg[:data] ||= []
|
41
|
+
}
|
42
|
+
@gnuplot = GnuPlot.spawn
|
43
|
+
end
|
44
|
+
|
45
|
+
def run
|
46
|
+
Thread.start {
|
47
|
+
@time = 0
|
48
|
+
loop do
|
49
|
+
@datasets.each do |cfg|
|
50
|
+
cfg[:proc].call(cfg[:data], @time)
|
51
|
+
end
|
52
|
+
@gnuplot.plot(@datasets)
|
53
|
+
sleep @interval
|
54
|
+
@time += @interval
|
55
|
+
end
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class ObjectPlotter < GenericPlotter
|
61
|
+
def initialize(interval, *klasses)
|
62
|
+
super(interval, klasses.map {|k|
|
63
|
+
{:title => k.to_s,# :params, 'with linespoints',
|
64
|
+
:proc => proc {|data, time| data << [time, ObjectSpace.each_object(k) {}] } }
|
65
|
+
})
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class MemoryPlotter < GenericPlotter
|
70
|
+
def initialize(interval, *pids)
|
71
|
+
super(interval, pids.map {|pid|
|
72
|
+
{:title => "pid: #{ pid }", :proc => proc {|data, time| data << [time, measure_memory(pid)] } }
|
73
|
+
})
|
74
|
+
end
|
75
|
+
|
76
|
+
# return usage of process +pid+ in kb
|
77
|
+
def measure_memory(pid=Process.pid)
|
78
|
+
["/proc/#{ pid }/status", "/compat/linux/proc/#{ pid }/status"].each {|file|
|
79
|
+
return $1.to_i if File.exists?(file) and File.read(file) =~ /^VmSize:\s*(\d+)\s*kB$/
|
80
|
+
}
|
81
|
+
mem, res = `ps -p #{ pid } -l`.split("\n").last.strip.split(/\s+/)[6..7]
|
82
|
+
return mem.to_i
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'mechanize'
|
3
|
+
|
4
|
+
class StressSession
|
5
|
+
def initialize
|
6
|
+
@agent = WWW::Mechanize.new {|a|
|
7
|
+
a.max_history = 1
|
8
|
+
}
|
9
|
+
@agent.get('http://localhost:2000/')
|
10
|
+
end
|
11
|
+
|
12
|
+
def click(val)
|
13
|
+
link = @agent.page.links.find {|l| l.node.text == val}
|
14
|
+
@agent.click(link)
|
15
|
+
end
|
16
|
+
|
17
|
+
def submit(val)
|
18
|
+
form = @agent.page.forms.first
|
19
|
+
button = form.buttons.find {|b| b.value == val}
|
20
|
+
@agent.submit(form, button)
|
21
|
+
rescue
|
22
|
+
puts "invalid"
|
23
|
+
p @agent.page
|
24
|
+
p form
|
25
|
+
sleep 5
|
26
|
+
end
|
27
|
+
|
28
|
+
def step
|
29
|
+
%w(OK Cancel).each {|b|
|
30
|
+
click('show')
|
31
|
+
submit('OK')
|
32
|
+
submit(b)
|
33
|
+
}
|
34
|
+
[%w(OK OK), %w(OK Cancel), %w(Cancel OK), %w(Cancel Cancel)].each {|b1, b2|
|
35
|
+
click('show')
|
36
|
+
submit('Cancel')
|
37
|
+
submit(b1)
|
38
|
+
submit(b2)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
if __FILE__ == $0
|
44
|
+
num_sessions = Integer(ARGV[0] || raise)
|
45
|
+
puts "num_sessions: #{num_sessions}"
|
46
|
+
|
47
|
+
sessions = (1..num_sessions).map { StressSession.new }
|
48
|
+
loop do
|
49
|
+
sessions.each {|s| s.step }
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
$LOAD_PATH.unshift "../../lib"
|
2
|
+
require 'rubygems'
|
3
|
+
require 'wee'
|
4
|
+
require 'rack/mock'
|
5
|
+
|
6
|
+
class HelloWorld < Wee::Component
|
7
|
+
|
8
|
+
class Called2 < Wee::Component
|
9
|
+
def render(r)
|
10
|
+
r.anchor.callback { answer }.with('back')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Called1 < Wee::Component
|
15
|
+
def render(r)
|
16
|
+
r.anchor.callback { callcc Called2.new; answer }.with('back')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
add_decoration(Wee::PageDecoration.new("Hello World"))
|
22
|
+
@counter = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def render(r)
|
26
|
+
r.h1 "Hello World from Wee!"
|
27
|
+
r.anchor.callback { callcc Called1.new }.with(@counter.to_s)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class StressTest
|
32
|
+
def initialize
|
33
|
+
@app = Wee::Application.new {
|
34
|
+
Wee::Session.new(HelloWorld.new, Wee::Session::ThreadSerializer.new)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def request(uri)
|
39
|
+
env = Rack::MockRequest.env_for(uri)
|
40
|
+
resp = @app.call(env)
|
41
|
+
if resp.first == 302
|
42
|
+
request(resp[1]["Location"])
|
43
|
+
else
|
44
|
+
resp.last.body.join
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def run(n=10_000, verbose=false)
|
49
|
+
next_uri = '/'
|
50
|
+
|
51
|
+
n.times do
|
52
|
+
p next_uri if verbose
|
53
|
+
body = request(next_uri)
|
54
|
+
|
55
|
+
if body =~ /href="([^"]*)"/
|
56
|
+
next_uri = $1
|
57
|
+
else
|
58
|
+
raise
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if __FILE__ == $0
|
65
|
+
if ARGV.size < 2 or ARGV.size > 3
|
66
|
+
puts %{USAGE: #$0 num_threads num_iters ["verbose"]}
|
67
|
+
exit 1
|
68
|
+
end
|
69
|
+
|
70
|
+
num_threads, num_iters, verbose = Integer(ARGV[0]), Integer(ARGV[1]), ARGV[2] == "verbose"
|
71
|
+
|
72
|
+
if verbose
|
73
|
+
$LOAD_PATH.unshift '.'
|
74
|
+
require 'plotter'
|
75
|
+
MemoryPlotter.new(5, Process.pid).run
|
76
|
+
ObjectPlotter.new(5, Object, Array, String, Hash, Bignum).run
|
77
|
+
ObjectPlotter.new(5, Thread, Continuation, Proc).run
|
78
|
+
end
|
79
|
+
|
80
|
+
app = StressTest.new
|
81
|
+
(1..num_threads).map {
|
82
|
+
Thread.new { app.run(num_iters, verbose) }
|
83
|
+
}.each {|th| th.join}
|
84
|
+
|
85
|
+
STDIN.readline if verbose
|
86
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
$LOAD_PATH.unshift '../../lib'
|
2
|
+
require 'wee'
|
3
|
+
|
4
|
+
class Wee::MessageBox < Wee::Component
|
5
|
+
def initialize(text)
|
6
|
+
@text = text
|
7
|
+
end
|
8
|
+
|
9
|
+
def render(r)
|
10
|
+
r.bold(@text)
|
11
|
+
r.form do
|
12
|
+
r.submit_button.value('OK').callback { answer true }
|
13
|
+
r.space
|
14
|
+
r.submit_button.value('Cancel').callback { answer false }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class CallTest < Wee::Component
|
20
|
+
def msgbox(msg, state=nil)
|
21
|
+
if state
|
22
|
+
call Wee::MessageBox.new(msg), &method(state)
|
23
|
+
else
|
24
|
+
call Wee::MessageBox.new(msg), &method(state)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def state1
|
29
|
+
msgbox('A', :state2)
|
30
|
+
end
|
31
|
+
|
32
|
+
def state2(res)
|
33
|
+
res ? msgbox('B') : msgbox('C', :state3)
|
34
|
+
end
|
35
|
+
|
36
|
+
def state3(res)
|
37
|
+
msgbox('D')
|
38
|
+
end
|
39
|
+
|
40
|
+
def render(r)
|
41
|
+
r.anchor.callback { state1 }.with("show")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class CallTestCC < Wee::Component
|
46
|
+
def msgbox(msg)
|
47
|
+
callcc Wee::MessageBox.new(msg)
|
48
|
+
end
|
49
|
+
|
50
|
+
def render(r)
|
51
|
+
r.anchor.callback {
|
52
|
+
if msgbox('A')
|
53
|
+
msgbox('B')
|
54
|
+
else
|
55
|
+
msgbox('C')
|
56
|
+
msgbox('D')
|
57
|
+
end
|
58
|
+
}.with("show")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if __FILE__ == $0
|
63
|
+
$LOAD_PATH.unshift '.'
|
64
|
+
require 'plotter'
|
65
|
+
MemoryPlotter.new(5, Process.pid).run
|
66
|
+
ObjectPlotter.new(5, Object, Array, String, Hash, Bignum).run
|
67
|
+
ObjectPlotter.new(5, Thread, Continuation, Proc).run
|
68
|
+
|
69
|
+
mode = ARGV[0]
|
70
|
+
page_cache_capa = Integer(ARGV[1] || 20)
|
71
|
+
|
72
|
+
puts "mode: #{mode}"
|
73
|
+
puts "capa: #{page_cache_capa}"
|
74
|
+
|
75
|
+
case mode
|
76
|
+
when 'call'
|
77
|
+
Wee.run { Wee::Session.new(CallTest.new, nil, page_cache_capa) }
|
78
|
+
when 'callcc'
|
79
|
+
Wee.run { Wee::Session.new(CallTestCC.new, Wee::Session::ThreadSerializer.new, page_cache_capa) }
|
80
|
+
else
|
81
|
+
raise
|
82
|
+
end
|
83
|
+
end
|