gorillib 0.4.0pre → 0.4.1pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/CHANGELOG.md +36 -1
  2. data/Gemfile +23 -19
  3. data/Guardfile +1 -1
  4. data/Rakefile +31 -31
  5. data/TODO.md +2 -30
  6. data/VERSION +1 -1
  7. data/examples/builder/ironfan.rb +4 -4
  8. data/gorillib.gemspec +40 -25
  9. data/lib/gorillib/array/average.rb +13 -0
  10. data/lib/gorillib/array/sorted_median.rb +11 -0
  11. data/lib/gorillib/array/sorted_percentile.rb +11 -0
  12. data/lib/gorillib/array/sorted_sample.rb +12 -0
  13. data/lib/gorillib/builder.rb +8 -14
  14. data/lib/gorillib/collection/has_collection.rb +31 -31
  15. data/lib/gorillib/collection/list_collection.rb +58 -0
  16. data/lib/gorillib/collection/model_collection.rb +63 -0
  17. data/lib/gorillib/collection.rb +57 -85
  18. data/lib/gorillib/logger/log.rb +26 -22
  19. data/lib/gorillib/model/base.rb +52 -39
  20. data/lib/gorillib/model/doc_string.rb +15 -0
  21. data/lib/gorillib/model/factories.rb +56 -61
  22. data/lib/gorillib/model/lint.rb +24 -0
  23. data/lib/gorillib/model/serialization.rb +12 -2
  24. data/lib/gorillib/model/validate.rb +2 -2
  25. data/lib/gorillib/pathname.rb +21 -6
  26. data/lib/gorillib/some.rb +2 -0
  27. data/lib/gorillib/type/extended.rb +0 -2
  28. data/lib/gorillib/type/url.rb +9 -0
  29. data/lib/gorillib/utils/console.rb +4 -1
  30. data/notes/HOWTO.md +22 -0
  31. data/notes/bucket.md +155 -0
  32. data/notes/builder.md +170 -0
  33. data/notes/collection.md +81 -0
  34. data/notes/factories.md +86 -0
  35. data/notes/model-overlay.md +209 -0
  36. data/notes/model.md +135 -0
  37. data/notes/structured-data-classes.md +127 -0
  38. data/spec/array/average_spec.rb +24 -0
  39. data/spec/array/sorted_median_spec.rb +18 -0
  40. data/spec/array/sorted_percentile_spec.rb +24 -0
  41. data/spec/array/sorted_sample_spec.rb +28 -0
  42. data/spec/gorillib/builder_spec.rb +46 -28
  43. data/spec/gorillib/collection_spec.rb +195 -10
  44. data/spec/gorillib/model/lint_spec.rb +28 -0
  45. data/spec/gorillib/model/record/factories_spec.rb +27 -13
  46. data/spec/gorillib/model/serialization_spec.rb +3 -5
  47. data/spec/gorillib/model_spec.rb +86 -104
  48. data/spec/spec_helper.rb +2 -1
  49. data/spec/support/gorillib_test_helpers.rb +83 -7
  50. data/spec/support/model_test_helpers.rb +9 -28
  51. metadata +52 -44
  52. data/lib/gorillib/configurable.rb +0 -28
  53. data/spec/gorillib/configurable_spec.rb +0 -62
  54. data/spec/support/shared_examples/included_module.rb +0 -20
@@ -12,25 +12,35 @@ module Gorillib
12
12
 
13
13
  def self.receive(type)
14
14
  case
15
- when type.is_a?(Proc) || type.is_a?(Method) then return Gorillib::Factory::ApplyProcFactory.new(type)
16
- when type.respond_to?(:receive) then return type
17
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
18
  when type.is_a?(String) then
19
- return Gorillib::Inflector.constantize(Gorillib::Inflector.camelize(type.gsub(/\./, '/')))
19
+ return( factories[type] = Gorillib::Inflector.constantize(Gorillib::Inflector.camelize(type.gsub(/\./, '/'))) )
20
20
  else raise ArgumentError, "Don't know which factory makes a #{type}"
21
21
  end
22
22
  end
23
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
+
24
37
  private
25
38
  def self.factories
26
- @factories ||= Gorillib::Collection.new.tap{|f| f.key_method = :name }
39
+ @factories ||= Hash.new
27
40
  end
28
41
  public
29
42
 
30
- def self.register_factory(factory, *typenames)
31
- if typenames.blank?
32
- typenames = [factory.typename, factory.product]
33
- end
43
+ def self.register_factory(factory, typenames)
34
44
  typenames.each{|typename| factories[typename] = factory }
35
45
  end
36
46
 
@@ -38,42 +48,27 @@ module Gorillib
38
48
  # [Class] The type of objects produced by this factory
39
49
  class_attribute :product
40
50
 
41
- # [Array<Symbol>] methods that can be redefined by passing a block to an
42
- # instance No not add to your superclass' value in-place; instead, use
43
- # `self.redefinable_methods += [...]`.
44
- # @see #redefine
45
- class_attribute :redefinable_methods, :instance_writer => false
46
- self.redefinable_methods = Set.new([:blankish?, :convert])
47
-
48
- # [Array<Object>] objects considered to be equivalent to `nil`
49
- class_attribute :blankish_vals
50
- self.blankish_vals = Set.new([ nil, "" ]) # note: [] {} and false are NOT blankish by default
51
-
52
51
  def initialize(options={})
53
- @product = options.delete(:product) if options.has_key?(:product)
54
- @blankish_vals = options.delete(:blankish_vals) if options.has_key?(:blankish_vals)
55
- options.extract!(*redefinable_methods).each do |meth, value_or_block|
56
- redefine(meth, value_or_block)
52
+ @product = options.delete(:product){ self.class.product }
53
+ if options[:blankish]
54
+ define_singleton_method(:blankish, options.delete(:blankish))
57
55
  end
56
+ redefine(:convert, options.delete(:convert)) if options.has_key?(:convert)
58
57
  warn "Unknown options #{options.keys}" unless options.empty?
59
58
  end
60
59
 
61
60
  def self.typename
62
- Gorillib::Inflector.underscore(product.name).to_sym
61
+ @typename ||= Gorillib::Inflector.underscore(product.name).to_sym
63
62
  end
64
63
  def typename ; self.class.typename ; end
65
64
 
66
- def self.receive(*args, &block)
67
- self.new.receive(*args, &block)
68
- end
69
-
70
65
  # A `native` object does not need any transformation; it is accepted directly.
71
66
  # By default, an object is native if it `is_a?(product)`
72
67
  #
73
68
  # @param [Object] obj the object to convert and receive
74
69
  # @return [true, false] true if the item does not need conversion
75
70
  def native?(obj)
76
- obj.is_a?(product)
71
+ obj.is_a?(@product)
77
72
  end
78
73
  def self.native?(obj) self.new.native?(obj) ; end
79
74
 
@@ -82,12 +77,15 @@ module Gorillib
82
77
  # @param [Object] obj the object to convert and receive
83
78
  # @return [true, false] true if the item is equivalent to a nil value
84
79
  def blankish?(obj)
85
- blankish_vals.include?(obj)
80
+ obj.nil? || (obj == "")
86
81
  end
87
- def self.blankish?(obj) self.new.blankish?(obj) ; end
82
+ def self.blankish?(obj)
83
+ obj.nil? || (obj == "")
84
+ end
85
+
86
+ protected
88
87
 
89
88
  def redefine(meth, *args, &block)
90
- raise ArgumentError, "Cannot redefine #{meth} -- only #{redefinable_methods.inspect} are redefinable" unless redefinable_methods.include?(meth)
91
89
  if args.present?
92
90
  val = args.first
93
91
  case
@@ -100,8 +98,6 @@ module Gorillib
100
98
  self
101
99
  end
102
100
 
103
- protected
104
-
105
101
  # Raises a FactoryMismatchError.
106
102
  def mismatched!(obj, message=nil, *args)
107
103
  message ||= "item cannot be converted to #{product}"
@@ -109,8 +105,9 @@ module Gorillib
109
105
  raise FactoryMismatchError, message, *args
110
106
  end
111
107
 
112
- def self.register_factory!(*args)
113
- Gorillib::Factory.register_factory(self, *args)
108
+ def self.register_factory!(*typenames)
109
+ typenames = [typename, product] if typenames.empty?
110
+ Gorillib::Factory.register_factory(self.new, typenames)
114
111
  end
115
112
  end
116
113
 
@@ -139,14 +136,13 @@ module Gorillib
139
136
  # throws a mismatch error for anything else.
140
137
  #
141
138
  # @example
142
- # ff = Gorillib::Factory::NonConvertingFactory.new(:product => String, :blankish_vals => [nil])
139
+ # ff = Gorillib::Factory::NonConvertingFactory.new(:product => String, :blankish => ->(obj){ obj.nil? })
143
140
  # ff.receive(nil) #=> nil
144
141
  # ff.receive("bob") #=> "bob"
145
142
  # ff.receive(:bob) #=> Gorillib::Factory::FactoryMismatchError: must be an instance of String, got 3
146
143
  #
147
144
  class NonConvertingFactory < BaseFactory
148
- self.blankish_vals = [nil]
149
-
145
+ def blankish?(obj) obj.nil? ; end
150
146
  def receive(obj)
151
147
  return nil if blankish?(obj)
152
148
  return obj if native?(obj)
@@ -156,17 +152,20 @@ module Gorillib
156
152
  end
157
153
  end
158
154
 
159
- class IdenticalFactory < BaseFactory
160
- self.redefinable_methods = []
161
- self.blankish_vals = []
155
+ class ::Whatever < BaseFactory
156
+ def initialize(options={})
157
+ options.slice!(:convert, :blankish)
158
+ super(options)
159
+ end
162
160
  def native?(obj) true ; end
163
161
  def blankish?(obj) false ; end
164
- def receive(obj)
162
+ def receive(obj) obj ; end
163
+ def self.receive(obj)
165
164
  obj
166
165
  end
167
- register_factory!(:identical, :whatever)
166
+ Gorillib::Factory.register_factory(self, [self, :identical, :whatever])
168
167
  end
169
- ::Whatever = IdenticalFactory unless defined?(Whatever)
168
+ IdenticalFactory = ::Whatever unless defined?(IdenticalFactory)
170
169
 
171
170
  # __________________________________________________________________________
172
171
  #
@@ -175,7 +174,7 @@ module Gorillib
175
174
 
176
175
  class StringFactory < ConvertingFactory
177
176
  self.product = String
178
- self.blankish_vals -= [""]
177
+ def blankish?(obj) obj.nil? ; end
179
178
  def native?(obj) obj.respond_to?(:to_str) end
180
179
  def convert(obj) obj.to_s end
181
180
  register_factory!
@@ -262,13 +261,13 @@ module Gorillib
262
261
 
263
262
  class NilFactory < NonConvertingFactory
264
263
  self.product = NilClass
265
- self.blankish_vals = []
264
+ def blankish?(obj) false ; end
266
265
  register_factory!(:nil, NilClass)
267
266
  end
268
267
 
269
268
  class BooleanFactory < ConvertingFactory
270
269
  self.product = [TrueClass, FalseClass]
271
- self.blankish_vals = [nil]
270
+ def blankish?(obj) obj.nil? ; end
272
271
  def native?(obj) obj.equal?(true) || obj.equal?(false) ; end
273
272
  def convert(obj) (obj.to_s == "false") ? false : true ; end
274
273
  register_factory!(:boolean)
@@ -282,20 +281,18 @@ module Gorillib
282
281
  class EnumerableFactory < ConvertingFactory
283
282
  # [#receive] factory for converting items
284
283
  attr_reader :items_factory
285
- self.redefinable_methods += [:empty_product]
286
- self.blankish_vals = Set.new([ nil ])
287
284
 
288
285
  def initialize(options={})
289
- @items_factory = Gorillib::Factory.receive( options.delete(:items){ IdenticalFactory.new } )
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)
290
288
  super(options)
291
289
  end
292
290
 
293
- def native?(obj)
294
- false
295
- end
291
+ def blankish?(obj) obj.nil? ; end
292
+ def native?(obj) false ; end
296
293
 
297
294
  def empty_product
298
- product.new
295
+ @product.new
299
296
  end
300
297
 
301
298
  def convert(obj)
@@ -323,7 +320,7 @@ module Gorillib
323
320
  self.product = Hash
324
321
 
325
322
  def initialize(options={})
326
- @keys_factory = Gorillib::Factory( options.delete(:keys){ Whatever.new } )
323
+ @keys_factory = Gorillib::Factory( options.delete(:keys){ Gorillib::Factory(:identical) } )
327
324
  super(options)
328
325
  end
329
326
 
@@ -339,7 +336,7 @@ module Gorillib
339
336
 
340
337
  class RangeFactory < NonConvertingFactory
341
338
  self.product = Range
342
- self.blankish_vals = [ nil, [] ]
339
+ def blankish?(obj) obj.nil? || obj == [] ; end
343
340
  register_factory!
344
341
  end
345
342
 
@@ -347,7 +344,6 @@ module Gorillib
347
344
 
348
345
  class ApplyProcFactory < ConvertingFactory
349
346
  attr_reader :callable
350
- self.blankish_vals = Set.new([nil])
351
347
 
352
348
  def initialize(callable=nil, options={}, &block)
353
349
  if block_given?
@@ -357,9 +353,8 @@ module Gorillib
357
353
  @callable = callable
358
354
  super(options)
359
355
  end
360
- def native?(val)
361
- false
362
- end
356
+ def blankish?(obj) obj.nil? ; end
357
+ def native?(val) false ; end
363
358
  def convert(obj)
364
359
  callable.call(obj)
365
360
  end
@@ -0,0 +1,24 @@
1
+ module Gorillib
2
+ module Model
3
+ #
4
+ # A set of guards for good behavior:
5
+ #
6
+ # * checks that fields given to read_attribute, write_attribute, etc are defined
7
+ #
8
+ module Lint
9
+ def read_attribute(field_name, *) check_field(field_name) ; super ; end
10
+ def write_attribute(field_name, *) check_field(field_name) ; super ; end
11
+ def unset_attribute(field_name, *) check_field(field_name) ; super ; end
12
+ def attribute_set?(field_name, *) check_field(field_name) ; super ; end
13
+
14
+ protected
15
+ # @return [true] if the field exists
16
+ # @raise [UnknownFieldError] if the field is missing
17
+ def check_field(field_name)
18
+ return true if self.class.has_field?(field_name)
19
+ raise UnknownFieldError, "unknown field: #{field_name} for #{self}"
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -1,17 +1,27 @@
1
+
2
+ class Array
3
+ def to_tsv
4
+ join("\t")
5
+ end
6
+ end
7
+
1
8
  module Gorillib
2
9
  module Model
3
-
4
10
  def to_wire(options={})
5
11
  attributes.merge(:_type => self.class.typename).inject({}) do |acc, (key,attr)|
6
12
  acc[key] = attr.respond_to?(:to_wire) ? attr.to_wire(options) : attr
7
13
  acc
8
14
  end
9
15
  end
16
+ def as_json(*args) to_wire(*args) ; end
10
17
 
11
18
  def to_json(options={})
12
19
  MultiJson.dump(to_wire(options), options)
13
20
  end
14
- alias_method(:as_json, :to_wire)
21
+
22
+ def to_tsv
23
+ attribute_values.map(&:to_s).join("\t")
24
+ end
15
25
 
16
26
  module ClassMethods
17
27
  def from_tuple(*vals)
@@ -9,9 +9,9 @@ module Gorillib
9
9
  raise ArgumentError, "Name must start with [A-Za-z_] and subsequently contain only [A-Za-z0-9_]", caller unless name =~ VALID_NAME_RE
10
10
  end
11
11
 
12
- def hashlike!(desc, val)
12
+ def hashlike!(val)
13
13
  return true if val.respond_to?(:[]) && val.respond_to?(:has_key?)
14
- raise ArgumentError, "#{desc} should be something that behaves like a hash: #{val.inspect}", caller
14
+ raise ArgumentError, "#{block_given? ? yield : 'value'} should be something that behaves like a hash: #{val.inspect}", caller
15
15
  end
16
16
 
17
17
  def included_in!(desc, val, colxn)
@@ -9,15 +9,21 @@ module Gorillib
9
9
 
10
10
  def register_path(handle, *pathsegs)
11
11
  ArgumentError.arity_at_least!(pathsegs, 1)
12
- ROOT_PATHS[handle] = pathsegs
12
+ ROOT_PATHS[handle.to_sym] = pathsegs
13
13
  end
14
14
 
15
- def register_paths(pairs = {})
16
- pairs.each_pair{ |handle, pathsegs| register_path(handle, *pathsegs) }
15
+ def register_paths(handle_paths = {})
16
+ handle_paths.each_pair{|handle, pathsegs| register_path(handle, *pathsegs) }
17
17
  end
18
18
 
19
- def unregister_path handle
20
- ROOT_PATHS.delete handle
19
+ def register_default_paths(handle_paths = {})
20
+ handle_paths.each_pair do |handle, pathsegs|
21
+ register_path(handle, *pathsegs) unless ROOT_PATHS.has_key?(handle.to_sym)
22
+ end
23
+ end
24
+
25
+ def unregister_path(handle)
26
+ ROOT_PATHS.delete handle.to_sym
21
27
  end
22
28
 
23
29
  # Expand a path with late-evaluated segments.
@@ -59,8 +65,13 @@ module Gorillib
59
65
  def relative_path_to(*pathsegs)
60
66
  ArgumentError.arity_at_least!(pathsegs, 1)
61
67
  pathsegs = pathsegs.map{|ps| expand_pathseg(ps) }.flatten
62
- new(File.join(*pathsegs)).cleanpath(true)
68
+ self.new(File.join(*pathsegs)).cleanpath(true)
63
69
  end
70
+ def relpath_to(*args) relative_path_to(*args) ; end
71
+
72
+ # def make_pathname(*args)
73
+ # Pathname.new(*args)
74
+ # end
64
75
 
65
76
  protected
66
77
  # Recursively expand a path handle
@@ -75,4 +86,8 @@ end
75
86
 
76
87
  class Pathname
77
88
  extend Gorillib::Pathref
89
+ class << self ; alias_method :new_pathname, :new ; end
90
+
91
+ # FIXME: find out if this is dangerous
92
+ alias_method :to_str, :to_path
78
93
  end
data/lib/gorillib/some.rb CHANGED
@@ -5,6 +5,8 @@ require 'pathname'
5
5
 
6
6
  require 'gorillib/base'
7
7
 
8
+ require 'gorillib/logger/log'
9
+
8
10
  require 'gorillib/pathname'
9
11
  require 'gorillib/string/simple_inflector'
10
12
  require 'gorillib/string/inflections'
@@ -9,8 +9,6 @@ class ::Guid < ::String ; end
9
9
  class ::IpAddress < ::String ; end
10
10
  class ::Hostname < ::String ; end
11
11
 
12
- class ::Url < ::String ; end
13
-
14
12
  # require 'gorillib/metaprogramming/delegation'
15
13
  #
16
14
  # class ::Boolean < ::Object
@@ -0,0 +1,9 @@
1
+ require 'addressable/uri'
2
+
3
+ class ::Url < Addressable::URI ; end
4
+
5
+ class UrlFactory < Gorillib::Factory::ConvertingFactory
6
+ self.product = ::Url
7
+ def convert(obj) product.parse(obj) ; end
8
+ register_factory!
9
+ end
@@ -36,6 +36,9 @@ class ItsATrap < BasicObject
36
36
  alias_method :pretty_inspect, :inspect
37
37
  def methods() @obj.methods ; end
38
38
 
39
+ # @returns the proxied object
40
+ def __obj__ ; @obj ; end
41
+
39
42
  # These are defined on BasicObject, delegate them along with the rest
40
43
  # BasicObject.instance_methods
41
44
  # => [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]
@@ -58,7 +61,7 @@ private
58
61
  end
59
62
 
60
63
  def __describe_and_send__(meth, *args, &block)
61
- pref = "%-3d %-14s %-15s" % [@call_count, __id__, self.to_s[0..14]]
64
+ pref = "%-3d %-14s %-15s" % [@call_count, @obj.__id__, self.to_s[0..14]]
62
65
  @call_count += 1
63
66
  $stderr.puts "%s %-15s <- %-30s %s -- %s" % [pref, meth.to_s[0..14], args.map(&:inspect).join(','), block, ::Kernel.caller.first]
64
67
  ret = @obj.__send__(meth, *args, &block)
data/notes/HOWTO.md ADDED
@@ -0,0 +1,22 @@
1
+ # Walkthrough
2
+
3
+ **note (2012-06): you must use the dev branch, `version_1` of Gorillib** -- https://github.com/infochimps-labs/gorillib/tree/version_1/
4
+
5
+ 1. Here's a set of examples of Gorillib records in action.
6
+ * [examples of `Gorillib::Model`](https://github.com/infochimps-labs/gorillib/tree/version_1/examples/model)
7
+ * see example gorillib records in the /examples directories
8
+
9
+ __________________________________________________________________________
10
+
11
+ 2. Walkthrough dataflow for a new customer:
12
+ * models to represent records in a dataflow (gorillib/examples)
13
+ * describe the macro dataflow (wukong/examples)
14
+ * simulate the dataflow at commandline
15
+ * project the dataflow using Flume
16
+ * export the dataflow using graphviz
17
+
18
+ https://github.com/infochimps-labs/wukong/wiki
19
+ https://github.com/infochimps-labs/gorillib/wiki
20
+ Plus source files (or you can instead see the Yard Docs)
21
+
22
+ 3. flume event model shim
data/notes/bucket.md ADDED
@@ -0,0 +1,155 @@
1
+ # gorillib/bucket (WIP -- NOT READY YET) -- freeform storage with definable access
2
+
3
+ * A `Go::Bucket` fulfills the contract of `Go::Model`
4
+
5
+ ## Arbitrary read/writes with `[]/[]=`, defined access with `.foo/.foo=`
6
+
7
+ Overriding `method_missing` is uncouth. It screws up your stack traces, muddies your interface and leads to unassertive code:
8
+
9
+ ```ruby
10
+ Settings.defcon = 3
11
+ # ... elsewhere ...
12
+ if Settings.def_con.to_i <= 1
13
+ WOPR.launch_nukes!
14
+ end
15
+ ```
16
+
17
+ Here, mispelling `def_con` as `defcon` (along with being wussy about the attribute's type and negligent about prescribing a default value) leads to the destruction of humanity.
18
+
19
+ Nonetheless, for several reasons (prudent laziness, handling configuration of an external component, etc) it's important to handle arbitrary attributes uniformly.
20
+
21
+ So the rule is that you can get or set anything you like using `[]` and `[]=` respectively, no need to define it first:
22
+
23
+ Settings[:whatever] = 3
24
+ Settings[:whatever] #=> 3
25
+
26
+ You don't get any magic, and the ugly accessor leaves you in no doubt that you're being lazy (ain't judging, just sayin'). When you define a setting you get accessors for that attribute and all associated magic:
27
+
28
+ Settings.option :defcon, Integer, :doc => 'Current NORAD defense condition', :default => 5, :validates => { :in => 1..5 }
29
+ Settings.defcon #=> 5
30
+ Settings.defcon = 0
31
+ Settings.validate! # raises a validation exception
32
+
33
+ Defined fields' magic works whichever form of access you use -- here, type-converting the value on assignment:
34
+
35
+ Settings[:defcon] = '5' #=> 5
36
+ Settings.defcon = '5' #=> 5
37
+ Settings.receive_defcon('5') #=> 5
38
+
39
+ ## Keys
40
+
41
+ Keys be lower-cased identifiers: they should match `/\A([a-z][a-z0-9\_]*)\z/`.
42
+
43
+ ## Deep Hash
44
+
45
+ Hash.new {|h,k| h[k] = Hash.new(&h.default_proc)}
46
+
47
+ ### Dot addressing
48
+
49
+ Can retrieve keys as 'x.y.z' meaning 'foo[x][y][z]'.
50
+
51
+ * On a `get`, will
52
+ - return the value, if `foo[:x][:y][:z]` exists
53
+ - return nil, if either `foo[:x]` or `foo[:x][:y]` is *unset*. It will not create `foo[:x]` or `foo[:x][:y]`.
54
+ - raise an error if either `foo[:x]` or `foo[:x][:y]` is set to a value that does not respond to `[]`
55
+
56
+ * On a `set`, will
57
+ - raise an error if either `foo[:x]` or `foo[:x][:y]` is set to a value that doesn't respond to `[]`
58
+ - set and return the value; any intermediate buckets (`foo[:x]` and `foo[:x][:y]`) will be created if they don't exist.
59
+
60
+
61
+ __________________________________________________________________________
62
+
63
+
64
+ ## Examples
65
+
66
+ ### how hash-like is this?
67
+
68
+ * obj.deep_get(:foo, :bar, :baz) #
69
+ * obj.deep_set(:foo, :bar, :baz, val) # sets foo bar baz.
70
+ - where property has a type, it uses that type
71
+ - otherwise it uses Mash, and calls [] on it
72
+
73
+ * TD votes NO on magic recursive hash behavior
74
+
75
+
76
+ {
77
+ :buck1 => {
78
+ :buck2 => { :k3 => 33, :ehsh => {}, :arr => [11, 12, 13] },
79
+ :cars => [{ :model => 'ford', :cylinders => 8 }, { :model => 'buick', :cylinders => 6 }],
80
+ }
81
+ :buck3 => {
82
+ :buck4 => { :k4 => 44 }
83
+ :k5 => nil,
84
+ }
85
+ :k6 => 69
86
+ }
87
+
88
+ * `obj[:buck3]` # b{ :buck4=>b{ :k4=>44 },:k5=>nil } -- it's a bucket
89
+ * `obj[:buck5] = Hash.new`
90
+ * `obj[:buck5].class` # Bucket -- it's converted to bucket
91
+ * `obj[:xxx]` # nil -- it's not there
92
+ * `obj[:xxx][:yyy][:zzz]` # fails -- it doesn't try to index into the nil `obj[:xxx]` cell.
93
+ * `obj[:xxx]` # nil -- it didn't create the `obj[:xxx]` object when we tried to read on the previous line.
94
+
95
+ c.options_for 'wheels', 'interior.fabric', 'interior.carpeting'
96
+
97
+ c[:wheels] # c{ }
98
+ c[:wheels][:whitewall] # nil
99
+ c[:wheels][:whitewall] = true # true
100
+
101
+ c.interior.fabric.color
102
+
103
+
104
+ * `obj[:k6][:bomb]` # raises ArgumentError; `obj[:k6]` is not a bucket.
105
+
106
+ * `obj[:'hello-there'] # undefined; `'hello-there'` is not a valid identifier.
107
+
108
+ * `obj[:buck3][:buck4][:k5] = 55`
109
+ `obj[:buck3]` # b{ :buck4=>b{ :k4=>44 },:k5 => 55}
110
+ * `obj[:f][:g][:h] = 7`
111
+ `obj[:f]` # b{ :g => b{ :h => 7 } }
112
+
113
+ * `obj[:foo][:bar][:baz] ||= 1` -- **hard** ?I think?
114
+
115
+ * `obj[:foo]` -- nil
116
+ * `obj[:foo][:bar]` -- raise
117
+ * `obj[:foo][:bar] = 3` --
118
+ - now obj[:foo][:bar] is 3
119
+ - and obj[:foo] is a ?dsl_object?? but
120
+
121
+ `obj[:foo][:bar]`
122
+
123
+ Suppose `obj[:foo]` is set to a
124
+
125
+ Seems clear these should do the right thing:
126
+
127
+ * `obj.merge`
128
+ * `obj.reverse_merge`
129
+ * `obj.keys`
130
+ * `obj.values`
131
+ * ... and a few more
132
+
133
+ Also:
134
+
135
+ * `obj.to_a`?
136
+ * `obj.each`?
137
+ * other crazy Enumerable properties?
138
+
139
+ ### TODO
140
+
141
+ * figure out the method structure for
142
+ - read/write/unset of attributes when Hash vs Accessors vs Instance Variables
143
+ - reader/writer: raw vs. hooks, dirty, etc.
144
+
145
+ ## Configliere Settings
146
+
147
+
148
+ Configliere lets you define arbitrary attributes of a param, notably:
149
+
150
+ [:description] Documentation for the param, used in the --help message
151
+ [:default] Sets a default value (applied immediately)
152
+ [:env_var] Environment variable to adopt (applied immediately, and after +:default+)
153
+ [:type] Converts param's value to the given type, just before the finally block is called
154
+ [:finally] Block of code to postprocess settings or handle complex configuration.
155
+ [:required] Raises an error if, at the end of calling resolve!, the param's value is nil.