mullen-wee 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/README.rdoc +127 -0
  2. data/Rakefile +25 -0
  3. data/aWee.gemspec +26 -0
  4. data/examples/ObjectSpaceBrowser.rb +191 -0
  5. data/examples/ajax.rb +73 -0
  6. data/examples/apotomo-webhunter/main.rb +75 -0
  7. data/examples/apotomo-webhunter/public/images/bear_trap_charged.png +0 -0
  8. data/examples/apotomo-webhunter/public/images/bear_trap_snapped.png +0 -0
  9. data/examples/apotomo-webhunter/public/images/cheese.png +0 -0
  10. data/examples/apotomo-webhunter/public/images/dark_forest.jpg +0 -0
  11. data/examples/apotomo-webhunter/public/images/mouse.png +0 -0
  12. data/examples/apotomo-webhunter/public/javascripts/jquery-1.3.2.min.js +19 -0
  13. data/examples/apotomo-webhunter/public/javascripts/wee-jquery.js +19 -0
  14. data/examples/apotomo-webhunter/public/stylesheets/forest.css +33 -0
  15. data/examples/arc_challenge.rb +42 -0
  16. data/examples/arc_challenge2.rb +46 -0
  17. data/examples/cheese_task.rb +27 -0
  18. data/examples/continuations.rb +28 -0
  19. data/examples/demo.rb +135 -0
  20. data/examples/demo/calculator.rb +63 -0
  21. data/examples/demo/calendar.rb +333 -0
  22. data/examples/demo/counter.rb +38 -0
  23. data/examples/demo/editable_counter.rb +36 -0
  24. data/examples/demo/example.rb +142 -0
  25. data/examples/demo/file_upload.rb +19 -0
  26. data/examples/demo/messagebox.rb +15 -0
  27. data/examples/demo/radio.rb +33 -0
  28. data/examples/demo/window.rb +71 -0
  29. data/examples/hw.rb +11 -0
  30. data/examples/i18n/app.rb +16 -0
  31. data/examples/i18n/locale/de/app.po +25 -0
  32. data/examples/i18n/locale/en/app.po +25 -0
  33. data/examples/pager.rb +102 -0
  34. data/lib/wee.rb +109 -0
  35. data/lib/wee/application.rb +89 -0
  36. data/lib/wee/callback.rb +109 -0
  37. data/lib/wee/component.rb +363 -0
  38. data/lib/wee/decoration.rb +251 -0
  39. data/lib/wee/dialog.rb +171 -0
  40. data/lib/wee/external_resource.rb +39 -0
  41. data/lib/wee/html_brushes.rb +795 -0
  42. data/lib/wee/html_canvas.rb +254 -0
  43. data/lib/wee/html_document.rb +52 -0
  44. data/lib/wee/html_writer.rb +71 -0
  45. data/lib/wee/id_generator.rb +81 -0
  46. data/lib/wee/jquery.rb +11 -0
  47. data/lib/wee/jquery/jquery-1.3.2.min.js +19 -0
  48. data/lib/wee/jquery/wee-jquery.js +19 -0
  49. data/lib/wee/locale.rb +56 -0
  50. data/lib/wee/lru_cache.rb +91 -0
  51. data/lib/wee/presenter.rb +44 -0
  52. data/lib/wee/renderer.rb +72 -0
  53. data/lib/wee/request.rb +56 -0
  54. data/lib/wee/response.rb +68 -0
  55. data/lib/wee/rightjs.rb +11 -0
  56. data/lib/wee/rightjs/rightjs-1.5.2.min.js +9 -0
  57. data/lib/wee/rightjs/wee-rightjs.js +18 -0
  58. data/lib/wee/root_component.rb +45 -0
  59. data/lib/wee/session.rb +366 -0
  60. data/lib/wee/state.rb +102 -0
  61. data/lib/wee/task.rb +16 -0
  62. data/test/bm_render.rb +34 -0
  63. data/test/component_spec.rb +40 -0
  64. data/test/stress/plotter.rb +84 -0
  65. data/test/stress/stress_client.rb +51 -0
  66. data/test/stress/stress_local.rb +86 -0
  67. data/test/stress/stress_server.rb +83 -0
  68. data/test/test_component.rb +106 -0
  69. data/test/test_html_canvas.rb +25 -0
  70. data/test/test_html_writer.rb +32 -0
  71. data/test/test_lru_cache.rb +51 -0
  72. data/test/test_request.rb +42 -0
  73. metadata +185 -0
@@ -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
@@ -0,0 +1,16 @@
1
+ require 'wee/component'
2
+
3
+ module Wee
4
+
5
+ class Task < Component
6
+
7
+ def go
8
+ end
9
+
10
+ def render(r)
11
+ r.session.send_response(RedirectResponse.new(r.url_for_callback(method(:go))))
12
+ end
13
+
14
+ end # class Task
15
+
16
+ end # module Wee
@@ -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