jinx 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.yardopts +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +27 -0
- data/History.md +6 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +44 -0
- data/Rakefile +41 -0
- data/examples/family/README.md +10 -0
- data/examples/family/ext/build.xml +35 -0
- data/examples/family/ext/src/family/Address.java +68 -0
- data/examples/family/ext/src/family/Child.java +24 -0
- data/examples/family/ext/src/family/DomainObject.java +26 -0
- data/examples/family/ext/src/family/Household.java +36 -0
- data/examples/family/ext/src/family/Parent.java +48 -0
- data/examples/family/ext/src/family/Person.java +42 -0
- data/examples/family/lib/family.rb +15 -0
- data/examples/family/lib/family/address.rb +6 -0
- data/examples/family/lib/family/domain_object.rb +6 -0
- data/examples/family/lib/family/household.rb +6 -0
- data/examples/family/lib/family/parent.rb +16 -0
- data/examples/family/lib/family/person.rb +6 -0
- data/examples/model/README.md +25 -0
- data/examples/model/ext/build.xml +35 -0
- data/examples/model/ext/src/domain/Child.java +192 -0
- data/examples/model/ext/src/domain/Dependent.java +29 -0
- data/examples/model/ext/src/domain/DomainObject.java +26 -0
- data/examples/model/ext/src/domain/Independent.java +83 -0
- data/examples/model/ext/src/domain/Parent.java +129 -0
- data/examples/model/ext/src/domain/Person.java +14 -0
- data/examples/model/lib/model.rb +13 -0
- data/examples/model/lib/model/child.rb +13 -0
- data/examples/model/lib/model/domain_object.rb +6 -0
- data/examples/model/lib/model/independent.rb +11 -0
- data/examples/model/lib/model/parent.rb +17 -0
- data/jinx.gemspec +22 -0
- data/lib/jinx.rb +3 -0
- data/lib/jinx/active_support/README.txt +2 -0
- data/lib/jinx/active_support/core_ext/string.rb +7 -0
- data/lib/jinx/active_support/core_ext/string/inflections.rb +167 -0
- data/lib/jinx/active_support/inflections.rb +55 -0
- data/lib/jinx/active_support/inflector.rb +398 -0
- data/lib/jinx/cli/application.rb +36 -0
- data/lib/jinx/cli/command.rb +214 -0
- data/lib/jinx/helpers/array.rb +108 -0
- data/lib/jinx/helpers/boolean.rb +42 -0
- data/lib/jinx/helpers/case_insensitive_hash.rb +39 -0
- data/lib/jinx/helpers/class.rb +149 -0
- data/lib/jinx/helpers/collection.rb +33 -0
- data/lib/jinx/helpers/collections.rb +11 -0
- data/lib/jinx/helpers/collector.rb +20 -0
- data/lib/jinx/helpers/conditional_enumerator.rb +21 -0
- data/lib/jinx/helpers/enumerable.rb +242 -0
- data/lib/jinx/helpers/enumerate.rb +35 -0
- data/lib/jinx/helpers/error.rb +15 -0
- data/lib/jinx/helpers/file_separator.rb +65 -0
- data/lib/jinx/helpers/filter.rb +52 -0
- data/lib/jinx/helpers/flattener.rb +38 -0
- data/lib/jinx/helpers/hash.rb +12 -0
- data/lib/jinx/helpers/hashable.rb +502 -0
- data/lib/jinx/helpers/inflector.rb +36 -0
- data/lib/jinx/helpers/key_transformer_hash.rb +43 -0
- data/lib/jinx/helpers/lazy_hash.rb +44 -0
- data/lib/jinx/helpers/log.rb +106 -0
- data/lib/jinx/helpers/math.rb +12 -0
- data/lib/jinx/helpers/merge.rb +60 -0
- data/lib/jinx/helpers/module.rb +18 -0
- data/lib/jinx/helpers/multi_enumerator.rb +31 -0
- data/lib/jinx/helpers/options.rb +92 -0
- data/lib/jinx/helpers/os.rb +19 -0
- data/lib/jinx/helpers/partial_order.rb +37 -0
- data/lib/jinx/helpers/pretty_print.rb +207 -0
- data/lib/jinx/helpers/set.rb +8 -0
- data/lib/jinx/helpers/stopwatch.rb +76 -0
- data/lib/jinx/helpers/transformer.rb +24 -0
- data/lib/jinx/helpers/transitive_closure.rb +55 -0
- data/lib/jinx/helpers/uniquifier.rb +50 -0
- data/lib/jinx/helpers/validation.rb +33 -0
- data/lib/jinx/helpers/visitor.rb +370 -0
- data/lib/jinx/import/class_path_modifier.rb +77 -0
- data/lib/jinx/import/java.rb +337 -0
- data/lib/jinx/importer.rb +240 -0
- data/lib/jinx/metadata.rb +155 -0
- data/lib/jinx/metadata/attribute_enumerator.rb +73 -0
- data/lib/jinx/metadata/dependency.rb +244 -0
- data/lib/jinx/metadata/id_alias.rb +23 -0
- data/lib/jinx/metadata/introspector.rb +179 -0
- data/lib/jinx/metadata/inverse.rb +170 -0
- data/lib/jinx/metadata/java_property.rb +169 -0
- data/lib/jinx/metadata/propertied.rb +500 -0
- data/lib/jinx/metadata/property.rb +401 -0
- data/lib/jinx/metadata/property_characteristics.rb +114 -0
- data/lib/jinx/resource.rb +862 -0
- data/lib/jinx/resource/copy_visitor.rb +36 -0
- data/lib/jinx/resource/inversible.rb +90 -0
- data/lib/jinx/resource/match_visitor.rb +180 -0
- data/lib/jinx/resource/matcher.rb +20 -0
- data/lib/jinx/resource/merge_visitor.rb +73 -0
- data/lib/jinx/resource/mergeable.rb +185 -0
- data/lib/jinx/resource/reference_enumerator.rb +49 -0
- data/lib/jinx/resource/reference_path_visitor.rb +38 -0
- data/lib/jinx/resource/reference_visitor.rb +55 -0
- data/lib/jinx/resource/unique.rb +35 -0
- data/lib/jinx/version.rb +3 -0
- data/spec/defaults_spec.rb +30 -0
- data/spec/definitions/model/alias/child.rb +5 -0
- data/spec/definitions/model/base/child.rb +5 -0
- data/spec/definitions/model/base/domain_object.rb +5 -0
- data/spec/definitions/model/base/independent.rb +5 -0
- data/spec/definitions/model/defaults/child.rb +5 -0
- data/spec/definitions/model/dependency/child.rb +5 -0
- data/spec/definitions/model/dependency/parent.rb +6 -0
- data/spec/definitions/model/inverse/child.rb +5 -0
- data/spec/definitions/model/inverse/independent.rb +5 -0
- data/spec/definitions/model/inverse/parent.rb +5 -0
- data/spec/definitions/model/mandatory/child.rb +6 -0
- data/spec/dependency_spec.rb +47 -0
- data/spec/family_spec.rb +64 -0
- data/spec/inverse_spec.rb +53 -0
- data/spec/mandatory_spec.rb +43 -0
- data/spec/metadata_spec.rb +68 -0
- data/spec/resource_spec.rb +30 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/model.rb +19 -0
- data/test/fixtures/line_separator/cr_line_sep.txt +1 -0
- data/test/fixtures/line_separator/crlf_line_sep.txt +3 -0
- data/test/fixtures/line_separator/lf_line_sep.txt +3 -0
- data/test/fixtures/mixed/ext/build.xml +35 -0
- data/test/fixtures/mixed/ext/src/mixed/Case/Example.java +5 -0
- data/test/helper.rb +7 -0
- data/test/lib/jinx/command_test.rb +41 -0
- data/test/lib/jinx/helpers/boolean_test.rb +27 -0
- data/test/lib/jinx/helpers/class_test.rb +60 -0
- data/test/lib/jinx/helpers/collections_test.rb +402 -0
- data/test/lib/jinx/helpers/file_separator_test.rb +29 -0
- data/test/lib/jinx/helpers/inflector_test.rb +11 -0
- data/test/lib/jinx/helpers/lazy_hash_test.rb +32 -0
- data/test/lib/jinx/helpers/module_test.rb +24 -0
- data/test/lib/jinx/helpers/options_test.rb +66 -0
- data/test/lib/jinx/helpers/partial_order_test.rb +41 -0
- data/test/lib/jinx/helpers/pretty_print_test.rb +83 -0
- data/test/lib/jinx/helpers/stopwatch_test.rb +16 -0
- data/test/lib/jinx/helpers/transitive_closure_test.rb +80 -0
- data/test/lib/jinx/helpers/visitor_test.rb +288 -0
- data/test/lib/jinx/import/java_test.rb +78 -0
- data/test/lib/jinx/import/mixed_case_test.rb +16 -0
- 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
|