gorillib 0.0.8 → 0.1.0

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 (64) hide show
  1. data/CHANGELOG.textile +6 -0
  2. data/README.textile +34 -11
  3. data/VERSION +1 -1
  4. data/gorillib.gemspec +63 -5
  5. data/lib/gorillib/enumerable/sum.rb +2 -2
  6. data/lib/gorillib/hash/compact.rb +2 -29
  7. data/lib/gorillib/hash/deep_compact.rb +2 -12
  8. data/lib/gorillib/hash/deep_dup.rb +4 -0
  9. data/lib/gorillib/hash/deep_merge.rb +2 -14
  10. data/lib/gorillib/hash/indifferent_access.rb +207 -0
  11. data/lib/gorillib/hash/keys.rb +2 -40
  12. data/lib/gorillib/hash/reverse_merge.rb +2 -24
  13. data/lib/gorillib/hash/slice.rb +2 -51
  14. data/lib/gorillib/hash/tree_merge.rb +4 -0
  15. data/lib/gorillib/hashlike.rb +824 -0
  16. data/lib/gorillib/hashlike/compact.rb +60 -0
  17. data/lib/gorillib/hashlike/deep_compact.rb +18 -0
  18. data/lib/gorillib/hashlike/deep_dup.rb +15 -0
  19. data/lib/gorillib/hashlike/deep_merge.rb +20 -0
  20. data/lib/gorillib/hashlike/hashlike_via_accessors.rb +169 -0
  21. data/lib/gorillib/hashlike/keys.rb +59 -0
  22. data/lib/gorillib/hashlike/reverse_merge.rb +31 -0
  23. data/lib/gorillib/hashlike/slice.rb +67 -0
  24. data/lib/gorillib/hashlike/tree_merge.rb +76 -0
  25. data/lib/gorillib/metaprogramming/mattr_accessor.rb +1 -1
  26. data/lib/gorillib/receiver.rb +315 -0
  27. data/lib/gorillib/receiver/active_model_shim.rb +19 -0
  28. data/lib/gorillib/receiver/acts_as_hash.rb +191 -0
  29. data/lib/gorillib/receiver/acts_as_loadable.rb +42 -0
  30. data/lib/gorillib/receiver/tree_diff.rb +74 -0
  31. data/lib/gorillib/receiver/validations.rb +30 -0
  32. data/lib/gorillib/struct/acts_as_hash.rb +108 -0
  33. data/lib/gorillib/struct/hashlike_iteration.rb +0 -0
  34. data/notes/fancy_hashes_and_receivers.textile +120 -0
  35. data/notes/hash_rdocs.textile +97 -0
  36. data/spec/hash/deep_merge_spec.rb +0 -2
  37. data/spec/hash/indifferent_access_spec.rb +391 -0
  38. data/spec/hash/slice_spec.rb +35 -12
  39. data/spec/hashlike/behave_same_as_hash_spec.rb +105 -0
  40. data/spec/hashlike/hashlike_behavior_spec.rb +824 -0
  41. data/spec/hashlike/hashlike_via_accessors_fuzzing_spec.rb +37 -0
  42. data/spec/hashlike/hashlike_via_accessors_spec.rb +262 -0
  43. data/spec/hashlike_spec.rb +302 -0
  44. data/spec/metaprogramming/aliasing_spec.rb +3 -0
  45. data/spec/metaprogramming/cattr_accessor_spec.rb +2 -0
  46. data/spec/metaprogramming/class_attribute_spec.rb +2 -0
  47. data/spec/metaprogramming/delegation_spec.rb +2 -0
  48. data/spec/metaprogramming/mattr_accessor_spec.rb +2 -0
  49. data/spec/metaprogramming/singleton_class_spec.rb +3 -0
  50. data/spec/receiver/acts_as_hash_spec.rb +286 -0
  51. data/spec/receiver_spec.rb +478 -0
  52. data/spec/spec_helper.rb +11 -6
  53. data/spec/string/truncate_spec.rb +1 -0
  54. data/spec/struct/acts_as_hash_fuzz_spec.rb +67 -0
  55. data/spec/struct/acts_as_hash_spec.rb +426 -0
  56. data/spec/support/hashlike_fuzzing_helper.rb +127 -0
  57. data/spec/support/hashlike_helper.rb +75 -0
  58. data/spec/support/hashlike_struct_helper.rb +37 -0
  59. data/spec/support/hashlike_via_delegation.rb +30 -0
  60. data/spec/support/matchers/be_array_eql.rb +12 -0
  61. data/spec/support/matchers/be_hash_eql.rb +14 -0
  62. data/spec/support/matchers/enumerate_method.rb +10 -0
  63. data/spec/support/matchers/evaluate_to_true.rb +5 -0
  64. metadata +62 -4
@@ -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,108 @@
1
+ module Gorillib
2
+ module Struct
3
+ #
4
+ # Make a Struct 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
+ # in addition to the below, by including Enumerable, this also adds
16
+ #
17
+ # :each_cons, :each_entry, :each_slice, :each_with_index, :each_with_object,
18
+ # :map, :collect, :collect_concat, :entries, :to_a, :flat_map, :inject, :reduce,
19
+ # :group_by, :chunk, :cycle, :partition, :reverse_each, :slice_before, :drop,
20
+ # :drop_while, :take, :take_while, :detect, :find, :find_all, :find_index, :grep,
21
+ # :all?, :any?, :none?, :one?, :first, :count, :zip :max, :max_by, :min, :min_by,
22
+ # :minmax, :minmax_by, :sort, :sort_by
23
+ #
24
+ # As opposed to hash, does *not* define
25
+ #
26
+ # default, default=, default_proc, default_proc=, shift, flatten, compare_by_identity
27
+ # compare_by_identity? rehash
28
+ #
29
+ # @example
30
+ # StructUsingHashlike = Struct.new(:a, :b, :c, :z) do
31
+ # include Gorillib::Struct::ActsAsHash
32
+ # include Gorillib::Hashlike
33
+ # end
34
+ # foo = StructUsingHashlike.new(1,2,3)
35
+ # foo.to_hash # => { :a => 1, :b => 2, :c => 3, :z => nil }
36
+ #
37
+ module ActsAsHash
38
+
39
+ # Hashlike#delete
40
+ #
41
+ # Deletes and returns the value from +hsh+ whose key is equal to +key+. If the
42
+ # optional code block is given and the key is not found, pass in the key and
43
+ # return the result of +block+.
44
+ #
45
+ # In a normal hash, a default value can be set; none is provided here.
46
+ #
47
+ # @example
48
+ # hsh = { :a => 100, :b => 200 }
49
+ # hsh.delete(:a) # => 100
50
+ # hsh.delete(:z) # => nil
51
+ # hsh.delete(:z){|el| "#{el} not found" } # => "z not found"
52
+ #
53
+ # @overload hsh.delete(key) -> val
54
+ # @param key [Object] key to remove
55
+ # @return [Object, Nil] the removed object, nil if missing
56
+ #
57
+ # @overload hsh.delete(key){|key| block } -> val
58
+ # @param key [Object] key to remove
59
+ # @yield [Object] called (with key) if key is missing
60
+ # @yieldparam key
61
+ # @return [Object, Nil] the removed object, or if missing, the return value
62
+ # of the block
63
+ #
64
+ def delete(key, &block)
65
+ if has_key?(key)
66
+ val = self[key]
67
+ self[key] = nil
68
+ self.send(:remove_instance_variable, "@#{key}") if instance_variables.include?("@#{key}")
69
+ val
70
+ elsif block_given?
71
+ block.call(key)
72
+ else
73
+ nil
74
+ end
75
+ end
76
+
77
+ # Hashlike#keys
78
+ #
79
+ # Returns a new array populated with the keys from this hashlike.
80
+ #
81
+ # @see Hashlike#values.
82
+ #
83
+ # @example
84
+ # hsh = { :a => 100, :b => 200, :c => 300, :d => 400 }
85
+ # hsh.keys # => [:a, :b, :c, :d]
86
+ #
87
+ # @return [Array] list of keys
88
+ #
89
+ def keys
90
+ @keys ||= members.map{|key| convert_key(key) }
91
+ end
92
+
93
+ def empty?
94
+ keys.select{|key| not self[key].nil? }.empty?
95
+ end
96
+
97
+ def convert_key(key)
98
+ return key if key.is_a?(Fixnum)
99
+ key.respond_to?(:to_sym) ? key.to_sym : key
100
+ end
101
+
102
+ def size
103
+ length
104
+ end
105
+
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,120 @@
1
+
2
+ * gorillib/hashlike
3
+ * gorillib/hashlike/slice
4
+ * gorillib/hashlike/deep_dup
5
+ * gorillib/hashlike/
6
+ * gorillib/hashlike/hashlike_via_accessors
7
+ * gorillib/struct/acts_as_hash
8
+ * gorillib/struct/iterates_as_hash
9
+
10
+
11
+
12
+ h4. acts_as_hash
13
+
14
+ * []= associates key with value
15
+ * [] retrieves value associated with given key
16
+ * delete removes that key and its association
17
+ * keys enumeration of all keys having associations.
18
+
19
+
20
+ * check for compatibility/semantics of Enumerable
21
+ * has_key? true if an association exists for the given key.
22
+
23
+ h4. hashlike
24
+
25
+ Want chokepoints:
26
+
27
+ keys
28
+ - each, each_pair
29
+ - each_value
30
+ - reject!
31
+ - delete_if
32
+ - reject
33
+ - select!
34
+ - keep_if
35
+ - each_key
36
+ - key
37
+ - has_key?
38
+ - values_at
39
+ - length, size
40
+ - empty?
41
+
42
+
43
+ * each keys.each{|k| yield [k, self[k]] } ; self
44
+
45
+ * has_key? keys.include?(k)
46
+ * length keys.length
47
+ * size alias for length
48
+ * empty? keys.empty?
49
+
50
+ * each_pair alias for each
51
+ * each_key keys.each{|k| yield k }
52
+ * each_value each{|k,v| yield v }
53
+ * key keys.find{|k| self[k] == val }
54
+ * values_at in_keys.each{|k| self[k] if has_key?(k) }
55
+ * values [].tap{|arr| each{|k,v| arr << v } }
56
+ * to_hash {}.tap{|hsh| each{|k,v| hsh[k] = v } }
57
+
58
+ * has_value? keys.any?{|k| self[k] == val }
59
+ * value? alias for has_value?
60
+ * include? alias for has_key?
61
+ * key? alias for has_key?
62
+ * member? alias for has_key?
63
+
64
+ * update other_hash.each{|k,v| self[k] = (block_given? && has_key?(k) ? yield(k,v,self[k]) : v) }
65
+ * merge! update
66
+ * merge dup.merge!
67
+
68
+ * reject! chgd = false ; each{|k,v| if yield(k,v) then delete(k); chgd = true ; end } chgd ? self : nil
69
+ * select! chgd = false ; each{|k,v| if !yield(k,v) then delete(k); chgd = true ; end } chgd ? self : nil
70
+ * delete_if: reject! ; return self
71
+ * keep_if: select! ; return self
72
+ * reject: self.dup.delete_if
73
+
74
+ * fetch [] if has_key? else default else yield else raise KeyError
75
+ * clear each_key{|k| delete(k) }
76
+
77
+ * invert to_hash.invert
78
+
79
+
80
+
81
+ #all?, #any?, #chunk, #collect, #collect_concat, #count, #cycle, #detect,
82
+ #drop, #drop_while, #each_cons, #each_entry, #each_slice,
83
+ #each_with_index, #each_with_object, #entries, #find, #find_all,
84
+ #find_index, #first, #flat_map, #grep, #group_by, #inject, #map, #max,
85
+ #max_by, #min, #min_by, #minmax, #minmax_by, #none?, #one?, #partition,
86
+ #reduce, #reverse_each, #slice_before, #sort, #sort_by, #take,
87
+ #take_while, #zip
88
+
89
+ As opposed to hash, does *not* define
90
+
91
+ default, default=, default_proc, default_proc=,
92
+ replace, rehash, compare_by_identity compare_by_identity?,
93
+ shift, flatten,
94
+
95
+ h4. Configliere::DeepHash -- a magic hash
96
+
97
+ - only allows handles as keys (only things that can be turned into symbols are interesting)
98
+ - indifferent access
99
+ - deep access, via the dotted hash
100
+
101
+ * FancyHash
102
+
103
+ * *gorillib/hash/compact* compact, compact!, compact_blank, compact_blank!
104
+ * *gorrilib/hash/deep_compact* deep_compact, deep_compact!
105
+ * *gorrilib/hash/deep_merge* deep_merge, deep_merge!
106
+ * *gorillib/hash/keys* stringify_keys, stringify_keys!, symbolize_keys, symbolize_keys!, assert_valid_keys
107
+ * *gorillib/hash/reverse_merge* reverse_merge, reverse_merge!
108
+ * *gorillib/hash/tree_merge* tree_merge, tree_merge!
109
+ * *gorillib/hash/slice* slice, slice!, extract!
110
+ * *gorillib/hash/zip* Hash.zip
111
+
112
+ * Receiver
113
+
114
+
115
+ ===========================================================================
116
+ ===========================================================================
117
+
118
+
119
+ ?> [ :store, :delete, :keys, :each, :each_key, :each_value, :has_key?, :include?, :key?, :member?, :has_value?, :value?, :fetch, :length, :empty?, :to_hash, :values, :values_at, :merge, :update, :key, :invert, :reject!, :select!, :delete_if, :keep_if, :reject, :clear, :assoc, :rassoc].each{|m| puts `ri 'Hash##{m}'` }
120
+
@@ -0,0 +1,97 @@
1
+
2
+ # == Hashlike ==
3
+ #
4
+ # == ActsAsHash ==
5
+ #
6
+ # Uses accessors and instance variables to provide
7
+ # * :[key] calls the getter <tt>send(key)</tt>
8
+ # * :[key]= val calls the setter <tt>send("key=", val)</tt>
9
+ # * :delete removes the instance variable, calls unset!
10
+ # * :keys all instance variables
11
+ #
12
+ # == Hashlike::Slice ==
13
+ # == Hashlike::Keys ==
14
+ # == Hashlike::Compact ==
15
+ # == Hashlike::DeepCompact ==
16
+ # == Hashlike::DeepMerge ==
17
+ # == Hashlike::TreeMerge ==
18
+ # == Hashlike::ReverseMerge ==
19
+ # == Hashlike::Zip ==
20
+ # == Hashlike::DeepKeys ==
21
+
22
+
23
+ # Hashlike#each
24
+
25
+
26
+ # Hashlike#each_key
27
+
28
+
29
+ # Hashlike#each_value
30
+
31
+
32
+ # Hashlike#has_key?
33
+
34
+
35
+ # Hashlike#include?
36
+ # Hashlike#key?
37
+ # Hashlike#member?
38
+
39
+ # Hashlike#has_value?
40
+ # Hashlike#value?
41
+
42
+
43
+ # Hashlike#fetch
44
+
45
+
46
+ # Hashlike#length
47
+
48
+
49
+ # Hashlike#empty?
50
+
51
+
52
+ # Hashlike#to_hash
53
+
54
+
55
+ # Hashlike#values
56
+
57
+
58
+ # Hashlike#values_at
59
+
60
+
61
+ # Hashlike#key
62
+
63
+
64
+ # Hashlike#invert
65
+
66
+
67
+ # Hashlike#delete_if
68
+
69
+
70
+ # Hashlike#reject!
71
+
72
+
73
+ # Hashlike#reject
74
+
75
+
76
+ # Hashlike#keep_if
77
+
78
+
79
+ # Hashlike#select!
80
+
81
+
82
+ # Hashlike#clear
83
+
84
+
85
+ # Hashlike#assoc
86
+
87
+
88
+ # Hashlike#rassoc
89
+
90
+
91
+ # Hashlike#flatten
92
+
93
+
94
+ # Hashlike#update
95
+
96
+
97
+ # Hashlike#merge