gorillib 0.4.1pre → 0.4.2pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.gitignore +13 -10
  2. data/.rspec +1 -1
  3. data/.yardopts +1 -0
  4. data/CHANGELOG.md +47 -0
  5. data/Gemfile +22 -19
  6. data/Guardfile +23 -9
  7. data/README.md +12 -12
  8. data/Rakefile +29 -40
  9. data/VERSION +1 -1
  10. data/examples/benchmark/factories_benchmark.rb +87 -0
  11. data/examples/builder/ironfan.rb +1 -19
  12. data/examples/hash/slicing_methods.rb +101 -0
  13. data/gorillib.gemspec +36 -35
  14. data/lib/gorillib/array/deep_compact.rb +4 -3
  15. data/lib/gorillib/array/simple_statistics.rb +76 -0
  16. data/lib/gorillib/base.rb +0 -1
  17. data/lib/gorillib/builder.rb +15 -30
  18. data/lib/gorillib/collection.rb +159 -57
  19. data/lib/gorillib/collection/model_collection.rb +136 -43
  20. data/lib/gorillib/datetime/parse.rb +4 -2
  21. data/lib/gorillib/{array → deprecated/array}/average.rb +0 -0
  22. data/lib/gorillib/{array → deprecated/array}/random.rb +2 -1
  23. data/lib/gorillib/{array → deprecated/array}/sorted_median.rb +0 -0
  24. data/lib/gorillib/{array → deprecated/array}/sorted_percentile.rb +0 -0
  25. data/lib/gorillib/deprecated/array/sorted_sample.rb +13 -0
  26. data/lib/gorillib/{metaprogramming → deprecated/metaprogramming}/aliasing.rb +0 -0
  27. data/lib/gorillib/enumerable/sum.rb +3 -3
  28. data/lib/gorillib/exception/raisers.rb +92 -22
  29. data/lib/gorillib/factories.rb +550 -0
  30. data/lib/gorillib/hash/mash.rb +15 -58
  31. data/lib/gorillib/hashlike/deep_compact.rb +2 -2
  32. data/lib/gorillib/hashlike/slice.rb +55 -40
  33. data/lib/gorillib/model.rb +5 -3
  34. data/lib/gorillib/model/base.rb +33 -119
  35. data/lib/gorillib/model/defaults.rb +58 -14
  36. data/lib/gorillib/model/errors.rb +10 -0
  37. data/lib/gorillib/model/factories.rb +1 -367
  38. data/lib/gorillib/model/field.rb +40 -18
  39. data/lib/gorillib/model/fixup.rb +16 -0
  40. data/lib/gorillib/model/positional_fields.rb +35 -0
  41. data/lib/gorillib/model/schema_magic.rb +162 -0
  42. data/lib/gorillib/model/serialization.rb +1 -2
  43. data/lib/gorillib/model/serialization/csv.rb +59 -0
  44. data/lib/gorillib/pathname.rb +19 -8
  45. data/lib/gorillib/some.rb +2 -0
  46. data/lib/gorillib/string/constantize.rb +17 -10
  47. data/lib/gorillib/string/inflector.rb +11 -7
  48. data/lib/gorillib/type/boolean.rb +40 -0
  49. data/lib/gorillib/type/extended.rb +76 -40
  50. data/lib/gorillib/type/url.rb +6 -4
  51. data/lib/gorillib/utils/console.rb +1 -18
  52. data/lib/gorillib/utils/edge_cases.rb +18 -0
  53. data/spec/examples/builder/ironfan_spec.rb +5 -10
  54. data/spec/gorillib/array/compact_blank_spec.rb +36 -21
  55. data/spec/gorillib/array/simple_statistics_spec.rb +143 -0
  56. data/spec/gorillib/builder_spec.rb +16 -20
  57. data/spec/gorillib/collection_spec.rb +131 -35
  58. data/spec/gorillib/exception/raisers_spec.rb +39 -0
  59. data/spec/gorillib/hash/deep_compact_spec.rb +3 -3
  60. data/spec/gorillib/model/{record/defaults_spec.rb → defaults_spec.rb} +5 -1
  61. data/spec/gorillib/model/factories_spec.rb +335 -0
  62. data/spec/gorillib/model/{record/overlay_spec.rb → overlay_spec.rb} +0 -0
  63. data/spec/gorillib/model/serialization_spec.rb +2 -2
  64. data/spec/gorillib/model_spec.rb +19 -18
  65. data/spec/gorillib/pathname_spec.rb +7 -7
  66. data/spec/gorillib/string/truncate_spec.rb +3 -13
  67. data/spec/gorillib/type/extended_spec.rb +50 -2
  68. data/spec/gorillib/utils/capture_output_spec.rb +1 -1
  69. data/spec/spec_helper.rb +10 -7
  70. data/spec/support/factory_test_helpers.rb +76 -0
  71. data/spec/support/gorillib_test_helpers.rb +36 -24
  72. data/spec/support/model_test_helpers.rb +39 -2
  73. metadata +86 -51
  74. data/lib/alt/kernel/call_stack.rb +0 -56
  75. data/lib/gorillib/array/sorted_sample.rb +0 -12
  76. data/lib/gorillib/builder/field.rb +0 -5
  77. data/lib/gorillib/collection/has_collection.rb +0 -31
  78. data/lib/gorillib/collection/list_collection.rb +0 -58
  79. data/lib/gorillib/exception/confidence.rb +0 -17
  80. data/lib/gorillib/io/system_helpers.rb +0 -30
  81. data/lib/gorillib/model/record_schema.rb +0 -9
  82. data/lib/gorillib/utils/stub_module.rb +0 -33
  83. data/spec/array/average_spec.rb +0 -24
  84. data/spec/array/sorted_median_spec.rb +0 -18
  85. data/spec/array/sorted_percentile_spec.rb +0 -24
  86. data/spec/array/sorted_sample_spec.rb +0 -28
  87. data/spec/gorillib/metaprogramming/aliasing_spec.rb +0 -180
  88. data/spec/gorillib/model/record/factories_spec.rb +0 -335
  89. data/spec/support/kcode_test_helper.rb +0 -16
@@ -9,19 +9,7 @@ module Gorillib
9
9
  attribute_set?(:default)
10
10
  end
11
11
  end
12
-
13
- # This is called by `read_attribute` if an attribute is unset; you should
14
- # not call this directly. You might use this to provide defaults, or lazy
15
- # access, or layered resolution.
16
- #
17
- # @param [String, Symbol, #to_s] field_name Name of the attribute to unset.
18
- # @return [nil] Ze goggles! Zey do nussing!
19
- def read_unset_attribute(field_name)
20
- field = self.class.fields[field_name]
21
- return unless field.has_default?
22
- write_attribute(field.name, attribute_default(field))
23
- end
24
-
12
+
25
13
  # FieldDefaults allows defaults to be declared for your fields
26
14
  #
27
15
  # Defaults are declared by passing the :default option to the field
@@ -50,6 +38,60 @@ module Gorillib
50
38
  # event.end_date.to_s #=> "2012-01-01"
51
39
  #
52
40
 
41
+ # This is called by `read_attribute` if an attribute is unset; you should
42
+ # not call this directly. You might use this to provide defaults, or lazy
43
+ # access, or layered resolution.
44
+ #
45
+ # Once a non-nil default value has been read, it is **fixed on the field**; this method
46
+ # will not be called again, and `attribute_set?(...)` will return `true`.
47
+ #
48
+ # @example values are fixed on read
49
+ # class Defaultable
50
+ # include Gorillib::Model
51
+ # field :timestamp, Integer, default: ->{ Time.now }
52
+ # end
53
+ # dd = Defaultable.new
54
+ # dd.attribute_set?(:timestamp) # => false
55
+ # dd.timestamp # => '2012-01-02 12:34:56 CST'
56
+ # dd.attribute_set?(:timestamp) # => true
57
+ # # The block is *not* re-run -- the time is the same
58
+ # dd.timestamp # => '2012-01-02 12:34:56 CST'
59
+ #
60
+ # @example If the default is a literal nil it is set as normal:
61
+ #
62
+ # Defaultable.field :might_be_nil, String, default: nil
63
+ # dd.attribute_set?(:might_be_nil) # => false
64
+ # dd.might_be_nil # => nil
65
+ # dd.attribute_set?(:might_be_nil) # => true
66
+ #
67
+ # If the default is generated from a block (or anything but a literal nil), no default is set:
68
+ #
69
+ # Defaultable.field :might_be_nil, String, default: ->{ puts 'ran!'; some_other_value ? some_other_value.reverse : nil }
70
+ # Defaultable.field :some_other_value, String
71
+ # dd = Defaultable.new
72
+ # dd.attribute_set?(:might_be_nil) # => false
73
+ # dd.might_be_nil # => nil
74
+ # 'ran!' # block was run
75
+ # dd.might_be_nil # => nil
76
+ # 'ran!' # block was run again
77
+ # dd.some_other_val = 'hello'
78
+ # dd.might_be_nil # => 'olleh'
79
+ # 'ran!' # block was run again, and set a value this time
80
+ # dd.some_other_val = 'goodbye'
81
+ # dd.might_be_nil # => 'olleh'
82
+ # # block was not run again
83
+ #
84
+ # @param [String, Symbol, #to_s] field_name Name of the attribute to unset.
85
+ # @return [Object] The new value
86
+ def read_unset_attribute(field_name)
87
+ field = self.class.fields[field_name] or return nil
88
+ return unless field.has_default?
89
+ val = attribute_default(field)
90
+ return nil if val.nil? && (not field.default.nil?) # don't write nil unless intent is clearly to have default nil
91
+ write_attribute(field.name, val)
92
+ end
93
+
94
+
53
95
  protected
54
96
 
55
97
  # the actual default value to assign to the attribute
@@ -57,8 +99,10 @@ module Gorillib
57
99
  return unless field.has_default?
58
100
  val = field.default
59
101
  case
60
- when (val.is_a?(Proc) || val.is_a?(UnboundMethod)) && (val.arity == 0)
102
+ when val.is_a?(Proc) && (val.arity == 0)
61
103
  self.instance_exec(&val)
104
+ when val.is_a?(UnboundMethod) && (val.arity == 0)
105
+ val.bind(self).call
62
106
  when val.respond_to?(:call)
63
107
  val.call(self, field.name)
64
108
  else
@@ -10,5 +10,15 @@ module Gorillib
10
10
  include Gorillib::Model::Error
11
11
  end
12
12
 
13
+ class ConflictingPositionError < ::ArgumentError
14
+ include Gorillib::Model::Error
15
+ end
16
+
17
+ # Exception raised if deserialized attributes don't have the right shape:
18
+ # for example, a CSV line with too many/too few fields
19
+ class RawDataMismatchError < ::StandardError
20
+ include Gorillib::Model::Error
21
+ end
22
+
13
23
  end
14
24
  end
@@ -1,367 +1 @@
1
- require 'pathname'
2
- require 'gorillib/type/extended'
3
-
4
- def Gorillib::Factory(*args)
5
- ::Gorillib::Factory.receive(*args)
6
- end
7
-
8
- module Gorillib
9
-
10
- module Factory
11
- class FactoryMismatchError < ArgumentError ; end
12
-
13
- def self.receive(type)
14
- case
15
- when factories.include?(type) then return factories[type]
16
- when type.respond_to?(:receive) then return type
17
- when type.is_a?(Proc) || type.is_a?(Method) then return Gorillib::Factory::ApplyProcFactory.new(type)
18
- when type.is_a?(String) then
19
- return( factories[type] = Gorillib::Inflector.constantize(Gorillib::Inflector.camelize(type.gsub(/\./, '/'))) )
20
- else raise ArgumentError, "Don't know which factory makes a #{type}"
21
- end
22
- end
23
-
24
- # Manufactures objects from their raw attributes hash
25
- #
26
- # A hash with a value for `:_type` is dispatched to the corresponding factory
27
- # Everything else is returned directly
28
- def self.make(obj)
29
- if obj.respond_to?(:has_key?) && (obj.has_key?(:_type) || obj.has_key?('_type'))
30
- factory = Gorillib::Factory(attrs[:_type])
31
- factory.receive(obj)
32
- else
33
- obj
34
- end
35
- end
36
-
37
- private
38
- def self.factories
39
- @factories ||= Hash.new
40
- end
41
- public
42
-
43
- def self.register_factory(factory, typenames)
44
- typenames.each{|typename| factories[typename] = factory }
45
- end
46
-
47
- class BaseFactory
48
- # [Class] The type of objects produced by this factory
49
- class_attribute :product
50
-
51
- def initialize(options={})
52
- @product = options.delete(:product){ self.class.product }
53
- if options[:blankish]
54
- define_singleton_method(:blankish, options.delete(:blankish))
55
- end
56
- redefine(:convert, options.delete(:convert)) if options.has_key?(:convert)
57
- warn "Unknown options #{options.keys}" unless options.empty?
58
- end
59
-
60
- def self.typename
61
- @typename ||= Gorillib::Inflector.underscore(product.name).to_sym
62
- end
63
- def typename ; self.class.typename ; end
64
-
65
- # A `native` object does not need any transformation; it is accepted directly.
66
- # By default, an object is native if it `is_a?(product)`
67
- #
68
- # @param [Object] obj the object to convert and receive
69
- # @return [true, false] true if the item does not need conversion
70
- def native?(obj)
71
- obj.is_a?(@product)
72
- end
73
- def self.native?(obj) self.new.native?(obj) ; end
74
-
75
- # A `blankish` object should be converted to `nil`, not a value
76
- #
77
- # @param [Object] obj the object to convert and receive
78
- # @return [true, false] true if the item is equivalent to a nil value
79
- def blankish?(obj)
80
- obj.nil? || (obj == "")
81
- end
82
- def self.blankish?(obj)
83
- obj.nil? || (obj == "")
84
- end
85
-
86
- protected
87
-
88
- def redefine(meth, *args, &block)
89
- if args.present?
90
- val = args.first
91
- case
92
- when block_given? then raise ArgumentError, "Pass a block or a value, not both"
93
- when val.is_a?(Proc) || val.is_a?(Method) then block = val
94
- else block = ->(*){ val.try_dup }
95
- end
96
- end
97
- define_singleton_method(meth, &block)
98
- self
99
- end
100
-
101
- # Raises a FactoryMismatchError.
102
- def mismatched!(obj, message=nil, *args)
103
- message ||= "item cannot be converted to #{product}"
104
- message << (" got #{obj.inspect}" rescue ' (and is uninspectable)')
105
- raise FactoryMismatchError, message, *args
106
- end
107
-
108
- def self.register_factory!(*typenames)
109
- typenames = [typename, product] if typenames.empty?
110
- Gorillib::Factory.register_factory(self.new, typenames)
111
- end
112
- end
113
-
114
- class ConvertingFactory < BaseFactory
115
- def receive(obj)
116
- return nil if blankish?(obj)
117
- return obj if native?(obj)
118
- convert(obj)
119
- rescue NoMethodError, TypeError, RangeError => err
120
- mismatched!(obj, err.message, err.backtrace)
121
- end
122
- protected
123
- # Convert a receivable object to the factory's product type. This method
124
- # should convert an object to `native?` form or die trying; any
125
- # polymorphism (such as converting an empty string to nil) happens in
126
- # other methods called by `receive`.
127
- #
128
- # @param [Object] obj the object to convert.
129
- def convert(obj)
130
- obj.dup
131
- end
132
- end
133
-
134
- #
135
- # A NonConvertingFactory accepts objects that are *already* native, and
136
- # throws a mismatch error for anything else.
137
- #
138
- # @example
139
- # ff = Gorillib::Factory::NonConvertingFactory.new(:product => String, :blankish => ->(obj){ obj.nil? })
140
- # ff.receive(nil) #=> nil
141
- # ff.receive("bob") #=> "bob"
142
- # ff.receive(:bob) #=> Gorillib::Factory::FactoryMismatchError: must be an instance of String, got 3
143
- #
144
- class NonConvertingFactory < BaseFactory
145
- def blankish?(obj) obj.nil? ; end
146
- def receive(obj)
147
- return nil if blankish?(obj)
148
- return obj if native?(obj)
149
- mismatched!(obj, "must be an instance of #{product},")
150
- rescue NoMethodError => err
151
- mismatched!(obj, err.message, err.backtrace)
152
- end
153
- end
154
-
155
- class ::Whatever < BaseFactory
156
- def initialize(options={})
157
- options.slice!(:convert, :blankish)
158
- super(options)
159
- end
160
- def native?(obj) true ; end
161
- def blankish?(obj) false ; end
162
- def receive(obj) obj ; end
163
- def self.receive(obj)
164
- obj
165
- end
166
- Gorillib::Factory.register_factory(self, [self, :identical, :whatever])
167
- end
168
- IdenticalFactory = ::Whatever unless defined?(IdenticalFactory)
169
-
170
- # __________________________________________________________________________
171
- #
172
- # Concrete Factories
173
- # __________________________________________________________________________
174
-
175
- class StringFactory < ConvertingFactory
176
- self.product = String
177
- def blankish?(obj) obj.nil? ; end
178
- def native?(obj) obj.respond_to?(:to_str) end
179
- def convert(obj) obj.to_s end
180
- register_factory!
181
- end
182
-
183
- class GuidFactory < StringFactory ; self.product = ::Guid ; register_factory! ; end
184
- class HostnameFactory < StringFactory ; self.product = ::Hostname ; register_factory! ; end
185
- class IpAddressFactory < StringFactory ; self.product = ::IpAddress ; register_factory! ; end
186
-
187
- class BinaryFactory < StringFactory
188
- def convert(obj)
189
- super.force_encoding("BINARY")
190
- end
191
- register_factory!(:binary)
192
- end
193
-
194
- class PathnameFactory < ConvertingFactory
195
- self.product = ::Pathname
196
- def convert(obj) Pathname.new(obj) end
197
- register_factory!
198
- end
199
-
200
- class SymbolFactory < ConvertingFactory
201
- self.product = Symbol
202
- def convert(obj) obj.to_sym end
203
- register_factory!
204
- end
205
-
206
- class RegexpFactory < ConvertingFactory
207
- self.product = Regexp
208
- def convert(obj) Regexp.new(obj) end
209
- register_factory!
210
- end
211
-
212
- class IntegerFactory < ConvertingFactory
213
- self.product = Integer
214
- def convert(obj) obj.to_i end
215
- register_factory!(:int, :integer, Integer)
216
- end
217
- class BignumFactory < IntegerFactory
218
- self.product = Bignum
219
- register_factory!
220
- end
221
- class FloatFactory < ConvertingFactory
222
- self.product = Float
223
- def convert(obj) obj.to_f end
224
- register_factory!
225
- end
226
- class ComplexFactory < ConvertingFactory
227
- self.product = Complex
228
- def convert(obj) obj.to_c end
229
- register_factory!
230
- end
231
- class RationalFactory < ConvertingFactory
232
- self.product = Rational
233
- def convert(obj) obj.to_r end
234
- register_factory!
235
- end
236
-
237
- class TimeFactory < ConvertingFactory
238
- self.product = Time
239
- def convert(obj)
240
- case obj
241
- when String
242
- Time.parse(obj).utc
243
- when Numeric
244
- Time.at(obj).utc
245
- end
246
- rescue ArgumentError => err
247
- warn "Cannot parse time #{obj}: #{err}"
248
- return nil
249
- end
250
- register_factory!
251
- end
252
-
253
- # __________________________________________________________________________
254
-
255
- class ClassFactory < NonConvertingFactory ; self.product = Class ; register_factory! ; end
256
- class ModuleFactory < NonConvertingFactory ; self.product = Module ; register_factory! ; end
257
- class TrueFactory < NonConvertingFactory ; self.product = TrueClass ; register_factory!(:true, TrueClass) ; end
258
- class FalseFactory < NonConvertingFactory ; self.product = FalseClass ; register_factory!(:false, FalseClass) ; end
259
-
260
- class ExceptionFactory < NonConvertingFactory ; self.product = Exception ; register_factory!(:exception, Exception) ; end
261
-
262
- class NilFactory < NonConvertingFactory
263
- self.product = NilClass
264
- def blankish?(obj) false ; end
265
- register_factory!(:nil, NilClass)
266
- end
267
-
268
- class BooleanFactory < ConvertingFactory
269
- self.product = [TrueClass, FalseClass]
270
- def blankish?(obj) obj.nil? ; end
271
- def native?(obj) obj.equal?(true) || obj.equal?(false) ; end
272
- def convert(obj) (obj.to_s == "false") ? false : true ; end
273
- register_factory!(:boolean)
274
- def self.typename() :boolean ; end
275
- end
276
-
277
- #
278
- #
279
- #
280
-
281
- class EnumerableFactory < ConvertingFactory
282
- # [#receive] factory for converting items
283
- attr_reader :items_factory
284
-
285
- def initialize(options={})
286
- @items_factory = Gorillib::Factory.receive( options.delete(:items){ Gorillib::Factory(:identical) } )
287
- redefine(:empty_product, options.delete(:empty_product)) if options.has_key?(:empty_product)
288
- super(options)
289
- end
290
-
291
- def blankish?(obj) obj.nil? ; end
292
- def native?(obj) false ; end
293
-
294
- def empty_product
295
- @product.new
296
- end
297
-
298
- def convert(obj)
299
- clxn = empty_product
300
- obj.each do |val|
301
- clxn << items_factory.receive(val)
302
- end
303
- clxn
304
- end
305
- end
306
-
307
- class ArrayFactory < EnumerableFactory
308
- self.product = Array
309
- register_factory!
310
- end
311
-
312
- class SetFactory < EnumerableFactory
313
- self.product = Set
314
- register_factory!
315
- end
316
-
317
- class HashFactory < EnumerableFactory
318
- # [#receive] factory for converting keys
319
- attr_reader :keys_factory
320
- self.product = Hash
321
-
322
- def initialize(options={})
323
- @keys_factory = Gorillib::Factory( options.delete(:keys){ Gorillib::Factory(:identical) } )
324
- super(options)
325
- end
326
-
327
- def convert(obj)
328
- hsh = empty_product
329
- obj.each_pair do |key, val|
330
- hsh[keys_factory.receive(key)] = items_factory.receive(val)
331
- end
332
- hsh
333
- end
334
- register_factory!
335
- end
336
-
337
- class RangeFactory < NonConvertingFactory
338
- self.product = Range
339
- def blankish?(obj) obj.nil? || obj == [] ; end
340
- register_factory!
341
- end
342
-
343
- # __________________________________________________________________________
344
-
345
- class ApplyProcFactory < ConvertingFactory
346
- attr_reader :callable
347
-
348
- def initialize(callable=nil, options={}, &block)
349
- if block_given?
350
- raise ArgumentError, "Pass a block or a value, not both" unless callable.nil?
351
- callable = block
352
- end
353
- @callable = callable
354
- super(options)
355
- end
356
- def blankish?(obj) obj.nil? ; end
357
- def native?(val) false ; end
358
- def convert(obj)
359
- callable.call(obj)
360
- end
361
- register_factory!(:proc)
362
- end
363
-
364
-
365
- end
366
-
367
- end
1
+ require_relative '../factories'