opal-js_wrap-three 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ require "js_wrap"
2
+ require "js_wrap/three/three" # JavaScript
3
+
4
+ Three = JSWrap::ClassView.wrap(`Three`)
@@ -0,0 +1,720 @@
1
+ # helpers: native, defineProperty
2
+
3
+ `var inspect_stack = []`
4
+
5
+ # JSWrap is basically a Native v2. It has the same aims and goals. It should
6
+ # be interoperable with Native, ie. both modules can coexist.
7
+
8
+ # TODO: DSL methods:
9
+ # - js_method
10
+ # - (
11
+ # how about something like: js_method :set_timeout => [:$block, :timeout]
12
+ # which would allow us also to call it using any of the following:
13
+ # `Window.set_timeout(timeout: 123) { p "Hello!" }`
14
+ # `Window.set_timeout(123) { p "Hello!" }`
15
+ # `Window.set_timeout(-> { p "Hello" }, 123)`
16
+ # )
17
+ # TODO: support for restricted values
18
+ # TODO: to_js
19
+
20
+ module JSWrap
21
+ module WrapperClassMethods
22
+ # By default, we will transfer calls and properties like
23
+ # "my_method" into "myMethod". Since, unlike with Native, we
24
+ # inherit from Object, not BasicObject, some call names may
25
+ # conflict, but we can use names like "class_" instead of
26
+ # "class". This setting allows us to disable this behavior.
27
+ attr_accessor :js_raw_naming
28
+
29
+ # The default behavior when dispatching to a function is to
30
+ # call it and return a result.
31
+ attr_accessor :js_function_wrap
32
+
33
+ # The default behavior for things based on ObjectWrapper is to
34
+ # automatically wrap even the regular Arrays, so that we may
35
+ # correctly wrap all members.
36
+ #
37
+ # The default behavior for things based on Wrapper is to never
38
+ # wrap the regular Arrays, only Array-like things. It is the
39
+ # responsibility of the programmer to provide a good interface.
40
+ #
41
+ # This property allows you to change the default behavior.
42
+ attr_accessor :js_array_wrap
43
+
44
+ def js_attr_reader(*vars)
45
+ vars.each do |var|
46
+ define_method(var) { js_property(var) }
47
+ end
48
+ vars.length > 1 ? vars : vars.first
49
+ end
50
+
51
+ def js_attr_writer(*vars)
52
+ vars.each do |var|
53
+ define_method(:"#{var}=") { |val| js_property_set(var, val) }
54
+ end
55
+ vars.length > 1 ? vars : vars.first
56
+ end
57
+
58
+ def js_attr_accessor(*vars)
59
+ Array(js_attr_reader(*vars)) + Array(js_attr_writer(*vars))
60
+ end
61
+
62
+ def js_method(*vars)
63
+ vars.each do |var|
64
+ define_method(var) do |*args,&block|
65
+ args << block if block_given?
66
+ js_property(var, args: args)
67
+ end
68
+ end
69
+ vars.length > 1 ? vars : vars.first
70
+ end
71
+
72
+ def js_export_class(classname = name)
73
+ `Opal.global[name] = self`
74
+ end
75
+
76
+ alias js_export_module js_export_class
77
+
78
+ # Example:
79
+ #
80
+ # module Blobulizer
81
+ # include JSWrap::Wrapper
82
+ # js_export_module
83
+ #
84
+ # js_export def self.extended_blobulize(thing)
85
+ # Blobulizer::Processor.new(thing).extended_blobulize
86
+ # end
87
+ # end
88
+ #
89
+ # JS:
90
+ # Blobulizer.extendedBlobulize({hello: "world"})
91
+ def js_export(*args)
92
+ args = Array(args.first) if args.length == 1
93
+
94
+ args.each do |i|
95
+ `self[#{js_property_name_rb2js(i)}] = #{JSWrap.unwrap_proc(`method(i).to_proc`, self)}`
96
+ end
97
+ end
98
+
99
+ def wrap(object, parent = nil)
100
+ obj = allocate
101
+ obj.initialize_wrapped(object, parent)
102
+ obj
103
+ end
104
+
105
+ def js_class(js_constructor)
106
+ js_constructor = JSWrap.unwrap(js_constructor, self)
107
+ @native = js_constructor
108
+ JSWrap.register_wrapping(self, js_constructor)
109
+ end
110
+
111
+ # Also includes RawClassWrapper, but we don't have that yet.
112
+ end
113
+
114
+ module Wrapper
115
+ def self.included(klass)
116
+ klass.extend(WrapperClassMethods)
117
+ super
118
+ end
119
+
120
+ attr_accessor :native
121
+
122
+ def initialize_wrapped(object, parent = nil)
123
+ %x{
124
+ try {
125
+ $defineProperty(object, '$$js_wrap', self)
126
+ }
127
+ catch(e) {}
128
+ }
129
+ @native_parent = parent if parent
130
+ @native = object
131
+ end
132
+
133
+ def initialize(*args, &block)
134
+ raise NotImplementedError, 'See [doc:JSWrap::Wrapper#initialize]' unless `#{self.class}.native`
135
+
136
+ args << block if block_given?
137
+ args = JSWrap.unwrap_ary(args, self)
138
+ obj = `new (self.$$class.native.bind.apply(self.$$class.native, [null].concat(args)))`
139
+ initialize_wrapped(obj)
140
+ end
141
+
142
+ # @private
143
+ def js_property_name_rb2js(name, raw: nil)
144
+ raw = self.js_function_wrap if raw.nil?
145
+ if raw || name.start_with?(/[A-Z]/)
146
+ name
147
+ else
148
+ name.gsub(/_(.?)/) { Regexp.last_match(1).upcase }
149
+ end
150
+ end
151
+
152
+ # @private
153
+ def js_property_name_js2rb(name, raw: nil)
154
+ raw = self.js_function_wrap if raw.nil?
155
+ if raw || name.start_with?(/[A-Z]/)
156
+ name
157
+ else
158
+ name.gsub(/([A-Z])/) { '_' + Regexp.last_match(1).downcase }
159
+ end
160
+ end
161
+
162
+ def js_properties(not_just_own = true, raw: nil)
163
+ %x{
164
+ var out = [];
165
+ for (var name in self.native) {
166
+ if (!not_just_own || self.native.hasOwnProperty(name)) {
167
+ #{`out` << js_property_name_js2rb(`name`, raw: raw)}
168
+ }
169
+ }
170
+ return out;
171
+ }
172
+ end
173
+
174
+ def js_property?(name, raw: nil)
175
+ %x{
176
+ return #{js_property_name_rb2js(name, raw: raw)} in self.native
177
+ }
178
+ end
179
+
180
+ # Returns a wrapped property. If args are nil, functions are
181
+ # always returned verbatim.
182
+ def js_property(name, raw: nil, args: nil)
183
+ JSWrap(`self.native[#{
184
+ js_property_name_rb2js(name, raw: raw)
185
+ }]`,
186
+ self, args, name: name
187
+ )
188
+ end
189
+
190
+ def js_property_set(name, value, raw: nil)
191
+ `self.native[#{
192
+ js_property_name_rb2js(name, raw: raw)
193
+ }] = #{JSWrap.unwrap(value, self)}`
194
+ end
195
+
196
+ def js_raw_naming
197
+ @js_raw_naming.nil? ? self.class.js_raw_naming : @js_raw_naming
198
+ end
199
+
200
+ def js_function_wrap
201
+ @js_function_wrap.nil? ? self.class.js_function_wrap : @js_function_wrap
202
+ end
203
+
204
+ def js_array_wrap
205
+ @js_array_wrap.nil? ? self.class.js_array_wrap : @js_array_wrap
206
+ end
207
+
208
+ def to_js
209
+ @native
210
+ end
211
+
212
+ alias to_n to_js
213
+ end
214
+
215
+ module ObjectWrapper
216
+ include Enumerable
217
+ include Wrapper
218
+
219
+ alias [] js_property
220
+
221
+ def []=(name, kwargs = {}, value)
222
+ js_property_set(name, value, **kwargs)
223
+ end
224
+
225
+ alias keys js_properties
226
+ alias has? js_property
227
+ alias include? js_property
228
+
229
+ def values
230
+ to_h.values
231
+ end
232
+
233
+ def each(&block)
234
+ return enum_for(:each) { length } unless block_given?
235
+
236
+ %x{
237
+ for (var name in self.native) {
238
+ Opal.yieldX(block, [
239
+ #{js_property_name_js2rb `name`},
240
+ #{JSWrap(`self.native[name]`, self)}
241
+ ]);
242
+ }
243
+ }
244
+ self
245
+ end
246
+
247
+ def inspect
248
+ if `inspect_stack`.include?(__id__)
249
+ inside = '...'
250
+ else
251
+ inside = to_h.inspect
252
+ `inspect_stack` << __id__
253
+ pushed = true
254
+ end
255
+ klass = `Object.prototype.toString.apply(self.native)`
256
+ klass =~ /^\[object (.*?)\]$/
257
+ "#<#{self.class.name} #{Regexp.last_match(1) || klass} #{inside}>"
258
+ ensure
259
+ `inspect_stack.pop()` if pushed
260
+ end
261
+
262
+ def pretty_print(o)
263
+ klass = `Object.prototype.toString.apply(self.native)`
264
+ klass =~ /^\[object (.*?)\]$/
265
+ o.group(1, "#<#{self.class.name} #{Regexp.last_match(1) || klass}", '>') do
266
+ o.breakable
267
+ o.pp(to_h)
268
+ end
269
+ end
270
+
271
+ def method_missing(method, *args, &block)
272
+ if method.end_with? '='
273
+ raise ArgumentError, 'JS attr assignment needs 1 argument' if args.length != 1
274
+ js_property_set(method[0..-2], args.first)
275
+ elsif js_property?(method)
276
+ args << block if block_given?
277
+ js_property(method, args: args)
278
+ else
279
+ super
280
+ end
281
+ end
282
+
283
+ def respond_to_missing?(method, include_all = true)
284
+ if method.end_with?('=') || js_property?(method)
285
+ true
286
+ else
287
+ super
288
+ end
289
+ end
290
+
291
+ def raw(raw = true)
292
+ self.dup.tap do |i|
293
+ i.instance_variable_set(:@js_raw_naming, true)
294
+ end
295
+ end
296
+ end
297
+
298
+ module ArrayWrapper
299
+ include Enumerable
300
+ include ObjectWrapper
301
+
302
+ def each(&block)
303
+ return enum_for(:each) { length } unless block_given?
304
+
305
+ %x{
306
+ for (var i = 0; i < self.native.length; i++) {
307
+ Opal.yield1(block, #{JSWrap(`self.native[i]`, self)});
308
+ }
309
+ }
310
+ self
311
+ end
312
+
313
+ def [](key)
314
+ if key.is_a? Number
315
+ JSWrap.wrap(`self.native[key]`, self)
316
+ else
317
+ super
318
+ end
319
+ end
320
+
321
+ def []=(key, kwargs = {}, value)
322
+ if key.is_a? Number
323
+ `self.native[key] = #{JSWrap.unwrap(value, self)}`
324
+ else
325
+ super
326
+ end
327
+ end
328
+
329
+ def <<(value)
330
+ `Array.prototype.push.apply(self.native, [#{JSWrap.unwrap(value, self)}])`
331
+ self
332
+ end
333
+
334
+ alias append <<
335
+
336
+ def length
337
+ `self.native.length`
338
+ end
339
+
340
+ def inspect
341
+ if `inspect_stack`.include?(__id__)
342
+ inside = '[...]'
343
+ else
344
+ `inspect_stack` << __id__
345
+ pushed = true
346
+ inside = to_a.inspect
347
+ end
348
+ klass = `Object.prototype.toString.apply(self.native)`
349
+ klass =~ /^\[object (.*?)\]$/
350
+ "#<#{self.class.name} #{Regexp.last_match(1) || klass} #{inside}>"
351
+ ensure
352
+ `inspect_stack.pop()` if pushed
353
+ end
354
+
355
+ def pretty_print(o)
356
+ klass = `Object.prototype.toString.apply(self.native)`
357
+ klass =~ /^\[object (.*?)\]$/
358
+ o.group(1, "#<#{self.class.name} #{Regexp.last_match(1) || klass}", '>') do
359
+ o.breakable
360
+ o.pp(to_a)
361
+ end
362
+ end
363
+ end
364
+
365
+ module FunctionWrapper
366
+ include ObjectWrapper
367
+
368
+ def call(*args, &block)
369
+ JSWrap.call(@native_parent, @native, *args, &block)
370
+ end
371
+
372
+ def to_proc
373
+ proc do |*args, &block|
374
+ call(*args, &block)
375
+ end
376
+ end
377
+
378
+ def inspect
379
+ if `typeof self.native.toString !== 'function'`
380
+ super
381
+ else
382
+ fundesc = `self.native.toString()`.split('{').first.strip.delete("\n")
383
+ "#<#{self.class.name} #{fundesc}>"
384
+ end
385
+ end
386
+
387
+ def pretty_print(o)
388
+ if `typeof self.native.toString !== 'function'`
389
+ super
390
+ else
391
+ o.text(inspect)
392
+ end
393
+ end
394
+ end
395
+
396
+ module RawClassWrapper
397
+ include Wrapper
398
+
399
+ def superclass
400
+ JSWrap(`#{@native}.prototype.__proto__`, self)
401
+ end
402
+ end
403
+
404
+ module ClassWrapper
405
+ include FunctionWrapper
406
+ include RawClassWrapper
407
+
408
+ def const_missing(name)
409
+ if js_property?(name)
410
+ js_property(name)
411
+ else
412
+ super
413
+ end
414
+ end
415
+
416
+ def new(*args, &block)
417
+ args << block if block_given?
418
+ args = JSWrap.unwrap_ary(args, self)
419
+ JSWrap(`new (#{@native}.bind.apply(#{@native}, [null].concat(args)))`, self)
420
+ end
421
+ end
422
+
423
+ def self.wrapped?(object)
424
+ `object != null && '$$class' in object && Opal.is_a(object, Opal.JSWrap.Wrapper)`
425
+ end
426
+
427
+ def self.unwrap(object, parent = nil)
428
+ %x{
429
+ if (object === null || object === undefined || object === nil) {
430
+ return null;
431
+ }
432
+ else if (object.$$class !== undefined) {
433
+ // Opal < 1.4 bug: Opal.respond_to vs #respond_to_missing? does not call
434
+ // #respond_to?
435
+ if (object.$to_js !== undefined && !object.$to_js.$$stub) {
436
+ return object.$to_js(parent);
437
+ }
438
+ else if (object.$to_n !== undefined && !object.$to_n.$$stub) {
439
+ return object.$to_n();
440
+ }
441
+ }
442
+ return object;
443
+ }
444
+ end
445
+
446
+ def self.unwrap_ary(ary, parent = nil)
447
+ %x{
448
+ var i;
449
+ for (i = 0; i < ary.length; i++) {
450
+ ary[i] = #{unwrap(ary[`i`], parent)}
451
+ }
452
+ return ary;
453
+ }
454
+ end
455
+
456
+ def self.unwrap_proc(proc, parent = nil)
457
+ %x{
458
+ var f = (function() {
459
+ var i, ary = Array.prototype.slice.apply(arguments), ret;
460
+ for (i = 0; i < ary.length; i++) {
461
+ ary[i] = #{wrap(`ary[i]`, parent)}
462
+ }
463
+ ret = proc.apply(null, ary);
464
+ return #{unwrap(`ret`, parent)}
465
+ });
466
+ f.$$proc_unwrapped = proc;
467
+ return f;
468
+ }
469
+ end
470
+
471
+ def self.wrap(object, parent = nil, args = nil, name: nil)
472
+ %x{
473
+ var i, out, wrapping;
474
+ for (i = self.wrappings.length - 1; i >= 0; i--) {
475
+ wrapping = self.wrappings[i];
476
+ if (wrapping.block !== nil) {
477
+ out = #{`wrapping.block`.call(object, parent, args, name)}
478
+ if (out == null) return nil;
479
+ else if (out !== nil) return out;
480
+ }
481
+ else if (object instanceof wrapping.js_constructor) {
482
+ return #{`wrapping.opal_klass`.wrap(`object`)}
483
+ }
484
+ }
485
+ }
486
+ end
487
+
488
+ def self.call(parent, item, *args, &block)
489
+ args << block if block_given?
490
+ orig_parent = parent
491
+ parent = unwrap(parent, orig_parent)
492
+ args = unwrap_ary(args, orig_parent)
493
+ %x{
494
+ if (typeof item !== 'function') {
495
+ item = parent[item];
496
+ }
497
+ item = item.apply(parent, args);
498
+ return #{wrap(item, orig_parent)}
499
+ }
500
+ end
501
+
502
+ @wrappings = []
503
+ def self.register_wrapping(opal_klass = undefined, js_constructor = undefined, priority: 0, &block)
504
+ @wrappings << `{
505
+ priority: priority,
506
+ opal_klass: opal_klass,
507
+ js_constructor: js_constructor,
508
+ block: block
509
+ }`
510
+ @wrappings.sort_by!(&`function(i){
511
+ return -i.priority;
512
+ }`
513
+ )
514
+ end
515
+
516
+ module WrapperClassMethods
517
+ include RawClassWrapper
518
+ end
519
+
520
+ class ObjectView
521
+ extend WrapperClassMethods
522
+ include ObjectWrapper
523
+ self.js_array_wrap = true
524
+ undef Array, String
525
+ end
526
+ class ArrayView
527
+ extend WrapperClassMethods
528
+ include ArrayWrapper
529
+ self.js_array_wrap = true
530
+ end
531
+ # ClassView does not work as a class fully. This is only a limited contract.
532
+ # ie. ClassView.wrap(`BigInt`).new.class == ObjectView.
533
+ # The reason why we inherit from Class is that so we can access properties like A::B
534
+ class ClassView < Class
535
+ extend WrapperClassMethods
536
+ include ClassWrapper
537
+ self.js_array_wrap = true
538
+ end
539
+ class FunctionView
540
+ extend WrapperClassMethods
541
+ include FunctionWrapper
542
+ self.js_array_wrap = true
543
+ end
544
+ end
545
+
546
+ module Kernel
547
+ def JSWrap(*args, &block)
548
+ JSWrap.wrap(*args, &block)
549
+ end
550
+ end
551
+
552
+ JSWrap.register_wrapping(priority: -10) do |item, parent, args, name|
553
+ %x{
554
+ var type = typeof item;
555
+
556
+ var array_wrap = $truthy(parent) ? $truthy(parent.$js_array_wrap()) : true;
557
+ var function_wrap = $truthy(parent) ? $truthy(parent.$js_function_wrap()) : false;
558
+
559
+ if (type === 'undefined' || item === null || item === nil) {
560
+ // A special case not documented anywhere: null makes it dispatch a nil.
561
+ if (args !== nil && args.length !== 0) {
562
+ #{raise ArgumentError, 'given args while dispatching a null value'}
563
+ }
564
+ return null;
565
+ }
566
+ else if (type === 'symbol' || type === 'bigint') {
567
+ // As of Opal 1.4, we don't support those. Let's pretend they
568
+ // are just objects.
569
+ if (args !== nil && args.length !== 0) {
570
+ #{raise ArgumentError, "given args while dispatching a #{`type`}"}
571
+ }
572
+ return #{JSWrap::ObjectView.wrap(item, parent)}
573
+ }
574
+ else if (type === 'number' || type === 'string' || type === 'boolean') {
575
+ // Otherwise it's some primitive and it's wrapped
576
+ if (args !== nil && args.length !== 0) {
577
+ #{raise ArgumentError, "given args while dispatching a #{`type`}"}
578
+ }
579
+ return item;
580
+ }
581
+ else if ('$$js_wrap' in item) {
582
+ // Wrapping is dispatched already, we can trust it to be wrapped properly
583
+ if (args !== nil && args.length !== 0) {
584
+ #{raise ArgumentError, 'given args while dispatching an already dispatched value'}
585
+ }
586
+ return item.$$js_wrap;
587
+ }
588
+ else if (type === 'function') {
589
+ if (item.$$class === Opal.Class) {
590
+ // Opal Class
591
+ if (args !== nil && args.length !== 0) {
592
+ #{raise ArgumentError, 'given args while dispatching an Opal class'}
593
+ }
594
+ return item;
595
+ }
596
+ else if ("$$arity" in item) {
597
+ // Native Opal proc
598
+ return item;
599
+ }
600
+ else if ("$$proc_unwrapped" in item) {
601
+ // Unwrapped native Opal proc
602
+ return item.$$proc_unwrapped;
603
+ }
604
+ else if (name !== nil && 'prototype' in item && name.match(/^[A-Z]/)) {
605
+ // Class
606
+ // There is no reliable way to detect a JS class. So we check if its
607
+ // name starts with an uppercase letter.
608
+ if (args !== nil && args.length !== 0) {
609
+ #{raise ArgumentError, 'given args while dispatching a class'}
610
+ }
611
+ return #{JSWrap::ClassView.wrap(item, parent)}
612
+ }
613
+ else {
614
+ // Regular function
615
+ if (function_wrap || args === nil) {
616
+ return #{JSWrap::FunctionView.wrap(item, parent)}
617
+ }
618
+ else {
619
+ var ret = #{JSWrap.call(parent, item, *args)}
620
+ return ret === nil ? null : ret;
621
+ }
622
+ }
623
+ }
624
+ else if (type === 'object') {
625
+ if (item instanceof Array) {
626
+ // A regular array
627
+ if (args !== nil && args.length !== 0) {
628
+ #{raise ArgumentError, 'given args while dispatching an array'}
629
+ }
630
+ if (array_wrap) {
631
+ return #{JSWrap::ArrayView.wrap(item, parent)}
632
+ }
633
+ else {
634
+ return item;
635
+ }
636
+ }
637
+ else if ('$$class' in item) {
638
+ // Opal item
639
+ if (args !== nil && args.length !== 0) {
640
+ #{raise ArgumentError, 'given args while dispatching an Opal object'}
641
+ }
642
+ return item;
643
+ }
644
+ else {
645
+ // Pass.
646
+ return nil;
647
+ }
648
+ }
649
+ else {
650
+ #{raise ArgumentError, "unknown value type #{type}"}
651
+ }
652
+ }
653
+ end
654
+
655
+ # Now custom wrappers run... but if nothing is found... we run a
656
+ # final wrapper:
657
+
658
+ JSWrap.register_wrapping(priority: 10) do |item, parent, args|
659
+ %x{
660
+ if (args !== nil && args.length !== 0) {
661
+ #{raise ArgumentError, 'given args while dispatching an object'}
662
+ }
663
+ else if ('length' in item) {
664
+ // A pseudo-array, we always wrap those
665
+ return #{JSWrap::ArrayView.wrap(item, parent)}
666
+ }
667
+ else {
668
+ // Otherwise it is a regulat object
669
+ return #{JSWrap::ObjectView.wrap(item, parent)}
670
+ }
671
+ }
672
+ end
673
+
674
+ class Hash
675
+ # @return a JavaScript object with the same keys but calling #to_n on
676
+ # all values.
677
+ def to_js(parent=nil)
678
+ %x{
679
+ var result = {},
680
+ keys = self.$$keys,
681
+ smap = self.$$smap,
682
+ key, value;
683
+ for (var i = 0, length = keys.length; i < length; i++) {
684
+ key = keys[i];
685
+ if (key.$$is_string) {
686
+ value = smap[key];
687
+ } else {
688
+ key = key.key;
689
+ value = key.value;
690
+ }
691
+ key = #{parent.js_property_name_rb2js(`key`)}
692
+ result[key] = #{JSWrap.unwrap(`value`, parent)}
693
+ }
694
+ return result;
695
+ }
696
+ end
697
+ end
698
+
699
+ class Method
700
+ def to_js(parent=nil)
701
+ JSWrap.unwrap_proc(to_proc, parent)
702
+ end
703
+ end
704
+
705
+ class Proc
706
+ def to_js(parent=nil)
707
+ %x{
708
+ // Is this a native Opal Proc? Or is it JavaScript
709
+ // created?
710
+ if ('$$arity' in self) {
711
+ return #{JSWrap.unwrap_proc(self, parent)}
712
+ }
713
+ else {
714
+ return self;
715
+ }
716
+ }
717
+ end
718
+ end
719
+
720
+ JSGlobal = JSWrap(`Opal.global`)