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.
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