jinx 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.yardopts +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +27 -0
- data/History.md +6 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +44 -0
- data/Rakefile +41 -0
- data/examples/family/README.md +10 -0
- data/examples/family/ext/build.xml +35 -0
- data/examples/family/ext/src/family/Address.java +68 -0
- data/examples/family/ext/src/family/Child.java +24 -0
- data/examples/family/ext/src/family/DomainObject.java +26 -0
- data/examples/family/ext/src/family/Household.java +36 -0
- data/examples/family/ext/src/family/Parent.java +48 -0
- data/examples/family/ext/src/family/Person.java +42 -0
- data/examples/family/lib/family.rb +15 -0
- data/examples/family/lib/family/address.rb +6 -0
- data/examples/family/lib/family/domain_object.rb +6 -0
- data/examples/family/lib/family/household.rb +6 -0
- data/examples/family/lib/family/parent.rb +16 -0
- data/examples/family/lib/family/person.rb +6 -0
- data/examples/model/README.md +25 -0
- data/examples/model/ext/build.xml +35 -0
- data/examples/model/ext/src/domain/Child.java +192 -0
- data/examples/model/ext/src/domain/Dependent.java +29 -0
- data/examples/model/ext/src/domain/DomainObject.java +26 -0
- data/examples/model/ext/src/domain/Independent.java +83 -0
- data/examples/model/ext/src/domain/Parent.java +129 -0
- data/examples/model/ext/src/domain/Person.java +14 -0
- data/examples/model/lib/model.rb +13 -0
- data/examples/model/lib/model/child.rb +13 -0
- data/examples/model/lib/model/domain_object.rb +6 -0
- data/examples/model/lib/model/independent.rb +11 -0
- data/examples/model/lib/model/parent.rb +17 -0
- data/jinx.gemspec +22 -0
- data/lib/jinx.rb +3 -0
- data/lib/jinx/active_support/README.txt +2 -0
- data/lib/jinx/active_support/core_ext/string.rb +7 -0
- data/lib/jinx/active_support/core_ext/string/inflections.rb +167 -0
- data/lib/jinx/active_support/inflections.rb +55 -0
- data/lib/jinx/active_support/inflector.rb +398 -0
- data/lib/jinx/cli/application.rb +36 -0
- data/lib/jinx/cli/command.rb +214 -0
- data/lib/jinx/helpers/array.rb +108 -0
- data/lib/jinx/helpers/boolean.rb +42 -0
- data/lib/jinx/helpers/case_insensitive_hash.rb +39 -0
- data/lib/jinx/helpers/class.rb +149 -0
- data/lib/jinx/helpers/collection.rb +33 -0
- data/lib/jinx/helpers/collections.rb +11 -0
- data/lib/jinx/helpers/collector.rb +20 -0
- data/lib/jinx/helpers/conditional_enumerator.rb +21 -0
- data/lib/jinx/helpers/enumerable.rb +242 -0
- data/lib/jinx/helpers/enumerate.rb +35 -0
- data/lib/jinx/helpers/error.rb +15 -0
- data/lib/jinx/helpers/file_separator.rb +65 -0
- data/lib/jinx/helpers/filter.rb +52 -0
- data/lib/jinx/helpers/flattener.rb +38 -0
- data/lib/jinx/helpers/hash.rb +12 -0
- data/lib/jinx/helpers/hashable.rb +502 -0
- data/lib/jinx/helpers/inflector.rb +36 -0
- data/lib/jinx/helpers/key_transformer_hash.rb +43 -0
- data/lib/jinx/helpers/lazy_hash.rb +44 -0
- data/lib/jinx/helpers/log.rb +106 -0
- data/lib/jinx/helpers/math.rb +12 -0
- data/lib/jinx/helpers/merge.rb +60 -0
- data/lib/jinx/helpers/module.rb +18 -0
- data/lib/jinx/helpers/multi_enumerator.rb +31 -0
- data/lib/jinx/helpers/options.rb +92 -0
- data/lib/jinx/helpers/os.rb +19 -0
- data/lib/jinx/helpers/partial_order.rb +37 -0
- data/lib/jinx/helpers/pretty_print.rb +207 -0
- data/lib/jinx/helpers/set.rb +8 -0
- data/lib/jinx/helpers/stopwatch.rb +76 -0
- data/lib/jinx/helpers/transformer.rb +24 -0
- data/lib/jinx/helpers/transitive_closure.rb +55 -0
- data/lib/jinx/helpers/uniquifier.rb +50 -0
- data/lib/jinx/helpers/validation.rb +33 -0
- data/lib/jinx/helpers/visitor.rb +370 -0
- data/lib/jinx/import/class_path_modifier.rb +77 -0
- data/lib/jinx/import/java.rb +337 -0
- data/lib/jinx/importer.rb +240 -0
- data/lib/jinx/metadata.rb +155 -0
- data/lib/jinx/metadata/attribute_enumerator.rb +73 -0
- data/lib/jinx/metadata/dependency.rb +244 -0
- data/lib/jinx/metadata/id_alias.rb +23 -0
- data/lib/jinx/metadata/introspector.rb +179 -0
- data/lib/jinx/metadata/inverse.rb +170 -0
- data/lib/jinx/metadata/java_property.rb +169 -0
- data/lib/jinx/metadata/propertied.rb +500 -0
- data/lib/jinx/metadata/property.rb +401 -0
- data/lib/jinx/metadata/property_characteristics.rb +114 -0
- data/lib/jinx/resource.rb +862 -0
- data/lib/jinx/resource/copy_visitor.rb +36 -0
- data/lib/jinx/resource/inversible.rb +90 -0
- data/lib/jinx/resource/match_visitor.rb +180 -0
- data/lib/jinx/resource/matcher.rb +20 -0
- data/lib/jinx/resource/merge_visitor.rb +73 -0
- data/lib/jinx/resource/mergeable.rb +185 -0
- data/lib/jinx/resource/reference_enumerator.rb +49 -0
- data/lib/jinx/resource/reference_path_visitor.rb +38 -0
- data/lib/jinx/resource/reference_visitor.rb +55 -0
- data/lib/jinx/resource/unique.rb +35 -0
- data/lib/jinx/version.rb +3 -0
- data/spec/defaults_spec.rb +30 -0
- data/spec/definitions/model/alias/child.rb +5 -0
- data/spec/definitions/model/base/child.rb +5 -0
- data/spec/definitions/model/base/domain_object.rb +5 -0
- data/spec/definitions/model/base/independent.rb +5 -0
- data/spec/definitions/model/defaults/child.rb +5 -0
- data/spec/definitions/model/dependency/child.rb +5 -0
- data/spec/definitions/model/dependency/parent.rb +6 -0
- data/spec/definitions/model/inverse/child.rb +5 -0
- data/spec/definitions/model/inverse/independent.rb +5 -0
- data/spec/definitions/model/inverse/parent.rb +5 -0
- data/spec/definitions/model/mandatory/child.rb +6 -0
- data/spec/dependency_spec.rb +47 -0
- data/spec/family_spec.rb +64 -0
- data/spec/inverse_spec.rb +53 -0
- data/spec/mandatory_spec.rb +43 -0
- data/spec/metadata_spec.rb +68 -0
- data/spec/resource_spec.rb +30 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/model.rb +19 -0
- data/test/fixtures/line_separator/cr_line_sep.txt +1 -0
- data/test/fixtures/line_separator/crlf_line_sep.txt +3 -0
- data/test/fixtures/line_separator/lf_line_sep.txt +3 -0
- data/test/fixtures/mixed/ext/build.xml +35 -0
- data/test/fixtures/mixed/ext/src/mixed/Case/Example.java +5 -0
- data/test/helper.rb +7 -0
- data/test/lib/jinx/command_test.rb +41 -0
- data/test/lib/jinx/helpers/boolean_test.rb +27 -0
- data/test/lib/jinx/helpers/class_test.rb +60 -0
- data/test/lib/jinx/helpers/collections_test.rb +402 -0
- data/test/lib/jinx/helpers/file_separator_test.rb +29 -0
- data/test/lib/jinx/helpers/inflector_test.rb +11 -0
- data/test/lib/jinx/helpers/lazy_hash_test.rb +32 -0
- data/test/lib/jinx/helpers/module_test.rb +24 -0
- data/test/lib/jinx/helpers/options_test.rb +66 -0
- data/test/lib/jinx/helpers/partial_order_test.rb +41 -0
- data/test/lib/jinx/helpers/pretty_print_test.rb +83 -0
- data/test/lib/jinx/helpers/stopwatch_test.rb +16 -0
- data/test/lib/jinx/helpers/transitive_closure_test.rb +80 -0
- data/test/lib/jinx/helpers/visitor_test.rb +288 -0
- data/test/lib/jinx/import/java_test.rb +78 -0
- data/test/lib/jinx/import/mixed_case_test.rb +16 -0
- metadata +272 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'jinx/active_support/inflector'
|
2
|
+
|
3
|
+
class String
|
4
|
+
# @param [Numeric] quantity the amount qualifier
|
5
|
+
# @return [String] this String qualified by a plural if the quantity is not 1
|
6
|
+
# @example
|
7
|
+
# "rose".quantify(3) #=> "roses"
|
8
|
+
# "rose".quantify(1 #=> "rose"
|
9
|
+
def quantify(quantity)
|
10
|
+
Jinx.fail(ArgumentError, "Missing quantity argument") if quantity.nil?
|
11
|
+
"#{quantity} #{quantity == 1 ? self : pluralize}"
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [String] this String with the first letter capitalized and other letters preserved
|
15
|
+
# @example
|
16
|
+
# "rosesAreRed".capitalize_first #=> "RosesAreRed"
|
17
|
+
def capitalize_first
|
18
|
+
sub(/(?:^)(.)/) { $1.upcase }
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [String] this String with a leading indefinite article
|
22
|
+
# @example
|
23
|
+
# "rose".indefinitize #=> "a rose"
|
24
|
+
# "eagle".indefinitize #=> "an eagle"
|
25
|
+
def indefinitize
|
26
|
+
article = self =~ /^[aeiou]/i ? 'an' : 'a'
|
27
|
+
"#{article} #{self}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [String] this String with the first letter decapitalized and other letters preserved
|
31
|
+
# @example
|
32
|
+
# "RosesAreRed".decapitalize #=> "rosesAreRed"
|
33
|
+
def decapitalize
|
34
|
+
sub(/(?:^)(.)/) { $1.downcase }
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'jinx/helpers/hashable'
|
2
|
+
|
3
|
+
module Jinx
|
4
|
+
# The KeyTransformerHash class pipes the key access argument into a transformer block before
|
5
|
+
# accessing a base Hashable, e.g.:
|
6
|
+
# hash = KeyTransformerHash.new { |key| key % 2 }
|
7
|
+
# hash[1] = :a
|
8
|
+
# hash[3] #=> :a
|
9
|
+
class KeyTransformerHash
|
10
|
+
include Hashable
|
11
|
+
|
12
|
+
# Creates a KeyTransformerHash on the optional base hash and required key transformer block.
|
13
|
+
#
|
14
|
+
# @param [Hash, nil] base the hash to transform
|
15
|
+
# @yield [key] transforms the base key
|
16
|
+
# @yieldparam key the base key to transform
|
17
|
+
def initialize(base={}, &transformer)
|
18
|
+
@base = base
|
19
|
+
@xfm = transformer
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the value at key after this KeyTransformerHash's transformer block is applied to the key,
|
23
|
+
# or nil if the base hash does not contain an association for the transformed key.
|
24
|
+
def [](key)
|
25
|
+
@base[@xfm.call(key)]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Sets the value at key after this KeyTransformerHash's transformer block is applied, or nil
|
29
|
+
# if this hash does not contain an association for the transformed key.
|
30
|
+
def []=(key, value)
|
31
|
+
@base[@xfm.call(key)] = value
|
32
|
+
end
|
33
|
+
|
34
|
+
# Delegates to the base hash.
|
35
|
+
# Note that this breaks the standard Hash contract, since
|
36
|
+
# all? { |k, v| self[k] }
|
37
|
+
# is not necessarily true because the key is transformed on access.
|
38
|
+
# @see Accessor for a KeyTransformerHash variant that restores this contract
|
39
|
+
def each(&block)
|
40
|
+
@base.each(&block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'jinx/helpers/options'
|
2
|
+
|
3
|
+
module Jinx
|
4
|
+
# A Hash that creates a new entry on demand.
|
5
|
+
class LazyHash < Hash
|
6
|
+
# Creates a new Hash with the specified value factory proc.
|
7
|
+
# The factory proc has one argument, the key.
|
8
|
+
# If access by key fails, then a new association is created
|
9
|
+
# from the key to the result of calling the factory proc.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
# hash = LazyHash.new { |key| key.to_s }
|
13
|
+
# hash[1] = "1"
|
14
|
+
# hash[1] #=> "1"
|
15
|
+
# hash[2] #=> "2"
|
16
|
+
#
|
17
|
+
# If a block is not provided, then the default association value is nil, e.g.:
|
18
|
+
# hash = LazyHash.new
|
19
|
+
# hash.has_key?(1) #=> false
|
20
|
+
# hash[1] #=> nil
|
21
|
+
# hash.has_key?(1) #=> true
|
22
|
+
#
|
23
|
+
# A nil key always returns nil. There is no hash entry for nil, e.g.:
|
24
|
+
# hash = LazyHash.new { |key| key }
|
25
|
+
# hash[nil] #=> nil
|
26
|
+
# hash.has_key?(nil) #=> false
|
27
|
+
#
|
28
|
+
# If the :compact option is set, then an entry is not created
|
29
|
+
# if the value initializer result is nil or empty, e.g.:
|
30
|
+
# hash = LazyHash.new { |n| 10.div(n) unless n.zero? }
|
31
|
+
# hash[0] #=> nil
|
32
|
+
# hash.has_key?(0) #=> false
|
33
|
+
def initialize(options=nil)
|
34
|
+
reject_flag = Options.get(:compact, options)
|
35
|
+
# Make the hash with the factory block
|
36
|
+
super() do |hash, key|
|
37
|
+
if key then
|
38
|
+
value = yield key if block_given?
|
39
|
+
hash[key] = value unless reject_flag and value.nil_or_empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'singleton'
|
3
|
+
require 'ftools'
|
4
|
+
require 'jinx/helpers/collections'
|
5
|
+
require 'jinx/helpers/options'
|
6
|
+
|
7
|
+
# @return [Jinx::MultilineLogger] the global logger
|
8
|
+
def logger
|
9
|
+
Jinx.logger
|
10
|
+
end
|
11
|
+
|
12
|
+
module Jinx
|
13
|
+
# @return (see Log#logger)
|
14
|
+
def self.logger
|
15
|
+
Log.instance.logger
|
16
|
+
end
|
17
|
+
|
18
|
+
# Extends the standard Logger to format multi-line messages on separate lines.
|
19
|
+
class MultilineLogger < ::Logger
|
20
|
+
# @see Logger#initialize
|
21
|
+
def initialize(*args)
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
# Rackify the logger with a write method, in conformance with
|
26
|
+
# the [Rack spec](http://rack.rubyforge.org/doc/SPEC.html).
|
27
|
+
alias :write :<<
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Writes msg to the log device. Each line in msg is formatted separately.
|
32
|
+
#
|
33
|
+
# @param (see Logger#format_message)
|
34
|
+
# @return (see Logger#format_message)
|
35
|
+
def format_message(severity, datetime, progname, msg)
|
36
|
+
if String === msg then
|
37
|
+
msg.inject('') { |s, line| s << super(severity, datetime, progname, line.chomp) }
|
38
|
+
else
|
39
|
+
super
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Wraps a standard global Logger.
|
45
|
+
class Log
|
46
|
+
include Singleton
|
47
|
+
|
48
|
+
# Opens the log.
|
49
|
+
#
|
50
|
+
# @param [String, IO, nil] dev the log file or device (default STDOUT)
|
51
|
+
# @param [Hash, nil] opts the logger options
|
52
|
+
# @option opts [Integer] :shift_age the number of log files retained in the rotation
|
53
|
+
# @option opts [Integer] :shift_size the maximum size of each log file
|
54
|
+
# @option opts [Boolean] :debug whether to include debug messages in the log file
|
55
|
+
# @return [MultilineLogger] the global logger
|
56
|
+
def open(dev=nil, opts=nil)
|
57
|
+
raise RuntimeError.new("Log already open") if open?
|
58
|
+
if String === dev then File.makedirs(File.dirname(dev)) end
|
59
|
+
# default is 4-file rotation @ 16MB each
|
60
|
+
shift_age = Options.get(:shift_age, opts, 4)
|
61
|
+
shift_size = Options.get(:shift_size, opts, 16 * 1048576)
|
62
|
+
@logger = MultilineLogger.new(dev, shift_age, shift_size)
|
63
|
+
@logger.level = Options.get(:debug, opts) ? Logger::DEBUG : Logger::INFO
|
64
|
+
@logger.formatter = lambda do |severity, time, progname, msg|
|
65
|
+
FORMAT % [
|
66
|
+
progname || 'I',
|
67
|
+
DateTime.now.strftime("%d/%b/%Y %H:%M:%S"),
|
68
|
+
severity,
|
69
|
+
msg]
|
70
|
+
end
|
71
|
+
@dev = dev
|
72
|
+
@logger
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Boolean] whether the logger is open
|
76
|
+
def open?
|
77
|
+
!!@logger
|
78
|
+
end
|
79
|
+
|
80
|
+
# Closes and releases the {#logger}.
|
81
|
+
def close
|
82
|
+
@logger.close
|
83
|
+
@logger = nil
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return (see #open)
|
87
|
+
def logger
|
88
|
+
@logger ||= open
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [String, nil] the log file, or nil if the log was opened on an IO rather
|
92
|
+
# than a String
|
93
|
+
def file
|
94
|
+
@dev if String === @dev
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
# Stream-lined log format.
|
100
|
+
FORMAT = %{%s [%s] %5s %s\n}
|
101
|
+
|
102
|
+
def same_file?(f1, f2)
|
103
|
+
f1 == f2 or (String === f2 and String === f1 and File.expand_path(f1) == File.expand_path(f2))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Extends the Numeric class with max and min methods.
|
2
|
+
class Numeric
|
3
|
+
# Returns the minimum of this Numeric and the other Numerics.
|
4
|
+
def min(*others)
|
5
|
+
others.inject(self) { |min, other| other < min ? other : min }
|
6
|
+
end
|
7
|
+
|
8
|
+
# Returns the minimum of this Numeric and the other Numerics.
|
9
|
+
def max(*others)
|
10
|
+
others.inject(self) { |max, other| other > max ? other : max }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'jinx/helpers/options'
|
2
|
+
require 'jinx/helpers/collections'
|
3
|
+
|
4
|
+
|
5
|
+
class Hash
|
6
|
+
# Returns a new hash which merges the other hash with this hash.
|
7
|
+
#
|
8
|
+
# Supported options include the following:
|
9
|
+
# * :deep - merge values which match on the key.
|
10
|
+
# If the :deep option is set, and a key matches both this hash and the other hash
|
11
|
+
# on hash values, then the other hash's value is recursively merged into this Hash's
|
12
|
+
# value using the non-destructive {#merge} method with the deep option set.
|
13
|
+
# If a block is given to this method, then the block is passed to the value merge.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# {:a => [1], :b => [2]}.merge({:b => [3]}, :deep) #=> {:a => [1], :b => [2, 3]}
|
17
|
+
# {:a => {:b => [1]}}.merge({:a => {:b => [2]}, :c => 3}, :deep) #=> {:a => {:b => [1, 2]}, :c => 3}
|
18
|
+
def merge(other, options=nil, &block)
|
19
|
+
dup.merge!(other, options, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
alias :base__merge! :merge!
|
23
|
+
private :base__merge!
|
24
|
+
|
25
|
+
# Merges the other hash into this hash and returns this modified hash.
|
26
|
+
#
|
27
|
+
# @see #merge the options and block description
|
28
|
+
def merge!(other, options=nil, &block)
|
29
|
+
# use the standard Hash merge unless the :deep option is set
|
30
|
+
return base__merge!(other, &block) unless Options.get(:deep, options)
|
31
|
+
# merge the other entries:
|
32
|
+
# if the hash value is a hash, then call merge on that hash value.
|
33
|
+
# otherwise, if the hash value understands merge, then call that method.
|
34
|
+
# otherwise, if there is a block, then call the block.
|
35
|
+
# otherwise, set the the hash value to the other value.
|
36
|
+
base__merge!(other) do |key, oldval, newval|
|
37
|
+
if Hash === oldval then
|
38
|
+
oldval.merge(newval, options, &block)
|
39
|
+
elsif oldval.respond_to?(:merge)
|
40
|
+
oldval.merge(newval, &block)
|
41
|
+
elsif block_given? then
|
42
|
+
yield(key, oldval, newval)
|
43
|
+
else
|
44
|
+
newval
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Array
|
51
|
+
# Adds the elements in the other Enumerable which are not already included in this Array.
|
52
|
+
# Returns this modified Array.
|
53
|
+
def merge(other)
|
54
|
+
# incompatible merge argument is allowed but ignored
|
55
|
+
self unless Enumerable === other
|
56
|
+
# concatenate the members of other not in self
|
57
|
+
unique = other.to_a - self
|
58
|
+
concat(unique)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Module
|
2
|
+
# Returns the class or module with the given name defined in this module.
|
3
|
+
# The name can qualified by parent modules, e.g. +MyApp::Person+.
|
4
|
+
# If name cannot be resolved as a Module, then this method returns nil.
|
5
|
+
#
|
6
|
+
# @param [String] the class name
|
7
|
+
# @return [Module, nil] the class or module defined in this module, or nil if none
|
8
|
+
def module_with_name(name)
|
9
|
+
name.split('::').inject(self) { |parent, part| parent.const_get(part) } rescue nil
|
10
|
+
end
|
11
|
+
|
12
|
+
# @example
|
13
|
+
# A::B.parent_module #=> A
|
14
|
+
# @return [Module] this module's definition context
|
15
|
+
def parent_module
|
16
|
+
Kernel.module_with_name(name.split('::')[0..-2].join('::'))
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Jinx
|
2
|
+
# A MultiEnumerator iterates over several Enumerators in sequence. Unlike Array#+, MultiEnumerator reflects changes to the
|
3
|
+
# underlying enumerators.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# a = [1, 2]
|
7
|
+
# b = [4, 5]
|
8
|
+
# ab = MultiEnumerator.new(a, b)
|
9
|
+
# ab.to_a #=> [1, 2, 4, 5]
|
10
|
+
# a << 3; b << 6; ab.to_a #=> [1, 2, 3, 4, 5, 6]
|
11
|
+
class MultiEnumerator
|
12
|
+
include Collection
|
13
|
+
|
14
|
+
# @return [<Enumerable>] the enumerated collections
|
15
|
+
attr_reader :components
|
16
|
+
|
17
|
+
# Initializes a new {MultiEnumerator} on the given components.
|
18
|
+
#
|
19
|
+
# @param [<Enumerable>] the component enumerators to compose
|
20
|
+
def initialize(*enums)
|
21
|
+
super()
|
22
|
+
@components = enums
|
23
|
+
@components.compact!
|
24
|
+
end
|
25
|
+
|
26
|
+
# Iterates over each of this MultiEnumerator's Enumerators in sequence.
|
27
|
+
def each
|
28
|
+
@components.each { |enum| enum.each { |item| yield item } }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'jinx/helpers/collections'
|
2
|
+
|
3
|
+
require 'jinx/helpers/validation'
|
4
|
+
require 'jinx/helpers/merge'
|
5
|
+
|
6
|
+
# Options is a utility class to support method options.
|
7
|
+
class Options
|
8
|
+
# Returns the value of option in options as follows:
|
9
|
+
# * If options is a hash which contains the option key, then this method returns
|
10
|
+
# the option value. A non-collection options[option] value is wrapped as a singleton
|
11
|
+
# collection to conform to a collection default type, as shown in the example below.
|
12
|
+
# * If options equals the option symbol, then this method returns +true+.
|
13
|
+
# * If options is an Array of symbols which includes the given option, then this method
|
14
|
+
# returns +true+.
|
15
|
+
# * Otherwise, this method returns the default.
|
16
|
+
#
|
17
|
+
# If default is nil and a block is given to this method, then the default is determined
|
18
|
+
# by calling the block with no arguments. The block can also be used to raise a missing
|
19
|
+
# option exception, e.g.:
|
20
|
+
# Options.get(:userid, options) { Jinx.fail(RuntimeError, "Missing required option: userid") }
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# Options.get(:create, {:create => true}) #=> true
|
24
|
+
# Options.get(:create, :create) #=> true
|
25
|
+
# Options.get(:create, [:create, :compress]) #=> true
|
26
|
+
# Options.get(:create, nil) #=> nil
|
27
|
+
# Options.get(:create, nil, :false) #=> false
|
28
|
+
# Options.get(:create, nil, :true) #=> true
|
29
|
+
# Options.get(:values, nil, []) #=> []
|
30
|
+
# Options.get(:values, {:values => :a}, []) #=> [:a]
|
31
|
+
# Options.get(:values, [:create, {:values => :a}], []) #=> [:a]
|
32
|
+
def self.get(option, options, default=nil, &block)
|
33
|
+
return default(default, &block) if options.nil?
|
34
|
+
case options
|
35
|
+
when Hash then
|
36
|
+
value = options[option]
|
37
|
+
if String === value then value.strip! end
|
38
|
+
value.nil_or_empty? ? default(default, &block) : value
|
39
|
+
when Enumerable then
|
40
|
+
detect_in_enumerable(option, options) or default(default, &block)
|
41
|
+
when Symbol then
|
42
|
+
option == options or default(default, &block)
|
43
|
+
else
|
44
|
+
Jinx.fail(ArgumentError, "Options argument type is not supported; expected Hash or Symbol, found: #{options.class}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the given option list as a hash, determined as follows:
|
49
|
+
# * If an item is a hash, then that hash is included in the result
|
50
|
+
# * If an item is a symbol _s_, then {_s_ => true} is included in the result
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# Options.to_hash() #=> {}
|
54
|
+
# Options.to_hash(nil) #=> {}
|
55
|
+
# Options.to_hash(:a => 1) #=> {:a => 1}
|
56
|
+
# Options.to_hash(:a) #=> {:a => true}
|
57
|
+
# Options.to_hash(:a, :b => 2) #=> {:a => true, :b => 2}
|
58
|
+
# @param [<[Symbol, Hash]>] opts the option list
|
59
|
+
# @return [Hash] the option hash
|
60
|
+
def self.to_hash(*opts)
|
61
|
+
hash = {}
|
62
|
+
opts.compact!
|
63
|
+
opts.each do |opt|
|
64
|
+
case opt
|
65
|
+
when Symbol then hash[opt] = true
|
66
|
+
when Hash then hash.merge!(opt)
|
67
|
+
else Jinx.fail(ArgumentError, "Expected a symbol or hash option, found #{opt.qp}")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
hash
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param [Hash, Symbol, nil] opts the options to validate
|
74
|
+
# @raise [ValidationError] if the given options are not in the given allowable choices
|
75
|
+
def self.validate(options, choices)
|
76
|
+
to_hash(options).each_key do |opt|
|
77
|
+
Jinx.fail(ValidationError, "Option is not supported: #{opt}") unless choices.include?(opt)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def self.detect_in_enumerable(key, options)
|
84
|
+
options.detect_value do |opt|
|
85
|
+
Hash === opt ? opt[key] : opt == key
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.default(value)
|
90
|
+
value.nil? && block_given? ? yield : value
|
91
|
+
end
|
92
|
+
end
|