jinx 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. data/.gitignore +14 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +27 -0
  6. data/History.md +6 -0
  7. data/LEGAL +5 -0
  8. data/LICENSE +22 -0
  9. data/README.md +44 -0
  10. data/Rakefile +41 -0
  11. data/examples/family/README.md +10 -0
  12. data/examples/family/ext/build.xml +35 -0
  13. data/examples/family/ext/src/family/Address.java +68 -0
  14. data/examples/family/ext/src/family/Child.java +24 -0
  15. data/examples/family/ext/src/family/DomainObject.java +26 -0
  16. data/examples/family/ext/src/family/Household.java +36 -0
  17. data/examples/family/ext/src/family/Parent.java +48 -0
  18. data/examples/family/ext/src/family/Person.java +42 -0
  19. data/examples/family/lib/family.rb +15 -0
  20. data/examples/family/lib/family/address.rb +6 -0
  21. data/examples/family/lib/family/domain_object.rb +6 -0
  22. data/examples/family/lib/family/household.rb +6 -0
  23. data/examples/family/lib/family/parent.rb +16 -0
  24. data/examples/family/lib/family/person.rb +6 -0
  25. data/examples/model/README.md +25 -0
  26. data/examples/model/ext/build.xml +35 -0
  27. data/examples/model/ext/src/domain/Child.java +192 -0
  28. data/examples/model/ext/src/domain/Dependent.java +29 -0
  29. data/examples/model/ext/src/domain/DomainObject.java +26 -0
  30. data/examples/model/ext/src/domain/Independent.java +83 -0
  31. data/examples/model/ext/src/domain/Parent.java +129 -0
  32. data/examples/model/ext/src/domain/Person.java +14 -0
  33. data/examples/model/lib/model.rb +13 -0
  34. data/examples/model/lib/model/child.rb +13 -0
  35. data/examples/model/lib/model/domain_object.rb +6 -0
  36. data/examples/model/lib/model/independent.rb +11 -0
  37. data/examples/model/lib/model/parent.rb +17 -0
  38. data/jinx.gemspec +22 -0
  39. data/lib/jinx.rb +3 -0
  40. data/lib/jinx/active_support/README.txt +2 -0
  41. data/lib/jinx/active_support/core_ext/string.rb +7 -0
  42. data/lib/jinx/active_support/core_ext/string/inflections.rb +167 -0
  43. data/lib/jinx/active_support/inflections.rb +55 -0
  44. data/lib/jinx/active_support/inflector.rb +398 -0
  45. data/lib/jinx/cli/application.rb +36 -0
  46. data/lib/jinx/cli/command.rb +214 -0
  47. data/lib/jinx/helpers/array.rb +108 -0
  48. data/lib/jinx/helpers/boolean.rb +42 -0
  49. data/lib/jinx/helpers/case_insensitive_hash.rb +39 -0
  50. data/lib/jinx/helpers/class.rb +149 -0
  51. data/lib/jinx/helpers/collection.rb +33 -0
  52. data/lib/jinx/helpers/collections.rb +11 -0
  53. data/lib/jinx/helpers/collector.rb +20 -0
  54. data/lib/jinx/helpers/conditional_enumerator.rb +21 -0
  55. data/lib/jinx/helpers/enumerable.rb +242 -0
  56. data/lib/jinx/helpers/enumerate.rb +35 -0
  57. data/lib/jinx/helpers/error.rb +15 -0
  58. data/lib/jinx/helpers/file_separator.rb +65 -0
  59. data/lib/jinx/helpers/filter.rb +52 -0
  60. data/lib/jinx/helpers/flattener.rb +38 -0
  61. data/lib/jinx/helpers/hash.rb +12 -0
  62. data/lib/jinx/helpers/hashable.rb +502 -0
  63. data/lib/jinx/helpers/inflector.rb +36 -0
  64. data/lib/jinx/helpers/key_transformer_hash.rb +43 -0
  65. data/lib/jinx/helpers/lazy_hash.rb +44 -0
  66. data/lib/jinx/helpers/log.rb +106 -0
  67. data/lib/jinx/helpers/math.rb +12 -0
  68. data/lib/jinx/helpers/merge.rb +60 -0
  69. data/lib/jinx/helpers/module.rb +18 -0
  70. data/lib/jinx/helpers/multi_enumerator.rb +31 -0
  71. data/lib/jinx/helpers/options.rb +92 -0
  72. data/lib/jinx/helpers/os.rb +19 -0
  73. data/lib/jinx/helpers/partial_order.rb +37 -0
  74. data/lib/jinx/helpers/pretty_print.rb +207 -0
  75. data/lib/jinx/helpers/set.rb +8 -0
  76. data/lib/jinx/helpers/stopwatch.rb +76 -0
  77. data/lib/jinx/helpers/transformer.rb +24 -0
  78. data/lib/jinx/helpers/transitive_closure.rb +55 -0
  79. data/lib/jinx/helpers/uniquifier.rb +50 -0
  80. data/lib/jinx/helpers/validation.rb +33 -0
  81. data/lib/jinx/helpers/visitor.rb +370 -0
  82. data/lib/jinx/import/class_path_modifier.rb +77 -0
  83. data/lib/jinx/import/java.rb +337 -0
  84. data/lib/jinx/importer.rb +240 -0
  85. data/lib/jinx/metadata.rb +155 -0
  86. data/lib/jinx/metadata/attribute_enumerator.rb +73 -0
  87. data/lib/jinx/metadata/dependency.rb +244 -0
  88. data/lib/jinx/metadata/id_alias.rb +23 -0
  89. data/lib/jinx/metadata/introspector.rb +179 -0
  90. data/lib/jinx/metadata/inverse.rb +170 -0
  91. data/lib/jinx/metadata/java_property.rb +169 -0
  92. data/lib/jinx/metadata/propertied.rb +500 -0
  93. data/lib/jinx/metadata/property.rb +401 -0
  94. data/lib/jinx/metadata/property_characteristics.rb +114 -0
  95. data/lib/jinx/resource.rb +862 -0
  96. data/lib/jinx/resource/copy_visitor.rb +36 -0
  97. data/lib/jinx/resource/inversible.rb +90 -0
  98. data/lib/jinx/resource/match_visitor.rb +180 -0
  99. data/lib/jinx/resource/matcher.rb +20 -0
  100. data/lib/jinx/resource/merge_visitor.rb +73 -0
  101. data/lib/jinx/resource/mergeable.rb +185 -0
  102. data/lib/jinx/resource/reference_enumerator.rb +49 -0
  103. data/lib/jinx/resource/reference_path_visitor.rb +38 -0
  104. data/lib/jinx/resource/reference_visitor.rb +55 -0
  105. data/lib/jinx/resource/unique.rb +35 -0
  106. data/lib/jinx/version.rb +3 -0
  107. data/spec/defaults_spec.rb +30 -0
  108. data/spec/definitions/model/alias/child.rb +5 -0
  109. data/spec/definitions/model/base/child.rb +5 -0
  110. data/spec/definitions/model/base/domain_object.rb +5 -0
  111. data/spec/definitions/model/base/independent.rb +5 -0
  112. data/spec/definitions/model/defaults/child.rb +5 -0
  113. data/spec/definitions/model/dependency/child.rb +5 -0
  114. data/spec/definitions/model/dependency/parent.rb +6 -0
  115. data/spec/definitions/model/inverse/child.rb +5 -0
  116. data/spec/definitions/model/inverse/independent.rb +5 -0
  117. data/spec/definitions/model/inverse/parent.rb +5 -0
  118. data/spec/definitions/model/mandatory/child.rb +6 -0
  119. data/spec/dependency_spec.rb +47 -0
  120. data/spec/family_spec.rb +64 -0
  121. data/spec/inverse_spec.rb +53 -0
  122. data/spec/mandatory_spec.rb +43 -0
  123. data/spec/metadata_spec.rb +68 -0
  124. data/spec/resource_spec.rb +30 -0
  125. data/spec/spec_helper.rb +3 -0
  126. data/spec/support/model.rb +19 -0
  127. data/test/fixtures/line_separator/cr_line_sep.txt +1 -0
  128. data/test/fixtures/line_separator/crlf_line_sep.txt +3 -0
  129. data/test/fixtures/line_separator/lf_line_sep.txt +3 -0
  130. data/test/fixtures/mixed/ext/build.xml +35 -0
  131. data/test/fixtures/mixed/ext/src/mixed/Case/Example.java +5 -0
  132. data/test/helper.rb +7 -0
  133. data/test/lib/jinx/command_test.rb +41 -0
  134. data/test/lib/jinx/helpers/boolean_test.rb +27 -0
  135. data/test/lib/jinx/helpers/class_test.rb +60 -0
  136. data/test/lib/jinx/helpers/collections_test.rb +402 -0
  137. data/test/lib/jinx/helpers/file_separator_test.rb +29 -0
  138. data/test/lib/jinx/helpers/inflector_test.rb +11 -0
  139. data/test/lib/jinx/helpers/lazy_hash_test.rb +32 -0
  140. data/test/lib/jinx/helpers/module_test.rb +24 -0
  141. data/test/lib/jinx/helpers/options_test.rb +66 -0
  142. data/test/lib/jinx/helpers/partial_order_test.rb +41 -0
  143. data/test/lib/jinx/helpers/pretty_print_test.rb +83 -0
  144. data/test/lib/jinx/helpers/stopwatch_test.rb +16 -0
  145. data/test/lib/jinx/helpers/transitive_closure_test.rb +80 -0
  146. data/test/lib/jinx/helpers/visitor_test.rb +288 -0
  147. data/test/lib/jinx/import/java_test.rb +78 -0
  148. data/test/lib/jinx/import/mixed_case_test.rb +16 -0
  149. metadata +272 -0
@@ -0,0 +1,35 @@
1
+ require 'enumerator'
2
+
3
+ class Object
4
+ # This base implementation of +enumerate+ calls the given block on self unless
5
+ # this object in nil. If this object is nil, this method is a no-op.
6
+ #
7
+ # @yield [item] the block to apply to this object
8
+ # @yieldparam item self
9
+ def enumerate
10
+ yield(self) unless nil?
11
+ end
12
+
13
+ # Returns an enumerator on this Object. This default implementation returns an Enumerable::Enumerator
14
+ # on enumerate.
15
+ #
16
+ # @return [Enumerable] this object as an enumerable item
17
+ def to_enum
18
+ Enumerable::Enumerator.new(self, :enumerate)
19
+ end
20
+ end
21
+
22
+ module Enumerable
23
+ # Synonym for each.
24
+ #
25
+ # @yield [item] the block to apply to each member
26
+ # @yieldparam item a member of this Enumerable
27
+ def enumerate(&block)
28
+ each(&block)
29
+ end
30
+
31
+ # @return self
32
+ def to_enum
33
+ self
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ module Jinx
2
+ # Prints a log message and raises an exception.
3
+ #
4
+ # @param [Class] klass the error class to raise
5
+ # @param [String] msg the error message
6
+ # @param [Exception, nil] cause the exception which caused the error
7
+ def self.fail(klass, msg, cause=nil)
8
+ logger.error(msg)
9
+ if cause then
10
+ logger.error("Caused by: #{cause.class} - #{cause}\n#{cause.backtrace.pp_s}")
11
+ end
12
+ emsg = cause ? "#{msg} - #{$!}" : msg
13
+ raise klass.new(emsg)
14
+ end
15
+ end
@@ -0,0 +1,65 @@
1
+ require 'jinx/helpers/class'
2
+
3
+ class File
4
+ [:gets, :readline, :readlines].each do |attr|
5
+ # Overrides the standard method to infer a line separator from the input
6
+ # if the separator argument is the default.
7
+ redefine_method(attr) do |original|
8
+ lambda do |*params|
9
+ sep = params.first || default_line_separator
10
+ send(original, sep)
11
+ end
12
+ end
13
+
14
+ # Overrides the standard {#each} method to infer a line separator from the input
15
+ # if the separator argument is not given.
16
+ def each(separator=nil)
17
+ while (line = gets(separator)) do
18
+ yield line
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ # Returns the default line separator. The logic is borrowed from the FasterCVS gem.
26
+ def default_line_separator
27
+ @def_line_sep ||= infer_line_separator
28
+ end
29
+
30
+ def infer_line_separator
31
+ type_line_separator or content_line_separator or $/
32
+ end
33
+
34
+ def type_line_separator
35
+ if [ARGF, STDIN, STDOUT, STDERR].include?(self) or
36
+ (defined?(Zlib) and self.class == Zlib::GzipWriter) then
37
+ return $/
38
+ end
39
+ end
40
+
41
+ def content_line_separator
42
+ begin
43
+ saved_pos = pos # remember where we were
44
+ # read a chunk until a separator is discovered
45
+ sep = discover_line_separator
46
+ # tricky seek() clone to work around GzipReader's lack of seek()
47
+ rewind
48
+ # reset back to the remembered position
49
+ chunks, residual = saved_pos.divmod(1024)
50
+ chunks.times { read(1024) }
51
+ read(residual)
52
+ rescue IOError # stream not opened for reading
53
+ end
54
+ sep
55
+ end
56
+
57
+ def discover_line_separator
58
+ # read a chunk until a separator is discovered
59
+ while (sample = read(1024)) do
60
+ sample += read(1) if sample[-1, 1] == "\r" and not eof?
61
+ # try to find a standard separator
62
+ return $& if sample =~ /\r\n?|\n/
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,52 @@
1
+ module Jinx
2
+ # This Filter helper class applies a selection block to a base enumeration.
3
+ class Filter
4
+ include Collection
5
+
6
+ def initialize(enum=[], &filter)
7
+ @base = enum
8
+ @filter = filter
9
+ end
10
+
11
+ # Calls the given block on each item which passes this Filter's filter test.
12
+ #
13
+ # @yield [item] the block called on each item
14
+ # @yieldparam item the enumerated item
15
+ def each
16
+ @base.each { |item| yield(item) if @filter ? @filter.call(item) : item }
17
+ end
18
+
19
+ # Optimized for a Set base.
20
+ #
21
+ # @param [item] the item to check
22
+ # @return [Boolean] whether the item is a member of this Enumerable
23
+ def include?(item)
24
+ return false if Set === @base and not @base.include?(item)
25
+ super
26
+ end
27
+
28
+ # Adds an item to the base Enumerable, if this Filter's base supports it.
29
+ #
30
+ # @param item the item to add
31
+ # @return [Filter] self
32
+ def <<(item)
33
+ @base << item
34
+ self
35
+ end
36
+
37
+ # @param [Enumerable] other the Enumerable to merge
38
+ # @return [Array] this Filter's filtered content merged with the other Enumerable
39
+ def merge(other)
40
+ to_a.merge!(other)
41
+ end
42
+
43
+ # Merges the other Enumerable into the base Enumerable, if the base supports it.
44
+ #
45
+ # @param other (see #merge)
46
+ # @return [Filter, nil] this Filter's filtered content merged with the other Enumerable
47
+ def merge!(other)
48
+ @base.merge!(other)
49
+ self
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,38 @@
1
+ module Jinx
2
+ # A Flattener applies a given block to flattened collection content.
3
+ class Flattener
4
+ include Collection
5
+
6
+ # Visits the enumerated items in the given object's flattened content.
7
+ # block is called on the base itself if the base is neither nil nor a Enumerable.
8
+ # If the base object is nil or empty, then this method is a no-op and returns nil.
9
+ def self.on(obj, &block)
10
+ obj.collection? ? obj.each { |item| on(item, &block) } : yield(obj) unless obj.nil?
11
+ end
12
+
13
+ # Initializes a new Flattener on the given object.
14
+ #
15
+ # @param obj the Enumerable or non-collection object
16
+ def initialize(obj)
17
+ @base = obj
18
+ end
19
+
20
+ # Calls the the given block on this Flattener's flattened content.
21
+ # If the base object is a collection, then the block is called on the flattened content.
22
+ # If the base object is nil, then this method is a no-op.
23
+ # If the base object is neither nil nor a collection, then the block given to this method
24
+ # is called on the base object itself.
25
+ #
26
+ # @example
27
+ # Flattener.new(nil).each { |n| print n } #=>
28
+ # Flattener.new(1).each { |n| print n } #=> 1
29
+ # Flattener.new([1, [2, 3]]).each { |n| print n } #=> 123
30
+ def each(&block)
31
+ Flattener.on(@base, &block)
32
+ end
33
+
34
+ def to_s
35
+ to_a.to_s
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ require 'jinx/helpers/hashable'
2
+
3
+ class Hash
4
+ include Jinx::Hashable
5
+
6
+ # The EMPTY_HASH constant is an immutable empty hash, used primarily as a default argument.
7
+ class << EMPTY_HASH ||= Hash.new
8
+ def []=(key, value)
9
+ Jinx.fail(NotImplementedError, "Modification of the constant empty hash is not supported")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,502 @@
1
+ require 'jinx/helpers/collection'
2
+ require 'jinx/helpers/hashable'
3
+
4
+ module Jinx
5
+ # Hashable is a Hash mixin that adds utility methods to a Hash.
6
+ # Hashable can be included by any class or module which implements an _each_ method
7
+ # with arguments _key_ and _value_.
8
+ module Hashable
9
+ include Collection
10
+
11
+ # @see Hash#each_pair
12
+ def each_pair(&block)
13
+ each(&block)
14
+ end
15
+
16
+ # @see Hash#[]
17
+ def [](key)
18
+ detect_value { |k, v| v if k == key }
19
+ end
20
+
21
+ # @see Hash#each_key
22
+ def each_key
23
+ each { |k, v| yield k }
24
+ end
25
+
26
+ # @yield [key] the detector block
27
+ # @yieldparam key the hash key
28
+ # @return the key for which the detector block returns a non-nil, non-false value,
29
+ # or nil if none
30
+ # @example
31
+ # {1 => :a, 2 => :b, 3 => :c}.detect_key { |k| k > 1 } #=> 2
32
+ def detect_key
33
+ each_key { |k| return k if yield k }
34
+ nil
35
+ end
36
+
37
+ def has_key?(key)
38
+ !!detect_key { |k| k == key }
39
+ end
40
+
41
+ # @yield [value] the detector block
42
+ # @yieldparam value the hash value
43
+ # @return the key for which the detector block returns a non-nil, non-false value,
44
+ # or nil if none
45
+ # @example
46
+ # {:a => 1, :b => 2, :c => 3}.detect_key_with_value { |v| v > 1 } #=> :b
47
+ def detect_key_with_value
48
+ each { |k, v| return k if yield v }
49
+ nil
50
+ end
51
+
52
+ # @see Hash#each_value
53
+ def each_value
54
+ each { |k, v| yield v }
55
+ end
56
+
57
+ # Returns a Hashable which composes each value in this Hashable with the key of
58
+ # the other Hashable, e.g.:
59
+ # x = {:a => :c, :b => :d}
60
+ # y = {:c => 1}
61
+ # z = x.compose(y)
62
+ # z[:a] #=> {:c => 1}
63
+ # z[:b] #=> nil
64
+ #
65
+ # The accessor reflects changes to the underlying hashes, e.g. given the above example:
66
+ # x[:b] = 2
67
+ # z[:b] #=> {:c => 1}
68
+ #
69
+ # Update operations on the result are not supported.
70
+ #
71
+ # @param [Hashable] other the Hashable to compose with this Hashable
72
+ # @return [Hashable] the composed result
73
+ def compose(other)
74
+ transform_value { |v| {v => other[v]} if other.has_key?(v) }
75
+ end
76
+
77
+ # Returns a Hashable which joins each value in this Hashable with the key of
78
+ # the other Hashable, e.g.:
79
+ # x = {:a => :c, :b => :d}
80
+ # y = {:c => 1}
81
+ # z = x.join(y)
82
+ # z[:a] #=> 1
83
+ # z[:b] #=> nil
84
+ #
85
+ # The accessor reflects changes to the underlying hashes, e.g. given the above example:
86
+ # x[:b] = 2
87
+ # z[:b] #=> 2
88
+ #
89
+ # Update operations on the result are not supported.
90
+ #
91
+ # @param [Hashable] other the Hashable to join with this Hashable
92
+ # @return [Hashable] the joined result
93
+ def join(other)
94
+ transform_value { |v| other[v] }
95
+ end
96
+
97
+ # Returns a Hashable which associates each key of both this Hashable and the other Hashable
98
+ # with the corresponding value in the first Hashable which has that key, e.g.:
99
+ # x = {:a => 1, :b => 2}
100
+ # y = {:b => 3, :c => 4}
101
+ # z = x + y
102
+ # z[:b] #=> 2
103
+ #
104
+ # The accessor reflects changes to the underlying hashes, e.g. given the above example:
105
+ # x.delete(:b)
106
+ # z[:b] #=> 3
107
+ #
108
+ # Update operations on the result are not supported.
109
+ #
110
+ # @param [Hashable] other the Hashable to form a union with this Hashable
111
+ # @return [Hashable] the union result
112
+ def union(other)
113
+ MultiHash.new(self, other)
114
+ end
115
+
116
+ alias :+ :union
117
+
118
+ # Returns a new Hashable that iterates over the base Hashable <key, value> pairs for which the block
119
+ # given to this method evaluates to a non-nil, non-false value, e.g.:
120
+ # {:a => 1, :b => 2, :c => 3}.filter { |k, v| k != :b }.to_hash #=> {:a => 1, :c => 3}
121
+ #
122
+ # The default filter block tests the value, e.g.:
123
+ # {:a => 1, :b => nil}.filter.to_hash #=> {:a => 1}
124
+ #
125
+ # @yield [key, value] the filter block
126
+ # @return [Hashable] the filtered result
127
+ def filter(&block)
128
+ Filter.new(self, &block)
129
+ end
130
+
131
+ # Optimization of {#filter} for a block that only uses the key.
132
+ #
133
+ # @example
134
+ # {:a => 1, :b => 2, :c => 3}.filter_on_key { |k| k != :b }.to_hash #=> {:a => 1, :c => 3}
135
+ #
136
+ # @yield [key] the filter block
137
+ # @yieldparam key the hash key to filter
138
+ # @return [Hashable] the filtered result
139
+ def filter_on_key(&block)
140
+ KeyFilter.new(self, &block)
141
+ end
142
+
143
+ # @return [Hashable] a {#filter} that only uses the value.
144
+ # @yield [value] the filter block
145
+ # @yieldparam value the hash value to filter
146
+ # @return [Hashable] the filtered result
147
+ def filter_on_value
148
+ filter { |k, v| yield v }
149
+ end
150
+
151
+ # @return [Hash] a {#filter} of this Hashable which excludes the entries with a null value
152
+ def compact
153
+ filter_on_value { |v| not v.nil? }
154
+ end
155
+
156
+ # Returns the difference between this Hashable and the other Hashable in a Hash of the form:
157
+ #
158
+ # _key_ => [_mine_, _theirs_]
159
+ #
160
+ # where:
161
+ # * _key_ is the key of association which differs
162
+ # * _mine_ is the value for _key_ in this hash
163
+ # * _theirs_ is the value for _key_ in the other hash
164
+ #
165
+ # @param [Hashable] other the Hashable to subtract
166
+ # @yield [key, v1, v2] the optional block which determines whether values differ (default is equality)
167
+ # @yieldparam key the key for which values are compared
168
+ # @yieldparam v1 the value for key from this Hashable
169
+ # @yieldparam v2 the value for key from the other Hashable
170
+ # @return [{Object => (Object,Object)}] a hash of the differences
171
+ def diff(other)
172
+ (keys.to_set + other.keys).to_compact_hash do |k|
173
+ mine = self[k]
174
+ yours = other[k]
175
+ [mine, yours] unless block_given? ? yield(k, mine, yours) : mine == yours
176
+ end
177
+ end
178
+
179
+ # @yield [key1, key2] the key sort block
180
+ # @return [Hashable] a hash whose #each and {#each_pair} enumerations are sorted by key
181
+ def sort(&sorter)
182
+ SortedHash.new(self, &sorter)
183
+ end
184
+
185
+ # Returns a hash which associates each key in this hash with the value mapped by the others.
186
+ #
187
+ # @example
188
+ # {:a => 1, :b => 2}.assoc_values({:a => 3, :c => 4}) #=> {:a => [1, 3], :b => [2, nil], :c => [nil, 4]}
189
+ # {:a => 1, :b => 2}.assoc_values({:a => 3}, {:a => 4, :b => 5}) #=> {:a => [1, 3, 4], :b => [2, nil, 5]}
190
+ #
191
+ # @param [<Hashable>] others the other Hashables to associate with this Hashable
192
+ # @return [Hash] the association hash
193
+ def assoc_values(*others)
194
+ all_keys = keys
195
+ others.each { |hash| all_keys.concat(hash.keys) }
196
+ all_keys.to_compact_hash do |k|
197
+ others.map { |other| other[k] }.unshift(self[k])
198
+ end
199
+ end
200
+
201
+ # Returns an Enumerable whose each block is called on each key which maps to a value which
202
+ # either equals the given target_value or satisfies the filter block.
203
+ #
204
+ # @param target_value the filter value
205
+ # @yield [value] the filter block
206
+ # @return [Enumerable] the filtered keys
207
+ def enum_keys_with_value(target_value=nil, &filter) # :yields: value
208
+ return enum_keys_with_value { |v| v == target_value } if target_value
209
+ filter_on_value(&filter).keys
210
+ end
211
+
212
+ # @return [Enumerable] Enumerable over this Hashable's keys
213
+ def enum_keys
214
+ Enumerable::Enumerator.new(self, :each_key)
215
+ end
216
+
217
+ # @return [Array] this Hashable's keys
218
+ def keys
219
+ enum_keys.to_a
220
+ end
221
+
222
+ # @param key search target
223
+ # @return [Boolean] whether this Hashable has the given key
224
+ def has_key?(key)
225
+ enum_keys.include?(key)
226
+ end
227
+
228
+ alias :include? :has_key?
229
+
230
+ # @return [Enumerable] an Enumerable over this Hashable's values
231
+ def enum_values
232
+ Enumerable::Enumerator.new(self, :each_value)
233
+ end
234
+
235
+ # @yield [key] the key selector
236
+ # @return [Enumerable] the keys which satisfy the block given to this method
237
+ def select_keys(&block)
238
+ enum_keys.select(&block)
239
+ end
240
+
241
+ # @yield [key] the key rejector
242
+ # @return [Enumerable] the keys which do not satisfy the block given to this method
243
+ def reject_keys(&block)
244
+ enum_keys.reject(&block)
245
+ end
246
+
247
+ # @yield [value] the value selector
248
+ # @return [Enumerable] the values which satisfy the block given to this method
249
+ def select_values(&block)
250
+ enum_values.select(&block)
251
+ end
252
+
253
+ # @yield [value] the value rejector
254
+ # @return [Enumerable] the values which do not satisfy the block given to this method
255
+ def reject_values(&block)
256
+ enum_values.reject(&block)
257
+ end
258
+
259
+ # @return [Array] this Enumerable's values
260
+ def values
261
+ enum_values.to_a
262
+ end
263
+
264
+ # @param value search target
265
+ # @return [Boolean] whether this Hashable has the given value
266
+ def has_value?(value)
267
+ enum_values.include?(value)
268
+ end
269
+
270
+ # @return [Array] a flattened Array of this Hash
271
+ # @example
272
+ # {:a => {:b => :c}, :d => :e, :f => [:g]} #=> [:a, :b, :c, :d, :e, :f, :g]
273
+ def flatten
274
+ Flattener.new(self).to_a
275
+ end
276
+
277
+ # @yield [key, value] hash splitter
278
+ # @return [(Hash, Hash)] two hashes split by whether calling the block on the
279
+ # entry returns a non-nil, non-false value
280
+ # @example
281
+ # {:a => 1, :b => 2}.split { |k, v| v < 2 } #=> [{:a => 1}, {:b => 2}]
282
+ def split(&block)
283
+ partition(&block).map { |pairs| pairs.to_assoc_hash }
284
+ end
285
+
286
+ # Returns a new Hash that recursively copies this hash's values. Values of type hash are copied using copy_recursive.
287
+ # Other values are unchanged.
288
+ #
289
+ # This method is useful for preserving and restoring hash associations.
290
+ #
291
+ # @return [Hash] a deep copy of this Hashable
292
+ def copy_recursive
293
+ copy = Hash.new
294
+ keys.each do |k|
295
+ value = self[k]
296
+ copy[k] = Hash === value ? value.copy_recursive : value
297
+ end
298
+ copy
299
+ end
300
+
301
+ # @example
302
+ # {:a => 1, :b => 2}.transform_value { |n| n * 2 }.values #=> [2, 4]
303
+ #
304
+ # @yield [value] transforms the given value
305
+ # @yieldparam [value] the value to transform
306
+ # @return [Hash] a new Hash that transforms each value
307
+ def transform_value(&transformer)
308
+ ValueTransformerHash.new(self, &transformer)
309
+ end
310
+
311
+ # @example
312
+ # {1 => :a, 2 => :b}.transform_key { |n| n * 2 }.keys #=> [2, 4]
313
+ #
314
+ # @yield [key] transforms the given key
315
+ # @yieldparam [value] the key to transform
316
+ # @return [Hash] a new Hash that transforms each key
317
+ def transform_key(&transformer)
318
+ KeyTransformerHash.new(self, &transformer)
319
+ end
320
+
321
+ # @return [Hash] a new Hash created from this Hashable's content
322
+ def to_hash
323
+ hash = {}
324
+ each { |k, v| hash[k] = v }
325
+ hash
326
+ end
327
+
328
+ def to_set
329
+ to_a.to_set
330
+ end
331
+
332
+ def to_s
333
+ to_hash.to_s
334
+ end
335
+
336
+ def inspect
337
+ to_hash.inspect
338
+ end
339
+
340
+ def ==(other)
341
+ to_hash == other.to_hash rescue super
342
+ end
343
+
344
+ private
345
+
346
+ # @see #filter
347
+ class Filter
348
+ include Hashable
349
+
350
+ def initialize(base, &filter)
351
+ @base = base
352
+ @filter = filter
353
+ end
354
+
355
+ def each
356
+ @base.each { |k, v| yield(k, v) if @filter ? @filter.call(k, v) : v }
357
+ end
358
+ end
359
+
360
+ # @see #filter_on_key
361
+ class KeyFilter < Filter
362
+ include Hashable
363
+
364
+ def initialize(base)
365
+ super(base) { |k, v| yield(k) }
366
+ end
367
+
368
+ def [](key)
369
+ super if @filter.call(key, nil)
370
+ end
371
+ end
372
+
373
+ # @see #sort
374
+ class SortedHash
375
+ include Hashable
376
+
377
+ def initialize(base, &comparator)
378
+ @base = base
379
+ @comparator = comparator
380
+ end
381
+
382
+ def each
383
+ @base.keys.sort { |k1, k2| @comparator ? @comparator.call(k1, k2) : k1 <=> k2 }.each { |k| yield(k, @base[k]) }
384
+ end
385
+ end
386
+
387
+ # Combines hashes. See Hash#+ for details.
388
+ class MultiHash
389
+ include Hashable
390
+
391
+ # @return [<Hashable>] the enumerated hashes
392
+ attr_reader :components
393
+
394
+ def initialize(*hashes)
395
+ if hashes.include?(nil) then Jinx.fail(ArgumentError, "MultiHash is missing a component hash.") end
396
+ @components = hashes
397
+ end
398
+
399
+ def [](key)
400
+ @components.each { |hash| return hash[key] if hash.has_key?(key) }
401
+ nil
402
+ end
403
+
404
+ def has_key?(key)
405
+ @components.any? { |hash| hash.has_key?(key) }
406
+ end
407
+
408
+ def has_value?(value)
409
+ @components.any? { |hash| hash.has_value?(value) }
410
+ end
411
+
412
+ def each
413
+ @components.each_with_index do |hash, index|
414
+ hash.each do |k, v|
415
+ yield(k, v) unless (0...index).any? { |i| @components[i].has_key?(k) }
416
+ end
417
+ end
418
+ self
419
+ end
420
+ end
421
+ end
422
+
423
+ # The ValueTransformerHash class pipes the value from a base Hashable into a transformer block.
424
+ # @private
425
+ class ValueTransformerHash
426
+ include Hashable
427
+
428
+ # Creates a ValueTransformerHash on the base hash and value transformer block.
429
+ #
430
+ # @param [Hash, nil] base the hash to transform
431
+ # @yield [value] transforms the base value
432
+ # @yieldparam value the base value to transform
433
+ def initialize(base, &transformer)
434
+ @base = base
435
+ @xfm = transformer
436
+ end
437
+
438
+ # @param key the hash key
439
+ # @return the value at key after this ValueTransformerHash's transformer block is applied, or nil
440
+ # if this hash does not contain key
441
+ def [](key)
442
+ @xfm.call(@base[key]) if @base.has_key?(key)
443
+ end
444
+
445
+ # @yield [key, value] operate on the key and transformed value
446
+ # @yieldparam key the hash key
447
+ # @yieldparam value the transformed hash value
448
+ def each
449
+ @base.each { |k, v| yield(k, @xfm.call(v)) }
450
+ end
451
+ end
452
+
453
+ # The KeyTransformerHash class pipes the key from a base Hashable into a transformer block.
454
+ # @private
455
+ class KeyTransformerHash
456
+ include Hashable
457
+
458
+ # Creates a KeyTransformerHash on the base hash and key transformer block.
459
+ #
460
+ # @param [Hash, nil] base the hash to transform
461
+ # @yield [key] transforms the base key
462
+ # @yieldparam key the base key to transform
463
+ def initialize(base, &transformer)
464
+ @base = base
465
+ @xfm = transformer
466
+ end
467
+
468
+ # @param key the untransformed hash key
469
+ # @return the value for the transformed key
470
+ def [](key)
471
+ @base[@xfm.call(@base[key])]
472
+ end
473
+
474
+ # @yield [key, value] operate on the transformed key and value
475
+ # @yieldparam key the transformed hash key
476
+ # @yieldparam value the hash value
477
+ def each
478
+ @base.each { |k, v| yield(@xfm.call(k), v) }
479
+ end
480
+ end
481
+
482
+ # Hashinator creates a Hashable from an Enumerable on [_key_, _value_] pairs.
483
+ # The Hashinator reflects changes to the underlying Enumerable.
484
+ #
485
+ # @example
486
+ # base = [[:a, 1], [:b, 2]]
487
+ # hash = Hashinator.new(base)
488
+ # hash[:a] #=> 1
489
+ # base.first[1] = 3
490
+ # hash[:a] #=> 3
491
+ class Hashinator
492
+ include Hashable
493
+
494
+ def initialize(enum)
495
+ @base = enum
496
+ end
497
+
498
+ def each
499
+ @base.each { |pair| yield(*pair) }
500
+ end
501
+ end
502
+ end