scorpio 0.1.0 → 0.2.0

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