jinx 2.1.1

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 (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