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
@@ -0,0 +1,254 @@
|
|
1
|
+
require 'wee/renderer'
|
2
|
+
|
3
|
+
module Wee
|
4
|
+
|
5
|
+
class HtmlCanvas < Renderer
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
super
|
9
|
+
@current_brush = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def close
|
13
|
+
@current_brush.close if @current_brush
|
14
|
+
@current_brush = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def nest
|
18
|
+
old_brush = @current_brush
|
19
|
+
# we don't want that Brush#close is calledas #nest
|
20
|
+
# is called from #with -> this avoids an infinite loop
|
21
|
+
@current_brush = nil
|
22
|
+
yield
|
23
|
+
@current_brush.close if @current_brush
|
24
|
+
@current_brush = old_brush
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.brush_tag(attr, klass, *args_to_new)
|
28
|
+
args_to_new = args_to_new.map {|a| a.inspect}.join(", ")
|
29
|
+
if klass.instance_method(:with).arity != 0
|
30
|
+
class_eval %{
|
31
|
+
def #{attr}(*args, &block)
|
32
|
+
handle(#{klass}.new(#{args_to_new}), *args, &block)
|
33
|
+
end
|
34
|
+
}
|
35
|
+
elsif klass.nesting?
|
36
|
+
class_eval %{
|
37
|
+
def #{attr}(&block)
|
38
|
+
handle2(#{klass}.new(#{args_to_new}), &block)
|
39
|
+
end
|
40
|
+
}
|
41
|
+
else
|
42
|
+
class_eval %{
|
43
|
+
def #{attr}
|
44
|
+
handle3(#{klass}.new(#{args_to_new}))
|
45
|
+
end
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.generic_tag(*attrs)
|
51
|
+
attrs.each {|attr| brush_tag attr, Brush::GenericTagBrush, attr }
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.generic_single_tag(*attrs)
|
55
|
+
attrs.each {|attr| brush_tag attr, Brush::GenericSingleTagBrush, attr }
|
56
|
+
end
|
57
|
+
|
58
|
+
generic_tag :html, :head, :body, :title, :label
|
59
|
+
generic_tag :h1, :h2, :h3, :h4, :h5
|
60
|
+
generic_tag :div, :span, :ul, :ol, :li, :pre
|
61
|
+
generic_single_tag :hr
|
62
|
+
|
63
|
+
brush_tag :link, Brush::LinkTag
|
64
|
+
brush_tag :table, Brush::TableTag
|
65
|
+
brush_tag :table_row, Brush::TableRowTag
|
66
|
+
brush_tag :table_data, Brush::TableDataTag
|
67
|
+
brush_tag :table_header, Brush::TableHeaderTag
|
68
|
+
brush_tag :form, Brush::FormTag
|
69
|
+
brush_tag :input, Brush::InputTag
|
70
|
+
brush_tag :hidden_input, Brush::HiddenInputTag
|
71
|
+
brush_tag :password_input, Brush::PasswordInputTag
|
72
|
+
brush_tag :text_input, Brush::TextInputTag
|
73
|
+
brush_tag :radio_button, Brush::RadioButtonTag
|
74
|
+
brush_tag :check_box, Brush::CheckboxTag; alias checkbox check_box
|
75
|
+
brush_tag :text_area, Brush::TextAreaTag
|
76
|
+
brush_tag :option, Brush::SelectOptionTag
|
77
|
+
brush_tag :submit_button, Brush::SubmitButtonTag
|
78
|
+
brush_tag :image_button, Brush::ImageButtonTag
|
79
|
+
brush_tag :file_upload, Brush::FileUploadTag
|
80
|
+
brush_tag :page, Brush::Page
|
81
|
+
brush_tag :anchor, Brush::AnchorTag
|
82
|
+
brush_tag :javascript, Brush::JavascriptTag
|
83
|
+
brush_tag :image, Brush::ImageTag
|
84
|
+
brush_tag :style, Brush::StyleTag
|
85
|
+
|
86
|
+
brush_tag :bold, Brush::GenericTagBrush, :b
|
87
|
+
brush_tag :paragraph, Brush::GenericTagBrush, :p
|
88
|
+
brush_tag :break, Brush::GenericSingleTagBrush, :br
|
89
|
+
|
90
|
+
def select_list(items, &block)
|
91
|
+
handle2(Brush::SelectListTag.new(items), &block)
|
92
|
+
end
|
93
|
+
|
94
|
+
HTML_NBSP = " ".freeze
|
95
|
+
|
96
|
+
def space(n=1)
|
97
|
+
text(HTML_NBSP*n)
|
98
|
+
end
|
99
|
+
|
100
|
+
def text(str)
|
101
|
+
@current_brush.close if @current_brush
|
102
|
+
@current_brush = nil
|
103
|
+
@document.text(str)
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
alias << text
|
108
|
+
|
109
|
+
def encode_text(str)
|
110
|
+
@current_brush.close if @current_brush
|
111
|
+
@current_brush = nil
|
112
|
+
@document.encode_text(str)
|
113
|
+
nil
|
114
|
+
end
|
115
|
+
|
116
|
+
def css(str)
|
117
|
+
style.type('text/css').with(str)
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Depends on an existing divert location :styles.
|
122
|
+
#
|
123
|
+
def render_style(component)
|
124
|
+
once(component.class) { try_divert(:styles, component.style) }
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# converts \n into <br/>
|
129
|
+
#
|
130
|
+
def multiline_text(str, encode=true)
|
131
|
+
@current_brush.close if @current_brush
|
132
|
+
@current_brush = nil
|
133
|
+
|
134
|
+
first = true
|
135
|
+
str.each_line do |line|
|
136
|
+
@document.single_tag(:br) unless first
|
137
|
+
first = false
|
138
|
+
|
139
|
+
if encode
|
140
|
+
@document.encode_text(line)
|
141
|
+
else
|
142
|
+
@document.text(line)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# Define a divert location
|
149
|
+
#
|
150
|
+
def define_divert(tag)
|
151
|
+
@document.define_divert(tag)
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# Change into an existing divert location and
|
156
|
+
# append +txt+ or the contents of +block+.
|
157
|
+
#
|
158
|
+
def divert(tag, txt=nil, &block)
|
159
|
+
@document.divert(tag, txt, &block)
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# If the divert +tag+ exists, divert, otherwise
|
164
|
+
# do nothing.
|
165
|
+
#
|
166
|
+
def try_divert(tag, txt=nil, &block)
|
167
|
+
if @document.has_divert?(tag)
|
168
|
+
divert(tag, txt, &block)
|
169
|
+
true
|
170
|
+
else
|
171
|
+
false
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# Render specific markup only once. For example style and/or
|
177
|
+
# javascript of a component which has many instances.
|
178
|
+
#
|
179
|
+
def once(tag)
|
180
|
+
return if @document.set.has_key?(tag)
|
181
|
+
@document.set[tag] = true
|
182
|
+
yield if block_given?
|
183
|
+
end
|
184
|
+
|
185
|
+
HTML_TYPE_CSS = 'text/css'.freeze
|
186
|
+
HTML_REL_STYLESHEET = 'stylesheet'.freeze
|
187
|
+
|
188
|
+
def link_css(url)
|
189
|
+
link.type(HTML_TYPE_CSS).rel(HTML_REL_STYLESHEET).href(url)
|
190
|
+
end
|
191
|
+
|
192
|
+
def new_radio_group
|
193
|
+
Wee::Brush::RadioButtonTag::RadioGroup.new(self)
|
194
|
+
end
|
195
|
+
|
196
|
+
def url_for_callback(callback, type=:action, hash=nil)
|
197
|
+
url_for_callback_id(register_callback(type, callback), hash)
|
198
|
+
end
|
199
|
+
|
200
|
+
def url_for_callback_id(callback_id, hash=nil)
|
201
|
+
if hash
|
202
|
+
build_url(hash.update(:callback_id => callback_id))
|
203
|
+
else
|
204
|
+
build_url(:callback_id => callback_id)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def build_url(*args)
|
209
|
+
@request.build_url(*args)
|
210
|
+
end
|
211
|
+
|
212
|
+
def register_callback(type, callback)
|
213
|
+
cbs = @callbacks
|
214
|
+
if cbs.respond_to?("#{type}_callbacks")
|
215
|
+
cbs.send("#{type}_callbacks").register(@current_component, callback)
|
216
|
+
else
|
217
|
+
raise
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
protected
|
222
|
+
|
223
|
+
def set_brush(brush)
|
224
|
+
brush.setup(self, @document)
|
225
|
+
|
226
|
+
@current_brush.close if @current_brush
|
227
|
+
@current_brush = brush
|
228
|
+
|
229
|
+
return brush
|
230
|
+
end
|
231
|
+
|
232
|
+
def handle(brush, *args, &block)
|
233
|
+
if block or not args.empty?
|
234
|
+
set_brush(brush)
|
235
|
+
brush.with(*args, &block)
|
236
|
+
else
|
237
|
+
set_brush(brush)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def handle2(brush, &block)
|
242
|
+
if block
|
243
|
+
set_brush(brush)
|
244
|
+
brush.with(&block)
|
245
|
+
else
|
246
|
+
set_brush(brush)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
alias handle3 set_brush
|
251
|
+
|
252
|
+
end # class HtmlCanvas
|
253
|
+
|
254
|
+
end # module Wee
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'wee/html_writer'
|
2
|
+
|
3
|
+
module Wee
|
4
|
+
|
5
|
+
#
|
6
|
+
# Represents a complete HTML document.
|
7
|
+
#
|
8
|
+
class HtmlDocument < HtmlWriter
|
9
|
+
def initialize
|
10
|
+
super([])
|
11
|
+
end
|
12
|
+
|
13
|
+
def set
|
14
|
+
@set ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_divert?(tag)
|
18
|
+
@divert and @divert[tag]
|
19
|
+
end
|
20
|
+
|
21
|
+
def define_divert(tag)
|
22
|
+
raise ArgumentError if has_divert?(tag)
|
23
|
+
@divert ||= {}
|
24
|
+
@port << (@divert[tag] = [])
|
25
|
+
end
|
26
|
+
|
27
|
+
def divert(tag, txt=nil, &block)
|
28
|
+
raise ArgumentError unless has_divert?(tag)
|
29
|
+
raise ArgumentError if txt and block
|
30
|
+
|
31
|
+
divert = @divert[tag]
|
32
|
+
|
33
|
+
if txt
|
34
|
+
divert << txt
|
35
|
+
end
|
36
|
+
|
37
|
+
if block
|
38
|
+
old_port = @port
|
39
|
+
begin
|
40
|
+
@port = divert
|
41
|
+
block.call
|
42
|
+
ensure
|
43
|
+
@port = old_port
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
@port.join
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
module Wee
|
4
|
+
|
5
|
+
#
|
6
|
+
# A class used to write out HTML documents easily.
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
#
|
10
|
+
# w = Wee::HtmlWriter.new(doc='')
|
11
|
+
# w.start_tag('html')
|
12
|
+
# w.start_tag('body')
|
13
|
+
# w.start_tag('a', 'href' => 'http://...')
|
14
|
+
# w.text('link')
|
15
|
+
# w.end_tag('a')
|
16
|
+
# w.end_tag('body')
|
17
|
+
# w.end_tag('html')
|
18
|
+
#
|
19
|
+
# p doc # => '<html><body><a href="http://...">link</a></body></html>'
|
20
|
+
#
|
21
|
+
class HtmlWriter
|
22
|
+
|
23
|
+
attr_accessor :port
|
24
|
+
|
25
|
+
def initialize(port=[])
|
26
|
+
@port = port
|
27
|
+
end
|
28
|
+
|
29
|
+
CLOSING = ">".freeze
|
30
|
+
SINGLE_CLOSING = " />".freeze
|
31
|
+
|
32
|
+
def start_tag(tag, attributes=nil, single=false)
|
33
|
+
if attributes
|
34
|
+
@port << "<#{tag}"
|
35
|
+
attributes.each {|k, v|
|
36
|
+
if v
|
37
|
+
@port << %[ #{ k }="#{ v }"]
|
38
|
+
else
|
39
|
+
@port << %[ #{ k }]
|
40
|
+
end
|
41
|
+
}
|
42
|
+
@port << (single ? SINGLE_CLOSING : CLOSING)
|
43
|
+
else
|
44
|
+
@port << (single ? "<#{tag} />" : "<#{tag}>")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def single_tag(tag, attributes=nil)
|
49
|
+
start_tag(tag, attributes, true)
|
50
|
+
end
|
51
|
+
|
52
|
+
def end_tag(tag)
|
53
|
+
@port << "</#{tag}>"
|
54
|
+
end
|
55
|
+
|
56
|
+
def text(str)
|
57
|
+
@port << str.to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
def encode_text(str)
|
61
|
+
@port << Rack::Utils.escape_html(str.to_s)
|
62
|
+
end
|
63
|
+
|
64
|
+
def write(str)
|
65
|
+
@port << str
|
66
|
+
end
|
67
|
+
alias << write
|
68
|
+
|
69
|
+
end # class HtmlWriter
|
70
|
+
|
71
|
+
end # module Wee
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Wee
|
2
|
+
|
3
|
+
#
|
4
|
+
# Abstract base class of all id generators.
|
5
|
+
#
|
6
|
+
class IdGenerator
|
7
|
+
def next
|
8
|
+
raise "subclass responsibility"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# Sequential id generator.
|
14
|
+
#
|
15
|
+
# Returned ids are guaranteed to be unique, but they are easily guessable.
|
16
|
+
#
|
17
|
+
class IdGenerator::Sequential < IdGenerator
|
18
|
+
def initialize(initial_value=0)
|
19
|
+
@value = initial_value - 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def next
|
23
|
+
(@value += 1).to_s(36)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Returned ids are unique with a very high probability and it's
|
29
|
+
# very hard to guess the next or any used id.
|
30
|
+
#
|
31
|
+
class IdGenerator::Secure < IdGenerator
|
32
|
+
|
33
|
+
require 'digest/md5'
|
34
|
+
begin
|
35
|
+
require 'securerandom'
|
36
|
+
rescue LoadError
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(salt='wee')
|
40
|
+
@salt = salt
|
41
|
+
|
42
|
+
@use_secure = false
|
43
|
+
if defined?(::SecureRandom)
|
44
|
+
begin
|
45
|
+
@use_secure = true if next_secure != next_secure
|
46
|
+
rescue NotImplementedError
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def next
|
52
|
+
pack(@use_secure ? next_secure : next_md5)
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def next_md5
|
58
|
+
now = Time.now
|
59
|
+
dig = Digest::MD5.new
|
60
|
+
dig.update(now.to_s)
|
61
|
+
dig.update(now.usec.to_s)
|
62
|
+
dig.update(rand(0).to_s)
|
63
|
+
dig.update($$.to_s)
|
64
|
+
dig.update(@salt.to_s)
|
65
|
+
dig.digest
|
66
|
+
end
|
67
|
+
|
68
|
+
def next_secure
|
69
|
+
SecureRandom.random_bytes(16)
|
70
|
+
end
|
71
|
+
|
72
|
+
def pack(str)
|
73
|
+
packed = [str].pack('m')
|
74
|
+
packed.tr!("=\r\n", '')
|
75
|
+
packed.tr!('+/', '-_')
|
76
|
+
packed
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end # module Wee
|