caruby-core 1.5.5 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -0
- data/History.md +5 -1
- data/lib/caruby.rb +3 -5
- data/lib/caruby/caruby-src.tar.gz +0 -0
- data/lib/caruby/database.rb +53 -69
- data/lib/caruby/database/application_service.rb +25 -0
- data/lib/caruby/database/cache.rb +60 -0
- data/lib/caruby/database/fetched_matcher.rb +52 -38
- data/lib/caruby/database/lazy_loader.rb +4 -4
- data/lib/caruby/database/operation.rb +34 -0
- data/lib/caruby/database/persistable.rb +171 -86
- data/lib/caruby/database/persistence_service.rb +32 -34
- data/lib/caruby/database/persistifier.rb +100 -43
- data/lib/caruby/database/reader.rb +107 -85
- data/lib/caruby/database/reader_template_builder.rb +60 -0
- data/lib/caruby/database/saved_matcher.rb +3 -3
- data/lib/caruby/database/sql_executor.rb +88 -17
- data/lib/caruby/database/writer.rb +213 -177
- data/lib/caruby/database/writer_template_builder.rb +334 -0
- data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
- data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
- data/lib/caruby/{util → helpers}/person.rb +3 -3
- data/lib/caruby/{util → helpers}/properties.rb +7 -9
- data/lib/caruby/{util → helpers}/roman.rb +2 -2
- data/lib/caruby/{util → helpers}/version.rb +1 -1
- data/lib/caruby/json/deserializer.rb +2 -2
- data/lib/caruby/json/serializer.rb +49 -7
- data/lib/caruby/metadata.rb +30 -0
- data/lib/caruby/metadata/java_property.rb +21 -0
- data/lib/caruby/metadata/propertied.rb +191 -0
- data/lib/caruby/metadata/property.rb +22 -0
- data/lib/caruby/metadata/property_characteristics.rb +201 -0
- data/lib/caruby/migration/migratable.rb +11 -182
- data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
- data/lib/caruby/resource.rb +20 -823
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/database/cache_test.rb +54 -0
- data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
- data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
- data/test/lib/caruby/helpers/properties_test.rb +34 -0
- data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
- data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
- data/test/lib/helper.rb +7 -0
- metadata +161 -214
- data/lib/caruby/cli/application.rb +0 -36
- data/lib/caruby/cli/command.rb +0 -202
- data/lib/caruby/csv/csv_mapper.rb +0 -159
- data/lib/caruby/csv/csvio.rb +0 -203
- data/lib/caruby/database/search_template_builder.rb +0 -56
- data/lib/caruby/database/store_template_builder.rb +0 -278
- data/lib/caruby/domain.rb +0 -193
- data/lib/caruby/domain/attribute.rb +0 -584
- data/lib/caruby/domain/attributes.rb +0 -628
- data/lib/caruby/domain/dependency.rb +0 -225
- data/lib/caruby/domain/id_alias.rb +0 -22
- data/lib/caruby/domain/importer.rb +0 -183
- data/lib/caruby/domain/introspection.rb +0 -176
- data/lib/caruby/domain/inverse.rb +0 -172
- data/lib/caruby/domain/inversible.rb +0 -90
- data/lib/caruby/domain/java_attribute.rb +0 -173
- data/lib/caruby/domain/merge.rb +0 -185
- data/lib/caruby/domain/metadata.rb +0 -142
- data/lib/caruby/domain/mixin.rb +0 -35
- data/lib/caruby/domain/properties.rb +0 -95
- data/lib/caruby/domain/reference_visitor.rb +0 -428
- data/lib/caruby/domain/uniquify.rb +0 -50
- data/lib/caruby/import/java.rb +0 -387
- data/lib/caruby/migration/migrator.rb +0 -918
- data/lib/caruby/migration/resource_module.rb +0 -9
- data/lib/caruby/migration/uniquify.rb +0 -17
- data/lib/caruby/util/attribute_path.rb +0 -44
- data/lib/caruby/util/cache.rb +0 -56
- data/lib/caruby/util/class.rb +0 -149
- data/lib/caruby/util/collection.rb +0 -1152
- data/lib/caruby/util/domain_extent.rb +0 -46
- data/lib/caruby/util/file_separator.rb +0 -65
- data/lib/caruby/util/inflector.rb +0 -27
- data/lib/caruby/util/log.rb +0 -95
- data/lib/caruby/util/math.rb +0 -12
- data/lib/caruby/util/merge.rb +0 -59
- data/lib/caruby/util/module.rb +0 -18
- data/lib/caruby/util/options.rb +0 -97
- data/lib/caruby/util/partial_order.rb +0 -35
- data/lib/caruby/util/pretty_print.rb +0 -204
- data/lib/caruby/util/stopwatch.rb +0 -74
- data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
- data/lib/caruby/util/transitive_closure.rb +0 -55
- data/lib/caruby/util/tree.rb +0 -48
- data/lib/caruby/util/trie.rb +0 -37
- data/lib/caruby/util/uniquifier.rb +0 -30
- data/lib/caruby/util/validation.rb +0 -20
- data/lib/caruby/util/visitor.rb +0 -365
- data/lib/caruby/util/weak_hash.rb +0 -36
- data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
- data/test/lib/caruby/csv/csvio_test.rb +0 -69
- data/test/lib/caruby/database/persistable_test.rb +0 -92
- data/test/lib/caruby/domain/domain_test.rb +0 -112
- data/test/lib/caruby/domain/inversible_test.rb +0 -99
- data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
- data/test/lib/caruby/import/java_test.rb +0 -80
- data/test/lib/caruby/import/mixed_case_test.rb +0 -14
- data/test/lib/caruby/migration/test_case.rb +0 -102
- data/test/lib/caruby/test_case.rb +0 -230
- data/test/lib/caruby/util/cache_test.rb +0 -23
- data/test/lib/caruby/util/class_test.rb +0 -61
- data/test/lib/caruby/util/collection_test.rb +0 -398
- data/test/lib/caruby/util/command_test.rb +0 -55
- data/test/lib/caruby/util/domain_extent_test.rb +0 -60
- data/test/lib/caruby/util/file_separator_test.rb +0 -30
- data/test/lib/caruby/util/inflector_test.rb +0 -12
- data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
- data/test/lib/caruby/util/merge_test.rb +0 -83
- data/test/lib/caruby/util/module_test.rb +0 -25
- data/test/lib/caruby/util/options_test.rb +0 -59
- data/test/lib/caruby/util/partial_order_test.rb +0 -42
- data/test/lib/caruby/util/pretty_print_test.rb +0 -85
- data/test/lib/caruby/util/properties_test.rb +0 -50
- data/test/lib/caruby/util/stopwatch_test.rb +0 -18
- data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
- data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
- data/test/lib/caruby/util/tree_test.rb +0 -23
- data/test/lib/caruby/util/trie_test.rb +0 -14
- data/test/lib/caruby/util/visitor_test.rb +0 -278
- data/test/lib/caruby/util/weak_hash_test.rb +0 -45
- data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
- data/test/lib/examples/clinical_trials/migration/test_case.rb +0 -38
@@ -1,9 +0,0 @@
|
|
1
|
-
module CaRuby
|
2
|
-
module Domain
|
3
|
-
# Declares the given classes which will be dynamically modified for migration.
|
4
|
-
# The Java caBIG classes are auto-loaded and wrapped as a CaRuby::Resource, if necessary, and enhanced in the migration shim.
|
5
|
-
def shims(*classes)
|
6
|
-
# nothing to do, since all this method does is ensure that the arguments are auto-loaded when they are referenced
|
7
|
-
end
|
8
|
-
end
|
9
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
require 'caruby/domain/uniquify'
|
2
|
-
|
3
|
-
module CaRuby
|
4
|
-
module Resource
|
5
|
-
module Unique
|
6
|
-
# Augments this {Unique} mix-in with a {Migratable#migrate} method which calls {Unique#uniquify}
|
7
|
-
# to make this Resource object unique in the scope of its class.
|
8
|
-
#
|
9
|
-
# @param (see Migratable#migrate)
|
10
|
-
def migrate(row, migrated)
|
11
|
-
super
|
12
|
-
logger.debug { "Migrator making #{self} unique..." }
|
13
|
-
uniquify
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'caruby/util/validation'
|
2
|
-
|
3
|
-
# An AttributePath encapsulates an array of attributes that can be evaluated on a source object.
|
4
|
-
class AttributePath < Array
|
5
|
-
# Creates an AttributePath from the path Array, String or Symbol. A path string is a period-delimited sequence
|
6
|
-
# of attributes, e.g. +person.name+.
|
7
|
-
def initialize(path)
|
8
|
-
raise ArgumentError.new("Path empty") if path.nil_or_empty?
|
9
|
-
# standardize the argument as a symbol array
|
10
|
-
attributes = case path
|
11
|
-
when Symbol then
|
12
|
-
[path]
|
13
|
-
when String then
|
14
|
-
path.split('.').map { |name| name.to_sym }
|
15
|
-
when Array then
|
16
|
-
path.map { |name| name.to_sym }
|
17
|
-
else
|
18
|
-
raise ArgumentError.new("Argument type unsupported - expected Symbol, String or Array; found #{path.class}")
|
19
|
-
end
|
20
|
-
# make the array
|
21
|
-
super(attributes)
|
22
|
-
end
|
23
|
-
|
24
|
-
# Returns the result of evaluating this evaluator's attribute path on the source object.
|
25
|
-
# If the evaluation results in a migratable object, then that object is migrated.
|
26
|
-
def evaluate(source)
|
27
|
-
# call the attribute path as far as possible
|
28
|
-
inject(source) do |current, attr|
|
29
|
-
return if current.nil?
|
30
|
-
evaluate_attribute(attr, current)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# Returns the result of evaluating attribute on the source object.
|
35
|
-
# If attr is +self+, then the source object is returned.
|
36
|
-
def evaluate_attribute(attr, source)
|
37
|
-
# call the attribute path as far as possible
|
38
|
-
attr == :self ? source : source.send(attr)
|
39
|
-
end
|
40
|
-
|
41
|
-
def to_s
|
42
|
-
join('.')
|
43
|
-
end
|
44
|
-
end
|
data/lib/caruby/util/cache.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
require 'caruby/util/collection'
|
2
|
-
|
3
|
-
module CaRuby
|
4
|
-
# Cache for objects held in memory and accessed by key.
|
5
|
-
class Cache
|
6
|
-
|
7
|
-
# The classes which are not cleared when {#clear} is called without the +all+ flag.
|
8
|
-
attr_reader :sticky
|
9
|
-
|
10
|
-
# Returns a new Cache whose value key is determined by calling the given
|
11
|
-
# extractor block on the cached value. The key must uniquely identify the
|
12
|
-
# cached value within the scope of the value class.
|
13
|
-
#
|
14
|
-
# If the value is not cached and there is a factory Proc, then the result of
|
15
|
-
# calling the factory on the missing value is cached with the value key.
|
16
|
-
#
|
17
|
-
# @param [Proc] optional factory Proc called with a missing value as argument
|
18
|
-
# to create a cached object
|
19
|
-
# @yield [value] returns the value key
|
20
|
-
# @yieldparam value the value to cach
|
21
|
-
def initialize(factory=nil, &extractor)
|
22
|
-
@factory = factory
|
23
|
-
# Make the class => { key => value } hash.
|
24
|
-
# The { key => value } hash takes a value as an argument and converts
|
25
|
-
# it to the key by calling the block given to this initializer.
|
26
|
-
@hash = LazyHash.new { KeyTransformerHash.new { |value| yield value } }
|
27
|
-
@sticky = Set.new
|
28
|
-
end
|
29
|
-
|
30
|
-
# Returns the object cached with the same class and key as the given value.
|
31
|
-
# If this Cache has a factory but does not have an entry for value, then the
|
32
|
-
# factory is called on the value to create a new entry.
|
33
|
-
def [](value)
|
34
|
-
chash = @hash[value.class]
|
35
|
-
cached = chash[value] if chash
|
36
|
-
return cached unless cached.nil? and @factory
|
37
|
-
obj = @factory.call(value) || return
|
38
|
-
chash[value] = obj
|
39
|
-
end
|
40
|
-
|
41
|
-
# Adds the given value to this cache.
|
42
|
-
def add(value)
|
43
|
-
@hash[value.class][value] = value
|
44
|
-
end
|
45
|
-
|
46
|
-
# Clears the cache key => object hashes. If all is true, then every class hash
|
47
|
-
# is cleared. Otherwise, only the non-sticky classes are cleared.
|
48
|
-
def clear(all=false)
|
49
|
-
if @sticky.empty? then
|
50
|
-
@hash.clear
|
51
|
-
else
|
52
|
-
@hash.each { |klass, chash| chash.clear unless @sticky.include?(klass) }
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
data/lib/caruby/util/class.rb
DELETED
@@ -1,149 +0,0 @@
|
|
1
|
-
require 'enumerator'
|
2
|
-
|
3
|
-
class Class
|
4
|
-
# Returns an Enumerable on this class and its ancestors.
|
5
|
-
def class_hierarchy
|
6
|
-
@class__hierarchy ||= Enumerable::Enumerator.new(self, :each_class_in_hierarchy)
|
7
|
-
end
|
8
|
-
|
9
|
-
# Returns this class's superclass, thereby enabling class ranges, e.g.
|
10
|
-
# class A; end
|
11
|
-
# class B < A; end
|
12
|
-
# (B..Object).to_a #=> [B, A, Object]
|
13
|
-
alias :succ :superclass
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
# Creates an alias for each accessor method of the given attribute.
|
18
|
-
#
|
19
|
-
# @example
|
20
|
-
# class Person
|
21
|
-
# attr_reader :social_security_number
|
22
|
-
# attr_accessor :postal_code
|
23
|
-
# alias_attribute(:ssn, :social_security_number)
|
24
|
-
# alias_attribute(:zip_code, :postal_code)
|
25
|
-
# end
|
26
|
-
# Person.method_defined?(:ssn) #=> true
|
27
|
-
# Person.method_defined?(:ssn=) #=> false
|
28
|
-
# Person.method_defined?(:zip_code) #=> true
|
29
|
-
# Person.method_defined?(:zip_code=) #=> true
|
30
|
-
def alias_attribute(aliaz, attribute)
|
31
|
-
alias_method(aliaz, attribute) if method_defined?(attribute)
|
32
|
-
writer = "#{attribute}=".to_sym
|
33
|
-
alias_method("#{aliaz}=".to_sym, writer) if method_defined?(writer)
|
34
|
-
end
|
35
|
-
|
36
|
-
# Creates new accessor methods for each _method_ => _original_ hash entry.
|
37
|
-
# The new _method_ offsets the existing Number _original_ attribute value by the given
|
38
|
-
# offset (default -1).
|
39
|
-
#
|
40
|
-
# @example
|
41
|
-
# class OneBased
|
42
|
-
# attr_accessor :index
|
43
|
-
# offset_attr_accessor :zero_based_index => :index
|
44
|
-
# end
|
45
|
-
#@param [{Symbol => Symbol}] hash the offset => original method hash
|
46
|
-
#@param [Integer, nil] offset the offset amount (default is -1)
|
47
|
-
def offset_attr_accessor(hash, offset=nil)
|
48
|
-
offset ||= -1
|
49
|
-
hash.each do |method, original|
|
50
|
-
define_method(method) { value = send(original); value + offset if value } if method_defined?(original)
|
51
|
-
original_writer = "#{original}=".to_sym
|
52
|
-
if method_defined?(original_writer) then
|
53
|
-
define_method("#{method}=".to_sym) do |value|
|
54
|
-
adjusted = value - offset if value
|
55
|
-
send(original_writer, adjusted)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
# Defines an instance variable accessor attribute whose reader calls the block given
|
62
|
-
# to this method to create a new instance variable on demand, if necessary.
|
63
|
-
#
|
64
|
-
# For example, the declaration
|
65
|
-
# class AlertHandler
|
66
|
-
# attr_create_on_demand_accessor(:pings) { Array.new }
|
67
|
-
# end
|
68
|
-
# is equivalent to:
|
69
|
-
# class AlertHandler
|
70
|
-
# attr_writer :pings
|
71
|
-
# def pings
|
72
|
-
# instance_variable_defined?(@pings) ? @pings : Array.new
|
73
|
-
# end
|
74
|
-
# end
|
75
|
-
#
|
76
|
-
# This method is useful either as a short-hand for the create-on-demand idiom
|
77
|
-
# as shown in the example above, or when it is desirable to dynamically add a
|
78
|
-
# mix-in attribute to a class at runtime whose name is not known when the class
|
79
|
-
# is defined.
|
80
|
-
#
|
81
|
-
# @example
|
82
|
-
# class AlertHandler
|
83
|
-
# def self.handle(alert)
|
84
|
-
# attr_create_on_demand_accessor(alert) { AlertQueue.new }
|
85
|
-
# end
|
86
|
-
# end
|
87
|
-
# ...
|
88
|
-
# AlertHandler.handle(:pings)
|
89
|
-
# AlertHandler.new.pings #=> empty AlertQueue
|
90
|
-
#
|
91
|
-
# @param [Symbol] symbol the attribute to define
|
92
|
-
# @yield [obj] factory to create the new attribute value for the given instance
|
93
|
-
# @yieldparam obj the class instance for which the attribute will be set
|
94
|
-
def attr_create_on_demand_accessor(symbol)
|
95
|
-
attr_writer(symbol)
|
96
|
-
wtr = "#{symbol}=".to_sym
|
97
|
-
iv = "@#{symbol}".to_sym
|
98
|
-
# the attribute reader creates a new proxy on demand
|
99
|
-
define_method(symbol) do
|
100
|
-
instance_variable_defined?(iv) ? instance_variable_get(iv) : send(wtr, yield(self))
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
# Enumerates each class in the hierarchy.
|
105
|
-
#
|
106
|
-
# @yield [klass] the enumeration block
|
107
|
-
# @yieldparam [Class] klass the class in the hierarchy
|
108
|
-
def each_class_in_hierarchy
|
109
|
-
current = self
|
110
|
-
until current.nil?
|
111
|
-
yield current
|
112
|
-
current = current.superclass
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
# Redefines method using the given block. The block argument is a new alias for the old method.
|
117
|
-
# The block creates a proc which implements the new method body.
|
118
|
-
#
|
119
|
-
# @example
|
120
|
-
# redefine_method(:ssn) { |omth| lambda { send(omth).delete('-').to_i } }
|
121
|
-
# @param [Symbol] method the method to redefine
|
122
|
-
# @yield [old_method] the redefinition Proc
|
123
|
-
# @yieldparam old_method [Symbol] the method being redefined
|
124
|
-
# @return [Symbol] an alias to the old method implementation
|
125
|
-
def redefine_method(method)
|
126
|
-
# make a new alias id method__base for the existing method.
|
127
|
-
# disambiguate with a counter suffix if necessary.
|
128
|
-
counter = 2
|
129
|
-
# make a valid alias base
|
130
|
-
old, eq = /^([^=]*)(=)?$/.match(method.to_s).captures
|
131
|
-
old.tr!('|', 'or')
|
132
|
-
old.tr!('&', 'and')
|
133
|
-
old.tr!('+', 'plus')
|
134
|
-
old.tr!('*', 'mult')
|
135
|
-
old.tr!('/', 'div')
|
136
|
-
old.gsub!(/[^\w]/, 'op')
|
137
|
-
base = "redefined__#{old}"
|
138
|
-
old_id = "#{base}#{eq}".to_sym
|
139
|
-
while method_defined?(old_id)
|
140
|
-
base = "#{base}#{counter}"
|
141
|
-
old_id = "#{base}#{eq}".to_sym
|
142
|
-
counter = counter + 1
|
143
|
-
end
|
144
|
-
alias_method(old_id, method)
|
145
|
-
body = yield old_id
|
146
|
-
define_method(method, body)
|
147
|
-
old_id
|
148
|
-
end
|
149
|
-
end
|
@@ -1,1152 +0,0 @@
|
|
1
|
-
require 'set'
|
2
|
-
require 'delegate'
|
3
|
-
require 'enumerator'
|
4
|
-
require 'caruby/util/class'
|
5
|
-
require 'caruby/util/validation'
|
6
|
-
require 'caruby/util/options'
|
7
|
-
require 'caruby/util/pretty_print'
|
8
|
-
|
9
|
-
class Object
|
10
|
-
# Returns whether this object is a collection capable of holding heterogenous items.
|
11
|
-
# An Object is a not a collection by default. Subclasses can override this method.
|
12
|
-
def collection?
|
13
|
-
false
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
module Enumerable
|
18
|
-
# Overrides {Object#collection?} to returns +true+, since an Enumerable is capable of
|
19
|
-
# holding heterogenous items by default. Subclasses can override this method.
|
20
|
-
def collection?
|
21
|
-
true
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
class String
|
26
|
-
# Overrides {Enumerable#collection?} to returns +false+, since a String is constrained
|
27
|
-
# to hold characters.
|
28
|
-
def collection?
|
29
|
-
false
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
module Enumerable
|
34
|
-
# Returns a new Hash generated from this Enumerable and an optional value generator block.
|
35
|
-
# This Enumerable contains the Hash keys. If the value generator block is given to this
|
36
|
-
# method then the block is called with each enumerated element as an argument to
|
37
|
-
# generate the associated hash value. If no block is given, then the values are nil.
|
38
|
-
#
|
39
|
-
# @example
|
40
|
-
# [1, 2, 3].hashify { |item| item.modulo(2) } #=> { 1 => 1, 2 => 0, 3 => 1 }
|
41
|
-
# [:a].hashify #=> { :a => nil }
|
42
|
-
# @return [Hash]
|
43
|
-
def hashify
|
44
|
-
hash = {}
|
45
|
-
each { |item| hash[item] = yield item if block_given? }
|
46
|
-
hash
|
47
|
-
end
|
48
|
-
|
49
|
-
# Returns a new Hash generated from this Enumerable and a required value generator block.
|
50
|
-
# This Enumerable contains the Hash keys. The block is called with each enumerated
|
51
|
-
# element as an argument to generate the associated hash value.
|
52
|
-
# Only non-nil, non-empty values are included in the hash.
|
53
|
-
#
|
54
|
-
# @example
|
55
|
-
# [1, 2, 3].to_compact_hash { |item| item.modulo(2) } #=> { 1 => 1, 2 => 0, 3 => 1 }
|
56
|
-
# [1, 2, 3].to_compact_hash { |n| n.modulo(2) unless item > 2 } #=> {1 => 1, 2 => 0}
|
57
|
-
# [1, 2, 3].to_compact_hash { |n| n > 2 } #=> {1 => false, 2 => false, 3 => true}
|
58
|
-
# [1, 2, 3].to_compact_hash { |n| Array.new(n - 1, n) } #=> {2 => [2], 3 => [2, 3]}
|
59
|
-
# @return [Hash]
|
60
|
-
# @raise [ArgumentError] if the generator block is not given
|
61
|
-
# @see #hashify
|
62
|
-
def to_compact_hash
|
63
|
-
raise ArgumentError.new("Compact hash builder is missing the value generator block") unless block_given?
|
64
|
-
to_compact_hash_with_index { |item, index| yield item }
|
65
|
-
end
|
66
|
-
|
67
|
-
# Returns a new Hash generated from this Enumerable with a block whose arguments include the enumerated item
|
68
|
-
# and its index. Every value which is nil or empty is excluded.
|
69
|
-
#
|
70
|
-
# @example
|
71
|
-
# [1, 2, 3].to_compact_hash_with_index { |item, index| item + index } #=> { 1 => 1, 2 => 3, 3 => 5 }
|
72
|
-
# @yield [item, index] the hash value
|
73
|
-
# @yieldparam item the enumerated value
|
74
|
-
# @yieldparam index the enumeration index
|
75
|
-
# @return [Hash] this {Enumerable} converted to a hash by the given block
|
76
|
-
def to_compact_hash_with_index
|
77
|
-
hash = {}
|
78
|
-
self.each_with_index do |item, index|
|
79
|
-
next if item.nil?
|
80
|
-
value = yield(item, index)
|
81
|
-
next if value.nil_or_empty?
|
82
|
-
hash[item] = value
|
83
|
-
end
|
84
|
-
hash
|
85
|
-
end
|
86
|
-
|
87
|
-
# This method is functionally equivalent to +to_a.empty+ but is more concise and efficient.
|
88
|
-
#
|
89
|
-
# @return [Boolean] whether this Enumerable iterates over at least one item
|
90
|
-
def empty?
|
91
|
-
not any? { true }
|
92
|
-
end
|
93
|
-
|
94
|
-
# This method is functionally equivalent to +to_a.first+ but is more concise and efficient.
|
95
|
-
#
|
96
|
-
# @return the first enumerated item in this Enumerable, or nil if this Enumerable is empty
|
97
|
-
def first
|
98
|
-
detect { true }
|
99
|
-
end
|
100
|
-
|
101
|
-
# This method is functionally equivalent to +to_a.last+ but is more concise and efficient.
|
102
|
-
#
|
103
|
-
# @return the last enumerated item in this Enumerable, or nil if this Enumerable is empty
|
104
|
-
def last
|
105
|
-
detect { true }
|
106
|
-
end
|
107
|
-
|
108
|
-
# This method is functionally equivalent to +to_a.size+ but is more concise and efficient
|
109
|
-
# for an Enumerable which does not implement the {#size} method.
|
110
|
-
#
|
111
|
-
# @return [Integer] the count of items enumerated in this Enumerable
|
112
|
-
def size
|
113
|
-
inject(0) { |size, item| size + 1 }
|
114
|
-
end
|
115
|
-
|
116
|
-
alias :length :size
|
117
|
-
|
118
|
-
# @return [String] the content of this Enumerable as a series using {Array#to_series}
|
119
|
-
def to_series(conjunction=nil)
|
120
|
-
to_a.to_series
|
121
|
-
end
|
122
|
-
|
123
|
-
# Returns the first non-nil, non-false enumerated value resulting from a call to the block given to this method,
|
124
|
-
# or nil if no value detected.
|
125
|
-
#
|
126
|
-
# @example
|
127
|
-
# [1, 2].detect_value { |item| item / 2 if item % 2 == 0 } #=> 1
|
128
|
-
# @return [Object] the detected block result
|
129
|
-
# @see #detect_with_value
|
130
|
-
def detect_value
|
131
|
-
each do |*item|
|
132
|
-
value = yield(*item)
|
133
|
-
return value if value
|
134
|
-
end
|
135
|
-
nil
|
136
|
-
end
|
137
|
-
|
138
|
-
# Returns the first item and value for which an enumeration on the block given to this method returns
|
139
|
-
# a non-nil, non-false value.
|
140
|
-
#
|
141
|
-
# @example
|
142
|
-
# [1, 2].detect_with_value { |item| item / 2 if item % 2 == 0 } #=> [2, 1]
|
143
|
-
# @return [(Object, Object)] the detected [item, value] pair
|
144
|
-
# @see #detect_value
|
145
|
-
def detect_with_value
|
146
|
-
value = nil
|
147
|
-
match = detect do |*item|
|
148
|
-
value = yield(*item)
|
149
|
-
end
|
150
|
-
[match, value]
|
151
|
-
end
|
152
|
-
|
153
|
-
# Returns a new Enumerable that iterates over the base Enumerable items for which filter evaluates to a non-nil,
|
154
|
-
# non-false value, e.g.:
|
155
|
-
# [1, 2, 3].filter { |n| n != 2 }.to_a #=> [1, 3]
|
156
|
-
#
|
157
|
-
# Unlike select, filter reflects changes to the base Enumerable, e.g.:
|
158
|
-
# a = [1, 2, 3]
|
159
|
-
# filter = a.filter { |n| n != 2 }
|
160
|
-
# a << 4
|
161
|
-
# filter.to_a #=> [1, 3, 4]
|
162
|
-
#
|
163
|
-
# In addition, filter has a small, fixed storage requirement, making it preferable to select for large collections.
|
164
|
-
# Note, however, that unlike select, filter does not return an Array.
|
165
|
-
# The default filter block returns the passed item.
|
166
|
-
#
|
167
|
-
# @example
|
168
|
-
# [1, nil, 3].filter.to_a #=> [1, 3]
|
169
|
-
# @yield [item] filter the selection filter
|
170
|
-
# @yieldparam item the collection member to filter
|
171
|
-
# @return [Enumerable] the filtered result
|
172
|
-
def filter(&filter)
|
173
|
-
Filter.new(self, &filter)
|
174
|
-
end
|
175
|
-
|
176
|
-
# @return an Enumerable which iterates over the non-nil items in this Enumerable
|
177
|
-
def compact
|
178
|
-
filter { |item| not item.nil? }
|
179
|
-
end
|
180
|
-
|
181
|
-
# Returns a new Flattener on this Enumerable, e.g.:
|
182
|
-
# {:a => {:b => :c}, :d => [:e]}.enum_values.flatten.to_a #=> [:b, :c, :e]
|
183
|
-
#
|
184
|
-
# @return [Enumerable] the flattened result
|
185
|
-
def flatten
|
186
|
-
Flattener.new(self).to_a
|
187
|
-
end
|
188
|
-
|
189
|
-
# Returns an Enumerable which iterates over items in this Enumerable and the other Enumerable in sequence, e.g.:
|
190
|
-
# [1, 2, 3] + [3, 4] #=> [1, 2, 3, 3, 4]
|
191
|
-
#
|
192
|
-
# Unlike the Array plus (+) operator, {#union} reflects changes to the underlying enumerators.
|
193
|
-
#
|
194
|
-
# @example
|
195
|
-
# a = [1, 2]
|
196
|
-
# b = [4, 5]
|
197
|
-
# ab = a.union(b)
|
198
|
-
# ab #=> [1, 2, 4, 5]
|
199
|
-
# a << 3
|
200
|
-
# ab #=> [1, 2, 3, 4, 5]
|
201
|
-
# @param [Enumerable] other the Enumerable to compose with this Enumerable
|
202
|
-
# @return [Enumerable] an enumerator over self followed by other
|
203
|
-
def union(other)
|
204
|
-
MultiEnumerator.new(self, other)
|
205
|
-
end
|
206
|
-
|
207
|
-
alias :+ :union
|
208
|
-
|
209
|
-
# @return an Enumerable which iterates over items in this Enumerable but not the other Enumerable
|
210
|
-
def difference(other)
|
211
|
-
filter { |item| not other.include?(item) }
|
212
|
-
end
|
213
|
-
|
214
|
-
alias :- :difference
|
215
|
-
|
216
|
-
# @return an Enumerable which iterates over items in this Enumerable which are also in the other Enumerable
|
217
|
-
def intersect(other)
|
218
|
-
filter { |item| other.include?(item) }
|
219
|
-
end
|
220
|
-
|
221
|
-
alias :& :intersect
|
222
|
-
|
223
|
-
# Returns a new Enumerable that iterates over the base Enumerable applying the transformer block to each item, e.g.:
|
224
|
-
# [1, 2, 3].transform { |n| n * 2 }.to_a #=> [2, 4, 6]
|
225
|
-
#
|
226
|
-
# Unlike Array.map, {#wrap} reflects changes to the base Enumerable, e.g.:
|
227
|
-
# a = [2, 4, 6]
|
228
|
-
``# transformed = a.wrap { |n| n * 2 }
|
229
|
-
# a << 4
|
230
|
-
# transformed.to_a #=> [2, 4, 6, 8]
|
231
|
-
#
|
232
|
-
# In addition, transform has a small, fixed storage requirement, making it preferable to select for large collections.
|
233
|
-
# Note, however, that unlike map, transform does not return an Array.
|
234
|
-
#
|
235
|
-
# @yield [item] the transformer on the enumerated items
|
236
|
-
# @yieldparam item an enumerated item
|
237
|
-
# @return [Enumerable] an enumeration on the transformed values
|
238
|
-
def transform(&mapper)
|
239
|
-
Transformer.new(self, &mapper)
|
240
|
-
end
|
241
|
-
|
242
|
-
alias :wrap :transform
|
243
|
-
|
244
|
-
def join(sep = $,)
|
245
|
-
to_a.join(sep)
|
246
|
-
end
|
247
|
-
|
248
|
-
# Sorts this collection's members with a partial sort operator, i.e. the comparison returns -1, 0, 1 or nil.
|
249
|
-
# The resulting sorted order places each non-nil comparable items in the sort order. The order of nil
|
250
|
-
# comparison items is indeterminate.
|
251
|
-
#
|
252
|
-
# #example
|
253
|
-
# [Array, Numeric, Enumerable, Set].partial_sort #=> [Array, Numeric, Set, Enumerable]
|
254
|
-
# @return [Enumerable] the items in this collection in partial sort order
|
255
|
-
def partial_sort
|
256
|
-
unless block_given? then return partial_sort { |item1, item2| item1 <=> item2 } end
|
257
|
-
sort { |item1, item2| yield(item1, item2) or 1 }
|
258
|
-
end
|
259
|
-
|
260
|
-
# Sorts this collection's members with a partial sort operator on the results of applying the block.
|
261
|
-
#
|
262
|
-
# @return [Enumerable] the items in this collection in partial sort order
|
263
|
-
def partial_sort_by
|
264
|
-
partial_sort { |item1, item2| yield(item1) <=> yield(item2) }
|
265
|
-
end
|
266
|
-
|
267
|
-
# @yield [item] the transformer on the enumerated items
|
268
|
-
# @yieldparam item an enumerated item
|
269
|
-
# @return [Enumerable] the mapped values excluding null values
|
270
|
-
def compact_map(&mapper)
|
271
|
-
wrap(&mapper).compact
|
272
|
-
end
|
273
|
-
|
274
|
-
private
|
275
|
-
|
276
|
-
# This Filter helper class applies a selection block to a base enumeration.
|
277
|
-
class Filter
|
278
|
-
include Enumerable
|
279
|
-
|
280
|
-
def initialize(enum=[], &filter)
|
281
|
-
@base = enum
|
282
|
-
@filter = filter
|
283
|
-
end
|
284
|
-
|
285
|
-
# Calls the given block on each item which passes this Filter's filter test.
|
286
|
-
#
|
287
|
-
# @yield [item] the block called on each item
|
288
|
-
# @yieldparam item the enumerated item
|
289
|
-
def each
|
290
|
-
@base.each { |item| yield(item) if @filter ? @filter.call(item) : item }
|
291
|
-
end
|
292
|
-
|
293
|
-
# Optimized for a Set base.
|
294
|
-
#
|
295
|
-
# @param [item] the item to check
|
296
|
-
# @return [Boolean] whether the item is a member of this Enumerable
|
297
|
-
def include?(item)
|
298
|
-
return false if Set === @base and not @base.include?(item)
|
299
|
-
super
|
300
|
-
end
|
301
|
-
|
302
|
-
# Adds an item to the base Enumerable, if this Filter's base supports it.
|
303
|
-
#
|
304
|
-
# @param item the item to add
|
305
|
-
# @return [Filter] self
|
306
|
-
def <<(item)
|
307
|
-
@base << item
|
308
|
-
self
|
309
|
-
end
|
310
|
-
|
311
|
-
# @param [Enumerable] other the Enumerable to merge
|
312
|
-
# @return [Array] this Filter's filtered content merged with the other Enumerable
|
313
|
-
def merge(other)
|
314
|
-
to_a.merge!(other)
|
315
|
-
end
|
316
|
-
|
317
|
-
# Merges the other Enumerable into the base Enumerable, if the base supports it.
|
318
|
-
#
|
319
|
-
# @param other (see #merge)
|
320
|
-
# @return [Filter, nil] this Filter's filtered content merged with the other Enumerable
|
321
|
-
def merge!(other)
|
322
|
-
@base.merge!(other)
|
323
|
-
self
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
# This Transformer helper class applies a transformer block to a base enumeration.
|
328
|
-
class Transformer
|
329
|
-
include Enumerable
|
330
|
-
|
331
|
-
def initialize(enum=[], &transformer)
|
332
|
-
@base = enum
|
333
|
-
@xfm = transformer
|
334
|
-
end
|
335
|
-
|
336
|
-
# Sets the base Enumerable on which this Transformer operates and returns this transformer, e.g.:
|
337
|
-
# transformer = Transformer.new { |n| n * 2 }
|
338
|
-
# transformer.on([1, 2, 3]).to_a #=> [2, 4, 6]
|
339
|
-
def on(enum)
|
340
|
-
@base = enum
|
341
|
-
self
|
342
|
-
end
|
343
|
-
|
344
|
-
# Calls the block on each item after this Transformer's transformer block is applied.
|
345
|
-
def each
|
346
|
-
@base.each { |item| yield(item.nil? ? nil : @xfm.call(item)) }
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
# A MultiEnumerator iterates over several Enumerators in sequence. Unlike Array#+, MultiEnumerator reflects changes to the
|
351
|
-
# underlying enumerators.
|
352
|
-
#
|
353
|
-
# @example
|
354
|
-
# a = [1, 2]
|
355
|
-
# b = [4, 5]
|
356
|
-
# ab = MultiEnumerator.new(a, b)
|
357
|
-
# ab.to_a #=> [1, 2, 4, 5]
|
358
|
-
# a << 3; b << 6; ab.to_a #=> [1, 2, 3, 4, 5, 6]
|
359
|
-
class MultiEnumerator
|
360
|
-
include Enumerable
|
361
|
-
|
362
|
-
# @return [<Enumerable>] the enumerated collections
|
363
|
-
attr_reader :components
|
364
|
-
|
365
|
-
# Initializes a new {MultiEnumerator} on the given components.
|
366
|
-
#
|
367
|
-
# @param [<Enumerable>] the component enumerators to compose
|
368
|
-
def initialize(*enums)
|
369
|
-
super()
|
370
|
-
@components = enums
|
371
|
-
@components.compact!
|
372
|
-
end
|
373
|
-
|
374
|
-
# Iterates over each of this MultiEnumerator's Enumerators in sequence.
|
375
|
-
def each
|
376
|
-
@components.each { |enum| enum.each { |item| yield item } }
|
377
|
-
end
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
|
-
# The Collector utility implements the {on} method to apply a block to a collection
|
382
|
-
# transitive closure.
|
383
|
-
module Collector
|
384
|
-
# Collects the result of applying the given block to the given obj.
|
385
|
-
# If obj is a collection, then collects the result of recursively calling this
|
386
|
-
# Collector on the enumerated members.
|
387
|
-
# If obj is nil, then returns nil.
|
388
|
-
# Otherwise, calls block on obj and returns the result.
|
389
|
-
#
|
390
|
-
# @example
|
391
|
-
# Collector.on([1, 2, [3, 4]]) { |n| n * 2 } #=> [2, 4, [6, 8]]]
|
392
|
-
# Collector.on(nil) { |n| n * 2 } #=> nil
|
393
|
-
# Collector.on(1) { |n| n * 2 } #=> 2
|
394
|
-
# @param obj the collection or item to enumerate
|
395
|
-
def self.on(obj, &block)
|
396
|
-
obj.collection? ? obj.map { |item| on(item, &block) } : yield(obj) unless obj.nil?
|
397
|
-
end
|
398
|
-
end
|
399
|
-
|
400
|
-
class Object
|
401
|
-
# Visits this object's enumerable content as follows:
|
402
|
-
# * If this object is an Enumerable, then the block given to this method is called on each
|
403
|
-
# item in this Enumerable.
|
404
|
-
# * Otherwise, if this object is non-nil, then the the block is called on self.
|
405
|
-
# * Otherwise, this object is nil and this method is a no-op.
|
406
|
-
#
|
407
|
-
# @yield [item] the block to apply to this object
|
408
|
-
# @yieldparam item the enumerated items, or this object if it is non-nil and not an Enumerable
|
409
|
-
def enumerate(&block)
|
410
|
-
Enumerable === self ? each(&block) : yield(self) unless nil?
|
411
|
-
end
|
412
|
-
|
413
|
-
# Returns an enumerator on this Object. This default implementation returns an Enumerable::Enumerator
|
414
|
-
# on enumerate.
|
415
|
-
#
|
416
|
-
# @return [Enumerable] this object as an enumerable item
|
417
|
-
def to_enum
|
418
|
-
Enumerable::Enumerator.new(self, :enumerate)
|
419
|
-
end
|
420
|
-
end
|
421
|
-
|
422
|
-
module Enumerable
|
423
|
-
# @return self
|
424
|
-
def to_enum
|
425
|
-
self
|
426
|
-
end
|
427
|
-
end
|
428
|
-
|
429
|
-
# A Flattener applies a given block to flattened collection content.
|
430
|
-
class Flattener
|
431
|
-
include Enumerable
|
432
|
-
|
433
|
-
# Visits the enumerated items in the given object's flattened content.
|
434
|
-
# block is called on the base itself if the base is neither nil nor a Enumerable.
|
435
|
-
# If the base object is nil or empty, then this method is a no-op and returns nil.
|
436
|
-
def self.on(obj, &block)
|
437
|
-
obj.collection? ? obj.each { |item| on(item, &block) } : yield(obj) unless obj.nil?
|
438
|
-
end
|
439
|
-
|
440
|
-
# Initializes a new Flattener on the given object.
|
441
|
-
#
|
442
|
-
# @param obj the Enumerable or non-collection object
|
443
|
-
def initialize(obj)
|
444
|
-
@base = obj
|
445
|
-
end
|
446
|
-
|
447
|
-
# Calls the the given block on this Flattener's flattened content.
|
448
|
-
# If the base object is a collection, then the block is called on the flattened content.
|
449
|
-
# If the base object is nil, then this method is a no-op.
|
450
|
-
# If the base object is neither nil nor a collection, then the block given to this method
|
451
|
-
# is called on the base object itself.
|
452
|
-
#
|
453
|
-
# @example
|
454
|
-
# Flattener.new(nil).each { |n| print n } #=>
|
455
|
-
# Flattener.new(1).each { |n| print n } #=> 1
|
456
|
-
# Flattener.new([1, [2, 3]]).each { |n| print n } #=> 123
|
457
|
-
def each(&block)
|
458
|
-
Flattener.on(@base, &block)
|
459
|
-
end
|
460
|
-
end
|
461
|
-
|
462
|
-
# ConditionalEnumerator applies a filter to another Enumerable.
|
463
|
-
# @example
|
464
|
-
# ConditionalEnumerator.new([1, 2, 3]) { |i| i < 3 }.to_a #=> [1, 2]
|
465
|
-
class ConditionalEnumerator
|
466
|
-
include Enumerable
|
467
|
-
|
468
|
-
# Creates a ConditionalEnumerator which wraps the base Enumerator with a conditional filter.
|
469
|
-
def initialize(base, &filter)
|
470
|
-
@base = base
|
471
|
-
@filter = filter
|
472
|
-
end
|
473
|
-
|
474
|
-
# Applies the iterator block to each of this ConditionalEnumerator's base Enumerable items
|
475
|
-
# for which this ConditionalEnumerator's filter returns true.
|
476
|
-
def each
|
477
|
-
@base.each { |item| (yield item) if @filter.call(item) }
|
478
|
-
end
|
479
|
-
end
|
480
|
-
|
481
|
-
# Hashable is a Hash mixin that adds utility methods to a Hash.
|
482
|
-
# Hashable can be included by any class or module which implements an _each_ method
|
483
|
-
# with arguments _key_ and _value_.
|
484
|
-
module Hashable
|
485
|
-
include Enumerable
|
486
|
-
|
487
|
-
# @see Hash#each_pair
|
488
|
-
def each_pair(&block)
|
489
|
-
each(&block)
|
490
|
-
end
|
491
|
-
|
492
|
-
# @see Hash#[]
|
493
|
-
def [](key)
|
494
|
-
detect_value { |k, v| v if k == key }
|
495
|
-
end
|
496
|
-
|
497
|
-
# @see Hash#each_key
|
498
|
-
def each_key
|
499
|
-
each { |key, value| yield key }
|
500
|
-
end
|
501
|
-
|
502
|
-
# @yield [key] the detector block
|
503
|
-
# @yieldparam key the hash key
|
504
|
-
# @return [Object, nil] the key for which the detector block returns a non-nil, non-false value,
|
505
|
-
# or nil if none
|
506
|
-
# @example
|
507
|
-
# {1 => :a, 2 => :b, 3 => :c}.detect_key { |k| k > 1 } #=> 2
|
508
|
-
def detect_key
|
509
|
-
each_key { |key| return key if yield key }
|
510
|
-
nil
|
511
|
-
end
|
512
|
-
|
513
|
-
# @yield [value] the detector block
|
514
|
-
# @yieldparam value the hash value
|
515
|
-
# @return [Object, nil] the key for which the detector block returns a non-nil, non-false value,
|
516
|
-
# or nil if none
|
517
|
-
# @example
|
518
|
-
# {:a => 1, :b => 2, :c => 3}.detect_key_with_value { |v| v > 1 } #=> :b
|
519
|
-
def detect_key_with_value
|
520
|
-
each { |key, value| return key if yield value }
|
521
|
-
nil
|
522
|
-
end
|
523
|
-
|
524
|
-
# @see Hash#each_value
|
525
|
-
def each_value
|
526
|
-
each { |key, value| yield value }
|
527
|
-
end
|
528
|
-
|
529
|
-
# Returns a Hashable which composes each value in this Hashable with the key of
|
530
|
-
# the other Hashable, e.g.:
|
531
|
-
# x = {:a => :c, :b => :d}
|
532
|
-
# y = {:c => 1}
|
533
|
-
# z = x.compose(y)
|
534
|
-
# z[:a] #=> {:c => 1}
|
535
|
-
# z[:b] #=> nil
|
536
|
-
#
|
537
|
-
# The accessor reflects changes to the underlying hashes, e.g. given the above example:
|
538
|
-
# x[:b] = 2
|
539
|
-
# z[:b] #=> {:c => 1}
|
540
|
-
#
|
541
|
-
# Update operations on the result are not supported.
|
542
|
-
#
|
543
|
-
# @param [Hashable] other the Hashable to compose with this Hashable
|
544
|
-
# @return [Hashable] the composed result
|
545
|
-
def compose(other)
|
546
|
-
transform { |value| {value => other[value]} if other.has_key?(value) }
|
547
|
-
end
|
548
|
-
|
549
|
-
# Returns a Hashable which joins each value in this Hashable with the key of
|
550
|
-
# the other Hashable, e.g.:
|
551
|
-
# x = {:a => :c, :b => :d}
|
552
|
-
# y = {:c => 1}
|
553
|
-
# z = x.join(y)
|
554
|
-
# z[:a] #=> 1
|
555
|
-
# z[:b] #=> nil
|
556
|
-
#
|
557
|
-
# The accessor reflects changes to the underlying hashes, e.g. given the above example:
|
558
|
-
# x[:b] = 2
|
559
|
-
# z[:b] #=> 2
|
560
|
-
#
|
561
|
-
# Update operations on the result are not supported.
|
562
|
-
#
|
563
|
-
# @param [Hashable] other the Hashable to join with this Hashable
|
564
|
-
# @return [Hashable] the joined result
|
565
|
-
def join(other)
|
566
|
-
transform { |value| other[value] }
|
567
|
-
end
|
568
|
-
|
569
|
-
# Returns a Hashable which associates each key of both this Hashable and the other Hashable
|
570
|
-
# with the corresponding value in the first Hashable which has that key, e.g.:
|
571
|
-
# x = {:a => 1, :b => 2}
|
572
|
-
# y = {:b => 3, :c => 4}
|
573
|
-
# z = x + y
|
574
|
-
# z[:b] #=> 2
|
575
|
-
#
|
576
|
-
# The accessor reflects changes to the underlying hashes, e.g. given the above example:
|
577
|
-
# x.delete(:b)
|
578
|
-
# z[:b] #=> 3
|
579
|
-
#
|
580
|
-
# Update operations on the result are not supported.
|
581
|
-
#
|
582
|
-
# @param [Hashable] other the Hashable to form a union with this Hashable
|
583
|
-
# @return [Hashable] the union result
|
584
|
-
def union(other)
|
585
|
-
MultiHash.new(self, other)
|
586
|
-
end
|
587
|
-
|
588
|
-
alias :+ :union
|
589
|
-
|
590
|
-
# Returns a new Hashable that iterates over the base Hashable <key, value> pairs for which the block
|
591
|
-
# given to this method evaluates to a non-nil, non-false value, e.g.:
|
592
|
-
# {:a => 1, :b => 2, :c => 3}.filter { |k, v| k != :b }.to_hash #=> {:a => 1, :c => 3}
|
593
|
-
#
|
594
|
-
# The default filter block tests the value, e.g.:
|
595
|
-
# {:a => 1, :b => nil}.filter.to_hash #=> {:a => 1}
|
596
|
-
#
|
597
|
-
# @yield [key, value] the filter block
|
598
|
-
# @return [Hashable] the filtered result
|
599
|
-
def filter(&block)
|
600
|
-
Filter.new(self, &block)
|
601
|
-
end
|
602
|
-
|
603
|
-
# Optimization of {#filter} for a block that only uses the key.
|
604
|
-
#
|
605
|
-
# @example
|
606
|
-
# {:a => 1, :b => 2, :c => 3}.filter_on_key { |k| k != :b }.to_hash #=> {:a => 1, :c => 3}
|
607
|
-
#
|
608
|
-
# @yield [key] the filter block
|
609
|
-
# @yieldparam key the hash key to filter
|
610
|
-
# @return [Hashable] the filtered result
|
611
|
-
def filter_on_key(&block)
|
612
|
-
KeyFilter.new(self, &block)
|
613
|
-
end
|
614
|
-
|
615
|
-
# @return [Hashable] a {#filter} that only uses the value.
|
616
|
-
# @yield [value] the filter block
|
617
|
-
# @yieldparam value the hash value to filter
|
618
|
-
# @return [Hashable] the filtered result
|
619
|
-
def filter_on_value
|
620
|
-
filter { |key, value| yield value }
|
621
|
-
end
|
622
|
-
|
623
|
-
# @return [Hash] a {#filter} of this Hashable which excludes the entries with a null value
|
624
|
-
def compact
|
625
|
-
filter_on_value { |value| not value.nil? }
|
626
|
-
end
|
627
|
-
|
628
|
-
# Returns the difference between this Hashable and the other Hashable in a Hash of the form:
|
629
|
-
#
|
630
|
-
# _key_ => [_mine_, _theirs_]
|
631
|
-
#
|
632
|
-
# where:
|
633
|
-
# * _key_ is the key of association which differs
|
634
|
-
# * _mine_ is the value for _key_ in this hash
|
635
|
-
# * _theirs_ is the value for _key_ in the other hash
|
636
|
-
#
|
637
|
-
# @param [Hashable] other the Hashable to subtract
|
638
|
-
# @yield [key, v1, v2] the optional block which determines whether values differ (default is equality)
|
639
|
-
# @yieldparam key the key for which values are compared
|
640
|
-
# @yieldparam v1 the value for key from this Hashable
|
641
|
-
# @yieldparam v2 the value for key from the other Hashable
|
642
|
-
# @return [{Object => (Object,Object)}] a hash of the differences
|
643
|
-
def diff(other)
|
644
|
-
(keys.to_set + other.keys).to_compact_hash do |key|
|
645
|
-
mine = self[key]
|
646
|
-
yours = other[key]
|
647
|
-
[mine, yours] unless block_given? ? yield(key, mine, yours) : mine == yours
|
648
|
-
end
|
649
|
-
end
|
650
|
-
|
651
|
-
# @yield [key1, key2] the key sort block
|
652
|
-
# @return a Hashable whose #each and {#each_pair} enumerations are sorted by key
|
653
|
-
def sort(&sorter)
|
654
|
-
SortedHash.new(self, &sorter)
|
655
|
-
end
|
656
|
-
|
657
|
-
# Returns a hash which associates each key in this hash with the value mapped by the others.
|
658
|
-
#
|
659
|
-
# @example
|
660
|
-
# {:a => 1, :b => 2}.assoc_values({:a => 3, :c => 4}) #=> {:a => [1, 3], :b => [2, nil], :c => [nil, 4]}
|
661
|
-
# {:a => 1, :b => 2}.assoc_values({:a => 3}, {:a => 4, :b => 5}) #=> {:a => [1, 3, 4], :b => [2, nil, 5]}
|
662
|
-
#
|
663
|
-
# @param [<Hashable>] others the other Hashables to associate with this Hashable
|
664
|
-
# @return [Hash] the association hash
|
665
|
-
def assoc_values(*others)
|
666
|
-
all_keys = keys
|
667
|
-
others.each { |hash| all_keys.concat(hash.keys) }
|
668
|
-
all_keys.to_compact_hash do |key|
|
669
|
-
others.map { |other| other[key] }.unshift(self[key])
|
670
|
-
end
|
671
|
-
end
|
672
|
-
|
673
|
-
# Returns an Enumerable whose each block is called on each key which maps to a value which
|
674
|
-
# either equals the given target_value or satisfies the filter block.
|
675
|
-
#
|
676
|
-
# @param target_value the filter value
|
677
|
-
# @yield [value] the filter block
|
678
|
-
# @return [Enumerable] the filtered keys
|
679
|
-
def enum_keys_with_value(target_value=nil, &filter) # :yields: value
|
680
|
-
return enum_keys_with_value { |value| value == target_value } if target_value
|
681
|
-
filter_on_value(&filter).keys
|
682
|
-
end
|
683
|
-
|
684
|
-
# @return [Enumerable] Enumerable over this Hashable's keys
|
685
|
-
def enum_keys
|
686
|
-
Enumerable::Enumerator.new(self, :each_key)
|
687
|
-
end
|
688
|
-
|
689
|
-
# @return [Array] this Hashable's keys
|
690
|
-
def keys
|
691
|
-
enum_keys.to_a
|
692
|
-
end
|
693
|
-
|
694
|
-
# @param key search target
|
695
|
-
# @return whether this Hashable has the given key
|
696
|
-
def has_key?(key)
|
697
|
-
enum_keys.include?(key)
|
698
|
-
end
|
699
|
-
|
700
|
-
alias :include? :has_key?
|
701
|
-
|
702
|
-
# @return [Enumerable] an Enumerable over this Hashable's values
|
703
|
-
def enum_values
|
704
|
-
Enumerable::Enumerator.new(self, :each_value)
|
705
|
-
end
|
706
|
-
|
707
|
-
# @yield [key] the key selector
|
708
|
-
# @return the keys which satisfy the block given to this method
|
709
|
-
def select_keys(&block)
|
710
|
-
enum_keys.select(&block)
|
711
|
-
end
|
712
|
-
|
713
|
-
# @yield [key] the key rejector
|
714
|
-
# @return the keys which do not satisfy the block given to this method
|
715
|
-
def reject_keys(&block)
|
716
|
-
enum_keys.reject(&block)
|
717
|
-
end
|
718
|
-
|
719
|
-
# @yield [value] the value selector
|
720
|
-
# @return the values which satisfy the block given to this method
|
721
|
-
def select_values(&block)
|
722
|
-
enum_values.select(&block)
|
723
|
-
end
|
724
|
-
|
725
|
-
# @yield [value] the value rejector
|
726
|
-
# @return the values which do not satisfy the block given to this method
|
727
|
-
def reject_values(&block)
|
728
|
-
enum_values.reject(&block)
|
729
|
-
end
|
730
|
-
|
731
|
-
# @return [Array] this Enumerable's values
|
732
|
-
def values
|
733
|
-
enum_values.to_a
|
734
|
-
end
|
735
|
-
|
736
|
-
# @param value search target
|
737
|
-
# @return whether this Hashable has the given value
|
738
|
-
def has_value?(value)
|
739
|
-
enum_values.include?(value)
|
740
|
-
end
|
741
|
-
|
742
|
-
# @return [Array] a flattened Array of this Hash
|
743
|
-
# @example
|
744
|
-
# {:a => {:b => :c}, :d => :e, :f => [:g]} #=> [:a, :b, :c, :d, :e, :f, :g]
|
745
|
-
def flatten
|
746
|
-
Flattener.new(self).to_a
|
747
|
-
end
|
748
|
-
|
749
|
-
# @yield [key, value] hash splitter
|
750
|
-
# @return [(Hash, Hash)] two hashes split by whether calling the block on the
|
751
|
-
# entry returns a non-nil, non-false value
|
752
|
-
# @example
|
753
|
-
# {:a => 1, :b => 2}.split { |key, value| value < 2 } #=> [{:a => 1}, {:b => 2}]
|
754
|
-
def split(&block)
|
755
|
-
partition(&block).map { |pairs| pairs.to_assoc_hash }
|
756
|
-
end
|
757
|
-
|
758
|
-
# Returns a new Hash that recursively copies this hash's values. Values of type hash are copied using copy_recursive.
|
759
|
-
# Other values are unchanged.
|
760
|
-
#
|
761
|
-
# This method is useful for preserving and restoring hash associations.
|
762
|
-
#
|
763
|
-
# @return [Hash] a deep copy of this Hashable
|
764
|
-
def copy_recursive
|
765
|
-
copy = Hash.new
|
766
|
-
keys.each do |key|
|
767
|
-
value = self[key]
|
768
|
-
copy[key] = Hash === value ? value.copy_recursive : value
|
769
|
-
end
|
770
|
-
copy
|
771
|
-
end
|
772
|
-
|
773
|
-
# @return [Hash] a new Hash that transforms each value
|
774
|
-
# @example
|
775
|
-
# {:a => 1, :b => 2}.transform { |n| n * 2 }.values #=> [2, 4]
|
776
|
-
def transform(&transformer)
|
777
|
-
ValueTransformerHash.new(self, &transformer)
|
778
|
-
end
|
779
|
-
|
780
|
-
def to_hash
|
781
|
-
inject({}) { |hash, pair| hash[pair.first] = pair.last; hash }
|
782
|
-
end
|
783
|
-
|
784
|
-
def to_set
|
785
|
-
to_a.to_set
|
786
|
-
end
|
787
|
-
|
788
|
-
def to_s
|
789
|
-
to_hash.to_s
|
790
|
-
end
|
791
|
-
|
792
|
-
def inspect
|
793
|
-
to_hash.inspect
|
794
|
-
end
|
795
|
-
|
796
|
-
def ==(other)
|
797
|
-
to_hash == other.to_hash rescue super
|
798
|
-
end
|
799
|
-
|
800
|
-
private
|
801
|
-
|
802
|
-
# @see #filter
|
803
|
-
class Filter
|
804
|
-
include Hashable
|
805
|
-
|
806
|
-
def initialize(base, &filter)
|
807
|
-
@base = base
|
808
|
-
@filter = filter
|
809
|
-
end
|
810
|
-
|
811
|
-
def each
|
812
|
-
@base.each { |k, v| yield(k, v) if @filter ? @filter.call(k, v) : v }
|
813
|
-
end
|
814
|
-
end
|
815
|
-
|
816
|
-
# @see #filter_on_key
|
817
|
-
class KeyFilter < Filter
|
818
|
-
include Hashable
|
819
|
-
|
820
|
-
def initialize(base)
|
821
|
-
super(base) { |k, v| yield(k) }
|
822
|
-
end
|
823
|
-
|
824
|
-
def [](key)
|
825
|
-
super if @filter.call(key, nil)
|
826
|
-
end
|
827
|
-
end
|
828
|
-
|
829
|
-
# @see #sort
|
830
|
-
class SortedHash
|
831
|
-
include Hashable
|
832
|
-
|
833
|
-
def initialize(base, &comparator)
|
834
|
-
@base = base
|
835
|
-
@comparator = comparator
|
836
|
-
end
|
837
|
-
|
838
|
-
def each
|
839
|
-
@base.keys.sort { |k1, k2| @comparator ? @comparator.call(k1, k2) : k1 <=> k2 }.each { |k| yield(k, @base[k]) }
|
840
|
-
end
|
841
|
-
end
|
842
|
-
|
843
|
-
# Combines hashes. See Hash#+ for details.
|
844
|
-
class MultiHash
|
845
|
-
include Hashable
|
846
|
-
|
847
|
-
# @return [<Hashable>] the enumerated hashes
|
848
|
-
attr_reader :components
|
849
|
-
|
850
|
-
def initialize(*hashes)
|
851
|
-
if hashes.include?(nil) then raise ArgumentError.new("MultiHash is missing a component hash.") end
|
852
|
-
@components = hashes
|
853
|
-
end
|
854
|
-
|
855
|
-
def [](key)
|
856
|
-
@components.each { |hash| return hash[key] if hash.has_key?(key) }
|
857
|
-
nil
|
858
|
-
end
|
859
|
-
|
860
|
-
def has_key?(key)
|
861
|
-
@components.any? { |hash| hash.has_key?(key) }
|
862
|
-
end
|
863
|
-
|
864
|
-
def has_value?(value)
|
865
|
-
@components.any? { |hash| hash.has_value?(value) }
|
866
|
-
end
|
867
|
-
|
868
|
-
def each
|
869
|
-
@components.each_with_index do |hash, index|
|
870
|
-
hash.each do |key, value|
|
871
|
-
yield(key, value) unless (0...index).any? { |i| @components[i].has_key?(key) }
|
872
|
-
end
|
873
|
-
end
|
874
|
-
self
|
875
|
-
end
|
876
|
-
end
|
877
|
-
|
878
|
-
# The ValueTransformerHash class pipes the value from a base Hashable into a transformer block.
|
879
|
-
class ValueTransformerHash
|
880
|
-
include Hashable
|
881
|
-
|
882
|
-
# Creates a ValueTransformerHash on the base hash and value transformer block.
|
883
|
-
def initialize(base, &transformer) # :yields: value
|
884
|
-
@base = base
|
885
|
-
@xfm = transformer
|
886
|
-
end
|
887
|
-
|
888
|
-
# Returns the value at key after this ValueTransformerHash's transformer block is applied, or nil
|
889
|
-
# if this hash does not contain key.
|
890
|
-
def [](key)
|
891
|
-
@xfm.call(@base[key]) if @base.has_key?(key)
|
892
|
-
end
|
893
|
-
|
894
|
-
def each
|
895
|
-
@base.each { |key, value| yield(key, @xfm.call(value)) }
|
896
|
-
end
|
897
|
-
end
|
898
|
-
end
|
899
|
-
|
900
|
-
# The KeyTransformerHash class pipes the key access argument into a transformer block before
|
901
|
-
# accessing a base Hashable, e.g.:
|
902
|
-
# hash = KeyTransformerHash.new { |key| key % 2 }
|
903
|
-
# hash[1] = :a
|
904
|
-
# hash[3] # => :a
|
905
|
-
class KeyTransformerHash
|
906
|
-
include Hashable
|
907
|
-
|
908
|
-
# Creates a KeyTransformerHash on the optional base hash and required key transformer block.
|
909
|
-
#
|
910
|
-
# Raises ArgumentError if there is no extractor block
|
911
|
-
def initialize(base={}, &transformer) # :yields: key
|
912
|
-
raise ArgumentError.new("Missing required Accessor block") unless block_given?
|
913
|
-
@base = base
|
914
|
-
@xfm = transformer
|
915
|
-
end
|
916
|
-
|
917
|
-
# Returns the value at key after this KeyTransformerHash's transformer block is applied to the key,
|
918
|
-
# or nil if the base hash does not contain an association for the transforemd key.
|
919
|
-
def [](key)
|
920
|
-
@base[@xfm.call(key)]
|
921
|
-
end
|
922
|
-
|
923
|
-
# Sets the value at key after this KeyTransformerHash's transformer block is applied, or nil
|
924
|
-
# if this hash does not contain an association for the transformed key.
|
925
|
-
def []=(key, value)
|
926
|
-
@base[@xfm.call(key)] = value
|
927
|
-
end
|
928
|
-
|
929
|
-
# Delegates to the base hash.
|
930
|
-
# Note that this breaks the standard Hash contract, since
|
931
|
-
# all? { |k, v| self[k] }
|
932
|
-
# is not necessarily true because the key is transformed on access.
|
933
|
-
# @see Accessor for a KeyTransformerHash variant that restores this contract
|
934
|
-
def each(&block)
|
935
|
-
@base.each(&block)
|
936
|
-
end
|
937
|
-
end
|
938
|
-
|
939
|
-
class Hash
|
940
|
-
include Hashable
|
941
|
-
|
942
|
-
# The EMPTY_HASH constant is an immutable empty hash, used primarily as a default argument.
|
943
|
-
class << EMPTY_HASH = Hash.new
|
944
|
-
def []=(key, value)
|
945
|
-
raise NotImplementedError.new("Modification of the constant empty hash is not supported")
|
946
|
-
end
|
947
|
-
end
|
948
|
-
end
|
949
|
-
|
950
|
-
# Hashinator creates a Hashable from an Enumerable on [_key_, _value_] pairs.
|
951
|
-
# The Hashinator reflects changes to the underlying Enumerable.
|
952
|
-
#
|
953
|
-
# @example
|
954
|
-
# base = [[:a, 1], [:b, 2]]
|
955
|
-
# hash = Hashinator.new(base)
|
956
|
-
# hash[:a] #=> 1
|
957
|
-
# base.first[1] = 3
|
958
|
-
# hash[:a] #=> 3
|
959
|
-
class Hashinator
|
960
|
-
include Hashable
|
961
|
-
|
962
|
-
def initialize(enum)
|
963
|
-
@base = enum
|
964
|
-
end
|
965
|
-
|
966
|
-
def each
|
967
|
-
@base.each { |pair| yield(*pair) }
|
968
|
-
end
|
969
|
-
end
|
970
|
-
|
971
|
-
#
|
972
|
-
# A Hash that creates a new entry on demand.
|
973
|
-
#
|
974
|
-
class LazyHash < Hash
|
975
|
-
#
|
976
|
-
# Creates a new Hash with the specified value factory proc.
|
977
|
-
# The factory proc has one argument, the key.
|
978
|
-
# If access by key fails, then a new association is created
|
979
|
-
# from the key to the result of calling the factory proc.
|
980
|
-
#
|
981
|
-
# Example:
|
982
|
-
# hash = LazyHash.new { |key| key.to_s }
|
983
|
-
# hash[1] = "1"
|
984
|
-
# hash[1] #=> "1"
|
985
|
-
# hash[2] #=> "2"
|
986
|
-
#
|
987
|
-
# If a block is not provided, then the default association value is nil, e.g.:
|
988
|
-
# hash = LazyHash.new
|
989
|
-
# hash.has_key?(1) #=> false
|
990
|
-
# hash[1] #=> nil
|
991
|
-
# hash.has_key?(1) #=> true
|
992
|
-
#
|
993
|
-
# A nil key always returns nil. There is no hash entry for nil, e.g.:
|
994
|
-
# hash = LazyHash.new { |key| key }
|
995
|
-
# hash[nil] #=> nil
|
996
|
-
# hash.has_key?(nil) #=> false
|
997
|
-
#
|
998
|
-
# If the :compact option is set, then an entry is not created
|
999
|
-
# if the value initializer result is nil or empty, e.g.:
|
1000
|
-
# hash = LazyHash.new { |n| 10.div(n) unless n.zero? }
|
1001
|
-
# hash[0] #=> nil
|
1002
|
-
# hash.has_key?(0) #=> false
|
1003
|
-
def initialize(options=nil)
|
1004
|
-
reject_flag = Options.get(:compact, options)
|
1005
|
-
# Make the hash with the factory block
|
1006
|
-
super() do |hash, key|
|
1007
|
-
if key then
|
1008
|
-
value = yield key if block_given?
|
1009
|
-
hash[key] = value unless reject_flag and value.nil_or_empty?
|
1010
|
-
end
|
1011
|
-
end
|
1012
|
-
end
|
1013
|
-
end
|
1014
|
-
|
1015
|
-
class Array
|
1016
|
-
# The EMPTY_ARRAY constant is an immutable empty array, used primarily as a default argument.
|
1017
|
-
class << EMPTY_ARRAY = Array.new
|
1018
|
-
def <<(value)
|
1019
|
-
raise NotImplementedError.new("Modification of the constant empty array is not supported")
|
1020
|
-
end
|
1021
|
-
end
|
1022
|
-
|
1023
|
-
# Relaxes the Ruby Array methods which take an Array argument to allow collection Enumerable arguments.
|
1024
|
-
[:|, :+, :-, :&].each do |meth|
|
1025
|
-
redefine_method(meth) do |old_meth|
|
1026
|
-
lambda { |other| send(old_meth, other.collection? ? other.to_a : other) }
|
1027
|
-
end
|
1028
|
-
end
|
1029
|
-
|
1030
|
-
redefine_method(:flatten) do |old_meth|
|
1031
|
-
# if an item is a non-Array collection, then convert it into an array before recursively flattening the list
|
1032
|
-
lambda { map { |item| item.collection? ? item.to_a : item }.send(old_meth) }
|
1033
|
-
end
|
1034
|
-
|
1035
|
-
# Returns an array containing all but the first item in this Array. This method is syntactic sugar for
|
1036
|
-
# +self[1..-1]+ or +last(length-1)+
|
1037
|
-
def rest
|
1038
|
-
self[1..-1]
|
1039
|
-
end
|
1040
|
-
|
1041
|
-
# Prints the content of this array as a series, e.g.:
|
1042
|
-
# [1, 2, 3].to_series #=> "1, 2 and 3"
|
1043
|
-
# [1, 2, 3].to_series('or') #=> "1, 2 or 3"
|
1044
|
-
#
|
1045
|
-
# If a block is given to this method, then the block is applied before the series is formed, e.g.:
|
1046
|
-
# [1, 2, 3].to_series { |n| n + 1 } #=> "2, 3 and 4"
|
1047
|
-
def to_series(conjunction=nil)
|
1048
|
-
conjunction ||= 'and'
|
1049
|
-
return map { |item| yield item }.to_series(conjunction) if block_given?
|
1050
|
-
padded_conjunction = " #{conjunction} "
|
1051
|
-
# join all but the last item as a comma-separated list and append the conjunction and last item
|
1052
|
-
length < 2 ? to_s : self[0...-1].join(', ') + padded_conjunction + last.to_s
|
1053
|
-
end
|
1054
|
-
|
1055
|
-
# Returns a new Hash generated from this array of arrays by associating the first element of each
|
1056
|
-
# member to the remaining elements. If there are only two elements in the member, then the first
|
1057
|
-
# element is associated with the second element. If there is less than two elements in the member,
|
1058
|
-
# the first element is associated with nil. An empty array is ignored.
|
1059
|
-
#
|
1060
|
-
# @example
|
1061
|
-
# [[:a, 1], [:b, 2, 3], [:c], []].to_assoc_hash #=> { :a => 1, :b => [2,3], :c => nil }
|
1062
|
-
# @return [Hash] the first => rest hash
|
1063
|
-
def to_assoc_hash
|
1064
|
-
hash = {}
|
1065
|
-
each do |item|
|
1066
|
-
raise ArgumentError.new("Array member must be an array: #{item.pp_s(:single_line)}") unless Array === item
|
1067
|
-
key = item.first
|
1068
|
-
if item.size < 2 then
|
1069
|
-
value = nil
|
1070
|
-
elsif item.size == 2 then
|
1071
|
-
value = item[1]
|
1072
|
-
else
|
1073
|
-
value = item[1..-1]
|
1074
|
-
end
|
1075
|
-
hash[key] = value unless key.nil?
|
1076
|
-
end
|
1077
|
-
hash
|
1078
|
-
end
|
1079
|
-
|
1080
|
-
alias :base__flatten :flatten
|
1081
|
-
private :base__flatten
|
1082
|
-
# Recursively flattens this array, including any collection item that implements the +to_a+ method.
|
1083
|
-
def flatten
|
1084
|
-
# if any item is a Set or Java Collection, then convert those into arrays before recursively flattening the list
|
1085
|
-
if any? { |item| Set === item or Java::JavaUtil::Collection === item } then
|
1086
|
-
return map { |item| (Set === item or Java::JavaUtil::Collection === item) ? item.to_a : item }.flatten
|
1087
|
-
end
|
1088
|
-
base__flatten
|
1089
|
-
end
|
1090
|
-
|
1091
|
-
# Concatenates the other Enumerable to this array.
|
1092
|
-
#
|
1093
|
-
# @param [#to_a] other the other Enumerable
|
1094
|
-
# @raise [ArgumentError] if other does not respond to the +to_a+ method
|
1095
|
-
def add_all(other)
|
1096
|
-
return concat(other) if Array === other
|
1097
|
-
begin
|
1098
|
-
add_all(other.to_a)
|
1099
|
-
rescue NoMethodError
|
1100
|
-
raise
|
1101
|
-
rescue
|
1102
|
-
raise ArgumentError.new("Can't convert #{other.class.name} to array")
|
1103
|
-
end
|
1104
|
-
end
|
1105
|
-
|
1106
|
-
alias :merge! :add_all
|
1107
|
-
end
|
1108
|
-
|
1109
|
-
# CaseInsensitiveHash accesses entries in a case-insensitive String comparison. The accessor method
|
1110
|
-
# key argument is converted to a String before look-up.
|
1111
|
-
#
|
1112
|
-
# @example
|
1113
|
-
# hash = CaseInsensitiveHash.new
|
1114
|
-
# hash[:UP] = "down"
|
1115
|
-
# hash['up'] #=> "down"
|
1116
|
-
class CaseInsensitiveHash < Hash
|
1117
|
-
def initialize
|
1118
|
-
super
|
1119
|
-
end
|
1120
|
-
|
1121
|
-
def [](key)
|
1122
|
-
# if there is lower-case key association, then convert to lower-case and return.
|
1123
|
-
# otherwise, delegate to super with the call argument unchanged. this ensures
|
1124
|
-
# that a default block passed to the constructor will be called with the correct
|
1125
|
-
# key argument.
|
1126
|
-
has_key?(key) ? super(key.to_s.downcase) : super(key)
|
1127
|
-
end
|
1128
|
-
|
1129
|
-
def []=(key, value)
|
1130
|
-
super(key.to_s.downcase, value)
|
1131
|
-
end
|
1132
|
-
|
1133
|
-
def has_key?(key)
|
1134
|
-
super(key.to_s.downcase)
|
1135
|
-
end
|
1136
|
-
|
1137
|
-
def delete(key)
|
1138
|
-
super(key.to_s.downcase)
|
1139
|
-
end
|
1140
|
-
|
1141
|
-
alias :store :[]=
|
1142
|
-
alias :include? :has_key?
|
1143
|
-
alias :key? :has_key?
|
1144
|
-
alias :member? :has_key?
|
1145
|
-
end
|
1146
|
-
|
1147
|
-
class Set
|
1148
|
-
# The standard Set {#merge} is an anomaly among Ruby collections, since merge modifies the called Set in-place rather
|
1149
|
-
# than return a new Set containing the merged contents. Preserve this unfortunate behavior, but partially address
|
1150
|
-
# the anomaly by adding the merge! alias for in-place merge.
|
1151
|
-
alias :merge! :merge
|
1152
|
-
end
|