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,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
|