therubyracer 0.4.4 → 0.4.5

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.

@@ -1,4 +1,12 @@
1
- === 0.X.X 2010-01-14
1
+ === 0.4.5 2010-01-18
2
+ * 3 major enhancements
3
+ * case munging so that ruby methods(perl_case) are accessed through javascript in camelCase.
4
+ * access 0-arity ruby methods as javascript properties
5
+ * invoke ruby setters from javascript as properties
6
+ * 1 minor enhancements
7
+ * contexts detect whether they are open or not and open when needed
8
+
9
+ === 0.4.4 2010-01-14
2
10
  * 2 major enhancements:
3
11
  * Ruby objects embedded into javascript are passed back to ruby as themselves and not a wrapped V8 object wrapping a ruby object.
4
12
  * Use any ruby object as the scope of eval().
@@ -48,5 +48,6 @@ spec/redjs/jsapi_spec.rb
48
48
  spec/redjs_helper.rb
49
49
  spec/spec.opts
50
50
  spec/spec_helper.rb
51
+ spec/v8/to_spec.rb
51
52
  tasks/rspec.rake
52
53
  therubyracer.gemspec
@@ -5,6 +5,39 @@
5
5
 
6
6
  using namespace v8;
7
7
 
8
+ namespace {
9
+ VALUE unwrap(const AccessorInfo& info) {
10
+ return (VALUE)External::Unwrap(info.Data());
11
+ }
12
+
13
+ Local<Array> TO_ARRAY(Arguments& args) {
14
+ Local<Array> array = Array::New(args.Length());
15
+ for (int i = 0; i < args.Length(); i++) {
16
+ array->Set(Integer::New(i), args[i]);
17
+ }
18
+ }
19
+
20
+ Local<Value> Racer_Call_Ruby_Method(VALUE object, VALUE method, Local<Array> args) {
21
+ VALUE * arguments = new VALUE[args->Length()];
22
+ for (unsigned int i = 0; i < args->Length(); i++) {
23
+ Handle<Value> val = args->Get(Integer::New(i));
24
+ arguments[i] = V82RB(val);
25
+ }
26
+ VALUE result = rb_funcall2(object, rb_to_id(method), args->Length(), arguments);
27
+ Local<Value> converted = RB2V8(result);
28
+ return converted;
29
+ }
30
+
31
+ Local<Value> Racer_Access_Ruby_Property(VALUE object, VALUE name) {
32
+ VALUE method = rb_obj_method(object, name);
33
+ if (FIX2INT(rb_funcall(method, rb_intern("arity"), 0)) == 0) {
34
+ return Racer_Call_Ruby_Method(object, name, Array::New(0));
35
+ } else {
36
+ return RB2V8(method);
37
+ }
38
+ }
39
+ }
40
+
8
41
  Handle<Value> RacerRubyInvocationCallback(const Arguments& args) {
9
42
  VALUE code = (VALUE)External::Unwrap(args.Data());
10
43
  if (NIL_P(code)) {
@@ -22,4 +55,106 @@ Handle<Value> RacerRubyInvocationCallback(const Arguments& args) {
22
55
  Handle<Value> convertedResult = RB2V8(result);
23
56
  return convertedResult ;
24
57
  }
25
- }
58
+ }
59
+
60
+
61
+
62
+
63
+ /**
64
+ * NamedProperty[Getter|Setter] are used as interceptors on object.
65
+ * See ObjectTemplate::SetNamedPropertyHandler.
66
+ */
67
+
68
+ Handle<Value> RacerRubyNamedPropertyGetter(Local<String> property, const AccessorInfo& info) {
69
+ // printf("Getter '%s'<br/>", *String::AsciiValue(property));
70
+ if (property->Length() == 0) {
71
+ return Handle<Value>();
72
+ }
73
+ VALUE object = unwrap(info);
74
+ VALUE camel_name = V82RB((Local<Value>&)property);
75
+ VALUE perl_name = rb_funcall(V8_To, rb_intern("perl_case"), 1, camel_name);
76
+ VALUE methods = rb_funcall(object, rb_intern("public_methods"), 1, Qfalse);
77
+
78
+ if (RTEST(rb_ary_includes(methods, perl_name))) {
79
+ return Racer_Access_Ruby_Property(object, perl_name);
80
+ }
81
+ if (RTEST(rb_ary_includes(methods, camel_name))) {
82
+ return Racer_Access_Ruby_Property(object, camel_name);
83
+ }
84
+ return Handle<Value>();
85
+ }
86
+
87
+ /**
88
+ * Returns the value if the setter intercepts the request.
89
+ * Otherwise, returns an empty handle.
90
+ */
91
+ Handle<Value> RacerRubyNamedPropertySetter(Local<String> property, Local<Value> value, const AccessorInfo& info) {
92
+ if (property->Length() == 0) {
93
+ return Handle<Value>();
94
+ }
95
+ // printf("Setter: '%s'<br/>", *String::AsciiValue(property));
96
+ std::string setter = V82String((Handle<Value>&)property);
97
+ setter += "=";
98
+ Local<String> setter_name = String::New(setter.c_str());
99
+ VALUE object = unwrap(info);
100
+ VALUE camel_name = V82RB((Local<Value>&)setter_name);
101
+ VALUE perl_name = rb_funcall(V8_To, rb_intern("perl_case"), 1, camel_name);
102
+ VALUE methods = rb_funcall(object, rb_intern("public_methods"), 1, Qfalse);
103
+ Local<Array> args = Array::New(1);
104
+ args->Set(Integer::New(0), value);
105
+ if (RTEST(rb_ary_includes(methods, perl_name))) {
106
+ Racer_Call_Ruby_Method(object, perl_name, args);
107
+ return value;
108
+ }
109
+ if (RTEST(rb_ary_includes(methods, camel_name))) {
110
+ Racer_Call_Ruby_Method(object, camel_name, args);
111
+ return value;
112
+ }
113
+ return Handle<Value>();
114
+ }
115
+
116
+ /**
117
+ * Returns a non-empty handle if the interceptor intercepts the request.
118
+ * The result is true if the property exists and false otherwise.
119
+ */
120
+ Handle<Boolean> RacerRubyNamedPropertyQuery(Local<String> property, const AccessorInfo& info) {
121
+ printf("Query: '%s'<br/>", *String::AsciiValue(property));
122
+ if (property->Length() == 0) {
123
+ return False();
124
+ }
125
+ VALUE object = unwrap(info);
126
+ VALUE methods = rb_funcall(object, rb_intern("public_methods"), 1, Qfalse);
127
+ VALUE attr_name = V82RB((Local<Value>&)property);
128
+ VALUE perl_name = rb_funcall(V8_To, rb_intern("perl_case"), 1, attr_name);
129
+
130
+ if (RTEST(rb_ary_includes(methods, attr_name)) || RTEST(rb_ary_includes(methods, perl_name))) {
131
+ return True();
132
+ } else {
133
+ return Handle<Boolean>();
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Returns a non-empty handle if the deleter intercepts the request.
139
+ * The return value is true if the property could be deleted and false
140
+ * otherwise.
141
+ */
142
+ Handle<Boolean> RacerRubyNamedPropertyDeleter(Local<String> property, const AccessorInfo& info) {
143
+ return False();
144
+ }
145
+
146
+ /**
147
+ * Returns an array containing the names of the properties the named
148
+ * property getter intercepts.
149
+ */
150
+ Handle<Array> RacerRubyNamedPropertyEnumerator(const AccessorInfo& info) {
151
+ VALUE object = unwrap(info);
152
+ VALUE methods = rb_funcall(object, rb_intern("public_methods"), 1, Qfalse);
153
+ int length = RARRAY_LEN(methods);
154
+ Local<Array> properties = Array::New(length);
155
+ for (int i = 0; i < length; i++) {
156
+ VALUE camel_name = rb_funcall(V8_To, rb_intern("camel_case"), 1, rb_ary_entry(methods, i));
157
+ properties->Set(Integer::New(i), RB2V8(camel_name));
158
+ }
159
+ return properties;
160
+ }
@@ -5,4 +5,10 @@
5
5
 
6
6
  v8::Handle<v8::Value> RacerRubyInvocationCallback(const v8::Arguments& args);
7
7
 
8
+ v8::Handle<v8::Value> RacerRubyNamedPropertyGetter(v8::Local<v8::String> property, const v8::AccessorInfo& info);
9
+ v8::Handle<v8::Value> RacerRubyNamedPropertySetter(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::AccessorInfo& info);
10
+ v8::Handle<v8::Boolean> RacerRubyNamedPropertyQuery(v8::Local<v8::String> property, const v8::AccessorInfo& info);
11
+ v8::Handle<v8::Boolean> RacerRubyNamedPropertyDeleter(v8::Local<v8::String> property, const v8::AccessorInfo& info);
12
+ v8::Handle<v8::Array> RacerRubyNamedPropertyEnumerator(const v8::AccessorInfo& info);
13
+
8
14
  #endif /* end of include guard: CALLBACKS_H_8VK3LWBG */
@@ -3,6 +3,7 @@
3
3
  #include "v8_ref.h"
4
4
  #include "v8_obj.h"
5
5
  #include "v8_cxt.h"
6
+ #include "v8_template.h"
6
7
 
7
8
  using namespace v8;
8
9
 
@@ -32,8 +33,10 @@ VALUE V82RB(Handle<Value>& value) {
32
33
  if (value->IsObject()) {
33
34
  Local<Object> object(Object::Cast(*value));
34
35
  Local<Value> peer = object->GetHiddenValue(String::New("TheRubyRacer::RubyObject"));
36
+ // Local<Value> peer = object->GetInternalField(0);
35
37
  if (peer.IsEmpty()) {
36
38
  VALUE context_ref = V8_Ref_Create(V8_C_Context, Context::GetCurrent());
39
+ // object->SetPointerInInternalField(1, (void *)context_ref);
37
40
  object->SetHiddenValue(String::New("TheRubyRacer::Context"), External::Wrap((void *)context_ref));
38
41
  return V8_Ref_Create(V8_C_Object, value, context_ref);
39
42
  } else {
@@ -55,10 +58,10 @@ Local<Value> RB2V8(VALUE value) {
55
58
  if (convert(value, result)) {
56
59
  return result;
57
60
  }
58
- Local<ObjectTemplate> tmpl = RB_VALUE_2_V8_ObjectTemplate(value);
59
- Local<Object> object = tmpl->NewInstance();
60
- object->SetHiddenValue(String::New("TheRubyRacer::RubyObject"), External::Wrap((void *)value));
61
- return object;
61
+ Local<Object> o = Racer_Create_V8_ObjectTemplate(value)->NewInstance();
62
+ o->SetHiddenValue(String::New("TheRubyRacer::RubyObject"), External::Wrap((void *) value));
63
+ // o->SetPointerInInternalField(0, (void*)value);
64
+ return o;
62
65
  }
63
66
 
64
67
  std::string V82String(Handle<Value>& value) {
@@ -77,21 +80,4 @@ std::string V82String(Handle<Value>& value) {
77
80
  }
78
81
 
79
82
  return UNDEFINED_STR;
80
- }
81
-
82
- Local<ObjectTemplate> RB_VALUE_2_V8_ObjectTemplate(VALUE value) {
83
- Local<ObjectTemplate> tmpl = ObjectTemplate::New();
84
- VALUE methods = rb_funcall(value, rb_intern("public_methods"), 1, Qfalse);
85
- int len = RARRAY_LEN(methods);
86
- for (int i = 0; i < len; i++) {
87
- VALUE method_name = RARRAY_PTR(methods)[i];
88
- VALUE camel_method_name = rb_funcall(V8_To, rb_intern("camelcase"), 1, method_name);
89
- VALUE method = rb_funcall(value, rb_intern("method"), 1, method_name);
90
- Local<String> keystr = (String *)*RB2V8(method_name);
91
- Local<String> camelstr = (String *)*RB2V8(camel_method_name);
92
- Local<FunctionTemplate> fun = FunctionTemplate::New(RacerRubyInvocationCallback, External::Wrap((void *)method));
93
- tmpl->Set(keystr, fun);
94
- tmpl->Set(camelstr, fun);
95
- }
96
- return tmpl;
97
- }
83
+ }
@@ -20,6 +20,4 @@ v8::Local<v8::Value> RB2V8(VALUE value);
20
20
  std::string RB2String(VALUE value);
21
21
  std::string V82String(v8::Handle<v8::Value>& value);
22
22
 
23
- v8::Local<v8::ObjectTemplate> RB_VALUE_2_V8_ObjectTemplate(VALUE value);
24
-
25
23
  #endif
@@ -1,5 +1,6 @@
1
1
  #include "v8_cxt.h"
2
2
  #include "v8_msg.h"
3
+ #include "v8_template.h"
3
4
  #include "converters.h"
4
5
 
5
6
  using namespace v8;
@@ -16,9 +17,10 @@ VALUE v8_Context_New(int argc, VALUE *argv, VALUE self) {
16
17
  if (NIL_P(scope)) {
17
18
  return V8_Ref_Create(self, Context::New());
18
19
  } else {
19
- Persistent<Context> context = Context::New(0, RB_VALUE_2_V8_ObjectTemplate(scope));
20
+ Persistent<Context> context = Context::New(0, Racer_Create_V8_ObjectTemplate(scope));
20
21
  Context::Scope enter(context);
21
22
  context->Global()->SetHiddenValue(String::New("TheRubyRacer::RubyObject"), External::Wrap((void *)scope));
23
+ // context->Global()->SetPointerInInternalField(0, (void*)scope);
22
24
  VALUE ref = V8_Ref_Create(self, context, scope);
23
25
  context.Dispose();
24
26
  return ref;
@@ -47,5 +47,6 @@ VALUE v8_Object_context(VALUE self) {
47
47
  HandleScope handles;
48
48
  Local<Object> object = unwrap(self);
49
49
  Local<Value> cxt = object->GetHiddenValue(String::New("TheRubyRacer::Context"));
50
+ // Local<Value> cxt = object->GetInternalField(1);
50
51
  return cxt.IsEmpty() ? Qnil : (VALUE)External::Unwrap(cxt);
51
52
  }
@@ -7,6 +7,22 @@
7
7
  #include "callbacks.h"
8
8
 
9
9
  using namespace v8;
10
+
11
+ Local<ObjectTemplate> Racer_Create_V8_ObjectTemplate(VALUE value) {
12
+ Local<ObjectTemplate> tmpl = ObjectTemplate::New();
13
+ // tmpl->SetInternalFieldCount(2);
14
+ tmpl->SetNamedPropertyHandler(
15
+ RacerRubyNamedPropertyGetter,
16
+ RacerRubyNamedPropertySetter,
17
+ 0, // RacerRubyNamedPropertyQuery,
18
+ 0, // RacerRubyNamedPropertyDeleter,
19
+ RacerRubyNamedPropertyEnumerator,
20
+ External::Wrap((void *)value)
21
+ );
22
+ return tmpl;
23
+ }
24
+
25
+
10
26
 
11
27
  VALUE v8_Template_Set(VALUE self, VALUE name, VALUE value) {
12
28
  HandleScope handles;
@@ -1,6 +1,8 @@
1
1
  #ifndef _RUBY_V8_TEMPLATE_
2
2
  #define _RUBY_V8_TEMPLATE_
3
3
 
4
+ v8::Local<v8::ObjectTemplate> Racer_Create_V8_ObjectTemplate(VALUE object);
5
+
4
6
  VALUE v8_Template_Set(VALUE self, VALUE name, VALUE value);
5
7
 
6
8
  VALUE v8_ObjectTemplate_New(VALUE clazz);
data/lib/v8.rb CHANGED
@@ -2,7 +2,7 @@ $:.unshift(File.dirname(__FILE__)) unless
2
2
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
3
 
4
4
  module V8
5
- VERSION = '0.4.4'
5
+ VERSION = '0.4.5'
6
6
  require 'v8/v8' #native glue
7
7
  require 'v8/to'
8
8
  require 'v8/context'
@@ -22,9 +22,11 @@ module V8
22
22
  if IO === javascript || StringIO === javascript
23
23
  javascript = javascript.read()
24
24
  end
25
- @native.eval(javascript).tap do |result|
26
- raise JavascriptError.new(result) if result.kind_of?(C::Message)
27
- return To.ruby(result)
25
+ @native.open do
26
+ @native.eval(javascript).tap do |result|
27
+ raise JavascriptError.new(result) if result.kind_of?(C::Message)
28
+ return To.ruby(result)
29
+ end
28
30
  end
29
31
  end
30
32
 
@@ -21,9 +21,13 @@ module V8
21
21
  end
22
22
  end
23
23
 
24
- def camelcase(str)
24
+ def camel_case(str)
25
25
  str.to_s.gsub(/_(\w)/) {$1.upcase}
26
- end
26
+ end
27
+
28
+ def perl_case(str)
29
+ str.gsub(/([A-Z])([a-z])/) {"_#{$1.downcase}#{$2}"}.downcase
30
+ end
27
31
  end
28
32
  end
29
33
  end
@@ -113,27 +113,30 @@ describe "Ruby Javascript API" do
113
113
  cxt.eval('timesfive(3)').should == 15
114
114
  end
115
115
  end
116
+
117
+ it "reports ruby methods that do not exist as undefined" do
118
+ Context.open(:with => Object.new) do |cxt|
119
+ cxt.eval('this.foobar').should be_nil
120
+ end
121
+ end
116
122
 
117
123
  it "can call public locally defined ruby methods" do
118
124
  class_eval do
119
- def voo
120
- "doo"
125
+ def voo(str)
126
+ "voo#{str}"
121
127
  end
122
128
  end
123
- evaljs("o.voo").should_not be_nil
124
- evaljs("o.voo()").should == "doo"
129
+ evaljs("o.voo('doo')").should == "voodoo"
125
130
  end
126
131
 
127
132
  it "translates ruby naming conventions into javascript naming conventions, but you can still access them by their original names" do
128
133
  class_eval do
129
- def my_special_method
130
- "hello"
134
+ def my_special_method(to)
135
+ "hello #{to}"
131
136
  end
132
137
  end
133
- evaljs("o.mySpecialMethod").should_not be_nil
134
- evaljs("o.mySpecialMethod()").should == "hello"
135
- evaljs("o.my_special_method").should_not be_nil
136
- evaljs("o.my_special_method()").should == "hello"
138
+ evaljs("o.mySpecialMethod('Frank')").should == "hello Frank"
139
+ evaljs("o.my_special_method('Jack')").should == "hello Jack"
137
140
  end
138
141
 
139
142
  it "hides methods not defined directly on this instance's class" do
@@ -152,8 +155,8 @@ describe "Ruby Javascript API" do
152
155
  def baz_bang
153
156
  end
154
157
  end
155
- pending "why the hell isn't the return value of getIds() being respected?!?"
156
- evaljs(<<-EOJS).should == ["fooBar,bazBang"]
158
+ require 'set'
159
+ evaljs(<<-EOJS).to_set.should == Set.new(["fooBar","bazBang"])
157
160
  var names = [];
158
161
  for (var p in o) {
159
162
  names.push(p);
@@ -166,18 +169,49 @@ describe "Ruby Javascript API" do
166
169
  Context.open do |cxt|
167
170
  cxt['o'] = @instance
168
171
  class_eval do
169
- def bar
170
- "baz!"
172
+ def whiz(str)
173
+ "whiz#{str}!"
171
174
  end
172
175
  end
173
- cxt.eval("o.bar").should_not be_nil
174
- cxt.eval("o.bar()").should == "baz!"
176
+ cxt.eval("o.whiz('bang')").should == "whizbang!"
175
177
  end
176
178
  end
177
179
 
178
- it "treats ruby methods that have an arity of 0 as javascript properties by default"
180
+ it "treats ruby methods that have an arity of 0 as javascript properties by default" do
181
+ class_eval do
182
+ def property
183
+ "flan!"
184
+ end
185
+ end
186
+ evaljs('o.property').should == 'flan!'
187
+ end
179
188
 
180
- it "will call ruby accesssor function when setting a property from javascript"
189
+ it "will call ruby accesssor function when setting a property from javascript" do
190
+ class_eval do
191
+ def dollars
192
+ @dollars
193
+ end
194
+
195
+ def dollars=(amount)
196
+ @dollars = amount
197
+ end
198
+ end
199
+ evaljs('o.dollars = 50')
200
+ @instance.dollars.should == 50
201
+ end
202
+
203
+ it "will accept expando properties by default for properties on ruby object that are not implemented in ruby" do
204
+ evaljs('o.five = 5; o.five').should == 5
205
+ end
206
+
207
+ it "it silently fails to replace properties which are defined on ruby objects but which are read-only" do
208
+ class_eval do
209
+ def bar
210
+ "baz"
211
+ end
212
+ end
213
+ evaljs('o.bar = "bing"; o.bar').should == "baz"
214
+ end
181
215
 
182
216
  def evaljs(str)
183
217
  Context.open do |cxt|
@@ -245,6 +279,7 @@ describe "Ruby Javascript API" do
245
279
  end
246
280
 
247
281
  it "can limit the number of instructions that are executed in the context" do
282
+ pending "haven't figured out how to constrain resources in V8"
248
283
  lambda {
249
284
  Context.open do |cxt|
250
285
  cxt.instruction_limit = 100 * 1000
@@ -345,9 +380,7 @@ end
345
380
  describe "Exception Handling" do
346
381
  it "raises javascript exceptions as ruby exceptions" do
347
382
  lambda {
348
- Context.open do |cxt|
349
- cxt.eval('foo')
350
- end
383
+ Context.new.eval('foo')
351
384
  }.should raise_error(JavascriptError)
352
385
  end
353
386
  end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ include V8
4
+
5
+ describe V8::To do
6
+
7
+ it "can convert into perl case" do
8
+ To.perl_case("foo").should == "foo"
9
+ To.perl_case("fooBar").should == "foo_bar"
10
+ To.perl_case("fooBarBaz").should == "foo_bar_baz"
11
+ To.perl_case("XMLDocument").should == "xml_document"
12
+ end
13
+
14
+
15
+ end
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{therubyracer}
5
- s.version = "0.4.4"
5
+ s.version = "0.4.5"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Charles Lowell", "Bill Robertson"]
9
- s.date = %q{2010-01-14}
9
+ s.date = %q{2010-01-18}
10
10
  s.description = %q{Embed the V8 Javascript interpreter into Ruby.}
11
11
  s.email = ["cowboyd@thefrontside.net", "billrobertson42@gmail.com"]
12
12
  s.extensions = ["ext/v8/extconf.rb"]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: therubyracer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charles Lowell
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2010-01-14 00:00:00 +02:00
13
+ date: 2010-01-18 00:00:00 +02:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -86,6 +86,7 @@ files:
86
86
  - spec/redjs_helper.rb
87
87
  - spec/spec.opts
88
88
  - spec/spec_helper.rb
89
+ - spec/v8/to_spec.rb
89
90
  - tasks/rspec.rake
90
91
  - therubyracer.gemspec
91
92
  has_rdoc: true