mullen-wee 2.2.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/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
|