jsi 0.0.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|