gorillib 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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