jinx 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,108 @@
|
|
|
1
|
+
require 'jinx/helpers/class'
|
|
2
|
+
|
|
3
|
+
class Array
|
|
4
|
+
# The EMPTY_ARRAY constant is an immutable empty array, used primarily as a default argument.
|
|
5
|
+
class << EMPTY_ARRAY ||= Array.new
|
|
6
|
+
def <<(value)
|
|
7
|
+
Jinx.fail(NotImplementedError, "Modification of the constant empty array is not supported")
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Relaxes the Ruby Array methods which take an Array argument to allow collection Enumerable arguments.
|
|
12
|
+
[:|, :+, :-, :&].each do |meth|
|
|
13
|
+
redefine_method(meth) do |old_meth|
|
|
14
|
+
lambda { |other| send(old_meth, other.collection? ? other.to_a : other) }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
redefine_method(:flatten) do |old_meth|
|
|
19
|
+
# if an item is a non-Array collection, then convert it into an array before recursively flattening the list
|
|
20
|
+
lambda { map { |item| item.collection? ? item.to_a : item }.send(old_meth) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Returns an array containing all but the first item in this Array. This method is syntactic sugar for
|
|
24
|
+
# +self[1..-1]+ or +last(length-1)+.
|
|
25
|
+
#
|
|
26
|
+
# @return [Array] an array the tail of this array
|
|
27
|
+
def rest
|
|
28
|
+
self[1..-1]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
alias :tail :rest
|
|
32
|
+
|
|
33
|
+
# Deletes items from this array which do not satisfy the given block.
|
|
34
|
+
#
|
|
35
|
+
# @yield [item] the retention test
|
|
36
|
+
# @yieldparam item an item in this array
|
|
37
|
+
# @return [Array] this array
|
|
38
|
+
def keep_if
|
|
39
|
+
delete_if { |item| not yield(item) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Prints the content of this array as a series, e.g.:
|
|
43
|
+
# [1, 2, 3].to_series #=> "1, 2 and 3"
|
|
44
|
+
# [1, 2, 3].to_series('or') #=> "1, 2 or 3"
|
|
45
|
+
#
|
|
46
|
+
# If a block is given to this method, then the block is applied before the series is formed, e.g.:
|
|
47
|
+
# [1, 2, 3].to_series { |n| n + 1 } #=> "2, 3 and 4"
|
|
48
|
+
def to_series(conjunction=nil)
|
|
49
|
+
conjunction ||= 'and'
|
|
50
|
+
return map { |item| yield item }.to_series(conjunction) if block_given?
|
|
51
|
+
padded_conjunction = " #{conjunction} "
|
|
52
|
+
# join all but the last item as a comma-separated list and append the conjunction and last item
|
|
53
|
+
length < 2 ? to_s : self[0...-1].join(', ') + padded_conjunction + last.to_s
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns a new Hash generated from this array of arrays by associating the first element of each
|
|
57
|
+
# member to the remaining elements. If there are only two elements in the member, then the first
|
|
58
|
+
# element is associated with the second element. If there is less than two elements in the member,
|
|
59
|
+
# the first element is associated with nil. An empty array is ignored.
|
|
60
|
+
#
|
|
61
|
+
# @example
|
|
62
|
+
# [[:a, 1], [:b, 2, 3], [:c], []].to_assoc_hash #=> { :a => 1, :b => [2,3], :c => nil }
|
|
63
|
+
# @return [Hash] the first => rest hash
|
|
64
|
+
def to_assoc_hash
|
|
65
|
+
hash = {}
|
|
66
|
+
each do |item|
|
|
67
|
+
Jinx.fail(ArgumentError, "Array member must be an array: #{item.pp_s(:single_line)}") unless Array === item
|
|
68
|
+
key = item.first
|
|
69
|
+
if item.size < 2 then
|
|
70
|
+
value = nil
|
|
71
|
+
elsif item.size == 2 then
|
|
72
|
+
value = item[1]
|
|
73
|
+
else
|
|
74
|
+
value = item[1..-1]
|
|
75
|
+
end
|
|
76
|
+
hash[key] = value unless key.nil?
|
|
77
|
+
end
|
|
78
|
+
hash
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
alias :base__flatten :flatten
|
|
82
|
+
private :base__flatten
|
|
83
|
+
# Recursively flattens this array, including any collection item that implements the +to_a+ method.
|
|
84
|
+
def flatten
|
|
85
|
+
# if any item is a Set or Java Collection, then convert those into arrays before recursively flattening the list
|
|
86
|
+
if any? { |item| Set === item or Java::JavaUtil::Collection === item } then
|
|
87
|
+
return map { |item| (Set === item or Java::JavaUtil::Collection === item) ? item.to_a : item }.flatten
|
|
88
|
+
end
|
|
89
|
+
base__flatten
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Concatenates the other Enumerable to this array.
|
|
93
|
+
#
|
|
94
|
+
# @param [#to_a] other the other Enumerable
|
|
95
|
+
# @raise [ArgumentError] if other does not respond to the +to_a+ method
|
|
96
|
+
def add_all(other)
|
|
97
|
+
return concat(other) if Array === other
|
|
98
|
+
begin
|
|
99
|
+
add_all(other.to_a)
|
|
100
|
+
rescue NoMethodError
|
|
101
|
+
raise e
|
|
102
|
+
rescue
|
|
103
|
+
Jinx.fail(ArgumentError, "Can't convert #{other.class.name} to array")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
alias :merge! :add_all
|
|
108
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Jinx
|
|
2
|
+
# Boolean marks the +true+ and +false+ primitive objects.
|
|
3
|
+
module Boolean
|
|
4
|
+
# The match for a +true+ String.
|
|
5
|
+
TRUE_REGEXP = /^(t(rue)?|y(es)?|1)$/i
|
|
6
|
+
|
|
7
|
+
# The match for a +false+ String.
|
|
8
|
+
FALSE_REGEXP = /^(f(alse)?||no?|0)$/i
|
|
9
|
+
|
|
10
|
+
# Converts the given object to a Boolean as follows:
|
|
11
|
+
# * If the object is nil or Boolean, then the unconverted value
|
|
12
|
+
# * 1 -> true
|
|
13
|
+
# * 0 -> false
|
|
14
|
+
# * A {TRUE_REGEXP} match -> true
|
|
15
|
+
# * A {FALSE_REGEXP} match -> false
|
|
16
|
+
#
|
|
17
|
+
# @param for the object to convert
|
|
18
|
+
# @return [Boolean, nil] the corresponding Boolean
|
|
19
|
+
# @raise [ArgumentError] if the value cannot be converted
|
|
20
|
+
def self.for(obj)
|
|
21
|
+
case obj
|
|
22
|
+
when nil then nil
|
|
23
|
+
when true then true
|
|
24
|
+
when false then false
|
|
25
|
+
when 1 then true
|
|
26
|
+
when 0 then false
|
|
27
|
+
when TRUE_REGEXP then true
|
|
28
|
+
when FALSE_REGEXP then false
|
|
29
|
+
else
|
|
30
|
+
raise ArgumentError.new("Value cannot be converted to boolean: '#{obj}'")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class TrueClass
|
|
37
|
+
include Jinx::Boolean
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class FalseClass
|
|
41
|
+
include Jinx::Boolean
|
|
42
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Jinx
|
|
2
|
+
# CaseInsensitiveHash accesses entries in a case-insensitive String comparison. The accessor method
|
|
3
|
+
# key argument is converted to a String before look-up.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# hash = CaseInsensitiveHash.new
|
|
7
|
+
# hash[:UP] = "down"
|
|
8
|
+
# hash['up'] #=> "down"
|
|
9
|
+
class CaseInsensitiveHash < Hash
|
|
10
|
+
def initialize
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def [](key)
|
|
15
|
+
# if there is lower-case key association, then convert to lower-case and return.
|
|
16
|
+
# otherwise, delegate to super with the call argument unchanged. this ensures
|
|
17
|
+
# that a default block passed to the constructor will be called with the correct
|
|
18
|
+
# key argument.
|
|
19
|
+
has_key?(key) ? super(key.to_s.downcase) : super(key)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def []=(key, value)
|
|
23
|
+
super(key.to_s.downcase, value)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def has_key?(key)
|
|
27
|
+
super(key.to_s.downcase)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def delete(key)
|
|
31
|
+
super(key.to_s.downcase)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
alias :store :[]=
|
|
35
|
+
alias :include? :has_key?
|
|
36
|
+
alias :key? :has_key?
|
|
37
|
+
alias :member? :has_key?
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
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
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class Object
|
|
2
|
+
# Returns whether this object is a collection capable of holding heterogenous items.
|
|
3
|
+
# An Object is a not a collection by default. Subclasses can override this method.
|
|
4
|
+
def collection?
|
|
5
|
+
false
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module Enumerable
|
|
10
|
+
# Overrides {Object#collection?} to returns +true+, since an Enumerable is capable of
|
|
11
|
+
# holding heterogenous items by default. Subclasses can override this method.
|
|
12
|
+
def collection?
|
|
13
|
+
true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class String
|
|
18
|
+
# Overrides {Enumerable#collection?} to returns +false+, since a String is constrained
|
|
19
|
+
# to hold characters.
|
|
20
|
+
def collection?
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module Jinx
|
|
26
|
+
module Collection
|
|
27
|
+
include Enumerable
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
module Java::JavaUtil::Collection
|
|
32
|
+
include Jinx::Collection
|
|
33
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# This file loads the definitions of useful collection mix-ins and utility classes.
|
|
2
|
+
|
|
3
|
+
require 'jinx/helpers/collection'
|
|
4
|
+
require 'jinx/helpers/array'
|
|
5
|
+
require 'jinx/helpers/hashable'
|
|
6
|
+
require 'jinx/helpers/hash'
|
|
7
|
+
require 'jinx/helpers/set'
|
|
8
|
+
require 'jinx/helpers/enumerable'
|
|
9
|
+
require 'jinx/helpers/enumerate'
|
|
10
|
+
require 'jinx/helpers/filter'
|
|
11
|
+
require 'jinx/helpers/flattener'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Jinx
|
|
2
|
+
# The Collector utility implements the {on} method to apply a block to a collection
|
|
3
|
+
# transitive closure.
|
|
4
|
+
module Collector
|
|
5
|
+
# Collects the result of applying the given block to the given obj.
|
|
6
|
+
# If obj is a collection, then collects the result of recursively calling this
|
|
7
|
+
# Collector on the enumerated members.
|
|
8
|
+
# If obj is nil, then returns nil.
|
|
9
|
+
# Otherwise, calls block on obj and returns the result.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# Collector.on([1, 2, [3, 4]]) { |n| n * 2 } #=> [2, 4, [6, 8]]]
|
|
13
|
+
# Collector.on(nil) { |n| n * 2 } #=> nil
|
|
14
|
+
# Collector.on(1) { |n| n * 2 } #=> 2
|
|
15
|
+
# @param obj the collection or item to enumerate
|
|
16
|
+
def self.on(obj, &block)
|
|
17
|
+
obj.collection? ? obj.map { |item| on(item, &block) } : yield(obj) unless obj.nil?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Jinx
|
|
2
|
+
# ConditionalEnumerator applies a filter to another Enumerable.
|
|
3
|
+
#
|
|
4
|
+
# @example
|
|
5
|
+
# ConditionalEnumerator.new([1, 2, 3]) { |i| i < 3 }.to_a #=> [1, 2]
|
|
6
|
+
class ConditionalEnumerator
|
|
7
|
+
include Collection
|
|
8
|
+
|
|
9
|
+
# Creates a ConditionalEnumerator which wraps the base Enumerator with a conditional filter.
|
|
10
|
+
def initialize(base, &filter)
|
|
11
|
+
@base = base
|
|
12
|
+
@filter = filter
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Applies the iterator block to each of this ConditionalEnumerator's base Enumerable items
|
|
16
|
+
# for which this ConditionalEnumerator's filter returns true.
|
|
17
|
+
def each
|
|
18
|
+
@base.each { |item| (yield item) if @filter.call(item) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
require 'jinx/helpers/transformer'
|
|
2
|
+
require 'jinx/helpers/multi_enumerator'
|
|
3
|
+
|
|
4
|
+
module Enumerable
|
|
5
|
+
# Returns a new Hash generated from this Enumerable and an optional value generator block.
|
|
6
|
+
# This Enumerable contains the Hash keys. If the value generator block is given to this
|
|
7
|
+
# method then the block is called with each enumerated element as an argument to
|
|
8
|
+
# generate the associated hash value. If no block is given, then the values are nil.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# [1, 2, 3].hashify { |item| item.modulo(2) } #=> { 1 => 1, 2 => 0, 3 => 1 }
|
|
12
|
+
# [:a].hashify #=> { :a => nil }
|
|
13
|
+
# @return [Hash]
|
|
14
|
+
def hashify
|
|
15
|
+
hash = {}
|
|
16
|
+
each { |item| hash[item] = yield item if block_given? }
|
|
17
|
+
hash
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns a new Hash generated from this Enumerable and a required value generator block.
|
|
21
|
+
# This Enumerable contains the Hash keys. The block is called with each enumerated
|
|
22
|
+
# element as an argument to generate the associated hash value.
|
|
23
|
+
# Only non-nil, non-empty values are included in the hash.
|
|
24
|
+
#
|
|
25
|
+
# @example
|
|
26
|
+
# [1, 2, 3].to_compact_hash { |item| item.modulo(2) } #=> { 1 => 1, 2 => 0, 3 => 1 }
|
|
27
|
+
# [1, 2, 3].to_compact_hash { |n| n.modulo(2) unless item > 2 } #=> {1 => 1, 2 => 0}
|
|
28
|
+
# [1, 2, 3].to_compact_hash { |n| n > 2 } #=> {1 => false, 2 => false, 3 => true}
|
|
29
|
+
# [1, 2, 3].to_compact_hash { |n| Array.new(n - 1, n) } #=> {2 => [2], 3 => [2, 3]}
|
|
30
|
+
# @return [Hash]
|
|
31
|
+
# @raise [ArgumentError] if the generator block is not given
|
|
32
|
+
# @see #hashify
|
|
33
|
+
def to_compact_hash
|
|
34
|
+
Jinx.fail(ArgumentError, "Compact hash builder is missing the value generator block") unless block_given?
|
|
35
|
+
to_compact_hash_with_index { |item, index| yield item }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns a new Hash generated from this Enumerable with a block whose arguments include the enumerated item
|
|
39
|
+
# and its index. Every value which is nil or empty is excluded.
|
|
40
|
+
#
|
|
41
|
+
# @example
|
|
42
|
+
# [1, 2, 3].to_compact_hash_with_index { |item, index| item + index } #=> { 1 => 1, 2 => 3, 3 => 5 }
|
|
43
|
+
# @yield [item, index] the hash value
|
|
44
|
+
# @yieldparam item the enumerated value
|
|
45
|
+
# @yieldparam index the enumeration index
|
|
46
|
+
# @return [Hash] this {Enumerable} converted to a hash by the given block
|
|
47
|
+
def to_compact_hash_with_index
|
|
48
|
+
hash = {}
|
|
49
|
+
self.each_with_index do |item, index|
|
|
50
|
+
next if item.nil?
|
|
51
|
+
value = yield(item, index)
|
|
52
|
+
next if value.nil_or_empty?
|
|
53
|
+
hash[item] = value
|
|
54
|
+
end
|
|
55
|
+
hash
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# This method is functionally equivalent to +to_a.empty+ but is more concise and efficient.
|
|
59
|
+
#
|
|
60
|
+
# @return [Boolean] whether this Enumerable iterates over at least one item
|
|
61
|
+
def empty?
|
|
62
|
+
not any? { true }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# This method is functionally equivalent to +to_a.first+ but is more concise and efficient.
|
|
66
|
+
#
|
|
67
|
+
# @return the first enumerated item in this Enumerable, or nil if this Enumerable is empty
|
|
68
|
+
def first
|
|
69
|
+
detect { true }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# This method is functionally equivalent to +to_a.last+ but is more concise and efficient.
|
|
73
|
+
#
|
|
74
|
+
# @return the last enumerated item in this Enumerable, or nil if this Enumerable is empty
|
|
75
|
+
def last
|
|
76
|
+
detect { true }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# This method is functionally equivalent to +to_a.size+ but is more concise and efficient
|
|
80
|
+
# for an Enumerable which does not implement the {#size} method.
|
|
81
|
+
#
|
|
82
|
+
# @return [Integer] the count of items enumerated in this Enumerable
|
|
83
|
+
def size
|
|
84
|
+
inject(0) { |size, item| size + 1 }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
alias :length :size
|
|
88
|
+
|
|
89
|
+
# @return [String] the content of this Enumerable as a series using {Array#to_series}
|
|
90
|
+
def to_series(conjunction=nil)
|
|
91
|
+
to_a.to_series
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns the first non-nil, non-false enumerated value resulting from a call to the block given to this method,
|
|
95
|
+
# or nil if no value detected.
|
|
96
|
+
#
|
|
97
|
+
# @example
|
|
98
|
+
# [1, 2].detect_value { |item| item / 2 if item % 2 == 0 } #=> 1
|
|
99
|
+
# @return [Object] the detected block result
|
|
100
|
+
# @see #detect_with_value
|
|
101
|
+
def detect_value
|
|
102
|
+
each do |*item|
|
|
103
|
+
value = yield(*item)
|
|
104
|
+
return value if value
|
|
105
|
+
end
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns the first item and value for which an enumeration on the block given to this method returns
|
|
110
|
+
# a non-nil, non-false value.
|
|
111
|
+
#
|
|
112
|
+
# @example
|
|
113
|
+
# [1, 2].detect_with_value { |item| item / 2 if item % 2 == 0 } #=> [2, 1]
|
|
114
|
+
# @return [(Object, Object)] the detected [item, value] pair
|
|
115
|
+
# @see #detect_value
|
|
116
|
+
def detect_with_value
|
|
117
|
+
value = nil
|
|
118
|
+
match = detect do |*item|
|
|
119
|
+
value = yield(*item)
|
|
120
|
+
end
|
|
121
|
+
[match, value]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Returns a new Enumerable that iterates over the base Enumerable items for which filter evaluates to a non-nil,
|
|
125
|
+
# non-false value, e.g.:
|
|
126
|
+
# [1, 2, 3].filter { |n| n != 2 }.to_a #=> [1, 3]
|
|
127
|
+
#
|
|
128
|
+
# Unlike select, filter reflects changes to the base Enumerable, e.g.:
|
|
129
|
+
# a = [1, 2, 3]
|
|
130
|
+
# filter = a.filter { |n| n != 2 }
|
|
131
|
+
# a << 4
|
|
132
|
+
# filter.to_a #=> [1, 3, 4]
|
|
133
|
+
#
|
|
134
|
+
# In addition, filter has a small, fixed storage requirement, making it preferable to select for large collections.
|
|
135
|
+
# Note, however, that unlike select, filter does not return an Array.
|
|
136
|
+
# The default filter block returns the passed item.
|
|
137
|
+
#
|
|
138
|
+
# @example
|
|
139
|
+
# [1, nil, 3].filter.to_a #=> [1, 3]
|
|
140
|
+
# @yield [item] filter the selection filter
|
|
141
|
+
# @yieldparam item the collection member to filter
|
|
142
|
+
# @return [Enumerable] the filtered result
|
|
143
|
+
def filter(&filter)
|
|
144
|
+
Jinx::Filter.new(self, &filter)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @return [Enumerable] an iterator over the non-nil items in this Enumerable
|
|
148
|
+
def compact
|
|
149
|
+
filter { |item| not item.nil? }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @example
|
|
153
|
+
# {:a => {:b => :c}, :d => [:e]}.enum_values.flatten.to_a #=> [:b, :c, :e]
|
|
154
|
+
# @return [Enumerable] the flattened result
|
|
155
|
+
def flatten
|
|
156
|
+
Jinx::Flattener.new(self).to_a
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Returns an Enumerable which iterates over items in this Enumerable and the other Enumerable in sequence.
|
|
160
|
+
# Unlike the Array plus (+) operator, {#union} reflects changes to the underlying enumerators.
|
|
161
|
+
#
|
|
162
|
+
# @example
|
|
163
|
+
# a = [1, 2]
|
|
164
|
+
# b = [4, 5]
|
|
165
|
+
# ab = a.union(b)
|
|
166
|
+
# ab #=> [1, 2, 4, 5]
|
|
167
|
+
# a << 3
|
|
168
|
+
# a + b #=> [1, 2, 4, 5]
|
|
169
|
+
# ab #=> [1, 2, 3, 4, 5]
|
|
170
|
+
# @param [Enumerable] other the Enumerable to compose with this Enumerable
|
|
171
|
+
# @return [Enumerable] an enumerator over self followed by other
|
|
172
|
+
def union(other)
|
|
173
|
+
Jinx::MultiEnumerator.new(self, other)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
alias :+ :union
|
|
177
|
+
|
|
178
|
+
# @return an Enumerable which iterates over items in this Enumerable but not the other Enumerable
|
|
179
|
+
def difference(other)
|
|
180
|
+
filter { |item| not other.include?(item) }
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
alias :- :difference
|
|
184
|
+
|
|
185
|
+
# @return an Enumerable which iterates over items in this Enumerable which are also in the other Enumerable
|
|
186
|
+
def intersect(other)
|
|
187
|
+
filter { |item| other.include?(item) }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
alias :& :intersect
|
|
191
|
+
|
|
192
|
+
# Returns a new Enumerable that iterates over the base Enumerable applying the transformer block to each item, e.g.:
|
|
193
|
+
# [1, 2, 3].transform_value { |n| n * 2 }.to_a #=> [2, 4, 6]
|
|
194
|
+
#
|
|
195
|
+
# Unlike Array.map, {#wrap} reflects changes to the base Enumerable, e.g.:
|
|
196
|
+
# a = [2, 4, 6]
|
|
197
|
+
``# transformed = a.wrap { |n| n * 2 }
|
|
198
|
+
# a << 4
|
|
199
|
+
# transformed.to_a #=> [2, 4, 6, 8]
|
|
200
|
+
#
|
|
201
|
+
# In addition, transform has a small, fixed storage requirement, making it preferable to select for large collections.
|
|
202
|
+
# Note, however, that unlike map, transform does not return an Array.
|
|
203
|
+
#
|
|
204
|
+
# @yield [item] the transformer on the enumerated items
|
|
205
|
+
# @yieldparam item an enumerated item
|
|
206
|
+
# @return [Enumerable] an enumeration on the transformed values
|
|
207
|
+
def transform(&mapper)
|
|
208
|
+
Jinx::Transformer.new(self, &mapper)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
alias :wrap :transform
|
|
212
|
+
|
|
213
|
+
def join(sep = $,)
|
|
214
|
+
to_a.join(sep)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Sorts this collection's members with a partial sort operator, i.e. the comparison returns -1, 0, 1 or nil.
|
|
218
|
+
# The resulting sorted order places each non-nil comparable items in the sort order. The order of nil
|
|
219
|
+
# comparison items is indeterminate.
|
|
220
|
+
#
|
|
221
|
+
# @example
|
|
222
|
+
# [Array, Numeric, Enumerable, Set].partial_sort #=> [Array, Numeric, Set, Enumerable]
|
|
223
|
+
# @return [Enumerable] the items in this collection in partial sort order
|
|
224
|
+
def partial_sort
|
|
225
|
+
unless block_given? then return partial_sort { |item1, item2| item1 <=> item2 } end
|
|
226
|
+
sort { |item1, item2| yield(item1, item2) or 1 }
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Sorts this collection's members with a partial sort operator on the results of applying the block.
|
|
230
|
+
#
|
|
231
|
+
# @return [Enumerable] the items in this collection in partial sort order
|
|
232
|
+
def partial_sort_by
|
|
233
|
+
partial_sort { |item1, item2| yield(item1) <=> yield(item2) }
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# @yield [item] the transformer on the enumerated items
|
|
237
|
+
# @yieldparam item an enumerated item
|
|
238
|
+
# @return [Enumerable] the mapped values excluding null values
|
|
239
|
+
def compact_map(&mapper)
|
|
240
|
+
wrap(&mapper).compact
|
|
241
|
+
end
|
|
242
|
+
end
|