icss 0.0.2 → 0.0.4
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.
- data/.rspec +0 -2
- data/Gemfile +1 -1
- data/Gemfile.lock +1 -1
- data/VERSION +1 -1
- data/icss.gemspec +10 -8
- data/lib/icss/protocol.rb +1 -1
- data/lib/icss/receiver/acts_as_hash.rb +436 -0
- data/lib/icss/receiver/acts_as_loadable.rb +42 -0
- data/lib/icss/receiver/tree_diff.rb +74 -0
- data/lib/icss/receiver/validations.rb +30 -0
- data/lib/icss/receiver.rb +289 -0
- data/lib/icss/type.rb +3 -4
- data/lib/icss.rb +7 -7
- data/spec/spec_helper.rb +8 -27
- metadata +40 -77
- data/.watchr +0 -20
- data/CHANGELOG.textile +0 -8
- data/lib/icss/core_ext.rb +0 -4
data/.rspec
CHANGED
data/Gemfile
CHANGED
@@ -4,7 +4,7 @@ source "http://rubygems.org"
|
|
4
4
|
# gem "activesupport", ">= 2.3.5"
|
5
5
|
|
6
6
|
gem 'yajl-ruby', "~> 0.8.2"
|
7
|
-
gem 'gorillib', "~> 0.0.
|
7
|
+
gem 'gorillib', "~> 0.0.4"
|
8
8
|
|
9
9
|
# Add dependencies to develop your gem here.
|
10
10
|
# Include everything needed to run rake, tests, features, etc.
|
data/Gemfile.lock
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.4
|
data/icss.gemspec
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{icss}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.4"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Philip (flip) Kromer for Infochimps"]
|
@@ -19,8 +19,6 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
21
|
".rspec",
|
22
|
-
".watchr",
|
23
|
-
"CHANGELOG.textile",
|
24
22
|
"Gemfile",
|
25
23
|
"Gemfile.lock",
|
26
24
|
"LICENSE.textile",
|
@@ -46,12 +44,16 @@ Gem::Specification.new do |s|
|
|
46
44
|
"lib/icss.rb",
|
47
45
|
"lib/icss/brevity.rb",
|
48
46
|
"lib/icss/code_asset.rb",
|
49
|
-
"lib/icss/core_ext.rb",
|
50
47
|
"lib/icss/data_asset.rb",
|
51
48
|
"lib/icss/message.rb",
|
52
49
|
"lib/icss/old.rb",
|
53
50
|
"lib/icss/protocol.rb",
|
54
51
|
"lib/icss/protocol_set.rb",
|
52
|
+
"lib/icss/receiver.rb",
|
53
|
+
"lib/icss/receiver/acts_as_hash.rb",
|
54
|
+
"lib/icss/receiver/acts_as_loadable.rb",
|
55
|
+
"lib/icss/receiver/tree_diff.rb",
|
56
|
+
"lib/icss/receiver/validations.rb",
|
55
57
|
"lib/icss/sample_message_call.rb",
|
56
58
|
"lib/icss/target.rb",
|
57
59
|
"lib/icss/type.rb",
|
@@ -64,7 +66,7 @@ Gem::Specification.new do |s|
|
|
64
66
|
s.homepage = %q{http://github.com/mrflip/icss}
|
65
67
|
s.licenses = ["MIT"]
|
66
68
|
s.require_paths = ["lib"]
|
67
|
-
s.rubygems_version = %q{1.
|
69
|
+
s.rubygems_version = %q{1.5.0}
|
68
70
|
s.summary = %q{Infochimps Stupid Schema library: an avro-compatible data description standard. ICSS completely describes a collection of data (and associated assets) in a way that is expressive, scalable and sufficient to drive remarkably complex downstream processes.}
|
69
71
|
s.test_files = [
|
70
72
|
"spec/icss_spec.rb",
|
@@ -76,7 +78,7 @@ Gem::Specification.new do |s|
|
|
76
78
|
|
77
79
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
78
80
|
s.add_runtime_dependency(%q<yajl-ruby>, ["~> 0.8.2"])
|
79
|
-
s.add_runtime_dependency(%q<gorillib>, ["~> 0.0.
|
81
|
+
s.add_runtime_dependency(%q<gorillib>, ["~> 0.0.4"])
|
80
82
|
s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
|
81
83
|
s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
|
82
84
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
@@ -84,7 +86,7 @@ Gem::Specification.new do |s|
|
|
84
86
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
85
87
|
else
|
86
88
|
s.add_dependency(%q<yajl-ruby>, ["~> 0.8.2"])
|
87
|
-
s.add_dependency(%q<gorillib>, ["~> 0.0.
|
89
|
+
s.add_dependency(%q<gorillib>, ["~> 0.0.4"])
|
88
90
|
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
89
91
|
s.add_dependency(%q<yard>, ["~> 0.6.0"])
|
90
92
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
@@ -93,7 +95,7 @@ Gem::Specification.new do |s|
|
|
93
95
|
end
|
94
96
|
else
|
95
97
|
s.add_dependency(%q<yajl-ruby>, ["~> 0.8.2"])
|
96
|
-
s.add_dependency(%q<gorillib>, ["~> 0.0.
|
98
|
+
s.add_dependency(%q<gorillib>, ["~> 0.0.4"])
|
97
99
|
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
98
100
|
s.add_dependency(%q<yard>, ["~> 0.6.0"])
|
99
101
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
data/lib/icss/protocol.rb
CHANGED
@@ -105,7 +105,7 @@ module Icss
|
|
105
105
|
|
106
106
|
def receive_targets hsh
|
107
107
|
self.targets = hsh.inject({}) do |target_obj_hsh, (target_name, target_info_list)|
|
108
|
-
target_obj_hsh[target_name] = TargetListFactory.receive(
|
108
|
+
target_obj_hsh[target_name] = TargetListFactory.receive(target_name, target_info_list) # returns an arry of targets
|
109
109
|
target_obj_hsh
|
110
110
|
end
|
111
111
|
end
|
@@ -0,0 +1,436 @@
|
|
1
|
+
module Receiver
|
2
|
+
|
3
|
+
#
|
4
|
+
# Makes a Receiver thingie behave mostly like a hash.
|
5
|
+
#
|
6
|
+
# By default, the hashlike methods iterate over the receiver attributes:
|
7
|
+
# instance #keys delegates to self.class.keys which calls
|
8
|
+
# receiver_attr_names. If you want to filter our add to the keys list, you
|
9
|
+
# can just override the class-level keys method (and call super, or not):
|
10
|
+
#
|
11
|
+
# def self.keys
|
12
|
+
# super + [:firstname, :lastname] - [:fullname]
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# All methods are defined naturally on [], []= and has_key? -- if you enjoy
|
16
|
+
#
|
17
|
+
#
|
18
|
+
# in addition to the below, by including Enumerable, this also adds
|
19
|
+
#
|
20
|
+
# #all?, #any?, #chunk, #collect, #collect_concat, #count, #cycle, #detect,
|
21
|
+
# #drop, #drop_while, #each_cons, #each_entry, #each_slice,
|
22
|
+
# #each_with_index, #each_with_object, #entries, #find, #find_all,
|
23
|
+
# #find_index, #first, #flat_map, #grep, #group_by, #inject, #map, #max,
|
24
|
+
# #max_by, #min, #min_by, #minmax, #minmax_by, #none?, #one?, #partition,
|
25
|
+
# #reduce, #reverse_each, #slice_before, #sort, #sort_by, #take,
|
26
|
+
# #take_while, #zip
|
27
|
+
#
|
28
|
+
# As opposed to hash, does *not* define
|
29
|
+
#
|
30
|
+
# default, default=, default_proc, default_proc=, shift
|
31
|
+
# length, size, empty?, flatten, replace, keep_if, key(value)
|
32
|
+
# compare_by_identity compare_by_identity? rehash, select!
|
33
|
+
#
|
34
|
+
# assoc rassoc
|
35
|
+
#
|
36
|
+
module ActsAsHash
|
37
|
+
|
38
|
+
# Fake hash reader semantics: delegates to self.send(key)
|
39
|
+
#
|
40
|
+
# Note: indifferent access -- either of :foo or "foo" will work
|
41
|
+
#
|
42
|
+
def [](name)
|
43
|
+
self.send(name) if keys.include?(name.to_sym)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Fake hash writer semantics: delegates to self.send("key=", val)
|
47
|
+
#
|
48
|
+
# NOTE: this calls self.foo= 5, not self.receive_foo(5)
|
49
|
+
# NOTE: indifferent access -- either of :foo or "foo" will work
|
50
|
+
#
|
51
|
+
def []=(name, val)
|
52
|
+
self.send("#{name}=", val) if keys.include?(name)
|
53
|
+
end
|
54
|
+
alias_method(:store, :[]=)
|
55
|
+
|
56
|
+
# @param key<Object> The key to check for.
|
57
|
+
#
|
58
|
+
# @return [Boolean] True if
|
59
|
+
# * the attribute is one of this object's keys, and
|
60
|
+
# * its value is non-nil OR the corresponding instance_variable is defined.
|
61
|
+
#
|
62
|
+
# For attributes that are virtual accessors, if its value is explicitly set
|
63
|
+
# to nil then has_key? is true.
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# class Foo
|
67
|
+
# include Receiver
|
68
|
+
# include Receiver::ActsAsHash
|
69
|
+
# rcvr_accessor :a, Integer
|
70
|
+
# rcvr_accessor :b, String
|
71
|
+
# end
|
72
|
+
# foo = Foo.receive({:a => 1})
|
73
|
+
# foo.has_key?(:b) # false
|
74
|
+
# foo[:b] # nil
|
75
|
+
# foo.b = nil
|
76
|
+
# foo.has_key?(:b) # true
|
77
|
+
# foo[:b] # nil
|
78
|
+
#
|
79
|
+
def has_key?(key)
|
80
|
+
keys.include?(key) && ((not self[key].nil?) || attr_set?(key))
|
81
|
+
end
|
82
|
+
|
83
|
+
# @param key<Object> The key to remove
|
84
|
+
#
|
85
|
+
# @return [Object]
|
86
|
+
# returns the value of the given attribute, and sets its new value to nil.
|
87
|
+
# If there is a corresponding instance_variable, it is subsequently removed.
|
88
|
+
def delete(key)
|
89
|
+
val = self[key]
|
90
|
+
self[key]= nil
|
91
|
+
unset!(key)
|
92
|
+
val
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Convert to a hash
|
97
|
+
#
|
98
|
+
# Each key in #keys becomes an element in the new array if the value of its
|
99
|
+
# attribute is non-nil OR the corresponding instance_variable is defined.
|
100
|
+
def to_hash
|
101
|
+
keys.inject({}) do |hsh, key|
|
102
|
+
val = self[key]
|
103
|
+
hsh[key] = val if (val || self.instance_variable_defined?("@#{key}"))
|
104
|
+
hsh
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module ClassMethods
|
109
|
+
# By default, the hashlike methods iterate over the receiver attributes.
|
110
|
+
# If you want to filter our add to the keys list, override this method
|
111
|
+
#
|
112
|
+
# @example
|
113
|
+
# def self.keys
|
114
|
+
# super + [:firstname, :lastname] - [:fullname]
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
def keys
|
118
|
+
receiver_attr_names
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# ===========================================================================
|
123
|
+
#
|
124
|
+
# The below methods are natural extensions of the above
|
125
|
+
#
|
126
|
+
|
127
|
+
# delegates to the class method. Typically you'll want to override that one,
|
128
|
+
# not the instance keys
|
129
|
+
def keys
|
130
|
+
self.class.keys
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns an array consisting of the value for each attribute in
|
134
|
+
# #keys, guaranteed in same order
|
135
|
+
def values
|
136
|
+
values_at *keys
|
137
|
+
end unless method_defined?(:values)
|
138
|
+
|
139
|
+
# Returns an array consisting of the value for each attribute in
|
140
|
+
# allowed_keys, guaranteed in same order
|
141
|
+
def values_at *allowed_keys
|
142
|
+
allowed_keys.map do |k|
|
143
|
+
self[k]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# a nested array of [ key, value ] pairs. Delegates to to_hash.to_a
|
148
|
+
def to_a
|
149
|
+
to_hash.to_a
|
150
|
+
end
|
151
|
+
|
152
|
+
# @return [Hash] the object as a Hash with symbolized keys.
|
153
|
+
def symbolize_keys() to_hash ; end
|
154
|
+
# @return [Hash] the object as a Hash with string keys.
|
155
|
+
def stringify_keys() to_hash.stringify_keys ; end
|
156
|
+
|
157
|
+
# Used to provide the same interface as Hash.
|
158
|
+
# @return This object unchanged.
|
159
|
+
def symbolize_keys!; self end
|
160
|
+
|
161
|
+
# Used to provide the same interface as Hash.
|
162
|
+
# @return This object unchanged.
|
163
|
+
def stringify_keys!; self end
|
164
|
+
|
165
|
+
#
|
166
|
+
# Return a Hash containing only values for the given keys where self.has_key?(k)
|
167
|
+
#
|
168
|
+
def slice *allowed_keys
|
169
|
+
allowed_keys.inject({}){|h,k| h[k] = self[k] if self.has_key?(k) ; h }
|
170
|
+
end
|
171
|
+
|
172
|
+
# Calls block once for each key in #keys in order, passing the key and value as parameters.
|
173
|
+
def each &block
|
174
|
+
keys.each do |key|
|
175
|
+
yield(key, self[key])
|
176
|
+
end
|
177
|
+
end
|
178
|
+
alias_method :each_pair, :each
|
179
|
+
|
180
|
+
# Calls block once for each key in #keys in order, passing the key as parameter.
|
181
|
+
def each_key &block
|
182
|
+
keys.each(&block)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Calls block once for each key in #keys in order, passing the value as parameter.
|
186
|
+
def each_value &block
|
187
|
+
keys.each do |key|
|
188
|
+
yield self[key]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
#
|
193
|
+
# Analogous to Hash#merge: returns a duplicate of self where for each
|
194
|
+
# element of self.keys, adopts the corresponding element of hsh if that key
|
195
|
+
# is set in hsh.
|
196
|
+
#
|
197
|
+
# Returns a duplicate of self, but adopting the corresponding element of hsh
|
198
|
+
# if that key is set in hsh. Only keys in self.keys are candidates for merging.
|
199
|
+
#
|
200
|
+
# With no block parameter, overwrites entries in hsh with duplicate keys
|
201
|
+
# with those from other_hash.
|
202
|
+
#
|
203
|
+
# The block parameter semantics aren't implemented yet. If a block is
|
204
|
+
# specified, it is called with each duplicate key and the values from the
|
205
|
+
# two hashes. The value returned by the block is stored in the new hash.
|
206
|
+
#
|
207
|
+
# @example
|
208
|
+
# h1 = { "a" => 100, "b" => 200 }
|
209
|
+
# h2 = { "b" => 254, "c" => 300 }
|
210
|
+
# h1.merge(h2) -> {"a"=>100, "b"=>254, "c"=>300}
|
211
|
+
# h1.merge(h2){|k,o,n| o} -> {"a"=>100, "b"=>200, "c"=>300}
|
212
|
+
# h1 -> {"a"=>100, "b"=>200}
|
213
|
+
#
|
214
|
+
def merge *args, &block
|
215
|
+
self.dup.merge!(*args, &block)
|
216
|
+
end
|
217
|
+
|
218
|
+
# For all keys that are in self.keys *and* other_hash.has_key?(key),
|
219
|
+
# sets the value to that from other_hash
|
220
|
+
#
|
221
|
+
def update other_hash, &block
|
222
|
+
raise "can't handle block arg yet" if block
|
223
|
+
keys.each do |key|
|
224
|
+
self[key] = other_hash[key] if other_hash.has_key?(key)
|
225
|
+
end
|
226
|
+
self
|
227
|
+
end
|
228
|
+
alias_method :merge!, :update
|
229
|
+
|
230
|
+
# # Returns a new hash with +self+ and +other_hash+ merged recursively.
|
231
|
+
# def deep_merge(other_hash)
|
232
|
+
# dup.deep_merge!(other_hash)
|
233
|
+
# end
|
234
|
+
|
235
|
+
# Recursively merges using receive
|
236
|
+
#
|
237
|
+
# Modifies the full receiver chain in-place.
|
238
|
+
#
|
239
|
+
# For each key in keys,
|
240
|
+
# * if self's value is nil, receive the attribute.
|
241
|
+
# * if self's attribute is an Array, append to it.
|
242
|
+
# * if self's value responds to tree_merge!, tree merge it.
|
243
|
+
# * if self's value responds_to merge!, merge! it.
|
244
|
+
# * otherwise, receive the value from other_hash
|
245
|
+
#
|
246
|
+
def tree_merge!(other_hash)
|
247
|
+
keys.each do |key|
|
248
|
+
# get other's val if any
|
249
|
+
if other_hash.has_key?(key.to_sym) then other_val = other_hash[key.to_sym]
|
250
|
+
elsif other_hash.has_key?(key.to_s) then other_val = other_hash[key.to_s]
|
251
|
+
else next ; end
|
252
|
+
#
|
253
|
+
self_val = self[key]
|
254
|
+
# p ['receiver tree_merge', key, self_val.respond_to?(:tree_merge!), self[key], other_val]
|
255
|
+
case
|
256
|
+
when other_val.nil? then next
|
257
|
+
when (not has_key?(key)) then _receive_attr(key, other_val)
|
258
|
+
when receiver_attrs[key][:merge_as] == :hash_of_arrays
|
259
|
+
self_val.merge!(other_val) do |k, v1, v2| case when v1.blank? then v2 when v2.blank? then v1 else v1 + v2 end end
|
260
|
+
when self_val.is_a?(Array) then self[key] += other_val
|
261
|
+
when self_val.respond_to?(:tree_merge!) then self[key] = self_val.tree_merge!(other_val)
|
262
|
+
when self_val.respond_to?(:merge!) then self[key] = self_val.merge!(other_val)
|
263
|
+
else _receive_attr(key, other_val)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
run_after_receivers(other_hash)
|
267
|
+
self
|
268
|
+
end
|
269
|
+
|
270
|
+
# Searches the hash for an entry whose value == value, returning the
|
271
|
+
# corresponding key. If multiple entries has this value, the key returned
|
272
|
+
# will be that on one of the entries. If not found,returns nil.
|
273
|
+
#
|
274
|
+
# You are guaranteed that the first matching key in #keys will be the one
|
275
|
+
# returned.
|
276
|
+
#
|
277
|
+
# @example
|
278
|
+
# foo = Foo.receive( "a" => 100, "b" => 200, "c" => 100 )
|
279
|
+
# foo.index(100) -> "a"
|
280
|
+
# foo.index(999) -> nil
|
281
|
+
#
|
282
|
+
def index val
|
283
|
+
keys.find{|key| self[key] == val }
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns a new hash created by using inverting self.to_hash. If this new
|
287
|
+
# hash has duplicate values, the result will contain only one of them as a
|
288
|
+
# key -- which one is not predictable.
|
289
|
+
def invert
|
290
|
+
to_hash.invert
|
291
|
+
end
|
292
|
+
|
293
|
+
# Returns true if the given value is present for some attribute in #keys
|
294
|
+
def has_value? val
|
295
|
+
!! index(val)
|
296
|
+
end
|
297
|
+
alias_method :value?, :has_value?
|
298
|
+
|
299
|
+
# def include? def key? def member?
|
300
|
+
alias_method :include?, :has_key?
|
301
|
+
alias_method :key?, :has_key?
|
302
|
+
alias_method :member?, :has_key?
|
303
|
+
|
304
|
+
# Deletes every attribute for which block is true.
|
305
|
+
# Returns nil if no changes were made, self otherwise.
|
306
|
+
def reject!(&block)
|
307
|
+
changed = false
|
308
|
+
each do |key, val|
|
309
|
+
if yield(key, val)
|
310
|
+
changed = true
|
311
|
+
delete(key)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
changed ? self : nil
|
315
|
+
end
|
316
|
+
|
317
|
+
# Deletes every attribute for which block is true.
|
318
|
+
# Similar to reject! but returns self.
|
319
|
+
def delete_if(&block)
|
320
|
+
reject!(&block)
|
321
|
+
self
|
322
|
+
end
|
323
|
+
|
324
|
+
# Deletes every attribute for which block is true.
|
325
|
+
# Equivalent to self.dup.delete_if.
|
326
|
+
def reject(&block)
|
327
|
+
self.dup.delete_if(&block)
|
328
|
+
end
|
329
|
+
|
330
|
+
# deletes all attributes
|
331
|
+
def clear
|
332
|
+
each_key{|k| delete(k) }
|
333
|
+
end
|
334
|
+
|
335
|
+
# delete all attributes where the value is blank?, and return self. Contrast with compact!
|
336
|
+
def compact_blank!
|
337
|
+
delete_if{|k,v| v.blank? }
|
338
|
+
end
|
339
|
+
# delete all attributes where the value is nil?, and return self. Contrast with compact_blank!
|
340
|
+
def compact!
|
341
|
+
delete_if{|k,v| v.nil? }
|
342
|
+
end
|
343
|
+
# returns a hash with key/value pairs having nil? values removed
|
344
|
+
def compact
|
345
|
+
to_hash.delete_if{|k,v| v.nil? }
|
346
|
+
end
|
347
|
+
# returns a hash with key/value pairs having blank? values removed
|
348
|
+
def compact_blank
|
349
|
+
to_hash.delete_if{|k,v| v.blank? }
|
350
|
+
end
|
351
|
+
|
352
|
+
def self.included base
|
353
|
+
base.class_eval do
|
354
|
+
extend ClassMethods
|
355
|
+
include Enumerable
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
#
|
360
|
+
# Not yet implemented
|
361
|
+
#
|
362
|
+
|
363
|
+
# # Returns true if has_key? is false for all attributes in #keys
|
364
|
+
# def empty?
|
365
|
+
# keys.all?{|key| not has_key?(key) }
|
366
|
+
# end
|
367
|
+
#
|
368
|
+
# # The number of keys where #has_key is true
|
369
|
+
# def length
|
370
|
+
# keys.select{|key| has_key?(key) }.length
|
371
|
+
# end
|
372
|
+
# alias_method :size, :length
|
373
|
+
|
374
|
+
# # @param key<Object> The key to fetch.
|
375
|
+
# # @param *extras<Array> Default value.
|
376
|
+
# #
|
377
|
+
# # Returns a value for the given key. If the object doesn't has_key?(key),
|
378
|
+
# # several options exist:
|
379
|
+
# #
|
380
|
+
# # * With no other arguments, it will raise an IndexError exception;
|
381
|
+
# # * if default is given, then that will be returned;
|
382
|
+
# # * if the optional code block is specified, then that will be run and its
|
383
|
+
# # result returned.
|
384
|
+
# #
|
385
|
+
# # fetch does not evaluate any default values supplied when
|
386
|
+
# # the hash was created -- it only looks for keys in the hash.
|
387
|
+
# #
|
388
|
+
# # @return [Object] The value at key or the default value.
|
389
|
+
# def fetch(key, default=nil, &block)
|
390
|
+
# raise ""
|
391
|
+
# end
|
392
|
+
|
393
|
+
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
|
398
|
+
class Hash
|
399
|
+
|
400
|
+
# Recursively merges using receive
|
401
|
+
#
|
402
|
+
# Modifies the full receiver chain in-place.
|
403
|
+
#
|
404
|
+
# For each key in keys,
|
405
|
+
# * if self's value is nil, receive the attribute.
|
406
|
+
# * if self's attribute is an Array, append to it.
|
407
|
+
# * if self's value responds to tree_merge!, deep merge it.
|
408
|
+
# * if self's value responds_to merge!, merge! it.
|
409
|
+
# * otherwise, receive the value from other_hash
|
410
|
+
#
|
411
|
+
def tree_merge!(other_hash)
|
412
|
+
[self.keys, other_hash.keys].flatten.uniq.each do |key|
|
413
|
+
# get other's val if any
|
414
|
+
if other_hash.has_key?(key.to_sym) then other_val = other_hash[key.to_sym]
|
415
|
+
elsif other_hash.has_key?(key.to_s) then other_val = other_hash[key.to_s]
|
416
|
+
else next ; end
|
417
|
+
#
|
418
|
+
self_val = self[key]
|
419
|
+
# p ['hash tree_merge', key, self_val.respond_to?(:tree_merge!), self_val, other_val]
|
420
|
+
case
|
421
|
+
when other_val.nil? then next
|
422
|
+
when (not has_key?(key)) then self[key] = other_val
|
423
|
+
when self_val.is_a?(Array) then self[key] += other_val
|
424
|
+
when self_val.respond_to?(:tree_merge!) then self[key] = self_val.tree_merge!(other_val)
|
425
|
+
when self_val.respond_to?(:merge!) then self[key] = self_val.merge!(other_val)
|
426
|
+
else self[key] = other_val
|
427
|
+
end
|
428
|
+
end
|
429
|
+
self
|
430
|
+
end
|
431
|
+
|
432
|
+
def compact_blank!
|
433
|
+
reject!{|k,v| v.blank? } ; self
|
434
|
+
end
|
435
|
+
|
436
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Receiver
|
2
|
+
#
|
3
|
+
# adds methods to load and store from json, yaml or magic
|
4
|
+
#
|
5
|
+
# This will require 'json' UNLESS you have already included something (so if
|
6
|
+
# you want to say require 'yajl' then do that first).
|
7
|
+
#
|
8
|
+
module ActsAsLoadable
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def receive_json stream
|
12
|
+
receive(JSON.load(stream))
|
13
|
+
end
|
14
|
+
|
15
|
+
def receive_yaml stream
|
16
|
+
receive(YAML.load(stream))
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# The file is loaded with
|
21
|
+
# * YAML if the filename ends in .yaml or .yml
|
22
|
+
# * JSON otherwise
|
23
|
+
#
|
24
|
+
def receive_from_file filename
|
25
|
+
stream = File.open(filename)
|
26
|
+
(filename =~ /.ya?ml$/) ? receive_yaml(stream) : receive_json(stream)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def merge_from_file! filename
|
31
|
+
other_obj = self.class.receive_from_file(filename)
|
32
|
+
tree_merge! other_obj
|
33
|
+
end
|
34
|
+
|
35
|
+
# put all the things in ClassMethods at class level
|
36
|
+
def self.included base
|
37
|
+
require 'yaml'
|
38
|
+
require 'json' unless defined?(JSON)
|
39
|
+
base.extend ClassMethods
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Receiver
|
2
|
+
def tree_diff(other)
|
3
|
+
diff_hsh = {}
|
4
|
+
other = other.symbolize_keys if other.respond_to?(:symbolize_keys)
|
5
|
+
each do |k, v|
|
6
|
+
case
|
7
|
+
when v.is_a?(Array) && other[k].is_a?(Array)
|
8
|
+
val = v.tree_diff(other[k])
|
9
|
+
diff_hsh[k] = val unless val.blank?
|
10
|
+
when v.respond_to?(:tree_diff) && other[k].respond_to?(:to_hash)
|
11
|
+
val = v.tree_diff(other[k])
|
12
|
+
diff_hsh[k] = val unless val.blank?
|
13
|
+
else
|
14
|
+
diff_hsh[k] = v unless v == other[k]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
other_hsh = other.dup.delete_if{|k, v| has_key?(k) }
|
18
|
+
diff_hsh.merge!(other_hsh)
|
19
|
+
end
|
20
|
+
|
21
|
+
module ActsAsHash
|
22
|
+
def <=>(other)
|
23
|
+
return 1 if other.blank?
|
24
|
+
each_key do |k|
|
25
|
+
if has_key?(k) && other.has_key?(k)
|
26
|
+
cmp = self[k] <=> other[k]
|
27
|
+
return cmp unless cmp == 0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Array
|
36
|
+
def tree_diff(other)
|
37
|
+
arr = dup
|
38
|
+
if other.length > arr.length then arr = arr + ([nil] * (other.length - arr.length)) end
|
39
|
+
diff_ary = arr.zip(other).map do |arr_el, other_el|
|
40
|
+
if arr_el.respond_to?(:tree_diff) && other_el.respond_to?(:to_hash)
|
41
|
+
arr_el.tree_diff(other_el)
|
42
|
+
else
|
43
|
+
(arr_el == other_el) ? nil : [arr_el, other_el]
|
44
|
+
end
|
45
|
+
end.reject(&:blank?)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Hash
|
50
|
+
# Returns a hash that represents the difference between two hashes.
|
51
|
+
#
|
52
|
+
# Examples:
|
53
|
+
#
|
54
|
+
# {1 => 2}.tree_diff(1 => 2) # => {}
|
55
|
+
# {1 => 2}.tree_diff(1 => 3) # => {1 => 2}
|
56
|
+
# {}.tree_diff(1 => 2) # => {1 => 2}
|
57
|
+
# {1 => 2, 3 => 4}.tree_diff(1 => 2) # => {3 => 4}
|
58
|
+
def tree_diff(other)
|
59
|
+
diff_hsh = self.dup
|
60
|
+
each do |k, v|
|
61
|
+
case
|
62
|
+
when v.is_a?(Array) && other[k].is_a?(Array)
|
63
|
+
diff_hsh[k] = v.tree_diff(other[k])
|
64
|
+
diff_hsh.delete(k) if diff_hsh[k].blank?
|
65
|
+
when v.respond_to?(:tree_diff) && other[k].respond_to?(:to_hash)
|
66
|
+
diff_hsh[k] = v.tree_diff(other[k])
|
67
|
+
diff_hsh.delete(k) if diff_hsh[k].blank?
|
68
|
+
else diff_hsh.delete(k) if v == other[k]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
other_hsh = other.dup.delete_if{|k, v| has_key?(k) || has_key?(k.to_s) }
|
72
|
+
diff_hsh.merge!(other_hsh)
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Receiver
|
2
|
+
|
3
|
+
# An array of strings describing any ways this fails validation
|
4
|
+
def validation_errors
|
5
|
+
errors = []
|
6
|
+
if (ma = missing_attrs).present?
|
7
|
+
errors << "Missing values for {#{ma.join(",")}}"
|
8
|
+
end
|
9
|
+
errors
|
10
|
+
end
|
11
|
+
|
12
|
+
# returns a list of required but missing attributes
|
13
|
+
def missing_attrs
|
14
|
+
missing = []
|
15
|
+
self.class.required_rcvrs.each do |name, info|
|
16
|
+
missing << name if (not attr_set?(name))
|
17
|
+
end
|
18
|
+
missing
|
19
|
+
end
|
20
|
+
|
21
|
+
# methods become class-level
|
22
|
+
module ClassMethods
|
23
|
+
|
24
|
+
# class method gives info for all receiver attributes with required => true
|
25
|
+
def required_rcvrs
|
26
|
+
receiver_attrs.select{|name, info| info[:required] }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,289 @@
|
|
1
|
+
require 'active_support/core_ext/class'
|
2
|
+
require 'time'
|
3
|
+
require 'icss/receiver/validations'
|
4
|
+
|
5
|
+
# dummy type for receiving True or False
|
6
|
+
class Boolean ; end unless defined?(Boolean)
|
7
|
+
|
8
|
+
# Receiver lets you describe complex (even recursive!) actively-typed data models that
|
9
|
+
# * are creatable or assignable from static data structures
|
10
|
+
# * perform efficient type conversion when assigning from a data structure,
|
11
|
+
# * but with nothing in the way of normal assignment or instantiation
|
12
|
+
# * and no requirements on the initializer
|
13
|
+
#
|
14
|
+
# class Tweet
|
15
|
+
# include Receiver
|
16
|
+
# rcvr_accessor :id, Integer
|
17
|
+
# rcvr_accessor :user_id, Integer
|
18
|
+
# rcvr_accessor :created_at, Time
|
19
|
+
# end
|
20
|
+
# p Tweet.receive(:id => "7", :user_id => 9, :created_at => "20101231010203" )
|
21
|
+
# # => #<Tweet @id=7, @user_id=9, @created_at=2010-12-31 07:02:03 UTC>
|
22
|
+
#
|
23
|
+
# You can override receive behavior in a straightforward and predictable way:
|
24
|
+
#
|
25
|
+
# class TwitterUser
|
26
|
+
# include Receiver
|
27
|
+
# rcvr_accessor :id, Integer
|
28
|
+
# rcvr_accessor :screen_name, String
|
29
|
+
# rcvr_accessor :follower_ids, Array, :of => Integer
|
30
|
+
# # accumulate unique follower ids
|
31
|
+
# def receive_follower_ids(arr)
|
32
|
+
# @follower_ids = (@follower_ids||[]) + arr.map(&:to_i)
|
33
|
+
# @follower_ids.uniq!
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# The receiver pattern works naturally with inheritance:
|
38
|
+
#
|
39
|
+
# class TweetWithUser < Tweet
|
40
|
+
# rcvr_accessor :user, TwitterUser
|
41
|
+
# after_receive do |hsh|
|
42
|
+
# self.user_id = self.user.id if self.user
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# p TweetWithUser.receive(:id => 8675309, :created_at => "20101231010203", :user => { :id => 24601, :screen_name => 'bob', :follower_ids => [1, 8, 3, 4] })
|
46
|
+
# => #<TweetWithUser @id=8675309, @created_at=2010-12-31 07:02:03 UTC, @user=#<TwitterUser @id=24601, @screen_name="bob", @follower_ids=[1, 8, 3, 4]>, @user_id=24601>
|
47
|
+
#
|
48
|
+
# TweetWithUser was able to add another receiver, applicable only to itself and its subclasses.
|
49
|
+
#
|
50
|
+
# The receive method works well with sparse data -- you can accumulate
|
51
|
+
# attributes without trampling formerly set values:
|
52
|
+
#
|
53
|
+
# tw = Tweet.receive(:id => "7", :user_id => 9 )
|
54
|
+
# p tw
|
55
|
+
# # => #<Tweet @id=7, @user_id=9>
|
56
|
+
#
|
57
|
+
# tw.receive!(:created_at => "20101231010203" )
|
58
|
+
# p tw
|
59
|
+
# # => #<Tweet @id=7, @user_id=9, @created_at=2010-12-31 07:02:03 UTC>
|
60
|
+
#
|
61
|
+
# Note the distinction between an explicit nil field and a missing field:
|
62
|
+
#
|
63
|
+
# tw.receive!(:user_id => nil, :created_at => "20090506070809" )
|
64
|
+
# p tw
|
65
|
+
# # => #<Tweet @id=7, @user_id=nil, @created_at=2009-05-06 12:08:09 UTC>
|
66
|
+
#
|
67
|
+
# There are helpers for default and required attributes:
|
68
|
+
#
|
69
|
+
# class Foo
|
70
|
+
# include Receiver
|
71
|
+
# rcvr_accessor :is_reqd, String, :required => true
|
72
|
+
# rcvr_accessor :also_reqd, String, :required => true
|
73
|
+
# rcvr_accessor :has_default, String, :default => 'hello'
|
74
|
+
# end
|
75
|
+
# foo_obj = Foo.receive(:is_reqd => "hi")
|
76
|
+
# # => #<Foo:0x00000100bd9740 @is_reqd="hi" @has_default="hello">
|
77
|
+
# foo_obj.missing_attrs
|
78
|
+
# # => [:also_reqd]
|
79
|
+
#
|
80
|
+
module Receiver
|
81
|
+
|
82
|
+
RECEIVER_BODIES = {} unless defined?(RECEIVER_BODIES)
|
83
|
+
RECEIVER_BODIES[Symbol] = %q{ v.nil? ? nil : v.to_sym }
|
84
|
+
RECEIVER_BODIES[String] = %q{ v.to_s }
|
85
|
+
RECEIVER_BODIES[Integer] = %q{ v.nil? ? nil : v.to_i }
|
86
|
+
RECEIVER_BODIES[Float] = %q{ v.nil? ? nil : v.to_f }
|
87
|
+
RECEIVER_BODIES[Time] = %q{ v.nil? ? nil : Time.parse(v.to_s).utc }
|
88
|
+
RECEIVER_BODIES[Date] = %q{ v.nil? ? nil : Date.parse(v.to_s) }
|
89
|
+
RECEIVER_BODIES[Array] = %q{ v.nil? ? nil : v }
|
90
|
+
RECEIVER_BODIES[Hash] = %q{ v.nil? ? nil : v }
|
91
|
+
RECEIVER_BODIES[Boolean] = %q{ v.nil? ? nil : (v.to_s.strip != "false") }
|
92
|
+
RECEIVER_BODIES[NilClass] = %q{ raise "This field must be nil, but #{v} was given" unless (v.nil?) ; nil }
|
93
|
+
RECEIVER_BODIES[Object] = %q{ v } # accept and love the object just as it is
|
94
|
+
RECEIVER_BODIES.each do |k,b|
|
95
|
+
if k.is_a?(Class)
|
96
|
+
k.class_eval <<-STR, __FILE__, __LINE__ + 1
|
97
|
+
def self.receive(v)
|
98
|
+
#{b}
|
99
|
+
end
|
100
|
+
STR
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
TYPE_ALIASES = {
|
105
|
+
:null => NilClass,
|
106
|
+
:boolean => Boolean,
|
107
|
+
:string => String, :bytes => String,
|
108
|
+
:symbol => Symbol,
|
109
|
+
:int => Integer, :integer => Integer, :long => Integer,
|
110
|
+
:time => Time, :date => Date,
|
111
|
+
:float => Float, :double => Float,
|
112
|
+
:hash => Hash, :map => Hash,
|
113
|
+
:array => Array,
|
114
|
+
} unless defined?(TYPE_ALIASES)
|
115
|
+
|
116
|
+
#
|
117
|
+
# modify object in place with new typecast values.
|
118
|
+
#
|
119
|
+
def receive! hsh
|
120
|
+
raise "Can't receive (it isn't hashlike): #{hsh.inspect}" unless hsh.respond_to?(:[])
|
121
|
+
self.class.receiver_attr_names.each do |attr|
|
122
|
+
if hsh.has_key?(attr.to_sym) then val = hsh[attr.to_sym]
|
123
|
+
elsif hsh.has_key?(attr.to_s) then val = hsh[attr.to_s]
|
124
|
+
else next ; end
|
125
|
+
_receive_attr attr, val
|
126
|
+
end
|
127
|
+
impose_defaults!(hsh)
|
128
|
+
run_after_receivers(hsh)
|
129
|
+
self
|
130
|
+
end
|
131
|
+
|
132
|
+
def unset!(attr)
|
133
|
+
self.send(:remove_instance_variable, "@#{attr}") if self.instance_variable_defined?("@#{attr}")
|
134
|
+
end
|
135
|
+
|
136
|
+
# true if the attr is a receiver variable and it has been set
|
137
|
+
def attr_set?(attr)
|
138
|
+
receiver_attrs.has_key?(attr) && self.instance_variable_defined?("@#{attr}")
|
139
|
+
end
|
140
|
+
|
141
|
+
protected
|
142
|
+
def _receive_attr attr, val
|
143
|
+
self.send("receive_#{attr}", val)
|
144
|
+
end
|
145
|
+
|
146
|
+
def impose_defaults!(hsh)
|
147
|
+
self.class.receiver_defaults.each do |attr, val|
|
148
|
+
next if attr_set?(attr)
|
149
|
+
self.instance_variable_set "@#{attr}", val
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def run_after_receivers(hsh)
|
154
|
+
self.class.after_receivers.each do |after_receiver|
|
155
|
+
self.instance_exec(hsh, &after_receiver)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
public
|
159
|
+
|
160
|
+
module ClassMethods
|
161
|
+
|
162
|
+
#
|
163
|
+
# Returns a new instance with the given hash used to set all rcvrs.
|
164
|
+
#
|
165
|
+
# All args after the first are passed to the initializer.
|
166
|
+
#
|
167
|
+
def receive hsh, *args
|
168
|
+
hsh ||= {}
|
169
|
+
obj = self.new(*args)
|
170
|
+
obj.receive!(hsh)
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# define a receiver attribute.
|
175
|
+
# automatically generates an attr_accessor on the class if none exists
|
176
|
+
#
|
177
|
+
# @option [Boolean] :required - Adds an error on validation if the attribute is never set
|
178
|
+
# @option [Object] :default - After any receive! operation, attribute is set to this value unless attr_set? is true
|
179
|
+
# @option [Class] :of - For collections (Array, Hash, etc), the type of the collection's items
|
180
|
+
#
|
181
|
+
def rcvr name, type, info={}
|
182
|
+
name = name.to_sym
|
183
|
+
type = type_to_klass(type)
|
184
|
+
class_eval <<-STR, __FILE__, __LINE__ + 1
|
185
|
+
def receive_#{name}(v)
|
186
|
+
self.#{name} = #{receiver_body_for(type, info)}
|
187
|
+
end
|
188
|
+
STR
|
189
|
+
receiver_attr_names << name unless receiver_attr_names.include?(name)
|
190
|
+
receiver_attrs[name] = info.merge({ :name => name, :type => type })
|
191
|
+
end
|
192
|
+
|
193
|
+
def after_receive &block
|
194
|
+
self.after_receivers << block
|
195
|
+
end
|
196
|
+
|
197
|
+
def type_to_klass(type)
|
198
|
+
case
|
199
|
+
when type.is_a?(Class) then return type
|
200
|
+
when TYPE_ALIASES.has_key?(type) then TYPE_ALIASES[type]
|
201
|
+
# when (type.is_a?(Symbol) && type.to_s =~ /^[A-Z]/) then type.to_s.constantize
|
202
|
+
else raise "Can\'t handle type #{type}: is it a Class or one of the TYPE_ALIASES? "
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# defines a receiver attribute, an attr_reader and an attr_writer
|
207
|
+
# attr_reader is skipped if the getter method is already defined;
|
208
|
+
# attr_writer is skipped if the setter method is already defined;
|
209
|
+
def rcvr_accessor name, type, info={}
|
210
|
+
attr_reader(name) unless method_defined?(name)
|
211
|
+
attr_writer(name) unless method_defined?("#{name}=")
|
212
|
+
rcvr name, type, info
|
213
|
+
end
|
214
|
+
# defines a receiver attribute and an attr_reader
|
215
|
+
# attr_reader is skipped if the getter method is already defined.
|
216
|
+
def rcvr_reader name, type, info={}
|
217
|
+
attr_reader(name) unless method_defined?(name)
|
218
|
+
rcvr name, type, info
|
219
|
+
end
|
220
|
+
# defines a receiver attribute and an attr_writer
|
221
|
+
# attr_writer is skipped if the setter method is already defined.
|
222
|
+
def rcvr_writer name, type, info={}
|
223
|
+
attr_writer(name) unless method_defined?("#{name}=")
|
224
|
+
rcvr name, type, info
|
225
|
+
end
|
226
|
+
|
227
|
+
#
|
228
|
+
# Defines a receiver for attributes sent to receive! that are
|
229
|
+
# * not defined as receivers
|
230
|
+
# * attribute name does not start with '_'
|
231
|
+
#
|
232
|
+
# @example
|
233
|
+
# class Foo ; include Receiver
|
234
|
+
# rcvr_accessor :bob, String
|
235
|
+
# rcvr_remaining :other_params
|
236
|
+
# end
|
237
|
+
# foo_obj = Foo.receive(:bob => 'hi, bob", :joe => 'hi, joe')
|
238
|
+
# # => <Foo @bob='hi, bob' @other_params={ :joe => 'hi, joe' }>
|
239
|
+
def rcvr_remaining name, info={}
|
240
|
+
rcvr_reader name, Hash, info
|
241
|
+
after_receive do |hsh|
|
242
|
+
remaining_vals_hsh = hsh.except(* keys).reject!{|k,v| k.to_s =~ /^_/}
|
243
|
+
self._receive_attr name, remaining_vals_hsh
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# a hash from attribute names to their default values if given
|
248
|
+
def receiver_defaults
|
249
|
+
defs = {}
|
250
|
+
receiver_attrs.each do |name, info|
|
251
|
+
defs[name] = info[:default] if info.has_key?(:default)
|
252
|
+
end
|
253
|
+
defs
|
254
|
+
end
|
255
|
+
|
256
|
+
private
|
257
|
+
def receiver_body_for type, info
|
258
|
+
type = type_to_klass(type)
|
259
|
+
# Note that Array and Hash only need (and only get) special treatment when
|
260
|
+
# they have an :of => SomeType option.
|
261
|
+
case
|
262
|
+
when info[:of] && (type == Array)
|
263
|
+
%Q{ v.nil? ? nil : v.map{|el| #{info[:of]}.receive(el) } }
|
264
|
+
when info[:of] && (type == Hash)
|
265
|
+
%Q{ v.nil? ? nil : v.inject({}){|h, (el,val)| h[el] = #{info[:of]}.receive(val); h } }
|
266
|
+
when Receiver::RECEIVER_BODIES.include?(type)
|
267
|
+
Receiver::RECEIVER_BODIES[type]
|
268
|
+
when type.is_a?(Class)
|
269
|
+
%Q{v.blank? ? nil : #{type}.receive(v) }
|
270
|
+
# when (type.is_a?(Symbol) && type.to_s =~ /^[A-Z]/)
|
271
|
+
# # a hack so you can use a class not defined yet
|
272
|
+
# %Q{v.blank? ? nil : #{type}.receive(v) }
|
273
|
+
else
|
274
|
+
raise("Can't receive #{type} #{info}")
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# set up receiver attributes, and bring in methods from the ClassMethods module at class-level
|
280
|
+
def self.included base
|
281
|
+
base.class_eval do
|
282
|
+
class_inheritable_accessor :receiver_attrs, :receiver_attr_names, :after_receivers
|
283
|
+
self.receiver_attrs = {} # info about the attr
|
284
|
+
self.receiver_attr_names = [] # ordered set of attr names
|
285
|
+
self.after_receivers = [] # blocks to execute following receive!
|
286
|
+
extend ClassMethods
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
data/lib/icss/type.rb
CHANGED
@@ -128,7 +128,7 @@ module Icss
|
|
128
128
|
rcvr_accessor :namespace, String
|
129
129
|
attr_accessor :parent
|
130
130
|
# the avro base type name
|
131
|
-
|
131
|
+
class_inheritable_accessor :type
|
132
132
|
include Icss::Validations
|
133
133
|
|
134
134
|
# In named types, the namespace and name are determined in one of the following ways:
|
@@ -385,8 +385,7 @@ module Icss
|
|
385
385
|
# (do not confuse with EnumType, which is not an EnumerableType. sigh).
|
386
386
|
#
|
387
387
|
class EnumerableType < Type
|
388
|
-
|
389
|
-
class_attribute :ruby_klass
|
388
|
+
class_inheritable_accessor :type, :ruby_klass
|
390
389
|
def to_hash
|
391
390
|
super.merge( :type => type.to_s )
|
392
391
|
end
|
@@ -489,7 +488,7 @@ module Icss
|
|
489
488
|
#
|
490
489
|
class FixedType < NamedType
|
491
490
|
rcvr_accessor :size, Integer, :required => true
|
492
|
-
|
491
|
+
class_inheritable_accessor :ruby_klass
|
493
492
|
self.type = :fixed
|
494
493
|
self.ruby_klass = String
|
495
494
|
|
data/lib/icss.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
require '
|
4
|
-
require 'gorillib/receiver/acts_as_hash'
|
5
|
-
require 'gorillib/receiver/acts_as_loadable'
|
6
|
-
require 'gorillib/receiver/validations'
|
7
|
-
require 'time' # ain't that always the way
|
1
|
+
require 'active_support/inflector' # for classify and constantize
|
2
|
+
require 'active_support/core_ext/hash/keys'
|
3
|
+
# require 'active_support/core_ext/hash/deep_merge'
|
8
4
|
|
9
5
|
$: << File.dirname(__FILE__)
|
6
|
+
require 'icss/receiver'
|
7
|
+
require 'icss/receiver/acts_as_hash'
|
8
|
+
require 'icss/receiver/acts_as_loadable'
|
9
|
+
#
|
10
10
|
require 'icss/validations'
|
11
11
|
require 'icss/type'
|
12
12
|
require 'icss/message'
|
data/spec/spec_helper.rb
CHANGED
@@ -1,31 +1,12 @@
|
|
1
|
-
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
3
|
require 'rspec'
|
4
|
+
require 'icss'
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
require File.join(File.dirname(__FILE__), '../lib/boot')
|
11
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
12
|
-
|
13
|
-
require 'goliath'
|
14
|
-
require 'em-synchrony'
|
15
|
-
require 'goliath/test_helper'
|
16
|
-
require 'support/test_helper'
|
17
|
-
|
18
|
-
# Requires custom matchers & macros, etc from files in ./support/ & subdirs
|
19
|
-
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
20
|
-
|
21
|
-
# Configure rspec
|
22
|
-
RSpec.configure do |config|
|
23
|
-
config.include Goliath::TestHelper, :example_group => {
|
24
|
-
:file_path => /spec/
|
25
|
-
}
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
Spork.each_run do
|
30
|
-
# This code will be run each time you run your specs.
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
31
12
|
end
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: icss
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 27
|
5
4
|
prerelease:
|
6
|
-
|
7
|
-
- 0
|
8
|
-
- 0
|
9
|
-
- 2
|
10
|
-
version: 0.0.2
|
5
|
+
version: 0.0.4
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Philip (flip) Kromer for Infochimps
|
@@ -15,118 +10,86 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-06-07 00:00:00
|
13
|
+
date: 2011-06-07 00:00:00 -07:00
|
14
|
+
default_executable:
|
19
15
|
dependencies:
|
20
16
|
- !ruby/object:Gem::Dependency
|
21
|
-
prerelease: false
|
22
17
|
name: yajl-ruby
|
23
|
-
|
24
|
-
version_requirements: &id001 !ruby/object:Gem::Requirement
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
19
|
none: false
|
26
20
|
requirements:
|
27
21
|
- - ~>
|
28
22
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 59
|
30
|
-
segments:
|
31
|
-
- 0
|
32
|
-
- 8
|
33
|
-
- 2
|
34
23
|
version: 0.8.2
|
35
|
-
|
36
|
-
- !ruby/object:Gem::Dependency
|
24
|
+
type: :runtime
|
37
25
|
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
38
28
|
name: gorillib
|
39
|
-
|
40
|
-
version_requirements: &id002 !ruby/object:Gem::Requirement
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
30
|
none: false
|
42
31
|
requirements:
|
43
32
|
- - ~>
|
44
33
|
- !ruby/object:Gem::Version
|
45
|
-
|
46
|
-
|
47
|
-
- 0
|
48
|
-
- 0
|
49
|
-
- 7
|
50
|
-
version: 0.0.7
|
51
|
-
requirement: *id002
|
52
|
-
- !ruby/object:Gem::Dependency
|
34
|
+
version: 0.0.4
|
35
|
+
type: :runtime
|
53
36
|
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
54
39
|
name: rspec
|
55
|
-
|
56
|
-
version_requirements: &id003 !ruby/object:Gem::Requirement
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
41
|
none: false
|
58
42
|
requirements:
|
59
43
|
- - ~>
|
60
44
|
- !ruby/object:Gem::Version
|
61
|
-
hash: 3
|
62
|
-
segments:
|
63
|
-
- 2
|
64
|
-
- 3
|
65
|
-
- 0
|
66
45
|
version: 2.3.0
|
67
|
-
|
68
|
-
- !ruby/object:Gem::Dependency
|
46
|
+
type: :development
|
69
47
|
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
70
50
|
name: yard
|
71
|
-
|
72
|
-
version_requirements: &id004 !ruby/object:Gem::Requirement
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
52
|
none: false
|
74
53
|
requirements:
|
75
54
|
- - ~>
|
76
55
|
- !ruby/object:Gem::Version
|
77
|
-
hash: 7
|
78
|
-
segments:
|
79
|
-
- 0
|
80
|
-
- 6
|
81
|
-
- 0
|
82
56
|
version: 0.6.0
|
83
|
-
|
84
|
-
- !ruby/object:Gem::Dependency
|
57
|
+
type: :development
|
85
58
|
prerelease: false
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
86
61
|
name: bundler
|
87
|
-
|
88
|
-
version_requirements: &id005 !ruby/object:Gem::Requirement
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
89
63
|
none: false
|
90
64
|
requirements:
|
91
65
|
- - ~>
|
92
66
|
- !ruby/object:Gem::Version
|
93
|
-
hash: 23
|
94
|
-
segments:
|
95
|
-
- 1
|
96
|
-
- 0
|
97
|
-
- 0
|
98
67
|
version: 1.0.0
|
99
|
-
|
100
|
-
- !ruby/object:Gem::Dependency
|
68
|
+
type: :development
|
101
69
|
prerelease: false
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
102
72
|
name: jeweler
|
103
|
-
|
104
|
-
version_requirements: &id006 !ruby/object:Gem::Requirement
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
105
74
|
none: false
|
106
75
|
requirements:
|
107
76
|
- - ~>
|
108
77
|
- !ruby/object:Gem::Version
|
109
|
-
hash: 7
|
110
|
-
segments:
|
111
|
-
- 1
|
112
|
-
- 5
|
113
|
-
- 2
|
114
78
|
version: 1.5.2
|
115
|
-
|
116
|
-
- !ruby/object:Gem::Dependency
|
79
|
+
type: :development
|
117
80
|
prerelease: false
|
81
|
+
version_requirements: *id006
|
82
|
+
- !ruby/object:Gem::Dependency
|
118
83
|
name: rcov
|
119
|
-
|
120
|
-
version_requirements: &id007 !ruby/object:Gem::Requirement
|
84
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
121
85
|
none: false
|
122
86
|
requirements:
|
123
87
|
- - ">="
|
124
88
|
- !ruby/object:Gem::Version
|
125
|
-
hash: 3
|
126
|
-
segments:
|
127
|
-
- 0
|
128
89
|
version: "0"
|
129
|
-
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: *id007
|
130
93
|
description: "Infochimps Stupid Schema library: an avro-compatible data description standard. ICSS completely describes a collection of data (and associated assets) in a way that is expressive, scalable and sufficient to drive remarkably complex downstream processes."
|
131
94
|
email: coders@infochimps.com
|
132
95
|
executables: []
|
@@ -139,8 +102,6 @@ extra_rdoc_files:
|
|
139
102
|
files:
|
140
103
|
- .document
|
141
104
|
- .rspec
|
142
|
-
- .watchr
|
143
|
-
- CHANGELOG.textile
|
144
105
|
- Gemfile
|
145
106
|
- Gemfile.lock
|
146
107
|
- LICENSE.textile
|
@@ -166,12 +127,16 @@ files:
|
|
166
127
|
- lib/icss.rb
|
167
128
|
- lib/icss/brevity.rb
|
168
129
|
- lib/icss/code_asset.rb
|
169
|
-
- lib/icss/core_ext.rb
|
170
130
|
- lib/icss/data_asset.rb
|
171
131
|
- lib/icss/message.rb
|
172
132
|
- lib/icss/old.rb
|
173
133
|
- lib/icss/protocol.rb
|
174
134
|
- lib/icss/protocol_set.rb
|
135
|
+
- lib/icss/receiver.rb
|
136
|
+
- lib/icss/receiver/acts_as_hash.rb
|
137
|
+
- lib/icss/receiver/acts_as_loadable.rb
|
138
|
+
- lib/icss/receiver/tree_diff.rb
|
139
|
+
- lib/icss/receiver/validations.rb
|
175
140
|
- lib/icss/sample_message_call.rb
|
176
141
|
- lib/icss/target.rb
|
177
142
|
- lib/icss/type.rb
|
@@ -180,6 +145,7 @@ files:
|
|
180
145
|
- lib/icss/view_helper.rb
|
181
146
|
- spec/icss_spec.rb
|
182
147
|
- spec/spec_helper.rb
|
148
|
+
has_rdoc: true
|
183
149
|
homepage: http://github.com/mrflip/icss
|
184
150
|
licenses:
|
185
151
|
- MIT
|
@@ -193,7 +159,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
193
159
|
requirements:
|
194
160
|
- - ">="
|
195
161
|
- !ruby/object:Gem::Version
|
196
|
-
hash:
|
162
|
+
hash: -803655258578775756
|
197
163
|
segments:
|
198
164
|
- 0
|
199
165
|
version: "0"
|
@@ -202,14 +168,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
202
168
|
requirements:
|
203
169
|
- - ">="
|
204
170
|
- !ruby/object:Gem::Version
|
205
|
-
hash: 3
|
206
|
-
segments:
|
207
|
-
- 0
|
208
171
|
version: "0"
|
209
172
|
requirements: []
|
210
173
|
|
211
174
|
rubyforge_project:
|
212
|
-
rubygems_version: 1.
|
175
|
+
rubygems_version: 1.5.0
|
213
176
|
signing_key:
|
214
177
|
specification_version: 3
|
215
178
|
summary: "Infochimps Stupid Schema library: an avro-compatible data description standard. ICSS completely describes a collection of data (and associated assets) in a way that is expressive, scalable and sufficient to drive remarkably complex downstream processes."
|
data/.watchr
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# -*- ruby -*-
|
2
|
-
|
3
|
-
def run_spec(file)
|
4
|
-
unless File.exist?(file)
|
5
|
-
puts "#{file} does not exist"
|
6
|
-
return
|
7
|
-
end
|
8
|
-
|
9
|
-
puts "Running #{file}"
|
10
|
-
system "bundle exec rspec #{file}"
|
11
|
-
puts
|
12
|
-
end
|
13
|
-
|
14
|
-
watch("spec/.*/*_spec\.rb") do |match|
|
15
|
-
run_spec match[0]
|
16
|
-
end
|
17
|
-
|
18
|
-
watch("app/(.*)\.rb") do |match|
|
19
|
-
run_spec %{spec/#{match[1]}_spec.rb}
|
20
|
-
end
|
data/CHANGELOG.textile
DELETED
data/lib/icss/core_ext.rb
DELETED