gorillib 0.4.1pre → 0.4.2pre

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.
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'