jinx 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. data/.gitignore +14 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +1 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +27 -0
  6. data/History.md +6 -0
  7. data/LEGAL +5 -0
  8. data/LICENSE +22 -0
  9. data/README.md +44 -0
  10. data/Rakefile +41 -0
  11. data/examples/family/README.md +10 -0
  12. data/examples/family/ext/build.xml +35 -0
  13. data/examples/family/ext/src/family/Address.java +68 -0
  14. data/examples/family/ext/src/family/Child.java +24 -0
  15. data/examples/family/ext/src/family/DomainObject.java +26 -0
  16. data/examples/family/ext/src/family/Household.java +36 -0
  17. data/examples/family/ext/src/family/Parent.java +48 -0
  18. data/examples/family/ext/src/family/Person.java +42 -0
  19. data/examples/family/lib/family.rb +15 -0
  20. data/examples/family/lib/family/address.rb +6 -0
  21. data/examples/family/lib/family/domain_object.rb +6 -0
  22. data/examples/family/lib/family/household.rb +6 -0
  23. data/examples/family/lib/family/parent.rb +16 -0
  24. data/examples/family/lib/family/person.rb +6 -0
  25. data/examples/model/README.md +25 -0
  26. data/examples/model/ext/build.xml +35 -0
  27. data/examples/model/ext/src/domain/Child.java +192 -0
  28. data/examples/model/ext/src/domain/Dependent.java +29 -0
  29. data/examples/model/ext/src/domain/DomainObject.java +26 -0
  30. data/examples/model/ext/src/domain/Independent.java +83 -0
  31. data/examples/model/ext/src/domain/Parent.java +129 -0
  32. data/examples/model/ext/src/domain/Person.java +14 -0
  33. data/examples/model/lib/model.rb +13 -0
  34. data/examples/model/lib/model/child.rb +13 -0
  35. data/examples/model/lib/model/domain_object.rb +6 -0
  36. data/examples/model/lib/model/independent.rb +11 -0
  37. data/examples/model/lib/model/parent.rb +17 -0
  38. data/jinx.gemspec +22 -0
  39. data/lib/jinx.rb +3 -0
  40. data/lib/jinx/active_support/README.txt +2 -0
  41. data/lib/jinx/active_support/core_ext/string.rb +7 -0
  42. data/lib/jinx/active_support/core_ext/string/inflections.rb +167 -0
  43. data/lib/jinx/active_support/inflections.rb +55 -0
  44. data/lib/jinx/active_support/inflector.rb +398 -0
  45. data/lib/jinx/cli/application.rb +36 -0
  46. data/lib/jinx/cli/command.rb +214 -0
  47. data/lib/jinx/helpers/array.rb +108 -0
  48. data/lib/jinx/helpers/boolean.rb +42 -0
  49. data/lib/jinx/helpers/case_insensitive_hash.rb +39 -0
  50. data/lib/jinx/helpers/class.rb +149 -0
  51. data/lib/jinx/helpers/collection.rb +33 -0
  52. data/lib/jinx/helpers/collections.rb +11 -0
  53. data/lib/jinx/helpers/collector.rb +20 -0
  54. data/lib/jinx/helpers/conditional_enumerator.rb +21 -0
  55. data/lib/jinx/helpers/enumerable.rb +242 -0
  56. data/lib/jinx/helpers/enumerate.rb +35 -0
  57. data/lib/jinx/helpers/error.rb +15 -0
  58. data/lib/jinx/helpers/file_separator.rb +65 -0
  59. data/lib/jinx/helpers/filter.rb +52 -0
  60. data/lib/jinx/helpers/flattener.rb +38 -0
  61. data/lib/jinx/helpers/hash.rb +12 -0
  62. data/lib/jinx/helpers/hashable.rb +502 -0
  63. data/lib/jinx/helpers/inflector.rb +36 -0
  64. data/lib/jinx/helpers/key_transformer_hash.rb +43 -0
  65. data/lib/jinx/helpers/lazy_hash.rb +44 -0
  66. data/lib/jinx/helpers/log.rb +106 -0
  67. data/lib/jinx/helpers/math.rb +12 -0
  68. data/lib/jinx/helpers/merge.rb +60 -0
  69. data/lib/jinx/helpers/module.rb +18 -0
  70. data/lib/jinx/helpers/multi_enumerator.rb +31 -0
  71. data/lib/jinx/helpers/options.rb +92 -0
  72. data/lib/jinx/helpers/os.rb +19 -0
  73. data/lib/jinx/helpers/partial_order.rb +37 -0
  74. data/lib/jinx/helpers/pretty_print.rb +207 -0
  75. data/lib/jinx/helpers/set.rb +8 -0
  76. data/lib/jinx/helpers/stopwatch.rb +76 -0
  77. data/lib/jinx/helpers/transformer.rb +24 -0
  78. data/lib/jinx/helpers/transitive_closure.rb +55 -0
  79. data/lib/jinx/helpers/uniquifier.rb +50 -0
  80. data/lib/jinx/helpers/validation.rb +33 -0
  81. data/lib/jinx/helpers/visitor.rb +370 -0
  82. data/lib/jinx/import/class_path_modifier.rb +77 -0
  83. data/lib/jinx/import/java.rb +337 -0
  84. data/lib/jinx/importer.rb +240 -0
  85. data/lib/jinx/metadata.rb +155 -0
  86. data/lib/jinx/metadata/attribute_enumerator.rb +73 -0
  87. data/lib/jinx/metadata/dependency.rb +244 -0
  88. data/lib/jinx/metadata/id_alias.rb +23 -0
  89. data/lib/jinx/metadata/introspector.rb +179 -0
  90. data/lib/jinx/metadata/inverse.rb +170 -0
  91. data/lib/jinx/metadata/java_property.rb +169 -0
  92. data/lib/jinx/metadata/propertied.rb +500 -0
  93. data/lib/jinx/metadata/property.rb +401 -0
  94. data/lib/jinx/metadata/property_characteristics.rb +114 -0
  95. data/lib/jinx/resource.rb +862 -0
  96. data/lib/jinx/resource/copy_visitor.rb +36 -0
  97. data/lib/jinx/resource/inversible.rb +90 -0
  98. data/lib/jinx/resource/match_visitor.rb +180 -0
  99. data/lib/jinx/resource/matcher.rb +20 -0
  100. data/lib/jinx/resource/merge_visitor.rb +73 -0
  101. data/lib/jinx/resource/mergeable.rb +185 -0
  102. data/lib/jinx/resource/reference_enumerator.rb +49 -0
  103. data/lib/jinx/resource/reference_path_visitor.rb +38 -0
  104. data/lib/jinx/resource/reference_visitor.rb +55 -0
  105. data/lib/jinx/resource/unique.rb +35 -0
  106. data/lib/jinx/version.rb +3 -0
  107. data/spec/defaults_spec.rb +30 -0
  108. data/spec/definitions/model/alias/child.rb +5 -0
  109. data/spec/definitions/model/base/child.rb +5 -0
  110. data/spec/definitions/model/base/domain_object.rb +5 -0
  111. data/spec/definitions/model/base/independent.rb +5 -0
  112. data/spec/definitions/model/defaults/child.rb +5 -0
  113. data/spec/definitions/model/dependency/child.rb +5 -0
  114. data/spec/definitions/model/dependency/parent.rb +6 -0
  115. data/spec/definitions/model/inverse/child.rb +5 -0
  116. data/spec/definitions/model/inverse/independent.rb +5 -0
  117. data/spec/definitions/model/inverse/parent.rb +5 -0
  118. data/spec/definitions/model/mandatory/child.rb +6 -0
  119. data/spec/dependency_spec.rb +47 -0
  120. data/spec/family_spec.rb +64 -0
  121. data/spec/inverse_spec.rb +53 -0
  122. data/spec/mandatory_spec.rb +43 -0
  123. data/spec/metadata_spec.rb +68 -0
  124. data/spec/resource_spec.rb +30 -0
  125. data/spec/spec_helper.rb +3 -0
  126. data/spec/support/model.rb +19 -0
  127. data/test/fixtures/line_separator/cr_line_sep.txt +1 -0
  128. data/test/fixtures/line_separator/crlf_line_sep.txt +3 -0
  129. data/test/fixtures/line_separator/lf_line_sep.txt +3 -0
  130. data/test/fixtures/mixed/ext/build.xml +35 -0
  131. data/test/fixtures/mixed/ext/src/mixed/Case/Example.java +5 -0
  132. data/test/helper.rb +7 -0
  133. data/test/lib/jinx/command_test.rb +41 -0
  134. data/test/lib/jinx/helpers/boolean_test.rb +27 -0
  135. data/test/lib/jinx/helpers/class_test.rb +60 -0
  136. data/test/lib/jinx/helpers/collections_test.rb +402 -0
  137. data/test/lib/jinx/helpers/file_separator_test.rb +29 -0
  138. data/test/lib/jinx/helpers/inflector_test.rb +11 -0
  139. data/test/lib/jinx/helpers/lazy_hash_test.rb +32 -0
  140. data/test/lib/jinx/helpers/module_test.rb +24 -0
  141. data/test/lib/jinx/helpers/options_test.rb +66 -0
  142. data/test/lib/jinx/helpers/partial_order_test.rb +41 -0
  143. data/test/lib/jinx/helpers/pretty_print_test.rb +83 -0
  144. data/test/lib/jinx/helpers/stopwatch_test.rb +16 -0
  145. data/test/lib/jinx/helpers/transitive_closure_test.rb +80 -0
  146. data/test/lib/jinx/helpers/visitor_test.rb +288 -0
  147. data/test/lib/jinx/import/java_test.rb +78 -0
  148. data/test/lib/jinx/import/mixed_case_test.rb +16 -0
  149. metadata +272 -0
@@ -0,0 +1,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