therubyracer 0.10.1 → 0.10.2beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of therubyracer might be problematic. Click here for more details.
- data/ext/v8/extconf.rb +3 -2
- data/lib/v8/version.rb +1 -1
- metadata +35 -22
- data/spec/redjs/.gitignore +0 -1
- data/spec/redjs/README.txt +0 -8
- data/spec/redjs/jsapi_spec.rb +0 -922
- data/spec/redjs/loadme.js +0 -1
data/ext/v8/extconf.rb
CHANGED
data/lib/v8/version.rb
CHANGED
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: therubyracer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.10.
|
5
|
-
prerelease:
|
4
|
+
version: 0.10.2beta1
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Charles Lowell
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-08-08 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: libv8
|
17
|
-
requirement:
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ~>
|
@@ -22,10 +22,15 @@ dependencies:
|
|
22
22
|
version: 3.3.10
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements:
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 3.3.10
|
26
31
|
- !ruby/object:Gem::Dependency
|
27
32
|
name: rake
|
28
|
-
requirement:
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
29
34
|
none: false
|
30
35
|
requirements:
|
31
36
|
- - ! '>='
|
@@ -33,10 +38,15 @@ dependencies:
|
|
33
38
|
version: '0'
|
34
39
|
type: :development
|
35
40
|
prerelease: false
|
36
|
-
version_requirements:
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
37
47
|
- !ruby/object:Gem::Dependency
|
38
48
|
name: rspec
|
39
|
-
requirement:
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
40
50
|
none: false
|
41
51
|
requirements:
|
42
52
|
- - ~>
|
@@ -44,10 +54,15 @@ dependencies:
|
|
44
54
|
version: '2.0'
|
45
55
|
type: :development
|
46
56
|
prerelease: false
|
47
|
-
version_requirements:
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '2.0'
|
48
63
|
- !ruby/object:Gem::Dependency
|
49
64
|
name: rake-compiler
|
50
|
-
requirement:
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
51
66
|
none: false
|
52
67
|
requirements:
|
53
68
|
- - ! '>='
|
@@ -55,7 +70,12 @@ dependencies:
|
|
55
70
|
version: '0'
|
56
71
|
type: :development
|
57
72
|
prerelease: false
|
58
|
-
version_requirements:
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
59
79
|
description: Call javascript code and manipulate javascript objects from ruby. Call
|
60
80
|
ruby code and manipulate ruby objects from javascript.
|
61
81
|
email: cowboyd@thefrontside.net
|
@@ -155,10 +175,6 @@ files:
|
|
155
175
|
- specthread/threading_spec.rb
|
156
176
|
- thefrontside.png
|
157
177
|
- therubyracer.gemspec
|
158
|
-
- spec/redjs/.gitignore
|
159
|
-
- spec/redjs/README.txt
|
160
|
-
- spec/redjs/jsapi_spec.rb
|
161
|
-
- spec/redjs/loadme.js
|
162
178
|
homepage: http://github.com/cowboyd/therubyracer
|
163
179
|
licenses: []
|
164
180
|
post_install_message:
|
@@ -174,19 +190,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
174
190
|
version: '0'
|
175
191
|
segments:
|
176
192
|
- 0
|
177
|
-
hash:
|
193
|
+
hash: 1052494851132937733
|
178
194
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
179
195
|
none: false
|
180
196
|
requirements:
|
181
|
-
- - ! '
|
197
|
+
- - ! '>'
|
182
198
|
- !ruby/object:Gem::Version
|
183
|
-
version:
|
184
|
-
segments:
|
185
|
-
- 0
|
186
|
-
hash: -1402630877173126943
|
199
|
+
version: 1.3.1
|
187
200
|
requirements: []
|
188
201
|
rubyforge_project: therubyracer
|
189
|
-
rubygems_version: 1.8.
|
202
|
+
rubygems_version: 1.8.24
|
190
203
|
signing_key:
|
191
204
|
specification_version: 3
|
192
205
|
summary: Embed the V8 Javascript interpreter into Ruby
|
data/spec/redjs/.gitignore
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
*.rbc
|
data/spec/redjs/README.txt
DELETED
@@ -1,8 +0,0 @@
|
|
1
|
-
Rspecs for a Ruby interface to javascript.
|
2
|
-
|
3
|
-
This is useful for different javascript interpreters wanting to present a unified interface to ruby.
|
4
|
-
|
5
|
-
Currently used by
|
6
|
-
The Ruby Rhino: http://github.com/cowboyd/therubyrhino
|
7
|
-
The Ruby Racer: http://github.com/cowboyd/therubyracer
|
8
|
-
|
data/spec/redjs/jsapi_spec.rb
DELETED
@@ -1,922 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
require "#{File.dirname(__FILE__)}/../redjs_helper.rb"
|
3
|
-
|
4
|
-
describe "Ruby Javascript API" do
|
5
|
-
|
6
|
-
describe "Basic Evaluation" do
|
7
|
-
|
8
|
-
before do
|
9
|
-
@cxt = Context.new
|
10
|
-
end
|
11
|
-
|
12
|
-
it "can evaluate some javascript" do
|
13
|
-
@cxt.eval("5 + 3").should == 8
|
14
|
-
end
|
15
|
-
|
16
|
-
it "can pass back null to ruby" do
|
17
|
-
@cxt.eval("null").should be_nil
|
18
|
-
end
|
19
|
-
|
20
|
-
it "can pass back undefined to ruby" do
|
21
|
-
@cxt.eval("this.undefined").should be_nil
|
22
|
-
end
|
23
|
-
|
24
|
-
it "can pass the empty string back to ruby" do
|
25
|
-
@cxt.eval("''").should == ""
|
26
|
-
end
|
27
|
-
|
28
|
-
it "can pass doubles back to ruby" do
|
29
|
-
@cxt.eval("2.5").should == 2.5
|
30
|
-
end
|
31
|
-
|
32
|
-
it "can pass fixed numbers back to ruby" do
|
33
|
-
@cxt.eval("1").should == 1
|
34
|
-
end
|
35
|
-
|
36
|
-
it "can pass boolean values back to ruby" do
|
37
|
-
@cxt.eval("true").should be(true)
|
38
|
-
@cxt.eval("false").should be(false)
|
39
|
-
end
|
40
|
-
|
41
|
-
it "treats nil and the empty string as the same thing when it comes to eval" do
|
42
|
-
@cxt.eval(nil).should == @cxt.eval('')
|
43
|
-
end
|
44
|
-
|
45
|
-
it "can pass back strings to ruby" do
|
46
|
-
@cxt['foo'] = "Hello World"
|
47
|
-
@cxt.eval("foo").should == "Hello World"
|
48
|
-
end
|
49
|
-
|
50
|
-
it "can pass back very long strings to ruby" do
|
51
|
-
lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis faucibus, diam vel pellentesque aliquet, nisl sapien molestie eros, vitae vehicula libero massa vel neque. Phasellus tempor pharetra ipsum vel venenatis. Quisque vitae nisl vitae quam mattis pellentesque et in sapien. Sed at lectus quis eros pharetra feugiat non ac neque. Vivamus lacus eros, feugiat at volutpat at, viverra id nisl. Vivamus ac dolor eleifend libero venenatis pharetra ut iaculis arcu. Donec neque nibh, vehicula non porta a, consectetur eu erat. Sed eleifend, metus vel euismod placerat, lectus lectus sollicitudin nisl, ac elementum sem quam nec dolor. In hac habitasse platea dictumst. Proin vitae suscipit orci. Suspendisse a ipsum vel lorem tempus scelerisque et vitae neque. Proin sodales, tellus sit amet consequat cursus, odio massa ultricies enim, eu fermentum velit lectus in lacus. Quisque eu porttitor diam. Nunc felis purus, facilisis non tristique ac, pulvinar nec nulla. Duis dolor risus, egestas nec tristique ac, ullamcorper cras amet."
|
52
|
-
@cxt.eval("'#{lorem}'").should == lorem
|
53
|
-
end
|
54
|
-
|
55
|
-
it "correctly handles translating strings that have non-standard characters" do
|
56
|
-
@cxt['utf8'] = "Σὲ γνωρίζω ἀπὸ τὴν κόψη"
|
57
|
-
@cxt['utf8'].should == "Σὲ γνωρίζω ἀπὸ τὴν κόψη"
|
58
|
-
@cxt.eval('var nativeUtf8 = "Σὲ γνωρίζω ἀπὸ τὴν κόψη"')
|
59
|
-
@cxt['nativeUtf8'].should == "Σὲ γνωρίζω ἀπὸ τὴν κόψη"
|
60
|
-
end
|
61
|
-
|
62
|
-
it "translates JavaScript dates properly into ruby Time objects" do
|
63
|
-
now = Time.now
|
64
|
-
@cxt.eval('new Date()').tap do |time|
|
65
|
-
time.should be_kind_of(Time)
|
66
|
-
time.year.should == now.year
|
67
|
-
time.day.should == now.day
|
68
|
-
time.month.should == now.month
|
69
|
-
time.min.should == now.min
|
70
|
-
time.sec.should == now.sec
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
it "can pass objects back to ruby" do
|
75
|
-
@cxt.eval("({foo: 'bar', baz: 'bang', '5': 5, embedded: {badda: 'bing'}})").tap do |object|
|
76
|
-
object.should_not be_nil
|
77
|
-
object['foo'].should == 'bar'
|
78
|
-
object['baz'].should == 'bang'
|
79
|
-
object['5'].should == 5
|
80
|
-
object['embedded'].tap do |embedded|
|
81
|
-
embedded.should_not be_nil
|
82
|
-
embedded['badda'].should == 'bing'
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
it "unwraps ruby objects returned by embedded ruby code to maintain referential integrity" do
|
88
|
-
Object.new.tap do |o|
|
89
|
-
@cxt['get'] = lambda {o}
|
90
|
-
@cxt.eval('get()').should be(o)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
it "always returns the same ruby object for a single javascript object" do
|
95
|
-
obj = @cxt.eval('obj = {}')
|
96
|
-
obj.should be(@cxt['obj'])
|
97
|
-
@cxt.eval('obj').should be(@cxt['obj'])
|
98
|
-
@cxt['obj'].should be(@cxt['obj'])
|
99
|
-
end
|
100
|
-
|
101
|
-
it "converts arrays to javascript" do
|
102
|
-
@cxt['a'] = [1,2,4]
|
103
|
-
@cxt.eval('var sum = 0;for (var i = 0; i < a.length; i++) {sum += a[i]}; sum').should == 7
|
104
|
-
@cxt.eval('[1,2,3]').tap do |a|
|
105
|
-
a.length.should == 3
|
106
|
-
a.to_a.should == [1,2,3]
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
it "can iterate over arrays" do
|
111
|
-
@cxt['a'] = @cxt.eval('[{num: 1},{num:2},{num:3},{num: 4}]')
|
112
|
-
a = @cxt['a']
|
113
|
-
a.inject(0) do |sum, item|
|
114
|
-
sum + item['num']
|
115
|
-
end.should == 10
|
116
|
-
|
117
|
-
end
|
118
|
-
|
119
|
-
it "converts ruby hashes to javascript objects" do
|
120
|
-
@cxt['h'] = {:foo => 'bar', :baz => 'bang', :bar => {'hello' => 'world'}}
|
121
|
-
@cxt['h']['foo'].should == 'bar'
|
122
|
-
@cxt['h']['baz'].should == 'bang'
|
123
|
-
@cxt['h']['bar']['hello'].should == 'world'
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
describe "Calling Ruby Code From Within Javascript" do
|
128
|
-
|
129
|
-
before(:each) do
|
130
|
-
@class = Class.new
|
131
|
-
@instance = @class.new
|
132
|
-
@cxt = Context.new
|
133
|
-
@cxt['puts'] = lambda {|o| puts o.inspect}
|
134
|
-
@cxt['o'] = @instance
|
135
|
-
end
|
136
|
-
|
137
|
-
it "can embed a closure into a context and call it" do
|
138
|
-
@cxt["say"] = lambda {|this, word, times| word * times}
|
139
|
-
@cxt.eval("say('Hello',2)").should == "HelloHello"
|
140
|
-
end
|
141
|
-
|
142
|
-
it "truncates the arguments passed in to match the arity of the function" do
|
143
|
-
@cxt['testing'] = lambda {|this|}
|
144
|
-
expect{@cxt.eval('testing(1,2,3)')}.should_not raise_error
|
145
|
-
@cxt['testing'] = lambda {}
|
146
|
-
expect{@cxt.eval('testing(1,2,3)')}.should_not raise_error
|
147
|
-
end
|
148
|
-
|
149
|
-
it "recognizes the same closure embedded into the same context as the same function object" do
|
150
|
-
@cxt['say'] = @cxt['declare'] = lambda {|word, times| word * times}
|
151
|
-
@cxt.eval('say == declare').should be(true)
|
152
|
-
@cxt.eval('say === declare').should be(true)
|
153
|
-
end
|
154
|
-
|
155
|
-
it "translates ruby Array to Javascript Array" do
|
156
|
-
class_eval do
|
157
|
-
def ruby_array
|
158
|
-
[]
|
159
|
-
end
|
160
|
-
end
|
161
|
-
evaljs('o.ruby_array instanceof Array').should == true
|
162
|
-
end
|
163
|
-
|
164
|
-
it "translates ruby Time to Javascript Date" do
|
165
|
-
now = Time.now
|
166
|
-
class_eval do
|
167
|
-
def ruby_time
|
168
|
-
@now
|
169
|
-
end
|
170
|
-
end
|
171
|
-
@instance.instance_variable_set(:@now, now)
|
172
|
-
evaljs('o.ruby_time instanceof Date').should == true
|
173
|
-
evaljs('o.ruby_time.getFullYear()').should == now.year
|
174
|
-
evaljs('o.ruby_time.getMonth() + 1').should == now.month
|
175
|
-
evaljs('o.ruby_time.getDate()').should == now.day
|
176
|
-
evaljs('o.ruby_time.getMinutes()').should == now.min
|
177
|
-
evaljs('o.ruby_time.getSeconds()').should == now.sec
|
178
|
-
end
|
179
|
-
|
180
|
-
it "translates ruby true to Javascript true" do
|
181
|
-
class_eval do
|
182
|
-
def bool
|
183
|
-
true
|
184
|
-
end
|
185
|
-
end
|
186
|
-
evaljs('o.bool === true').should == true
|
187
|
-
end
|
188
|
-
|
189
|
-
it "translates ruby false to Javascript false" do
|
190
|
-
class_eval do
|
191
|
-
def bool
|
192
|
-
false
|
193
|
-
end
|
194
|
-
end
|
195
|
-
evaljs('o.bool === false').should == true
|
196
|
-
end
|
197
|
-
|
198
|
-
it "can embed a ruby object into a context and call its methods" do
|
199
|
-
class_eval do
|
200
|
-
def say_hello(to)
|
201
|
-
"Hello #{to}!"
|
202
|
-
end
|
203
|
-
end
|
204
|
-
evaljs('o.say_hello("Gracie")').should == "Hello Gracie!"
|
205
|
-
end
|
206
|
-
|
207
|
-
it "recognizes object method as the same." do |variable|
|
208
|
-
class_eval do
|
209
|
-
def foo(*a);end
|
210
|
-
end
|
211
|
-
@cxt.eval('o.foo == o.foo').should be(true)
|
212
|
-
end
|
213
|
-
|
214
|
-
it "recognizes functions on objects of the same class as being the same function" do
|
215
|
-
cls = class_eval do
|
216
|
-
def foo(*a);end
|
217
|
-
self
|
218
|
-
end
|
219
|
-
@cxt['one'] = cls.new
|
220
|
-
@cxt['two'] = cls.new
|
221
|
-
@cxt.eval('one.foo === two.foo').should be(true)
|
222
|
-
#TODO: nice to have, but a bit tricky.
|
223
|
-
# @cxt.eval('one.foo === one.constructor.prototype.foo').should be(true)
|
224
|
-
end
|
225
|
-
|
226
|
-
it "can call a bound ruby method" do
|
227
|
-
five = class_eval do
|
228
|
-
def initialize(lhs)
|
229
|
-
@lhs = lhs
|
230
|
-
end
|
231
|
-
def times(rhs)
|
232
|
-
@lhs * rhs
|
233
|
-
end
|
234
|
-
new(5)
|
235
|
-
end
|
236
|
-
|
237
|
-
@cxt['timesfive'] = five.method(:times)
|
238
|
-
@cxt.eval('timesfive(3)').should == 15
|
239
|
-
end
|
240
|
-
|
241
|
-
describe "Default Ruby Object Access" do
|
242
|
-
|
243
|
-
it "can call public locally defined ruby methods" do
|
244
|
-
class_eval do
|
245
|
-
def voo(str)
|
246
|
-
"voo#{str}"
|
247
|
-
end
|
248
|
-
end
|
249
|
-
evaljs("o.voo('doo')").should == "voodoo"
|
250
|
-
end
|
251
|
-
|
252
|
-
it "reports ruby methods that do not exist as undefined" do
|
253
|
-
Context.new(:with => Object.new) do |cxt|
|
254
|
-
cxt.eval('this.foobar').should be_nil
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
it "can access methods defined in an object's superclass" do
|
259
|
-
o = Class.new.class_eval do
|
260
|
-
attr_accessor :foo
|
261
|
-
def foo
|
262
|
-
@foo ||= "FOO"
|
263
|
-
end
|
264
|
-
Class.new(self)
|
265
|
-
end.new
|
266
|
-
Context.new(:with => o) do |cxt|
|
267
|
-
cxt.eval('this.foo').should == 'FOO'
|
268
|
-
cxt.eval('this.foo = "bar!"')
|
269
|
-
cxt.eval('this.foo').should == "bar!"
|
270
|
-
end
|
271
|
-
end
|
272
|
-
|
273
|
-
it "allows a ruby object to intercept property access with []" do
|
274
|
-
Class.new.class_eval do
|
275
|
-
def [](val)
|
276
|
-
"FOO"
|
277
|
-
end
|
278
|
-
Context.new(:with => new) do |cxt|
|
279
|
-
cxt.eval('this.foo').should == "FOO"
|
280
|
-
cxt.eval('this.bar').should == "FOO"
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
it "allows a ruby object to intercept property setting with []=" do
|
286
|
-
Class.new.class_eval do
|
287
|
-
def initialize
|
288
|
-
@properties = {}
|
289
|
-
end
|
290
|
-
def [](name)
|
291
|
-
@properties[name]
|
292
|
-
end
|
293
|
-
def []=(name, value)
|
294
|
-
@properties[name] = value
|
295
|
-
end
|
296
|
-
|
297
|
-
Context.new(:with => new) do |cxt|
|
298
|
-
cxt.eval('this.foo = "bar"').should == "bar"
|
299
|
-
cxt.eval('this.foo').should == "bar"
|
300
|
-
end
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
it "allows a ruby object which intercepts property access to take a pass on intercepting the property" do
|
305
|
-
Class.new.class_eval do
|
306
|
-
def initialize
|
307
|
-
@attrs = {}
|
308
|
-
end
|
309
|
-
def [](name)
|
310
|
-
name =~ /foo/ ? @attrs[name] : yield
|
311
|
-
end
|
312
|
-
def []=(name, value)
|
313
|
-
name =~ /foo/ ? @attrs[name] = "#{value}-diddly" : yield
|
314
|
-
end
|
315
|
-
Context.new do |cxt|
|
316
|
-
cxt['foo'] = new
|
317
|
-
cxt.eval('typeof foo.bar').should == 'undefined'
|
318
|
-
cxt.eval('foo.bar = "baz"')
|
319
|
-
cxt.eval('foo.bar').should == 'baz'
|
320
|
-
cxt.eval('foo.foobar').should == nil
|
321
|
-
cxt.eval('foo.foobar = "baz"')
|
322
|
-
cxt.eval('foo.foobar').should == "baz-diddly"
|
323
|
-
end
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
it "allows a ruby object to take a pass on intercepting an indexed property" do
|
328
|
-
Class.new.class_eval do
|
329
|
-
def initialize
|
330
|
-
@a = []
|
331
|
-
end
|
332
|
-
def [](i)
|
333
|
-
i >= 5 ? @a[i] : yield
|
334
|
-
end
|
335
|
-
def []=(i, value)
|
336
|
-
i >= 5 ? @a[i] = "#{value}-diddly" : yield
|
337
|
-
end
|
338
|
-
Context.new do |cxt|
|
339
|
-
cxt['obj'] = new
|
340
|
-
cxt.eval('typeof obj[1]').should == 'undefined'
|
341
|
-
cxt.eval('obj[1] = "foo"')
|
342
|
-
cxt.eval('obj[1]').should == "foo"
|
343
|
-
cxt.eval('obj[5] = "foo"').should == "foo"
|
344
|
-
cxt.eval('obj[5]').should == "foo-diddly"
|
345
|
-
end
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
349
|
-
it "does not make the [] and []= methods visible or enumerable by default" do
|
350
|
-
Class.new.class_eval do
|
351
|
-
def [](name)
|
352
|
-
end
|
353
|
-
def []=(name, value)
|
354
|
-
end
|
355
|
-
def bar=(value)
|
356
|
-
end
|
357
|
-
Context.new do |cxt|
|
358
|
-
cxt['o'] = new
|
359
|
-
cxt.eval('o["[]"]').should == nil
|
360
|
-
cxt.eval('o["[]="]').should == nil
|
361
|
-
cxt.eval('a = new Array(); for (var i in o) {a.push(i)}')
|
362
|
-
cxt['a'].length.should == 0
|
363
|
-
end
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
|
-
it "doesn't kill the whole process if a dynamic interceptor or setter throws an exception" do
|
368
|
-
cls = Class.new.class_eval do
|
369
|
-
def [](name)
|
370
|
-
raise "BOOM!"
|
371
|
-
end
|
372
|
-
def []=(name, val)
|
373
|
-
raise "Bam!"
|
374
|
-
end
|
375
|
-
self
|
376
|
-
end
|
377
|
-
Context.new do |cxt|
|
378
|
-
cxt['foo'] = cls.new
|
379
|
-
lambda {
|
380
|
-
cxt.eval('foo.bar')
|
381
|
-
}.should raise_error
|
382
|
-
lambda {
|
383
|
-
cxt.eval('foo.bar = "baz"')
|
384
|
-
}.should raise_error
|
385
|
-
end
|
386
|
-
end
|
387
|
-
|
388
|
-
it "doesn't kill the whole process if reader or accessor throws an exception" do
|
389
|
-
cxt = Class.new.class_eval do
|
390
|
-
def foo
|
391
|
-
raise "NO GET 4 U!"
|
392
|
-
end
|
393
|
-
def foo=(val)
|
394
|
-
raise "NO SET 4 U!"
|
395
|
-
end
|
396
|
-
Context.new(:with => new)
|
397
|
-
end
|
398
|
-
lambda {
|
399
|
-
cxt.eval(this.foo)
|
400
|
-
}.should raise_error
|
401
|
-
lambda {
|
402
|
-
cxt.eval("this.foo = 'bar'")
|
403
|
-
}.should raise_error
|
404
|
-
end
|
405
|
-
|
406
|
-
it "allows access to methods defined on an objects included/extended modules (class)" do
|
407
|
-
m = Module.new.module_eval do
|
408
|
-
attr_accessor :foo
|
409
|
-
def foo
|
410
|
-
@foo ||= "FOO"
|
411
|
-
end
|
412
|
-
self
|
413
|
-
end
|
414
|
-
o = Class.new.class_eval do
|
415
|
-
include m
|
416
|
-
new
|
417
|
-
end
|
418
|
-
Context.new(:with => o) do |cxt|
|
419
|
-
cxt.eval('this.foo').should == "FOO"
|
420
|
-
cxt.eval('this.foo = "bar!"')
|
421
|
-
cxt.eval('this.foo').should == "bar!"
|
422
|
-
end
|
423
|
-
end
|
424
|
-
|
425
|
-
it "allows access to methods defined on an objects included/extended modules (instance)" do
|
426
|
-
m = Module.new.module_eval do
|
427
|
-
attr_accessor :foo
|
428
|
-
def foo
|
429
|
-
@foo ||= "FOO"
|
430
|
-
end
|
431
|
-
self
|
432
|
-
end
|
433
|
-
Object.new.tap do |o|
|
434
|
-
o.extend(m)
|
435
|
-
Context.new(:with => o) do |cxt|
|
436
|
-
cxt.eval('this.foo').should == "FOO"
|
437
|
-
cxt.eval('this.foo = "bar!"')
|
438
|
-
cxt.eval('this.foo').should == "bar!"
|
439
|
-
end
|
440
|
-
end
|
441
|
-
end
|
442
|
-
|
443
|
-
it "allows access to public singleton methods" do
|
444
|
-
Object.new.tap do |o|
|
445
|
-
class << o
|
446
|
-
attr_accessor :foo
|
447
|
-
end
|
448
|
-
def o.foo
|
449
|
-
@foo ||= "FOO"
|
450
|
-
end
|
451
|
-
Context.new(:with => o) do |cxt|
|
452
|
-
cxt.eval("this.foo").should == "FOO"
|
453
|
-
cxt.eval('this.foo = "bar!"')
|
454
|
-
cxt.eval('this.foo').should == "bar!"
|
455
|
-
end
|
456
|
-
end
|
457
|
-
end
|
458
|
-
|
459
|
-
it "does not allow access to methods defined on Object and above" do
|
460
|
-
o = Class.new.class_eval do
|
461
|
-
def foo
|
462
|
-
"FOO"
|
463
|
-
end
|
464
|
-
self.new
|
465
|
-
end
|
466
|
-
Context.new(:with => o) do |cxt|
|
467
|
-
for method in Object.public_instance_methods
|
468
|
-
cxt.eval("this['#{method}']").should be_nil
|
469
|
-
end
|
470
|
-
end
|
471
|
-
end
|
472
|
-
|
473
|
-
it "hides methods derived from Object, Kernel, etc..." do
|
474
|
-
class_eval do
|
475
|
-
def bar
|
476
|
-
end
|
477
|
-
end
|
478
|
-
evaljs("o.to_s").should be_nil
|
479
|
-
end
|
480
|
-
|
481
|
-
describe "with an integer index" do
|
482
|
-
it "allows accessing indexed properties via the []() method" do
|
483
|
-
class_eval do
|
484
|
-
def [](i)
|
485
|
-
"foo" * i
|
486
|
-
end
|
487
|
-
end
|
488
|
-
evaljs("o[3]").should == "foofoofoo"
|
489
|
-
end
|
490
|
-
it "allows setting indexed properties via the []=() method" do
|
491
|
-
class_eval do
|
492
|
-
def [](i)
|
493
|
-
@storage ||= []
|
494
|
-
@storage[i]
|
495
|
-
end
|
496
|
-
def []=(i, val)
|
497
|
-
@storage ||= []
|
498
|
-
@storage[i] = val
|
499
|
-
end
|
500
|
-
end
|
501
|
-
evaljs("o[3] = 'three'").should == 'three'
|
502
|
-
evaljs("o[3]").should == 'three'
|
503
|
-
end
|
504
|
-
|
505
|
-
it "doesn't kill the whole process if indexed interceptors throw exceptions" do
|
506
|
-
class_eval do
|
507
|
-
def [](idx)
|
508
|
-
raise "No Indexed Get For You!"
|
509
|
-
end
|
510
|
-
def []=(idx, value)
|
511
|
-
raise "No Indexed Set For You!"
|
512
|
-
end
|
513
|
-
end
|
514
|
-
lambda {
|
515
|
-
evaljs("o[1] = 'boo'")
|
516
|
-
}.should raise_error
|
517
|
-
lambda {
|
518
|
-
evaljs("o[1]")
|
519
|
-
}.should raise_error end
|
520
|
-
|
521
|
-
#TODO: I'm not sure this is warranted
|
522
|
-
#it "will enumerate indexed properties if a length property is provided"
|
523
|
-
end
|
524
|
-
|
525
|
-
end
|
526
|
-
|
527
|
-
it "will see a method that appears after the wrapper was first created" do
|
528
|
-
@cxt['o'] = @instance
|
529
|
-
class_eval do
|
530
|
-
def whiz(str)
|
531
|
-
"whiz#{str}!"
|
532
|
-
end
|
533
|
-
end
|
534
|
-
@cxt.eval("o.whiz('bang')").should == "whizbang!"
|
535
|
-
end
|
536
|
-
|
537
|
-
it "treats ruby methods that have an arity of 0 as javascript properties by default" do
|
538
|
-
class_eval do
|
539
|
-
def property
|
540
|
-
"flan!"
|
541
|
-
end
|
542
|
-
end
|
543
|
-
evaljs('o.property').should == 'flan!'
|
544
|
-
end
|
545
|
-
|
546
|
-
it "will call ruby accesssor function when setting a property from javascript" do
|
547
|
-
class_eval do
|
548
|
-
def dollars
|
549
|
-
@dollars
|
550
|
-
end
|
551
|
-
|
552
|
-
def dollars=(amount)
|
553
|
-
@dollars = amount
|
554
|
-
end
|
555
|
-
end
|
556
|
-
evaljs('o.dollars = 50')
|
557
|
-
@instance.dollars.should == 50
|
558
|
-
end
|
559
|
-
|
560
|
-
it "will accept expando properties by default for properties on ruby object that are not implemented in ruby" do
|
561
|
-
evaljs('o.five = 5; o.five').should == 5
|
562
|
-
end
|
563
|
-
|
564
|
-
it "it silently fails to replace properties which are defined on ruby objects but which are read-only" do
|
565
|
-
class_eval do
|
566
|
-
def bar
|
567
|
-
"baz"
|
568
|
-
end
|
569
|
-
end
|
570
|
-
evaljs('o.bar = "bing"; o.bar').should == "baz"
|
571
|
-
end
|
572
|
-
|
573
|
-
def evaljs(str)
|
574
|
-
@cxt.eval(str)
|
575
|
-
end
|
576
|
-
|
577
|
-
def class_eval(&body)
|
578
|
-
@class.class_eval &body
|
579
|
-
end
|
580
|
-
|
581
|
-
end
|
582
|
-
|
583
|
-
describe "Calling JavaScript Code From Within Ruby" do
|
584
|
-
|
585
|
-
before(:each) do
|
586
|
-
@cxt = Context.new
|
587
|
-
end
|
588
|
-
|
589
|
-
it "allows you to capture a reference to a javascript function and call it" do
|
590
|
-
f = @cxt.eval('(function add(lhs, rhs) {return lhs + rhs})')
|
591
|
-
f.call(1,2).should == 3
|
592
|
-
end
|
593
|
-
|
594
|
-
it "can path the 'this' object into a function as context with methodcall()" do
|
595
|
-
obj = @cxt.eval('({num: 5})')
|
596
|
-
times = @cxt.eval('(function times(num) {return this.num * num})')
|
597
|
-
times.methodcall(obj, 5).should == 25
|
598
|
-
end
|
599
|
-
|
600
|
-
it "unwraps objects that are backed by javascript objects to pass their native equivalents" do |cxt|
|
601
|
-
@cxt.eval('obj = {foo: "bar"}')
|
602
|
-
f = @cxt.eval('(function() {return this == obj})')
|
603
|
-
f.methodcall(@cxt['obj']).should be(true)
|
604
|
-
end
|
605
|
-
|
606
|
-
it "can invoke a javacript constructor and return the new object reflected into ruby" do
|
607
|
-
wrapper = @cxt.eval('(function Wrapper(value) {this.value = value})')
|
608
|
-
wrapper.new(5)['value'].should == 5
|
609
|
-
end
|
610
|
-
|
611
|
-
it "can call a javascript method directly from a ruby object" do
|
612
|
-
obj = @cxt.eval('Object').new
|
613
|
-
obj.should respond_to(:toString)
|
614
|
-
obj.toString().should == '[object Object]'
|
615
|
-
end
|
616
|
-
|
617
|
-
it "can access properties defined on a javascript object through ruby" do
|
618
|
-
obj = @cxt.eval('({str: "bar", num: 5})')
|
619
|
-
obj.str.should == "bar"
|
620
|
-
obj.num.should == 5
|
621
|
-
end
|
622
|
-
|
623
|
-
it "can set properties on the javascript object via ruby setter methods" do
|
624
|
-
obj = @cxt.eval('({str: "bar", num: 5})')
|
625
|
-
obj.str = "baz"
|
626
|
-
obj.str.should == "baz"
|
627
|
-
obj.double = proc {|i| i * 2}
|
628
|
-
obj.double.call(2).should == 4
|
629
|
-
obj.array = 1,2,3
|
630
|
-
obj.array.to_a.should == [1,2,3]
|
631
|
-
obj.array = [1,2,3]
|
632
|
-
obj.array.to_a.should == [1,2,3]
|
633
|
-
end
|
634
|
-
|
635
|
-
it "is an error to try and pass parameters to a property" do
|
636
|
-
obj = @cxt.eval('({num: 1})')
|
637
|
-
lambda {
|
638
|
-
obj.num(5)
|
639
|
-
}.should raise_error(ArgumentError)
|
640
|
-
end
|
641
|
-
end
|
642
|
-
|
643
|
-
describe "Setting up the Host Environment" do
|
644
|
-
before(:each) do
|
645
|
-
@cxt = Context.new
|
646
|
-
end
|
647
|
-
|
648
|
-
it "can eval javascript with a given ruby object as the scope." do
|
649
|
-
scope = Class.new.class_eval do
|
650
|
-
def plus(lhs, rhs)
|
651
|
-
lhs + rhs
|
652
|
-
end
|
653
|
-
|
654
|
-
def minus(lhs, rhs)
|
655
|
-
lhs - rhs
|
656
|
-
end
|
657
|
-
|
658
|
-
new
|
659
|
-
end
|
660
|
-
|
661
|
-
Context.new(:with => scope) do |cxt|
|
662
|
-
cxt.eval("plus(1,2)", "test").should == 3
|
663
|
-
cxt.eval("minus(10, 20)", "test").should == -10
|
664
|
-
cxt.eval("this").should be(scope)
|
665
|
-
end
|
666
|
-
end
|
667
|
-
|
668
|
-
it "can directly embed ruby values into javascript" do
|
669
|
-
@cxt["bar"] = 9
|
670
|
-
@cxt['foo'] = "bar"
|
671
|
-
@cxt['num'] = 3.14
|
672
|
-
@cxt['trU'] = true
|
673
|
-
@cxt['falls'] = false
|
674
|
-
@cxt.eval("bar + 10").should be(19)
|
675
|
-
@cxt.eval('foo').should == "bar"
|
676
|
-
@cxt.eval('num').should == 3.14
|
677
|
-
@cxt.eval('trU').should be(true)
|
678
|
-
@cxt.eval('falls').should be(false)
|
679
|
-
end
|
680
|
-
|
681
|
-
it "has the global object available as a javascript value" do
|
682
|
-
@cxt['foo'] = 'bar'
|
683
|
-
@cxt.scope.should be_kind_of(V8::Object)
|
684
|
-
@cxt.scope['foo'].should == 'bar'
|
685
|
-
end
|
686
|
-
|
687
|
-
it "will treat class objects as constructors by default" do
|
688
|
-
@cxt[:MyClass] = Class.new.tap do |cls|
|
689
|
-
cls.class_eval do
|
690
|
-
attr_reader :one, :two
|
691
|
-
def initialize(one, two)
|
692
|
-
@one, @two = one, two
|
693
|
-
# rputs "one: #{@one}, two: #{@two}"
|
694
|
-
end
|
695
|
-
end
|
696
|
-
end
|
697
|
-
@cxt.eval('new MyClass(1,2).one').should == 1
|
698
|
-
@cxt.eval('new MyClass(1,2).two').should == 2
|
699
|
-
end
|
700
|
-
|
701
|
-
it "exposes class properties as javascript properties on the corresponding constructor" do
|
702
|
-
@cxt[:MyClass] = Class.new.tap do |cls|
|
703
|
-
def cls.foo
|
704
|
-
"bar"
|
705
|
-
end
|
706
|
-
end
|
707
|
-
@cxt.eval('MyClass.foo').should == "bar"
|
708
|
-
end
|
709
|
-
|
710
|
-
it "unwraps reflected ruby constructor objects into their underlying ruby classes" do
|
711
|
-
@cxt['RubyObject'] = Object
|
712
|
-
@cxt['RubyObject'].should be(Object)
|
713
|
-
end
|
714
|
-
|
715
|
-
it "honors the instanceof operator for ruby instances when compared to their reflected constructors" do
|
716
|
-
@cxt['RubyObject'] = Object
|
717
|
-
@cxt['one'] = Object.new
|
718
|
-
@cxt['two'] = Object.new
|
719
|
-
@cxt.eval('one instanceof RubyObject')
|
720
|
-
@cxt.eval('two instanceof RubyObject')
|
721
|
-
@cxt.eval('RubyObject instanceof Function').should be(true)
|
722
|
-
@cxt.eval('new RubyObject() instanceof RubyObject').should be(true)
|
723
|
-
@cxt.eval('new RubyObject() instanceof Array').should be(false)
|
724
|
-
@cxt.eval('new RubyObject() instanceof Object').should be(true)
|
725
|
-
end
|
726
|
-
|
727
|
-
it "unwraps instances created by a native constructor when passing them back to ruby" do
|
728
|
-
cls = Class.new.tap do |c|
|
729
|
-
c.class_eval do
|
730
|
-
def definitely_a_product_of_this_one_off_class?
|
731
|
-
true
|
732
|
-
end
|
733
|
-
end
|
734
|
-
end
|
735
|
-
@cxt['RubyClass'] = cls
|
736
|
-
@cxt.eval('new RubyClass()').should be_definitely_a_product_of_this_one_off_class
|
737
|
-
end
|
738
|
-
|
739
|
-
it "does not allow you to call a native ruby constructor, unless that constructor has been directly embedded" do
|
740
|
-
@cxt['o'] = Class.new.new
|
741
|
-
lambda {
|
742
|
-
@cxt.eval('new (o.constructor)()')
|
743
|
-
}.should raise_error(JSError)
|
744
|
-
end
|
745
|
-
|
746
|
-
it "extends object to allow for the arbitrary execution of javascript with any object as the scope" do
|
747
|
-
Class.new.class_eval do
|
748
|
-
|
749
|
-
def initialize
|
750
|
-
@lhs = 5
|
751
|
-
end
|
752
|
-
|
753
|
-
def timesfive(rhs)
|
754
|
-
@lhs * rhs
|
755
|
-
end
|
756
|
-
|
757
|
-
new.eval_js("timesfive(6)").should == 30
|
758
|
-
end
|
759
|
-
end
|
760
|
-
|
761
|
-
# it "can limit the number of instructions that are executed in the context" do
|
762
|
-
# pending "haven't figured out how to constrain resources in V8"
|
763
|
-
# lambda {
|
764
|
-
# Context.new do |cxt|
|
765
|
-
# cxt.instruction_limit = 100 * 1000
|
766
|
-
# timeout(1) do
|
767
|
-
# cxt.eval('while (true);')
|
768
|
-
# end
|
769
|
-
# end
|
770
|
-
# }.should raise_error(RunawayScriptError)
|
771
|
-
# end
|
772
|
-
end
|
773
|
-
|
774
|
-
describe "Loading javascript source into the interpreter" do
|
775
|
-
|
776
|
-
it "can take an IO object in the eval method instead of a string" do
|
777
|
-
source = StringIO.new(<<-EOJS)
|
778
|
-
/*
|
779
|
-
* we want to have a fairly verbose function so that we can be assured tha
|
780
|
-
* we overflow the buffer size so that we see that the reader is chunking
|
781
|
-
* it's payload in at least several fragments.
|
782
|
-
*
|
783
|
-
* That's why we're wasting space here
|
784
|
-
*/
|
785
|
-
function five() {
|
786
|
-
return 5
|
787
|
-
}
|
788
|
-
foo = 'bar'
|
789
|
-
five();
|
790
|
-
EOJS
|
791
|
-
Context.new do |cxt|
|
792
|
-
cxt.eval(source, "StringIO").should == 5
|
793
|
-
cxt['foo'].should == "bar"
|
794
|
-
end
|
795
|
-
end
|
796
|
-
|
797
|
-
it "can load a file into the runtime" do
|
798
|
-
Context.new do |cxt|
|
799
|
-
cxt.load(Pathname(__FILE__).dirname.join("loadme.js")).should == "I am Legend"
|
800
|
-
end
|
801
|
-
end
|
802
|
-
end
|
803
|
-
|
804
|
-
describe "A Javascript Object Reflected Into Ruby" do
|
805
|
-
|
806
|
-
before(:each) do
|
807
|
-
@cxt = Context.new
|
808
|
-
@o = @cxt.eval("o = new Object(); o")
|
809
|
-
end
|
810
|
-
|
811
|
-
def evaljs(js)
|
812
|
-
@cxt.eval(js)
|
813
|
-
end
|
814
|
-
|
815
|
-
it "can have its properties manipulated via ruby style [] hash access" do
|
816
|
-
@o["foo"] = 'bar'
|
817
|
-
evaljs('o.foo').should == "bar"
|
818
|
-
evaljs('o.blue = "blam"')
|
819
|
-
@o["blue"].should == "blam"
|
820
|
-
end
|
821
|
-
|
822
|
-
it "doesn't matter if you use a symbol or a string to set a value" do
|
823
|
-
@o[:foo] = "bar"
|
824
|
-
@o['foo'].should == "bar"
|
825
|
-
@o['baz'] = "bang"
|
826
|
-
@o[:baz].should == "bang"
|
827
|
-
end
|
828
|
-
|
829
|
-
it "returns nil when the value is null, null, or not defined" do
|
830
|
-
@o[:foo].should be_nil
|
831
|
-
end
|
832
|
-
|
833
|
-
it "traverses the prototype chain when hash accessing properties from the ruby object" do
|
834
|
-
Context.new do |cxt|
|
835
|
-
cxt.eval(<<EOJS)['bar'].should == "baz"
|
836
|
-
function Foo() {}
|
837
|
-
Foo.prototype.bar = 'baz'
|
838
|
-
new Foo()
|
839
|
-
EOJS
|
840
|
-
end
|
841
|
-
end
|
842
|
-
|
843
|
-
it "is enumenable" do
|
844
|
-
evaljs("o.foo = 'bar'; o.bang = 'baz'; o[5] = 'flip'")
|
845
|
-
{}.tap do |h|
|
846
|
-
@o.each do |k,v|
|
847
|
-
h[k] = v
|
848
|
-
end
|
849
|
-
h.should == {"foo" => 'bar', "bang" => 'baz', 5 => 'flip'}
|
850
|
-
end
|
851
|
-
end
|
852
|
-
end
|
853
|
-
|
854
|
-
describe "Exception Handling" do
|
855
|
-
it "raises javascript exceptions as ruby exceptions" do
|
856
|
-
lambda {
|
857
|
-
Context.new.eval('foo')
|
858
|
-
}.should raise_error(JSError)
|
859
|
-
end
|
860
|
-
|
861
|
-
it "can handle syntax errors" do
|
862
|
-
lambda {
|
863
|
-
Context.eval('does not compiles')
|
864
|
-
}.should raise_error
|
865
|
-
end
|
866
|
-
|
867
|
-
it "translates ruby exceptions into javascript exceptions if they are thrown from code called it javascript" do
|
868
|
-
Context.new do |cxt|
|
869
|
-
cxt['rputs'] = lambda {|this, msg| rputs msg}
|
870
|
-
cxt['boom'] = lambda do |this|
|
871
|
-
raise "BOOM!"
|
872
|
-
end
|
873
|
-
cxt.eval('var msg;try {boom()} catch (e) {msg = e.message};msg').should == 'BOOM!'
|
874
|
-
end
|
875
|
-
end
|
876
|
-
|
877
|
-
it "will allow exceptions to pass through multiple languages boundaries (i.e. js -> rb -> js -> rb)" do
|
878
|
-
Context.new do |cxt|
|
879
|
-
cxt['one'] = lambda do
|
880
|
-
cxt.eval('two()', 'one.js')
|
881
|
-
end
|
882
|
-
cxt['two'] = lambda do
|
883
|
-
cxt.eval('three()', 'two.js')
|
884
|
-
end
|
885
|
-
cxt['three'] = lambda do
|
886
|
-
cxt.eval('throw "BOOM!"', "three.js")
|
887
|
-
end
|
888
|
-
lambda {
|
889
|
-
cxt['one'].call(cxt.scope)
|
890
|
-
}.should raise_error {|e|
|
891
|
-
#TODO: assert something about the contents of the stack?
|
892
|
-
#--cowboyd 05/25/2010
|
893
|
-
}
|
894
|
-
end
|
895
|
-
end
|
896
|
-
end
|
897
|
-
|
898
|
-
describe "A Ruby class reflected into JavaScript" do
|
899
|
-
it "will extend instances of the class when properties are added to the corresponding JavaScript constructor's prototype" do
|
900
|
-
Class.new.tap do |cls|
|
901
|
-
Context.new do |cxt|
|
902
|
-
cxt['RubyObject'] = cls
|
903
|
-
cxt.eval('RubyObject.prototype.foo = function() {return "bar"}')
|
904
|
-
cxt['o'] = cls.new
|
905
|
-
cxt.eval('o.foo()').should == "bar"
|
906
|
-
end
|
907
|
-
end
|
908
|
-
end
|
909
|
-
|
910
|
-
it "will extend instances of subclasses when properties are added to the corresponding JavaScript constructor's prototype" do
|
911
|
-
superclass = Class.new
|
912
|
-
subclass = Class.new(superclass)
|
913
|
-
Context.new do |cxt|
|
914
|
-
cxt['SuperClass'] = superclass
|
915
|
-
cxt['SubClass'] = subclass
|
916
|
-
cxt['o'] = subclass.new
|
917
|
-
cxt.eval('SuperClass.prototype.foo = function() {return "bar"}')
|
918
|
-
cxt.eval('o.foo()').should == "bar"
|
919
|
-
end
|
920
|
-
end
|
921
|
-
end
|
922
|
-
end
|
data/spec/redjs/loadme.js
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
"I am Legend"
|