jsi 0.0.4 → 0.4.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/.simplecov +3 -1
- data/CHANGELOG.md +48 -0
- data/LICENSE.md +613 -0
- data/README.md +84 -45
- data/jsi.gemspec +11 -14
- data/lib/jsi.rb +31 -12
- data/lib/jsi/base.rb +310 -344
- data/lib/jsi/base/to_rb.rb +2 -0
- data/lib/jsi/jsi_coder.rb +91 -0
- data/lib/jsi/json-schema-fragments.rb +3 -135
- data/lib/jsi/json.rb +3 -0
- data/lib/jsi/json/node.rb +72 -197
- data/lib/jsi/json/pointer.rb +419 -0
- data/lib/jsi/metaschema.rb +7 -0
- data/lib/jsi/metaschema_node.rb +218 -0
- data/lib/jsi/pathed_node.rb +118 -0
- data/lib/jsi/schema.rb +168 -223
- data/lib/jsi/schema_classes.rb +158 -0
- data/lib/jsi/simple_wrap.rb +12 -0
- data/lib/jsi/typelike_modules.rb +71 -45
- data/lib/jsi/util.rb +47 -57
- data/lib/jsi/version.rb +1 -1
- data/lib/schemas/json-schema.org/draft-04/schema.rb +7 -0
- data/lib/schemas/json-schema.org/draft-06/schema.rb +7 -0
- data/resources/icons/AGPL-3.0.png +0 -0
- data/test/base_array_test.rb +210 -84
- data/test/base_hash_test.rb +201 -58
- data/test/base_test.rb +212 -121
- data/test/jsi_coder_test.rb +85 -0
- data/test/jsi_json_arraynode_test.rb +26 -25
- data/test/jsi_json_hashnode_test.rb +40 -39
- data/test/jsi_json_node_test.rb +95 -126
- data/test/jsi_json_pointer_test.rb +102 -0
- data/test/jsi_typelike_as_json_test.rb +53 -0
- data/test/metaschema_node_test.rb +19 -0
- data/test/schema_module_test.rb +21 -0
- data/test/schema_test.rb +109 -97
- data/test/spreedly_openapi_test.rb +8 -0
- data/test/test_helper.rb +42 -8
- data/test/util_test.rb +14 -14
- metadata +54 -25
- data/LICENSE.txt +0 -21
- data/lib/jsi/schema_instance_json_coder.rb +0 -83
- data/lib/jsi/struct_json_coder.rb +0 -30
- data/test/schema_instance_json_coder_test.rb +0 -121
- data/test/struct_json_coder_test.rb +0 -130
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSI
|
4
|
+
# JSI Schema Modules are extended with JSI::SchemaModule
|
5
|
+
module SchemaModule
|
6
|
+
# @return [String] absolute schema_id of the schema this module represents.
|
7
|
+
# see {Schema#schema_id}.
|
8
|
+
def schema_id
|
9
|
+
schema.schema_id
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
def inspect
|
14
|
+
uri = schema.schema_id || schema.node_ptr.uri
|
15
|
+
if name
|
16
|
+
"#{name} (#{uri})"
|
17
|
+
else
|
18
|
+
"(JSI Schema Module: #{uri})"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# invokes {JSI::Schema#new_jsi} on this module's schema, passing the given instance.
|
23
|
+
# @return [JSI::Base] a JSI whose instance is the given instance
|
24
|
+
def new_jsi(instance, *a, &b)
|
25
|
+
schema.new_jsi(instance, *a, &b)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# this module is just a namespace for schema classes.
|
30
|
+
module SchemaClasses
|
31
|
+
class << self
|
32
|
+
include Util::Memoize
|
33
|
+
|
34
|
+
# see {JSI.class_for_schemas}
|
35
|
+
def class_for_schemas(schema_objects)
|
36
|
+
schemas = schema_objects.map { |schema_object| JSI::Schema.from_object(schema_object) }.to_set
|
37
|
+
jsi_memoize(:class_for_schemas, schemas) do |schemas|
|
38
|
+
Class.new(Base).instance_exec(schemas) do |schemas|
|
39
|
+
define_singleton_method(:jsi_class_schemas) { schemas }
|
40
|
+
define_method(:jsi_schemas) { schemas }
|
41
|
+
schemas.each { |schema| include(schema.jsi_schema_module) }
|
42
|
+
jsi_class = self
|
43
|
+
define_method(:jsi_class) { jsi_class }
|
44
|
+
|
45
|
+
self
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# a module for the given schema, with accessor methods for any object property names the schema
|
51
|
+
# identifies (see {JSI::Schema#described_object_property_names}).
|
52
|
+
#
|
53
|
+
# defines a singleton method #schema to access the {JSI::Schema} this module represents, and extends
|
54
|
+
# the module with {JSI::SchemaModule}.
|
55
|
+
def module_for_schema(schema_object)
|
56
|
+
schema = JSI::Schema.from_object(schema_object)
|
57
|
+
jsi_memoize(:module_for_schema, schema) do |schema|
|
58
|
+
Module.new.tap do |m|
|
59
|
+
m.module_eval do
|
60
|
+
define_singleton_method(:schema) { schema }
|
61
|
+
|
62
|
+
extend SchemaModule
|
63
|
+
|
64
|
+
include JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [JSI::Base, JSI::PathedArrayNode, JSI::PathedHashNode])
|
65
|
+
|
66
|
+
@possibly_schema_node = schema
|
67
|
+
extend(SchemaModulePossibly)
|
68
|
+
schema.jsi_schemas.each do |schema_schema|
|
69
|
+
extend(JSI::SchemaClasses.accessor_module_for_schema(schema_schema, conflicting_modules: [Module, SchemaModule, SchemaModulePossibly]))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param schema [JSI::Schema] a schema for which to define accessors for any described property names
|
77
|
+
# @param conflicting_modules [Enumerable<Module>] an array of modules (or classes) which
|
78
|
+
# may be used alongside the accessor module. methods defined by any conflicting_module
|
79
|
+
# will not be defined as accessors.
|
80
|
+
# @return [Module] a module of accessors (setters and getters) for described property names of the given
|
81
|
+
# schema
|
82
|
+
def accessor_module_for_schema(schema, conflicting_modules: )
|
83
|
+
unless schema.is_a?(JSI::Schema)
|
84
|
+
raise(JSI::Schema::NotASchemaError, "not a schema: #{schema.pretty_inspect.chomp}")
|
85
|
+
end
|
86
|
+
jsi_memoize(:accessor_module_for_schema, schema, conflicting_modules) do |schema, conflicting_modules|
|
87
|
+
Module.new.tap do |m|
|
88
|
+
m.module_eval do
|
89
|
+
conflicting_instance_methods = (conflicting_modules + [m]).map do |mod|
|
90
|
+
mod.instance_methods + mod.private_instance_methods
|
91
|
+
end.inject(Set.new, &:|)
|
92
|
+
accessors_to_define = schema.described_object_property_names.map(&:to_s) - conflicting_instance_methods.map(&:to_s)
|
93
|
+
accessors_to_define.each do |property_name|
|
94
|
+
define_method(property_name) do
|
95
|
+
self[property_name]
|
96
|
+
end
|
97
|
+
define_method("#{property_name}=") do |value|
|
98
|
+
self[property_name] = value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# a JSI::Schema module and a JSI::NotASchemaModule are both a SchemaModulePossibly.
|
109
|
+
# this module provides a #[] method.
|
110
|
+
module SchemaModulePossibly
|
111
|
+
attr_reader :possibly_schema_node
|
112
|
+
|
113
|
+
# subscripting a JSI schema module or a NotASchemaModule will subscript the node, and
|
114
|
+
# if the result is a JSI::Schema, return a JSI::Schema class; if it is a PathedNode,
|
115
|
+
# return a NotASchemaModule; or if it is another value (a basic type), return that value.
|
116
|
+
#
|
117
|
+
# @param token [Object]
|
118
|
+
# @return [Class, NotASchemaModule, Object]
|
119
|
+
def [](token)
|
120
|
+
sub = @possibly_schema_node[token]
|
121
|
+
if sub.is_a?(JSI::Schema)
|
122
|
+
sub.jsi_schema_module
|
123
|
+
elsif sub.is_a?(JSI::PathedNode)
|
124
|
+
NotASchemaModule.new(sub)
|
125
|
+
else
|
126
|
+
sub
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# a schema module is a module which represents a schema. a NotASchemaModule represents
|
132
|
+
# a node in a schema's document which is not a schema, such as the 'properties'
|
133
|
+
# node (which contains schemas but is not a schema).
|
134
|
+
#
|
135
|
+
# a NotASchemaModule is extended with the module_for_schema of the node's schema.
|
136
|
+
#
|
137
|
+
# NotASchemaModule holds a node which is not a schema. when subscripted, it subscripts
|
138
|
+
# its node. if the value is a JSI::Schema, its schema module is returned. if the value
|
139
|
+
# is another node, a NotASchemaModule for that node is returned. otherwise - when the
|
140
|
+
# value is a basic type - that value itself is returned.
|
141
|
+
class NotASchemaModule
|
142
|
+
# @param node [JSI::PathedNode]
|
143
|
+
def initialize(node)
|
144
|
+
unless node.is_a?(JSI::PathedNode)
|
145
|
+
raise(TypeError, "not JSI::PathedNode: #{node.pretty_inspect.chomp}")
|
146
|
+
end
|
147
|
+
if node.is_a?(JSI::Schema)
|
148
|
+
raise(TypeError, "cannot instantiate NotASchemaModule for a JSI::Schema node: #{node.pretty_inspect.chomp}")
|
149
|
+
end
|
150
|
+
@possibly_schema_node = node
|
151
|
+
node.jsi_schemas.each do |schema|
|
152
|
+
extend(JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [NotASchemaModule, SchemaModulePossibly]))
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
include SchemaModulePossibly
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSI
|
4
|
+
SimpleWrap = JSI::Schema.new({
|
5
|
+
"additionalProperties": {"$ref": "#"},
|
6
|
+
"items": {"$ref": "#"}
|
7
|
+
}).jsi_schema_module
|
8
|
+
|
9
|
+
# SimpleWrap is a JSI schema module which recursively wraps nested structures
|
10
|
+
module SimpleWrap
|
11
|
+
end
|
12
|
+
end
|
data/lib/jsi/typelike_modules.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JSI
|
2
4
|
# a module relating to objects that act like Hash or Array instances
|
3
5
|
module Typelike
|
@@ -36,7 +38,9 @@ module JSI
|
|
36
38
|
# @raise [TypeError] when the object (or an object nested with a hash or
|
37
39
|
# array of object) cannot be expressed as json
|
38
40
|
def self.as_json(object, *opt)
|
39
|
-
if object.
|
41
|
+
if object.is_a?(JSI::PathedNode)
|
42
|
+
as_json(object.node_content, *opt)
|
43
|
+
elsif object.respond_to?(:to_hash)
|
40
44
|
(object.respond_to?(:map) ? object : object.to_hash).map do |k, v|
|
41
45
|
unless k.is_a?(Symbol) || k.respond_to?(:to_str)
|
42
46
|
raise(TypeError, "json object (hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
|
@@ -72,7 +76,7 @@ module JSI
|
|
72
76
|
SAFE_KEY_VALUE_METHODS = %w(< <= > >= any? assoc compact dig each_pair each_value fetch fetch_values has_value? invert key merge rassoc reject select to_h to_proc transform_values value? values values_at)
|
73
77
|
DESTRUCTIVE_METHODS = %w(clear delete delete_if keep_if reject! replace select! shift)
|
74
78
|
# these return a modified copy
|
75
|
-
safe_modified_copy_methods = %w(compact
|
79
|
+
safe_modified_copy_methods = %w(compact)
|
76
80
|
# select and reject will return a modified copy but need the yielded block variable value from #[]
|
77
81
|
safe_kv_block_modified_copy_methods = %w(select reject)
|
78
82
|
SAFE_METHODS = SAFE_KEY_ONLY_METHODS | SAFE_KEY_VALUE_METHODS
|
@@ -82,7 +86,7 @@ module JSI
|
|
82
86
|
end
|
83
87
|
safe_modified_copy_methods.each do |method_name|
|
84
88
|
define_method(method_name) do |*a, &b|
|
85
|
-
|
89
|
+
modified_copy do |object_to_modify|
|
86
90
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
|
87
91
|
responsive_object.public_send(method_name, *a, &b)
|
88
92
|
end
|
@@ -90,7 +94,7 @@ module JSI
|
|
90
94
|
end
|
91
95
|
safe_kv_block_modified_copy_methods.each do |method_name|
|
92
96
|
define_method(method_name) do |*a, &b|
|
93
|
-
|
97
|
+
modified_copy do |object_to_modify|
|
94
98
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
|
95
99
|
responsive_object.public_send(method_name, *a) do |k, _v|
|
96
100
|
b.call(k, self[k])
|
@@ -99,39 +103,66 @@ module JSI
|
|
99
103
|
end
|
100
104
|
end
|
101
105
|
|
106
|
+
# the same as Hash#update
|
107
|
+
# @param other [#to_hash] the other hash to update this hash from
|
108
|
+
# @yield [key, oldval, newval] for entries with duplicate keys, the value of each duplicate key
|
109
|
+
# is determined by calling the block with the key, its value in hsh and its value in other_hash.
|
110
|
+
# @return self, updated with other
|
111
|
+
# @raise [TypeError] when `other` does not respond to #to_hash
|
112
|
+
def update(other, &block)
|
113
|
+
unless other.respond_to?(:to_hash)
|
114
|
+
raise(TypeError, "cannot update with argument that does not respond to #to_hash: #{other.pretty_inspect.chomp}")
|
115
|
+
end
|
116
|
+
self_respondingto_key = self.respond_to?(:key?) ? self : to_hash
|
117
|
+
other.to_hash.each_pair do |key, value|
|
118
|
+
if block_given? && self_respondingto_key.key?(key)
|
119
|
+
value = yield(key, self[key], value)
|
120
|
+
end
|
121
|
+
self[key] = value
|
122
|
+
end
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
alias_method :merge!, :update
|
127
|
+
|
128
|
+
# the same as Hash#merge
|
129
|
+
# @param other [#to_hash] the other hash to merge into this
|
130
|
+
# @yield [key, oldval, newval] for entries with duplicate keys, the value of each duplicate key
|
131
|
+
# is determined by calling the block with the key, its value in hsh and its value in other_hash.
|
132
|
+
# @return duplicate of this hash with the other hash merged in
|
133
|
+
# @raise [TypeError] when `other` does not respond to #to_hash
|
134
|
+
def merge(other, &block)
|
135
|
+
dup.update(other, &block)
|
136
|
+
end
|
137
|
+
|
102
138
|
# @return [String] basically the same #inspect as Hash, but has the
|
103
139
|
# class name and, if responsive, self's #object_group_text
|
104
140
|
def inspect
|
105
|
-
|
106
|
-
"\#{<#{
|
141
|
+
object_group_str = (respond_to?(:object_group_text) ? self.object_group_text : [self.class]).join(' ')
|
142
|
+
"\#{<#{object_group_str}>#{empty? ? '' : ' '}#{self.map { |k, v| "#{k.inspect} => #{v.inspect}" }.join(', ')}}"
|
107
143
|
end
|
108
144
|
|
109
|
-
|
110
|
-
def to_s
|
111
|
-
inspect
|
112
|
-
end
|
145
|
+
alias_method :to_s, :inspect
|
113
146
|
|
114
147
|
# pretty-prints a representation this node to the given printer
|
115
148
|
# @return [void]
|
116
149
|
def pretty_print(q)
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
pp v
|
128
|
-
}
|
150
|
+
object_group_str = (respond_to?(:object_group_text) ? object_group_text : [self.class]).join(' ')
|
151
|
+
q.text "\#{<#{object_group_str}>"
|
152
|
+
q.group_sub {
|
153
|
+
q.nest(2) {
|
154
|
+
q.breakable(any? { true } ? ' ' : '')
|
155
|
+
q.seplist(self, nil, :each_pair) { |k, v|
|
156
|
+
q.group {
|
157
|
+
q.pp k
|
158
|
+
q.text ' => '
|
159
|
+
q.pp v
|
129
160
|
}
|
130
161
|
}
|
131
162
|
}
|
132
|
-
|
133
|
-
|
134
|
-
|
163
|
+
}
|
164
|
+
q.breakable ''
|
165
|
+
q.text '}'
|
135
166
|
end
|
136
167
|
end
|
137
168
|
|
@@ -162,7 +193,7 @@ module JSI
|
|
162
193
|
end
|
163
194
|
safe_modified_copy_methods.each do |method_name|
|
164
195
|
define_method(method_name) do |*a, &b|
|
165
|
-
|
196
|
+
modified_copy do |object_to_modify|
|
166
197
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
|
167
198
|
responsive_object.public_send(method_name, *a, &b)
|
168
199
|
end
|
@@ -170,7 +201,7 @@ module JSI
|
|
170
201
|
end
|
171
202
|
safe_el_block_methods.each do |method_name|
|
172
203
|
define_method(method_name) do |*a, &b|
|
173
|
-
|
204
|
+
modified_copy do |object_to_modify|
|
174
205
|
i = 0
|
175
206
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
|
176
207
|
responsive_object.public_send(method_name, *a) do |_e|
|
@@ -183,32 +214,27 @@ module JSI
|
|
183
214
|
# @return [String] basically the same #inspect as Array, but has the
|
184
215
|
# class name and, if responsive, self's #object_group_text
|
185
216
|
def inspect
|
186
|
-
|
187
|
-
"\#[<#{
|
217
|
+
object_group_str = (respond_to?(:object_group_text) ? object_group_text : [self.class]).join(' ')
|
218
|
+
"\#[<#{object_group_str}>#{empty? ? '' : ' '}#{self.map { |e| e.inspect }.join(', ')}]"
|
188
219
|
end
|
189
220
|
|
190
|
-
|
191
|
-
def to_s
|
192
|
-
inspect
|
193
|
-
end
|
221
|
+
alias_method :to_s, :inspect
|
194
222
|
|
195
223
|
# pretty-prints a representation this node to the given printer
|
196
224
|
# @return [void]
|
197
225
|
def pretty_print(q)
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
pp e
|
206
|
-
}
|
226
|
+
object_group_str = (respond_to?(:object_group_text) ? object_group_text : [self.class]).join(' ')
|
227
|
+
q.text "\#[<#{object_group_str}>"
|
228
|
+
q.group_sub {
|
229
|
+
q.nest(2) {
|
230
|
+
q.breakable(any? { true } ? ' ' : '')
|
231
|
+
q.seplist(self, nil, :each) { |e|
|
232
|
+
q.pp e
|
207
233
|
}
|
208
234
|
}
|
209
|
-
|
210
|
-
|
211
|
-
|
235
|
+
}
|
236
|
+
q.breakable ''
|
237
|
+
q.text ']'
|
212
238
|
end
|
213
239
|
end
|
214
240
|
end
|
data/lib/jsi/util.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JSI
|
4
|
+
# JSI::Util classes, modules, constants, and methods are INTERNAL and will be added and removed without warning.
|
5
|
+
# do not rely on them.
|
2
6
|
module Util
|
7
|
+
# a proc which does nothing
|
8
|
+
NOOP = -> (*_) { }
|
9
|
+
|
3
10
|
# returns a version of the given hash, in which any symbol keys are
|
4
11
|
# converted to strings. behavior on collisions is undefined (but in the
|
5
12
|
# future could take a block like
|
@@ -11,54 +18,36 @@ module JSI
|
|
11
18
|
# the return if you need to ensure it is not the same instance as the
|
12
19
|
# argument instance.
|
13
20
|
#
|
14
|
-
# @param
|
21
|
+
# @param hashlike [#to_hash] the hash from which to convert symbol keys to strings
|
15
22
|
# @return [same class as the param `hash`, or Hash if the former cannot be done] a
|
16
23
|
# hash(-like) instance containing no symbol keys
|
17
|
-
def stringify_symbol_keys(
|
18
|
-
unless
|
19
|
-
raise(ArgumentError, "expected argument to be a hash; got #{
|
24
|
+
def stringify_symbol_keys(hashlike)
|
25
|
+
unless hashlike.respond_to?(:to_hash)
|
26
|
+
raise(ArgumentError, "expected argument to be a hash; got #{hashlike.class.inspect}: #{hashlike.pretty_inspect.chomp}")
|
20
27
|
end
|
21
|
-
JSI::Typelike.modified_copy(
|
22
|
-
changed = false
|
28
|
+
JSI::Typelike.modified_copy(hashlike) do |hash|
|
23
29
|
out = {}
|
24
|
-
|
25
|
-
|
26
|
-
changed = true
|
27
|
-
k = k.to_s
|
28
|
-
end
|
29
|
-
out[k] = v
|
30
|
+
hash.each do |k, v|
|
31
|
+
out[k.is_a?(Symbol) ? k.to_s : k] = v
|
30
32
|
end
|
31
|
-
|
33
|
+
out
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
35
37
|
def deep_stringify_symbol_keys(object)
|
36
38
|
if object.respond_to?(:to_hash)
|
37
39
|
JSI::Typelike.modified_copy(object) do |hash|
|
38
|
-
changed = false
|
39
40
|
out = {}
|
40
41
|
(hash.respond_to?(:each) ? hash : hash.to_hash).each do |k, v|
|
41
|
-
|
42
|
-
changed = true
|
43
|
-
k = k.to_s
|
44
|
-
end
|
45
|
-
out_k = deep_stringify_symbol_keys(k)
|
46
|
-
out_v = deep_stringify_symbol_keys(v)
|
47
|
-
changed = true if out_k.object_id != k.object_id
|
48
|
-
changed = true if out_v.object_id != v.object_id
|
49
|
-
out[out_k] = out_v
|
42
|
+
out[k.is_a?(Symbol) ? k.to_s : deep_stringify_symbol_keys(k)] = deep_stringify_symbol_keys(v)
|
50
43
|
end
|
51
|
-
|
44
|
+
out
|
52
45
|
end
|
53
46
|
elsif object.respond_to?(:to_ary)
|
54
47
|
JSI::Typelike.modified_copy(object) do |ary|
|
55
|
-
|
56
|
-
|
57
|
-
out_e = deep_stringify_symbol_keys(e)
|
58
|
-
changed = true if out_e.object_id != e.object_id
|
59
|
-
out_e
|
48
|
+
(ary.respond_to?(:each) ? ary : ary.to_ary).map do |e|
|
49
|
+
deep_stringify_symbol_keys(e)
|
60
50
|
end
|
61
|
-
changed ? out : ary
|
62
51
|
end
|
63
52
|
else
|
64
53
|
object
|
@@ -69,7 +58,7 @@ module JSI
|
|
69
58
|
# to define a recursive function to return the length of an array:
|
70
59
|
#
|
71
60
|
# length = ycomb do |len|
|
72
|
-
# proc{|list| list == [] ? 0 : 1 + len.call(list[1..-1]) }
|
61
|
+
# proc { |list| list == [] ? 0 : 1 + len.call(list[1..-1]) }
|
73
62
|
# end
|
74
63
|
#
|
75
64
|
# see https://secure.wikimedia.org/wikipedia/en/wiki/Fixed_point_combinator#Y_combinator
|
@@ -78,41 +67,42 @@ module JSI
|
|
78
67
|
proc { |f| f.call(f) }.call(proc { |f| yield proc { |*x| f.call(f).call(*x) } })
|
79
68
|
end
|
80
69
|
module_function :ycomb
|
81
|
-
end
|
82
|
-
public
|
83
|
-
extend Util
|
84
70
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
71
|
+
module FingerprintHash
|
72
|
+
# overrides BasicObject#==
|
73
|
+
def ==(other)
|
74
|
+
object_id == other.object_id || (other.respond_to?(:jsi_fingerprint) && other.jsi_fingerprint == self.jsi_fingerprint)
|
75
|
+
end
|
89
76
|
|
90
|
-
|
77
|
+
alias_method :eql?, :==
|
91
78
|
|
92
|
-
|
93
|
-
|
79
|
+
# overrides Kernel#hash
|
80
|
+
def hash
|
81
|
+
jsi_fingerprint.hash
|
82
|
+
end
|
94
83
|
end
|
95
|
-
end
|
96
84
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
85
|
+
module Memoize
|
86
|
+
def jsi_memoize(key, *args_)
|
87
|
+
@jsi_memos ||= {}
|
88
|
+
@jsi_memos[key] ||= Hash.new do |h, args|
|
89
|
+
h[args] = yield(*args)
|
90
|
+
end
|
91
|
+
@jsi_memos[key][args_]
|
102
92
|
end
|
103
|
-
@memos[key][args_]
|
104
|
-
end
|
105
93
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
94
|
+
def jsi_clear_memo(key, *args)
|
95
|
+
@jsi_memos ||= {}
|
96
|
+
if @jsi_memos[key]
|
97
|
+
if args.empty?
|
98
|
+
@jsi_memos[key].clear
|
99
|
+
else
|
100
|
+
@jsi_memos[key].delete(args)
|
101
|
+
end
|
113
102
|
end
|
114
103
|
end
|
115
104
|
end
|
116
105
|
end
|
117
|
-
|
106
|
+
public
|
107
|
+
extend Util
|
118
108
|
end
|