scorpio 0.1.0 → 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.
@@ -1,227 +0,0 @@
1
- require 'json'
2
- require 'scorpio/typelike_modules'
3
-
4
- module Scorpio
5
- # base class for representing an instance of an object described by a schema
6
- class SchemaObjectBase
7
- def initialize(object)
8
- if object.is_a?(Scorpio::JSON::Node)
9
- @object = object
10
- else
11
- @object = Scorpio::JSON::Node.new_by_type(object, [])
12
- end
13
- end
14
-
15
- attr_reader :object
16
-
17
- def fragment
18
- object.fragment
19
- end
20
-
21
- def fully_validate
22
- module_schema.fully_validate(object)
23
- end
24
- def validate
25
- module_schema.validate(object)
26
- end
27
- def validate!
28
- module_schema.validate!(object)
29
- end
30
- def inspect
31
- "\#<#{self.class.name} #{object.inspect}>"
32
- end
33
- def pretty_print(q)
34
- q.instance_exec(self) do |obj|
35
- text "\#<#{obj.class.name}"
36
- group_sub {
37
- nest(2) {
38
- breakable ' '
39
- pp obj.object
40
- }
41
- }
42
- breakable ''
43
- text '>'
44
- end
45
- end
46
-
47
- def fingerprint
48
- {class: self.class, object: object}
49
- end
50
- include FingerprintHash
51
- end
52
-
53
- CLASS_FOR_SCHEMA = Hash.new do |h, schema_node_|
54
- h[schema_node_] = Class.new(SchemaObjectBase).instance_exec(schema_node_) do |schema_node|
55
- prepend(Scorpio.module_for_schema(schema_node))
56
- end
57
- end
58
-
59
- def self.class_for_schema(schema_node)
60
- schema_node = schema_node.object if schema_node.is_a?(Scorpio::SchemaObjectBase)
61
- CLASS_FOR_SCHEMA[schema_node.deref]
62
- end
63
-
64
- # this invokes methods of type-like modules (Arraylike, Hashlike) but only if the #object
65
- # is of the expected class. since the object may be anything - it will just not be a valid
66
- # instance of its schema - we can't assume that the methods on the Xlike modules will work
67
- # (e.g. trying to call #each_index on an #object that's not array-like)
68
- module SchemaObjectMightBeLike
69
- def inspect(*a, &b)
70
- if object.is_a?(expected_object_class)
71
- super
72
- else
73
- SchemaObjectBase.instance_method(:inspect).bind(self).call(*a, &b)
74
- end
75
- end
76
- def pretty_print(*a, &b)
77
- if object.is_a?(expected_object_class)
78
- super
79
- else
80
- SchemaObjectBase.instance_method(:pretty_print).bind(self).call(*a, &b)
81
- end
82
- end
83
- end
84
- module SchemaObjectBaseHash
85
- def expected_object_class
86
- Scorpio::JSON::HashNode
87
- end
88
-
89
- # Hash methods
90
- def each
91
- return to_enum(__method__) { object.size } unless block_given?
92
- object.each_key { |k| yield(k, self[k]) }
93
- self
94
- end
95
- include Enumerable
96
-
97
- def to_hash
98
- inject({}) { |h, (k, v)| h[k] = v; h }
99
- end
100
-
101
- include Hashlike
102
- include SchemaObjectMightBeLike
103
-
104
- # hash methods - define only those which do not modify the hash.
105
-
106
- # methods that don't look at the value; can skip the overhead of #[]
107
- key_methods = %w(each_key empty? include? has_key? key key? keys length member? size)
108
- key_methods.each do |method_name|
109
- define_method(method_name) { |*a, &b| object.public_send(method_name, *a, &b) }
110
- end
111
-
112
- # methods which use key and value
113
- hash_methods = %w(compact each_pair each_value fetch fetch_values has_value? invert
114
- rassoc reject select to_h transform_values value? values values_at)
115
- hash_methods.each do |method_name|
116
- define_method(method_name) { |*a, &b| to_hash.public_send(method_name, *a, &b) }
117
- end
118
-
119
- def [](property_name_)
120
- @object_mapped ||= Hash.new do |hash, property_name|
121
- hash[property_name] = begin
122
- property_schema = module_schema.subschema_for_property(property_name)
123
- property_schema = property_schema && property_schema.match_to_object(object[property_name])
124
-
125
- if property_schema && object[property_name].is_a?(JSON::Node)
126
- Scorpio.class_for_schema(property_schema.schema_node).new(object[property_name])
127
- else
128
- object[property_name]
129
- end
130
- end
131
- end
132
- @object_mapped[property_name_]
133
- end
134
-
135
- def merge(other)
136
- # we want to strip the containers from this before we merge
137
- # this is kind of annoying. wish I had a better way.
138
- other_stripped = ycomb do |striprec|
139
- proc do |stripobject|
140
- stripobject = stripobject.object if stripobject.is_a?(Scorpio::SchemaObjectBase)
141
- stripobject = stripobject.content if stripobject.is_a?(Scorpio::JSON::Node)
142
- if stripobject.is_a?(Hash)
143
- stripobject.map { |k, v| {striprec.call(k) => striprec.call(v)} }.inject({}, &:update)
144
- elsif stripobject.is_a?(Array)
145
- stripobject.map(&striprec)
146
- elsif stripobject.is_a?(Symbol)
147
- stripobject.to_s
148
- elsif [String, TrueClass, FalseClass, NilClass, Numeric].any? { |c| stripobject.is_a?(c) }
149
- stripobject
150
- else
151
- raise(TypeError, "bad (not jsonifiable) object: #{stripobject.pretty_inspect}")
152
- end
153
- end
154
- end.call(other)
155
-
156
- self.class.new(object.merge(other_stripped))
157
- end
158
- end
159
-
160
- module SchemaObjectBaseArray
161
- def expected_object_class
162
- Scorpio::JSON::ArrayNode
163
- end
164
-
165
- def each
166
- return to_enum(__method__) { object.size } unless block_given?
167
- object.each_index { |i| yield(self[i]) }
168
- self
169
- end
170
- include Enumerable
171
-
172
- def to_ary
173
- to_a
174
- end
175
-
176
- include Arraylike
177
- include SchemaObjectMightBeLike
178
-
179
- def [](i_)
180
- # it would make more sense for this to be an array here, but but Array doesn't have a nice memoizing
181
- # constructor, so it's a hash with integer keys
182
- @object_mapped ||= Hash.new do |hash, i|
183
- hash[i] = begin
184
- index_schema = module_schema.subschema_for_index(i)
185
- index_schema = index_schema && index_schema.match_to_object(object[i])
186
-
187
- if index_schema && object[i].is_a?(JSON::Node)
188
- Scorpio.class_for_schema(index_schema.schema_node).new(object[i])
189
- else
190
- object[i]
191
- end
192
- end
193
- end
194
- @object_mapped[i_]
195
- end
196
- end
197
-
198
- def self.module_for_schema(schema_node_)
199
- Module.new.tap do |m|
200
- m.instance_exec(schema_node_) do |module_schema_node|
201
- unless module_schema_node.is_a?(Scorpio::JSON::Node)
202
- raise(ArgumentError, "expected instance of Scorpio::JSON::Node; got: #{module_schema_node.pretty_inspect.chomp}")
203
- end
204
-
205
- module_schema = Scorpio::Schema.new(module_schema_node)
206
-
207
- define_method(:module_schema) { module_schema }
208
- define_singleton_method(:module_schema) { module_schema }
209
- define_singleton_method(:included) do |includer|
210
- includer.send(:define_singleton_method, :module_schema) { module_schema }
211
- end
212
-
213
- if module_schema.describes_hash?
214
- include SchemaObjectBaseHash
215
-
216
- module_schema.described_hash_property_names.each do |property_name|
217
- define_method(property_name) do
218
- self[property_name]
219
- end
220
- end
221
- elsif module_schema.describes_array?
222
- include SchemaObjectBaseArray
223
- end
224
- end
225
- end
226
- end
227
- end