data_bindings 0.0.1

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.
@@ -0,0 +1,32 @@
1
+ module DataBindings
2
+ module Adapters
3
+ module TNetstring
4
+ include Ruby
5
+ include DataBindings::GemRequirement
6
+
7
+ # Constructs a wrapped object from a Tnetstring
8
+ # @param [String] str The Tnetstring object
9
+ # @return [RubyObjectAdapter, RubyArrayAdapter] The wrapped object
10
+ def from_tnetstring(str)
11
+ from_ruby(::TNetstring.parse(str)[0])
12
+ end
13
+ gentle_require_gem :from_tnetstring, 'tnetstring'
14
+
15
+ module Convert
16
+ include ConverterHelper
17
+ include DataBindings::GemRequirement
18
+
19
+ # Creates a String repsentation of a Ruby Hash or Array.
20
+ # @param [Generator] generator The generator that invokes this constructor
21
+ # @param [Symbol] name The name of the binding used on this object
22
+ # @param [Array, Hash] obj The object to be represented in JSON
23
+ # @return [String] The Tnetstring representation of this object
24
+ def force_convert_to_tnetstring
25
+ ::TNetstring.dump(self.to_hash)
26
+ end
27
+ gentle_require_gem :force_convert_to_tnetstring, 'tnetstring'
28
+ standard_converter :convert_to_tnetstring
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,74 @@
1
+ module DataBindings
2
+ module Adapters
3
+ module XML
4
+ include Ruby
5
+ include DataBindings::GemRequirement
6
+
7
+ # Constructs a wrapped object from a JSON string
8
+ # @param [String] str The JSON object
9
+ # @return [RubyObjectAdapter, RubyArrayAdapter] The wrapped object
10
+ def from_xml(str)
11
+ from_ruby(from_xml_obj(Nokogiri::XML(str)))
12
+ end
13
+ gentle_require_gem :from_xml, 'nokogiri'
14
+
15
+ def from_xml_obj(o)
16
+ case o.type
17
+ when Nokogiri::XML::Node::DOCUMENT_NODE
18
+ from_xml_obj(o.children[0])
19
+ when Nokogiri::XML::Node::TEXT_NODE
20
+ o.text
21
+ when Nokogiri::XML::Node::ELEMENT_NODE
22
+ if o.children.size == 1 and o.children[0].text?
23
+ from_xml_obj(o.children[0])
24
+ elsif o.children[0].name == '0'
25
+ o.children.map{ |c| from_xml_obj(c) }
26
+ else
27
+ Hash[o.children.map { |n| [n.name, from_xml_obj(n)] }]
28
+ end
29
+ end
30
+ end
31
+ gentle_require_gem :from_xml_obj, 'nokogiri'
32
+
33
+ # Creates a String repsentation of a Ruby Hash or Array.
34
+ # @param [Generator] generator The generator that invokes this constructor
35
+ # @param [Symbol] name The name of the binding used on this object
36
+ # @param [Array, Hash] obj The object to be represented in JSON
37
+ # @return [String] The JSON representation of this object
38
+
39
+ module Convert
40
+ include DataBindings::GemRequirement
41
+ include ConverterHelper
42
+
43
+ def force_convert_to_xml
44
+ Convert.construct(@generator, @name, self, @binding_block)
45
+ end
46
+ gentle_require_gem :force_convert_to_xml, 'builder'
47
+ standard_converter :convert_to_xml
48
+
49
+ def self.construct(generator, name, obj, builder = nil)
50
+ root = builder.nil?
51
+ builder ||= Builder::XmlMarkup.new
52
+ builder.instruct!(:xml, :encoding => "UTF-8") if root
53
+ case obj
54
+ when Array
55
+ builder.__send__(name || "doc") do |b|
56
+ obj.each_with_index(o, i)
57
+ construct(generator, i.to_s, o, b)
58
+ end
59
+ when Hash
60
+ builder.__send__(name || "doc") do |b|
61
+ obj.each do |k, v|
62
+ construct(generator, k, v, b)
63
+ end
64
+ end
65
+ else
66
+ builder.__send__(name, obj)
67
+ end
68
+ builder.target! if root
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,26 @@
1
+ require 'yaml'
2
+
3
+ module DataBindings
4
+ module Adapters
5
+ module YAML
6
+ include Ruby
7
+
8
+ def from_yaml(str)
9
+ from_ruby(::YAML::load(str))
10
+ end
11
+
12
+ def from_yaml_file(f)
13
+ from_ruby(::YAML::load_file(f))
14
+ end
15
+
16
+ module Convert
17
+ include ConverterHelper
18
+
19
+ def force_convert_to_yaml
20
+ ::YAML::dump(self.to_nonindifferent_hash)
21
+ end
22
+ standard_converter :convert_to_yaml
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,12 @@
1
+ module DataBindings
2
+ module Adapters
3
+ autoload :BSON, 'data_bindings/adapters/bson'
4
+ autoload :JSON, 'data_bindings/adapters/json'
5
+ autoload :Native, 'data_bindings/adapters/native'
6
+ autoload :Ruby, 'data_bindings/adapters/ruby'
7
+ autoload :YAML, 'data_bindings/adapters/yaml'
8
+ autoload :Params, 'data_bindings/adapters/params'
9
+ autoload :XML, 'data_bindings/adapters/xml'
10
+ autoload :TNetstring, 'data_bindings/adapters/tnetstring'
11
+ end
12
+ end
@@ -0,0 +1,331 @@
1
+ module DataBindings
2
+ module Bound
3
+ ValidationError = Class.new(RuntimeError)
4
+ NoBindingName = Class.new(RuntimeError)
5
+
6
+ class Errors < DataBindings::IndifferentHash
7
+ attr_accessor :base
8
+
9
+ def join(st = nil)
10
+ (base ? [base].concat(values) : values).join(st)
11
+ end
12
+
13
+ def valid?
14
+ base.nil? && empty?
15
+ end
16
+
17
+ def clear
18
+ super
19
+ @base = nil
20
+ end
21
+ end
22
+
23
+ #include DataBindings::WriterInterceptor
24
+
25
+ attr_reader :errors, :source, :name, :generator
26
+
27
+ def valid?
28
+ calculate_validness
29
+ errors.valid?
30
+ end
31
+
32
+ def calculate_validness
33
+ if @last_hash.nil? || @last_hash != hash
34
+ @from = self if @last_hash
35
+ errors.clear
36
+ validate
37
+ @last_hash = hash
38
+ end
39
+ end
40
+
41
+ def valid!
42
+ valid? or raise FailedValidation.new("Object was invalid with the following errors: #{errors.join(", ")}", errors, source)
43
+ self
44
+ end
45
+
46
+ def pre_convert
47
+ valid!
48
+ end
49
+
50
+ def cast_element(lookup_name, source, type, opts = nil, &blk)
51
+ name = get_parameter_name(lookup_name, source, opts)
52
+ el = name && source[name]
53
+ raise_on_error = opts && opts.key?(:raise_on_error) ? opts[:raise_on_error] : false
54
+ allow_nil = opts && opts.key?(:allow_nil) ? opts[:allow_nil] : false
55
+ el ||= opts[:default] if opts && opts.key?(:default)
56
+
57
+ if el.nil? && name.nil?
58
+ @errors[lookup_name] = generate_error("not found", raise_on_error)
59
+ nil
60
+ else
61
+ new_el = if type.nil?
62
+ # anything goes
63
+ case el
64
+ when Array, Hash
65
+ blk ? register_sub(name, @generator.from_ruby(el).bind(&blk), raise_on_error) : el
66
+ else
67
+ el
68
+ end
69
+ elsif type == String
70
+ @errors[lookup_name] = generate_error("was not a String", raise_on_error) unless el.is_a?(String)
71
+ el
72
+ elsif type == Integer
73
+ begin
74
+ Integer(el)
75
+ rescue ArgumentError, TypeError
76
+ @errors[lookup_name] = generate_error("was not an Integer", raise_on_error)
77
+ el
78
+ end
79
+ elsif type == Float
80
+ begin
81
+ Float(el)
82
+ rescue ArgumentError, TypeError
83
+ @errors[lookup_name] = generate_error("was not a Float", raise_on_error)
84
+ el
85
+ end
86
+ elsif Array === type
87
+ if el.is_a?(Array)
88
+ @errors[lookup_name] = generate_error("did not match the length", raise_on_error) if opts && opts[:length] && !(opts[:length] === el.size)
89
+ if type.first
90
+ register_sub(name, @generator.from_ruby(el).bind_array { all_elements type.first }, raise_on_error)
91
+ elsif blk
92
+ register_sub(name, @generator.from_ruby(el).bind_array { all_elements &blk }, raise_on_error)
93
+ else
94
+ el
95
+ end
96
+ else
97
+ @errors[lookup_name] = generate_error("was not an Array", raise_on_error)
98
+ el
99
+ end
100
+ elsif type == :boolean
101
+ if allow_nil
102
+ el.nil? ? nil : DataBindings.true_boolean?(el)
103
+ else
104
+ DataBindings.true_boolean?(el)
105
+ end
106
+ elsif Symbol === type
107
+ if el.is_a?(Hash)
108
+ register_sub(name, @generator.from_ruby(el).bind(type), raise_on_error)
109
+ else
110
+ @errors[lookup_name] = generate_error("was not a Hash", raise_on_error)
111
+ el
112
+ end
113
+ else
114
+ raise "Unknown type #{type.inspect}"
115
+ end
116
+ if inclusion = opts && opts[:in]
117
+ @errors[lookup_name] = generate_error("was not included in #{inclusion.inspect}", raise_on_error) unless inclusion.include?(new_el)
118
+ end
119
+ @errors[lookup_name] = generate_error("was nil", raise_on_error) if new_el.nil? && !allow_nil
120
+ new_el
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ def dump_val(val)
127
+ if val.respond_to?(:to_hash)
128
+ val.to_hash
129
+ elsif val.respond_to?(:to_ary)
130
+ val.to_ary
131
+ else
132
+ val
133
+ end
134
+ end
135
+
136
+ def convert_target
137
+ self
138
+ end
139
+
140
+ def generate_error(str, raise_on_error)
141
+ raise_on_error ? raise(ValidationError, str) : str
142
+ end
143
+
144
+ def register_sub(name, sub, raise_on_error)
145
+ unless sub.valid?
146
+ @errors[name] = generate_error(sub.errors.to_s, raise_on_error)
147
+ end
148
+ sub
149
+ end
150
+
151
+ def validate
152
+ reset_validation_state
153
+ run_validation
154
+ enforce_strictness if @strict
155
+ end
156
+
157
+ def init_bound(generator, source, name, opts, validator)
158
+ @errors, @generator, @source, @name, @opts, @validator = Errors.new, generator, source, name, opts, validator
159
+ @from = @source
160
+ @strict = opts && opts.key?(:strict) ? opts[:strict] : generator.strict?
161
+ reset_validation_state
162
+ valid?
163
+ end
164
+
165
+ class BoundObject < DataBindings::IndifferentHash
166
+ include Bound
167
+
168
+ def initialize(generator, array_expected, source, name, opts, &blk)
169
+ raise BindingMismatch if array_expected
170
+ init_bound(generator, source, name, opts, blk)
171
+ end
172
+
173
+ def to_hash
174
+ keys.inject(DataBindings::IndifferentHash.new) { |h, k|
175
+ val = self[k]
176
+ h[k] = dump_val(val)
177
+ h
178
+ }
179
+ end
180
+
181
+ def to_nonindifferent_hash
182
+ keys.inject({}) { |h, k|
183
+ val = self[k]
184
+ h[k.to_s] = dump_val(val)
185
+ h
186
+ }
187
+ end
188
+
189
+ def to_native
190
+ valid!
191
+ data = inject(IndifferentHash.new) { |h, (k, v)|
192
+ h[k] = v.respond_to?(:to_native) ? v.to_native : v
193
+ h
194
+ }
195
+ if constructor = generator.native_constructors[name]
196
+ o = constructor[data.to_hash]
197
+ else
198
+ OpenStruct.new(data)
199
+ end
200
+ end
201
+
202
+ def property(name, type = nil, opts = nil, &blk)
203
+ type, opts = nil, type if type.is_a?(Hash)
204
+ self[name] = cast_element(name, @from, type, opts, &blk)
205
+ end
206
+
207
+ def required(name, type = nil, opts = nil, &blk)
208
+ type, opts = nil, type if type.is_a?(Hash)
209
+ opts ||= {}
210
+ opts[:allow_nil] = false
211
+ property(name, type, opts, &blk)
212
+ end
213
+
214
+ def optional(name, type = nil, opts = nil, &blk)
215
+ type, opts = nil, type if type.is_a?(Hash)
216
+ opts ||= {}
217
+ opts[:allow_nil] = true
218
+ property(name, type, opts, &blk)
219
+ end
220
+
221
+ def all_properties(type = nil, opts = nil)
222
+ type, opts = nil, type if type.is_a?(Hash)
223
+ @from.keys.each do |key|
224
+ property key, type, opts
225
+ end
226
+ end
227
+
228
+ def enforce_strictness
229
+ @errors.base = "hasn't been fully matched" unless size == @from.size
230
+ end
231
+
232
+ def copy_source
233
+ replace @source
234
+ end
235
+
236
+ private
237
+
238
+ def get_parameter_name(name, source, opts)
239
+ name = if opts && opts[:alias]
240
+ aliases = Array(opts[:alias])
241
+ index = aliases.index {|k| source.key?(k) }
242
+ index ? aliases[index] : name
243
+ else
244
+ name
245
+ end
246
+ source.key?(name) ? name : nil
247
+ end
248
+
249
+ def reset_validation_state
250
+ errors.clear
251
+ end
252
+
253
+ def run_validation
254
+ instance_eval(&@validator)
255
+ end
256
+ end
257
+
258
+ class BoundArray < Array
259
+ include Bound
260
+
261
+ def initialize(generator, array_expected, source, name, opts, &blk)
262
+ raise BindingMismatch unless array_expected
263
+ init_bound(generator, source, name, opts, blk)
264
+ end
265
+
266
+ def to_ary
267
+ self.inject([]) { |a, v|
268
+ a << dump_val(v)
269
+ }
270
+ end
271
+
272
+ def to_native
273
+ valid!
274
+ inject([]) {|a, el| a << (el.respond_to?(:to_native) ? el.to_native : el); a}
275
+ end
276
+
277
+ def elements(size = nil, type = nil, opts = nil, &blk)
278
+ if size.nil? || size.respond_to?(:to_int)
279
+ size ||= source.size
280
+ # consume all
281
+ (@pos...(@pos+size)).each do |i|
282
+ self[@pos] = cast_element(@pos, @from, type, opts, &blk)
283
+ @pos += 1
284
+ end
285
+ elsif size.respond_to?(:min) && size.respond_to?(:max)
286
+ original_pos = @pos
287
+ while (@pos - original_pos) <= size.max
288
+ begin
289
+ opts ||= {}
290
+ opts[:raise_on_error] = @pos >= size.min
291
+ self[@pos] = cast_element(@pos, @from, type, opts, &blk)
292
+ rescue ValidationError
293
+ break
294
+ end
295
+ @pos += 1
296
+ end
297
+ else
298
+ raise "Size isn't understood: #{size.inspect}"
299
+ end
300
+ end
301
+
302
+ def copy_source
303
+ replace @source
304
+ end
305
+
306
+ def enforce_strictness
307
+ @errors.base = "hasn't been fully matched" unless @pos.succ == source.size
308
+ end
309
+
310
+ def all_elements(type = nil, &blk)
311
+ elements(nil, type, &blk)
312
+ end
313
+
314
+ private
315
+
316
+ def get_parameter_name(name, source, opts)
317
+ source.at(name) ? name : nil
318
+ end
319
+
320
+ def reset_validation_state
321
+ @pos = 0
322
+ errors.clear
323
+ end
324
+
325
+ def run_validation
326
+ errors.base = "didn't match legnth #{@opts[:length]}" if @opts && @opts[:length] && !(@opts[:length] === size)
327
+ instance_eval(&@validator)
328
+ end
329
+ end
330
+ end
331
+ end