opal_proxy 0.1.1 → 0.2.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 +4 -4
- data/.rubocop.yml +36 -0
- data/CHANGELOG.md +14 -4
- data/README.md +35 -5
- data/lib/js/proxy.rb +164 -100
- data/lib/opal_proxy/version.rb +1 -1
- data/lib/opal_proxy.rb +3 -2
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 16f6f216939bb7ad43a3e8046eba3a468376b385b7c517e7f114ba3ed4fb6f8e
|
|
4
|
+
data.tar.gz: 2fd8b4d5b95354facae44f4978a2b8ed566ff70d005269d2ab715ab1e60a69f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 378e55ea22b7d67177c22cf580e4adf00fc92d9d76d67c3ee531fdc8a462a2471b12625639d8c7a82d213a6960b96e4a0f86511b5a5df2c733edd0da3416dbad
|
|
7
|
+
data.tar.gz: f4520e248767de0a95bb026eba5adadf06e0b0473626436421ff27759a73a4c455862a640535ccef3b0a62cb4b9b54d9108fbadb00b2907a689e2eea19e8c6cf
|
data/.rubocop.yml
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
SuggestExtensions: false
|
|
2
4
|
TargetRubyVersion: 3.1
|
|
3
5
|
|
|
6
|
+
Metrics/ClassLength:
|
|
7
|
+
Exclude:
|
|
8
|
+
- "test/**/*.rb"
|
|
9
|
+
Max: 150
|
|
10
|
+
|
|
11
|
+
Metrics/MethodLength:
|
|
12
|
+
Exclude:
|
|
13
|
+
- "lib/js/**/*.rb"
|
|
14
|
+
- "test/**/*.rb"
|
|
15
|
+
|
|
16
|
+
Lint/LiteralAsCondition:
|
|
17
|
+
Exclude:
|
|
18
|
+
- "lib/js/**/*.rb"
|
|
19
|
+
|
|
20
|
+
Lint/UnusedMethodArgument:
|
|
21
|
+
Exclude:
|
|
22
|
+
- "lib/js/**/*.rb"
|
|
23
|
+
|
|
24
|
+
Lint/UselessAssignment:
|
|
25
|
+
Exclude:
|
|
26
|
+
- "lib/js/**/*.rb"
|
|
27
|
+
|
|
28
|
+
Lint/Void:
|
|
29
|
+
Exclude:
|
|
30
|
+
- "lib/js/**/*.rb"
|
|
31
|
+
|
|
32
|
+
Naming/PredicateMethod:
|
|
33
|
+
Exclude:
|
|
34
|
+
- "lib/js/**/*.rb"
|
|
35
|
+
|
|
36
|
+
Style/CommandLiteral:
|
|
37
|
+
Exclude:
|
|
38
|
+
- "lib/js/**/*.rb"
|
|
39
|
+
|
|
4
40
|
Style/StringLiterals:
|
|
5
41
|
EnforcedStyle: double_quotes
|
|
6
42
|
|
data/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
-
## [0.
|
|
3
|
+
## [0.2.0] - 2026-08-19
|
|
4
|
+
|
|
5
|
+
- Preserve `JS::Promise` wrappers across `then` and `catch` chains.
|
|
6
|
+
- Wrap callback receivers and arguments, and unwrap callback return values.
|
|
7
|
+
- Support JavaScript iterables and array-like objects through `Enumerable`.
|
|
8
|
+
- Make `respond_to?` reflect native property availability.
|
|
9
|
+
- Improve exact, camelCase, and acronym-aware property lookup.
|
|
10
|
+
- Refresh native property introspection for dynamically added properties.
|
|
11
|
+
- Add deterministic browser coverage without external network dependencies.
|
|
4
12
|
|
|
5
|
-
-
|
|
13
|
+
## [0.1.1] - 2025-08-08
|
|
6
14
|
|
|
7
|
-
|
|
15
|
+
- Add `[]=` method to Proxy.
|
|
16
|
+
|
|
17
|
+
## [0.1.0] - 2025-07-24
|
|
8
18
|
|
|
9
|
-
-
|
|
19
|
+
- Initial release.
|
data/README.md
CHANGED
|
@@ -58,14 +58,44 @@ window.set_timeout(-> {
|
|
|
58
58
|
puts "1. Timeout test OK (1s delay)"
|
|
59
59
|
}, 1000)
|
|
60
60
|
window.fetch("https://jsonplaceholder.typicode.com/todos/1")
|
|
61
|
-
.then
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
end
|
|
61
|
+
.then { |response| response.json }
|
|
62
|
+
.then do |data|
|
|
63
|
+
puts "5. Fetched: #{data["title"]}"
|
|
64
|
+
document.get_element_by_id("output").inner_html += "<p>5. Fetched: #{data["title"]}</p>"
|
|
66
65
|
end
|
|
66
|
+
.catch { |error| warn error["message"] }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Promise callbacks automatically unwrap returned `JS::Proxy` and `JS::Promise` values,
|
|
70
|
+
so native Promise flattening works across chained `then`, `catch`, and `finally` calls.
|
|
71
|
+
|
|
72
|
+
## Interop behavior
|
|
73
|
+
|
|
74
|
+
Property lookup tries the original name first, then camelCase and common JavaScript
|
|
75
|
+
acronyms such as `HTML`, `URL`, and `URI`. For example, both `inner_html` and
|
|
76
|
+
`document_uri` map to their DOM equivalents.
|
|
77
|
+
|
|
78
|
+
JavaScript properties that collide with Ruby methods can always be accessed with `[]`:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
proxy["count"]
|
|
82
|
+
proxy["class"]
|
|
67
83
|
```
|
|
68
84
|
|
|
85
|
+
When a Ruby block is passed to a JavaScript method, it is appended as the final callback
|
|
86
|
+
argument. The callback receives its JavaScript `this` value first, followed by the native
|
|
87
|
+
callback arguments, all wrapped when necessary:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
target.add_event_listener("click") do |receiver, event|
|
|
91
|
+
event.prevent_default
|
|
92
|
+
puts receiver
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`JS::Proxy` is enumerable for JavaScript iterable and array-like objects, including
|
|
97
|
+
arrays, `Set`, `Map`, `NodeList`, and objects with numeric indexes and a `length`.
|
|
98
|
+
|
|
69
99
|
## JQuery example
|
|
70
100
|
|
|
71
101
|
```ruby
|
data/lib/js/proxy.rb
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
# backtick_javascript: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
1
4
|
require "opal"
|
|
2
5
|
require "native"
|
|
3
6
|
|
|
4
7
|
module JS
|
|
8
|
+
# Shared conversion helpers for values crossing the Ruby/JavaScript boundary.
|
|
5
9
|
module Helpers
|
|
6
10
|
def wrap_result(result)
|
|
7
|
-
if `result
|
|
11
|
+
return nil if `result == null`
|
|
12
|
+
|
|
13
|
+
if `typeof result.then === "function" && !result.then.$$owner`
|
|
8
14
|
Promise.new(result)
|
|
9
|
-
elsif `
|
|
15
|
+
elsif `result instanceof Number || result instanceof String || result instanceof Boolean`
|
|
16
|
+
`result.valueOf()`
|
|
17
|
+
elsif `typeof result === "object"`
|
|
10
18
|
Proxy.new(result)
|
|
11
19
|
else
|
|
12
20
|
result
|
|
@@ -14,114 +22,151 @@ module JS
|
|
|
14
22
|
end
|
|
15
23
|
|
|
16
24
|
def native_methods
|
|
17
|
-
|
|
18
|
-
let
|
|
19
|
-
const
|
|
25
|
+
%x{
|
|
26
|
+
let object = #{to_n};
|
|
27
|
+
const properties = new Set();
|
|
28
|
+
|
|
29
|
+
while (object !== null) {
|
|
30
|
+
for (const key of Reflect.ownKeys(object)) {
|
|
31
|
+
if (typeof key === "symbol") continue;
|
|
20
32
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const stringKey = key.toString()
|
|
24
|
-
const rubyName = #{to_rb_name(`stringKey`)}
|
|
33
|
+
const nativeName = key.toString();
|
|
34
|
+
const rubyName = #{to_rb_name(`nativeName`)};
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
36
|
+
properties.add(nativeName);
|
|
37
|
+
properties.add(rubyName);
|
|
28
38
|
}
|
|
29
|
-
|
|
39
|
+
object = Object.getPrototypeOf(object);
|
|
30
40
|
}
|
|
31
41
|
|
|
32
|
-
return Array.from(
|
|
42
|
+
return Array.from(properties);
|
|
33
43
|
}
|
|
34
44
|
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def unwrap_result(result)
|
|
49
|
+
Native.try_convert(result, result)
|
|
50
|
+
end
|
|
35
51
|
end
|
|
36
52
|
|
|
53
|
+
# Provides Ruby-style access to the properties and methods of a JavaScript object.
|
|
37
54
|
class Proxy
|
|
38
55
|
include Enumerable
|
|
39
56
|
include Helpers
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
IRREGULARS = %w(html url uri)
|
|
58
|
+
IRREGULARS = %w[html url uri].freeze
|
|
44
59
|
|
|
45
60
|
def initialize(native)
|
|
46
|
-
|
|
61
|
+
self.native = native
|
|
47
62
|
end
|
|
48
63
|
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
unless existing_property?(js_name) || js_name.end_with?("=")
|
|
53
|
-
raise NoMethodError, "undefined method `#{name}` for #{self}"
|
|
54
|
-
end
|
|
64
|
+
def native
|
|
65
|
+
Native(to_n)
|
|
66
|
+
end
|
|
55
67
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
native[prop] = args.first
|
|
59
|
-
else
|
|
60
|
-
val = native[js_name]
|
|
61
|
-
|
|
62
|
-
if `typeof val === 'function'`
|
|
63
|
-
js_args = args.dup
|
|
64
|
-
|
|
65
|
-
if block
|
|
66
|
-
js_callback = %x{
|
|
67
|
-
function() {
|
|
68
|
-
let args = Array.prototype.slice.call(arguments);
|
|
69
|
-
return #{block.call(self.class.new(`this`), *args)};
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
js_args << js_callback
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
result = `val.apply(#{to_n}, #{js_args.to_n})`
|
|
76
|
-
wrap_result(result)
|
|
77
|
-
elsif `typeof val === 'object' && val !== null`
|
|
78
|
-
wrap_result(val)
|
|
79
|
-
else
|
|
80
|
-
val
|
|
81
|
-
end
|
|
82
|
-
end
|
|
68
|
+
def native=(value)
|
|
69
|
+
@native = Native.try_convert(value, value)
|
|
83
70
|
end
|
|
84
71
|
|
|
85
|
-
def
|
|
86
|
-
|
|
72
|
+
def method_missing(name, *args, &block)
|
|
73
|
+
setter = name.end_with?("=")
|
|
74
|
+
property = resolve_property_name(name, allow_missing: setter)
|
|
75
|
+
|
|
76
|
+
return super unless property
|
|
77
|
+
return write_property(property, args.first) if setter
|
|
78
|
+
|
|
79
|
+
read_property(property, args, block)
|
|
87
80
|
end
|
|
88
81
|
|
|
89
82
|
def respond_to_missing?(name, include_private = false)
|
|
90
|
-
|
|
83
|
+
setter = name.end_with?("=")
|
|
84
|
+
!!resolve_property_name(name, allow_missing: setter) || super
|
|
91
85
|
end
|
|
92
86
|
|
|
93
|
-
def each
|
|
94
|
-
return enum_for(:each) unless
|
|
87
|
+
def each(&block)
|
|
88
|
+
return enum_for(:each) unless block
|
|
95
89
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
if iterable?
|
|
91
|
+
each_iterable(&block)
|
|
92
|
+
elsif array_like?
|
|
93
|
+
each_array_like(&block)
|
|
94
|
+
else
|
|
95
|
+
raise TypeError, "#{self.class} does not wrap an iterable or array-like object"
|
|
99
96
|
end
|
|
97
|
+
|
|
98
|
+
self
|
|
100
99
|
end
|
|
101
100
|
|
|
102
101
|
def [](index)
|
|
103
|
-
|
|
104
|
-
wrap_result(val)
|
|
102
|
+
wrap_result(`#{to_n}[#{index}]`)
|
|
105
103
|
end
|
|
106
104
|
|
|
107
|
-
def []=(
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
def []=(key, value)
|
|
106
|
+
converted = unwrap_result(value)
|
|
107
|
+
`#{to_n}[#{key}] = #{converted}`
|
|
108
|
+
value
|
|
110
109
|
end
|
|
111
110
|
|
|
112
111
|
def to_n
|
|
113
|
-
native
|
|
112
|
+
@native
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def to_str
|
|
116
|
+
`String(#{to_n})`
|
|
114
117
|
end
|
|
115
118
|
|
|
116
119
|
def length
|
|
117
|
-
|
|
120
|
+
`#{to_n}.length`
|
|
118
121
|
end
|
|
119
122
|
|
|
120
123
|
private
|
|
121
124
|
|
|
122
|
-
def
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
def read_property(property, args, block)
|
|
126
|
+
value = `#{to_n}[#{property}]`
|
|
127
|
+
return wrap_result(value) unless `typeof value === "function"`
|
|
128
|
+
|
|
129
|
+
invoke_native_function(value, args, block)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def write_property(property, value)
|
|
133
|
+
converted = unwrap_result(value)
|
|
134
|
+
`#{to_n}[#{property}] = #{converted}`
|
|
135
|
+
value
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def invoke_native_function(callable, args, block)
|
|
139
|
+
arguments = args.dup
|
|
140
|
+
arguments << callback_for(block) if block
|
|
141
|
+
wrap_result(`callable.apply(#{to_n}, #{arguments.to_n})`)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def callback_for(block)
|
|
145
|
+
%x{
|
|
146
|
+
return function() {
|
|
147
|
+
const callbackArgs = Array.prototype.slice.call(arguments).map(function(argument) {
|
|
148
|
+
return #{wrap_result(`argument`)};
|
|
149
|
+
});
|
|
150
|
+
const receiver = #{wrap_result(`this`)};
|
|
151
|
+
return #{unwrap_result(block.call(`receiver`, *`callbackArgs`))};
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def resolve_property_name(name, allow_missing: false)
|
|
157
|
+
ruby_name = name.to_s.delete_suffix("=")
|
|
158
|
+
candidates = js_name_candidates(ruby_name)
|
|
159
|
+
candidates.find { |candidate| existing_property?(candidate) } ||
|
|
160
|
+
(candidates.last if allow_missing)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def js_name_candidates(name)
|
|
164
|
+
[name, camelize(name), camelize(name, acronyms: true)].uniq
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def camelize(name, acronyms: false)
|
|
168
|
+
name.split("_").map.with_index do |part, index|
|
|
169
|
+
if acronyms && IRREGULARS.include?(part.downcase)
|
|
125
170
|
part.upcase
|
|
126
171
|
else
|
|
127
172
|
index.zero? ? part : part.capitalize
|
|
@@ -132,51 +177,70 @@ module JS
|
|
|
132
177
|
def to_rb_name(name)
|
|
133
178
|
name
|
|
134
179
|
.to_s
|
|
135
|
-
.gsub(/([A-Z]+)
|
|
136
|
-
.
|
|
180
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, "\\1_\\2")
|
|
181
|
+
.gsub(/([a-z\d])([A-Z])/, "\\1_\\2")
|
|
182
|
+
.tr("-", "_")
|
|
183
|
+
.downcase
|
|
137
184
|
end
|
|
138
185
|
|
|
139
186
|
def existing_property?(property)
|
|
140
187
|
`#{property} in #{to_n}`
|
|
141
188
|
end
|
|
142
|
-
end
|
|
143
189
|
|
|
144
|
-
|
|
145
|
-
|
|
190
|
+
def iterable?
|
|
191
|
+
`typeof Symbol !== "undefined" && typeof #{to_n}[Symbol.iterator] === "function"`
|
|
192
|
+
end
|
|
146
193
|
|
|
147
|
-
def
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return ruby_result.to_n();
|
|
155
|
-
} else {
|
|
156
|
-
return ruby_result;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
194
|
+
def array_like?
|
|
195
|
+
%x{
|
|
196
|
+
const length = #{to_n}.length;
|
|
197
|
+
return typeof length === "number" &&
|
|
198
|
+
Number.isFinite(length) &&
|
|
199
|
+
length >= 0 &&
|
|
200
|
+
Math.floor(length) === length;
|
|
159
201
|
}
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def each_iterable
|
|
205
|
+
iterator = `#{to_n}[Symbol.iterator]()`
|
|
206
|
+
|
|
207
|
+
loop do
|
|
208
|
+
step = `iterator.next()`
|
|
209
|
+
break if `step.done`
|
|
160
210
|
|
|
161
|
-
|
|
211
|
+
yield wrap_result(`step.value`)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def each_array_like
|
|
216
|
+
(0...length).each { |index| yield self[index] }
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Ruby wrapper for JavaScript promises with chain-preserving return values.
|
|
221
|
+
class Promise < Proxy
|
|
222
|
+
def then(&block)
|
|
223
|
+
result = block ? `#{to_n}.then(#{promise_callback(block)})` : `#{to_n}.then()`
|
|
224
|
+
Promise.new(result)
|
|
162
225
|
end
|
|
163
226
|
|
|
164
227
|
def catch(&block)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (ruby_result && typeof ruby_result.then === 'function') {
|
|
170
|
-
return ruby_result;
|
|
171
|
-
} else if (ruby_result && typeof ruby_result.to_n === 'function') {
|
|
172
|
-
return ruby_result.to_n();
|
|
173
|
-
} else {
|
|
174
|
-
return ruby_result;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
228
|
+
result = block ? `#{to_n}.catch(#{promise_callback(block)})` : `#{to_n}.catch()`
|
|
229
|
+
Promise.new(result)
|
|
230
|
+
end
|
|
178
231
|
|
|
179
|
-
|
|
232
|
+
private
|
|
233
|
+
|
|
234
|
+
def promise_callback(block)
|
|
235
|
+
%x{
|
|
236
|
+
const proxy = #{self};
|
|
237
|
+
|
|
238
|
+
return function(value) {
|
|
239
|
+
const wrappedValue = proxy.$wrap_result(value);
|
|
240
|
+
const rubyResult = block.$call(wrappedValue);
|
|
241
|
+
return proxy.$unwrap_result(rubyResult);
|
|
242
|
+
};
|
|
243
|
+
}
|
|
180
244
|
end
|
|
181
245
|
end
|
|
182
246
|
end
|
data/lib/opal_proxy/version.rb
CHANGED
data/lib/opal_proxy.rb
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
if RUBY_ENGINE ==
|
|
3
|
+
if RUBY_ENGINE == "opal"
|
|
4
4
|
require_relative "js/proxy"
|
|
5
5
|
else
|
|
6
6
|
require "opal"
|
|
7
7
|
require_relative "opal_proxy/version"
|
|
8
8
|
|
|
9
|
-
Opal.append_path File.expand_path(
|
|
9
|
+
Opal.append_path File.expand_path("lib", __dir__)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
# Namespace for Opal Proxy versioning and Ruby-side integration.
|
|
12
13
|
module OpalProxy
|
|
13
14
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: opal_proxy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joseph Schito
|
|
@@ -24,8 +24,7 @@ dependencies:
|
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: 1.8.2
|
|
26
26
|
description: Opal Proxy provides a dynamic interface to JavaScript objects in Opal,
|
|
27
|
-
|
|
28
|
-
Ruby syntax.
|
|
27
|
+
with idiomatic property access, method calls, and Promise handling.
|
|
29
28
|
email:
|
|
30
29
|
- joseph.schito@gmail.com
|
|
31
30
|
executables: []
|
|
@@ -48,6 +47,7 @@ metadata:
|
|
|
48
47
|
homepage_uri: https://github.com/josephschito/opal_proxy
|
|
49
48
|
source_code_uri: https://github.com/josephschito/opal_proxy
|
|
50
49
|
changelog_uri: https://github.com/josephschito/opal_proxy/blob/main/CHANGELOG.md
|
|
50
|
+
rubygems_mfa_required: 'true'
|
|
51
51
|
rdoc_options: []
|
|
52
52
|
require_paths:
|
|
53
53
|
- lib
|
|
@@ -62,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
62
62
|
- !ruby/object:Gem::Version
|
|
63
63
|
version: '0'
|
|
64
64
|
requirements: []
|
|
65
|
-
rubygems_version:
|
|
65
|
+
rubygems_version: 4.0.16
|
|
66
66
|
specification_version: 4
|
|
67
67
|
summary: Dynamic Ruby-style wrapper for JavaScript objects in Opal.
|
|
68
68
|
test_files: []
|