h8 0.4.11 → 0.5.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.
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