h8 0.4.5 → 0.4.8

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: da7dc92fe9f18d03e202dbd8566c5469f3b122b2
4
- data.tar.gz: 4c570745ab23ac22f705405742ff10b7fb9310df
3
+ metadata.gz: ae98780cc495a9c2f15db1b3de81f81cc014978b
4
+ data.tar.gz: ee00bd70c38b2f4b62ecf8beddb2e15fb6aacb61
5
5
  SHA512:
6
- metadata.gz: 3eade24933e29d3d5b124b4665a3eef82e4e6dee3cde4a7e62c1a5f68acf08ee5d060bdaa1f7a61eeedd53aea417b751618d847ffd67dd8f75dddbdd32f5cc56
7
- data.tar.gz: 90c14a2a879418134c41ce3958ce8b943cc5d86285811657e280af079e3802d275e1156860f6af4f3f664515ec37f19e9a51d07d8914a8e5eaa5c03218d20026
6
+ metadata.gz: 4a2be9806423de66eacb312891d1abcff2af0f24679e740af339689cae1ae50fd8dbb07d15d138fc13976bbb410b3acf92951d020bcb96df93c16ab9a5fd3b20
7
+ data.tar.gz: 85f4398e82fc5a818654936cdb2b0fe5b51ee64c8e08284d131657d4f89c90b55585fbb8fc052dfe673a8371ce9ad7f69ab4a440c3d8301ec5a23d5777981250
@@ -19,8 +19,7 @@ extern VALUE value_class;
19
19
  extern VALUE ruby_gate_class;
20
20
  extern VALUE Rundefined;
21
21
 
22
- extern ID id_is_a;
23
- extern ID id_safe_call;
22
+ extern ID id_is_a, id_safe_call, id_safe_proc_call;
24
23
 
25
24
  VALUE protect_ruby(const std::function<VALUE()> &block);
26
25
 
@@ -14,8 +14,7 @@ VALUE ruby_gate_class;
14
14
  VALUE value_class;
15
15
  VALUE Rundefined;
16
16
 
17
- ID id_is_a;
18
- ID id_safe_call;
17
+ ID id_is_a, id_safe_call, id_safe_proc_call;
19
18
 
20
19
  VALUE protect_ruby(const std::function<VALUE()> &block) {
21
20
  try {
@@ -189,6 +188,7 @@ void Init_h8(void) {
189
188
 
190
189
  id_is_a = rb_intern("is_a?");
191
190
  id_safe_call = rb_intern("secure_call");
191
+ id_safe_proc_call = rb_intern("safe_proc_call");
192
192
 
193
193
  VALUE h8 = rb_define_module("H8");
194
194
 
@@ -124,7 +124,7 @@ VALUE h8::RubyGate::rescue_callback(VALUE me, VALUE exception_object) {
124
124
  VALUE RubyGate::call(VALUE args) {
125
125
  VALUE callable = rb_ary_pop(args);
126
126
  VALUE context = rb_ary_pop(args);
127
- VALUE res = rb_funcall(context, rb_intern("safe_proc_call"), 2, callable, args);
127
+ VALUE res = rb_funcall(context, id_safe_proc_call, 2, callable, args);
128
128
  return res;
129
129
  }
130
130
 
@@ -33,6 +33,7 @@ spec = Gem::Specification.new do |spec|
33
33
  spec.add_development_dependency "rake"
34
34
  spec.add_development_dependency "rake-compiler"
35
35
  spec.add_development_dependency "rspec", '~> 3.1'
36
+ spec.add_development_dependency 'hashie', '>= 0.1.2'
36
37
 
37
38
  spec.add_dependency 'pargser', '>= 0.1.2'
38
39
  end
data/lib/h8.rb CHANGED
@@ -7,3 +7,6 @@ require 'h8/coffee'
7
7
 
8
8
  # The native library should be required AFTER ruby defintions
9
9
  require 'h8/h8'
10
+
11
+ # Initialize dependencies
12
+ H8::Coffee.compile ''
@@ -18,7 +18,7 @@ module H8
18
18
  # compiler instance across all threads with a mutex.
19
19
  def self.eval src, ** kwargs
20
20
  @@mutex.synchronize {
21
- (@@compiler ||= Coffee.new).eval src, ** kwargs
21
+ (@@compiler ||= Coffee.new).eval src, **kwargs
22
22
  }
23
23
  end
24
24
 
@@ -29,13 +29,13 @@ module H8
29
29
  # compiler instance across all threads with a mutex.
30
30
  def self.compile src, ** kwargs
31
31
  @@mutex.synchronize {
32
- (@@compiler ||= Coffee.new).compile src, ** kwargs
32
+ (@@compiler ||= Coffee.new).compile src, **kwargs
33
33
  }
34
34
  end
35
35
 
36
36
  # Create compiler instance.
37
37
  def initialize
38
- @context = H8::Context.new
38
+ @context = H8::Context.new noglobals: true
39
39
  @context.eval read_script 'coffee-script.js'
40
40
  eval read_script('globals.coffee')
41
41
  end
@@ -70,5 +70,4 @@ module H8
70
70
  end
71
71
  end
72
72
 
73
-
74
73
  end
@@ -1,14 +1,17 @@
1
1
  require 'thread'
2
+ require 'h8'
2
3
 
3
4
  module H8
5
+
4
6
  class Context
5
7
  # Create new context optionally providing variables hash
6
- def initialize **kwargs
8
+ def initialize noglobals: false, **kwargs
7
9
  @idcount = 0
8
10
  set_all **kwargs
9
11
  _set_var '___create_ruby_class', -> (cls, args) {
10
12
  _do_create_ruby_class cls, args
11
13
  }
14
+ # noglobals or execute_script 'globals.coffee'
12
15
  end
13
16
 
14
17
  # set variables from keyword arguments to this context
@@ -48,13 +51,13 @@ module H8
48
51
  # If you need to execute same script more than once consider first H8::Coffee.compile
49
52
  # and cache compiled script.
50
53
  def coffee script, ** kwargs
51
- eval Coffee.compile script, **kwargs
54
+ eval Coffee.compile script, ** kwargs
52
55
  end
53
56
 
54
57
 
55
58
  # Execute script in a new context with optionally set vars. @see H8#set_all
56
59
  # @return [Value] wrapped object returned by the script
57
- def self.eval script, file_name: nil, **kwargs
60
+ def self.eval script, file_name: nil, ** kwargs
58
61
  Context.new(** kwargs).eval script, file_name: file_name
59
62
  end
60
63
 
@@ -67,12 +70,12 @@ module H8
67
70
  def self.secure_call instance, method, args=nil
68
71
  method = method.to_sym
69
72
  begin
70
- m = instance.public_method(method)
73
+ m = instance.public_method(method)
71
74
  owner = m.owner
72
75
  if can_access?(owner)
73
76
  return m.call(*args) if method[0] == '[' || method[-1] == '='
74
77
  if m.arity != 0
75
- return -> (*args) { m.call *args }
78
+ return ProcGate.new( -> (*args) { m.call *args } )
76
79
  else
77
80
  return m.call
78
81
  end
@@ -80,7 +83,7 @@ module H8
80
83
  rescue NameError
81
84
  # No exact method, calling []/[]= if any
82
85
  method, args = if method[-1] == '='
83
- [:[]=, [method[0..-2].to_s, args[0]] ]
86
+ [:[]=, [method[0..-2].to_s, args[0]]]
84
87
  else
85
88
  [:[], [method.to_s]]
86
89
  end
@@ -88,7 +91,11 @@ module H8
88
91
  m = instance.public_method(method)
89
92
  if can_access?(owner)
90
93
  if method == :[]
91
- return m.call(*args) || m.call(args[0].to_sym)
94
+ if instance.is_a?(Hash)
95
+ return m.call(*args) || m.call(args[0].to_sym)
96
+ else
97
+ return m.call(*args)
98
+ end
92
99
  else
93
100
  return m.call(*args)
94
101
  end
@@ -115,10 +122,13 @@ module H8
115
122
  # Set var that could be either a callable, class instance, simple value or a Class class
116
123
  # in which case constructor function will be created
117
124
  def set_var name, value
118
- if value.is_a?(Class)
119
- _gate_class name.to_s, -> (*args) { value.new *args }
120
- else
121
- _set_var name, value
125
+ case value
126
+ when Class
127
+ _gate_class name.to_s, -> (*args) { value.new *args }
128
+ when Proc
129
+ _set_var name, ProcGate.new(value)
130
+ else
131
+ _set_var name, value
122
132
  end
123
133
  end
124
134
 
@@ -127,6 +137,36 @@ module H8
127
137
  def _do_create_ruby_class(klass, arguments)
128
138
  klass.new *H8::arguments_to_a(arguments.to_ruby.values)
129
139
  end
140
+
141
+
142
+ @@base = File.expand_path File.join(File.dirname(__FILE__), '../scripts')
143
+ @@cache = {}
144
+
145
+ def execute_script name
146
+ p [:exs, name]
147
+ script = @@cache[name] ||= begin
148
+ p 'cache miss'
149
+ script = open(File.join(@@base, name), 'r').read
150
+ name.downcase.end_with?('.coffee') ? H8::Coffee.compile(script) : script
151
+ end
152
+ eval script
153
+ end
154
+
155
+ end
156
+
157
+ # The gate for Ruby's callable to support javascript's 'apply' functionality
158
+ class ProcGate
159
+ def initialize callable
160
+ @callable = callable
161
+ end
162
+
163
+ def apply this, args
164
+ @callable.call *args
165
+ end
166
+
167
+ def call *args
168
+ @callable.call *args
169
+ end
130
170
  end
131
171
 
132
172
  end
@@ -1,3 +1,5 @@
1
+ require 'ostruct'
2
+
1
3
  module H8
2
4
 
3
5
  # Wrapper for javascript objects.
@@ -179,6 +181,37 @@ module H8
179
181
 
180
182
  end
181
183
 
184
+ class OpenStruct
185
+ # OpenStruct converts to plain ruby hash in depth. Primary usage
186
+ # is when it was used bu javascript and could contain gated objects.
187
+ def to_ruby depth=0
188
+ to_h.to_ruby depth+1
189
+ end
190
+ end
191
+
192
+ class Hash
193
+ # Hash copies in depth converting its data. Primary usage
194
+ # is when it was used bu javascript and could contain gated objects.
195
+ #
196
+ # Important! that converted keys are turned to string even of were
197
+ # pure ruby symbols. This is done to remove ambiguity: work the same
198
+ # with ruby hashes, javasctipt objects, OpenStruct and Hashie::Mash instances
199
+ def to_ruby depth=0
200
+ res = {}
201
+ depth += 1
202
+ each { |k,v| res[k.to_ruby(depth).to_s] = v.to_ruby depth }
203
+ res
204
+ end
205
+ end
206
+
207
+ class Array
208
+ # @return new array with all components converted to_ruby
209
+ def to_ruby depth
210
+ depth += 1
211
+ map { |x| x.to_ruby depth }
212
+ end
213
+ end
214
+
182
215
  # Ruby object's t_ruby does nothing (tree conversion optimization)
183
216
  class Object
184
217
  # It is already a ruby object. Gate objects should override
@@ -1,3 +1,3 @@
1
1
  module H8
2
- VERSION = "0.4.5"
2
+ VERSION = "0.4.8"
3
3
  end
@@ -1,4 +1,5 @@
1
1
  @.puts ?= ->
2
2
  # Do nothing if not set
3
3
 
4
- @.globalsIncluded = true
4
+ @globalsIncluded = true
5
+
@@ -35,7 +35,7 @@ describe 'coffeescript' do
35
35
  # above is bad
36
36
  END
37
37
  # pending
38
- expect(->{
38
+ expect(-> {
39
39
  H8::Coffee.compile script, file_name: 'test.coffee'
40
40
  }).to raise_error(H8::JsError) { |e| e.to_s.should =~ /test.coffee\:4/ }
41
41
  end
@@ -63,15 +63,49 @@ describe 'coffeescript' do
63
63
  // CoffeeScript.sourceMaps['inner'] = res.sourceMap
64
64
  eval(res.js);
65
65
  END
66
- cxt = H8::Coffee.new.context
66
+ cxt = H8::Coffee.new.context
67
67
  cxt[:puts] = -> (*args) { puts args.join(' ') }
68
- cxt[:src] = src
68
+ cxt[:src] = src
69
69
  # cxt[:src] = 'return "hello"'
70
70
  begin
71
- res = cxt.eval script, file_name: 'extest.coffee'
72
- rescue Exception=>e
71
+ res = cxt.eval script, file_name: 'extest.coffee'
72
+ rescue Exception => e
73
73
  puts e
74
74
  end
75
75
  end
76
76
 
77
+ context 'realword' do
78
+ class Room
79
+ end
80
+
81
+ it 'should process varargs' do
82
+ c = H8::Context.new
83
+ c[:puts] = ->(*args) { puts "> "+args.join('') }
84
+ c.coffee <<-End
85
+ @.fn1 = (args...) ->
86
+ args.join(',')
87
+
88
+ @.test = (args...) ->
89
+ fn1 'first', args...
90
+ End
91
+ c.coffee('return test(1,2,3);').should == 'first,1,2,3'
92
+
93
+ # Real world example
94
+ c[:r3] = -> (*args) {
95
+ @last_args = args
96
+ }
97
+ c[:r4] = -> (first, second=0) {
98
+ [first+100, second]
99
+ }
100
+ script = <<-End
101
+ @r1 = (args...) ->
102
+ r3 'done', args[0..-2]...
103
+ @r2 = (args...) ->
104
+ r4 args...
105
+ End
106
+ c.coffee script
107
+ c.eval('r1( "now", 1, 2, 4);').should == ['done', 'now', 1, 2]
108
+ c.eval('r2( 100, 200);').should == [200, 200]
109
+ end
110
+ end
77
111
  end
@@ -1,5 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'h8'
3
+ require 'ostruct'
4
+ require 'hashie'
3
5
 
4
6
  describe 'ruby gate' do
5
7
 
@@ -173,7 +175,7 @@ describe 'ruby gate' do
173
175
  end
174
176
 
175
177
  class Test < Base
176
- attr :ro
178
+ attr :ro, :val
177
179
  attr_accessor :rw
178
180
 
179
181
  def initialize
@@ -216,7 +218,7 @@ describe 'ruby gate' do
216
218
  end
217
219
 
218
220
  it 'should access object properties and methods' do
219
- cxt = H8::Context.new
221
+ cxt = H8::Context.new
220
222
  cxt.eval('RubyGate.prototype.test2 = function() { return "ttt"; }; null;');
221
223
  cxt[:foo] = Test.new
222
224
  cxt.eval('foo.ro').should == 'readonly'
@@ -231,19 +233,11 @@ describe 'ruby gate' do
231
233
  cxt.eval('foo.object_id').should == H8::Undefined
232
234
  cxt.eval('foo.prot_method').should == H8::Undefined
233
235
  cxt.eval('foo.priv_method').should == H8::Undefined
234
- cxt.eval('foo.test_args').should be_kind_of(Proc)
236
+ cxt.eval('foo.test_args').should be_kind_of(H8::ProcGate)
235
237
  cxt.eval('foo.test_args("hi", "you")').should == 'hi-you'
236
238
  cxt.eval('foo instanceof RubyGate').should == true
237
239
  end
238
240
 
239
- it 'should set ruby properties' do
240
- cxt = H8::Context.new
241
- cxt[:foo] = t = Test.new
242
- cxt.eval('foo.rw="hello";')
243
- t.rw.should == 'hello'
244
- cxt.eval('foo.rw').should == 'hello'
245
- end
246
-
247
241
  context 'do interceptors' do
248
242
  class Test2 < Base
249
243
  attr :ro
@@ -280,7 +274,7 @@ describe 'ruby gate' do
280
274
  cxt.eval('foo.send').should == H8::Undefined
281
275
  cxt.eval('foo.prot_method').should == H8::Undefined
282
276
  cxt.eval('foo.priv_method').should == H8::Undefined
283
- cxt.eval('foo.test_args').should be_kind_of(Proc)
277
+ cxt.eval('foo.test_args').should be_kind_of(H8::ProcGate)
284
278
  cxt.eval('foo.test_args("hi", "you")').should == 'hi-you'
285
279
  end
286
280
 
@@ -294,15 +288,37 @@ describe 'ruby gate' do
294
288
  end
295
289
 
296
290
  it 'should add and intercept property access' do
297
- # pending
298
291
  t = Test.new
299
292
  t.do_throw = true
300
293
  cxt = H8::Context.new t: t
294
+
295
+ # see Test class implementation: this is a valid test
301
296
  cxt.eval("t['foo'];").should == 'init[]'
297
+ cxt.eval("t.foo").should == 'init[]'
302
298
  expect(-> { cxt.eval("t['foo1'];") }).to raise_error(RuntimeError)
303
- cxt.eval("t['foo']='bar'");
304
- # p t['foo']
305
- # p cxt.eval "t['foo'] = 'bar';"
299
+ cxt.eval("t.foo='bar'");
300
+ cxt.eval("t.foo;").should == 'bar'
301
+ t.val.should == 'bar'
302
+ end
303
+
304
+ it 'should allow adding data to ruby hash' do
305
+ [OpenStruct.new, Hashie::Mash.new].each { |s|
306
+ s.test = 'foo'
307
+ c = H8::Context.new
308
+ c[:data] = s
309
+ c[:assert] = -> (cond) { !cond and raise "assertion failed" }
310
+ c.coffee 'assert data.test == "foo"'
311
+ c.coffee 'data.test = "bar"; assert data.test == "bar"'
312
+ s.test.should == 'bar'
313
+ c.coffee 'data.foo = "baz"'
314
+ s.foo.should == 'baz'
315
+ c.coffee 'data.h = { foo: "bar", bar: { baz: 1 } }'
316
+ s.h.foo.should == 'bar'
317
+ c.coffee 'data.h.bar.baz == 1'
318
+ s.h.bar.baz.should == 1
319
+ c.coffee 'data.h.bar.arr = ["hello", { one: 2 }]'
320
+ s.to_ruby.should == { 'test' => "bar", 'foo' => "baz", 'h' => { "foo" => "bar", "bar" => { "baz" => 1, "arr" => ["hello", { "one" => 2 }] } } }
321
+ }
306
322
  end
307
323
 
308
324
  it 'should access plain arrays (provide numeric indexes)' do
@@ -341,9 +357,9 @@ describe 'ruby gate' do
341
357
  end
342
358
 
343
359
  it 'should gate classes through API' do
344
- c = H8::Context.new
360
+ c = H8::Context.new
345
361
  la = -> (*args) {
346
- { 'hello' => 'world'}
362
+ { 'hello' => 'world' }
347
363
  }
348
364
  c._gate_class 'Tec', la
349
365
  c.eval("var res = new Tec()")
@@ -372,6 +388,10 @@ describe 'ruby gate' do
372
388
  self
373
389
  end
374
390
 
391
+ def testm a1, a2='???'
392
+ "#{a1} - #{a2}"
393
+ end
394
+
375
395
  def to_str
376
396
  inspect
377
397
  end
@@ -391,18 +411,24 @@ describe 'ruby gate' do
391
411
  cxt.eval('rc.init_args').should == ['hello', 'world']
392
412
  end
393
413
 
414
+ it 'should provide apply to gated class and instance' do
415
+ c = H8::Context.new RClass: Gated
416
+ c.eval('new RClass().testm(1,"d");').should == '1 - d'
417
+ c.eval('new RClass().testm.apply( null, [1,"n"]);').should == '1 - n'
418
+ end
419
+
394
420
  it 'should not die on calling wrong arity' do
395
421
  cxt = H8::Context.new RClass: Gated
396
- g1 = cxt.eval 'var g1 = new RClass(1,2.3); g1'
422
+ g1 = cxt.eval 'var g1 = new RClass(1,2.3); g1'
397
423
 
398
424
  # We call gated ruby object with wrong number of args
399
425
  # which in turn causes attempt to call not callable result:
400
- expect(-> {cxt.eval('g1.checkself(12)')}).to raise_error(NoMethodError)
426
+ expect(-> { cxt.eval('g1.checkself(12)') }).to raise_error(NoMethodError)
401
427
  end
402
428
 
403
429
  it 'should return self from gated class' do
404
430
  cxt = H8::Context.new RClass: Gated
405
- g1 = cxt.eval 'var g1 = new RClass(1,2.3); g1'
431
+ g1 = cxt.eval 'var g1 = new RClass(1,2.3); g1'
406
432
  g1.should be_a(Gated)
407
433
  g2 = cxt.eval 'g1.checkself'
408
434
  g2.should be_a(Gated)
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.5
4
+ version: 0.4.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - sergeych
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-21 00:00:00.000000000 Z
11
+ date: 2015-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: hashie
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 0.1.2
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.1.2
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: pargser
71
85
  requirement: !ruby/object:Gem::Requirement