h8 0.4.11 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0948d18a5d505eae5dea20a665f08c4e585fa128
4
- data.tar.gz: b3ff10f56ff8952278e3a255901d761fd86c48c6
3
+ metadata.gz: c3c42fdc46126f4cd9b55893dcf773b531f27dfb
4
+ data.tar.gz: 2b6b7b9157cd99488987657a34cbbf3449b504a8
5
5
  SHA512:
6
- metadata.gz: 626e81dd8d1552775252869f5fed2c7c4c02ab6544e3edc232851d60bebb532ce20a67bd0c5a577446432f2e2eb78ffa608bd411b0602927f24d5fcaac4d0d0d
7
- data.tar.gz: 50edb3ade915b7016cfd67be0879fa77ec2805ee5fa19384cd5237a874a41486f8aec7aa3aabfeaedd4b99dd47e3acd749af9335d132284225114372c7aa4a9f
6
+ metadata.gz: 7e4443044d235df479575c0db14d79060c3318364c8c535920d3ca4f2b074a1100813c56e8121cbf2946d9db293c82bfd0a81d252b28eda823b6563f0eac48a0
7
+ data.tar.gz: 5d5fa89d8992e39fd03264b2323f78965c55cfc4dada8d2aa9bb57b3e139807a0077e81af3143c5aec9bc0218617b369d7679bfe5bb087a816c681b95e75fa88
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Hybrid8, aka H8
2
2
 
3
+ *version 0.5* breaks the way nested exceptions are passed. Now riby -> js -> ruby uncaught
4
+ exception is wrapped into H8::NestedError so one can get both ruby and javascript trace.
5
+
3
6
  _Warning_ this gem is in public beta at the moment - beta testers are welcome! It means it's not
4
7
  yet stable enough for running in production - we haven't tried it yet ourselves.
5
8
 
data/bin/h8 CHANGED
@@ -1,10 +1,13 @@
1
1
  #!/bin/env ruby
2
- require 'h8'
2
+ #
3
+ #
4
+ require File.expand_path("#{File.dirname(__FILE__)}/../lib/h8")
3
5
  require 'h8/command'
4
6
 
5
7
  cmd = nil
6
8
  begin
7
9
  cmd = H8::Command.new(*ARGV)
10
+ rescue SystemExit
8
11
  rescue Exception => e
9
12
  STDERR.puts "Error: #{e}\n\n#{e.backtrace.join("\n")}"
10
13
  STDERR.puts cmd.usage
@@ -18,22 +18,28 @@ void h8::JsError::raise() const {
18
18
  Local<Object> jsx = exception().As<Object>();
19
19
  Local<Value> source = jsx->Get(h8->js("source"));
20
20
  RubyGate *rg = RubyGate::unwrap(source.As<Object>());
21
- if (rg) {
22
- // Passing thru the Ruby exception
23
- ruby_exception = rg->rubyObject();
24
- } else {
21
+ // if (rg) {
22
+ // // Passing thru the Ruby exception
23
+ // ruby_exception = rg->rubyObject();
24
+ // } else {
25
25
  Local<Message> m = message();
26
26
  Local<String> s = m->Get();
27
27
  String::Utf8Value res(s->ToString());
28
- ruby_exception = ruby_exception = rb_exc_new2(js_exception,
28
+
29
+ VALUE ex_class = rg ? js_nested_exception : js_exception;
30
+ ruby_exception = rb_exc_new2(ex_class,
29
31
  *res ? *res : "unknown javascript exception");
32
+
30
33
  rb_iv_set(ruby_exception, "@message", h8->to_ruby(s));
31
34
  rb_iv_set(ruby_exception, "@javascript_error",
32
35
  h8->to_ruby(jsx));
33
36
  rb_iv_set(ruby_exception, "@origin_name", h8->to_ruby(m->GetScriptResourceName()));
34
37
  rb_iv_set(ruby_exception, "@origin_line", INT2FIX(m->GetLineNumber()));
35
38
  rb_iv_set(ruby_exception, "@origin_column", INT2FIX(m->GetEndColumn()));
36
- }
39
+ if( rg ) {
40
+ rb_iv_set(ruby_exception, "@ruby_error", rg->rubyObject());
41
+ }
42
+ // }
37
43
  }
38
44
  rb_exc_raise(ruby_exception);
39
45
  // }
@@ -180,7 +186,7 @@ void h8::H8::register_ruby_gate(RubyGate* gate) {
180
186
  }
181
187
 
182
188
  void h8::H8::unregister_ruby_gate(RubyGate* gate) {
183
- add_resource(gate);
189
+ gate->unlink();
184
190
  id_map.erase(gate->ruby_object);
185
191
  }
186
192
 
@@ -13,7 +13,7 @@
13
13
  using namespace v8;
14
14
  using namespace std;
15
15
 
16
- extern VALUE h8_exception, js_exception, js_timeout_exception;
16
+ extern VALUE h8_exception, js_exception, js_timeout_exception, js_nested_exception;
17
17
  extern VALUE context_class;
18
18
  extern VALUE value_class;
19
19
  extern VALUE ruby_gate_class;
@@ -8,7 +8,7 @@ extern "C" {
8
8
  void Init_h8(void);
9
9
  }
10
10
 
11
- VALUE h8_exception, js_exception, js_timeout_exception;
11
+ VALUE h8_exception, js_exception, js_timeout_exception, js_nested_exception;
12
12
  VALUE context_class;
13
13
  VALUE ruby_gate_class;
14
14
  VALUE value_class;
@@ -231,6 +231,7 @@ void Init_h8(void) {
231
231
 
232
232
  h8_exception = rb_define_class_under(h8, "Error", rb_eStandardError);
233
233
  js_exception = rb_define_class_under(h8, "JsError", h8_exception);
234
+ js_nested_exception = rb_define_class_under(h8, "NestedError", js_exception);
234
235
  js_timeout_exception = rb_define_class_under(h8, "TimeoutError",
235
236
  js_exception);
236
237
 
@@ -42,7 +42,9 @@ public:
42
42
 
43
43
  virtual void free() {
44
44
  // printf("RG::FREE(%p)\n", this);
45
- delete this;
45
+ context->unregister_ruby_gate(this);
46
+ context = 0;
47
+ // delete this;
46
48
  }
47
49
 
48
50
  VALUE rubyObject() const {
@@ -51,9 +53,12 @@ public:
51
53
 
52
54
  virtual ~RubyGate() {
53
55
  // puts("~RG()");
54
- context->unregister_ruby_gate(this);
55
- persistent().ClearWeak();
56
- persistent().Reset();
56
+ if( context ) {
57
+ // puts("rg2");
58
+ context->unregister_ruby_gate(this);
59
+ }
60
+ // persistent().ClearWeak();
61
+ // persistent().Reset();
57
62
  // The rest is done by the base classes
58
63
  }
59
64
 
data/lib/h8.rb CHANGED
@@ -1,8 +1,8 @@
1
+ require 'h8/tools'
1
2
  require 'h8/version'
2
3
  require 'h8/context'
3
4
  require 'h8/value'
4
5
  require 'h8/errors'
5
- require 'h8/tools'
6
6
  require 'h8/coffee'
7
7
 
8
8
  # The native library should be required AFTER ruby defintions
@@ -7,11 +7,19 @@ module H8
7
7
 
8
8
  class Command
9
9
 
10
- def initialize *args, out: STDOUT, err: STDERR
10
+ def initialize *args, out: STDOUT, err: STDERR, dont_run: false
11
11
  @out = out
12
12
  @err = err
13
13
 
14
- run *args if args.length > 0
14
+ run *args unless dont_run
15
+ end
16
+
17
+ def usage
18
+ puts "h8 #{H8::VERSION} CLI runner\n"
19
+ puts "Usage:\n h8 [keys] file1..."
20
+ puts
21
+ puts "executes 1 or more javascript/coffeescript files connected with Ruby environment"
22
+ puts @parser.keys_doc
15
23
  end
16
24
 
17
25
  def run *args
@@ -33,8 +41,12 @@ module H8
33
41
  count += 1
34
42
  end
35
43
  }
44
+ .key('-h', '--h') {
45
+ usage
46
+ count = -100000000
47
+ }
36
48
 
37
- @parser.parse { |file|
49
+ rest = @parser.parse { |file|
38
50
  count += 1
39
51
  @script = open(file, 'r').read
40
52
  file.downcase.end_with?('.coffee') and @script = H8::Coffee.compile(@script)
@@ -42,7 +54,10 @@ module H8
42
54
  context.eval @script
43
55
  }
44
56
 
45
- count > 0 or raise 'Must provide at least one file'
57
+ unless count != 0
58
+ STDERR.puts 'H8: error: provide at least one file (use -h for help)'
59
+ exit 300
60
+ end
46
61
  end
47
62
 
48
63
  def context
@@ -55,17 +70,11 @@ module H8
55
70
  cxt[:puts] = print
56
71
  cxt[:open] = -> (name, mode='r', block=nil) { Stream.new(name, mode, block) }
57
72
  cxt['__FILE__'] = @file ? @file.to_s : '<inline>'
58
- cxt[:File] = FileProxy.new
73
+ cxt[:File] = FileProxy.new
59
74
  cxt
60
75
  end
61
76
  end
62
77
 
63
- def usage
64
- "\nh8 #{H8::VERSION} CLI inteface\n\n" +
65
- "Usage: h8 <file.js/file.coffe>\n\n" +
66
- @parser.keys_doc
67
- end
68
-
69
78
  class FileProxy
70
79
  def dirname str
71
80
  File.dirname str
@@ -1,6 +1,14 @@
1
1
  require 'thread'
2
2
  require 'h8'
3
3
 
4
+ class Array
5
+ def _select_js callable
6
+ select { |item|
7
+ callable.call item
8
+ }
9
+ end
10
+ end
11
+
4
12
  module H8
5
13
 
6
14
  class Context
@@ -68,6 +76,9 @@ module H8
68
76
  # It has very complex logic so the security model update should be done somehow
69
77
  # else.
70
78
  def self.secure_call instance, method, args=nil
79
+ if instance.is_a?(Array)
80
+ method == 'select' and method = '_select_js'
81
+ end
71
82
  method = method.to_sym
72
83
  begin
73
84
  m = instance.public_method(method)
@@ -110,7 +121,16 @@ module H8
110
121
  # This is workaround for buggy rb_proc_call which produces segfaults
111
122
  # if proc is not exactly a proc, so we call it like this:
112
123
  def safe_proc_call proc, args
113
- proc.call *args
124
+ if proc.respond_to?(:call)
125
+ proc.call(*args)
126
+ else
127
+ if args.length == 0
128
+ proc # Popular bug: call no-arg method not like a property
129
+ else
130
+ raise NoMethodError, "Invalid callable"
131
+ end
132
+ end
133
+ # proc.is_a?(Array) ? proc : proc.call(*args)
114
134
  end
115
135
 
116
136
  # :nodoc:
@@ -127,6 +147,7 @@ module H8
127
147
  end
128
148
 
129
149
  def self.can_access?(owner)
150
+ return true if owner.is_a?(Array.class)
130
151
  owner != Object.class && owner != Kernel && owner != BasicObject.class
131
152
  end
132
153
 
@@ -39,6 +39,13 @@ module H8
39
39
  end
40
40
  end
41
41
 
42
+ # The exception that carries out uncaught ruby exception #ruby_error
43
+ # therefore it is possible to get javascript backtrace too
44
+ class NestedError < JsError
45
+ # The uncaught ruby exception
46
+ attr :ruby_error
47
+ end
48
+
42
49
  # Script execution is timed out (see H8::Context#eval timeout parameter)
43
50
  class TimeoutError < JsError
44
51
  def initialize message
@@ -2,7 +2,7 @@ require 'singleton'
2
2
 
3
3
  module H8
4
4
  # The class representing undefined in javascript. Singleton
5
- # Nota that H8::Undefined == false but is not FalseClass
5
+ # Note that H8::Undefined == false but is not FalseClass
6
6
  class UndefinedClass
7
7
  include Singleton
8
8
 
@@ -29,13 +29,13 @@ module H8
29
29
  def == x
30
30
  x.is_a?(H8::UndefinedClass) || x == false
31
31
  end
32
-
33
32
  end
34
33
 
35
34
  # The constant representing 'undefined' value in Javascript
36
35
  # The proper use is to compare returned value res == H8::Undefined
37
36
  Undefined = UndefinedClass.instance
38
37
 
38
+
39
39
  # Convert javascript 'arguments' object to ruby array
40
40
  def arguments_to_a args
41
41
  res = Array.new(l=args.length)
@@ -45,3 +45,4 @@ module H8
45
45
 
46
46
  module_function :arguments_to_a
47
47
  end
48
+
@@ -1,3 +1,3 @@
1
1
  module H8
2
- VERSION = "0.4.11"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -7,7 +7,7 @@ describe 'cli' do
7
7
  before :each do
8
8
  @out = StringIO.new '', 'w'
9
9
  @err = StringIO.new '', 'w'
10
- @command = H8::Command.new out: @out, err: @err
10
+ @command = H8::Command.new out: @out, err: @err, dont_run: true
11
11
  end
12
12
 
13
13
  def output
@@ -29,12 +29,6 @@ describe 'cli' do
29
29
  File.expand_path(File.join(File.dirname(__FILE__), *path_components))
30
30
  end
31
31
 
32
- it 'should print usage' do
33
- expect(-> { @command.run }).to raise_error(RuntimeError, "Must provide at least one file")
34
- expect(@command.usage =~ /Usage:/).to be_truthy
35
- # puts @command.usage
36
- end
37
-
38
32
  it 'should print' do
39
33
  run 'print "hello"; console.log "world"; console.error "life sucks!"'
40
34
  output.should == "hello\nworld\n"
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+ require 'h8'
3
+ require 'ostruct'
4
+ require 'hashie'
5
+
6
+ desk_gen = <<END
7
+ console.log 'started'
8
+
9
+ offsets = [[-1, 0], [0, 1], [1, 0], [0, -1]]
10
+
11
+ class DeskGenerator
12
+
13
+ constructor: (@n, r0=0, c0=0) ->
14
+ @desk = []
15
+ for r in [0...@n]
16
+ @desk.push (null for col in [0...@n])
17
+ @retries = 0
18
+ @step(r0, c0, @n * @n) or throw new Error("Failed to generate desk")
19
+
20
+ step: (r, c, depth) ->
21
+ @desk[r][c] = depth--
22
+ console.log r, c, depth
23
+ return true if depth == 0
24
+
25
+ for [r1, c1] in @moves(r, c)
26
+ return true if @step(r1, c1, depth)
27
+
28
+ @retries++
29
+ depth++
30
+ @desk[r][c] = null
31
+ false
32
+
33
+ moves: (r, c) ->
34
+ moves = []
35
+ for [sr, sc] in offsets
36
+ [r1, c1] = [r + sr, c + sc]
37
+ if 0 <= r1 < @n && 0 <= c1 < @n && !@desk[r1][c1]
38
+ moves.push [r1, c1]
39
+ moves
40
+
41
+ toString: ->
42
+ res = []
43
+ for r in [0...@n]
44
+ res.push ( (if x == 0 then ' .' else pad(x, 3)) for x in @desk[r]).join('')
45
+ res.join "\n"
46
+
47
+ pad = (n, len) ->
48
+ len ?= 3
49
+ res = n?.toString() || '.'
50
+ res = ' ' + res while res.length < len
51
+ res
52
+
53
+ timing = (name, cb) ->
54
+ start = new Date().getTime()
55
+ res = cb()
56
+ console.log("\#{name}: \#{(new Date().getTime() - start) / 1000}")
57
+ res
58
+
59
+ result = timing 'default', ->
60
+ new DeskGenerator(6, 5, 1)
61
+
62
+ console.log result.toString()
63
+ console.log 'retries',result.retries
64
+ END
65
+
66
+ class Console
67
+ def debug *args
68
+ log *args
69
+ end
70
+
71
+ def log *args
72
+ # puts *args.join(' ')
73
+ end
74
+ end
75
+
76
+ describe 'heavy scripts' do
77
+ it 'should pass desk gen test' do
78
+ c = H8::Context.new
79
+ c[:console] = Console.new
80
+ # c.eval "console.log('fine');"
81
+ # pending
82
+ js = H8::Coffee.compile desk_gen
83
+ begin
84
+ c.eval js
85
+ rescue
86
+ n = 1
87
+ js.each_line { |l|
88
+ puts "%3d %s" % [n, l]
89
+ n += 1
90
+ }
91
+ raise
92
+ end
93
+ end
94
+ end
95
+
96
+
@@ -274,7 +274,7 @@ describe 'js_gate' do
274
274
  }).to raise_error(H8::JsError)
275
275
  end
276
276
 
277
- it 'should pass thru uncaught ruby exceptions from js->ruby callbacks' do
277
+ it 'should report thru uncaught ruby exceptions from js->ruby callbacks' do
278
278
  class MyException < StandardError;
279
279
  end;
280
280
  cxt = H8::Context.new
@@ -287,7 +287,9 @@ describe 'js_gate' do
287
287
  End
288
288
  expect(-> {
289
289
  res.call('foo', 'bar').should == 'foo:bar'
290
- }).to raise_error(MyException)
290
+ }).to raise_error(H8::NestedError) { |e|
291
+ e.ruby_error.should be_instance_of(MyException)
292
+ }
291
293
  end
292
294
 
293
295
  it 'should dynamically add and remove properties to js objects' do
@@ -135,8 +135,9 @@ describe 'ruby gate' do
135
135
  result = fn(11, 22);
136
136
  throw Error("It should not happen");
137
137
  End
138
- }).to raise_error(MyException) { |e|
139
- e.message.should == 'Shit happens'
138
+ }).to raise_error(H8::NestedError) { |e|
139
+ e.ruby_error.should be_instance_of(MyException)
140
+ e.ruby_error.message.should == 'Shit happens'
140
141
  }
141
142
  end
142
143
 
@@ -295,7 +296,7 @@ describe 'ruby gate' do
295
296
  # see Test class implementation: this is a valid test
296
297
  cxt.eval("t['foo'];").should == 'init[]'
297
298
  cxt.eval("t.foo").should == 'init[]'
298
- expect(-> { cxt.eval("t['foo1'];") }).to raise_error(RuntimeError)
299
+ expect(-> { cxt.eval("t['foo1'];") }).to raise_error(H8::NestedError)
299
300
  cxt.eval("t.foo='bar'");
300
301
  cxt.eval("t.foo;").should == 'bar'
301
302
  t.val.should == 'bar'
@@ -366,6 +367,14 @@ describe 'ruby gate' do
366
367
  h['one'].should == 1
367
368
  end
368
369
 
370
+ it 'should access ruby and java array functions' do
371
+ cxt = H8::Context.new
372
+ src = cxt[:h] = [1,20,3,4,5]
373
+ cxt.eval("h.reverse()").should == [5, 4, 3, 20, 1]
374
+ cxt.eval("h.sort()").should == [1,3,4,5,20]
375
+ cxt.eval("h.select(function(x){return x >=4;}).sort()").should == [4, 5, 20]
376
+ end
377
+
369
378
  it 'should pass varargs' do
370
379
  cxt = H8::Context.new
371
380
  cxt[:test] = -> (args) {
@@ -448,7 +457,9 @@ describe 'ruby gate' do
448
457
 
449
458
  # We call gated ruby object with wrong number of args
450
459
  # which in turn causes attempt to call not callable result:
451
- expect(-> { cxt.eval('g1.checkself(12)') }).to raise_error(NoMethodError)
460
+ expect(-> { cxt.eval('g1.checkself(12)') }).to raise_error(H8::NestedError) { |e|
461
+ e.ruby_error.should be_instance_of(NoMethodError)
462
+ }
452
463
  end
453
464
 
454
465
  it 'should return self from gated class' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: h8
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.11
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - sergeych
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-04 00:00:00.000000000 Z
11
+ date: 2015-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -153,6 +153,7 @@ files:
153
153
  - spec/coffee_spec.rb
154
154
  - spec/command_spec.rb
155
155
  - spec/context_spec.rb
156
+ - spec/heavy_load_spec.rb
156
157
  - spec/js_gate_spec.rb
157
158
  - spec/ruby_gate_spec.rb
158
159
  - spec/spec_helper.rb
@@ -178,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
179
  version: '0'
179
180
  requirements: []
180
181
  rubyforge_project:
181
- rubygems_version: 2.2.2
182
+ rubygems_version: 2.4.5
182
183
  signing_key:
183
184
  specification_version: 4
184
185
  summary: Minimalistic and fast Ruby <--> V8 integration
@@ -187,6 +188,7 @@ test_files:
187
188
  - spec/coffee_spec.rb
188
189
  - spec/command_spec.rb
189
190
  - spec/context_spec.rb
191
+ - spec/heavy_load_spec.rb
190
192
  - spec/js_gate_spec.rb
191
193
  - spec/ruby_gate_spec.rb
192
194
  - spec/spec_helper.rb